From 98922b7a87f6d18bc0d63bc2fd9db092ee4b8318 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 11 May 2016 23:05:24 -0700 Subject: [PATCH 01/90] GB: Improve accuracy of video timings --- src/gb/video.c | 4 ++-- src/gb/video.h | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index 9aae94110..36ab36d08 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -179,11 +179,11 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->dotCounter = 0; video->nextEvent = GB_VIDEO_HORIZONTAL_LENGTH; video->x = 0; - video->nextMode = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 8; + video->nextMode = GB_VIDEO_MODE_3_LENGTH_BASE + video->objMax * 12; video->mode = 3; break; case 3: - video->nextMode = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 8; + video->nextMode = GB_VIDEO_MODE_0_LENGTH_BASE - video->objMax * 12; video->mode = 0; if (GBRegisterSTATIsHblankIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); diff --git a/src/gb/video.h b/src/gb/video.h index 2c8da3c6b..bf5c97401 100644 --- a/src/gb/video.h +++ b/src/gb/video.h @@ -19,9 +19,9 @@ enum { GB_VIDEO_VERTICAL_TOTAL_PIXELS = GB_VIDEO_VERTICAL_PIXELS + GB_VIDEO_VBLANK_PIXELS, // TODO: Figure out exact lengths - GB_VIDEO_MODE_2_LENGTH = 85, - GB_VIDEO_MODE_3_LENGTH_BASE = 167, - GB_VIDEO_MODE_0_LENGTH_BASE = 204, + GB_VIDEO_MODE_2_LENGTH = 80, + GB_VIDEO_MODE_3_LENGTH_BASE = 176, + GB_VIDEO_MODE_0_LENGTH_BASE = 200, GB_VIDEO_HORIZONTAL_LENGTH = GB_VIDEO_MODE_0_LENGTH_BASE + GB_VIDEO_MODE_2_LENGTH + GB_VIDEO_MODE_3_LENGTH_BASE, From 4375e7029f0c872b5686ff43d3b55063c301e939 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 11 May 2016 23:09:22 -0700 Subject: [PATCH 02/90] SDL: Fix SDL 1.2 build --- CHANGES | 1 + src/platform/sdl/sdl-events.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 131e37e75..866b6125c 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ Bugfixes: - GBA Memory: Fix executing code from OBJ region of VRAM - Util: Fix socket bind addresses - All: Fix instruction tables getting zeroed when linking sometimes + - SDL: Fix SDL 1.2 build Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index 9a5d9fb28..bc3365c38 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -68,10 +68,12 @@ bool mSDLInitEvents(struct mSDLEvents* context) { for (i = 0; i < nJoysticks; ++i) { struct SDL_JoystickCombo* joystick = SDL_JoystickListAppend(&context->joysticks); joystick->joystick = SDL_JoystickOpen(i); - joystick->id = SDL_JoystickInstanceID(joystick->joystick); joystick->index = SDL_JoystickListSize(&context->joysticks) - 1; #if SDL_VERSION_ATLEAST(2, 0, 0) + joystick->id = SDL_JoystickInstanceID(joystick->joystick); joystick->haptic = SDL_HapticOpenFromJoystick(joystick->joystick); +#else + joystick->id = SDL_JoystickIndex(joystick->joystick); #endif } } From 2d1ad16e1c7b20b8de2ed1d79b0397ef5e4b45c9 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 11 May 2016 23:10:40 -0700 Subject: [PATCH 03/90] ARM7: Improve decoder for memory access --- src/arm/decoder-thumb.c | 48 ++++++++++++++++++++--------------------- src/arm/decoder.h | 1 + 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/arm/decoder-thumb.c b/src/arm/decoder-thumb.c index da0b156cd..bfd8fc942 100644 --- a/src/arm/decoder-thumb.c +++ b/src/arm/decoder-thumb.c @@ -27,14 +27,14 @@ ARM_OPERAND_REGISTER_2 | \ ARM_OPERAND_IMMEDIATE_3;) -#define DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, MNEMONIC, CYCLES, WIDTH) \ +#define DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(NAME, MNEMONIC, CYCLES, WIDTH, AFFECTED) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ info->op1.reg = opcode & 0x0007; \ info->memory.baseReg = (opcode >> 3) & 0x0007; \ info->memory.offset.immediate = ((opcode >> 6) & 0x001F) * WIDTH; \ info->memory.width = (enum ARMMemoryAccessType) WIDTH; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ - ARM_OPERAND_AFFECTED_1 | \ + ARM_OPERAND_AFFECTED_ ## AFFECTED | \ ARM_OPERAND_MEMORY_2; \ info->memory.format = ARM_MEMORY_REGISTER_BASE | \ ARM_MEMORY_IMMEDIATE_OFFSET; \ @@ -43,12 +43,12 @@ DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(LSL1, LSL) DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(LSR1, LSR) DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(ASR1, ASR) -DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDR1, LDR, LOAD_CYCLES, 4) -DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRB1, LDR, LOAD_CYCLES, 1) -DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRH1, LDR, LOAD_CYCLES, 2) -DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STR1, STR, STORE_CYCLES, 4) -DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRB1, STR, STORE_CYCLES, 1) -DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRH1, STR, STORE_CYCLES, 2) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDR1, LDR, LOAD_CYCLES, 4, 1) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRB1, LDR, LOAD_CYCLES, 1, 1) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(LDRH1, LDR, LOAD_CYCLES, 2, 1) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STR1, STR, STORE_CYCLES, 4, 2) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRB1, STR, STORE_CYCLES, 1, 2) +DEFINE_IMMEDIATE_5_DECODER_MEM_THUMB(STRH1, STR, STORE_CYCLES, 2, 2) #define DEFINE_DATA_FORM_1_DECODER_THUMB(NAME, MNEMONIC) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ @@ -151,47 +151,47 @@ DEFINE_DECODER_WITH_HIGH_THUMB(MOV3, MOV, ARM_OPERAND_AFFECTED_1, 0) ARM_OPERAND_REGISTER_2 | \ ARM_OPERAND_IMMEDIATE_3;) -#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES) \ +#define DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(NAME, MNEMONIC, REG, CYCLES, AFFECTED) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ info->op1.reg = (opcode >> 8) & 0x0007; \ info->memory.baseReg = REG; \ info->memory.offset.immediate = (opcode & 0x00FF) << 2; \ info->memory.width = ARM_ACCESS_WORD; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ - ARM_OPERAND_AFFECTED_1 | \ + ARM_OPERAND_AFFECTED_ ## AFFECTED | \ ARM_OPERAND_MEMORY_2; \ info->memory.format = ARM_MEMORY_REGISTER_BASE | \ ARM_MEMORY_IMMEDIATE_OFFSET; \ CYCLES;) -DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR3, LDR, ARM_PC, LOAD_CYCLES) -DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR4, LDR, ARM_SP, LOAD_CYCLES) -DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(STR3, STR, ARM_SP, STORE_CYCLES) +DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR3, LDR, ARM_PC, LOAD_CYCLES, 1) +DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(LDR4, LDR, ARM_SP, LOAD_CYCLES, 1) +DEFINE_IMMEDIATE_WITH_REGISTER_MEM_THUMB(STR3, STR, ARM_SP, STORE_CYCLES, 2) DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD5, ADD, ARM_PC) DEFINE_IMMEDIATE_WITH_REGISTER_DATA_THUMB(ADD6, ADD, ARM_SP) -#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, MNEMONIC, CYCLES, TYPE) \ +#define DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(NAME, MNEMONIC, CYCLES, TYPE, AFFECTED) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ info->memory.offset.reg = (opcode >> 6) & 0x0007; \ info->op1.reg = opcode & 0x0007; \ info->memory.baseReg = (opcode >> 3) & 0x0007; \ info->memory.width = TYPE; \ info->operandFormat = ARM_OPERAND_REGISTER_1 | \ - ARM_OPERAND_AFFECTED_1 | /* TODO: Remove this for STR */ \ + ARM_OPERAND_AFFECTED_ ## AFFECTED | \ ARM_OPERAND_MEMORY_2; \ info->memory.format = ARM_MEMORY_REGISTER_BASE | \ ARM_MEMORY_REGISTER_OFFSET; \ CYCLES;) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, LDR, LOAD_CYCLES, ARM_ACCESS_WORD) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, LDR, LOAD_CYCLES, ARM_ACCESS_BYTE) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, LDR, LOAD_CYCLES, ARM_ACCESS_HALFWORD) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_BYTE) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_HALFWORD) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, STR, STORE_CYCLES, ARM_ACCESS_WORD) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, STR, STORE_CYCLES, ARM_ACCESS_BYTE) -DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFWORD) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDR2, LDR, LOAD_CYCLES, ARM_ACCESS_WORD, 1) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRB2, LDR, LOAD_CYCLES, ARM_ACCESS_BYTE, 1) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRH2, LDR, LOAD_CYCLES, ARM_ACCESS_HALFWORD, 1) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSB, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_BYTE, 1) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(LDRSH, LDR, LOAD_CYCLES, ARM_ACCESS_SIGNED_HALFWORD, 1) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STR2, STR, STORE_CYCLES, ARM_ACCESS_WORD, 2) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRB2, STR, STORE_CYCLES, ARM_ACCESS_BYTE, 2) +DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFWORD, 2) // TODO: Estimate memory cycles #define DEFINE_LOAD_STORE_MULTIPLE_EX_THUMB(NAME, RN, MNEMONIC, DIRECTION, ADDITIONAL_REG) \ @@ -201,7 +201,7 @@ DEFINE_LOAD_STORE_WITH_REGISTER_THUMB(STRH2, STR, STORE_CYCLES, ARM_ACCESS_HALFW if (info->op1.immediate & (1 << ARM_PC)) { \ info->branchType = ARM_BRANCH_INDIRECT; \ } \ - info->operandFormat = ARM_OPERAND_MEMORY_1; \ + info->operandFormat = ARM_OPERAND_MEMORY_1 | ARM_OPERAND_AFFECTED_1; \ info->memory.format = ARM_MEMORY_REGISTER_BASE | \ ARM_MEMORY_WRITEBACK | \ DIRECTION;) diff --git a/src/arm/decoder.h b/src/arm/decoder.h index a1dd59dbc..6ca478046 100644 --- a/src/arm/decoder.h +++ b/src/arm/decoder.h @@ -47,6 +47,7 @@ #define ARM_OPERAND_SHIFT_IMMEDIATE_4 0x20000000 #define ARM_OPERAND_4 0xFF000000 +#define ARM_OPERAND_MEMORY (ARM_OPERAND_MEMORY_1 | ARM_OPERAND_MEMORY_2 | ARM_OPERAND_MEMORY_3 | ARM_OPERAND_MEMORY_4) #define ARM_MEMORY_REGISTER_BASE 0x0001 #define ARM_MEMORY_IMMEDIATE_OFFSET 0x0002 From b37761327ed545da997934c18630ff80d68dc71c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 12 May 2016 00:18:08 -0700 Subject: [PATCH 04/90] GBA Core: Fix missing include --- src/gba/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gba/core.c b/src/gba/core.c index a2aec489f..4eef3da65 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -7,6 +7,7 @@ #include "core/core.h" #include "core/log.h" +#include "arm/debugger/debugger.h" #include "gba/cheats.h" #include "gba/gba.h" #include "gba/extra/cli.h" From b5ff48a74e73fcf0dc3d555ebcd39e5250bb9646 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 12 May 2016 00:18:49 -0700 Subject: [PATCH 05/90] ARM7: Support forcing Thumb mode via MSR --- CHANGES | 1 + src/arm/isa-arm.c | 3 +++ src/arm/isa-inlines.h | 2 ++ 3 files changed, 6 insertions(+) diff --git a/CHANGES b/CHANGES index 866b6125c..35e65d7d4 100644 --- a/CHANGES +++ b/CHANGES @@ -39,6 +39,7 @@ Misc: - All: Add QUIET parameter to silence CMake - GBA Video: Null renderer should return proper register values - Libretro: Disable logging game errors, BIOS calls and stubs in release builds + - ARM7: Support forcing Thumb mode via MSR 0.4.0: (2016-02-02) Features: diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index 518ab5134..bf826f44b 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -629,6 +629,9 @@ DEFINE_INSTRUCTION_ARM(MSR, if (mask & PSR_USER_MASK) { cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK); } + if (mask & PSR_STATE_MASK) { + cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_STATE_MASK) | (operand & PSR_STATE_MASK); + } if (cpu->privilegeMode != MODE_USER && (mask & PSR_PRIV_MASK)) { ARMSetPrivilegeMode(cpu, (enum PrivilegeMode) ((operand & 0x0000000F) | 0x00000010)); cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_PRIV_MASK) | (operand & PSR_PRIV_MASK); diff --git a/src/arm/isa-inlines.h b/src/arm/isa-inlines.h index 42a581b4a..2e7395a00 100644 --- a/src/arm/isa-inlines.h +++ b/src/arm/isa-inlines.h @@ -85,6 +85,8 @@ static inline void _ARMSetMode(struct ARMCore* cpu, enum ExecutionMode execution break; case MODE_THUMB: cpu->cpsr.t = 1; + cpu->prefetch[0] &= 0xFFFF; + cpu->prefetch[1] &= 0xFFFF; } cpu->nextEvent = cpu->cycles; } From e81de71f50f1dc7339bb76cda4cd620ce95f1d9a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 16 May 2016 01:18:09 -0700 Subject: [PATCH 06/90] ARM7: Flush prefetch cache when loading CPSR via MSR --- CHANGES | 1 + src/arm/isa-arm.c | 21 +++++++++++++++++++-- src/arm/isa-inlines.h | 2 -- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 35e65d7d4..6639130c7 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,7 @@ Misc: - GBA Video: Null renderer should return proper register values - Libretro: Disable logging game errors, BIOS calls and stubs in release builds - ARM7: Support forcing Thumb mode via MSR + - ARM7: Flush prefetch cache when loading CPSR via MSR 0.4.0: (2016-02-02) Features: diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index bf826f44b..bce24c608 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -636,7 +636,14 @@ DEFINE_INSTRUCTION_ARM(MSR, ARMSetPrivilegeMode(cpu, (enum PrivilegeMode) ((operand & 0x0000000F) | 0x00000010)); cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_PRIV_MASK) | (operand & PSR_PRIV_MASK); } - _ARMReadCPSR(cpu);) + _ARMReadCPSR(cpu); + if (cpu->executionMode == MODE_THUMB) { + LOAD_16(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & cpu->memory.activeMask, cpu->memory.activeRegion); + LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); + } else { + LOAD_32(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & cpu->memory.activeMask, cpu->memory.activeRegion); + LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); + }) DEFINE_INSTRUCTION_ARM(MSRR, int c = opcode & 0x00010000; @@ -663,11 +670,21 @@ DEFINE_INSTRUCTION_ARM(MSRI, if (mask & PSR_USER_MASK) { cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK); } + if (mask & PSR_STATE_MASK) { + cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_STATE_MASK) | (operand & PSR_STATE_MASK); + } if (cpu->privilegeMode != MODE_USER && (mask & PSR_PRIV_MASK)) { ARMSetPrivilegeMode(cpu, (enum PrivilegeMode) ((operand & 0x0000000F) | 0x00000010)); cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_PRIV_MASK) | (operand & PSR_PRIV_MASK); } - _ARMReadCPSR(cpu);) + _ARMReadCPSR(cpu); + if (cpu->executionMode == MODE_THUMB) { + LOAD_16(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_THUMB) & cpu->memory.activeMask, cpu->memory.activeRegion); + LOAD_16(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); + } else { + LOAD_32(cpu->prefetch[0], (cpu->gprs[ARM_PC] - WORD_SIZE_ARM) & cpu->memory.activeMask, cpu->memory.activeRegion); + LOAD_32(cpu->prefetch[1], cpu->gprs[ARM_PC] & cpu->memory.activeMask, cpu->memory.activeRegion); + }) DEFINE_INSTRUCTION_ARM(MSRRI, int c = opcode & 0x00010000; diff --git a/src/arm/isa-inlines.h b/src/arm/isa-inlines.h index 2e7395a00..42a581b4a 100644 --- a/src/arm/isa-inlines.h +++ b/src/arm/isa-inlines.h @@ -85,8 +85,6 @@ static inline void _ARMSetMode(struct ARMCore* cpu, enum ExecutionMode execution break; case MODE_THUMB: cpu->cpsr.t = 1; - cpu->prefetch[0] &= 0xFFFF; - cpu->prefetch[1] &= 0xFFFF; } cpu->nextEvent = cpu->cycles; } From 9569ccb6349d85b82a9e6ba283d5c9fca3f7d453 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 17 May 2016 00:17:01 -0700 Subject: [PATCH 07/90] GBA: Fix warning about too old version of a savestate --- src/gba/serialize.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gba/serialize.c b/src/gba/serialize.c index f5717e1b5..fe1fbba57 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -118,7 +118,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { } else if (ucheck < GBA_SAVESTATE_MAGIC) { mLOG(GBA_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck); error = true; - } else { + } else if (ucheck < GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION) { mLOG(GBA_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GBA_SAVESTATE_MAGIC + GBA_SAVESTATE_VERSION, ucheck); } LOAD_32(ucheck, 0, &state->biosChecksum); From 2f33cd70f601c06809c34ee58bf4ca964cec7318 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 17 May 2016 00:19:39 -0700 Subject: [PATCH 08/90] GBA: Fix PSG audio serialization --- src/gba/serialize.h | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/gba/serialize.h b/src/gba/serialize.h index 225a77fbf..6c387b34f 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -70,21 +70,23 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * | TODO: Fix this, they're in big-endian order, but field is little-endian * | 0x001DC - 0x001DC: Channel 1 envelope state * | bits 0 - 3: Current volume - * | bit 4: Is dead? - * | bit 5: Is high? - * | bit 6: Is sweep enabled? - * | bit 7: Has sweep occurred? + * | bits 4 - 5: Is dead? + * | bit 6: Is high? * | 0x001DD - 0x001DD: Channel 2 envelope state * | bits 0 - 3: Current volume - * | bit 4: Is dead? - * | bit 5: Is high? - * | bits 6 - 7: Reserved + * | bits 4 - 5: Is dead? + * | bit 6: Is high? +* | bits 7: Reserved * | 0x001DE - 0x001DE: Channel 4 envelope state * | bits 0 - 3: Current volume - * | bit 4: Is dead? - * | bits 5 - 7: Reserved + * | bits 4 - 5: Is dead? + * | bit 6: Is high? +* | bits 7: Reserved * | 0x001DF - 0x001DF: Miscellaneous audio flags * | bits 0 - 3: Current frame + * | bit 4: Is channel 1 sweep enabled? + * | bit 5: Has channel 1 sweep occurred? + * | bits 6 - 7: Reserved * 0x001E0 - 0x001FF: Video miscellaneous state * | 0x001E0 - 0x001E3: Next event * | 0x001E4 - 0x001E7: Event diff @@ -204,16 +206,16 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); DECL_BITFIELD(GBASerializedAudioFlags, uint32_t); DECL_BITS(GBASerializedAudioFlags, Ch1Volume, 0, 4); -DECL_BIT(GBASerializedAudioFlags, Ch1Dead, 4); -DECL_BIT(GBASerializedAudioFlags, Ch1Hi, 5); -DECL_BIT(GBASerializedAudioFlags, Ch1SweepEnabled, 6); -DECL_BIT(GBASerializedAudioFlags, Ch1SweepOccurred, 7); +DECL_BITS(GBASerializedAudioFlags, Ch1Dead, 4, 2); +DECL_BIT(GBASerializedAudioFlags, Ch1Hi, 6); DECL_BITS(GBASerializedAudioFlags, Ch2Volume, 8, 4); -DECL_BIT(GBASerializedAudioFlags, Ch2Dead, 12); -DECL_BIT(GBASerializedAudioFlags, Ch2Hi, 13); +DECL_BITS(GBASerializedAudioFlags, Ch2Dead, 12, 2); +DECL_BIT(GBASerializedAudioFlags, Ch2Hi, 14); DECL_BITS(GBASerializedAudioFlags, Ch4Volume, 16, 4); -DECL_BIT(GBASerializedAudioFlags, Ch4Dead, 20); -DECL_BITS(GBASerializedAudioFlags, Frame, 21, 3); +DECL_BITS(GBASerializedAudioFlags, Ch4Dead, 20, 2); +DECL_BITS(GBASerializedAudioFlags, Frame, 22, 3); +DECL_BIT(GBASerializedAudioFlags, Ch1SweepEnabled, 25); +DECL_BIT(GBASerializedAudioFlags, Ch1SweepOccurred, 26); DECL_BITFIELD(GBASerializedAudioEnvelope, uint32_t); DECL_BITS(GBASerializedAudioEnvelope, Length, 0, 7); From eb4b53a7c4dc292e71e2796c19cdb59b828367b6 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 19 May 2016 22:31:13 -0700 Subject: [PATCH 09/90] GB: Initial BIOS support --- src/gb/core.c | 17 +++++++--- src/gb/gb.c | 88 ++++++++++++++++++++++++++++++++++++++----------- src/gb/gb.h | 3 ++ src/gb/io.c | 6 ++++ src/gb/memory.c | 4 +-- src/gb/memory.h | 1 + 6 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/gb/core.c b/src/gb/core.c index 0ca70be10..d693eeb0f 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -13,6 +13,7 @@ #include "lr35902/debugger/debugger.h" #include "util/memory.h" #include "util/patch.h" +#include "util/vfs.h" struct GBCore { struct mCore d; @@ -93,6 +94,16 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf gb->audio.masterVolume = core->opts.volume; } gb->video.frameskip = core->opts.frameskip; + +#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 + struct VFile* bios = 0; + if (core->opts.useBios && core->opts.bios) { + bios = VFileOpen(core->opts.bios, O_RDONLY); + } + if (bios) { + GBLoadBIOS(gb, bios); + } +#endif } static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) { @@ -148,11 +159,9 @@ static bool _GBCoreLoadROM(struct mCore* core, struct VFile* vf) { } static bool _GBCoreLoadBIOS(struct mCore* core, struct VFile* vf, int type) { - UNUSED(core); - UNUSED(vf); UNUSED(type); - // TODO - return false; + GBLoadBIOS(core->board, vf); + return true; } static bool _GBCoreLoadSave(struct mCore* core, struct VFile* vf) { diff --git a/src/gb/gb.c b/src/gb/gb.c index abd3f7e84..c14051a52 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -88,6 +88,7 @@ bool GBLoadROM(struct GB* gb, struct VFile* vf) { } gb->yankedRomSize = 0; gb->memory.rom = gb->pristineRom; + gb->memory.romBase = gb->memory.rom; gb->memory.romSize = gb->pristineRomSize; gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize); @@ -109,6 +110,9 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf) { void GBUnloadROM(struct GB* gb) { // TODO: Share with GBAUnloadROM + if (gb->memory.rom && gb->memory.romBase != gb->memory.rom) { + free(gb->memory.romBase); + } if (gb->memory.rom && gb->pristineRom != gb->memory.rom) { if (gb->yankedRomSize) { gb->yankedRomSize = 0; @@ -135,6 +139,10 @@ void GBUnloadROM(struct GB* gb) { gb->memory.sram = 0; } +void GBLoadBIOS(struct GB* gb, struct VFile* vf) { + gb->biosVf = vf; +} + void GBApplyPatch(struct GB* gb, struct Patch* patch) { size_t patchedSize = patch->outputSize(patch, gb->memory.romSize); if (!patchedSize) { @@ -171,31 +179,71 @@ void GBInterruptHandlerInit(struct LR35902InterruptHandler* irqh) { void GBReset(struct LR35902Core* cpu) { struct GB* gb = (struct GB*) cpu->master; - const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100]; - if (cart->cgb & 0x80) { - gb->model = GB_MODEL_CGB; - gb->audio.style = GB_AUDIO_CGB; - cpu->a = 0x11; - cpu->f.packed = 0x80; + if (gb->biosVf) { + gb->biosVf->seek(gb->biosVf, 0, SEEK_SET); + gb->memory.romBase = malloc(GB_SIZE_CART_BANK0); + ssize_t size = gb->biosVf->read(gb->biosVf, gb->memory.romBase, GB_SIZE_CART_BANK0); + uint32_t biosCrc = doCrc32(gb->memory.romBase, size); + switch (biosCrc) { + case 0x59C8598E: + gb->model = GB_MODEL_DMG; + gb->audio.style = GB_AUDIO_DMG; + break; + case 0x41884E46: + gb->model = GB_MODEL_CGB; + gb->audio.style = GB_AUDIO_CGB; + break; + default: + free(gb->memory.romBase); + gb->memory.romBase = gb->memory.rom; + gb->biosVf = NULL; + break; + } + + memcpy(&gb->memory.romBase[size], &gb->memory.rom[size], GB_SIZE_CART_BANK0 - size); + if (size > 0x100) { + memcpy(&gb->memory.romBase[0x100], &gb->memory.rom[0x100], sizeof(struct GBCartridge)); + } + + cpu->a = 0; + cpu->f.packed = 0; cpu->c = 0; - cpu->e = 0x08; + cpu->e = 0; cpu->h = 0; - cpu->l = 0x7C; - } else { - // TODO: SGB - gb->model = GB_MODEL_DMG; - gb->audio.style = GB_AUDIO_DMG; - cpu->a = 1; - cpu->f.packed = 0xB0; - cpu->c = 0x13; - cpu->e = 0xD8; - cpu->h = 1; - cpu->l = 0x4D; + cpu->l = 0; + cpu->sp = 0; + cpu->pc = 0; } + if (!gb->biosVf) { + const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100]; + if (cart->cgb & 0x80) { + gb->model = GB_MODEL_CGB; + gb->audio.style = GB_AUDIO_CGB; + cpu->a = 0x11; + cpu->f.packed = 0x80; + cpu->c = 0; + cpu->e = 0x08; + cpu->h = 0; + cpu->l = 0x7C; + } else { + // TODO: SGB + gb->model = GB_MODEL_DMG; + gb->audio.style = GB_AUDIO_DMG; + cpu->a = 1; + cpu->f.packed = 0xB0; + cpu->c = 0x13; + cpu->e = 0xD8; + cpu->h = 1; + cpu->l = 0x4D; + } + + cpu->sp = 0xFFFE; + cpu->pc = 0x100; + } + cpu->b = 0; cpu->d = 0; - cpu->sp = 0xFFFE; - cpu->pc = 0x100; + cpu->memory.setActiveRegion(cpu, cpu->pc); if (gb->yankedRomSize) { diff --git a/src/gb/gb.h b/src/gb/gb.h index e7c5ccd8a..f803a68a5 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -62,6 +62,7 @@ struct GB { size_t yankedRomSize; uint32_t romCrc32; struct VFile* romVf; + struct VFile* biosVf; struct VFile* sramVf; struct mAVStream* stream; @@ -108,6 +109,8 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf); void GBYankROM(struct GB* gb); void GBUnloadROM(struct GB* gb); +void GBLoadBIOS(struct GB* gb, struct VFile* vf); + struct Patch; void GBApplyPatch(struct GB* gb, struct Patch* patch); diff --git a/src/gb/io.c b/src/gb/io.c index a6d67be0a..5eed35823 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -311,6 +311,12 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case REG_STAT: GBVideoWriteSTAT(&gb->video, value); break; + case 0x50: + if (gb->memory.romBase != gb->memory.rom) { + free(gb->memory.romBase); + gb->memory.romBase = gb->memory.rom; + } + break; case REG_IE: gb->memory.ie = value; GBUpdateIRQs(gb); diff --git a/src/gb/memory.c b/src/gb/memory.c index c5271fbb7..dbafa7dab 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -52,7 +52,7 @@ static void GBSetActiveRegion(struct LR35902Core* cpu, uint16_t address) { case GB_REGION_CART_BANK0 + 2: case GB_REGION_CART_BANK0 + 3: cpu->memory.cpuLoad8 = GBFastLoad8; - cpu->memory.activeRegion = memory->rom; + cpu->memory.activeRegion = memory->romBase; cpu->memory.activeRegionEnd = GB_BASE_CART_BANK1; cpu->memory.activeMask = GB_SIZE_CART_BANK0 - 1; break; @@ -211,7 +211,7 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { case GB_REGION_CART_BANK0 + 1: case GB_REGION_CART_BANK0 + 2: case GB_REGION_CART_BANK0 + 3: - return memory->rom[address & (GB_SIZE_CART_BANK0 - 1)]; + return memory->romBase[address & (GB_SIZE_CART_BANK0 - 1)]; case GB_REGION_CART_BANK1: case GB_REGION_CART_BANK1 + 1: case GB_REGION_CART_BANK1 + 2: diff --git a/src/gb/memory.h b/src/gb/memory.h index f2f4a4d26..84fe2efd3 100644 --- a/src/gb/memory.h +++ b/src/gb/memory.h @@ -112,6 +112,7 @@ union GBMBCState { struct mRotationSource; struct GBMemory { uint8_t* rom; + uint8_t* romBase; uint8_t* romBank; enum GBMemoryBankControllerType mbcType; GBMemoryBankController mbc; From 62076d885c06b738bc21edb839a452c19777f5ca Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 19 May 2016 23:57:45 -0700 Subject: [PATCH 10/90] GB Video: Fix LCD IRQs sticking around for too long --- src/gb/video.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index 36ab36d08..5d80ff0d3 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -90,6 +90,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->nextMode -= video->eventDiff; } video->nextEvent = INT_MAX; + video->p->memory.io[REG_IF] &= ~(1 << GB_IRQ_LCDSTAT); GBVideoProcessDots(video); if (video->nextMode <= 0) { int lyc = video->p->memory.io[REG_LYC]; @@ -106,6 +107,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->mode = 2; if (GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; } } else { video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH; @@ -135,9 +137,11 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); + video->nextEvent = 4; } if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; } GBUpdateIRQs(video->p); break; @@ -151,6 +155,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->mode = 2; if (GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; GBUpdateIRQs(video->p); } break; @@ -168,6 +173,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->p->memory.io[REG_LY]); if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->p->memory.io[REG_LY]) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; GBUpdateIRQs(video->p); } if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { @@ -187,6 +193,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->mode = 0; if (GBRegisterSTATIsHblankIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; GBUpdateIRQs(video->p); } if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { @@ -254,11 +261,17 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { video->nextMode = GB_VIDEO_MODE_2_LENGTH - 5; // TODO: Why is this fudge factor needed? Might be related to T-cycles for load/store differing video->nextEvent = video->nextMode; video->eventDiff = -video->p->cpu->cycles >> video->p->doubleSpeed; - // TODO: Does this read as 0 for 4 T-cycles? - video->stat = GBRegisterSTATSetMode(video->stat, 2); - video->p->memory.io[REG_STAT] = video->stat; video->ly = 0; video->p->memory.io[REG_LY] = 0; + // TODO: Does this read as 0 for 4 T-cycles? + video->stat = GBRegisterSTATSetMode(video->stat, 2); + video->stat = GBRegisterSTATSetLYC(video->stat, video->ly == video->p->memory.io[REG_LYC]); + if (GBRegisterSTATIsLYCIRQ(video->stat) && video->ly == video->p->memory.io[REG_LYC]) { + video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; + GBUpdateIRQs(video->p); + } + video->p->memory.io[REG_STAT] = video->stat; if (video->p->cpu->cycles + (video->nextEvent << video->p->doubleSpeed) < video->p->cpu->nextEvent) { video->p->cpu->nextEvent = video->p->cpu->cycles + (video->nextEvent << video->p->doubleSpeed); @@ -280,6 +293,7 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) { video->stat = (video->stat & 0x7) | (value & 0x78); if (video->p->model == GB_MODEL_DMG && video->mode == 1) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); + video->nextEvent = 4; GBUpdateIRQs(video->p); } } From feb5ad226090f844b4283c4f7f18f25a95c9071b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 20 May 2016 19:02:15 -0700 Subject: [PATCH 11/90] ARM7: Fix flags on SBC/RSC --- CHANGES | 1 + src/arm/isa-arm.c | 30 +++++++++++++++++++----------- src/arm/isa-inlines.h | 2 ++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 6639130c7..14a83d267 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Bugfixes: - Util: Fix socket bind addresses - All: Fix instruction tables getting zeroed when linking sometimes - SDL: Fix SDL 1.2 build + - ARM7: Fix flags on SBC/RSC Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index bce24c608..61bae3163 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -184,8 +184,6 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) { // Instruction definitions // Beware pre-processor antics -#define NO_EXTEND64(V) (uint64_t)(uint32_t) (V) - #define ARM_ADDITION_S(M, N, D) \ if (rd == ARM_PC && _ARMModeHasSPSR(cpu->cpsr.priv)) { \ cpu->cpsr = cpu->spsr; \ @@ -208,6 +206,17 @@ static inline void _immediate(struct ARMCore* cpu, uint32_t opcode) { cpu->cpsr.v = ARM_V_SUBTRACTION(M, N, D); \ } +#define ARM_SUBTRACTION_CARRY_S(M, N, D, C) \ + if (rd == ARM_PC && _ARMModeHasSPSR(cpu->cpsr.priv)) { \ + cpu->cpsr = cpu->spsr; \ + _ARMReadCPSR(cpu); \ + } else { \ + cpu->cpsr.n = ARM_SIGN(D); \ + cpu->cpsr.z = !(D); \ + cpu->cpsr.c = ARM_BORROW_FROM_CARRY(M, N, D, C); \ + cpu->cpsr.v = ARM_V_SUBTRACTION(M, N, D); \ + } + #define ARM_NEUTRAL_S(M, N, D) \ if (rd == ARM_PC && _ARMModeHasSPSR(cpu->cpsr.priv)) { \ cpu->cpsr = cpu->spsr; \ @@ -454,14 +463,13 @@ DEFINE_ALU_INSTRUCTION_ARM(RSB, ARM_SUBTRACTION_S(cpu->shifterOperand, n, cpu->g int32_t n = cpu->gprs[rn]; cpu->gprs[rd] = cpu->shifterOperand - n;) -DEFINE_ALU_INSTRUCTION_ARM(RSC, ARM_SUBTRACTION_S(cpu->shifterOperand, n, cpu->gprs[rd]), - int32_t n = cpu->gprs[rn] + !cpu->cpsr.c; - cpu->gprs[rd] = cpu->shifterOperand - n;) - -DEFINE_ALU_INSTRUCTION_ARM(SBC, ARM_SUBTRACTION_S(n, shifterOperand, cpu->gprs[rd]), +DEFINE_ALU_INSTRUCTION_ARM(RSC, ARM_SUBTRACTION_CARRY_S(cpu->shifterOperand, n, cpu->gprs[rd], !cpu->cpsr.c), int32_t n = cpu->gprs[rn]; - int32_t shifterOperand = cpu->shifterOperand + !cpu->cpsr.c; - cpu->gprs[rd] = n - shifterOperand;) + cpu->gprs[rd] = cpu->shifterOperand - n - !cpu->cpsr.c;) + +DEFINE_ALU_INSTRUCTION_ARM(SBC, ARM_SUBTRACTION_CARRY_S(n, cpu->shifterOperand, cpu->gprs[rd], !cpu->cpsr.c), + int32_t n = cpu->gprs[rn]; + cpu->gprs[rd] = n - cpu->shifterOperand - !cpu->cpsr.c;) DEFINE_ALU_INSTRUCTION_ARM(SUB, ARM_SUBTRACTION_S(n, cpu->shifterOperand, cpu->gprs[rd]), int32_t n = cpu->gprs[rn]; @@ -495,7 +503,7 @@ DEFINE_MULTIPLY_INSTRUCTION_ARM(SMULL, ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi])) DEFINE_MULTIPLY_INSTRUCTION_ARM(UMLAL, - uint64_t d = NO_EXTEND64(cpu->gprs[rm]) * NO_EXTEND64(cpu->gprs[rs]); + uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]); int32_t dm = cpu->gprs[rd]; int32_t dn = d; cpu->gprs[rd] = dm + dn; @@ -503,7 +511,7 @@ DEFINE_MULTIPLY_INSTRUCTION_ARM(UMLAL, ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi])) DEFINE_MULTIPLY_INSTRUCTION_ARM(UMULL, - uint64_t d = NO_EXTEND64(cpu->gprs[rm]) * NO_EXTEND64(cpu->gprs[rs]); + uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]); cpu->gprs[rd] = d; cpu->gprs[rdHi] = d >> 32;, ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi])) diff --git a/src/arm/isa-inlines.h b/src/arm/isa-inlines.h index 42a581b4a..5850dab69 100644 --- a/src/arm/isa-inlines.h +++ b/src/arm/isa-inlines.h @@ -29,9 +29,11 @@ #define ARM_SIGN(I) ((I) >> 31) #define ARM_SXT_8(I) (((int8_t) (I) << 24) >> 24) #define ARM_SXT_16(I) (((int16_t) (I) << 16) >> 16) +#define ARM_UXT_64(I) (uint64_t)(uint32_t) (I) #define ARM_CARRY_FROM(M, N, D) (((uint32_t) (M) >> 31) + ((uint32_t) (N) >> 31) > ((uint32_t) (D) >> 31)) #define ARM_BORROW_FROM(M, N, D) (((uint32_t) (M)) >= ((uint32_t) (N))) +#define ARM_BORROW_FROM_CARRY(M, N, D, C) (ARM_UXT_64(M) >= (ARM_UXT_64(N)) + (uint64_t) (C)) #define ARM_V_ADDITION(M, N, D) (!(ARM_SIGN((M) ^ (N))) && (ARM_SIGN((M) ^ (D))) && (ARM_SIGN((N) ^ (D)))) #define ARM_V_SUBTRACTION(M, N, D) ((ARM_SIGN((M) ^ (N))) && (ARM_SIGN((M) ^ (D)))) From 0080fab314192b5b24beb338f2f83fbe9f3c1e82 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 20 May 2016 19:04:06 -0700 Subject: [PATCH 12/90] GB Video: Revert video IRQ change, disable OAM IRQ if Hblank IRQ is enabled --- src/gb/video.c | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index 5d80ff0d3..d1a82874a 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -90,7 +90,6 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->nextMode -= video->eventDiff; } video->nextEvent = INT_MAX; - video->p->memory.io[REG_IF] &= ~(1 << GB_IRQ_LCDSTAT); GBVideoProcessDots(video); if (video->nextMode <= 0) { int lyc = video->p->memory.io[REG_LYC]; @@ -105,9 +104,8 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { if (video->ly < GB_VIDEO_VERTICAL_PIXELS) { video->nextMode = GB_VIDEO_MODE_2_LENGTH; video->mode = 2; - if (GBRegisterSTATIsOAMIRQ(video->stat)) { + if (!GBRegisterSTATIsHblankIRQ(video->stat) && GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; } } else { video->nextMode = GB_VIDEO_HORIZONTAL_LENGTH; @@ -137,11 +135,9 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); } video->p->memory.io[REG_IF] |= (1 << GB_IRQ_VBLANK); - video->nextEvent = 4; } if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->ly) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; } GBUpdateIRQs(video->p); break; @@ -155,7 +151,6 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->mode = 2; if (GBRegisterSTATIsOAMIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; GBUpdateIRQs(video->p); } break; @@ -173,7 +168,6 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->stat = GBRegisterSTATSetLYC(video->stat, lyc == video->p->memory.io[REG_LY]); if (GBRegisterSTATIsLYCIRQ(video->stat) && lyc == video->p->memory.io[REG_LY]) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; GBUpdateIRQs(video->p); } if (video->p->memory.mbcType == GB_MBC7 && video->p->memory.rotation && video->p->memory.rotation->sample) { @@ -193,7 +187,6 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->mode = 0; if (GBRegisterSTATIsHblankIRQ(video->stat)) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; GBUpdateIRQs(video->p); } if (video->ly < GB_VIDEO_VERTICAL_PIXELS && video->p->memory.isHdma && video->p->memory.io[REG_HDMA5] != 0xFF) { @@ -268,7 +261,6 @@ void GBVideoWriteLCDC(struct GBVideo* video, GBRegisterLCDC value) { video->stat = GBRegisterSTATSetLYC(video->stat, video->ly == video->p->memory.io[REG_LYC]); if (GBRegisterSTATIsLYCIRQ(video->stat) && video->ly == video->p->memory.io[REG_LYC]) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; GBUpdateIRQs(video->p); } video->p->memory.io[REG_STAT] = video->stat; @@ -293,7 +285,6 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) { video->stat = (video->stat & 0x7) | (value & 0x78); if (video->p->model == GB_MODEL_DMG && video->mode == 1) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); - video->nextEvent = 4; GBUpdateIRQs(video->p); } } From 210ef6db506e26cd3bc2f0eb67d46563285fdfdb Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 22 May 2016 22:48:12 -0700 Subject: [PATCH 13/90] Qt: Clean up some path canonicalization --- CHANGES | 1 + src/platform/qt/GameController.cpp | 15 +++++++++------ src/platform/qt/Window.cpp | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGES b/CHANGES index 14a83d267..2c048bc92 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Misc: - Libretro: Disable logging game errors, BIOS calls and stubs in release builds - ARM7: Support forcing Thumb mode via MSR - ARM7: Flush prefetch cache when loading CPSR via MSR + - Qt: Canonicalize file paths when loading games 0.4.0: (2016-02-02) Features: diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index b724ea09c..f55403b23 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -288,14 +288,12 @@ void GameController::setDebugger(mDebugger* debugger) { void GameController::loadGame(const QString& path) { closeGame(); - QFile file(path); - if (!file.open(QIODevice::ReadOnly)) { + QFileInfo info(path); + if (!info.isReadable()) { LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); return; } - file.close(); - - m_fname = path; + m_fname = info.canonicalFilePath(); openGame(); } @@ -406,7 +404,12 @@ void GameController::replaceGame(const QString& path) { return; } - m_fname = path; + QFileInfo info(path); + if (!info.isReadable()) { + LOG(QT, ERROR) << tr("Failed to open game file: %1").arg(path); + return; + } + m_fname = info.canonicalFilePath(); threadInterrupt(); mCoreLoadFile(m_threadContext.core, m_fname.toLocal8Bit().constData()); threadContinue(); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 93a23b6ea..dcef253bd 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -333,11 +333,12 @@ void Window::multiplayerChanged() { void Window::selectBIOS() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS")); if (!filename.isEmpty()) { - m_config->setOption("bios", filename); + QFileInfo info(filename); + m_config->setOption("bios", info.canonicalFilePath()); m_config->updateOption("bios"); m_config->setOption("useBios", true); m_config->updateOption("useBios"); - m_controller->loadBIOS(filename); + m_controller->loadBIOS(info.canonicalFilePath()); } } From 11f21ecc463bc08ee44d70387c29309e7ce7c098 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 23 May 2016 19:09:20 -0700 Subject: [PATCH 14/90] GB Core: step now advances with instruction granularity --- src/gb/core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/gb/core.c b/src/gb/core.c index d693eeb0f..ec2ca3c52 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -206,7 +206,10 @@ static void _GBCoreRunLoop(struct mCore* core) { } static void _GBCoreStep(struct mCore* core) { - LR35902Tick(core->cpu); + struct LR35902Core* cpu = core->cpu; + do { + LR35902Tick(cpu); + } while (cpu->executionState != LR35902_CORE_FETCH); } static bool _GBCoreLoadState(struct mCore* core, struct VFile* vf, int flags) { From 616625a510a9f9ff265deda9c9a71841853c547f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 23 May 2016 19:29:11 -0700 Subject: [PATCH 15/90] GB Core: Add `frame` command to debugger --- src/gb/cli.c | 60 ++++++++++++++++++++++++++++++++++++++++++++-------- src/gb/cli.h | 9 ++++++++ 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/gb/cli.c b/src/gb/cli.c index 1c3debbf7..3eed3e519 100644 --- a/src/gb/cli.c +++ b/src/gb/cli.c @@ -5,27 +5,69 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cli.h" +#include "core/core.h" +#include "gb/gb.h" +#include "gb/io.h" +#include "gb/video.h" #include "lr35902/debugger/cli-debugger.h" #ifdef USE_CLI_DEBUGGER +static void _GBCLIDebuggerInit(struct CLIDebuggerSystem*); +static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*); + +static void _frame(struct CLIDebugger*, struct CLIDebugVector*); + struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = { + { "frame", _frame, 0, "Frame advance" }, { 0, 0, 0, 0 } }; struct CLIDebuggerSystem* GBCLIDebuggerCreate(struct mCore* core) { UNUSED(core); - struct CLIDebuggerSystem* debugger = malloc(sizeof(struct CLIDebuggerSystem)); - LR35902CLIDebuggerCreate(debugger); - debugger->init = NULL; - debugger->deinit = NULL; - debugger->custom = NULL; - debugger->lookupIdentifier = NULL; + struct GBCLIDebugger* debugger = malloc(sizeof(struct GBCLIDebugger)); + LR35902CLIDebuggerCreate(&debugger->d); + debugger->d.init = _GBCLIDebuggerInit; + debugger->d.deinit = NULL; + debugger->d.custom = _GBCLIDebuggerCustom; + debugger->d.lookupIdentifier = NULL; - debugger->name = "Game Boy"; - debugger->commands = _GBCLIDebuggerCommands; + debugger->d.name = "Game Boy"; + debugger->d.commands = _GBCLIDebuggerCommands; - return debugger; + debugger->core = core; + + return &debugger->d; +} + +static void _GBCLIDebuggerInit(struct CLIDebuggerSystem* debugger) { + struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger; + + gbDebugger->frameAdvance = false; +} + +static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { + struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger; + + if (gbDebugger->frameAdvance) { + if (!gbDebugger->inVblank && GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1) { + mDebuggerEnter(&gbDebugger->d.p->d, DEBUGGER_ENTER_MANUAL, 0); + gbDebugger->frameAdvance = false; + return false; + } + gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1; + return true; + } + return false; +} + +static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + UNUSED(dv); + debugger->d.state = DEBUGGER_CUSTOM; + + struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; + gbDebugger->frameAdvance = true; + gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1; } #endif diff --git a/src/gb/cli.h b/src/gb/cli.h index 2ae3c1a1a..f9b2fca9a 100644 --- a/src/gb/cli.h +++ b/src/gb/cli.h @@ -9,6 +9,15 @@ #ifdef USE_CLI_DEBUGGER #include "debugger/cli-debugger.h" +struct GBCLIDebugger { + struct CLIDebuggerSystem d; + + struct mCore* core; + + bool frameAdvance; + bool inVblank; +}; + struct CLIDebuggerSystem* GBCLIDebuggerCreate(struct mCore*); #endif From 0f16569e7d89352158ec9ca6ea2dae745802636c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 23 May 2016 20:13:51 -0700 Subject: [PATCH 16/90] GB: Fix eiPending issue --- src/gb/gb.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index c14051a52..f739d78b7 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -65,7 +65,7 @@ static void GBInit(void* cpu, struct mCPUComponent* component) { gb->stream = NULL; - gb->eiPending = false; + gb->eiPending = INT_MAX; gb->doubleSpeed = 0; } @@ -301,12 +301,12 @@ void GBProcessEvents(struct LR35902Core* cpu) { int32_t nextEvent = INT_MAX; int32_t testEvent; - if (gb->eiPending) { + if (gb->eiPending != INT_MAX) { gb->eiPending -= cycles; if (gb->eiPending <= 0) { gb->memory.ime = true; GBUpdateIRQs(gb); - gb->eiPending = 0; + gb->eiPending = INT_MAX; } } @@ -349,7 +349,7 @@ void GBSetInterrupts(struct LR35902Core* cpu, bool enable) { struct GB* gb = (struct GB*) cpu->master; if (!enable) { gb->memory.ime = enable; - gb->eiPending = 0; + gb->eiPending = INT_MAX; GBUpdateIRQs(gb); } else { if (cpu->nextEvent > cpu->cycles + 4) { From 73777259524e6ffc48f330f7c77f2e0bb86ad706 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 23 May 2016 21:57:29 -0700 Subject: [PATCH 17/90] GB Video: Fix OBJ/BG priority --- src/gb/renderers/software.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 41f88cbb4..6eb78876a 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -297,7 +297,7 @@ static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* rende if (GBRegisterLCDCIsObjSize(renderer->lcdc) && obj->tile & 1) { --tileOffset; } - uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0xE3 : 0x60; + uint8_t mask = GBObjAttributesIsPriority(obj->attr) ? 0x63 : 0x60; uint8_t mask2 = GBObjAttributesIsPriority(obj->attr) ? 0 : 0x83; int p; if (renderer->model >= GB_MODEL_CGB) { From e08087a682c011bd8893803939b67419fdd61b18 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 24 May 2016 19:59:54 -0700 Subject: [PATCH 18/90] All: Update libzip dependencies on Ubuntu --- CMakeLists.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b345e9596..081b863cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -437,7 +437,14 @@ if(USE_LIBZIP) list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES}) list(APPEND FEATURES LIBZIP) list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-zip.c) - set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2") + string(REGEX MATCH "^[0-9]+" LIBZIP_VERSION_MAJOR ${libzip_VERSION}) + if (LIBZIP_VERSION_MAJOR LESS 1) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2") + elseif(LIBZIP_VERSION_MAJOR EQUAL 1) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip4") + else() + message(AUTHOR_WARNING Unknown version of libzip detected: ${libzip_VERSION}) + endif() elseif(USE_MINIZIP) include_directories(AFTER ${MINIZIP_INCLUDE_DIRS}) link_directories(${MINIZIP_LIBRARY_DIRS}) From d242638e28fe899406068e11681b002256708d46 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 25 May 2016 21:25:09 -0700 Subject: [PATCH 19/90] OpenGL: Add texSize uniform --- CHANGES | 1 + res/shaders/agb001.shader/agb001.fs | 5 +++-- res/shaders/ags001.shader/ags001.fs | 5 +++-- res/shaders/xbr.shader/xbr.fs | 4 ++-- res/shaders/xbr.shader/xbr.vs | 4 +++- src/platform/opengl/gles2.c | 2 ++ src/platform/opengl/gles2.h | 1 + 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 2c048bc92..c257ce6a6 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,7 @@ Misc: - ARM7: Support forcing Thumb mode via MSR - ARM7: Flush prefetch cache when loading CPSR via MSR - Qt: Canonicalize file paths when loading games + - OpenGL: Add texSize uniform 0.4.0: (2016-02-02) Features: diff --git a/res/shaders/agb001.shader/agb001.fs b/res/shaders/agb001.shader/agb001.fs index cb3964598..79a036ff7 100644 --- a/res/shaders/agb001.shader/agb001.fs +++ b/res/shaders/agb001.shader/agb001.fs @@ -1,5 +1,6 @@ varying vec2 texCoord; uniform sampler2D tex; +uniform vec2 texSize; void main() { vec4 color = texture2D(tex, texCoord); @@ -14,8 +15,8 @@ void main() { arrayY[2] = vec3(1.0, 1.0, 1.0); arrayY[3] = vec3(0.8, 0.8, 0.8); color.rgb = pow(color.rgb * vec3(0.8, 0.8, 0.8), vec3(1.8, 1.8, 1.8)) + vec3(0.16, 0.16, 0.16); - color.rgb *= arrayX[int(mod(texCoord.s * 960.0, 4.0))]; - color.rgb *= arrayY[int(mod(texCoord.t * 640.0, 4.0))]; + color.rgb *= arrayX[int(mod(texCoord.s * texSize.x * 4.0, 4.0))]; + color.rgb *= arrayY[int(mod(texCoord.t * texSize.y * 4.0, 4.0))]; color.a = 0.5; gl_FragColor = color; } diff --git a/res/shaders/ags001.shader/ags001.fs b/res/shaders/ags001.shader/ags001.fs index 96d0544d0..8b7265bd3 100644 --- a/res/shaders/ags001.shader/ags001.fs +++ b/res/shaders/ags001.shader/ags001.fs @@ -1,5 +1,6 @@ varying vec2 texCoord; uniform sampler2D tex; +uniform vec2 texSize; void main() { vec4 color = texture2D(tex, texCoord); @@ -14,8 +15,8 @@ void main() { arrayY[2] = vec3(1.0, 1.0, 1.0); arrayY[3] = vec3(0.9, 0.9, 0.9); color.rgb = pow(color.rgb, vec3(1.6, 1.6, 1.6)); - color.rgb *= arrayX[int(mod(texCoord.s * 960.0, 4.0))]; - color.rgb *= arrayY[int(mod(texCoord.t * 640.0, 4.0))]; + color.rgb *= arrayX[int(mod(texCoord.s * texSize.x * 4.0, 4.0))]; + color.rgb *= arrayY[int(mod(texCoord.t * texSize.y * 4.0, 4.0))]; color.a = 0.8; gl_FragColor = color; } diff --git a/res/shaders/xbr.shader/xbr.fs b/res/shaders/xbr.shader/xbr.fs index 69c0faaad..8fec26bc7 100644 --- a/res/shaders/xbr.shader/xbr.fs +++ b/res/shaders/xbr.shader/xbr.fs @@ -98,7 +98,7 @@ varying vec4 TEX5; varying vec4 TEX6; varying vec4 TEX7; -const vec2 TextureSize = vec2(240.0, 160.0); +uniform vec2 texSize; void main() { @@ -110,7 +110,7 @@ void main() vec3 res1, res2, pix1, pix2; float blend1, blend2; - vec2 fp = fract(texCoord * TextureSize); + vec2 fp = fract(texCoord * texSize); vec3 A1 = COMPAT_TEXTURE(tex, TEX1.xw).rgb; vec3 B1 = COMPAT_TEXTURE(tex, TEX1.yw).rgb; diff --git a/res/shaders/xbr.shader/xbr.vs b/res/shaders/xbr.shader/xbr.vs index d59a970ef..0d7b0d25e 100644 --- a/res/shaders/xbr.shader/xbr.vs +++ b/res/shaders/xbr.shader/xbr.vs @@ -34,12 +34,14 @@ varying vec4 TEX6; varying vec4 TEX7; attribute vec4 position; +uniform vec2 texSize; + /* VERTEX_SHADER */ void main() { gl_Position = position; - vec2 ps = vec2(1.0/240.0, 1.0/160.0); + vec2 ps = vec2(1.0) / texSize; float dx = ps.x; float dy = ps.y; diff --git a/src/platform/opengl/gles2.c b/src/platform/opengl/gles2.c index 88f6079dd..604384055 100644 --- a/src/platform/opengl/gles2.c +++ b/src/platform/opengl/gles2.c @@ -230,6 +230,7 @@ void _drawShader(struct mGLES2Context* context, struct mGLES2Shader* shader) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, shader->filter ? GL_LINEAR : GL_NEAREST); glUseProgram(shader->program); glUniform1i(shader->texLocation, 0); + glUniform2f(shader->texSizeLocation, context->d.width, context->d.height); glVertexAttribPointer(shader->positionLocation, 2, GL_FLOAT, GL_FALSE, 0, _vertices); glEnableVertexAttribArray(shader->positionLocation); size_t u; @@ -399,6 +400,7 @@ void mGLES2ShaderInit(struct mGLES2Shader* shader, const char* vs, const char* f } shader->texLocation = glGetUniformLocation(shader->program, "tex"); + shader->texSizeLocation = glGetUniformLocation(shader->program, "texSize"); shader->positionLocation = glGetAttribLocation(shader->program, "position"); size_t i; for (i = 0; i < shader->nUniforms; ++i) { diff --git a/src/platform/opengl/gles2.h b/src/platform/opengl/gles2.h index 510580861..fabdebc14 100644 --- a/src/platform/opengl/gles2.h +++ b/src/platform/opengl/gles2.h @@ -62,6 +62,7 @@ struct mGLES2Shader { GLuint vertexShader; GLuint program; GLuint texLocation; + GLuint texSizeLocation; GLuint positionLocation; struct mGLES2Uniform* uniforms; From 334963511b513bcfc64d2b506337fea5774a18d7 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 May 2016 01:44:05 -0700 Subject: [PATCH 20/90] LR35902: Optimize core loop to always run one M-cycle at a time --- src/lr35902/lr35902.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lr35902/lr35902.c b/src/lr35902/lr35902.c index dd61555af..a5782cb71 100644 --- a/src/lr35902/lr35902.c +++ b/src/lr35902/lr35902.c @@ -104,8 +104,7 @@ static void _LR35902InstructionIRQ(struct LR35902Core* cpu) { static void _LR35902Step(struct LR35902Core* cpu) { ++cpu->cycles; enum LR35902ExecutionState state = cpu->executionState; - ++cpu->executionState; - cpu->executionState &= 3; + cpu->executionState = LR35902_CORE_IDLE_0; switch (state) { case LR35902_CORE_FETCH: if (cpu->irqPending) { @@ -119,9 +118,6 @@ static void _LR35902Step(struct LR35902Core* cpu) { cpu->instruction = _lr35902InstructionTable[cpu->bus]; ++cpu->pc; break; - case LR35902_CORE_EXECUTE: - cpu->instruction(cpu); - break; case LR35902_CORE_MEMORY_LOAD: cpu->bus = cpu->memory.load8(cpu, cpu->index); break; @@ -142,6 +138,17 @@ static void _LR35902Step(struct LR35902Core* cpu) { void LR35902Tick(struct LR35902Core* cpu) { _LR35902Step(cpu); + if (cpu->cycles + 2 >= cpu->nextEvent) { + int32_t diff = cpu->nextEvent - cpu->cycles; + cpu->cycles = cpu->nextEvent; + cpu->irqh.processEvents(cpu); + cpu->cycles += 2 - diff; + } else { + cpu->cycles += 2; + } + cpu->executionState = LR35902_CORE_FETCH; + cpu->instruction(cpu); + ++cpu->cycles; if (cpu->cycles >= cpu->nextEvent) { cpu->irqh.processEvents(cpu); } @@ -150,12 +157,19 @@ void LR35902Tick(struct LR35902Core* cpu) { void LR35902Run(struct LR35902Core* cpu) { while (true) { _LR35902Step(cpu); + if (cpu->cycles + 2 >= cpu->nextEvent) { + int32_t diff = cpu->nextEvent - cpu->cycles; + cpu->cycles = cpu->nextEvent; + cpu->irqh.processEvents(cpu); + cpu->cycles += 2 - diff; + } else { + cpu->cycles += 2; + } + cpu->executionState = LR35902_CORE_FETCH; + cpu->instruction(cpu); + ++cpu->cycles; if (cpu->cycles >= cpu->nextEvent) { break; - } else if (cpu->executionState < LR35902_CORE_EXECUTE) { - // Silly hack: keep us from calling step if we know the next step is a no-op - ++cpu->cycles; - ++cpu->executionState; } } cpu->irqh.processEvents(cpu); From 2ee192d868a452ff0e562925dd76cd8c805a0d67 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 May 2016 10:50:17 -0700 Subject: [PATCH 21/90] GB Video: Frame end callback should only happen on instruction boundary --- src/gb/video.c | 23 +++++++++++++++++++---- src/gb/video.h | 2 ++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/gb/video.c b/src/gb/video.c index d1a82874a..6798d1005 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -50,6 +50,7 @@ void GBVideoReset(struct GBVideo* video) { video->nextMode = INT_MAX; video->dotCounter = INT_MIN; + video->nextFrame = INT_MAX; video->frameCounter = 0; video->frameskipCounter = 0; @@ -88,6 +89,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { if (video->nextEvent <= 0) { if (video->nextEvent != INT_MAX) { video->nextMode -= video->eventDiff; + video->nextFrame -= video->eventDiff; } video->nextEvent = INT_MAX; GBVideoProcessDots(video); @@ -116,12 +118,10 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { mCoreSyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; } - GBFrameEnded(video->p); ++video->frameCounter; - struct mCoreThread* thread = mCoreThreadGet(); - if (thread && thread->frameCallback) { - thread->frameCallback(thread); + if (video->nextFrame != 0) { + video->nextFrame = 0; } if (video->p->stream && video->p->stream->postVideoFrame) { @@ -198,6 +198,21 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->stat = GBRegisterSTATSetMode(video->stat, video->mode); video->p->memory.io[REG_STAT] = video->stat; } + if (video->nextFrame <= 0) { + if (video->p->cpu->executionState == LR35902_CORE_FETCH) { + GBFrameEnded(video->p); + struct mCoreThread* thread = mCoreThreadGet(); + if (thread && thread->frameCallback) { + thread->frameCallback(thread); + } + video->nextFrame = GB_VIDEO_TOTAL_LENGTH; + } else { + video->nextFrame = 4 - ((video->p->cpu->executionState + 1) & 3); + if (video->nextFrame < video->nextEvent) { + video->nextEvent = video->nextFrame; + } + } + } if (video->nextMode < video->nextEvent) { video->nextEvent = video->nextMode; } diff --git a/src/gb/video.h b/src/gb/video.h index bf5c97401..7bce4e1db 100644 --- a/src/gb/video.h +++ b/src/gb/video.h @@ -104,6 +104,8 @@ struct GBVideo { int32_t nextMode; int32_t dotCounter; + int32_t nextFrame; + uint8_t* vram; uint8_t* vramBank; int vramCurrentBank; From b14aafe9cb5499bbeb54478c74b56e781e5936ca Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 May 2016 12:27:30 -0700 Subject: [PATCH 22/90] GB Memory: Fix RTC latching --- src/gb/memory.c | 5 +++-- src/gb/memory.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gb/memory.c b/src/gb/memory.c index dbafa7dab..9ae5af086 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -132,7 +132,7 @@ void GBMemoryReset(struct GB* gb) { gb->memory.sramAccess = false; gb->memory.rtcAccess = false; gb->memory.activeRtcReg = 0; - gb->memory.rtcLatched = 0; + gb->memory.rtcLatched = false; memset(&gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs)); memset(&gb->memory.hram, 0, sizeof(gb->memory.hram)); @@ -682,9 +682,10 @@ void _GBMBC3(struct GBMemory* memory, uint16_t address, uint8_t value) { break; case 0x3: if (memory->rtcLatched && value == 0) { - memory->rtcLatched = value; + memory->rtcLatched = false; } else if (!memory->rtcLatched && value == 1) { _latchRtc(memory); + memory->rtcLatched = true; } break; } diff --git a/src/gb/memory.h b/src/gb/memory.h index 84fe2efd3..bfba364f5 100644 --- a/src/gb/memory.h +++ b/src/gb/memory.h @@ -149,7 +149,7 @@ struct GBMemory { bool rtcAccess; int activeRtcReg; - int rtcLatched; + bool rtcLatched; uint8_t rtcRegs[5]; struct mRTCSource* rtc; struct mRotationSource* rotation; From 36ea5ea89c86b97c03729ecbc67725fba47f55a6 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 May 2016 16:25:01 -0700 Subject: [PATCH 23/90] Core: Migrate extdata to core --- src/core/serialize.c | 131 ++++++++++++++++++++ src/core/serialize.h | 42 +++++++ src/feature/gui/gui-runner.c | 2 +- src/gb/cli.c | 1 + src/gba/extra/cli.c | 1 + src/gba/rr/rr.c | 1 + src/gba/serialize.c | 180 +++++----------------------- src/gba/serialize.h | 32 +---- src/platform/openemu/mGBAGameCore.m | 3 +- src/platform/qt/GameController.cpp | 2 +- src/platform/qt/LoadSaveState.cpp | 14 ++- src/platform/qt/SettingsView.cpp | 3 +- src/platform/sdl/sdl-events.c | 2 +- 13 files changed, 222 insertions(+), 192 deletions(-) create mode 100644 src/core/serialize.c create mode 100644 src/core/serialize.h diff --git a/src/core/serialize.c b/src/core/serialize.c new file mode 100644 index 000000000..a3b4c91dc --- /dev/null +++ b/src/core/serialize.c @@ -0,0 +1,131 @@ +/* Copyright (c) 2013-2016 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 "serialize.h" + +#include "util/vfs.h" + +struct mStateExtdataHeader { + uint32_t tag; + int32_t size; + int64_t offset; +}; + +bool mStateExtdataInit(struct mStateExtdata* extdata) { + memset(extdata->data, 0, sizeof(extdata->data)); + return true; +} + +void mStateExtdataDeinit(struct mStateExtdata* extdata) { + size_t i; + for (i = 1; i < EXTDATA_MAX; ++i) { + if (extdata->data[i].data && extdata->data[i].clean) { + extdata->data[i].clean(extdata->data[i].data); + } + } +} + +void mStateExtdataPut(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) { + if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) { + return; + } + + if (extdata->data[tag].data && extdata->data[tag].clean) { + extdata->data[tag].clean(extdata->data[tag].data); + } + extdata->data[tag] = *item; +} + +bool mStateExtdataGet(struct mStateExtdata* extdata, enum mStateExtdataTag tag, struct mStateExtdataItem* item) { + if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) { + return false; + } + + *item = extdata->data[tag]; + return true; +} + +bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf) { + ssize_t position = vf->seek(vf, 0, SEEK_CUR); + ssize_t size = sizeof(struct mStateExtdataHeader); + size_t i = 0; + for (i = 1; i < EXTDATA_MAX; ++i) { + if (extdata->data[i].data) { + size += sizeof(struct mStateExtdataHeader); + } + } + if (size == sizeof(struct mStateExtdataHeader)) { + return true; + } + struct mStateExtdataHeader* header = malloc(size); + position += size; + + size_t j; + for (i = 1, j = 0; i < EXTDATA_MAX; ++i) { + if (extdata->data[i].data) { + STORE_32LE(i, offsetof(struct mStateExtdataHeader, tag), &header[j]); + STORE_32LE(extdata->data[i].size, offsetof(struct mStateExtdataHeader, size), &header[j]); + STORE_64LE(position, offsetof(struct mStateExtdataHeader, offset), &header[j]); + position += extdata->data[i].size; + ++j; + } + } + header[j].tag = 0; + header[j].size = 0; + header[j].offset = 0; + + if (vf->write(vf, header, size) != size) { + free(header); + return false; + } + free(header); + + for (i = 1; i < EXTDATA_MAX; ++i) { + if (extdata->data[i].data) { + if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) { + return false; + } + } + } + return true; +} + +bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) { + while (true) { + struct mStateExtdataHeader buffer, header; + if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) { + return false; + } + LOAD_32LE(header.tag, 0, &buffer.tag); + LOAD_32LE(header.size, 0, &buffer.size); + LOAD_64LE(header.offset, 0, &buffer.offset); + + if (header.tag == EXTDATA_NONE) { + break; + } + if (header.tag >= EXTDATA_MAX) { + continue; + } + ssize_t position = vf->seek(vf, 0, SEEK_CUR); + if (vf->seek(vf, header.offset, SEEK_SET) < 0) { + return false; + } + struct mStateExtdataItem item = { + .data = malloc(header.size), + .size = header.size, + .clean = free + }; + if (!item.data) { + continue; + } + if (vf->read(vf, item.data, header.size) != header.size) { + free(item.data); + continue; + } + mStateExtdataPut(extdata, header.tag, &item); + vf->seek(vf, position, SEEK_SET); + }; + return true; +} diff --git a/src/core/serialize.h b/src/core/serialize.h new file mode 100644 index 000000000..c51b811bf --- /dev/null +++ b/src/core/serialize.h @@ -0,0 +1,42 @@ +/* Copyright (c) 2013-2016 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 M_SERIALIZE_H +#define M_SERIALIZE_H + +#include "util/common.h" + +enum mStateExtdataTag { + EXTDATA_NONE = 0, + EXTDATA_SCREENSHOT = 1, + EXTDATA_SAVEDATA = 2, + EXTDATA_CHEATS = 3, + EXTDATA_MAX +}; + +#define SAVESTATE_SCREENSHOT 1 +#define SAVESTATE_SAVEDATA 2 +#define SAVESTATE_CHEATS 4 + +struct mStateExtdataItem { + int32_t size; + void* data; + void (*clean)(void*); +}; + +struct mStateExtdata { + struct mStateExtdataItem data[EXTDATA_MAX]; +}; + +bool mStateExtdataInit(struct mStateExtdata*); +void mStateExtdataDeinit(struct mStateExtdata*); +void mStateExtdataPut(struct mStateExtdata*, enum mStateExtdataTag, struct mStateExtdataItem*); +bool mStateExtdataGet(struct mStateExtdata*, enum mStateExtdataTag, struct mStateExtdataItem*); + +struct VFile; +bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf); +bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf); + +#endif diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 1d0977720..5ef565fa1 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -6,10 +6,10 @@ #include "gui-runner.h" #include "core/core.h" +#include "core/serialize.h" #include "feature/gui/gui-config.h" #include "gba/input.h" #include "gba/interface.h" -#include "gba/serialize.h" #include "util/gui/file-select.h" #include "util/gui/font.h" #include "util/gui/menu.h" diff --git a/src/gb/cli.c b/src/gb/cli.c index 3eed3e519..132e6747f 100644 --- a/src/gb/cli.c +++ b/src/gb/cli.c @@ -6,6 +6,7 @@ #include "cli.h" #include "core/core.h" +#include "core/serialize.h" #include "gb/gb.h" #include "gb/io.h" #include "gb/video.h" diff --git a/src/gba/extra/cli.c b/src/gba/extra/cli.c index d4110f98b..d5afcb6b2 100644 --- a/src/gba/extra/cli.c +++ b/src/gba/extra/cli.c @@ -6,6 +6,7 @@ #include "cli.h" #include "arm/debugger/cli-debugger.h" +#include "core/serialize.h" #include "gba/io.h" #include "gba/serialize.h" diff --git a/src/gba/rr/rr.c b/src/gba/rr/rr.c index 88f733859..ad6375115 100644 --- a/src/gba/rr/rr.c +++ b/src/gba/rr/rr.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "rr.h" +#include "core/serialize.h" #include "util/vfs.h" mLOG_DEFINE_CATEGORY(GBA_RR, "GBA RR"); diff --git a/src/gba/serialize.c b/src/gba/serialize.c index fe1fbba57..d8e35364d 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -5,6 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "serialize.h" +#include "core/serialize.h" #include "core/sync.h" #include "gba/audio.h" #include "gba/cheats.h" @@ -35,13 +36,7 @@ mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate"); struct GBABundledState { struct GBASerializedState* state; - struct GBAExtdata* extdata; -}; - -struct GBAExtdataHeader { - uint32_t tag; - int32_t size; - int64_t offset; + struct mStateExtdata* extdata; }; void GBASerialize(struct GBA* gba, struct GBASerializedState* state) { @@ -221,7 +216,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { } #ifdef USE_PNG -static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct GBAExtdata* extdata) { +static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct mStateExtdata* extdata) { unsigned stride; const void* pixels = 0; gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels); @@ -290,11 +285,11 @@ static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { return 1; } if (!strcmp((const char*) chunk->name, "gbAx")) { - struct GBAExtdata* extdata = bundle->extdata; + struct mStateExtdata* extdata = bundle->extdata; if (!extdata) { return 0; } - struct GBAExtdataItem item; + struct mStateExtdataItem item; if (chunk->size < sizeof(uint32_t) * 2) { return 0; } @@ -314,13 +309,13 @@ static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { data += sizeof(uint32_t) * 2; uncompress((Bytef*) item.data, &len, data, chunk->size); item.size = len; - GBAExtdataPut(extdata, tag, &item); + mStateExtdataPut(extdata, tag, &item); return 1; } return 0; } -static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* extdata) { +static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct mStateExtdata* extdata) { png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); png_infop info = png_create_info_struct(png); png_infop end = png_create_info_struct(png); @@ -347,12 +342,12 @@ static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtd PNGReadClose(png, info, end); if (success) { - struct GBAExtdataItem item = { + struct mStateExtdataItem item = { .size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4, .data = pixels, .clean = free }; - GBAExtdataPut(extdata, EXTDATA_SCREENSHOT, &item); + mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item); } else { free(pixels); GBADeallocateState(state); @@ -363,19 +358,19 @@ static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtd #endif bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { - struct GBAExtdata extdata; - GBAExtdataInit(&extdata); + struct mStateExtdata extdata; + mStateExtdataInit(&extdata); if (flags & SAVESTATE_SAVEDATA) { // TODO: A better way to do this would be nice void* sram = malloc(SIZE_CART_FLASH1M); struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M); if (GBASavedataClone(&gba->memory.savedata, svf)) { - struct GBAExtdataItem item = { + struct mStateExtdataItem item = { .size = svf->seek(svf, 0, SEEK_CUR), .data = sram, .clean = free }; - GBAExtdataPut(&extdata, EXTDATA_SAVEDATA, &item); + mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item); } else { free(sram); } @@ -387,12 +382,12 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { cheatVf = VFileMemChunk(0, 0); if (cheatVf) { mCheatSaveFile(device, cheatVf); - struct GBAExtdataItem item = { + struct mStateExtdataItem item = { .size = cheatVf->size(cheatVf), .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ), .clean = 0 }; - GBAExtdataPut(&extdata, EXTDATA_CHEATS, &item); + mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item); } }; #ifdef USE_PNG @@ -403,7 +398,7 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { vf->truncate(vf, sizeof(struct GBASerializedState)); struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE); if (!state) { - GBAExtdataDeinit(&extdata); + mStateExtdataDeinit(&extdata); if (cheatVf) { cheatVf->close(cheatVf); } @@ -412,8 +407,8 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { GBASerialize(gba, state); vf->unmap(vf, state, sizeof(struct GBASerializedState)); vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET); - GBAExtdataSerialize(&extdata, vf); - GBAExtdataDeinit(&extdata); + mStateExtdataSerialize(&extdata, vf); + mStateExtdataDeinit(&extdata); if (cheatVf) { cheatVf->close(cheatVf); } @@ -422,15 +417,15 @@ bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { } else { bool success = _savePNGState(gba, vf, &extdata); - GBAExtdataDeinit(&extdata); + mStateExtdataDeinit(&extdata); return success; } #endif - GBAExtdataDeinit(&extdata); + mStateExtdataDeinit(&extdata); return false; } -struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata) { +struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata) { #ifdef USE_PNG if (isPNG(vf)) { return _loadPNGState(vf, extdata); @@ -446,14 +441,14 @@ struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* return 0; } if (extdata) { - GBAExtdataDeserialize(extdata, vf); + mStateExtdataDeserialize(extdata, vf); } return state; } bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { - struct GBAExtdata extdata; - GBAExtdataInit(&extdata); + struct mStateExtdata extdata; + mStateExtdataInit(&extdata); struct GBASerializedState* state = GBAExtractState(vf, &extdata); if (!state) { return false; @@ -461,8 +456,8 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { bool success = GBADeserialize(gba, state); GBADeallocateState(state); - struct GBAExtdataItem item; - if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { + struct mStateExtdataItem item; + if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) { gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data); mCoreSyncForceFrame(gba->sync); @@ -470,14 +465,14 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot"); } } - if (flags & SAVESTATE_SAVEDATA && GBAExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) { + if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) { struct VFile* svf = VFileFromMemory(item.data, item.size); GBASavedataLoad(&gba->memory.savedata, svf); if (svf) { svf->close(svf); } } - if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE] && GBAExtdataGet(&extdata, EXTDATA_CHEATS, &item)) { + if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE] && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) { if (item.size) { struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]; struct VFile* svf = VFileFromMemory(item.data, item.size); @@ -488,127 +483,10 @@ bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { } } } - GBAExtdataDeinit(&extdata); + mStateExtdataDeinit(&extdata); return success; } -bool GBAExtdataInit(struct GBAExtdata* extdata) { - memset(extdata->data, 0, sizeof(extdata->data)); - return true; -} - -void GBAExtdataDeinit(struct GBAExtdata* extdata) { - size_t i; - for (i = 1; i < EXTDATA_MAX; ++i) { - if (extdata->data[i].data && extdata->data[i].clean) { - extdata->data[i].clean(extdata->data[i].data); - } - } -} - -void GBAExtdataPut(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) { - if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) { - return; - } - - if (extdata->data[tag].data && extdata->data[tag].clean) { - extdata->data[tag].clean(extdata->data[tag].data); - } - extdata->data[tag] = *item; -} - -bool GBAExtdataGet(struct GBAExtdata* extdata, enum GBAExtdataTag tag, struct GBAExtdataItem* item) { - if (tag == EXTDATA_NONE || tag >= EXTDATA_MAX) { - return false; - } - - *item = extdata->data[tag]; - return true; -} - -bool GBAExtdataSerialize(struct GBAExtdata* extdata, struct VFile* vf) { - ssize_t position = vf->seek(vf, 0, SEEK_CUR); - ssize_t size = sizeof(struct GBAExtdataHeader); - size_t i = 0; - for (i = 1; i < EXTDATA_MAX; ++i) { - if (extdata->data[i].data) { - size += sizeof(struct GBAExtdataHeader); - } - } - if (size == sizeof(struct GBAExtdataHeader)) { - return true; - } - struct GBAExtdataHeader* header = malloc(size); - position += size; - - size_t j; - for (i = 1, j = 0; i < EXTDATA_MAX; ++i) { - if (extdata->data[i].data) { - STORE_32(i, offsetof(struct GBAExtdataHeader, tag), &header[j]); - STORE_32(extdata->data[i].size, offsetof(struct GBAExtdataHeader, size), &header[j]); - STORE_64(position, offsetof(struct GBAExtdataHeader, offset), &header[j]); - position += extdata->data[i].size; - ++j; - } - } - header[j].tag = 0; - header[j].size = 0; - header[j].offset = 0; - - if (vf->write(vf, header, size) != size) { - free(header); - return false; - } - free(header); - - for (i = 1; i < EXTDATA_MAX; ++i) { - if (extdata->data[i].data) { - if (vf->write(vf, extdata->data[i].data, extdata->data[i].size) != extdata->data[i].size) { - return false; - } - } - } - return true; -} - -bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf) { - while (true) { - struct GBAExtdataHeader buffer, header; - if (vf->read(vf, &buffer, sizeof(buffer)) != sizeof(buffer)) { - return false; - } - LOAD_32(header.tag, 0, &buffer.tag); - LOAD_32(header.size, 0, &buffer.size); - LOAD_64(header.offset, 0, &buffer.offset); - - if (header.tag == EXTDATA_NONE) { - break; - } - if (header.tag >= EXTDATA_MAX) { - continue; - } - ssize_t position = vf->seek(vf, 0, SEEK_CUR); - if (vf->seek(vf, header.offset, SEEK_SET) < 0) { - return false; - } - struct GBAExtdataItem item = { - .data = malloc(header.size), - .size = header.size, - .clean = free - }; - if (!item.data) { - continue; - } - if (vf->read(vf, item.data, header.size) != header.size) { - free(item.data); - continue; - } - GBAExtdataPut(extdata, header.tag, &item); - vf->seek(vf, position, SEEK_SET); - }; - return true; -} - struct GBASerializedState* GBAAllocateState(void) { return anonymousMemoryMap(sizeof(struct GBASerializedState)); } diff --git a/src/gba/serialize.h b/src/gba/serialize.h index 6c387b34f..51c5d3b58 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -360,28 +360,6 @@ struct GBASerializedState { uint8_t wram[SIZE_WORKING_RAM]; }; -enum GBAExtdataTag { - EXTDATA_NONE = 0, - EXTDATA_SCREENSHOT = 1, - EXTDATA_SAVEDATA = 2, - EXTDATA_CHEATS = 3, - EXTDATA_MAX -}; - -#define SAVESTATE_SCREENSHOT 1 -#define SAVESTATE_SAVEDATA 2 -#define SAVESTATE_CHEATS 4 - -struct GBAExtdataItem { - int32_t size; - void* data; - void (*clean)(void*); -}; - -struct GBAExtdata { - struct GBAExtdataItem data[EXTDATA_MAX]; -}; - struct VDir; void GBASerialize(struct GBA* gba, struct GBASerializedState* state); @@ -390,14 +368,8 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags); bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags); -bool GBAExtdataInit(struct GBAExtdata*); -void GBAExtdataDeinit(struct GBAExtdata*); -void GBAExtdataPut(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*); -bool GBAExtdataGet(struct GBAExtdata*, enum GBAExtdataTag, struct GBAExtdataItem*); -bool GBAExtdataSerialize(struct GBAExtdata* extpdata, struct VFile* vf); -bool GBAExtdataDeserialize(struct GBAExtdata* extdata, struct VFile* vf); - -struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* extdata); +struct mStateExtdata; +struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata); struct GBASerializedState* GBAAllocateState(void); void GBADeallocateState(struct GBASerializedState* state); diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index 9a2b0bb35..fa08d6a96 100644 --- a/src/platform/openemu/mGBAGameCore.m +++ b/src/platform/openemu/mGBAGameCore.m @@ -26,11 +26,12 @@ #include "util/common.h" +#include "core/serialize.h" #include "core/core.h" #include "gba/cheats.h" #include "gba/core.h" +#include "gba/gba.h" #include "gba/input.h" -#include "gba/serialize.h" #include "util/circle-buffer.h" #include "util/memory.h" #include "util/vfs.h" diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index f55403b23..566d66660 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -20,11 +20,11 @@ extern "C" { #include "core/config.h" #include "core/directories.h" +#include "core/serialize.h" #ifdef M_CORE_GBA #include "gba/bios.h" #include "gba/core.h" #include "gba/gba.h" -#include "gba/serialize.h" #include "gba/extra/sharkport.h" #endif #ifdef M_CORE_GB diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 85f63f63f..20b69cbf2 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -15,8 +15,10 @@ #include extern "C" { +#include "core/serialize.h" +#ifdef M_CORE_GBA #include "gba/serialize.h" -#include "gba/video.h" +#endif } using namespace QGBA; @@ -176,21 +178,21 @@ void LoadSaveState::loadState(int slot) { return; } - GBAExtdata extdata; - GBAExtdataInit(&extdata); + mStateExtdata extdata; + mStateExtdataInit(&extdata); GBASerializedState* state = GBAExtractState(vf, &extdata); vf->seek(vf, 0, SEEK_SET); if (!state) { m_slots[slot - 1]->setText(tr("Corrupted")); - GBAExtdataDeinit(&extdata); + mStateExtdataDeinit(&extdata); return; } QDateTime creation(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL)); QImage stateImage; - GBAExtdataItem item; - if (GBAExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) { + mStateExtdataItem item; + if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) { stateImage = QImage((uchar*) item.data, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_ARGB32).rgbSwapped(); } diff --git a/src/platform/qt/SettingsView.cpp b/src/platform/qt/SettingsView.cpp index ac7df35a3..488e09bbc 100644 --- a/src/platform/qt/SettingsView.cpp +++ b/src/platform/qt/SettingsView.cpp @@ -14,7 +14,8 @@ #include "ShortcutView.h" extern "C" { -#include "gba/serialize.h" +#include "core/serialize.h" +#include "gba/gba.h" } using namespace QGBA; diff --git a/src/platform/sdl/sdl-events.c b/src/platform/sdl/sdl-events.c index bc3365c38..651ccc885 100644 --- a/src/platform/sdl/sdl-events.c +++ b/src/platform/sdl/sdl-events.c @@ -6,12 +6,12 @@ #include "sdl-events.h" #include "core/input.h" +#include "core/serialize.h" #include "core/thread.h" #include "debugger/debugger.h" #include "gba/input.h" #include "gba/io.h" #include "gba/rr/rr.h" -#include "gba/serialize.h" #include "gba/video.h" #include "gba/renderers/video-software.h" #include "util/configuration.h" From bd5b3036a12c40569ec4a9897437a041fa267c10 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 May 2016 16:25:01 -0700 Subject: [PATCH 24/90] Core: Migrate extdata to core --- src/feature/gui/gui-runner.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 5ef565fa1..e4686b862 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -8,6 +8,7 @@ #include "core/core.h" #include "core/serialize.h" #include "feature/gui/gui-config.h" +#include "gba/gba.h" #include "gba/input.h" #include "gba/interface.h" #include "util/gui/file-select.h" @@ -64,9 +65,11 @@ static void _drawState(struct GUIBackground* background, void* id) { return; } struct VFile* vf = mCoreGetState(gbaBackground->p->core, stateId, false); + unsigned w, h; + gbaBackground->p->core->desiredVideoDimensions(gbaBackground->p->core, &w, &h); uint32_t* pixels = gbaBackground->screenshot; if (!pixels) { - pixels = anonymousMemoryMap(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); + pixels = anonymousMemoryMap(w * h * 4); gbaBackground->screenshot = pixels; } bool success = false; @@ -76,7 +79,7 @@ static void _drawState(struct GUIBackground* background, void* id) { png_infop end = png_create_info_struct(png); if (png && info && end) { success = PNGReadHeader(png, info); - success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS); + success = success && PNGReadPixels(png, info, pixels, w, h, w); success = success && PNGReadFooter(png, end); } PNGReadClose(png, info, end); @@ -376,7 +379,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) { runner->core->unloadROM(runner->core); drawState.screenshotId = 0; if (drawState.screenshot) { - mappedMemoryFree(drawState.screenshot, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); + unsigned w, h; + runner->core->desiredVideoDimensions(runner->core, &w, &h); + mappedMemoryFree(drawState.screenshot, w * h * 4); } if (runner->core->config.port) { From f3d3c59df7dccf6afcf353c37de96f14c192584e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 28 May 2016 18:30:42 -0700 Subject: [PATCH 25/90] GB: Initialize biosVf --- src/gb/gb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gb/gb.c b/src/gb/gb.c index f739d78b7..ac157f3ad 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -56,6 +56,7 @@ static void GBInit(void* cpu, struct mCPUComponent* component) { gb->timer.p = gb; + gb->biosVf = 0; gb->romVf = 0; gb->sramVf = 0; From f4dc546da68a757b6e8c04a379583909902f3628 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 May 2016 09:57:10 -0700 Subject: [PATCH 26/90] Util: Fix realloc semantics in utf16to8 --- CHANGES | 1 + src/util/string.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c257ce6a6..2d37353d7 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ Bugfixes: - All: Fix instruction tables getting zeroed when linking sometimes - SDL: Fix SDL 1.2 build - ARM7: Fix flags on SBC/RSC + - Util: Fix realloc semantics in utf16to8 Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/util/string.c b/src/util/string.c index a2a23dea0..08e4a4292 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -202,8 +202,9 @@ char* utf16to8(const uint16_t* utf16, size_t length) { } char* newUTF8 = realloc(utf8, utf8Length + 1); - if (newUTF8 != utf8) { + if (!newUTF8) { free(utf8); + return 0; } newUTF8[utf8Length] = '\0'; return newUTF8; From 13c882411a03ce2adbd1492f7f4fef877b526c7f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 May 2016 11:16:51 -0700 Subject: [PATCH 27/90] GB: Fix IRQs trampling each other if two happen mid-instruction --- src/gb/gb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gb/gb.c b/src/gb/gb.c index ac157f3ad..9d050867f 100644 --- a/src/gb/gb.c +++ b/src/gb/gb.c @@ -265,7 +265,7 @@ void GBUpdateIRQs(struct GB* gb) { } gb->cpu->halted = false; - if (!gb->memory.ime) { + if (!gb->memory.ime || gb->cpu->irqPending) { return; } From 5f95e7e486746572f2fd61cbc75667b7adceca7e Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 May 2016 12:33:40 -0700 Subject: [PATCH 28/90] GB Core: Fix uninitialized variable --- src/gb/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gb/core.c b/src/gb/core.c index ec2ca3c52..c746a204b 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -45,6 +45,7 @@ static bool _GBCoreInit(struct mCore* core) { LR35902Init(cpu); GBVideoSoftwareRendererCreate(&gbcore->renderer); + gbcore->renderer.outputBuffer = NULL; gbcore->keys = 0; gb->keySource = &gbcore->keys; From 0c59856fe364ff949eaee7f76f266292d600dada Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 29 May 2016 23:32:18 -0700 Subject: [PATCH 29/90] GB Video: Remove broken hack --- src/gb/renderers/software.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gb/renderers/software.c b/src/gb/renderers/software.c index 6eb78876a..111a72ae3 100644 --- a/src/gb/renderers/software.c +++ b/src/gb/renderers/software.c @@ -73,7 +73,6 @@ static uint8_t GBVideoSoftwareRendererWriteVideoRegister(struct GBVideoRenderer* break; case REG_WY: softwareRenderer->wy = value; - softwareRenderer->currentWy = value; break; case REG_WX: softwareRenderer->wx = value; From f40b9ec82edbb852f2ac0677fda01b5728a3bfba Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 12:12:00 -0700 Subject: [PATCH 30/90] GB: Fix up debugger a bit --- src/gb/cli.c | 16 +++++++- src/gb/io.c | 58 +++++++++++++++++++++++++++++ src/gb/io.h | 5 ++- src/gba/io.c | 2 +- src/gba/io.h | 2 +- src/lr35902/debugger/cli-debugger.c | 2 +- 6 files changed, 80 insertions(+), 5 deletions(-) diff --git a/src/gb/cli.c b/src/gb/cli.c index 132e6747f..7a166855e 100644 --- a/src/gb/cli.c +++ b/src/gb/cli.c @@ -16,6 +16,7 @@ static void _GBCLIDebuggerInit(struct CLIDebuggerSystem*); static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*); +static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); @@ -31,7 +32,7 @@ struct CLIDebuggerSystem* GBCLIDebuggerCreate(struct mCore* core) { debugger->d.init = _GBCLIDebuggerInit; debugger->d.deinit = NULL; debugger->d.custom = _GBCLIDebuggerCustom; - debugger->d.lookupIdentifier = NULL; + debugger->d.lookupIdentifier = _GBCLIDebuggerLookupIdentifier; debugger->d.name = "Game Boy"; debugger->d.commands = _GBCLIDebuggerCommands; @@ -62,6 +63,19 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem* debugger) { return false; } +static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem* debugger, const char* name, struct CLIDebugVector* dv) { + UNUSED(debugger); + int i; + for (i = 0; i < REG_MAX; ++i) { + const char* reg = GBIORegisterNames[i]; + if (reg && strcasecmp(reg, name) == 0) { + return GB_BASE_IO | i; + } + } + dv->type = CLIDV_ERROR_TYPE; + return 0; +} + static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); debugger->d.state = DEBUGGER_CUSTOM; diff --git a/src/gb/io.c b/src/gb/io.c index 5eed35823..34107601f 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -9,6 +9,64 @@ mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O"); +const char* const GBIORegisterNames[] = { + [REG_JOYP] = "JOYP", + [REG_SB] = "SB", + [REG_SC] = "SC", + [REG_DIV] = "DIV", + [REG_TIMA] = "TIMA", + [REG_TMA] = "TMA", + [REG_TAC] = "TAC", + [REG_IF] = "IF", + [REG_NR10] = "NR10", + [REG_NR11] = "NR11", + [REG_NR12] = "NR12", + [REG_NR13] = "NR13", + [REG_NR14] = "NR14", + [REG_NR21] = "NR21", + [REG_NR22] = "NR22", + [REG_NR23] = "NR23", + [REG_NR24] = "NR24", + [REG_NR30] = "NR30", + [REG_NR31] = "NR31", + [REG_NR32] = "NR32", + [REG_NR33] = "NR33", + [REG_NR34] = "NR34", + [REG_NR41] = "NR41", + [REG_NR42] = "NR42", + [REG_NR43] = "NR43", + [REG_NR44] = "NR44", + [REG_NR50] = "NR50", + [REG_NR51] = "NR51", + [REG_NR52] = "NR52", + [REG_LCDC] = "LCDC", + [REG_STAT] = "STAT", + [REG_SCY] = "SCY", + [REG_SCX] = "SCX", + [REG_LY] = "LY", + [REG_LYC] = "LYC", + [REG_DMA] = "DMA", + [REG_BGP] = "BGP", + [REG_OBP0] = "OBP0", + [REG_OBP1] = "OBP1", + [REG_WY] = "WY", + [REG_WX] = "WX", + [REG_KEY1] = "KEY1", + [REG_VBK] = "VBK", + [REG_HDMA1] = "HDMA1", + [REG_HDMA2] = "HDMA2", + [REG_HDMA3] = "HDMA3", + [REG_HDMA4] = "HDMA4", + [REG_HDMA5] = "HDMA5", + [REG_RP] = "RP", + [REG_BCPS] = "BCPS", + [REG_BCPD] = "BCPD", + [REG_OCPS] = "OCPS", + [REG_OCPD] = "OCPD", + [REG_SVBK] = "SVBK", + [REG_IE] = "IE", +}; + static const uint8_t _registerMask[] = { [REG_SC] = 0x7E, // TODO: GBC differences [REG_IF] = 0xE0, diff --git a/src/gb/io.h b/src/gb/io.h index 7c5cec231..88e4543b1 100644 --- a/src/gb/io.h +++ b/src/gb/io.h @@ -101,9 +101,12 @@ enum GBIORegisters { REG_UNK74 = 0x74, REG_UNK75 = 0x75, REG_UNK76 = 0x76, - REG_UNK77 = 0x77 + REG_UNK77 = 0x77, + REG_MAX = 0x100 }; +extern const char* const GBIORegisterNames[]; + struct GB; void GBIOInit(struct GB* gb); void GBIOReset(struct GB* gb); diff --git a/src/gba/io.c b/src/gba/io.c index ec340bf90..84b7a0043 100644 --- a/src/gba/io.c +++ b/src/gba/io.c @@ -12,7 +12,7 @@ mLOG_DEFINE_CATEGORY(GBA_IO, "GBA I/O"); -const char* GBAIORegisterNames[] = { +const char* const GBAIORegisterNames[] = { // Video "DISPCNT", 0, diff --git a/src/gba/io.h b/src/gba/io.h index b66cc2a39..a47662a64 100644 --- a/src/gba/io.h +++ b/src/gba/io.h @@ -155,7 +155,7 @@ enum GBAIORegisters { mLOG_DECLARE_CATEGORY(GBA_IO); -extern const char* GBAIORegisterNames[]; +extern const char* const GBAIORegisterNames[]; void GBAIOInit(struct GBA* gba); void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value); diff --git a/src/lr35902/debugger/cli-debugger.c b/src/lr35902/debugger/cli-debugger.c index d6badb25f..6773a7120 100644 --- a/src/lr35902/debugger/cli-debugger.c +++ b/src/lr35902/debugger/cli-debugger.c @@ -87,7 +87,7 @@ void LR35902CLIDebuggerCreate(struct CLIDebuggerSystem* debugger) { debugger->disassemble = NULL; debugger->lookupPlatformIdentifier = _lookupPlatformIdentifier; debugger->platformName = "GB-Z80"; - debugger->platformCommands = NULL; + debugger->platformCommands = _lr35902Commands; } #endif From cf8868d5cb5cfd8244713328f96d8fdd814f1f83 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 12:52:03 -0700 Subject: [PATCH 31/90] GB: Fix timer resetting --- src/gb/timer.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/gb/timer.c b/src/gb/timer.c index 881632205..8757af1f7 100644 --- a/src/gb/timer.c +++ b/src/gb/timer.c @@ -30,13 +30,19 @@ int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) { if (timer->nextTima != INT_MAX) { timer->nextTima -= timer->eventDiff; if (timer->nextTima <= 0) { - ++timer->p->memory.io[REG_TIMA]; if (!timer->p->memory.io[REG_TIMA]) { timer->p->memory.io[REG_TIMA] = timer->p->memory.io[REG_TMA]; timer->p->memory.io[REG_IF] |= (1 << GB_IRQ_TIMER); GBUpdateIRQs(timer->p); + timer->nextTima = timer->timaPeriod - 4; + } else { + ++timer->p->memory.io[REG_TIMA]; + if (!timer->p->memory.io[REG_TIMA]) { + timer->nextTima = 4; + } else { + timer->nextTima = timer->timaPeriod; + } } - timer->nextTima = timer->timaPeriod; } if (timer->nextTima < timer->nextEvent) { timer->nextEvent = timer->nextTima; @@ -51,8 +57,8 @@ int32_t GBTimerProcessEvents(struct GBTimer* timer, int32_t cycles) { void GBTimerDivReset(struct GBTimer* timer) { timer->p->memory.io[REG_DIV] = 0; timer->nextDiv = timer->eventDiff + timer->p->cpu->cycles + GB_DMG_DIV_PERIOD; - if (timer->eventDiff + GB_DMG_DIV_PERIOD < timer->nextEvent) { - timer->nextEvent = timer->eventDiff + GB_DMG_DIV_PERIOD; + if (timer->nextDiv - timer->eventDiff < timer->nextEvent) { + timer->nextEvent = timer->nextDiv - timer->eventDiff; if (timer->nextEvent < timer->p->cpu->nextEvent) { timer->p->cpu->nextEvent = timer->nextEvent; } @@ -84,8 +90,8 @@ uint8_t GBTimerUpdateTAC(struct GBTimer* timer, GBRegisterTAC tac) { void GBTimerUpdateTIMA(struct GBTimer* timer) { timer->nextTima = timer->eventDiff + timer->p->cpu->cycles + timer->timaPeriod; - if (timer->eventDiff + timer->timaPeriod < timer->nextEvent) { - timer->nextEvent = timer->eventDiff + timer->timaPeriod; + if (timer->nextTima - timer->eventDiff < timer->nextEvent) { + timer->nextEvent = timer->nextTima - timer->eventDiff; if (timer->nextEvent < timer->p->cpu->nextEvent) { timer->p->cpu->nextEvent = timer->nextEvent; } From a3a380d9f551bbc5ad726ef07c11d20c0263d1e5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 15:01:40 -0700 Subject: [PATCH 32/90] Core: Refactor savestates --- src/core/core.c | 4 +- src/core/core.h | 6 +- src/core/serialize.c | 311 ++++++++++++++++++++++++++++ src/core/serialize.h | 5 + src/gba/core.c | 15 +- src/gba/rr/rr.c | 4 +- src/gba/serialize.c | 279 ------------------------- src/gba/serialize.h | 5 - src/platform/openemu/mGBAGameCore.m | 8 +- src/platform/qt/GameController.cpp | 4 +- src/platform/qt/LoadSaveState.cpp | 7 +- src/platform/test/fuzz-main.c | 2 +- src/platform/test/perf-main.c | 2 +- 13 files changed, 347 insertions(+), 305 deletions(-) diff --git a/src/core/core.c b/src/core/core.c index dbe11b781..ea5d17971 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -108,7 +108,7 @@ bool mCoreSaveState(struct mCore* core, int slot, int flags) { if (!vf) { return false; } - bool success = core->saveState(core, vf, flags); + bool success = mCoreSaveStateNamed(core, vf, flags); vf->close(vf); if (success) { mLOG(STATUS, INFO, "State %i saved", slot); @@ -124,7 +124,7 @@ bool mCoreLoadState(struct mCore* core, int slot, int flags) { if (!vf) { return false; } - bool success = core->loadState(core, vf, flags); + bool success = mCoreLoadStateNamed(core, vf, flags); vf->close(vf); if (success) { mLOG(STATUS, INFO, "State %i loaded", slot); diff --git a/src/core/core.h b/src/core/core.h index 3fedc2a17..b1e5563a0 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -31,6 +31,7 @@ enum mPlatform { struct mRTCSource; struct mCoreConfig; struct mCoreSync; +struct mStateExtdata; struct mCore { void* cpu; void* board; @@ -78,8 +79,9 @@ struct mCore { void (*runLoop)(struct mCore*); void (*step)(struct mCore*); - bool (*loadState)(struct mCore*, struct VFile*, int flags); - bool (*saveState)(struct mCore*, struct VFile*, int flags); + size_t (*stateSize)(struct mCore*); + bool (*loadState)(struct mCore*, const void* state); + bool (*saveState)(struct mCore*, void* state); void (*setKeys)(struct mCore*, uint32_t keys); void (*addKeys)(struct mCore*, uint32_t keys); diff --git a/src/core/serialize.c b/src/core/serialize.c index a3b4c91dc..5049f44a4 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -5,8 +5,26 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "serialize.h" +#include "core/core.h" +#include "core/cheats.h" +#include "core/sync.h" +#include "util/memory.h" #include "util/vfs.h" +#ifdef USE_PNG +#include "util/png-io.h" +#include +#include +#endif + +mLOG_DEFINE_CATEGORY(SAVESTATE, "Savestate"); + +struct mBundledState { + size_t stateSize; + void* state; + struct mStateExtdata* extdata; +}; + struct mStateExtdataHeader { uint32_t tag; int32_t size; @@ -129,3 +147,296 @@ bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) { }; return true; } + + + + + + + +#ifdef USE_PNG +static bool _savePNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) { + size_t stride; + color_t* pixels = 0; + + core->getVideoBuffer(core, &pixels, &stride); + if (!pixels) { + return false; + } + + size_t stateSize = core->stateSize(core); + void* state = anonymousMemoryMap(stateSize); + if (!state) { + return false; + } + core->saveState(core, state); + + uLongf len = compressBound(stateSize); + void* buffer = malloc(len); + if (!buffer) { + mappedMemoryFree(state, stateSize); + return false; + } + compress(buffer, &len, (const Bytef*) state, stateSize); + mappedMemoryFree(state, stateSize); + + unsigned width, height; + core->desiredVideoDimensions(core, &width, &height); + png_structp png = PNGWriteOpen(vf); + png_infop info = PNGWriteHeader(png, width, height); + if (!png || !info) { + PNGWriteClose(png, info); + free(buffer); + return false; + } + PNGWritePixels(png, width, height, stride, pixels); + PNGWriteCustomChunk(png, "gbAs", len, buffer); + if (extdata) { + uint32_t i; + for (i = 1; i < EXTDATA_MAX; ++i) { + if (!extdata->data[i].data) { + continue; + } + uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2; + uint32_t* data = malloc(len); + if (!data) { + continue; + } + STORE_32LE(i, 0, data); + STORE_32LE(extdata->data[i].size, sizeof(uint32_t), data); + compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size); + PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data); + free(data); + } + } + PNGWriteClose(png, info); + free(buffer); + return true; +} + +static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { + struct mBundledState* bundle = png_get_user_chunk_ptr(png); + if (!bundle) { + return 0; + } + if (!strcmp((const char*) chunk->name, "gbAs")) { + void* state = bundle->state; + if (!state) { + return 0; + } + uLongf len = bundle->stateSize; + uncompress((Bytef*) state, &len, chunk->data, chunk->size); + return 1; + } + if (!strcmp((const char*) chunk->name, "gbAx")) { + struct mStateExtdata* extdata = bundle->extdata; + if (!extdata) { + return 0; + } + struct mStateExtdataItem item; + if (chunk->size < sizeof(uint32_t) * 2) { + return 0; + } + uint32_t tag; + LOAD_32LE(tag, 0, chunk->data); + LOAD_32LE(item.size, sizeof(uint32_t), chunk->data); + uLongf len = item.size; + if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) { + return 0; + } + item.data = malloc(item.size); + item.clean = free; + if (!item.data) { + return 0; + } + const uint8_t* data = chunk->data; + data += sizeof(uint32_t) * 2; + uncompress((Bytef*) item.data, &len, data, chunk->size); + item.size = len; + mStateExtdataPut(extdata, tag, &item); + return 1; + } + return 0; +} + +static void* _loadPNGState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) { + png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); + png_infop info = png_create_info_struct(png); + png_infop end = png_create_info_struct(png); + if (!png || !info || !end) { + PNGReadClose(png, info, end); + return false; + } + unsigned width, height; + core->desiredVideoDimensions(core, &width, &height); + uint32_t* pixels = malloc(width * height * 4); + if (!pixels) { + PNGReadClose(png, info, end); + return false; + } + + size_t stateSize = core->stateSize(core); + void* state = anonymousMemoryMap(stateSize); + struct mBundledState bundle = { + .stateSize = stateSize, + .state = state, + .extdata = extdata + }; + + PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx"); + bool success = PNGReadHeader(png, info); + success = success && PNGReadPixels(png, info, pixels, width, height, width); + success = success && PNGReadFooter(png, end); + PNGReadClose(png, info, end); + + if (success) { + struct mStateExtdataItem item = { + .size = width * height * 4, + .data = pixels, + .clean = free + }; + mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item); + } else { + free(pixels); + mappedMemoryFree(state, stateSize); + return 0; + } + return state; +} +#endif + +bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { + struct mStateExtdata extdata; + mStateExtdataInit(&extdata); + size_t stateSize = core->stateSize(core); + if (flags & SAVESTATE_SAVEDATA) { + /* // TODO: A better way to do this would be nice + void* sram = malloc(SIZE_CART_FLASH1M); + struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M); + if (GBASavedataClone(&gba->memory.savedata, svf)) { + struct mStateExtdataItem item = { + .size = svf->seek(svf, 0, SEEK_CUR), + .data = sram, + .clean = free + }; + mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item); + } else { + free(sram); + } + svf->close(svf);*/ + } + struct VFile* cheatVf = 0; + struct mCheatDevice* device; + if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core))) { + cheatVf = VFileMemChunk(0, 0); + if (cheatVf) { + mCheatSaveFile(device, cheatVf); + struct mStateExtdataItem item = { + .size = cheatVf->size(cheatVf), + .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ), + .clean = 0 + }; + mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item); + } + } +#ifdef USE_PNG + if (!(flags & SAVESTATE_SCREENSHOT)) { +#else + UNUSED(flags); +#endif + vf->truncate(vf, stateSize); + struct GBASerializedState* state = vf->map(vf, stateSize, MAP_WRITE); + if (!state) { + mStateExtdataDeinit(&extdata); + if (cheatVf) { + cheatVf->close(cheatVf); + } + return false; + } + core->saveState(core, state); + vf->unmap(vf, state, stateSize); + vf->seek(vf, stateSize, SEEK_SET); + mStateExtdataSerialize(&extdata, vf); + mStateExtdataDeinit(&extdata); + if (cheatVf) { + cheatVf->close(cheatVf); + } + return true; +#ifdef USE_PNG + } + else { + bool success = _savePNGState(core, vf, &extdata); + mStateExtdataDeinit(&extdata); + return success; + } +#endif + mStateExtdataDeinit(&extdata); + return false; +} + +void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata) { +#ifdef USE_PNG + if (isPNG(vf)) { + return _loadPNGState(core, vf, extdata); + } +#endif + ssize_t stateSize = core->stateSize(core); + vf->seek(vf, 0, SEEK_SET); + if (vf->size(vf) < stateSize) { + return false; + } + void* state = anonymousMemoryMap(stateSize); + if (vf->read(vf, state, stateSize) != stateSize) { + mappedMemoryFree(state, stateSize); + return 0; + } + if (extdata) { + mStateExtdataDeserialize(extdata, vf); + } + return state; +} + +bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { + struct mStateExtdata extdata; + mStateExtdataInit(&extdata); + void* state = mCoreExtractState(core, vf, &extdata); + if (!state) { + return false; + } + bool success = core->loadState(core, state); + mappedMemoryFree(state, core->stateSize(core)); + + unsigned width, height; + core->desiredVideoDimensions(core, &width, &height); + + struct mStateExtdataItem item; + if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { + if (item.size >= (int) (width * height) * 4) { + /*gba->video.renderer->putPixels(gba->video.renderer, width, item.data); + mCoreSyncForceFrame(core->sync);*/ + } else { + mLOG(SAVESTATE, WARN, "Savestate includes invalid screenshot"); + } + } + if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) { + /*struct VFile* svf = VFileFromMemory(item.data, item.size); + GBASavedataLoad(&gba->memory.savedata, svf); + if (svf) { + svf->close(svf); + }*/ + } + struct mCheatDevice* device; + if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) { + if (item.size) { + struct VFile* svf = VFileFromMemory(item.data, item.size); + if (svf) { + mCheatDeviceClear(device); + mCheatParseFile(device, svf); + svf->close(svf); + } + } + } + mStateExtdataDeinit(&extdata); + return success; +} + diff --git a/src/core/serialize.h b/src/core/serialize.h index c51b811bf..d639aaae4 100644 --- a/src/core/serialize.h +++ b/src/core/serialize.h @@ -39,4 +39,9 @@ struct VFile; bool mStateExtdataSerialize(struct mStateExtdata* extdata, struct VFile* vf); bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf); +struct mCore; +bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags); +bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags); +void* mCoreExtractState(struct mCore* core, struct VFile* vf, struct mStateExtdata* extdata); + #endif diff --git a/src/gba/core.c b/src/gba/core.c index 4eef3da65..8d9c7beb4 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -244,12 +244,18 @@ static void _GBACoreStep(struct mCore* core) { ARMRun(core->cpu); } -static bool _GBACoreLoadState(struct mCore* core, struct VFile* vf, int flags) { - return GBALoadStateNamed(core->board, vf, flags); +static size_t _GBACoreStateSize(struct mCore* core) { + UNUSED(core); + return sizeof(struct GBASerializedState); } -static bool _GBACoreSaveState(struct mCore* core, struct VFile* vf, int flags) { - return GBASaveStateNamed(core->board, vf, flags); +static bool _GBACoreLoadState(struct mCore* core, const void* state) { + return GBADeserialize(core->board, state); +} + +static bool _GBACoreSaveState(struct mCore* core, void* state) { + GBASerialize(core->board, state); + return true; } static void _GBACoreSetKeys(struct mCore* core, uint32_t keys) { @@ -452,6 +458,7 @@ struct mCore* GBACoreCreate(void) { core->runFrame = _GBACoreRunFrame; core->runLoop = _GBACoreRunLoop; core->step = _GBACoreStep; + core->stateSize = _GBACoreStateSize; core->loadState = _GBACoreLoadState; core->saveState = _GBACoreSaveState; core->setKeys = _GBACoreSetKeys; diff --git a/src/gba/rr/rr.c b/src/gba/rr/rr.c index ad6375115..24edae7c3 100644 --- a/src/gba/rr/rr.c +++ b/src/gba/rr/rr.c @@ -30,7 +30,7 @@ void GBARRInitRecord(struct GBA* gba) { if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR); - GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA); + //GBASaveStateNamed(gba, vf, SAVESTATE_SAVEDATA); vf->close(vf); } else { ARMReset(gba->cpu); @@ -54,7 +54,7 @@ void GBARRInitPlay(struct GBA* gba) { if (gba->rr->initFrom & INIT_FROM_SAVESTATE) { struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY); - GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA); + //GBALoadStateNamed(gba, vf, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA); vf->close(vf); } else { ARMReset(gba->cpu); diff --git a/src/gba/serialize.c b/src/gba/serialize.c index d8e35364d..469b616a7 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -6,7 +6,6 @@ #include "serialize.h" #include "core/serialize.h" -#include "core/sync.h" #include "gba/audio.h" #include "gba/cheats.h" #include "gba/io.h" @@ -23,12 +22,6 @@ #include #endif -#ifdef USE_PNG -#include "util/png-io.h" -#include -#include -#endif - const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000; const uint32_t GBA_SAVESTATE_VERSION = 0x00000001; @@ -215,278 +208,6 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) { return true; } -#ifdef USE_PNG -static bool _savePNGState(struct GBA* gba, struct VFile* vf, struct mStateExtdata* extdata) { - unsigned stride; - const void* pixels = 0; - gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels); - if (!pixels) { - return false; - } - - struct GBASerializedState* state = GBAAllocateState(); - if (!state) { - return false; - } - GBASerialize(gba, state); - uLongf len = compressBound(sizeof(*state)); - void* buffer = malloc(len); - if (!buffer) { - GBADeallocateState(state); - return false; - } - compress(buffer, &len, (const Bytef*) state, sizeof(*state)); - GBADeallocateState(state); - - png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - if (!png || !info) { - PNGWriteClose(png, info); - free(buffer); - return false; - } - PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); - PNGWriteCustomChunk(png, "gbAs", len, buffer); - if (extdata) { - uint32_t i; - for (i = 1; i < EXTDATA_MAX; ++i) { - if (!extdata->data[i].data) { - continue; - } - uLongf len = compressBound(extdata->data[i].size) + sizeof(uint32_t) * 2; - uint32_t* data = malloc(len); - if (!data) { - continue; - } - STORE_32(i, 0, data); - STORE_32(extdata->data[i].size, sizeof(uint32_t), data); - compress((Bytef*) (data + 2), &len, extdata->data[i].data, extdata->data[i].size); - PNGWriteCustomChunk(png, "gbAx", len + sizeof(uint32_t) * 2, data); - free(data); - } - } - PNGWriteClose(png, info); - free(buffer); - return true; -} - -static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) { - struct GBABundledState* bundle = png_get_user_chunk_ptr(png); - if (!bundle) { - return 0; - } - if (!strcmp((const char*) chunk->name, "gbAs")) { - struct GBASerializedState* state = bundle->state; - if (!state) { - return 0; - } - uLongf len = sizeof(*state); - uncompress((Bytef*) state, &len, chunk->data, chunk->size); - return 1; - } - if (!strcmp((const char*) chunk->name, "gbAx")) { - struct mStateExtdata* extdata = bundle->extdata; - if (!extdata) { - return 0; - } - struct mStateExtdataItem item; - if (chunk->size < sizeof(uint32_t) * 2) { - return 0; - } - uint32_t tag; - LOAD_32(tag, 0, chunk->data); - LOAD_32(item.size, sizeof(uint32_t), chunk->data); - uLongf len = item.size; - if (item.size < 0 || tag == EXTDATA_NONE || tag >= EXTDATA_MAX) { - return 0; - } - item.data = malloc(item.size); - item.clean = free; - if (!item.data) { - return 0; - } - const uint8_t* data = chunk->data; - data += sizeof(uint32_t) * 2; - uncompress((Bytef*) item.data, &len, data, chunk->size); - item.size = len; - mStateExtdataPut(extdata, tag, &item); - return 1; - } - return 0; -} - -static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct mStateExtdata* extdata) { - png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES); - png_infop info = png_create_info_struct(png); - png_infop end = png_create_info_struct(png); - if (!png || !info || !end) { - PNGReadClose(png, info, end); - return false; - } - uint32_t* pixels = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4); - if (!pixels) { - PNGReadClose(png, info, end); - return false; - } - - struct GBASerializedState* state = GBAAllocateState(); - struct GBABundledState bundle = { - .state = state, - .extdata = extdata - }; - - PNGInstallChunkHandler(png, &bundle, _loadPNGChunkHandler, "gbAs gbAx"); - bool success = PNGReadHeader(png, info); - success = success && PNGReadPixels(png, info, pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS); - success = success && PNGReadFooter(png, end); - PNGReadClose(png, info, end); - - if (success) { - struct mStateExtdataItem item = { - .size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4, - .data = pixels, - .clean = free - }; - mStateExtdataPut(extdata, EXTDATA_SCREENSHOT, &item); - } else { - free(pixels); - GBADeallocateState(state); - return 0; - } - return state; -} -#endif - -bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags) { - struct mStateExtdata extdata; - mStateExtdataInit(&extdata); - if (flags & SAVESTATE_SAVEDATA) { - // TODO: A better way to do this would be nice - void* sram = malloc(SIZE_CART_FLASH1M); - struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M); - if (GBASavedataClone(&gba->memory.savedata, svf)) { - struct mStateExtdataItem item = { - .size = svf->seek(svf, 0, SEEK_CUR), - .data = sram, - .clean = free - }; - mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item); - } else { - free(sram); - } - svf->close(svf); - } - struct VFile* cheatVf = 0; - if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]) { - struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]; - cheatVf = VFileMemChunk(0, 0); - if (cheatVf) { - mCheatSaveFile(device, cheatVf); - struct mStateExtdataItem item = { - .size = cheatVf->size(cheatVf), - .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ), - .clean = 0 - }; - mStateExtdataPut(&extdata, EXTDATA_CHEATS, &item); - } - }; -#ifdef USE_PNG - if (!(flags & SAVESTATE_SCREENSHOT)) { -#else - UNUSED(flags); -#endif - vf->truncate(vf, sizeof(struct GBASerializedState)); - struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE); - if (!state) { - mStateExtdataDeinit(&extdata); - if (cheatVf) { - cheatVf->close(cheatVf); - } - return false; - } - GBASerialize(gba, state); - vf->unmap(vf, state, sizeof(struct GBASerializedState)); - vf->seek(vf, sizeof(struct GBASerializedState), SEEK_SET); - mStateExtdataSerialize(&extdata, vf); - mStateExtdataDeinit(&extdata); - if (cheatVf) { - cheatVf->close(cheatVf); - } - return true; -#ifdef USE_PNG - } - else { - bool success = _savePNGState(gba, vf, &extdata); - mStateExtdataDeinit(&extdata); - return success; - } -#endif - mStateExtdataDeinit(&extdata); - return false; -} - -struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata) { -#ifdef USE_PNG - if (isPNG(vf)) { - return _loadPNGState(vf, extdata); - } -#endif - vf->seek(vf, 0, SEEK_SET); - if (vf->size(vf) < (ssize_t) sizeof(struct GBASerializedState)) { - return false; - } - struct GBASerializedState* state = GBAAllocateState(); - if (vf->read(vf, state, sizeof(*state)) != sizeof(*state)) { - GBADeallocateState(state); - return 0; - } - if (extdata) { - mStateExtdataDeserialize(extdata, vf); - } - return state; -} - -bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { - struct mStateExtdata extdata; - mStateExtdataInit(&extdata); - struct GBASerializedState* state = GBAExtractState(vf, &extdata); - if (!state) { - return false; - } - bool success = GBADeserialize(gba, state); - GBADeallocateState(state); - - struct mStateExtdataItem item; - if (flags & SAVESTATE_SCREENSHOT && mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item)) { - if (item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) { - gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, item.data); - mCoreSyncForceFrame(gba->sync); - } else { - mLOG(GBA_STATE, WARN, "Savestate includes invalid screenshot"); - } - } - if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) { - struct VFile* svf = VFileFromMemory(item.data, item.size); - GBASavedataLoad(&gba->memory.savedata, svf); - if (svf) { - svf->close(svf); - } - } - if (flags & SAVESTATE_CHEATS && gba->cpu->components && gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE] && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) { - if (item.size) { - struct mCheatDevice* device = (struct mCheatDevice*) gba->cpu->components[CPU_COMPONENT_CHEAT_DEVICE]; - struct VFile* svf = VFileFromMemory(item.data, item.size); - if (svf) { - mCheatDeviceClear(device); - mCheatParseFile(device, svf); - svf->close(svf); - } - } - } - mStateExtdataDeinit(&extdata); - return success; -} - struct GBASerializedState* GBAAllocateState(void) { return anonymousMemoryMap(sizeof(struct GBASerializedState)); } diff --git a/src/gba/serialize.h b/src/gba/serialize.h index 51c5d3b58..be2838904 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -365,11 +365,6 @@ struct VDir; void GBASerialize(struct GBA* gba, struct GBASerializedState* state); bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state); -bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, int flags); -bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags); - -struct mStateExtdata; -struct GBASerializedState* GBAExtractState(struct VFile* vf, struct mStateExtdata* extdata); struct GBASerializedState* GBAAllocateState(void); void GBADeallocateState(struct GBASerializedState* state); diff --git a/src/platform/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index fa08d6a96..91ef7b892 100644 --- a/src/platform/openemu/mGBAGameCore.m +++ b/src/platform/openemu/mGBAGameCore.m @@ -196,7 +196,7 @@ - (NSData *)serializeStateWithError:(NSError **)outError { struct VFile* vf = VFileMemChunk(nil, 0); - if (!core->saveState(core, vf, SAVESTATE_SAVEDATA)) { + if (!mCoreSaveStateNamed(core, vf, SAVESTATE_SAVEDATA)) { *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil]; vf->close(vf); return nil; @@ -212,7 +212,7 @@ - (BOOL)deserializeState:(NSData *)state withError:(NSError **)outError { struct VFile* vf = VFileFromConstMemory(state.bytes, state.length); - if (!core->loadState(core, vf, SAVESTATE_SAVEDATA)) { + if (!mCoreLoadStateNamed(core, vf, SAVESTATE_SAVEDATA)) { *outError = [NSError errorWithDomain:OEGameCoreErrorDomain code:OEGameCoreCouldNotLoadStateError userInfo:nil]; vf->close(vf); return NO; @@ -224,14 +224,14 @@ - (void)saveStateToFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block { struct VFile* vf = VFileOpen([fileName UTF8String], O_CREAT | O_TRUNC | O_RDWR); - block(core->saveState(core, vf, 0), nil); + block(mCoreSaveStateNamed(core, vf, 0), nil); vf->close(vf); } - (void)loadStateFromFileAtPath:(NSString *)fileName completionHandler:(void (^)(BOOL, NSError *))block { struct VFile* vf = VFileOpen([fileName UTF8String], O_RDONLY); - block(core->loadState(core, vf, 0), nil); + block(mCoreLoadStateNamed(core, vf, 0), nil); vf->close(vf); } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index 566d66660..dcc8ddadc 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -788,7 +788,7 @@ void GameController::loadState(int slot) { if (!controller->m_backupLoadState) { controller->m_backupLoadState = VFileMemChunk(nullptr, 0); } - context->core->saveState(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); + mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags); if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) { controller->frameAvailable(controller->m_drawContext); controller->stateLoaded(context); @@ -824,7 +824,7 @@ void GameController::loadBackupState() { mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) { GameController* controller = static_cast(context->userData); controller->m_backupLoadState->seek(controller->m_backupLoadState, 0, SEEK_SET); - if (context->core->loadState(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) { + if (mCoreLoadStateNamed(context->core, controller->m_backupLoadState, controller->m_loadStateFlags)) { mLOG(STATUS, INFO, "Undid state load"); controller->frameAvailable(controller->m_drawContext); controller->stateLoaded(context); diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 20b69cbf2..e4ffb01ec 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -19,6 +19,7 @@ extern "C" { #ifdef M_CORE_GBA #include "gba/serialize.h" #endif +#include "util/memory.h" } using namespace QGBA; @@ -180,7 +181,7 @@ void LoadSaveState::loadState(int slot) { mStateExtdata extdata; mStateExtdataInit(&extdata); - GBASerializedState* state = GBAExtractState(vf, &extdata); + void* state = mCoreExtractState(thread->core, vf, &extdata); vf->seek(vf, 0, SEEK_SET); if (!state) { m_slots[slot - 1]->setText(tr("Corrupted")); @@ -188,7 +189,7 @@ void LoadSaveState::loadState(int slot) { return; } - QDateTime creation(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL)); + QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO QImage stateImage; mStateExtdataItem item; @@ -209,7 +210,7 @@ void LoadSaveState::loadState(int slot) { m_slots[slot - 1]->setText(QString()); } vf->close(vf); - GBADeallocateState(state); + mappedMemoryFree(state, thread->core->stateSize(thread->core)); } void LoadSaveState::triggerState(int slot) { diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index 432ff7d9d..8d78d954c 100644 --- a/src/platform/test/fuzz-main.c +++ b/src/platform/test/fuzz-main.c @@ -102,7 +102,7 @@ int main(int argc, char** argv) { } if (savestate) { if (!savestateOverlay) { - core->loadState(core, savestate, 0); + mCoreLoadStateNamed(core, savestate, 0); } else { struct GBASerializedState* state = GBAAllocateState(); savestate->read(savestate, state, sizeof(*state)); diff --git a/src/platform/test/perf-main.c b/src/platform/test/perf-main.c index 26a1f6b31..06579c313 100644 --- a/src/platform/test/perf-main.c +++ b/src/platform/test/perf-main.c @@ -162,7 +162,7 @@ bool _mPerfRunCore(const char* fname, const struct mArguments* args, const struc core->reset(core); if (_savestate) { - core->loadState(core, _savestate, 0); + mCoreLoadStateNamed(core, _savestate, 0); } core->getGameCode(core, gameCode); From 5a2c6d037dcb0d77d96780ced908c733411ee30d Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 15:03:20 -0700 Subject: [PATCH 33/90] GB: First pass at savestates --- src/gb/audio.c | 102 +++++++++++++++++ src/gb/audio.h | 8 ++ src/gb/cli.c | 35 ++++++ src/gb/core.c | 21 ++-- src/gb/interface.h | 8 +- src/gb/io.c | 17 +++ src/gb/io.h | 4 + src/gb/memory.c | 59 ++++++++++ src/gb/memory.h | 4 + src/gb/serialize.c | 163 ++++++++++++++++++++++++++ src/gb/serialize.h | 274 ++++++++++++++++++++++++++++++++++++++++++++ src/gb/timer.c | 17 +++ src/gb/video.c | 53 +++++++++ src/gb/video.h | 4 + src/gba/audio.c | 82 +------------ src/gba/serialize.h | 45 +------- 16 files changed, 759 insertions(+), 137 deletions(-) create mode 100644 src/gb/serialize.c create mode 100644 src/gb/serialize.h diff --git a/src/gb/audio.c b/src/gb/audio.c index 44b8ff1c6..091c02914 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -8,6 +8,7 @@ #include "core/interface.h" #include "core/sync.h" #include "gb/gb.h" +#include "gb/serialize.h" #include "gb/io.h" #ifdef _3DS @@ -859,3 +860,104 @@ void _scheduleEvent(struct GBAudio* audio) { audio->nextEvent = 0; } } + +void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) { + uint32_t flags = 0; + uint32_t ch1Flags = 0; + uint32_t ch2Flags = 0; + uint32_t ch4Flags = 0; + + flags = GBSerializedAudioFlagsSetFrame(flags, audio->frame); + + flags = GBSerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume); + flags = GBSerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead); + flags = GBSerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi); + flags = GBSerializedAudioFlagsSetCh1SweepEnabled(flags, audio->ch1.sweepEnable); + flags = GBSerializedAudioFlagsSetCh1SweepOccurred(flags, audio->ch1.sweepOccurred); + ch1Flags = GBSerializedAudioEnvelopeSetLength(ch1Flags, audio->ch1.control.length); + ch1Flags = GBSerializedAudioEnvelopeSetNextStep(ch1Flags, audio->ch1.envelope.nextStep); + ch1Flags = GBSerializedAudioEnvelopeSetFrequency(ch1Flags, audio->ch1.realFrequency); + STORE_32LE(ch1Flags, 0, &state->ch1.envelope); + STORE_32LE(audio->nextFrame, 0, &state->ch1.nextFrame); + STORE_32LE(audio->nextCh1, 0, &state->ch1.nextEvent); + + flags = GBSerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume); + flags = GBSerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead); + flags = GBSerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi); + ch2Flags = GBSerializedAudioEnvelopeSetLength(ch2Flags, audio->ch2.control.length); + ch2Flags = GBSerializedAudioEnvelopeSetNextStep(ch2Flags, audio->ch2.envelope.nextStep); + STORE_32LE(ch2Flags, 0, &state->ch2.envelope); + STORE_32LE(audio->nextCh2, 0, &state->ch2.nextEvent); + + memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks)); + STORE_16LE(audio->ch3.length, 0, &state->ch3.length); + STORE_32LE(audio->nextCh3, 0, &state->ch3.nextEvent); + + flags = GBSerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume); + flags = GBSerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead); + STORE_32LE(audio->ch4.lfsr, 0, &state->ch4.lfsr); + ch4Flags = GBSerializedAudioEnvelopeSetLength(ch4Flags, audio->ch4.length); + ch4Flags = GBSerializedAudioEnvelopeSetNextStep(ch4Flags, audio->ch4.envelope.nextStep); + STORE_32LE(ch4Flags, 0, &state->ch4.envelope); + STORE_32LE(audio->nextCh4, 0, &state->ch4.nextEvent); + + STORE_32LE(flags, 0, flagsOut); +} + +void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGState* state, const uint32_t* flagsIn) { + uint32_t flags; + uint32_t ch1Flags = 0; + uint32_t ch2Flags = 0; + uint32_t ch4Flags = 0; + + LOAD_32LE(flags, 0, flagsIn); + LOAD_32LE(ch1Flags, 0, &state->ch1.envelope); + audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags); + audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags); + audio->ch1.control.hi = GBSerializedAudioFlagsGetCh1Hi(flags); + audio->ch1.sweepEnable = GBSerializedAudioFlagsGetCh1SweepEnabled(flags); + audio->ch1.sweepOccurred = GBSerializedAudioFlagsGetCh1SweepOccurred(flags); + audio->ch1.control.length = GBSerializedAudioEnvelopeGetLength(ch1Flags); + audio->ch1.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch1Flags); + audio->ch1.realFrequency = GBSerializedAudioEnvelopeGetFrequency(ch1Flags); + LOAD_32LE(audio->nextFrame, 0, &state->ch1.nextFrame); + LOAD_32LE(audio->nextCh1, 0, &state->ch1.nextEvent); + + LOAD_32LE(ch2Flags, 0, &state->ch1.envelope); + audio->ch2.envelope.currentVolume = GBSerializedAudioFlagsGetCh2Volume(flags); + audio->ch2.envelope.dead = GBSerializedAudioFlagsGetCh2Dead(flags); + audio->ch2.control.hi = GBSerializedAudioFlagsGetCh2Hi(flags); + audio->ch2.control.length = GBSerializedAudioEnvelopeGetLength(ch2Flags); + audio->ch2.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch2Flags); + LOAD_32LE(audio->nextCh2, 0, &state->ch2.nextEvent); + + // TODO: Big endian? + memcpy(audio->ch3.wavedata32, state->ch3.wavebanks, sizeof(audio->ch3.wavedata32)); + LOAD_16LE(audio->ch3.length, 0, &state->ch3.length); + LOAD_32LE(audio->nextCh3, 0, &state->ch3.nextEvent); + + LOAD_32LE(ch4Flags, 0, &state->ch1.envelope); + audio->ch4.envelope.currentVolume = GBSerializedAudioFlagsGetCh4Volume(flags); + audio->ch4.envelope.dead = GBSerializedAudioFlagsGetCh4Dead(flags); + audio->ch4.length = GBSerializedAudioEnvelopeGetLength(ch4Flags); + audio->ch4.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch4Flags); + LOAD_32LE(audio->ch4.lfsr, 0, &state->ch4.lfsr); + LOAD_32LE(audio->nextCh4, 0, &state->ch4.nextEvent); +} + +void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) { + GBAudioPSGSerialize(audio, &state->audio.psg, &state->audio.flags); + + STORE_32LE(audio->nextEvent, 0, &state->audio.nextEvent); + STORE_32LE(audio->eventDiff, 0, &state->audio.eventDiff); + STORE_32LE(audio->nextSample, 0, &state->audio.nextSample); +} + +void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* state) { + GBAudioPSGDeserialize(audio, &state->audio.psg, &state->audio.flags); + + LOAD_32LE(audio->nextEvent, 0, &state->audio.nextEvent); + LOAD_32LE(audio->eventDiff, 0, &state->audio.eventDiff); + LOAD_32LE(audio->nextSample, 0, &state->audio.nextSample); +} + diff --git a/src/gb/audio.h b/src/gb/audio.h index a77e341d6..babae44ed 100644 --- a/src/gb/audio.h +++ b/src/gb/audio.h @@ -235,4 +235,12 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t); int32_t GBAudioProcessEvents(struct GBAudio* audio, int32_t cycles); void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right); +struct GBSerializedPSGState; +void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut); +void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGState* state, const uint32_t* flagsIn); + +struct GBSerializedState; +void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state); +void GBAudioDeserialize(struct GBAudio* audio, const struct GBSerializedState* state); + #endif diff --git a/src/gb/cli.c b/src/gb/cli.c index 7a166855e..6aaae8ac1 100644 --- a/src/gb/cli.c +++ b/src/gb/cli.c @@ -19,9 +19,13 @@ static bool _GBCLIDebuggerCustom(struct CLIDebuggerSystem*); static uint32_t _GBCLIDebuggerLookupIdentifier(struct CLIDebuggerSystem*, const char* name, struct CLIDebugVector* dv); static void _frame(struct CLIDebugger*, struct CLIDebugVector*); +static void _load(struct CLIDebugger*, struct CLIDebugVector*); +static void _save(struct CLIDebugger*, struct CLIDebugVector*); struct CLIDebuggerCommandSummary _GBCLIDebuggerCommands[] = { { "frame", _frame, 0, "Frame advance" }, + { "load", _load, CLIDVParse, "Load a savestate" }, + { "save", _save, CLIDVParse, "Save a savestate" }, { 0, 0, 0, 0 } }; @@ -85,4 +89,35 @@ static void _frame(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { gbDebugger->inVblank = GBRegisterSTATGetMode(((struct GB*) gbDebugger->core->board)->memory.io[REG_STAT]) == 1; } +static void _load(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + + int state = dv->intValue; + if (state < 1 || state > 9) { + printf("State %u out of range", state); + } + + struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; + + mCoreLoadState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT); +} + +static void _save(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!dv || dv->type != CLIDV_INT_TYPE) { + printf("%s\n", ERROR_MISSING_ARGS); + return; + } + + int state = dv->intValue; + if (state < 1 || state > 9) { + printf("State %u out of range", state); + } + + struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; + + mCoreSaveState(gbDebugger->core, dv->intValue, SAVESTATE_SCREENSHOT); +} #endif diff --git a/src/gb/core.c b/src/gb/core.c index c746a204b..eb6385cf6 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -10,6 +10,7 @@ #include "gb/cli.h" #include "gb/gb.h" #include "gb/renderers/software.h" +#include "gb/serialize.h" #include "lr35902/debugger/debugger.h" #include "util/memory.h" #include "util/patch.h" @@ -213,20 +214,17 @@ static void _GBCoreStep(struct mCore* core) { } while (cpu->executionState != LR35902_CORE_FETCH); } -static bool _GBCoreLoadState(struct mCore* core, struct VFile* vf, int flags) { +static size_t _GBCoreStateSize(struct mCore* core) { UNUSED(core); - UNUSED(vf); - UNUSED(flags); - // TODO - return false; + return sizeof(struct GBSerializedState); } -static bool _GBCoreSaveState(struct mCore* core, struct VFile* vf, int flags) { - UNUSED(core); - UNUSED(vf); - UNUSED(flags); - // TODO - return false; +static bool _GBCoreLoadState(struct mCore* core, const void* state) { + return GBDeserialize(core->board, state); +} + +static bool _GBCoreSaveState(struct mCore* core, void* state) { + return GBSerialize(core->board, state); } static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) { @@ -439,6 +437,7 @@ struct mCore* GBCoreCreate(void) { core->runFrame = _GBCoreRunFrame; core->runLoop = _GBCoreRunLoop; core->step = _GBCoreStep; + core->stateSize = _GBCoreStateSize; core->loadState = _GBCoreLoadState; core->saveState = _GBCoreSaveState; core->setKeys = _GBCoreSetKeys; diff --git a/src/gb/interface.h b/src/gb/interface.h index a7f53df2b..b52282ceb 100644 --- a/src/gb/interface.h +++ b/src/gb/interface.h @@ -9,10 +9,10 @@ #include "util/common.h" enum GBModel { - GB_MODEL_DMG, - GB_MODEL_SGB, - GB_MODEL_CGB, - GB_MODEL_AGB + GB_MODEL_DMG = 0x00, + GB_MODEL_SGB = 0x40, + GB_MODEL_CGB = 0x80, + GB_MODEL_AGB = 0xC0 }; #endif diff --git a/src/gb/io.c b/src/gb/io.c index 34107601f..437e318c5 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -6,6 +6,7 @@ #include "io.h" #include "gb/gb.h" +#include "gb/serialize.h" mLOG_DEFINE_CATEGORY(GB_IO, "GB I/O"); @@ -551,3 +552,19 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { success: return gb->memory.io[address] | _registerMask[address]; } + +struct GBSerializedState; +void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state) { + memcpy(state->io, gb->memory.io, GB_SIZE_IO); + state->ie = gb->memory.ie; +} + +void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) { + memcpy(gb->memory.io, state->io, GB_SIZE_IO); + gb->memory.ie = state->ie; + gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_LCDC, state->io[REG_LCDC]); + gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCY, state->io[REG_SCY]); + gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCX, state->io[REG_SCX]); + gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WY, state->io[REG_WY]); + gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WX, state->io[REG_WX]); +} diff --git a/src/gb/io.h b/src/gb/io.h index 88e4543b1..ffd6f47c9 100644 --- a/src/gb/io.h +++ b/src/gb/io.h @@ -114,4 +114,8 @@ void GBIOReset(struct GB* gb); void GBIOWrite(struct GB* gb, unsigned address, uint8_t value); uint8_t GBIORead(struct GB* gb, unsigned address); +struct GBSerializedState; +void GBIOSerialize(const struct GB* gb, struct GBSerializedState* state); +void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state); + #endif diff --git a/src/gb/memory.c b/src/gb/memory.c index 9ae5af086..d42e4c682 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -8,6 +8,7 @@ #include "core/interface.h" #include "gb/gb.h" #include "gb/io.h" +#include "gb/serialize.h" #include "util/memory.h" @@ -932,6 +933,64 @@ void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { } } +void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state) { + memcpy(state->wram, memory->wram, GB_SIZE_WORKING_RAM); + memcpy(state->hram, memory->hram, GB_SIZE_HRAM); + STORE_16LE(memory->currentBank, 0, &state->memory.currentBank); + state->memory.wramCurrentBank = memory->wramCurrentBank; + state->memory.sramCurrentBank = memory->sramCurrentBank; + + STORE_32LE(memory->dmaNext, 0, &state->memory.dmaNext); + STORE_16LE(memory->dmaSource, 0, &state->memory.dmaSource); + STORE_16LE(memory->dmaDest, 0, &state->memory.dmaDest); + + STORE_32LE(memory->hdmaNext, 0, &state->memory.hdmaNext); + STORE_16LE(memory->hdmaSource, 0, &state->memory.hdmaSource); + STORE_16LE(memory->hdmaDest, 0, &state->memory.hdmaDest); + + STORE_16LE(memory->hdmaRemaining, 0, &state->memory.hdmaRemaining); + state->memory.dmaRemaining = memory->dmaRemaining; + memcpy(state->memory.rtcRegs, memory->rtcRegs, sizeof(state->memory.rtcRegs)); + + state->memory.sramAccess = memory->sramAccess; + state->memory.rtcAccess = memory->rtcAccess; + state->memory.rtcLatched = memory->rtcLatched; + state->memory.ime = memory->ime; + state->memory.isHdma = memory->isHdma; + state->memory.activeRtcReg = memory->activeRtcReg; +} + +void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state) { + memcpy(memory->wram, state->wram, GB_SIZE_WORKING_RAM); + memcpy(memory->hram, state->hram, GB_SIZE_HRAM); + LOAD_16LE(memory->currentBank, 0, &state->memory.currentBank); + memory->wramCurrentBank = state->memory.wramCurrentBank; + memory->sramCurrentBank = state->memory.sramCurrentBank; + + _switchBank(memory, memory->currentBank); + GBMemorySwitchWramBank(memory, memory->wramCurrentBank); + _switchSramBank(memory, memory->sramCurrentBank); + + LOAD_32LE(memory->dmaNext, 0, &state->memory.dmaNext); + LOAD_16LE(memory->dmaSource, 0, &state->memory.dmaSource); + LOAD_16LE(memory->dmaDest, 0, &state->memory.dmaDest); + + LOAD_32LE(memory->hdmaNext, 0, &state->memory.hdmaNext); + LOAD_16LE(memory->hdmaSource, 0, &state->memory.hdmaSource); + LOAD_16LE(memory->hdmaDest, 0, &state->memory.hdmaDest); + + LOAD_16LE(memory->hdmaRemaining, 0, &state->memory.hdmaRemaining); + memory->dmaRemaining = state->memory.dmaRemaining; + memcpy(memory->rtcRegs, state->memory.rtcRegs, sizeof(state->memory.rtcRegs)); + + memory->sramAccess = state->memory.sramAccess; + memory->rtcAccess = state->memory.rtcAccess; + memory->rtcLatched = state->memory.rtcLatched; + memory->ime = state->memory.ime; + memory->isHdma = state->memory.isHdma; + memory->activeRtcReg = state->memory.activeRtcReg; +} + void _pristineCow(struct GB* gb) { if (gb->memory.rom != gb->pristineRom) { return; diff --git a/src/gb/memory.h b/src/gb/memory.h index bfba364f5..48e52459e 100644 --- a/src/gb/memory.h +++ b/src/gb/memory.h @@ -174,4 +174,8 @@ void GBDMAStore8(struct LR35902Core* cpu, uint16_t address, int8_t value); void GBPatch8(struct LR35902Core* cpu, uint16_t address, int8_t value, int8_t* old); +struct GBSerializedState; +void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state); +void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state); + #endif diff --git a/src/gb/serialize.c b/src/gb/serialize.c new file mode 100644 index 000000000..9a6c6bf03 --- /dev/null +++ b/src/gb/serialize.c @@ -0,0 +1,163 @@ +/* Copyright (c) 2013-2016 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 "serialize.h" + +mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate"); + +#ifdef _MSC_VER +#include +#else +#include +#endif + +const uint32_t GB_SAVESTATE_MAGIC = 0x00400000; +const uint32_t GB_SAVESTATE_VERSION = 0x00000000; + +void GBSerialize(struct GB* gb, struct GBSerializedState* state) { + STORE_32LE(GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, 0, &state->versionMagic); + STORE_32LE(gb->romCrc32, 0, &state->romCrc32); + + if (gb->memory.rom) { + memcpy(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title)); + } else { + memset(state->title, 0, sizeof(state->title)); + } + + state->model = gb->model; + + state->cpu.a = gb->cpu->a; + state->cpu.f = gb->cpu->f.packed; + state->cpu.b = gb->cpu->b; + state->cpu.c = gb->cpu->c; + state->cpu.d = gb->cpu->d; + state->cpu.e = gb->cpu->e; + state->cpu.h = gb->cpu->h; + state->cpu.l = gb->cpu->l; + STORE_16LE(gb->cpu->sp, 0, &state->cpu.sp); + STORE_16LE(gb->cpu->pc, 0, &state->cpu.pc); + + STORE_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); + STORE_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent); + + STORE_16LE(gb->cpu->index, 0, &state->cpu.index); + state->cpu.bus = gb->cpu->bus; + state->cpu.executionState = gb->cpu->executionState; + STORE_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector); + + state->cpu.condition = gb->cpu->condition; + state->cpu.irqPending = gb->cpu->irqPending; + + state->cpu.doubleSpeed = gb->doubleSpeed; + + GBMemorySerialize(&gb->memory, state); + GBIOSerialize(gb, state); + GBVideoSerialize(&gb->video, state); + GBTimerSerialize(&gb->timer, state); + GBAudioSerialize(&gb->audio, state); + +#ifndef _MSC_VER + struct timeval tv; + if (!gettimeofday(&tv, 0)) { + uint64_t usec = tv.tv_usec; + usec += tv.tv_sec * 1000000LL; + STORE_64LE(usec, 0, &state->creationUsec); + } +#else + struct timespec ts; + if (timespec_get(&ts, TIME_UTC)) { + uint64_t usec = ts.tv_nsec / 1000; + usec += ts.tv_sec * 1000000LL; + STORE_64LE(usec, 0, &state->creationUsec); + } +#endif + else { + state->creationUsec = 0; + } +} + +bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { + bool error = false; + int32_t check; + uint32_t ucheck; + LOAD_32LE(ucheck, 0, &state->versionMagic); + if (ucheck > GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) { + mLOG(GB_STATE, WARN, "Invalid or too new savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck); + error = true; + } else if (ucheck < GB_SAVESTATE_MAGIC) { + mLOG(GB_STATE, WARN, "Invalid savestate: expected %08X, got %08X", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck); + error = true; + } else if (ucheck < GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION) { + mLOG(GB_STATE, WARN, "Old savestate: expected %08X, got %08X, continuing anyway", GB_SAVESTATE_MAGIC + GB_SAVESTATE_VERSION, ucheck); + } + + if (gb->memory.rom && memcmp(state->title, ((struct GBCartridge*) gb->memory.rom)->titleLong, sizeof(state->title))) { + mLOG(GB_STATE, WARN, "Savestate is for a different game"); + error = true; + } + LOAD_32LE(ucheck, 0, &state->romCrc32); + if (ucheck != gb->romCrc32) { + mLOG(GB_STATE, WARN, "Savestate is for a different version of the game"); + } + LOAD_32LE(check, 0, &state->cpu.cycles); + if (check < 0) { + mLOG(GB_STATE, WARN, "Savestate is corrupted: CPU cycles are negative"); + error = true; + } + if (check >= (int32_t) DMG_LR35902_FREQUENCY) { + mLOG(GB_STATE, WARN, "Savestate is corrupted: CPU cycles are too high"); + error = true; + } + LOAD_32LE(check, 0, &state->video.eventDiff); + if (check < 0) { + mLOG(GB_STATE, WARN, "Savestate is corrupted: video eventDiff is negative"); + error = true; + } + if (error) { + return false; + } + + gb->cpu->a = state->cpu.a; + gb->cpu->f.packed = state->cpu.f; + gb->cpu->b = state->cpu.b; + gb->cpu->c = state->cpu.c; + gb->cpu->d = state->cpu.d; + gb->cpu->e = state->cpu.e; + gb->cpu->h = state->cpu.h; + gb->cpu->l = state->cpu.l; + LOAD_16LE(gb->cpu->sp, 0, &state->cpu.sp); + LOAD_16LE(gb->cpu->pc, 0, &state->cpu.pc); + + LOAD_16LE(gb->cpu->index, 0, &state->cpu.index); + gb->cpu->bus = state->cpu.bus; + gb->cpu->executionState = state->cpu.executionState; + LOAD_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector); + + gb->cpu->condition = state->cpu.condition; + gb->cpu->irqPending = state->cpu.irqPending; + + gb->doubleSpeed = state->cpu.doubleSpeed; + + LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); + LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent); + + gb->model = state->model; + + if (gb->model < GB_MODEL_CGB) { + gb->audio.style = GB_AUDIO_DMG; + } else { + gb->audio.style = GB_AUDIO_CGB; + } + + GBMemoryDeserialize(&gb->memory, state); + GBIODeserialize(gb, state); + GBVideoDeserialize(&gb->video, state); + GBTimerDeserialize(&gb->timer, state); + GBAudioDeserialize(&gb->audio, state); + + gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc); + + return true; +} diff --git a/src/gb/serialize.h b/src/gb/serialize.h new file mode 100644 index 000000000..8f4ae7d73 --- /dev/null +++ b/src/gb/serialize.h @@ -0,0 +1,274 @@ +/* Copyright (c) 2013-2016 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 GB_SERIALIZE_H +#define GB_SERIALIZE_H + +#include "util/common.h" + +#include "core/core.h" +#include "gb/gb.h" + +extern const uint32_t GB_SAVESTATE_MAGIC; +extern const uint32_t GB_SAVESTATE_VERSION; + +mLOG_DECLARE_CATEGORY(GB_STATE); + +/* Savestate format: + * 0x00000 - 0x00003: Version Magic (0x01000001) + * 0x00004 - 0x00007: ROM CRC32 + * 0x00008: Game Boy model + * 0x00009 - 0x0000F: Reserved (leave zero) + * 0x00010 - 0x0001F: Game title/code (e.g. PM_CRYSTALBYTE) + * 0x00020 - 0x00047: CPU state: + * | 0x00020: A register + * | 0x00021: F register + * | 0x00022: B register + * | 0x00023: C register + * | 0x00024: D register + * | 0x00025: E register + * | 0x00026: H register + * | 0x00027: L register + * | 0x00028 - 0z00029: SP register + * | 0x0002A - 0z0002B: PC register + * | 0x0002C - 0x0002F: Cycles since last event + * | 0x00030 - 0x00033: Cycles until next event + * | 0x00034 - 0x00035: Reserved (current instruction) + * | 0x00036 - 0x00037: Index address + * | 0x00038: Bus value + * | 0x00039: Execution state + * | 0x0003A - 0x0003B: IRQ vector + * | 0x0003C - 0x0003F: EI pending cycles + * | 0x00040 - 0x00043: Reserved (DI pending cycles) + * | 0x00044 - 0x00048: Flags + * 0x00048 - 0x0005B: Audio channel 1/framer state + * | 0x00048 - 0x0004B: Envelepe timing + * | bits 0 - 6: Remaining length + * | bits 7 - 9: Next step + * | bits 10 - 20: Shadow frequency register + * | bits 21 - 31: Reserved + * | 0x0004C - 0x0004F: Next frame + * | 0x00050 - 0x00057: Reserved + * | 0x00058 - 0x0005B: Next event + * 0x0005C - 0x0006B: Audio channel 2 state + * | 0x0005C - 0x0005F: Envelepe timing + * | bits 0 - 2: Remaining length + * | bits 3 - 5: Next step + * | bits 6 - 31: Reserved + * | 0x00060 - 0x00067: Reserved + * | 0x00068 - 0x0006B: Next event + * 0x0006C - 0x00093: Audio channel 3 state + * | 0x0006C - 0x0008B: Wave banks + * | 0x0008C - 0x0008D: Remaining length + * | 0x0008E - 0x0008F: Reserved + * | 0x00090 - 0x00093: Next event + * 0x00094 - 0x000A3: Audio channel 4 state + * | 0x00094 - 0x00097: Linear feedback shift register state + * | 0x00098 - 0x0009B: Envelepe timing + * | bits 0 - 2: Remaining length + * | bits 3 - 5: Next step + * | bits 6 - 31: Reserved + * | 0x00098 - 0x0009F: Reserved + * | 0x000A0 - 0x000A3: Next event + * 0x000A4 - 0x000B7: Audio miscellaneous state + * | TODO: Fix this, they're in big-endian order, but field is little-endian + * | 0x000A4: Channel 1 envelope state + * | bits 0 - 3: Current volume + * | bits 4 - 5: Is dead? + * | bit 6: Is high? + * | 0x000A5: Channel 2 envelope state + * | bits 0 - 3: Current volume + * | bits 4 - 5: Is dead? + * | bit 6: Is high? +* | bits 7: Reserved + * | 0x000A6: Channel 4 envelope state + * | bits 0 - 3: Current volume + * | bits 4 - 5: Is dead? + * | bit 6: Is high? +* | bits 7: Reserved + * | 0x000A7: Miscellaneous audio flags + * | bits 0 - 3: Current frame + * | bit 4: Is channel 1 sweep enabled? + * | bit 5: Has channel 1 sweep occurred? + * | bits 6 - 7: Reserved + * | 0x000A8 - 0x000AB: Next event + * | 0x000AC - 0x000AF: Event diff + * | 0x000B0 - 0x000B3: Next sample +*/ + +DECL_BITFIELD(GBSerializedAudioFlags, uint32_t); +DECL_BITS(GBSerializedAudioFlags, Ch1Volume, 0, 4); +DECL_BITS(GBSerializedAudioFlags, Ch1Dead, 4, 2); +DECL_BIT(GBSerializedAudioFlags, Ch1Hi, 6); +DECL_BITS(GBSerializedAudioFlags, Ch2Volume, 8, 4); +DECL_BITS(GBSerializedAudioFlags, Ch2Dead, 12, 2); +DECL_BIT(GBSerializedAudioFlags, Ch2Hi, 14); +DECL_BITS(GBSerializedAudioFlags, Ch4Volume, 16, 4); +DECL_BITS(GBSerializedAudioFlags, Ch4Dead, 20, 2); +DECL_BITS(GBSerializedAudioFlags, Frame, 22, 3); +DECL_BIT(GBSerializedAudioFlags, Ch1SweepEnabled, 25); +DECL_BIT(GBSerializedAudioFlags, Ch1SweepOccurred, 26); + +DECL_BITFIELD(GBSerializedAudioEnvelope, uint32_t); +DECL_BITS(GBSerializedAudioEnvelope, Length, 0, 7); +DECL_BITS(GBSerializedAudioEnvelope, NextStep, 7, 3); +DECL_BITS(GBSerializedAudioEnvelope, Frequency, 10, 11); + +struct GBSerializedPSGState { + struct { + GBSerializedAudioEnvelope envelope; + int32_t nextFrame; + int32_t reserved[2]; + int32_t nextEvent; + } ch1; + struct { + GBSerializedAudioEnvelope envelope; + int32_t reserved[2]; + int32_t nextEvent; + } ch2; + struct { + uint32_t wavebanks[8]; + int16_t length; + int16_t reserved; + int32_t nextEvent; + } ch3; + struct { + int32_t lfsr; + GBSerializedAudioEnvelope envelope; + int32_t reserved; + int32_t nextEvent; + } ch4; +}; + +#pragma pack(push, 1) +struct GBSerializedState { + uint32_t versionMagic; + uint32_t romCrc32; + uint8_t model; + uint8_t reservedHeader[7]; + + char title[16]; + + struct { + uint8_t a; + uint8_t f; + uint8_t b; + uint8_t c; + uint8_t d; + uint8_t e; + uint8_t h; + uint8_t l; + uint16_t sp; + uint16_t pc; + + int32_t cycles; + int32_t nextEvent; + + uint16_t reservedInstruction; + uint16_t index; + uint8_t bus; + uint8_t executionState; + + uint16_t irqVector; + + int32_t eiPending; + int32_t reservedDiPending; + bool condition : 1; + bool irqPending : 1; + bool doubleSpeed : 1; + } cpu; + + struct { + struct GBSerializedPSGState psg; + GBSerializedAudioFlags flags; + int32_t nextEvent; + int32_t eventDiff; + int32_t nextSample; + } audio; + + struct { + int16_t x; + int16_t ly; + int32_t nextEvent; + int32_t eventDiff; + int32_t nextMode; + int32_t dotCounter; + int32_t frameCounter; + + uint8_t vramCurrentBank; + + bool bcpIncrement : 1; + bool ocpIncrement : 1; + uint16_t bcpIndex; + uint16_t ocpIndex; + + uint16_t palette[64]; + } video; + + struct { + int32_t nextEvent; + int32_t eventDiff; + + int32_t nextDiv; + int32_t nextTima; + int32_t timaPeriod; + } timer; + + struct { + uint16_t currentBank; + uint8_t wramCurrentBank; + uint8_t sramCurrentBank; + + int32_t dmaNext; + uint16_t dmaSource; + uint16_t dmaDest; + + int32_t hdmaNext; + uint16_t hdmaSource; + uint16_t hdmaDest; + + uint16_t hdmaRemaining; + uint8_t dmaRemaining; + uint8_t rtcRegs[5]; + + union { + struct { + uint32_t mode; + } mbc1; + struct { + int8_t machineState; + GBMBC7Field field; + int8_t address; + uint8_t srBits; + uint32_t sr; + uint8_t command : 2; + bool writable : 1; + } mbc7; + struct { + uint8_t reserved[16]; + } padding; + }; + + bool sramAccess : 1; + bool rtcAccess : 1; + bool rtcLatched : 1; + bool ime : 1; + bool isHdma : 1; + uint8_t activeRtcReg : 5; + } memory; + + uint8_t io[GB_SIZE_IO]; + uint8_t hram[GB_SIZE_HRAM]; + uint8_t ie; + + uint64_t creationUsec; + + uint8_t oam[GB_SIZE_OAM]; + uint8_t vram[GB_SIZE_VRAM]; + uint8_t wram[GB_SIZE_WORKING_RAM]; +}; +#pragma pack(pop) + +#endif diff --git a/src/gb/timer.c b/src/gb/timer.c index 8757af1f7..aaee2d7a5 100644 --- a/src/gb/timer.c +++ b/src/gb/timer.c @@ -7,6 +7,7 @@ #include "gb/gb.h" #include "gb/io.h" +#include "gb/serialize.h" void GBTimerReset(struct GBTimer* timer) { timer->nextDiv = GB_DMG_DIV_PERIOD; // TODO: GBC differences @@ -97,3 +98,19 @@ void GBTimerUpdateTIMA(struct GBTimer* timer) { } } } + +void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state) { + STORE_32LE(timer->nextEvent, 0, &state->timer.nextEvent); + STORE_32LE(timer->eventDiff, 0, &state->timer.eventDiff); + STORE_32LE(timer->nextDiv, 0, &state->timer.nextDiv); + STORE_32LE(timer->nextTima, 0, &state->timer.nextTima); + STORE_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod); +} + +void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state) { + LOAD_32LE(timer->nextEvent, 0, &state->timer.nextEvent); + LOAD_32LE(timer->eventDiff, 0, &state->timer.eventDiff); + LOAD_32LE(timer->nextDiv, 0, &state->timer.nextDiv); + LOAD_32LE(timer->nextTima, 0, &state->timer.nextTima); + LOAD_32LE(timer->timaPeriod, 0, &state->timer.timaPeriod); +} diff --git a/src/gb/video.c b/src/gb/video.c index 6798d1005..d18bb14f9 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -9,6 +9,7 @@ #include "core/thread.h" #include "gb/gb.h" #include "gb/io.h" +#include "gb/serialize.h" #include "util/memory.h" @@ -431,3 +432,55 @@ static void GBVideoDummyRendererGetPixels(struct GBVideoRenderer* renderer, unsi UNUSED(pixels); // Nothing to do } + +void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state) { + STORE_16LE(video->x, 0, &state->video.x); + STORE_16LE(video->ly, 0, &state->video.ly); + STORE_32LE(video->nextEvent, 0, &state->video.nextEvent); + STORE_32LE(video->eventDiff, 0, &state->video.eventDiff); + STORE_32LE(video->nextMode, 0, &state->video.nextMode); + STORE_32LE(video->dotCounter, 0, &state->video.dotCounter); + STORE_32LE(video->frameCounter, 0, &state->video.frameCounter); + state->video.vramCurrentBank = video->vramCurrentBank; + + state->video.bcpIncrement = video->bcpIncrement; + state->video.ocpIncrement = video->ocpIncrement; + STORE_16LE(video->bcpIndex, 0, &state->video.bcpIndex); + STORE_16LE(video->ocpIndex, 0, &state->video.ocpIndex); + + size_t i; + for (i = 0; i < 64; ++i) { + STORE_16LE(video->palette[i], i * 2, state->video.palette); + } + + memcpy(state->vram, video->vram, GB_SIZE_VRAM); + memcpy(state->oam, &video->oam.raw, GB_SIZE_OAM); +} + +void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state) { + LOAD_16LE(video->x, 0, &state->video.x); + LOAD_16LE(video->ly, 0, &state->video.ly); + LOAD_32LE(video->nextEvent, 0, &state->video.nextEvent); + LOAD_32LE(video->eventDiff, 0, &state->video.eventDiff); + LOAD_32LE(video->nextMode, 0, &state->video.nextMode); + LOAD_32LE(video->dotCounter, 0, &state->video.dotCounter); + LOAD_32LE(video->frameCounter, 0, &state->video.frameCounter); + video->vramCurrentBank = state->video.vramCurrentBank; + + video->bcpIncrement = state->video.bcpIncrement; + video->ocpIncrement = state->video.ocpIncrement; + LOAD_16LE(video->bcpIndex, 0, &state->video.bcpIndex); + LOAD_16LE(video->ocpIndex, 0, &state->video.ocpIndex); + + size_t i; + for (i = 0; i < 64; ++i) { + LOAD_16LE(video->palette[i], i * 2, state->video.palette); + video->renderer->writePalette(video->renderer, i, video->palette[i]); + } + + memcpy(video->vram, state->vram, GB_SIZE_VRAM); + memcpy(&video->oam.raw, state->oam, GB_SIZE_OAM); + + _cleanOAM(video, video->ly); + GBVideoSwitchBank(video, video->vramCurrentBank); +} diff --git a/src/gb/video.h b/src/gb/video.h index 7bce4e1db..1555385ea 100644 --- a/src/gb/video.h +++ b/src/gb/video.h @@ -138,4 +138,8 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value); void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value); void GBVideoSwitchBank(struct GBVideo* video, uint8_t value); +struct GBSerializedState; +void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* state); +void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* state); + #endif diff --git a/src/gba/audio.c b/src/gba/audio.c index a4d12385e..0072b542c 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -332,48 +332,7 @@ static void _sample(struct GBAAudio* audio) { } void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) { - uint32_t flags = 0; - uint32_t ch1Flags = 0; - uint32_t ch2Flags = 0; - uint32_t ch4Flags = 0; - - flags = GBASerializedAudioFlagsSetFrame(flags, audio->psg.frame); - - flags = GBASerializedAudioFlagsSetCh1Volume(flags, audio->psg.ch1.envelope.currentVolume); - flags = GBASerializedAudioFlagsSetCh1Dead(flags, audio->psg.ch1.envelope.dead); - flags = GBASerializedAudioFlagsSetCh1Hi(flags, audio->psg.ch1.control.hi); - flags = GBASerializedAudioFlagsSetCh1SweepEnabled(flags, audio->psg.ch1.sweepEnable); - flags = GBASerializedAudioFlagsSetCh1SweepOccurred(flags, audio->psg.ch1.sweepOccurred); - ch1Flags = GBASerializedAudioEnvelopeSetLength(ch1Flags, audio->psg.ch1.control.length); - ch1Flags = GBASerializedAudioEnvelopeSetNextStep(ch1Flags, audio->psg.ch1.envelope.nextStep); - ch1Flags = GBASerializedAudioEnvelopeSetFrequency(ch1Flags, audio->psg.ch1.realFrequency); - STORE_32(ch1Flags, 0, &state->audio.ch1.envelope); - STORE_32(audio->psg.nextFrame, 0, &state->audio.ch1.nextFrame); - STORE_32(audio->psg.nextCh1, 0, &state->audio.ch1.nextEvent); - - flags = GBASerializedAudioFlagsSetCh2Volume(flags, audio->psg.ch2.envelope.currentVolume); - flags = GBASerializedAudioFlagsSetCh2Dead(flags, audio->psg.ch2.envelope.dead); - flags = GBASerializedAudioFlagsSetCh2Hi(flags, audio->psg.ch2.control.hi); - ch2Flags = GBASerializedAudioEnvelopeSetLength(ch2Flags, audio->psg.ch2.control.length); - ch2Flags = GBASerializedAudioEnvelopeSetNextStep(ch2Flags, audio->psg.ch2.envelope.nextStep); - STORE_32(ch2Flags, 0, &state->audio.ch2.envelope); - STORE_32(audio->psg.nextCh2, 0, &state->audio.ch2.nextEvent); - - memcpy(state->audio.ch3.wavebanks, audio->psg.ch3.wavedata32, sizeof(state->audio.ch3.wavebanks)); - STORE_16(audio->psg.ch3.length, 0, &state->audio.ch3.length); - STORE_32(audio->psg.nextCh3, 0, &state->audio.ch3.nextEvent); - - flags = GBASerializedAudioFlagsSetCh4Volume(flags, audio->psg.ch4.envelope.currentVolume); - flags = GBASerializedAudioFlagsSetCh4Dead(flags, audio->psg.ch4.envelope.dead); - state->audio.flags = GBASerializedAudioFlagsSetCh4Volume(flags, audio->psg.ch4.envelope.currentVolume); - state->audio.flags = GBASerializedAudioFlagsSetCh4Dead(flags, audio->psg.ch4.envelope.dead); - STORE_32(audio->psg.ch4.lfsr, 0, &state->audio.ch4.lfsr); - ch4Flags = GBASerializedAudioEnvelopeSetLength(ch4Flags, audio->psg.ch4.length); - ch4Flags = GBASerializedAudioEnvelopeSetNextStep(ch4Flags, audio->psg.ch4.envelope.nextStep); - STORE_32(ch4Flags, 0, &state->audio.ch4.envelope); - STORE_32(audio->psg.nextCh4, 0, &state->audio.ch4.nextEvent); - - STORE_32(flags, 0, &state->audio.flags); + GBAudioPSGSerialize(&audio->psg, &state->audio.psg, &state->audio.flags); CircleBufferDump(&audio->chA.fifo, state->audio.fifoA, sizeof(state->audio.fifoA)); CircleBufferDump(&audio->chB.fifo, state->audio.fifoB, sizeof(state->audio.fifoB)); @@ -386,44 +345,7 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* } void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) { - uint32_t flags; - uint32_t ch1Flags = 0; - uint32_t ch2Flags = 0; - uint32_t ch4Flags = 0; - - LOAD_32(flags, 0, &state->audio.flags); - LOAD_32(ch1Flags, 0, &state->audio.ch1.envelope); - audio->psg.ch1.envelope.currentVolume = GBASerializedAudioFlagsGetCh1Volume(flags); - audio->psg.ch1.envelope.dead = GBASerializedAudioFlagsGetCh1Dead(flags); - audio->psg.ch1.control.hi = GBASerializedAudioFlagsGetCh1Hi(flags); - audio->psg.ch1.sweepEnable = GBASerializedAudioFlagsGetCh1SweepEnabled(flags); - audio->psg.ch1.sweepOccurred = GBASerializedAudioFlagsGetCh1SweepOccurred(flags); - audio->psg.ch1.control.length = GBASerializedAudioEnvelopeGetLength(ch1Flags); - audio->psg.ch1.envelope.nextStep = GBASerializedAudioEnvelopeGetNextStep(ch1Flags); - audio->psg.ch1.realFrequency = GBASerializedAudioEnvelopeGetFrequency(ch1Flags); - LOAD_32(audio->psg.nextFrame, 0, &state->audio.ch1.nextFrame); - LOAD_32(audio->psg.nextCh1, 0, &state->audio.ch1.nextEvent); - - LOAD_32(ch2Flags, 0, &state->audio.ch1.envelope); - audio->psg.ch2.envelope.currentVolume = GBASerializedAudioFlagsGetCh2Volume(flags); - audio->psg.ch2.envelope.dead = GBASerializedAudioFlagsGetCh2Dead(flags); - audio->psg.ch2.control.hi = GBASerializedAudioFlagsGetCh2Hi(flags); - audio->psg.ch2.control.length = GBASerializedAudioEnvelopeGetLength(ch2Flags); - audio->psg.ch2.envelope.nextStep = GBASerializedAudioEnvelopeGetNextStep(ch2Flags); - LOAD_32(audio->psg.nextCh2, 0, &state->audio.ch2.nextEvent); - - // TODO: Big endian? - memcpy(audio->psg.ch3.wavedata32, state->audio.ch3.wavebanks, sizeof(audio->psg.ch3.wavedata32)); - LOAD_16(audio->psg.ch3.length, 0, &state->audio.ch3.length); - LOAD_32(audio->psg.nextCh3, 0, &state->audio.ch3.nextEvent); - - LOAD_32(ch4Flags, 0, &state->audio.ch1.envelope); - audio->psg.ch4.envelope.currentVolume = GBASerializedAudioFlagsGetCh4Volume(flags); - audio->psg.ch4.envelope.dead = GBASerializedAudioFlagsGetCh4Dead(flags); - audio->psg.ch4.length = GBASerializedAudioEnvelopeGetLength(ch4Flags); - audio->psg.ch4.envelope.nextStep = GBASerializedAudioEnvelopeGetNextStep(ch4Flags); - LOAD_32(audio->psg.ch4.lfsr, 0, &state->audio.ch4.lfsr); - LOAD_32(audio->psg.nextCh4, 0, &state->audio.ch4.nextEvent); + GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags); CircleBufferClear(&audio->chA.fifo); CircleBufferClear(&audio->chB.fifo); diff --git a/src/gba/serialize.h b/src/gba/serialize.h index be2838904..05a1053e1 100644 --- a/src/gba/serialize.h +++ b/src/gba/serialize.h @@ -10,6 +10,7 @@ #include "core/core.h" #include "gba/gba.h" +#include "gb/serialize.h" extern const uint32_t GBA_SAVESTATE_MAGIC; extern const uint32_t GBA_SAVESTATE_VERSION; @@ -204,24 +205,6 @@ mLOG_DECLARE_CATEGORY(GBA_STATE); * Total size: 0x61000 (397,312) bytes */ -DECL_BITFIELD(GBASerializedAudioFlags, uint32_t); -DECL_BITS(GBASerializedAudioFlags, Ch1Volume, 0, 4); -DECL_BITS(GBASerializedAudioFlags, Ch1Dead, 4, 2); -DECL_BIT(GBASerializedAudioFlags, Ch1Hi, 6); -DECL_BITS(GBASerializedAudioFlags, Ch2Volume, 8, 4); -DECL_BITS(GBASerializedAudioFlags, Ch2Dead, 12, 2); -DECL_BIT(GBASerializedAudioFlags, Ch2Hi, 14); -DECL_BITS(GBASerializedAudioFlags, Ch4Volume, 16, 4); -DECL_BITS(GBASerializedAudioFlags, Ch4Dead, 20, 2); -DECL_BITS(GBASerializedAudioFlags, Frame, 22, 3); -DECL_BIT(GBASerializedAudioFlags, Ch1SweepEnabled, 25); -DECL_BIT(GBASerializedAudioFlags, Ch1SweepOccurred, 26); - -DECL_BITFIELD(GBASerializedAudioEnvelope, uint32_t); -DECL_BITS(GBASerializedAudioEnvelope, Length, 0, 7); -DECL_BITS(GBASerializedAudioEnvelope, NextStep, 7, 3); -DECL_BITS(GBASerializedAudioEnvelope, Frequency, 10, 11); - DECL_BITFIELD(GBASerializedHWFlags1, uint16_t); DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0); DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1); @@ -261,36 +244,14 @@ struct GBASerializedState { } cpu; struct { - struct { - GBASerializedAudioEnvelope envelope; - int32_t nextFrame; - int32_t reserved[2]; - int32_t nextEvent; - } ch1; - struct { - GBASerializedAudioEnvelope envelope; - int32_t reserved[2]; - int32_t nextEvent; - } ch2; - struct { - uint32_t wavebanks[8]; - int16_t length; - int16_t reserved; - int32_t nextEvent; - } ch3; - struct { - int32_t lfsr; - GBASerializedAudioEnvelope envelope; - int32_t reserved; - int32_t nextEvent; - } ch4; + struct GBSerializedPSGState psg; uint8_t fifoA[32]; uint8_t fifoB[32]; int32_t nextEvent; int32_t eventDiff; int32_t nextSample; uint32_t fifoSize; - GBASerializedAudioFlags flags; + GBSerializedAudioFlags flags; } audio; struct { From 1d021a72c7a1245adeceecd706dbebfb739f6247 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 15:08:42 -0700 Subject: [PATCH 34/90] GB Memory: Fix null deref when rumbling --- src/gb/memory.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gb/memory.c b/src/gb/memory.c index d42e4c682..df08311ec 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -721,7 +721,7 @@ void _GBMBC5(struct GBMemory* memory, uint16_t address, uint8_t value) { break; case 0x4: case 0x5: - if (memory->mbcType == GB_MBC5_RUMBLE) { + if (memory->mbcType == GB_MBC5_RUMBLE && memory->rumble) { memory->rumble->setRumble(memory->rumble, (value >> 3) & 1); value &= ~8; } From 66345e9b7eb2d854494aa61bc01f40c51765714a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 15:26:54 -0700 Subject: [PATCH 35/90] Qt: Add refresh button to controller editing --- CHANGES | 1 + src/platform/qt/GBAKeyEditor.cpp | 33 +++++++++++++++++++++----------- src/platform/qt/GBAKeyEditor.h | 1 + 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index 2d37353d7..1355dd10f 100644 --- a/CHANGES +++ b/CHANGES @@ -45,6 +45,7 @@ Misc: - ARM7: Flush prefetch cache when loading CPSR via MSR - Qt: Canonicalize file paths when loading games - OpenGL: Add texSize uniform + - Qt: Add refresh button to controller editing 0.4.0: (2016-02-02) Features: diff --git a/src/platform/qt/GBAKeyEditor.cpp b/src/platform/qt/GBAKeyEditor.cpp index f071620f2..0c33058ba 100644 --- a/src/platform/qt/GBAKeyEditor.cpp +++ b/src/platform/qt/GBAKeyEditor.cpp @@ -57,20 +57,11 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& #ifdef BUILD_SDL if (type == SDL_BINDING_BUTTON) { - controller->updateJoysticks(); - controller->recalibrateAxes(); - lookupAxes(map); - m_profileSelect = new QComboBox(this); - m_profileSelect->addItems(controller->connectedGamepads(type)); - int activeGamepad = controller->gamepad(type); - selectGamepad(activeGamepad); - if (activeGamepad > 0) { - m_profileSelect->setCurrentIndex(activeGamepad); - } - connect(m_profileSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectGamepad(int))); + updateJoysticks(); + m_clear = new QWidget(this); QHBoxLayout* layout = new QHBoxLayout; m_clear->setLayout(layout); @@ -97,6 +88,10 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const QString& (*m_currentKey)->clearAxis(); (*m_currentKey)->blockSignals(signalsBlocked); }); + + QPushButton* updateJoysticksButton = new QPushButton(tr("Refresh")); + layout->addWidget(updateJoysticksButton); + connect(updateJoysticksButton, SIGNAL(pressed()), this, SLOT(updateJoysticks())); } #endif @@ -355,3 +350,19 @@ void GBAKeyEditor::setLocation(QWidget* widget, qreal x, qreal y) { widget->setGeometry(s.width() * x - hint.width() / 2.0, s.height() * y - hint.height() / 2.0, hint.width(), hint.height()); } + +#ifdef BUILD_SDL +void GBAKeyEditor::updateJoysticks() { + m_controller->updateJoysticks(); + m_controller->recalibrateAxes(); + + m_profileSelect->clear(); + m_profileSelect->addItems(m_controller->connectedGamepads(m_type)); + int activeGamepad = m_controller->gamepad(m_type); + selectGamepad(activeGamepad); + if (activeGamepad > 0) { + m_profileSelect->setCurrentIndex(activeGamepad); + } + lookupAxes(m_controller->map()); +} +#endif diff --git a/src/platform/qt/GBAKeyEditor.h b/src/platform/qt/GBAKeyEditor.h index 844a0bebf..e609cf164 100644 --- a/src/platform/qt/GBAKeyEditor.h +++ b/src/platform/qt/GBAKeyEditor.h @@ -47,6 +47,7 @@ private slots: #ifdef BUILD_SDL void setAxisValue(int axis, int32_t value); void selectGamepad(int index); + void updateJoysticks(); #endif private: From c3f69e7b693ae568e33d83cee264040c2db3d516 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 23:06:32 -0700 Subject: [PATCH 36/90] GB: Add serialization headers --- src/gb/core.c | 3 ++- src/gb/serialize.h | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gb/core.c b/src/gb/core.c index eb6385cf6..3fe2318d1 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -224,7 +224,8 @@ static bool _GBCoreLoadState(struct mCore* core, const void* state) { } static bool _GBCoreSaveState(struct mCore* core, void* state) { - return GBSerialize(core->board, state); + GBSerialize(core->board, state); + return true; } static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) { diff --git a/src/gb/serialize.h b/src/gb/serialize.h index 8f4ae7d73..1284e0688 100644 --- a/src/gb/serialize.h +++ b/src/gb/serialize.h @@ -271,4 +271,7 @@ struct GBSerializedState { }; #pragma pack(pop) +bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state); +void GBSerialize(struct GB* gb, struct GBSerializedState* state); + #endif From 5ea104844d58095e3dc002d93074d3302976cae6 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 23:16:00 -0700 Subject: [PATCH 37/90] Core: Put back sram in savestates --- src/core/core.h | 3 +++ src/core/serialize.c | 20 +++++++------------- src/gb/core.c | 29 +++++++++++++++++++++++++++++ src/gba/core.c | 38 ++++++++++++++++++++++++++++++++++++++ src/gba/savedata.c | 21 +++++++++++++++++++++ src/gba/savedata.h | 1 + 6 files changed, 99 insertions(+), 13 deletions(-) diff --git a/src/core/core.h b/src/core/core.h index b1e5563a0..728d70aab 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -121,6 +121,9 @@ struct mCore { void (*detachDebugger)(struct mCore*); struct mCheatDevice* (*cheatDevice)(struct mCore*); + + size_t (*savedataClone)(struct mCore*, void** sram); + bool (*savedataLoad)(struct mCore*, const void* sram, size_t size); }; #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 diff --git a/src/core/serialize.c b/src/core/serialize.c index 5049f44a4..b64910e54 100644 --- a/src/core/serialize.c +++ b/src/core/serialize.c @@ -310,20 +310,16 @@ bool mCoreSaveStateNamed(struct mCore* core, struct VFile* vf, int flags) { mStateExtdataInit(&extdata); size_t stateSize = core->stateSize(core); if (flags & SAVESTATE_SAVEDATA) { - /* // TODO: A better way to do this would be nice - void* sram = malloc(SIZE_CART_FLASH1M); - struct VFile* svf = VFileFromMemory(sram, SIZE_CART_FLASH1M); - if (GBASavedataClone(&gba->memory.savedata, svf)) { + void* sram = NULL; + size_t size = core->savedataClone(core, &sram); + if (size) { struct mStateExtdataItem item = { - .size = svf->seek(svf, 0, SEEK_CUR), + .size = size, .data = sram, .clean = free }; mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item); - } else { - free(sram); } - svf->close(svf);*/ } struct VFile* cheatVf = 0; struct mCheatDevice* device; @@ -419,11 +415,9 @@ bool mCoreLoadStateNamed(struct mCore* core, struct VFile* vf, int flags) { } } if (flags & SAVESTATE_SAVEDATA && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &item)) { - /*struct VFile* svf = VFileFromMemory(item.data, item.size); - GBASavedataLoad(&gba->memory.savedata, svf); - if (svf) { - svf->close(svf); - }*/ + if (item.data) { + core->savedataLoad(core, item.data, item.size); + } } struct mCheatDevice* device; if (flags & SAVESTATE_CHEATS && (device = core->cheatDevice(core)) && mStateExtdataGet(&extdata, EXTDATA_CHEATS, &item)) { diff --git a/src/gb/core.c b/src/gb/core.c index 3fe2318d1..4ea53e673 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -409,6 +409,33 @@ static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) { return gbcore->cheatDevice; } +static size_t _GBCoreSavedataClone(struct mCore* core, void** sram) { + struct GB* gb = core->board; + struct VFile* vf = gb->sramVf; + if (vf) { + *sram = malloc(vf->size(vf)); + vf->seek(vf, 0, SEEK_SET); + return vf->read(vf, *sram, vf->size(vf)); + } + *sram = malloc(0x20000); + memcpy(*sram, gb->memory.sram, 0x20000); + return 0x20000; +} + +static bool _GBCoreSavedataLoad(struct mCore* core, const void* sram, size_t size) { + struct GB* gb = core->board; + struct VFile* vf = gb->sramVf; + if (vf) { + vf->seek(vf, 0, SEEK_SET); + return vf->write(vf, sram, size) > 0; + } + if (size > 0x20000) { + size = 0x20000; + } + memcpy(gb->memory.sram, sram, 0x20000); + return true; +} + struct mCore* GBCoreCreate(void) { struct GBCore* gbcore = malloc(sizeof(*gbcore)); struct mCore* core = &gbcore->d; @@ -470,5 +497,7 @@ struct mCore* GBCoreCreate(void) { core->attachDebugger = _GBCoreAttachDebugger; core->detachDebugger = _GBCoreDetachDebugger; core->cheatDevice = _GBCoreCheatDevice; + core->savedataClone = _GBCoreSavedataClone; + core->savedataLoad = _GBCoreSavedataLoad; return core; } diff --git a/src/gba/core.c b/src/gba/core.c index 8d9c7beb4..5667e5a24 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -13,6 +13,7 @@ #include "gba/extra/cli.h" #include "gba/overrides.h" #include "gba/renderers/video-software.h" +#include "gba/savedata.h" #include "gba/serialize.h" #include "util/memory.h" #include "util/patch.h" @@ -429,6 +430,41 @@ static struct mCheatDevice* _GBACoreCheatDevice(struct mCore* core) { return gbacore->cheatDevice; } +static size_t _GBACoreSavedataClone(struct mCore* core, void** sram) { + struct GBA* gba = core->board; + size_t size = GBASavedataSize(&gba->memory.savedata); + if (!size) { + *sram = NULL; + return 0; + } + *sram = malloc(size); + struct VFile* vf = VFileFromMemory(*sram, size); + if (!vf) { + free(*sram); + *sram = NULL; + return 0; + } + bool success = GBASavedataClone(&gba->memory.savedata, vf); + vf->close(vf); + if (!success) { + free(*sram); + *sram = NULL; + return 0; + } + return size; +} + +static bool _GBACoreSavedataLoad(struct mCore* core, const void* sram, size_t size) { + struct VFile* vf = VFileFromConstMemory(sram, size); + if (!vf) { + return false; + } + struct GBA* gba = core->board; + bool success = GBASavedataLoad(&gba->memory.savedata, vf); + vf->close(vf); + return success; +} + struct mCore* GBACoreCreate(void) { struct GBACore* gbacore = malloc(sizeof(*gbacore)); struct mCore* core = &gbacore->d; @@ -490,5 +526,7 @@ struct mCore* GBACoreCreate(void) { core->attachDebugger = _GBACoreAttachDebugger; core->detachDebugger = _GBACoreDetachDebugger; core->cheatDevice = _GBACoreCheatDevice; + core->savedataClone = _GBACoreSavedataClone; + core->savedataLoad = _GBACoreSavedataLoad; return core; } diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 6a83ff1ce..091b13fb3 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -125,6 +125,27 @@ bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) { return true; } +size_t GBASavedataSize(struct GBASavedata* savedata) { + switch (savedata->type) { + case SAVEDATA_SRAM: + return SIZE_CART_SRAM; + case SAVEDATA_FLASH512: + return SIZE_CART_FLASH512; + case SAVEDATA_FLASH1M: + return SIZE_CART_FLASH1M; + case SAVEDATA_EEPROM: + return SIZE_CART_EEPROM; + case SAVEDATA_FORCE_NONE: + return 0; + case SAVEDATA_AUTODETECT: + default: + if (savedata->vf) { + return savedata->vf->size(savedata->vf); + } + return 0; + } +} + bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) { if (savedata->vf) { off_t read = 0; diff --git a/src/gba/savedata.h b/src/gba/savedata.h index fbc03f1c3..b248729ff 100644 --- a/src/gba/savedata.h +++ b/src/gba/savedata.h @@ -97,6 +97,7 @@ void GBASavedataDeinit(struct GBASavedata* savedata); void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf); void GBASavedataUnmask(struct GBASavedata* savedata); +size_t GBASavedataSize(struct GBASavedata* savedata); bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out); bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in); void GBASavedataForceType(struct GBASavedata* savedata, enum SavedataType type, bool realisticTiming); From 61b4d53150ff03b6307ed00db761f2567a695f4b Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 30 May 2016 23:24:39 -0700 Subject: [PATCH 38/90] Qt: Enable savestates for GB games --- src/platform/qt/LoadSaveState.cpp | 9 ++++-- src/platform/qt/LoadSaveState.ui | 54 ------------------------------- src/platform/qt/Window.cpp | 8 ----- 3 files changed, 7 insertions(+), 64 deletions(-) diff --git a/src/platform/qt/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index e4ffb01ec..4c6786a12 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -43,10 +43,13 @@ LoadSaveState::LoadSaveState(GameController* controller, QWidget* parent) m_slots[7] = m_ui.state8; m_slots[8] = m_ui.state9; + unsigned width, height; + controller->thread()->core->desiredVideoDimensions(controller->thread()->core, &width, &height); int i; for (i = 0; i < NUM_SLOTS; ++i) { loadState(i + 1); m_slots[i]->installEventFilter(this); + m_slots[i]->setMaximumSize(width + 2, height + 2); connect(m_slots[i], &QAbstractButton::clicked, this, [this, i]() { triggerState(i + 1); }); } @@ -192,9 +195,11 @@ void LoadSaveState::loadState(int slot) { QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO QImage stateImage; + unsigned width, height; + thread->core->desiredVideoDimensions(thread->core, &width, &height); mStateExtdataItem item; - if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4) { - stateImage = QImage((uchar*) item.data, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, QImage::Format_ARGB32).rgbSwapped(); + if (mStateExtdataGet(&extdata, EXTDATA_SCREENSHOT, &item) && item.size >= width * height * 4) { + stateImage = QImage((uchar*) item.data, width, height, QImage::Format_ARGB32).rgbSwapped(); } if (!stateImage.isNull()) { diff --git a/src/platform/qt/LoadSaveState.ui b/src/platform/qt/LoadSaveState.ui index 38155cfd7..46e90fc03 100644 --- a/src/platform/qt/LoadSaveState.ui +++ b/src/platform/qt/LoadSaveState.ui @@ -37,12 +37,6 @@ 0 - - - 242 - 162 - - No Save @@ -65,12 +59,6 @@ 0 - - - 242 - 162 - - No Save @@ -115,12 +103,6 @@ 0 - - - 242 - 162 - - No Save @@ -143,12 +125,6 @@ 0 - - - 242 - 162 - - No Save @@ -171,12 +147,6 @@ 0 - - - 242 - 162 - - No Save @@ -199,12 +169,6 @@ 0 - - - 242 - 162 - - No Save @@ -227,12 +191,6 @@ 0 - - - 242 - 162 - - No Save @@ -255,12 +213,6 @@ 0 - - - 242 - 162 - - No Save @@ -283,12 +235,6 @@ 0 - - - 242 - 162 - - No Save diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index dcef253bd..3dd2bbda8 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -836,7 +836,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); }); m_gameActions.append(loadState); m_nonMpActions.append(loadState); - m_gbaActions.append(loadState); addControlledAction(fileMenu, loadState, "loadState"); QAction* saveState = new QAction(tr("&Save state"), fileMenu); @@ -844,7 +843,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); }); m_gameActions.append(saveState); m_nonMpActions.append(saveState); - m_gbaActions.append(saveState); addControlledAction(fileMenu, saveState, "saveState"); QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load")); @@ -856,14 +854,12 @@ void Window::setupMenu(QMenuBar* menubar) { connect(quickLoad, SIGNAL(triggered()), m_controller, SLOT(loadState())); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); - m_gbaActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); connect(quickSave, SIGNAL(triggered()), m_controller, SLOT(saveState())); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); - m_gbaActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, "quickSave"); quickLoadMenu->addSeparator(); @@ -874,7 +870,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState())); m_gameActions.append(undoLoadState); m_nonMpActions.append(undoLoadState); - m_gbaActions.append(undoLoadState); addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); @@ -882,7 +877,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState())); m_gameActions.append(undoSaveState); m_nonMpActions.append(undoSaveState); - m_gbaActions.append(undoSaveState); addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); quickLoadMenu->addSeparator(); @@ -895,7 +889,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); m_gameActions.append(quickLoad); m_nonMpActions.append(quickLoad); - m_gbaActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); @@ -903,7 +896,6 @@ void Window::setupMenu(QMenuBar* menubar) { connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); m_gameActions.append(quickSave); m_nonMpActions.append(quickSave); - m_gbaActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); } From c213ee9bb6f91e65af778d818772e7d36971b824 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 31 May 2016 23:24:20 -0700 Subject: [PATCH 39/90] GB: Polish savestates a bit --- src/gb/memory.c | 28 +++++++----- src/gb/serialize.c | 21 ++++++--- src/gb/serialize.h | 110 ++++++++++++++++++++++++++++++++++++++------- src/gb/timer.h | 4 ++ src/gb/video.c | 11 +++-- 5 files changed, 135 insertions(+), 39 deletions(-) diff --git a/src/gb/memory.c b/src/gb/memory.c index df08311ec..54b4a6f72 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -952,12 +952,14 @@ void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state->memory.dmaRemaining = memory->dmaRemaining; memcpy(state->memory.rtcRegs, memory->rtcRegs, sizeof(state->memory.rtcRegs)); - state->memory.sramAccess = memory->sramAccess; - state->memory.rtcAccess = memory->rtcAccess; - state->memory.rtcLatched = memory->rtcLatched; - state->memory.ime = memory->ime; - state->memory.isHdma = memory->isHdma; - state->memory.activeRtcReg = memory->activeRtcReg; + GBSerializedMemoryFlags flags = 0; + flags = GBSerializedMemoryFlagsSetSramAccess(flags, memory->sramAccess); + flags = GBSerializedMemoryFlagsSetRtcAccess(flags, memory->rtcAccess); + flags = GBSerializedMemoryFlagsSetRtcLatched(flags, memory->rtcLatched); + flags = GBSerializedMemoryFlagsSetIme(flags, memory->ime); + flags = GBSerializedMemoryFlagsSetIsHdma(flags, memory->isHdma); + flags = GBSerializedMemoryFlagsSetActiveRtcReg(flags, memory->activeRtcReg); + STORE_16LE(flags, 0, &state->memory.flags); } void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState* state) { @@ -983,12 +985,14 @@ void GBMemoryDeserialize(struct GBMemory* memory, const struct GBSerializedState memory->dmaRemaining = state->memory.dmaRemaining; memcpy(memory->rtcRegs, state->memory.rtcRegs, sizeof(state->memory.rtcRegs)); - memory->sramAccess = state->memory.sramAccess; - memory->rtcAccess = state->memory.rtcAccess; - memory->rtcLatched = state->memory.rtcLatched; - memory->ime = state->memory.ime; - memory->isHdma = state->memory.isHdma; - memory->activeRtcReg = state->memory.activeRtcReg; + GBSerializedMemoryFlags flags; + LOAD_16LE(flags, 0, &state->memory.flags); + memory->sramAccess = GBSerializedMemoryFlagsGetSramAccess(flags); + memory->rtcAccess = GBSerializedMemoryFlagsGetRtcAccess(flags); + memory->rtcLatched = GBSerializedMemoryFlagsGetRtcLatched(flags); + memory->ime = GBSerializedMemoryFlagsGetIme(flags); + memory->isHdma = GBSerializedMemoryFlagsGetIsHdma(flags); + memory->activeRtcReg = GBSerializedMemoryFlagsGetActiveRtcReg(flags); } void _pristineCow(struct GB* gb) { diff --git a/src/gb/serialize.c b/src/gb/serialize.c index 9a6c6bf03..e5985ad4e 100644 --- a/src/gb/serialize.c +++ b/src/gb/serialize.c @@ -5,6 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "serialize.h" +#include "gb/io.h" +#include "gb/timer.h" + mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate"); #ifdef _MSC_VER @@ -47,10 +50,13 @@ void GBSerialize(struct GB* gb, struct GBSerializedState* state) { state->cpu.executionState = gb->cpu->executionState; STORE_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector); - state->cpu.condition = gb->cpu->condition; - state->cpu.irqPending = gb->cpu->irqPending; + STORE_32LE(gb->eiPending, 0, &state->cpu.eiPending); - state->cpu.doubleSpeed = gb->doubleSpeed; + GBSerializedCpuFlags flags = 0; + flags = GBSerializedCpuFlagsSetCondition(flags, gb->cpu->condition); + flags = GBSerializedCpuFlagsSetIrqPending(flags, gb->cpu->irqPending); + flags = GBSerializedCpuFlagsSetDoubleSpeed(flags, gb->doubleSpeed); + STORE_32LE(flags, 0, &state->cpu.flags); GBMemorySerialize(&gb->memory, state); GBIOSerialize(gb, state); @@ -135,10 +141,13 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) { gb->cpu->executionState = state->cpu.executionState; LOAD_16LE(gb->cpu->irqVector, 0, &state->cpu.irqVector); - gb->cpu->condition = state->cpu.condition; - gb->cpu->irqPending = state->cpu.irqPending; + LOAD_32LE(gb->eiPending, 0, &state->cpu.eiPending); - gb->doubleSpeed = state->cpu.doubleSpeed; + GBSerializedCpuFlags flags; + LOAD_32LE(flags, 0, &state->cpu.flags); + gb->cpu->condition = GBSerializedCpuFlagsGetCondition(flags); + gb->cpu->irqPending = GBSerializedCpuFlagsGetIrqPending(flags); + gb->doubleSpeed = GBSerializedCpuFlagsGetDoubleSpeed(flags); LOAD_32LE(gb->cpu->cycles, 0, &state->cpu.cycles); LOAD_32LE(gb->cpu->nextEvent, 0, &state->cpu.nextEvent); diff --git a/src/gb/serialize.h b/src/gb/serialize.h index 1284e0688..9be4997cc 100644 --- a/src/gb/serialize.h +++ b/src/gb/serialize.h @@ -42,7 +42,11 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * | 0x0003A - 0x0003B: IRQ vector * | 0x0003C - 0x0003F: EI pending cycles * | 0x00040 - 0x00043: Reserved (DI pending cycles) - * | 0x00044 - 0x00048: Flags + * | 0x00044 - 0x00047: Flags + * | bit 0: Is condition met? + * | bit 1: Is condition IRQ pending? + * | bit 2: Double speed + * | bits 3 - 31: Reserved * 0x00048 - 0x0005B: Audio channel 1/framer state * | 0x00048 - 0x0004B: Envelepe timing * | bits 0 - 6: Remaining length @@ -96,6 +100,60 @@ mLOG_DECLARE_CATEGORY(GB_STATE); * | 0x000A8 - 0x000AB: Next event * | 0x000AC - 0x000AF: Event diff * | 0x000B0 - 0x000B3: Next sample + * 0x000B4 - 0x000153: Video state + * | 0x000B4 - 0x000B5: Current x + * | 0x000B6 - 0x000B7: Current y (ly) + * | 0x000B8 - 0x000BB: Next event + * | 0x000BC - 0x000BF: Event diff + * | 0x000C0 - 0x000C3: Next mode + * | 0x000C4 - 0x000C7: Dot cycle counter + * | 0x000C8 - 0x000CB: Frame counter + * | 0x000CC: Current VRAM bank + * | 0x000CD: Palette flags + * | bit 0: BCP increment + * | bit 1: OCP increment + * | bits 2 - 7: Reserved + * | 0x000CE - 0x000CF: Reserved + * | 0x000D0 - 0x000D1: BCP index + * | 0x000D1 - 0x000D3: OCP index + * | 0x000D4 - 0x00153: Palette entries + * 0x00154 - 0x000167: Timer state + * | 0x00154 - 0x00157: Next event + * | 0x00158 - 0x0015B: Event diff + * | 0x0015C - 0x0015F: Next DIV + * | 0x00160 - 0x00163: Next TIMA + * | 0x00164 - 0x00167: TIMA period + * 0x000168 - 0x000197: Memory state + * | 0x00168 - 0x00169: Current ROM bank + * | 0x0016A: Current WRAM bank + * | 0x0016B: Current SRAM bank + * | 0x0016C - 0x0016F: Next DMA + * | 0x00170 - 0x00171: Next DMA source + * | 0x00172 - 0x00173: Next DMA destination + * | 0x00174 - 0x00177: Next HDMA + * | 0x00178 - 0x00179: Next HDMA source + * | 0x0017A - 0x0017B: Next HDMA destination + * | 0x0017C - 0x0017D: HDMA remaining + * | 0x0017E: DMA remaining + * | 0x0017F - 0x00183: RTC registers + * | 0x00184 - 0x00193: MBC state (TODO) + * | 0x00194 - 0x00195: Flags + * | bit 0: SRAM accessable + * | bit 1: RTC accessible + * | bit 2: RTC latched + * | bit 3: IME + * | bit 4: Is HDMA active? + * | bits 5 - 7: Active RTC register + * | 0x00196 - 0x00197: Reserved (leave zero) + * 0x00198 - 0x0019F: Savestate creation time (usec since 1970) + * 0x001A0 - 0x0025F: Reserved (leave zero) + * 0x00260 - 0x002FF: OAM + * 0x00300 - 0x0037F: I/O memory + * 0x00380 - 0x003FE: HRAM + * 0x003FF: Interrupts enabled + * 0x00400 - 0x043FF: VRAM + * 0x04400 - 0x0C3FF: WRAM + * Total size: 0xC400 (50,176) bytes */ DECL_BITFIELD(GBSerializedAudioFlags, uint32_t); @@ -142,6 +200,28 @@ struct GBSerializedPSGState { } ch4; }; +DECL_BITFIELD(GBSerializedCpuFlags, uint32_t); +DECL_BIT(GBSerializedCpuFlags, Condition, 0); +DECL_BIT(GBSerializedCpuFlags, IrqPending, 1); +DECL_BIT(GBSerializedCpuFlags, DoubleSpeed, 2); + + +DECL_BITFIELD(GBSerializedVideoFlags, uint8_t); +DECL_BIT(GBSerializedVideoFlags, BcpIncrement, 0); +DECL_BIT(GBSerializedVideoFlags, OcpIncrement, 1); + +DECL_BITFIELD(GBSerializedMBC7Flags, uint8_t); +DECL_BITS(GBSerializedMBC7Flags, Command, 0, 2); +DECL_BIT(GBSerializedMBC7Flags, Writable, 2); + +DECL_BITFIELD(GBSerializedMemoryFlags, uint16_t); +DECL_BIT(GBSerializedMemoryFlags, SramAccess, 0); +DECL_BIT(GBSerializedMemoryFlags, RtcAccess, 1); +DECL_BIT(GBSerializedMemoryFlags, RtcLatched, 2); +DECL_BIT(GBSerializedMemoryFlags, Ime, 3); +DECL_BIT(GBSerializedMemoryFlags, IsHdma, 4); +DECL_BITS(GBSerializedMemoryFlags, ActiveRtcReg, 5, 3); + #pragma pack(push, 1) struct GBSerializedState { uint32_t versionMagic; @@ -175,9 +255,7 @@ struct GBSerializedState { int32_t eiPending; int32_t reservedDiPending; - bool condition : 1; - bool irqPending : 1; - bool doubleSpeed : 1; + GBSerializedCpuFlags flags; } cpu; struct { @@ -198,9 +276,9 @@ struct GBSerializedState { int32_t frameCounter; uint8_t vramCurrentBank; + GBSerializedVideoFlags flags; + uint16_t reserved; - bool bcpIncrement : 1; - bool ocpIncrement : 1; uint16_t bcpIndex; uint16_t ocpIndex; @@ -243,29 +321,27 @@ struct GBSerializedState { int8_t address; uint8_t srBits; uint32_t sr; - uint8_t command : 2; - bool writable : 1; + GBSerializedMBC7Flags flags; } mbc7; struct { uint8_t reserved[16]; } padding; }; - bool sramAccess : 1; - bool rtcAccess : 1; - bool rtcLatched : 1; - bool ime : 1; - bool isHdma : 1; - uint8_t activeRtcReg : 5; + GBSerializedMemoryFlags flags; + uint16_t reserved; } memory; + uint64_t creationUsec; + + uint32_t reserved[48]; + + uint8_t oam[GB_SIZE_OAM]; + uint8_t io[GB_SIZE_IO]; uint8_t hram[GB_SIZE_HRAM]; uint8_t ie; - uint64_t creationUsec; - - uint8_t oam[GB_SIZE_OAM]; uint8_t vram[GB_SIZE_VRAM]; uint8_t wram[GB_SIZE_WORKING_RAM]; }; diff --git a/src/gb/timer.h b/src/gb/timer.h index c650dad3a..84d5ea035 100644 --- a/src/gb/timer.h +++ b/src/gb/timer.h @@ -34,4 +34,8 @@ void GBTimerDivReset(struct GBTimer*); uint8_t GBTimerUpdateTAC(struct GBTimer*, GBRegisterTAC tac); void GBTimerUpdateTIMA(struct GBTimer* timer); +struct GBSerializedState; +void GBTimerSerialize(const struct GBTimer* timer, struct GBSerializedState* state); +void GBTimerDeserialize(struct GBTimer* timer, const struct GBSerializedState* state); + #endif diff --git a/src/gb/video.c b/src/gb/video.c index d18bb14f9..47fa1c3c9 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -443,8 +443,10 @@ void GBVideoSerialize(const struct GBVideo* video, struct GBSerializedState* sta STORE_32LE(video->frameCounter, 0, &state->video.frameCounter); state->video.vramCurrentBank = video->vramCurrentBank; - state->video.bcpIncrement = video->bcpIncrement; - state->video.ocpIncrement = video->ocpIncrement; + GBSerializedVideoFlags flags = 0; + flags = GBSerializedVideoFlagsSetBcpIncrement(flags, video->bcpIncrement); + flags = GBSerializedVideoFlagsSetOcpIncrement(flags, video->ocpIncrement); + state->video.flags = flags; STORE_16LE(video->bcpIndex, 0, &state->video.bcpIndex); STORE_16LE(video->ocpIndex, 0, &state->video.ocpIndex); @@ -467,8 +469,9 @@ void GBVideoDeserialize(struct GBVideo* video, const struct GBSerializedState* s LOAD_32LE(video->frameCounter, 0, &state->video.frameCounter); video->vramCurrentBank = state->video.vramCurrentBank; - video->bcpIncrement = state->video.bcpIncrement; - video->ocpIncrement = state->video.ocpIncrement; + GBSerializedVideoFlags flags = state->video.flags; + video->bcpIncrement = GBSerializedVideoFlagsGetBcpIncrement(flags); + video->ocpIncrement = GBSerializedVideoFlagsGetOcpIncrement(flags); LOAD_16LE(video->bcpIndex, 0, &state->video.bcpIndex); LOAD_16LE(video->ocpIndex, 0, &state->video.ocpIndex); From 59938208abdaa8a223ac237667c3a679cf4b280c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 1 Jun 2016 02:56:53 -0700 Subject: [PATCH 40/90] GB: Add preliminary HuC-3 support --- src/gb/memory.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/gb/memory.c b/src/gb/memory.c index 54b4a6f72..6c6c7b113 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -35,6 +35,7 @@ static void _GBMBC6(struct GBMemory*, uint16_t address, uint8_t value); static void _GBMBC7(struct GBMemory*, uint16_t address, uint8_t value); static uint8_t _GBMBC7Read(struct GBMemory*, uint16_t address); static void _GBMBC7Write(struct GBMemory*, uint16_t address, uint8_t value); +static void _GBHuC3(struct GBMemory*, uint16_t address, uint8_t value); static uint8_t GBFastLoad8(struct LR35902Core* cpu, uint16_t address) { if (UNLIKELY(address > cpu->memory.activeRegionEnd)) { @@ -188,6 +189,10 @@ void GBMemoryReset(struct GB* gb) { gb->memory.mbc = _GBMBC7; gb->memory.mbcType = GB_MBC7; break; + case 0xFE: + gb->memory.mbc = _GBHuC3; + gb->memory.mbcType = GB_HuC3; + break; } if (!gb->memory.wram) { @@ -229,6 +234,8 @@ uint8_t GBLoad8(struct LR35902Core* cpu, uint16_t address) { return gb->memory.sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)]; } else if (memory->mbcType == GB_MBC7) { return _GBMBC7Read(memory, address); + } else if (memory->mbcType == GB_HuC3) { + return 0x01; // TODO: Is this supposed to be the current SRAM bank? } return 0xFF; case GB_REGION_WORKING_RAM_BANK0: @@ -933,6 +940,38 @@ void _GBMBC7Write(struct GBMemory* memory, uint16_t address, uint8_t value) { } } +void _GBHuC3(struct GBMemory* memory, uint16_t address, uint8_t value) { + int bank = value & 0x3F; + if (address & 0x1FFF) { + mLOG(GB_MBC, STUB, "HuC-3 unknown value %04X:%02X", address, value); + } + + switch (address >> 13) { + case 0x0: + switch (value) { + case 0xA: + memory->sramAccess = true; + _switchSramBank(memory, memory->sramCurrentBank); + break; + default: + memory->sramAccess = false; + break; + } + break; + case 0x1: + mLOG(GB_MBC, STUB, "Bank switch %02X", value); + _switchBank(memory, bank); + break; + case 0x2: + _switchSramBank(memory, bank); + break; + default: + // TODO + mLOG(GB_MBC, STUB, "HuC-3 unknown address: %04X:%02X", address, value); + break; + } +} + void GBMemorySerialize(const struct GBMemory* memory, struct GBSerializedState* state) { memcpy(state->wram, memory->wram, GB_SIZE_WORKING_RAM); memcpy(state->hram, memory->hram, GB_SIZE_HRAM); From c313f0951829b527b4b06766582852b690f2f90a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 1 Jun 2016 03:09:23 -0700 Subject: [PATCH 41/90] GB Memory: Remove debugging code that got left in by accident --- src/gb/memory.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gb/memory.c b/src/gb/memory.c index 6c6c7b113..d999a7bed 100644 --- a/src/gb/memory.c +++ b/src/gb/memory.c @@ -959,7 +959,6 @@ void _GBHuC3(struct GBMemory* memory, uint16_t address, uint8_t value) { } break; case 0x1: - mLOG(GB_MBC, STUB, "Bank switch %02X", value); _switchBank(memory, bank); break; case 0x2: From 6e5a79564f9fefe7da0b3a31f5dff55c2e53ffde Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 1 Jun 2016 19:28:59 -0700 Subject: [PATCH 42/90] Core: Fix missing include --- src/core/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/core.c b/src/core/core.c index ea5d17971..c11051c6d 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -6,6 +6,7 @@ #include "core.h" #include "core/log.h" +#include "core/serialize.h" #include "util/vfs.h" #if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2 From 1a42ed2b372c2f7a8a7e84778f67099f40881784 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 4 Jun 2016 15:28:05 -0700 Subject: [PATCH 43/90] SDL, Debugger: Fix some deinitialization issues --- src/debugger/cli-debugger.c | 8 ++++++-- src/platform/sdl/main.c | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index b94c45a8a..9a6444580 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -741,7 +741,9 @@ static void _cliDebuggerDeinit(struct mDebugger* debugger) { el_end(cliDebugger->elstate); if (cliDebugger->system) { - cliDebugger->system->deinit(cliDebugger->system); + if (cliDebugger->system->deinit) { + cliDebugger->system->deinit(cliDebugger->system); + } free(cliDebugger->system); cliDebugger->system = 0; } @@ -770,7 +772,9 @@ void CLIDebuggerCreate(struct CLIDebugger* debugger) { void CLIDebuggerAttachSystem(struct CLIDebugger* debugger, struct CLIDebuggerSystem* system) { if (debugger->system) { - debugger->system->deinit(debugger->system); + if (debugger->system->deinit) { + debugger->system->deinit(debugger->system); + } free(debugger->system); } diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index d66f85668..e0ad73eac 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -146,6 +146,7 @@ int main(int argc, char** argv) { freeArguments(&args); mCoreConfigFreeOpts(&opts); mCoreConfigDeinit(&renderer.core->config); + renderer.core->deinit(renderer.core); return ret; } From b1b5cf8a11d8f19753312b554abc8752708a7347 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 6 Jun 2016 21:26:02 -0700 Subject: [PATCH 44/90] ARM7: Clean up instruction decoding for future expandability --- CHANGES | 1 + src/arm/decoder-arm.c | 8 +------- src/arm/decoder-thumb.c | 8 +------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index 1355dd10f..c04481d96 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,7 @@ Misc: - Qt: Canonicalize file paths when loading games - OpenGL: Add texSize uniform - Qt: Add refresh button to controller editing + - ARM7: Clean up instruction decoding for future expandability 0.4.0: (2016-02-02) Features: diff --git a/src/arm/decoder-arm.c b/src/arm/decoder-arm.c index e65f8aecd..3b082a0d2 100644 --- a/src/arm/decoder-arm.c +++ b/src/arm/decoder-arm.c @@ -437,18 +437,12 @@ static const ARMDecoder _armDecoderTable[0x1000] = { }; void ARMDecodeARM(uint32_t opcode, struct ARMInstructionInfo* info) { + memset(info, 0, sizeof(*info)); info->execMode = MODE_ARM; info->opcode = opcode; info->branchType = ARM_BRANCH_NONE; - info->traps = 0; - info->affectsCPSR = 0; info->condition = opcode >> 28; - info->sDataCycles = 0; - info->nDataCycles = 0; info->sInstructionCycles = 1; - info->nInstructionCycles = 0; - info->iCycles = 0; - info->cCycles = 0; ARMDecoder decoder = _armDecoderTable[((opcode >> 16) & 0xFF0) | ((opcode >> 4) & 0x00F)]; decoder(opcode, info); } diff --git a/src/arm/decoder-thumb.c b/src/arm/decoder-thumb.c index bfd8fc942..538773ec7 100644 --- a/src/arm/decoder-thumb.c +++ b/src/arm/decoder-thumb.c @@ -299,18 +299,12 @@ static const ThumbDecoder _thumbDecoderTable[0x400] = { }; void ARMDecodeThumb(uint16_t opcode, struct ARMInstructionInfo* info) { + memset(info, 0, sizeof(*info)); info->execMode = MODE_THUMB; info->opcode = opcode; info->branchType = ARM_BRANCH_NONE; - info->traps = 0; - info->affectsCPSR = 0; info->condition = ARM_CONDITION_AL; - info->sDataCycles = 0; - info->nDataCycles = 0; info->sInstructionCycles = 1; - info->nInstructionCycles = 0; - info->iCycles = 0; - info->cCycles = 0; ThumbDecoder decoder = _thumbDecoderTable[opcode >> 6]; decoder(opcode, info); } From 87758b274cb45e575ca34240cddc684e3fe03aa5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 6 Jun 2016 22:06:51 -0700 Subject: [PATCH 45/90] Debugger: CLI debugger now exits when end-of-stream is reached --- CHANGES | 1 + src/debugger/cli-debugger.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index c04481d96..4d9b89586 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,7 @@ Misc: - OpenGL: Add texSize uniform - Qt: Add refresh button to controller editing - ARM7: Clean up instruction decoding for future expandability + - Debugger: CLI debugger now exits when end-of-stream is reached 0.4.0: (2016-02-02) Features: diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index 9a6444580..62ec56e05 100644 --- a/src/debugger/cli-debugger.c +++ b/src/debugger/cli-debugger.c @@ -617,6 +617,7 @@ static void _commandLine(struct mDebugger* debugger) { while (debugger->state == DEBUGGER_PAUSED) { line = el_gets(cliDebugger->elstate, &count); if (!line) { + debugger->state = DEBUGGER_SHUTDOWN; return; } if (line[0] == '\n') { From 723b91dfe346c9f59ddd37e5e2074699b791907f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Tue, 7 Jun 2016 15:06:44 -0700 Subject: [PATCH 46/90] Qt: Load temporary saves (untested) --- src/core/core.h | 1 + src/gba/core.c | 6 ++++++ src/gba/savedata.c | 4 ++++ src/platform/qt/GameController.cpp | 17 +++++++++++++++++ src/platform/qt/GameController.h | 1 + src/platform/qt/Window.cpp | 15 +++++++++++++++ src/platform/qt/Window.h | 1 + 7 files changed, 45 insertions(+) diff --git a/src/core/core.h b/src/core/core.h index 728d70aab..eba8986c9 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -67,6 +67,7 @@ struct mCore { bool (*isROM)(struct VFile* vf); bool (*loadROM)(struct mCore*, struct VFile* vf); bool (*loadSave)(struct mCore*, struct VFile* vf); + bool (*loadTemporarySave)(struct mCore*, struct VFile* vf); void (*unloadROM)(struct mCore*); bool (*loadBIOS)(struct mCore*, struct VFile* vf, int biosID); diff --git a/src/gba/core.c b/src/gba/core.c index 5667e5a24..4d394c0ca 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -192,6 +192,11 @@ static bool _GBACoreLoadSave(struct mCore* core, struct VFile* vf) { return GBALoadSave(core->board, vf); } +static bool _GBACoreLoadTemporarySave(struct mCore* core, struct VFile* vf) { + GBASavedataMask(core->board, vf); + return true; // TODO: Return a real value +} + static bool _GBACoreLoadPatch(struct mCore* core, struct VFile* vf) { if (!vf) { return false; @@ -488,6 +493,7 @@ struct mCore* GBACoreCreate(void) { core->loadROM = _GBACoreLoadROM; core->loadBIOS = _GBACoreLoadBIOS; core->loadSave = _GBACoreLoadSave; + core->loadTemporarySave = _GBACoreLoadTemporarySave; core->loadPatch = _GBACoreLoadPatch; core->unloadROM = _GBACoreUnloadROM; core->reset = _GBACoreReset; diff --git a/src/gba/savedata.c b/src/gba/savedata.c index 091b13fb3..f041805a5 100644 --- a/src/gba/savedata.c +++ b/src/gba/savedata.c @@ -84,18 +84,22 @@ void GBASavedataDeinit(struct GBASavedata* savedata) { } void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf) { + enum SavedataType type = savedata->type; GBASavedataDeinit(savedata); savedata->vf = vf; savedata->mapMode = MAP_READ; + GBASavedataForceType(savedata, type, savedata->realisticTiming); } void GBASavedataUnmask(struct GBASavedata* savedata) { if (savedata->mapMode != MAP_READ) { return; } + enum SavedataType type = savedata->type; GBASavedataDeinit(savedata); savedata->vf = savedata->realVf; savedata->mapMode = MAP_WRITE; + GBASavedataForceType(savedata, type, savedata->realisticTiming); } bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) { diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index dcc8ddadc..da7ab1730 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -390,6 +390,23 @@ void GameController::loadBIOS(const QString& path) { } } +void GameController::loadSave(const QString& path, bool temporary) { + if (!isLoaded()) { + return; + } + VFile* vf = VFileDevice::open(path, temporary ? O_RDONLY : O_RDWR); + if (!vf) { + LOG(QT, ERROR) << tr("Failed to open save file: %1").arg(path); + return; + } + + if (temporary) { + m_threadContext.core->loadTemporarySave(m_threadContext.core, vf); + } else { + m_threadContext.core->loadSave(m_threadContext.core, vf); + } +} + void GameController::yankPak() { if (!m_gameOpen) { return; diff --git a/src/platform/qt/GameController.h b/src/platform/qt/GameController.h index 5ca2ca22c..5ce3a8a3e 100644 --- a/src/platform/qt/GameController.h +++ b/src/platform/qt/GameController.h @@ -103,6 +103,7 @@ signals: public slots: void loadGame(const QString& path); void loadBIOS(const QString& path); + void loadSave(const QString& path, bool temporary = true); void yankPak(); void replaceGame(const QString& path); void setUseBIOS(bool); diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 3dd2bbda8..1ab90199c 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -313,6 +313,15 @@ void Window::replaceROM() { } } +void Window::selectSave(bool temporary) { + QStringList formats{"*.sav"}; + QString filter = tr("Game Boy Advance save files (%1)").arg(formats.join(QChar(' '))); + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select save"), filter); + if (!filename.isEmpty()) { + m_controller->loadSave(filename, temporary); + } +} + void Window::multiplayerChanged() { disconnect(nullptr, this, SLOT(multiplayerChanged())); int attached = 1; @@ -812,6 +821,12 @@ void Window::setupMenu(QMenuBar* menubar) { installEventFilter(m_shortcutController); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &ROM..."), this, SLOT(selectROM()), QKeySequence::Open), "loadROM"); + QAction* loadTemporarySave = new QAction(tr("Load temporary save"), fileMenu); + connect(loadTemporarySave, &QAction::triggered, [this]() { this->selectSave(true); }); + m_gameActions.append(loadTemporarySave); + m_gbaActions.append(loadTemporarySave); + addControlledAction(fileMenu, loadTemporarySave, "loadTemporarySave"); + addControlledAction(fileMenu, fileMenu->addAction(tr("Load &BIOS..."), this, SLOT(selectBIOS())), "loadBIOS"); addControlledAction(fileMenu, fileMenu->addAction(tr("Load &patch..."), this, SLOT(selectPatch())), "loadPatch"); addControlledAction(fileMenu, fileMenu->addAction(tr("Boot BIOS"), m_controller, SLOT(bootBIOS())), "bootBIOS"); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 96674f096..954ab777d 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -59,6 +59,7 @@ signals: public slots: void selectROM(); + void selectSave(bool temporary); void selectBIOS(); void selectPatch(); void enterFullScreen(); From 6b1cbbd5e2f08c52a5f22ac6a71c3b2caba523d1 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 10 Jun 2016 10:17:29 -0700 Subject: [PATCH 47/90] ARM7: Fix setting spsr privilege bits when spsr is empty --- CHANGES | 1 + src/arm/isa-arm.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 4d9b89586..9bec23bed 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Bugfixes: - SDL: Fix SDL 1.2 build - ARM7: Fix flags on SBC/RSC - Util: Fix realloc semantics in utf16to8 + - ARM7: Fix setting spsr privilege bits when spsr is empty Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index 61bae3163..d1f7b7c29 100644 --- a/src/arm/isa-arm.c +++ b/src/arm/isa-arm.c @@ -659,7 +659,7 @@ DEFINE_INSTRUCTION_ARM(MSRR, int32_t operand = cpu->gprs[opcode & 0x0000000F]; int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK; - cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask);) + cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask) | 0x00000010;) DEFINE_INSTRUCTION_ARM(MRS, \ int rd = (opcode >> 12) & 0xF; \ @@ -701,7 +701,7 @@ DEFINE_INSTRUCTION_ARM(MSRRI, int32_t operand = ROR(opcode & 0x000000FF, rotate); int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0); mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK; - cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask);) + cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask) | 0x00000010;) DEFINE_INSTRUCTION_ARM(SWI, cpu->irqh.swi32(cpu, opcode & 0xFFFFFF)) From eb6cedde2e11184c560dc69717891d0c00c96452 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 15 Jun 2016 22:46:24 -0700 Subject: [PATCH 48/90] VFS: VFile.sync now updates modified time --- CHANGES | 1 + src/util/vfs/vfs-fd.c | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 9bec23bed..471468cdd 100644 --- a/CHANGES +++ b/CHANGES @@ -49,6 +49,7 @@ Misc: - Qt: Add refresh button to controller editing - ARM7: Clean up instruction decoding for future expandability - Debugger: CLI debugger now exits when end-of-stream is reached + - VFS: VFile.sync now updates modified time 0.4.0: (2016-02-02) Features: diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c index 59aa0706d..ec8abfb09 100644 --- a/src/util/vfs/vfs-fd.c +++ b/src/util/vfs/vfs-fd.c @@ -9,6 +9,7 @@ #include #ifndef _WIN32 #include +#include #else #include #endif @@ -159,8 +160,15 @@ static bool _vfdSync(struct VFile* vf, const void* buffer, size_t size) { UNUSED(size); struct VFileFD* vfd = (struct VFileFD*) vf; #ifndef _WIN32 + futimes(vfd->fd, NULL); return fsync(vfd->fd) == 0; #else - return FlushFileBuffers((HANDLE) _get_osfhandle(vfd->fd)); + HANDLE h = (HANDLE) _get_osfhandle(vfd->fd); + FILETIME ft; + SYSTEMTIME st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ft); + SetFileTime(h, NULL, &ft, &ft); + return FlushFileBuffers(h); #endif } From 415298ebcd0d702581d9b7d4244c4a69cc0dd54f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 16 Jun 2016 00:19:20 -0700 Subject: [PATCH 49/90] GBA BIOS: Fix ArcTan2 accuracy and boundary conditions --- CHANGES | 1 + src/gba/bios.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 471468cdd..5f42c8cc3 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Bugfixes: - ARM7: Fix flags on SBC/RSC - Util: Fix realloc semantics in utf16to8 - ARM7: Fix setting spsr privilege bits when spsr is empty + - GBA BIOS: Fix ArcTan2 accuracy and boundary conditions Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/gba/bios.c b/src/gba/bios.c index bcfb6b7ce..c81c50596 100644 --- a/src/gba/bios.c +++ b/src/gba/bios.c @@ -304,7 +304,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) { cpu->gprs[0] = sqrt((uint32_t) cpu->gprs[0]); break; case 0xA: - cpu->gprs[0] = atan2f(cpu->gprs[1] / 16384.f, cpu->gprs[0] / 16384.f) / (2 * M_PI) * 0x10000; + cpu->gprs[0] = (uint16_t) (atan2f(cpu->gprs[1] / 16384.f, cpu->gprs[0] / 16384.f) / (2 * M_PI) * 0x10001); break; case 0xB: case 0xC: From fb7ecb8079aa848c1b6811c8a5fdc692dd201bed Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 19 Jun 2016 15:05:48 -0700 Subject: [PATCH 50/90] GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku --- CHANGES | 1 + src/gba/overrides.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 5f42c8cc3..363313b12 100644 --- a/CHANGES +++ b/CHANGES @@ -51,6 +51,7 @@ Misc: - ARM7: Clean up instruction decoding for future expandability - Debugger: CLI debugger now exits when end-of-stream is reached - VFS: VFile.sync now updates modified time + - GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku 0.4.0: (2016-02-02) Features: diff --git a/src/gba/overrides.c b/src/gba/overrides.c index a2f70c7fe..339a3cb52 100644 --- a/src/gba/overrides.c +++ b/src/gba/overrides.c @@ -32,6 +32,11 @@ static const struct GBACartridgeOverride _overrides[] = { // Dragon Ball Z - The Legacy of Goku { "ALGP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + // Dragon Ball Z - The Legacy of Goku II + { "ALFJ", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + { "ALFE", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + { "ALFP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + // Dragon Ball Z - Taiketsu { "BDBE", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, { "BDBP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, @@ -151,6 +156,9 @@ static const struct GBACartridgeOverride _overrides[] = { // Top Gun - Combat Zones { "A2YE", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false }, + // Ueki no Housoku - Jingi Sakuretsu! Nouryokusha Battle + { "BUHJ", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE, false }, + // Wario Ware Twisted { "RZWJ", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE, false }, { "RZWE", SAVEDATA_SRAM, HW_RUMBLE | HW_GYRO, IDLE_LOOP_NONE, false }, From e1a4acec9f39a1f7081d56b69c851747caada807 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 19 Jun 2016 22:42:03 -0700 Subject: [PATCH 51/90] GB Audio: Rearrange WriteNR14 and fix GBC timing --- src/gb/audio.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/gb/audio.c b/src/gb/audio.c index 091c02914..d055d66e9 100644 --- a/src/gb/audio.c +++ b/src/gb/audio.c @@ -144,13 +144,6 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { } } if (GBAudioRegisterControlIsRestart(value << 8)) { - if (audio->nextEvent == INT_MAX) { - audio->eventDiff = 0; - } - if (audio->playingCh1) { - audio->ch1.control.hi = !audio->ch1.control.hi; - } - audio->nextCh1 = audio->eventDiff; audio->playingCh1 = audio->ch1.envelope.initialVolume || audio->ch1.envelope.direction; audio->ch1.envelope.currentVolume = audio->ch1.envelope.initialVolume; if (audio->ch1.envelope.currentVolume > 0) { @@ -158,6 +151,14 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) { } else { audio->ch1.envelope.dead = audio->ch1.envelope.stepTime ? 0 : 2; } + if (audio->nextEvent == INT_MAX) { + audio->eventDiff = 0; + } + if (audio->playingCh1) { + audio->ch1.control.hi = !audio->ch1.control.hi; + } + audio->nextCh1 = audio->eventDiff; + audio->ch1.realFrequency = audio->ch1.control.frequency; audio->ch1.sweepStep = audio->ch1.time; audio->ch1.sweepEnable = (audio->ch1.sweepStep != 8) || audio->ch1.shift; @@ -855,7 +856,7 @@ void _scheduleEvent(struct GBAudio* audio) { // TODO: Don't need p if (audio->p) { audio->nextEvent = audio->p->cpu->cycles >> audio->p->doubleSpeed; - audio->p->cpu->nextEvent = audio->nextEvent; + audio->p->cpu->nextEvent = audio->p->cpu->cycles; } else { audio->nextEvent = 0; } From ca46fedd34dad2c5982ea7ff6c371cc9d15de510 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 20 Jun 2016 21:34:06 -0700 Subject: [PATCH 52/90] SDL: Modernize software renderer --- src/platform/sdl/CMakeLists.txt | 4 ++- src/platform/sdl/sw-sdl.c | 48 ++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/platform/sdl/CMakeLists.txt b/src/platform/sdl/CMakeLists.txt index ad0e9f097..14a377f57 100644 --- a/src/platform/sdl/CMakeLists.txt +++ b/src/platform/sdl/CMakeLists.txt @@ -70,7 +70,6 @@ endif() if(BUILD_PANDORA) list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/pandora-sdl.c) else() - #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) list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) @@ -81,6 +80,9 @@ else() list(APPEND PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/opengl/gles2.c ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-common.c) include_directories(${OPENGLES2_INCLUDE_DIR}) endif() + if(NOT BUILD_GL AND NOT BUILD_GLES2) + list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c) + endif() endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) diff --git a/src/platform/sdl/sw-sdl.c b/src/platform/sdl/sw-sdl.c index b79718121..3074dec0d 100644 --- a/src/platform/sdl/sw-sdl.c +++ b/src/platform/sdl/sw-sdl.c @@ -5,17 +5,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "main.h" -#include "gba/supervisor/thread.h" +#include "core/thread.h" +#include "core/version.h" #include "util/arm-algo.h" static bool mSDLSWInit(struct mSDLRenderer* renderer); -static void mSDLSWRunloopGBA(struct mSDLRenderer* renderer, void* user); +static void mSDLSWRunloop(struct mSDLRenderer* renderer, void* user); static void mSDLSWDeinit(struct mSDLRenderer* renderer); void mSDLSWCreate(struct mSDLRenderer* renderer) { renderer->init = mSDLSWInit; renderer->deinit = mSDLSWDeinit; - renderer->runloop = mSDLSWRunloopGBA; + renderer->runloop = mSDLSWRunloop; } bool mSDLSWInit(struct mSDLRenderer* renderer) { @@ -27,6 +28,8 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { #endif #endif + unsigned width, height; + renderer->core->desiredVideoDimensions(renderer->core, &width, &height); #if SDL_VERSION_ATLEAST(2, 0, 0) renderer->window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen)); SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight); @@ -34,27 +37,27 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { renderer->sdlRenderer = SDL_CreateRenderer(renderer->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 - renderer->sdlTex = 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, width, height); #else - renderer->sdlTex = 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, width, height); #endif #else - renderer->sdlTex = 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, width, height); #endif - SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); - renderer->d.outputBufferStride /= BYTES_PER_PIXEL; + int stride; + SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->outputBuffer, &stride); + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, stride / BYTES_PER_PIXEL); #else SDL_Surface* surface = SDL_GetVideoSurface(); SDL_LockSurface(surface); if (renderer->ratio == 1) { - renderer->d.outputBuffer = surface->pixels; - renderer->d.outputBufferStride = surface->pitch / BYTES_PER_PIXEL; + renderer->core->setVideoBuffer(renderer->core, surface->pixels, surface->pitch / BYTES_PER_PIXEL); } else { #ifdef USE_PIXMAN - renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); - renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS; + renderer->outputBuffer = malloc(width * height * BYTES_PER_PIXEL); + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, width); #ifdef COLOR_16_BIT #ifdef COLOR_5_6_5 pixman_format_code_t format = PIXMAN_r5g6b5; @@ -64,8 +67,8 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { #else pixman_format_code_t format = PIXMAN_x8b8g8r8; #endif - renderer->pix = pixman_image_create_bits(format, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, - renderer->d.outputBuffer, renderer->d.outputBufferStride * BYTES_PER_PIXEL); + renderer->pix = pixman_image_create_bits(format, width, height, + renderer->outputBuffer, width * BYTES_PER_PIXEL); renderer->screenpix = pixman_image_create_bits(format, renderer->viewportWidth, renderer->viewportHeight, surface->pixels, surface->pitch); pixman_transform_t transform; @@ -82,8 +85,8 @@ bool mSDLSWInit(struct mSDLRenderer* renderer) { return true; } -void mSDLSWRunloopGBA(struct mSDLRenderer* renderer, void* user) { - struct GBAThread* context = user; +void mSDLSWRunloop(struct mSDLRenderer* renderer, void* user) { + struct mCoreThread* context = user; SDL_Event event; #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_Surface* surface = SDL_GetVideoSurface(); @@ -91,7 +94,7 @@ void mSDLSWRunloopGBA(struct mSDLRenderer* renderer, void* user) { while (context->state < THREAD_EXITING) { while (SDL_PollEvent(&event)) { - mSDLHandleEventGBA(context, &renderer->player, &event); + mSDLHandleEvent(context, &renderer->player, &event); } if (mCoreSyncWaitFrameStart(&context->sync)) { @@ -99,8 +102,9 @@ void mSDLSWRunloopGBA(struct mSDLRenderer* renderer, void* user) { SDL_UnlockTexture(renderer->sdlTex); SDL_RenderCopy(renderer->sdlRenderer, renderer->sdlTex, 0, 0); SDL_RenderPresent(renderer->sdlRenderer); - SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride); - renderer->d.outputBufferStride /= BYTES_PER_PIXEL; + int stride; + SDL_LockTexture(renderer->sdlTex, 0, (void**) &renderer->outputBuffer, &stride); + renderer->core->setVideoBuffer(renderer->core, renderer->outputBuffer, stride / BYTES_PER_PIXEL); #else #ifdef USE_PIXMAN if (renderer->ratio > 1) { @@ -112,10 +116,10 @@ void mSDLSWRunloopGBA(struct mSDLRenderer* renderer, void* user) { switch (renderer->ratio) { #if defined(__ARM_NEON) && COLOR_16_BIT case 2: - _neon2x(surface->pixels, renderer->d.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + _neon2x(surface->pixels, renderer->outputBuffer, width, height); break; case 4: - _neon4x(surface->pixels, renderer->d.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + _neon4x(surface->pixels, renderer->outputBuffer, width, height); break; #endif case 1: @@ -135,7 +139,7 @@ void mSDLSWRunloopGBA(struct mSDLRenderer* renderer, void* user) { void mSDLSWDeinit(struct mSDLRenderer* renderer) { if (renderer->ratio > 1) { - free(renderer->d.outputBuffer); + free(renderer->outputBuffer); } #if !SDL_VERSION_ATLEAST(2, 0, 0) SDL_Surface* surface = SDL_GetVideoSurface(); From 74caccd6df2298b0d123c677fabc5b751251156c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 25 Jun 2016 00:58:50 -0700 Subject: [PATCH 53/90] GB: Palette I/O fixes --- src/gb/io.c | 6 ++++-- src/gb/video.c | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/gb/io.c b/src/gb/io.c index 437e318c5..9cb6d221c 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -408,7 +408,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case REG_BCPD: GBVideoProcessDots(&gb->video); GBVideoWritePalette(&gb->video, address, value); - break; + return; case REG_OCPS: gb->video.ocpIndex = value & 0x3F; gb->video.ocpIncrement = value & 0x80; @@ -417,7 +417,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { case REG_OCPD: GBVideoProcessDots(&gb->video); GBVideoWritePalette(&gb->video, address, value); - break; + return; case REG_SVBK: GBMemorySwitchWramBank(&gb->memory, value); value = gb->memory.wramCurrentBank; @@ -537,7 +537,9 @@ uint8_t GBIORead(struct GB* gb, unsigned address) { case REG_HDMA3: case REG_HDMA4: case REG_HDMA5: + case REG_BCPS: case REG_BCPD: + case REG_OCPS: case REG_OCPD: case REG_SVBK: // Handled transparently by the registers diff --git a/src/gb/video.c b/src/gb/video.c index 47fa1c3c9..cc8301527 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -354,7 +354,9 @@ void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) if (video->bcpIncrement) { ++video->bcpIndex; video->bcpIndex &= 0x3F; - video->p->memory.io[REG_BCPD] = video->palette[video->bcpIndex >> 1]; + video->p->memory.io[REG_BCPS] &= 0x80; + video->p->memory.io[REG_BCPS] |= video->bcpIndex; + video->p->memory.io[REG_BCPD] = video->palette[video->bcpIndex >> 1] >> (8 * (video->bcpIndex & 1)); } break; case REG_OCPD: @@ -369,7 +371,9 @@ void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value) if (video->ocpIncrement) { ++video->ocpIndex; video->ocpIndex &= 0x3F; - video->p->memory.io[REG_OCPD] = video->palette[8 * 4 + (video->ocpIndex >> 1)]; + video->p->memory.io[REG_OCPS] &= 0x80; + video->p->memory.io[REG_OCPS] |= video->ocpIndex; + video->p->memory.io[REG_OCPD] = video->palette[8 * 4 + (video->ocpIndex >> 1)] >> (8 * (video->ocpIndex & 1)); } break; } From f5663675a5ac78c2927fe0796df92f72be421535 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 25 Jun 2016 01:34:13 -0700 Subject: [PATCH 54/90] GB I/O: Fix STAT writing --- src/gb/io.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gb/io.c b/src/gb/io.c index 9cb6d221c..3317acdf9 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -369,6 +369,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) { break; case REG_STAT: GBVideoWriteSTAT(&gb->video, value); + value = gb->video.stat; break; case 0x50: if (gb->memory.romBase != gb->memory.rom) { From 31cf1622a7a91c2c4b4c5713ec1354063d37db17 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 25 Jun 2016 01:34:29 -0700 Subject: [PATCH 55/90] GB Video: Call finishFrame at end of vblank --- src/gb/video.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gb/video.c b/src/gb/video.c index cc8301527..78ae8430f 100644 --- a/src/gb/video.c +++ b/src/gb/video.c @@ -115,7 +115,6 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->mode = 1; --video->frameskipCounter; if (video->frameskipCounter < 0) { - video->renderer->finishFrame(video->renderer); mCoreSyncPostFrame(video->p->sync); video->frameskipCounter = video->frameskip; } @@ -154,6 +153,7 @@ int32_t GBVideoProcessEvents(struct GBVideo* video, int32_t cycles) { video->p->memory.io[REG_IF] |= (1 << GB_IRQ_LCDSTAT); GBUpdateIRQs(video->p); } + video->renderer->finishFrame(video->renderer); break; } else if (video->ly == GB_VIDEO_VERTICAL_TOTAL_PIXELS) { video->p->memory.io[REG_LY] = 0; From c5092559ef04c700a233e8fcf3263e818766075f Mon Sep 17 00:00:00 2001 From: taizou Date: Wed, 22 Jun 2016 17:56:13 +0100 Subject: [PATCH 56/90] GBA Memory: Add emulation of Vast Fame protected carts --- CHANGES | 1 + src/gba/gba.c | 2 + src/gba/memory.c | 16 ++- src/gba/memory.h | 2 + src/gba/vfame.c | 298 +++++++++++++++++++++++++++++++++++++++++++++++ src/gba/vfame.h | 34 ++++++ 6 files changed, 352 insertions(+), 1 deletion(-) create mode 100644 src/gba/vfame.c create mode 100644 src/gba/vfame.h diff --git a/CHANGES b/CHANGES index 9bec23bed..6318cd17e 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,7 @@ Features: - Game Boy support - Support for encrypted CodeBreaker GBA cheats + - Emulation of Vast Fame protected GBA carts (taizou) Bugfixes: - VFS: Fix reading 7z archives without rewinding first - Qt: Fix sending gameStopped twice diff --git a/src/gba/gba.c b/src/gba/gba.c index bd50d3706..038e40e55 100644 --- a/src/gba/gba.c +++ b/src/gba/gba.c @@ -18,6 +18,7 @@ #include "gba/rr/rr.h" #include "gba/serialize.h" #include "gba/sio.h" +#include "gba/vfame.h" #include "util/crc32.h" #include "util/memory.h" @@ -474,6 +475,7 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) { gba->memory.mirroring = false; gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize); GBAHardwareInit(&gba->memory.hw, &((uint16_t*) gba->memory.rom)[GPIO_REG_DATA >> 1]); + GBAVFameDetect(&gba->memory.vfame, gba->memory.rom, gba->memory.romSize); // TODO: error check return true; } diff --git a/src/gba/memory.c b/src/gba/memory.c index 63f2a43a8..153d1249d 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -81,6 +81,8 @@ void GBAMemoryInit(struct GBA* gba) { cpu->memory.activeNonseqCycles16 = 0; gba->memory.biosPrefetch = 0; gba->memory.mirroring = false; + + GBAVFameInit(&gba->memory.vfame); } void GBAMemoryDeinit(struct GBA* gba) { @@ -383,6 +385,8 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \ } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { \ LOAD_32(value, address & memory->romMask, memory->rom); \ + } else if (memory->vfame.cartType) { \ + value = GBAVFameGetPatternValue(address, 32); \ } else { \ mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ value = ((address & ~3) >> 1) & 0xFFFF; \ @@ -515,6 +519,8 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { LOAD_16(value, address & memory->romMask, memory->rom); + } else if (memory->vfame.cartType) { + value = GBAVFameGetPatternValue(address, 16); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF; @@ -528,6 +534,8 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { LOAD_16(value, address & (SIZE_CART0 - 2), memory->rom); } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { LOAD_16(value, address & memory->romMask, memory->rom); + } else if (memory->vfame.cartType) { + value = GBAVFameGetPatternValue(address, 16); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load16: 0x%08X", address); value = (address >> 1) & 0xFFFF; @@ -613,6 +621,8 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) { value = ((uint8_t*) memory->rom)[address & (SIZE_CART0 - 1)]; } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { value = ((uint8_t*) memory->rom)[address & memory->romMask]; + } else if (memory->vfame.cartType) { + value = GBAVFameGetPatternValue(address, 8); } else { mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load8: 0x%08X", address); value = (address >> 1) & 0xFF; @@ -873,7 +883,11 @@ void GBAStore8(struct ARMCore* cpu, uint32_t address, int8_t value, int* cycleCo if (memory->savedata.type == SAVEDATA_FLASH512 || memory->savedata.type == SAVEDATA_FLASH1M) { GBASavedataWriteFlash(&memory->savedata, address, value); } else if (memory->savedata.type == SAVEDATA_SRAM) { - memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value; + if (memory->vfame.cartType) { + GBAVFameSramWrite(&memory->vfame, address, value, memory->savedata.data); + } else { + memory->savedata.data[address & (SIZE_CART_SRAM - 1)] = value; + } memory->savedata.dirty |= SAVEDATA_DIRT_NEW; } else if (memory->hw.devices & HW_TILT) { GBAHardwareTiltWrite(&memory->hw, address & OFFSET_MASK, value); diff --git a/src/gba/memory.h b/src/gba/memory.h index 4869f7159..8b4df9904 100644 --- a/src/gba/memory.h +++ b/src/gba/memory.h @@ -12,6 +12,7 @@ #include "gba/hardware.h" #include "gba/savedata.h" +#include "gba/vfame.h" enum GBAMemoryRegion { REGION_BIOS = 0x0, @@ -118,6 +119,7 @@ struct GBAMemory { struct GBACartridgeHardware hw; struct GBASavedata savedata; + struct GBAVFameCart vfame; size_t romSize; uint32_t romMask; uint16_t romID; diff --git a/src/gba/vfame.c b/src/gba/vfame.c new file mode 100644 index 000000000..4c50b5ec8 --- /dev/null +++ b/src/gba/vfame.c @@ -0,0 +1,298 @@ +/* Copyright (c) 2016 taizou + * + * 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 "vfame.h" +#include "gba/gba.h" +#include "gba/memory.h" + +static const uint8_t ADDRESS_REORDERING[4][16] = { + { 15, 14, 9, 1, 8, 10, 7, 3, 5, 11, 4, 0, 13, 12, 2, 6 }, + { 15, 7, 13, 5, 11, 6, 0, 9, 12, 2, 10, 14, 3, 1, 8, 4 }, + { 15, 0, 3, 12, 2, 4, 14, 13, 1, 8, 6, 7, 9, 5, 11, 10 } +}; +static const uint8_t ADDRESS_REORDERING_GEORGE[4][16] = { + { 15, 7, 13, 1, 11, 10, 14, 9, 12, 2, 4, 0, 3, 5, 8, 6 }, + { 15, 14, 3, 12, 8, 4, 0, 13, 5, 11, 6, 7, 9, 1, 2, 10 }, + { 15, 0, 9, 5, 2, 6, 7, 3, 1, 8, 10, 14, 13, 12, 11, 4 } +}; +static const uint8_t VALUE_REORDERING[4][16] = { + { 5, 4, 3, 2, 1, 0, 7, 6 }, + { 3, 2, 1, 0, 7, 6, 5, 4 }, + { 1, 0, 7, 6, 5, 4, 3, 2 } +}; +static const uint8_t VALUE_REORDERING_GEORGE[4][16] = { + { 3, 0, 7, 2, 1, 4, 5, 6 }, + { 1, 4, 3, 0, 5, 6, 7, 2 }, + { 5, 2, 1, 6, 7, 0, 3, 4 } +}; + +static const int8_t MODE_CHANGE_START_SEQUENCE[5] = { 0x99, 0x02, 0x05, 0x02, 0x03 }; +static const int8_t MODE_CHANGE_END_SEQUENCE[5] = { 0x99, 0x03, 0x62, 0x02, 0x56 }; + +// A portion of the initialisation routine that gets copied into RAM - Always seems to be present at 0x15C in VFame game ROM +static const char INIT_SEQUENCE[16] = { 0xB4, 0x00, 0x9F, 0xE5, 0x99, 0x10, 0xA0, 0xE3, 0x00, 0x10, 0xC0, 0xE5, 0xAC, 0x00, 0x9F, 0xE5 }; + +static bool _isInMirroredArea(uint32_t address, size_t romSize); +static uint32_t _getPatternValue(uint32_t addr); +static uint32_t _patternRightShift2(uint32_t addr); +static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode); +static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode); +static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength); + +void GBAVFameInit(struct GBAVFameCart* cart) { + cart->cartType = VFAME_NO; + cart->sramMode = -1; + cart->romMode = -1; + cart->acceptingModeChange = false; +} + +void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize) { + cart->cartType = VFAME_NO; + + // The initialisation code is also present & run in the dumps of Digimon Ruby & Sapphire from hacked/deprotected reprint carts, + // which would break if run in "proper" VFame mode so we need to exclude those.. + if (romSize == 0x2000000) { // the deprotected dumps are 32MB but no real VF games are this size + return; + } + + if (memcmp(INIT_SEQUENCE, &rom[0x57], sizeof(INIT_SEQUENCE)) == 0) { + cart->cartType = VFAME_STANDARD; + mLOG(GBA_MEM, INFO, "Vast Fame game detected"); + } + + // This game additionally operates with a different set of SRAM modes + // Its initialisation seems to be identical so the difference must be in the cart HW itself + // Other undumped games may have similar differences + if (memcmp("George Sango", &((struct GBACartridge*) rom)->title, 12) == 0) { + cart->cartType = VFAME_GEORGE; + mLOG(GBA_MEM, INFO, "George mode"); + } +} + +// This is not currently being used but would be called on ROM reads +// Emulates mirroring used by real VF carts, but no games seem to rely on this behaviour +uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize) { + if (cart->romMode == -1 && (address & 0x01000000) == 0) { + // When ROM mode is uninitialised, it just mirrors the first 0x80000 bytes + // All known games set the ROM mode to 00 which enables full range of reads, it's currently unknown what other values do + address &= 0x7FFFF; + } else if (_isInMirroredArea(address, romSize)) { + address -= 0x800000; + } + return address; +} + +static bool _isInMirroredArea(uint32_t address, size_t romSize) { + address &= 0x01FFFFFF; + // For some reason known 4m games e.g. Zook, Sango repeat the game at 800000 but the 8m Digimon R. does not + if (romSize != 0x400000) { + return false; + } + if (address < 0x800000) { + return false; + } + if (address >= 0x800000 + romSize) { + return false; + } + return true; +} + +// Looks like only 16-bit reads are done by games but others are possible... +uint32_t GBAVFameGetPatternValue(uint32_t address, int bits) { + switch (bits) { + case 8: + if (address & 1) { + return _getPatternValue(address) & 0xFF; + } else { + return (_getPatternValue(address) & 0xFF00) >> 8; + } + case 16: + return _getPatternValue(address); + case 32: + return (_getPatternValue(address) << 2) + _getPatternValue(address + 2); + } + return 0; +} + +// when you read from a ROM location outside the actual ROM data or its mirror, it returns a value based on some 16-bit transformation of the address +// which the game relies on to run +static uint32_t _getPatternValue(uint32_t addr) { + addr &= 0x1FFFFF; + uint32_t value = 0; + switch (addr & 0x1F0000) { + case 0x000000: + case 0x010000: + value = (addr >> 1) & 0xFFFF; + break; + case 0x020000: + value = addr & 0xFFFF; + break; + case 0x030000: + value = (addr & 0xFFFF) + 1; + break; + case 0x040000: + value = 0xFFFF - (addr & 0xFFFF); + break; + case 0x050000: + value = (0xFFFF - (addr & 0xFFFF)) - 1; + break; + case 0x060000: + value = (addr & 0xFFFF) ^ 0xAAAA; + break; + case 0x070000: + value = ((addr & 0xFFFF) ^ 0xAAAA) + 1; + break; + case 0x080000: + value = (addr & 0xFFFF) ^ 0x5555; + break; + case 0x090000: + value = ((addr & 0xFFFF) ^ 0x5555) - 1; + break; + case 0x0A0000: + case 0x0B0000: + value = _patternRightShift2(addr); + break; + case 0x0C0000: + case 0x0D0000: + value = 0xFFFF - _patternRightShift2(addr); + break; + case 0x0E0000: + case 0x0F0000: + value = _patternRightShift2(addr) ^ 0xAAAA; + break; + case 0x100000: + case 0x110000: + value = _patternRightShift2(addr) ^ 0x5555; + break; + case 0x120000: + value = 0xFFFF - ((addr & 0xFFFF) >> 1); + break; + case 0x130000: + value = 0xFFFF - ((addr & 0xFFFF) >> 1) - 0x8000; + break; + case 0x140000: + case 0x150000: + value = ((addr >> 1) & 0xFFFF) ^ 0xAAAA; + break; + case 0x160000: + case 0x170000: + value = ((addr >> 1) & 0xFFFF) ^ 0x5555; + break; + case 0x180000: + case 0x190000: + value = ((addr >> 1) & 0xFFFF) ^ 0xF0F0; + break; + case 0x1A0000: + case 0x1B0000: + value = ((addr >> 1) & 0xFFFF) ^ 0x0F0F; + break; + case 0x1C0000: + case 0x1D0000: + value = ((addr >> 1) & 0xFFFF) ^ 0xFF00; + break; + case 0x1E0000: + case 0x1F0000: + value = ((addr >> 1) & 0xFFFF) ^ 0x00FF; + break; + } + + return value & 0xFFFF; +} + +static uint32_t _patternRightShift2(uint32_t addr) { + uint32_t value = addr & 0xFFFF; + value >>= 2; + value += (addr & 3) == 2 ? 0x8000 : 0; + value += (addr & 0x10000) ? 0x4000 : 0; + return value; +} + +void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData) { + address &= 0x00FFFFFF; + // A certain sequence of writes to SRAM FFF8->FFFC can enable or disable "mode change" mode + // Currently unknown if these writes have to be sequential, or what happens if you write different values, if anything + if (address >= 0xFFF8 && address <= 0xFFFC) { + cart->writeSequence[address - 0xFFF8] = value; + if (address == 0xFFFC) { + if (memcmp(MODE_CHANGE_START_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_START_SEQUENCE)) == 0) { + cart->acceptingModeChange = true; + } + if (memcmp(MODE_CHANGE_END_SEQUENCE, cart->writeSequence, sizeof(MODE_CHANGE_END_SEQUENCE)) == 0) { + cart->acceptingModeChange = false; + } + } + } + + // If we are in "mode change mode" we can change either SRAM or ROM modes + // Currently unknown if other SRAM writes in this mode should have any effect + if (cart->acceptingModeChange) { + if (address == 0xFFFE) { + cart->sramMode = value; + } else if (address == 0xFFFD) { + cart->romMode = value; + } + } + + if (cart->sramMode == -1) { + // when SRAM mode is uninitialised you can't write to it + return; + } + + // if mode has been set - the address and value of the SRAM write will be modified + address = _modifySramAddress(cart->cartType, address, cart->sramMode); + value = _modifySramValue(cart->cartType, value, cart->sramMode); + // these writes are mirrored + address &= 0x7FFF; + sramData[address] = value; + sramData[address + 0x8000] = value; +} + +static uint32_t _modifySramAddress(enum GBAVFameCartType type, uint32_t address, int mode) { + mode &= 0x3; + if (mode == 0) { + return address; + } else if (type == VFAME_GEORGE) { + return _reorderBits(address, ADDRESS_REORDERING_GEORGE[mode - 1], 16); + } else { + return _reorderBits(address, ADDRESS_REORDERING[mode - 1], 16); + } +} + +static int8_t _modifySramValue(enum GBAVFameCartType type, uint8_t value, int mode) { + mode = (mode & 0xF) >> 2; + if (mode == 0) { + return value; + } else if (type == VFAME_GEORGE) { + return _reorderBits(value, VALUE_REORDERING_GEORGE[mode - 1], 8); + } else { + return _reorderBits(value, VALUE_REORDERING[mode - 1], 8); + } +} + +// Reorder bits in a byte according to the reordering given +static int _reorderBits(uint32_t value, const uint8_t* reordering, int reorderLength) { + uint32_t retval = value; + + int x; + for (x = reorderLength; x > 0; x--) { + uint8_t reorderPlace = reordering[reorderLength - x]; // get the reorder position + + uint32_t mask = 1 << reorderPlace; // move the bit to the position we want + uint32_t val = value & mask; // AND it with the original value + val >>= reorderPlace; // move the bit back, so we have the correct 0 or 1 + + unsigned destinationPlace = x - 1; + + uint32_t newMask = 1 << destinationPlace; + if (val == 1) { + retval |= newMask; + } else { + retval &= ~newMask; + } + } + + return retval; +} diff --git a/src/gba/vfame.h b/src/gba/vfame.h new file mode 100644 index 000000000..d418f7a9b --- /dev/null +++ b/src/gba/vfame.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2016 taizou + * + * 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/. */ + +// Support for copy protected unlicensed games from the company Vast Fame + +#ifndef GBA_VFAME_H +#define GBA_VFAME_H + +#include "util/common.h" + +enum GBAVFameCartType { + VFAME_NO = 0, + VFAME_STANDARD = 1, + VFAME_GEORGE = 2 +}; + +struct GBAVFameCart { + enum GBAVFameCartType cartType; + int sramMode; + int romMode; + int8_t writeSequence[5]; + bool acceptingModeChange; +}; + +void GBAVFameInit(struct GBAVFameCart* cart); +void GBAVFameDetect(struct GBAVFameCart* cart, uint32_t* rom, size_t romSize); +void GBAVFameSramWrite(struct GBAVFameCart* cart, uint32_t address, uint8_t value, uint8_t* sramData); +uint32_t GBAVFameModifyRomAddress(struct GBAVFameCart* cart, uint32_t address, size_t romSize); +uint32_t GBAVFameGetPatternValue(uint32_t address, int bits); + +#endif From c82ee873e1f12b83b9017caeb3c872015ec38cde Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 26 Jun 2016 20:31:12 -0700 Subject: [PATCH 57/90] Util: Fix intermittent build failure on OS X --- CHANGES | 1 + CMakeLists.txt | 2 +- src/util/formatting.h | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 6e0a70693..f4cca9f3f 100644 --- a/CHANGES +++ b/CHANGES @@ -53,6 +53,7 @@ Misc: - Debugger: CLI debugger now exits when end-of-stream is reached - VFS: VFile.sync now updates modified time - GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku + - Util: Fix intermittent build failure on OS X 0.4.0: (2016-02-02) Features: diff --git a/CMakeLists.txt b/CMakeLists.txt index 081b863cf..e49021081 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,7 +250,7 @@ if(HAVE_LOCALTIME_R) list(APPEND FUNCTION_DEFINES HAVE_LOCALTIME_R) endif() -if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE) +if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE OR APPLE) list(APPEND FUNCTION_DEFINES HAVE_LOCALE) if (HAVE_STRTOF_L) list(APPEND FUNCTION_DEFINES HAVE_STRTOF_L) diff --git a/src/util/formatting.h b/src/util/formatting.h index 43372f80c..b09c12944 100644 --- a/src/util/formatting.h +++ b/src/util/formatting.h @@ -12,9 +12,7 @@ #if defined(__APPLE__) || defined(__FreeBSD__) #include "xlocale.h" -#endif - -#ifndef HAVE_LOCALE +#elif !defined(HAVE_LOCALE) typedef const char* locale_t; #endif From fcd1ce8073f89581346e6ccdc15d3d538cac1655 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 27 Jun 2016 22:17:13 -0700 Subject: [PATCH 58/90] SDL: Fix sporadic crash when deinitializing audio --- CHANGES | 1 + src/platform/sdl/main.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index f4cca9f3f..2c6666774 100644 --- a/CHANGES +++ b/CHANGES @@ -29,6 +29,7 @@ Bugfixes: - Util: Fix realloc semantics in utf16to8 - ARM7: Fix setting spsr privilege bits when spsr is empty - GBA BIOS: Fix ArcTan2 accuracy and boundary conditions + - SDL: Fix sporadic crash when deinitializing audio Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index e0ad73eac..dab32bc10 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -185,6 +185,7 @@ int mSDLRun(struct mSDLRenderer* renderer, struct mArguments* args) { #endif if (mCoreThreadStart(&thread)) { renderer->runloop(renderer, &thread); + mSDLPauseAudio(&renderer->audio); mCoreThreadJoin(&thread); } else { didFail = true; From 6fe17bc0f8526e3974f534be7d5489b393dc735f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 8 Jul 2016 21:35:22 -0700 Subject: [PATCH 59/90] GBA Audio: Reset audio FIFO DMA if an invalid destination is set --- CHANGES | 1 + src/gba/audio.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 2c6666774..c2eb314c8 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugfixes: - ARM7: Fix setting spsr privilege bits when spsr is empty - GBA BIOS: Fix ArcTan2 accuracy and boundary conditions - SDL: Fix sporadic crash when deinitializing audio + - GBA Audio: Reset audio FIFO DMA if an invalid destination is set Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/gba/audio.c b/src/gba/audio.c index 0072b542c..5b2900654 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -126,6 +126,12 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* audio->chB.dmaSource = number; break; default: + if (audio->chA.dmaSource == number) { + audio->chA.dmaSource = -1; + } + if (audio->chB.dmaSource == number) { + audio->chB.dmaSource = -1; + } mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } From f64629448fc395f248e7d5485ee6a9958eaacfdd Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 9 Jul 2016 14:41:51 -0700 Subject: [PATCH 60/90] Qt: Split out ROM types in load menu --- src/platform/qt/Window.cpp | 44 +++++++++++++++++++++++++------------- src/platform/qt/Window.h | 2 ++ 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 1ab90199c..88c7ee654 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -271,11 +271,13 @@ void Window::saveConfig() { m_config->write(); } -void Window::selectROM() { - QStringList formats{ +QString Window::getFilters() const { + QStringList filters; + QStringList formats; + +#ifdef M_CORE_GBA + QStringList gbaFormats{ "*.gba", - "*.gb", - "*.gbc", #if defined(USE_LIBZIP) || defined(USE_ZLIB) "*.zip", #endif @@ -286,16 +288,12 @@ void Window::selectROM() { "*.mb", "*.rom", "*.bin"}; - QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' '))); - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); - if (!filename.isEmpty()) { - m_controller->loadGame(filename); - } -} + formats.append(gbaFormats); + filters.append(tr("Game Boy Advance ROMs (%1)").arg(gbaFormats.join(QChar(' ')))); +#endif -void Window::replaceROM() { - QStringList formats{ - "*.gba", +#ifdef M_CORE_GB + QStringList gbFormats{ "*.gb", "*.gbc", #if defined(USE_LIBZIP) || defined(USE_ZLIB) @@ -306,8 +304,24 @@ void Window::replaceROM() { #endif "*.rom", "*.bin"}; - QString filter = tr("Game Boy Advance ROMs (%1)").arg(formats.join(QChar(' '))); - QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), filter); + formats.append(gbFormats); + filters.append(tr("Game Boy ROMs (%1)").arg(gbFormats.join(QChar(' ')))); +#endif + + formats.removeDuplicates(); + filters.prepend(tr("All ROMs (%1)").arg(formats.join(QChar(' ')))); + return filters.join(";;"); +} + +void Window::selectROM() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters()); + if (!filename.isEmpty()) { + m_controller->loadGame(filename); + } +} + +void Window::replaceROM() { + QString filename = GBAApp::app()->getOpenFileName(this, tr("Select ROM"), getFilters()); if (!filename.isEmpty()) { m_controller->replaceGame(filename); } diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index 954ab777d..eac223dbd 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -146,6 +146,8 @@ private: void updateTitle(float fps = -1); + QString getFilters() const; + GameController* m_controller; Display* m_display; // TODO: Move these to a new class From 4e4a266d5307f1686f61ab251beb00bca69fb110 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 9 Jul 2016 14:58:12 -0700 Subject: [PATCH 61/90] Qt: Make -g flag work in Qt build --- CHANGES | 1 + doc/mgba-qt.6 | 7 ++++++- src/platform/qt/Window.cpp | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index c2eb314c8..09405880c 100644 --- a/CHANGES +++ b/CHANGES @@ -56,6 +56,7 @@ Misc: - VFS: VFile.sync now updates modified time - GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku - Util: Fix intermittent build failure on OS X + - Qt: Make -g flag work in Qt build 0.4.0: (2016-02-02) Features: diff --git a/doc/mgba-qt.6 b/doc/mgba-qt.6 index 4ab76aafc..6662ec0c7 100644 --- a/doc/mgba-qt.6 +++ b/doc/mgba-qt.6 @@ -11,7 +11,7 @@ .Nd Game Boy Advance emulator .Sh SYNOPSIS .Nm mgba-qt -.Op Fl 123456f +.Op Fl 123456fg .Op Fl b Ar biosfile .Op Fl l Ar loglevel .Op Fl p Ar patchfile @@ -42,6 +42,11 @@ will use the BIOS specified in the configuration file, or a high\(hylevel emulated BIOS if none is specified. .It Fl f Start the emulator full\(hyscreen. +.It Fl g +Start a +.Xr gdb 1 +session. +By default the session starts on port 2345. .It Fl l Ar loglevel Log messages during emulation. .Ar loglevel diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 88c7ee654..3f987853e 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -182,6 +182,15 @@ void Window::argumentsPassed(mArguments* args) { if (args->fname) { m_controller->loadGame(args->fname); } + +#ifdef USE_GDB_STUB + if (args->debuggerType == DEBUGGER_GDB) { + if (!m_gdbController) { + m_gdbController = new GDBController(m_controller, this); + m_gdbController->listen(); + } + } +#endif } void Window::resizeFrame(const QSize& size) { From 2dc729f17a45a4699a099a219ee18124904f94fa Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 9 Jul 2016 15:19:31 -0700 Subject: [PATCH 62/90] VFS: VFileFromFD should not open directories --- CHANGES | 1 + src/util/vfs/vfs-fd.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGES b/CHANGES index 09405880c..d98741f8b 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugfixes: - GBA BIOS: Fix ArcTan2 accuracy and boundary conditions - SDL: Fix sporadic crash when deinitializing audio - GBA Audio: Reset audio FIFO DMA if an invalid destination is set + - VFS: VFileFromFD should not open directories Misc: - GBA: Slightly optimize GBAProcessEvents - Qt: Add preset for DualShock 4 diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c index ec8abfb09..5b96f58b5 100644 --- a/src/util/vfs/vfs-fd.c +++ b/src/util/vfs/vfs-fd.c @@ -52,6 +52,12 @@ struct VFile* VFileFromFD(int fd) { return 0; } + struct stat stat; + if (fstat(fd, &stat) < 0 || S_ISDIR(stat.st_mode)) { + close(fd); + return 0; + } + struct VFileFD* vfd = malloc(sizeof(struct VFileFD)); if (!vfd) { return 0; From 5ca36e9418101caee8d41a22c817c1bcf7432e41 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Mon, 11 Jul 2016 17:47:06 -0700 Subject: [PATCH 63/90] All: Update CHANGES for 0.4.1 --- CHANGES | 85 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/CHANGES b/CHANGES index d98741f8b..e93897927 100644 --- a/CHANGES +++ b/CHANGES @@ -4,61 +4,66 @@ Features: - Support for encrypted CodeBreaker GBA cheats - Emulation of Vast Fame protected GBA carts (taizou) Bugfixes: - - VFS: Fix reading 7z archives without rewinding first - - Qt: Fix sending gameStopped twice - - Qt: Fix hang if audio sync is enabled and audio fails to initialize - - GBA BIOS: Fix RegisterRamReset setting DISPCNT to the wrong value - - OpenGL: Correct boolean vector strcmp strings for uniforms - - Wii: Fix tilting direction - - SDL: Fix joystick initialization on BSD - - SDL: Fix potential joystick crash in games with rumble - SDL: Fix axes being mapped wrong - - Qt: Fix initial state of key mapping - - Shaders: Fix AGS-001 shader with some bad drivers - GBA Memory: Fix mirror on non-overdumped Classic NES games - - Qt: Initialize m_useBios - - GBA Serialize: Fix memory corruption bug in GBAExtdataSerialize - - GBA Serialize: Fix loading savegames from savestates - - All: Fix several file handle leaks - - Util: Use closesocket on Windows - - GBA Memory: Fix executing code from OBJ region of VRAM - - Util: Fix socket bind addresses - - All: Fix instruction tables getting zeroed when linking sometimes - - SDL: Fix SDL 1.2 build - - ARM7: Fix flags on SBC/RSC - Util: Fix realloc semantics in utf16to8 - - ARM7: Fix setting spsr privilege bits when spsr is empty - - GBA BIOS: Fix ArcTan2 accuracy and boundary conditions - - SDL: Fix sporadic crash when deinitializing audio - - GBA Audio: Reset audio FIFO DMA if an invalid destination is set - - VFS: VFileFromFD should not open directories Misc: - - GBA: Slightly optimize GBAProcessEvents - - Qt: Add preset for DualShock 4 - - SDL: Remove default gamepad mappings - - Qt: Update 360 input profile on OS X to reflect newer drivers - - Qt: Remove use of NaN - 3DS: Use blip_add_delta_fast for a small speed improvement - - FFmpeg: Update dependencies on Ubuntu - OpenGL: Log shader compilation failure - - All: Allow use of external minizip library - Qt: Remove some C99isms from C++ code - Windows: Add native VDir support - All: Add QUIET parameter to silence CMake - - GBA Video: Null renderer should return proper register values - - Libretro: Disable logging game errors, BIOS calls and stubs in release builds - ARM7: Support forcing Thumb mode via MSR - ARM7: Flush prefetch cache when loading CPSR via MSR - - Qt: Canonicalize file paths when loading games - OpenGL: Add texSize uniform - - Qt: Add refresh button to controller editing - ARM7: Clean up instruction decoding for future expandability - - Debugger: CLI debugger now exits when end-of-stream is reached - - VFS: VFile.sync now updates modified time - - GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku - - Util: Fix intermittent build failure on OS X - Qt: Make -g flag work in Qt build +0.4.1: (2016-07-11) +Bugfixes: + - All: Fix several file handle leaks + - All: Fix instruction tables getting zeroed when linking sometimes + - ARM7: Fix flags on SBC/RSC + - ARM7: Fix setting spsr privilege bits when spsr is empty + - GBA Audio: Reset audio FIFO DMA if an invalid destination is set + - GBA BIOS: Fix RegisterRamReset setting DISPCNT to the wrong value + - GBA BIOS: Fix ArcTan2 accuracy and boundary conditions + - GBA Memory: Fix executing code from OBJ region of VRAM + - GBA Serialize: Fix memory corruption bug in GBAExtdataSerialize + - GBA Serialize: Fix loading savegames from savestates + - OpenGL: Correct boolean vector strcmp strings for uniforms + - Qt: Fix sending gameStopped twice + - Qt: Fix hang if audio sync is enabled and audio fails to initialize + - Qt: Fix initial state of key mapping + - Qt: Initialize m_useBios + - SDL: Fix joystick initialization on BSD + - SDL: Fix potential joystick crash in games with rumble + - SDL: Fix SDL 1.2 build + - SDL: Fix sporadic crash when deinitializing audio + - Shaders: Fix AGS-001 shader with some bad drivers + - Util: Use closesocket on Windows + - Util: Fix socket bind addresses + - VFS: Fix reading 7z archives without rewinding first + - VFS: VFileFromFD should not open directories + - Wii: Fix tilting direction + - Util: Fix realloc semantics in utf16to8 +Misc: + - All: Allow use of external minizip library + - Debugger: CLI debugger now exits when end-of-stream is reached + - FFmpeg: Update dependencies on Ubuntu + - GBA: Slightly optimize GBAProcessEvents + - GBA: Add overrides for DBZ: Legacy of Goku II and Ueki no Housoku + - GBA Video: Null renderer should return proper register values + - Libretro: Disable logging game errors, BIOS calls and stubs in release builds + - Qt: Add preset for DualShock 4 + - Qt: Update 360 input profile on OS X to reflect newer drivers + - Qt: Remove use of NaN + - Qt: Canonicalize file paths when loading games + - Qt: Add refresh button to controller editing + - SDL: Remove default gamepad mappings + - Util: Fix intermittent build failure on OS X + - VFS: VFile.sync now updates modified time + 0.4.0: (2016-02-02) Features: - Officially supported ports for the Nintendo 3DS, Wii, and PlayStation Vita From 9b9de7f2dbcf295becdcbbf16dbb9d74190e9936 Mon Sep 17 00:00:00 2001 From: Lars Wendler Date: Tue, 12 Jul 2016 15:46:58 +0200 Subject: [PATCH 64/90] Don't include internal zip library when USE_MINIZIP is defined. According to CmakeLists.txt file USE_MINIZIP enables linking against system minizip library but the code in src/util/vfs/vfs-zip.c doesn't reflect that. --- src/util/vfs/vfs-zip.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/util/vfs/vfs-zip.c b/src/util/vfs/vfs-zip.c index 83936f6e2..e06ae7b8e 100644 --- a/src/util/vfs/vfs-zip.c +++ b/src/util/vfs/vfs-zip.c @@ -34,7 +34,11 @@ enum { BLOCK_SIZE = 1024 }; #else +#ifdef USE_MINIZIP +#include +#else #include "third-party/zlib/contrib/minizip/unzip.h" +#endif #include "util/memory.h" struct VDirEntryZip { From f95be3071abe4393d940cfb14c2aa4f7dd0f1398 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 16 Jul 2016 23:53:40 -0700 Subject: [PATCH 65/90] Qt: Simplify OpenGL context creation --- CHANGES | 1 + src/platform/qt/Display.cpp | 9 ++++++--- src/platform/qt/DisplayGL.cpp | 16 ++++------------ src/platform/qt/DisplayGL.h | 4 ++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index e93897927..b0fcb8616 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Misc: - OpenGL: Add texSize uniform - ARM7: Clean up instruction decoding for future expandability - Qt: Make -g flag work in Qt build + - Qt: Simplify OpenGL context creation 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/platform/qt/Display.cpp b/src/platform/qt/Display.cpp index b231f0ebc..2bd772836 100644 --- a/src/platform/qt/Display.cpp +++ b/src/platform/qt/Display.cpp @@ -29,11 +29,13 @@ Display* Display::create(QWidget* parent) { switch (s_driver) { #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) case Driver::OPENGL: - return new DisplayGL(format, false, parent); + format.setVersion(3, 0); + return new DisplayGL(format, parent); #endif #ifdef BUILD_GL case Driver::OPENGL1: - return new DisplayGL(format, true, parent); + format.setVersion(1, 4); + return new DisplayGL(format, parent); #endif case Driver::QT: @@ -41,7 +43,8 @@ Display* Display::create(QWidget* parent) { default: #if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY) - return new DisplayGL(format, false, parent); + format.setVersion(3, 0); + return new DisplayGL(format, parent); #else return new DisplayQt(parent); #endif diff --git a/src/platform/qt/DisplayGL.cpp b/src/platform/qt/DisplayGL.cpp index a43ba6e88..3dc43acdd 100644 --- a/src/platform/qt/DisplayGL.cpp +++ b/src/platform/qt/DisplayGL.cpp @@ -24,22 +24,14 @@ extern "C" { using namespace QGBA; -DisplayGL::DisplayGL(const QGLFormat& format, bool force1, QWidget* parent) +DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent) : Display(parent) , m_isDrawing(false) , m_gl(new EmptyGLWidget(format, this)) , m_drawThread(nullptr) , m_context(nullptr) { - QGLFormat::OpenGLVersionFlags versions = QGLFormat::openGLVersionFlags(); - if (force1) { - versions &= QGLFormat::OpenGL_Version_1_1 | - QGLFormat::OpenGL_Version_1_2 | - QGLFormat::OpenGL_Version_1_3 | - QGLFormat::OpenGL_Version_1_4 | - QGLFormat::OpenGL_Version_1_5; - } - m_painter = new PainterGL(m_gl, versions); + m_painter = new PainterGL(format.majorVersion(), m_gl); m_gl->setMouseTracking(true); m_gl->setAttribute(Qt::WA_TransparentForMouseEvents); // This doesn't seem to work? } @@ -179,7 +171,7 @@ void DisplayGL::resizePainter() { } } -PainterGL::PainterGL(QGLWidget* parent, QGLFormat::OpenGLVersionFlags glVersion) +PainterGL::PainterGL(int majorVersion, QGLWidget* parent) : m_gl(parent) , m_active(false) , m_started(false) @@ -196,7 +188,7 @@ PainterGL::PainterGL(QGLWidget* parent, QGLFormat::OpenGLVersionFlags glVersion) #endif #if !defined(_WIN32) || defined(USE_EPOXY) - if (glVersion & (QGLFormat::OpenGL_Version_3_0 | QGLFormat::OpenGL_ES_Version_2_0)) { + if (majorVersion >= 2) { gl2Backend = new mGLES2Context; mGLES2ContextCreate(gl2Backend); m_backend = &gl2Backend->d; diff --git a/src/platform/qt/DisplayGL.h b/src/platform/qt/DisplayGL.h index a98518e46..5803dfd92 100644 --- a/src/platform/qt/DisplayGL.h +++ b/src/platform/qt/DisplayGL.h @@ -43,7 +43,7 @@ class DisplayGL : public Display { Q_OBJECT public: - DisplayGL(const QGLFormat& format, bool force1 = false, QWidget* parent = nullptr); + DisplayGL(const QGLFormat& format, QWidget* parent = nullptr); ~DisplayGL(); bool isDrawing() const override { return m_isDrawing; } @@ -80,7 +80,7 @@ class PainterGL : public QObject { Q_OBJECT public: - PainterGL(QGLWidget* parent, QGLFormat::OpenGLVersionFlags = QGLFormat::OpenGL_Version_1_1); + PainterGL(int majorVersion, QGLWidget* parent); ~PainterGL(); void setContext(mCoreThread*); From d3763b0f5de78ba2f18509dd5cad1b02af9c8ef5 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 17 Jul 2016 19:05:32 -0700 Subject: [PATCH 66/90] All: Fix CMake install path handling some --- CMakeLists.txt | 6 +++--- src/platform/qt/CMakeLists.txt | 16 +++++++++------- src/platform/qt/GBAApp.cpp | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e49021081..81b0eb463 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,14 +61,14 @@ if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g. Release or Debug)" FORCE) endif() -set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}") - include(GNUInstallDirs) if (NOT DEFINED LIBDIR) - set(LIBDIR "lib") + set(LIBDIR "${CMAKE_INSTALL_LIBDIR}") endif() +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_LIBDIR}") + if (NOT DEFINED MANDIR) set(MANDIR ${CMAKE_INSTALL_MANDIR}) endif() diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 0701046e7..8700f5514 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -167,15 +167,17 @@ if(WIN32) list(APPEND QT_LIBRARIES qwindows imm32) endif() endif() -if(APPLE) - set(DATA_DIR Applications/${PROJECT_NAME}.app/Contents/Resources) -else() - set(DATA_DIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME}) +if(NOT DEFINED DATADIR) + if(APPLE) + set(DATADIR Applications/${PROJECT_NAME}.app/Contents/Resources) + else() + set(DATADIR ${CMAKE_INSTALL_DATADIR}/${BINARY_NAME}) + endif() endif() -install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATA_DIR} COMPONENT ${BINARY_NAME}-qt) -install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATA_DIR} COMPONENT ${BINARY_NAME}-qt) +install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) +install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt) if(NOT WIN32 AND NOT APPLE) - list(APPEND QT_DEFINES DATA_DIR="${DATA_DIR}") + list(APPEND QT_DEFINES DATADIR="${DATADIR}") endif() add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES}) diff --git a/src/platform/qt/GBAApp.cpp b/src/platform/qt/GBAApp.cpp index 8e55d12ad..93db50bcc 100644 --- a/src/platform/qt/GBAApp.cpp +++ b/src/platform/qt/GBAApp.cpp @@ -188,8 +188,8 @@ QFileDialog* GBAApp::getSaveFileDialog(QWidget* owner, const QString& title, con } QString GBAApp::dataDir() { -#ifdef DATA_DIR - QString path = QString::fromUtf8(DATA_DIR); +#ifdef DATADIR + QString path = QString::fromUtf8(DATADIR); #else QString path = QCoreApplication::applicationDirPath(); #ifdef Q_OS_MAC From 5e4e00938cc930167bf2bb3108ab19d0a62e7608 Mon Sep 17 00:00:00 2001 From: Touched Date: Mon, 11 Jul 2016 19:22:56 +0200 Subject: [PATCH 67/90] Debugger: Support additional GDB stub packets Implements memory writing packets 'X' and 'M', and register writing packets 'G' and 'P'. Fixes the checksum verification to allow inclusion of '\0', which is needed for the binary data argument of packet 'X'. --- CHANGES | 1 + src/debugger/gdb-stub.c | 120 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index e93897927..89b3af8da 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,7 @@ Misc: - OpenGL: Add texSize uniform - ARM7: Clean up instruction decoding for future expandability - Qt: Make -g flag work in Qt build + - Debugger: Support register and memory writes via GDB stub 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/debugger/gdb-stub.c b/src/debugger/gdb-stub.c index c28b7f782..d575b7733 100644 --- a/src/debugger/gdb-stub.c +++ b/src/debugger/gdb-stub.c @@ -6,6 +6,7 @@ #include "gdb-stub.h" #include "core/core.h" +#include "gba/memory.h" #include @@ -149,7 +150,7 @@ static void _int2hex32(uint32_t value, char* out) { static uint32_t _readHex(const char* in, unsigned* out) { unsigned i; for (i = 0; i < 8; ++i) { - if (in[i] == ',') { + if (in[i] == ',' || in[i] == ':' || in[i] == '=') { break; } } @@ -210,6 +211,65 @@ static void _step(struct GDBStub* stub, const char* message) { UNUSED(message); } +static void _writeMemoryBinary(struct GDBStub* stub, const char* message) { + const char* readAddress = message; + unsigned i = 0; + uint32_t address = _readHex(readAddress, &i); + readAddress += i + 1; + + i = 0; + uint32_t size = _readHex(readAddress, &i); + readAddress += i + 1; + + if (size > 512) { + _error(stub, GDB_BAD_ARGUMENTS); + return; + } + + struct ARMCore* cpu = stub->d.core->cpu; + for (i = 0; i < size; i++) { + uint8_t byte = *readAddress; + ++readAddress; + + // Parse escape char + if (byte == 0x7D) { + byte = *readAddress ^ 0x20; + ++readAddress; + } + + GBAPatch8(cpu, address + i, byte, 0); + } + + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); +} + + +static void _writeMemory(struct GDBStub* stub, const char* message) { + const char* readAddress = message; + unsigned i = 0; + uint32_t address = _readHex(readAddress, &i); + readAddress += i + 1; + + i = 0; + uint32_t size = _readHex(readAddress, &i); + readAddress += i + 1; + + if (size > 512) { + _error(stub, GDB_BAD_ARGUMENTS); + return; + } + + struct ARMCore* cpu = stub->d.core->cpu; + for (i = 0; i < size; ++i, readAddress += 2) { + uint8_t byte = _hex2int(readAddress, 2); + GBAPatch8(cpu, address + i, byte, 0); + } + + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); +} + static void _readMemory(struct GDBStub* stub, const char* message) { const char* readAddress = message; unsigned i = 0; @@ -230,6 +290,20 @@ static void _readMemory(struct GDBStub* stub, const char* message) { _sendMessage(stub); } +static void _writeGPRs(struct GDBStub* stub, const char* message) { + struct ARMCore* cpu = stub->d.core->cpu; + const char* readAddress = message; + + int r; + for (r = 0; r < 16; ++r) { + cpu->gprs[r] = _hex2int(readAddress, 8); + readAddress += 8; + } + + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); +} + static void _readGPRs(struct GDBStub* stub, const char* message) { struct ARMCore* cpu = stub->d.core->cpu; UNUSED(message); @@ -243,6 +317,36 @@ static void _readGPRs(struct GDBStub* stub, const char* message) { _sendMessage(stub); } +static void _writeRegister(struct GDBStub* stub, const char* message) { + struct ARMCore* cpu = stub->d.core->cpu; + const char* readAddress = message; + + unsigned i = 0; + uint32_t reg = _readHex(readAddress, &i); + readAddress += i + 1; + + uint32_t value = _readHex(readAddress, &i); + +#ifdef _MSC_VER + value = _byteswap_ulong(value); +#else + value = __builtin_bswap32(value); +#endif + + if (reg < 0x10) { + cpu->gprs[reg] = value; + } else if (reg == 0x19) { + cpu->cpsr.packed = value; + } else { + stub->outgoing[0] = '\0'; + _sendMessage(stub); + return; + } + + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); +} + static void _readRegister(struct GDBStub* stub, const char* message) { struct ARMCore* cpu = stub->d.core->cpu; const char* readAddress = message; @@ -388,7 +492,7 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { int i; char messageType = message[0]; - for (i = 0; message[i] && message[i] != '#'; ++i, ++parsed) { + for (i = 0; message[i] != '#'; ++i, ++parsed) { checksum += message[i]; } if (!message[i]) { @@ -423,6 +527,9 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { case 'c': _continue(stub, message); break; + case 'G': + _writeGPRs(stub, message); + break; case 'g': _readGPRs(stub, message); break; @@ -431,9 +538,15 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); _sendMessage(stub); break; + case 'M': + _writeMemory(stub, message); + break; case 'm': _readMemory(stub, message); break; + case 'P': + _writeRegister(stub, message); + break; case 'p': _readRegister(stub, message); break; @@ -452,6 +565,9 @@ size_t _parseGDBMessage(struct GDBStub* stub, const char* message) { case 'v': _processVReadCommand(stub, message); break; + case 'X': + _writeMemoryBinary(stub, message); + break; case 'Z': _setBreakpoint(stub, message); break; From fc32c1ddd9ed2e40ab73276a4587b026ea3ea819 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 21 Jul 2016 21:59:01 -0700 Subject: [PATCH 68/90] Qt: Put back patch autoloading --- src/platform/qt/GameController.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index da7ab1730..c2ad434d0 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -356,6 +356,8 @@ void GameController::openGame(bool biosOnly) { m_threadContext.core->loadPatch(m_threadContext.core, patch); } patch->close(patch); + } else { + mCoreAutoloadPatch(m_threadContext.core); } m_inputController->recalibrateAxes(); From b1f750e441af63b35141702ac00fd2f13313bd44 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 22 Jul 2016 01:05:49 -0700 Subject: [PATCH 69/90] Qt: First pass at a tile view --- src/platform/qt/CMakeLists.txt | 3 + src/platform/qt/TilePainter.cpp | 40 +++++++ src/platform/qt/TilePainter.h | 37 ++++++ src/platform/qt/TileView.cpp | 111 ++++++++++++++++++ src/platform/qt/TileView.h | 45 ++++++++ src/platform/qt/TileView.ui | 199 ++++++++++++++++++++++++++++++++ src/platform/qt/Window.cpp | 12 ++ src/platform/qt/Window.h | 1 + 8 files changed, 448 insertions(+) create mode 100644 src/platform/qt/TilePainter.cpp create mode 100644 src/platform/qt/TilePainter.h create mode 100644 src/platform/qt/TileView.cpp create mode 100644 src/platform/qt/TileView.h create mode 100644 src/platform/qt/TileView.ui diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 8700f5514..8ec615750 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -102,6 +102,8 @@ set(SOURCE_FILES ShortcutController.cpp ShortcutView.cpp Swatch.cpp + TilePainter.cpp + TileView.cpp Window.cpp VFileDevice.cpp VideoView.cpp) @@ -121,6 +123,7 @@ qt5_wrap_ui(UI_FILES SettingsView.ui ShaderSelector.ui ShortcutView.ui + TileView.ui VideoView.ui) set(QT_LIBRARIES) diff --git a/src/platform/qt/TilePainter.cpp b/src/platform/qt/TilePainter.cpp new file mode 100644 index 000000000..5c6ce2310 --- /dev/null +++ b/src/platform/qt/TilePainter.cpp @@ -0,0 +1,40 @@ +/* Copyright (c) 2013-2016 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 "TilePainter.h" + +#include +#include +#include + +using namespace QGBA; + +TilePainter::TilePainter(QWidget* parent) + : QWidget(parent) +{ + m_backing = QPixmap(256, 768); + m_backing.fill(Qt::transparent); +} + +void TilePainter::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.drawPixmap(QPoint(), m_backing); +} + +void TilePainter::mousePressEvent(QMouseEvent* event) { + int x = event->x() / 8; + int y = event->y() / 8; + emit indexPressed(y * 32 + x); +} + +void TilePainter::setTile(int index, const uint16_t* data) { + QPainter painter(&m_backing); + int x = index & 31; + int y = index / 32; + QRect r(x * 8, y * 8, 8, 8); + QImage tile(reinterpret_cast(data), 8, 8, QImage::Format_RGB555); + painter.fillRect(r, tile.rgbSwapped()); + update(r); +} diff --git a/src/platform/qt/TilePainter.h b/src/platform/qt/TilePainter.h new file mode 100644 index 000000000..412006228 --- /dev/null +++ b/src/platform/qt/TilePainter.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2013-2016 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_TILE_PAINTER +#define QGBA_TILE_PAINTER + +#include +#include +#include + +namespace QGBA { + +class TilePainter : public QWidget { +Q_OBJECT + +public: + TilePainter(QWidget* parent = nullptr); + +public slots: + void setTile(int index, const uint16_t*); + +signals: + void indexPressed(int index); + +protected: + void paintEvent(QPaintEvent*) override; + void mousePressEvent(QMouseEvent*) override; + +private: + QPixmap m_backing; +}; + +} + +#endif diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp new file mode 100644 index 000000000..5d6c6b880 --- /dev/null +++ b/src/platform/qt/TileView.cpp @@ -0,0 +1,111 @@ +/* Copyright (c) 2013-2016 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 "TileView.h" + +#include "GBAApp.h" + +#include + +extern "C" { +#include "gba/gba.h" +} + +using namespace QGBA; + +TileView::TileView(GameController* controller, QWidget* parent) + : QWidget(parent) + , m_controller(controller) + , m_paletteId(0) +{ + m_ui.setupUi(this); + GBAVideoTileCacheInit(&m_tileCache); + + m_ui.preview->setDimensions(QSize(8, 8)); + updateTiles(); + + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + m_ui.tileId->setFont(font); + m_ui.address->setFont(font); + + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateTiles())); + connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); + connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(selectIndex(int))); + connect(m_ui.paletteId, SIGNAL(valueChanged(int)), this, SLOT(updatePalette(int))); + connect(m_ui.palette256, SIGNAL(toggled(bool)), this, SLOT(updateTiles())); +} + +TileView::~TileView() { + GBAVideoTileCacheDeinit(&m_tileCache); +} + +void TileView::selectIndex(int index) { + const uint16_t* data; + m_ui.tileId->setText(QString::number(index)); + if (m_ui.palette256->isChecked()) { + m_ui.address->setText(tr("0x%0").arg(index * 64 | BASE_VRAM, 8, 16, QChar('0'))); + if (index < 1024) { + data = GBAVideoTileCacheGetTile256(&m_tileCache, index, 0); + } else { + data = GBAVideoTileCacheGetTile256(&m_tileCache, index, 1); + } + } else { + m_ui.address->setText(tr("0x%0").arg(index * 32 | BASE_VRAM, 8, 16, QChar('0'))); + if (index < 2048) { + data = GBAVideoTileCacheGetTile16(&m_tileCache, index, m_paletteId); + } else { + data = GBAVideoTileCacheGetTile16(&m_tileCache, index, m_paletteId + 16); + } + } + for (int i = 0; i < 64; ++i) { + m_ui.preview->setColor(i, data[i]); + } + m_ui.preview->update(); +} + +void TileView::updateTiles() { + if (!m_controller->thread() || !m_controller->thread()->core) { + return; + } + + GBA* gba = static_cast(m_controller->thread()->core->board); + GBAVideoTileCacheAssociate(&m_tileCache, &gba->video); + + if (m_ui.palette256->isChecked()) { + m_ui.tiles->setMinimumSize(256, 384); + for (int i = 0; i < 1024; ++i) { + const uint16_t* data = GBAVideoTileCacheGetTile256IfDirty(&m_tileCache, i, 0); + if (data) { + m_ui.tiles->setTile(i, data); + } + } + for (int i = 1024; i < 1536; ++i) { + const uint16_t* data = GBAVideoTileCacheGetTile256IfDirty(&m_tileCache, i, 1); + if (data) { + m_ui.tiles->setTile(i, data); + } + } + } else { + m_ui.tiles->setMinimumSize(256, 768); + for (int i = 0; i < 2048; ++i) { + const uint16_t* data = GBAVideoTileCacheGetTile16IfDirty(&m_tileCache, i, m_paletteId); + if (data) { + m_ui.tiles->setTile(i, data); + } + } + for (int i = 2048; i < 3072; ++i) { + const uint16_t* data = GBAVideoTileCacheGetTile16IfDirty(&m_tileCache, i, m_paletteId + 16); + if (data) { + m_ui.tiles->setTile(i, data); + } + } + } +} + +void TileView::updatePalette(int palette) { + m_paletteId = palette; + updateTiles(); +} diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h new file mode 100644 index 000000000..5ef4ad9c2 --- /dev/null +++ b/src/platform/qt/TileView.h @@ -0,0 +1,45 @@ +/* Copyright (c) 2013-2016 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_TILE_VIEW +#define QGBA_TILE_VIEW + +#include + +#include "GameController.h" + +#include "ui_TileView.h" + +extern "C" { +#include "gba/renderers/tile-cache.h" +} + +namespace QGBA { + +class TileView : public QWidget { +Q_OBJECT + +public: + TileView(GameController* controller, QWidget* parent = nullptr); + virtual ~TileView(); + +public slots: + void updateTiles(); + void updatePalette(int); + +private slots: + void selectIndex(int); + +private: + Ui::TileView m_ui; + + GameController* m_controller; + GBAVideoTileCache m_tileCache; + int m_paletteId; +}; + +} + +#endif diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui new file mode 100644 index 000000000..cd662adb0 --- /dev/null +++ b/src/platform/qt/TileView.ui @@ -0,0 +1,199 @@ + + + TileView + + + + 0 + 0 + 288 + 269 + + + + Tiles + + + + + + 256 colors + + + + + + + Preview + + + + + + + + Qt::Horizontal + + + + + + + + 87 + 87 + + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + + + + Tile # + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Address + + + + + + + 0x06000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + 15 + + + 1 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 16 + 16 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + + + + + + QGBA::TilePainter + QWidget +
TilePainter.h
+ 1 +
+ + QGBA::Swatch + QWidget +
Swatch.h
+ 1 +
+
+ + +
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 3f987853e..6f6fe594d 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -30,6 +30,7 @@ #include "MemoryView.h" #include "OverrideView.h" #include "PaletteView.h" +#include "TileView.h" #include "ROMInfo.h" #include "SensorView.h" #include "SettingsView.h" @@ -430,6 +431,11 @@ void Window::openPaletteWindow() { openView(paletteWindow); } +void Window::openTileWindow() { + TileView* tileWindow = new TileView(m_controller); + openView(tileWindow); +} + void Window::openMemoryWindow() { MemoryView* memoryWindow = new MemoryView(m_controller); openView(memoryWindow); @@ -1288,6 +1294,12 @@ void Window::setupMenu(QMenuBar* menubar) { m_gbaActions.append(paletteView); addControlledAction(toolsMenu, paletteView, "paletteWindow"); + QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu); + connect(tileView, SIGNAL(triggered()), this, SLOT(openTileWindow())); + m_gameActions.append(tileView); + m_gbaActions.append(tileView); + addControlledAction(toolsMenu, tileView, "tileWindow"); + QAction* memoryView = new QAction(tr("View memory..."), toolsMenu); connect(memoryView, SIGNAL(triggered()), this, SLOT(openMemoryWindow())); m_gameActions.append(memoryView); diff --git a/src/platform/qt/Window.h b/src/platform/qt/Window.h index eac223dbd..309fdb784 100644 --- a/src/platform/qt/Window.h +++ b/src/platform/qt/Window.h @@ -82,6 +82,7 @@ public slots: void openCheatsWindow(); void openPaletteWindow(); + void openTileWindow(); void openMemoryWindow(); void openIOViewer(); From ccf66299e684f6dd1f27afb3e1ec32d2ac26ec7a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 22 Jul 2016 19:16:30 -0700 Subject: [PATCH 70/90] Qt: Very broken tile viewer resizing --- src/platform/qt/Swatch.cpp | 1 - src/platform/qt/TilePainter.cpp | 20 ++++++- src/platform/qt/TilePainter.h | 2 + src/platform/qt/TileView.cpp | 26 +++++++- src/platform/qt/TileView.h | 6 +- src/platform/qt/TileView.ui | 101 ++++++++++++++++++-------------- 6 files changed, 103 insertions(+), 53 deletions(-) diff --git a/src/platform/qt/Swatch.cpp b/src/platform/qt/Swatch.cpp index f3332274b..cc575cff1 100644 --- a/src/platform/qt/Swatch.cpp +++ b/src/platform/qt/Swatch.cpp @@ -18,7 +18,6 @@ Swatch::Swatch(QWidget* parent) : QWidget(parent) { m_size = 10; - setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); } void Swatch::setSize(int size) { diff --git a/src/platform/qt/TilePainter.cpp b/src/platform/qt/TilePainter.cpp index 5c6ce2310..e78dd95c1 100644 --- a/src/platform/qt/TilePainter.cpp +++ b/src/platform/qt/TilePainter.cpp @@ -16,6 +16,7 @@ TilePainter::TilePainter(QWidget* parent) { m_backing = QPixmap(256, 768); m_backing.fill(Qt::transparent); + resize(256, 768); } void TilePainter::paintEvent(QPaintEvent* event) { @@ -23,18 +24,31 @@ void TilePainter::paintEvent(QPaintEvent* event) { painter.drawPixmap(QPoint(), m_backing); } +void TilePainter::resizeEvent(QResizeEvent* event) { + if (width() / 8 != m_backing.width() / 8) { + m_backing = QPixmap(width(), (3072 * 8) / (width() / 8)); + m_backing.fill(Qt::transparent); + } +} + void TilePainter::mousePressEvent(QMouseEvent* event) { int x = event->x() / 8; int y = event->y() / 8; - emit indexPressed(y * 32 + x); + emit indexPressed(y * (width() / 8) + x); } void TilePainter::setTile(int index, const uint16_t* data) { QPainter painter(&m_backing); - int x = index & 31; - int y = index / 32; + int w = width() / 8; + int x = index % w; + int y = index / w; QRect r(x * 8, y * 8, 8, 8); QImage tile(reinterpret_cast(data), 8, 8, QImage::Format_RGB555); painter.fillRect(r, tile.rgbSwapped()); update(r); } + +void TilePainter::setTileCount(int tiles) { + setMinimumSize(16, (tiles * 8) / (width() / 8)); + resizeEvent(nullptr); +} diff --git a/src/platform/qt/TilePainter.h b/src/platform/qt/TilePainter.h index 412006228..8786aa206 100644 --- a/src/platform/qt/TilePainter.h +++ b/src/platform/qt/TilePainter.h @@ -20,6 +20,7 @@ public: public slots: void setTile(int index, const uint16_t*); + void setTileCount(int tiles); signals: void indexPressed(int index); @@ -27,6 +28,7 @@ signals: protected: void paintEvent(QPaintEvent*) override; void mousePressEvent(QMouseEvent*) override; + void resizeEvent(QResizeEvent*) override; private: QPixmap m_backing; diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 5d6c6b880..86087c4bf 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -8,6 +8,7 @@ #include "GBAApp.h" #include +#include extern "C" { #include "gba/gba.h" @@ -66,7 +67,7 @@ void TileView::selectIndex(int index) { m_ui.preview->update(); } -void TileView::updateTiles() { +void TileView::updateTiles(bool force) { if (!m_controller->thread() || !m_controller->thread()->core) { return; } @@ -75,31 +76,39 @@ void TileView::updateTiles() { GBAVideoTileCacheAssociate(&m_tileCache, &gba->video); if (m_ui.palette256->isChecked()) { - m_ui.tiles->setMinimumSize(256, 384); + m_ui.tiles->setTileCount(1536); for (int i = 0; i < 1024; ++i) { const uint16_t* data = GBAVideoTileCacheGetTile256IfDirty(&m_tileCache, i, 0); if (data) { m_ui.tiles->setTile(i, data); + } else if (force) { + m_ui.tiles->setTile(i, GBAVideoTileCacheGetTile256(&m_tileCache, i, 0)); } } for (int i = 1024; i < 1536; ++i) { const uint16_t* data = GBAVideoTileCacheGetTile256IfDirty(&m_tileCache, i, 1); if (data) { m_ui.tiles->setTile(i, data); + } else if (force) { + m_ui.tiles->setTile(i, GBAVideoTileCacheGetTile256(&m_tileCache, i, 1)); } } } else { - m_ui.tiles->setMinimumSize(256, 768); + m_ui.tiles->setTileCount(3072); for (int i = 0; i < 2048; ++i) { const uint16_t* data = GBAVideoTileCacheGetTile16IfDirty(&m_tileCache, i, m_paletteId); if (data) { m_ui.tiles->setTile(i, data); + } else if (force) { + m_ui.tiles->setTile(i, GBAVideoTileCacheGetTile16(&m_tileCache, i, m_paletteId)); } } for (int i = 2048; i < 3072; ++i) { const uint16_t* data = GBAVideoTileCacheGetTile16IfDirty(&m_tileCache, i, m_paletteId + 16); if (data) { m_ui.tiles->setTile(i, data); + } else if (force) { + m_ui.tiles->setTile(i, GBAVideoTileCacheGetTile16(&m_tileCache, i, m_paletteId + 16)); } } } @@ -109,3 +118,14 @@ void TileView::updatePalette(int palette) { m_paletteId = palette; updateTiles(); } + +void TileView::resizeEvent(QResizeEvent*) { + updateTiles(true); +} + +void TileView::showEvent(QShowEvent*) { + // XXX: Figure out how to prevent the first resizeEvent + QTimer::singleShot(10, [this]() { + updateTiles(true); + }); +} diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h index 5ef4ad9c2..fec37afd2 100644 --- a/src/platform/qt/TileView.h +++ b/src/platform/qt/TileView.h @@ -26,12 +26,16 @@ public: virtual ~TileView(); public slots: - void updateTiles(); + void updateTiles(bool force = false); void updatePalette(int); private slots: void selectIndex(int); +protected: + void resizeEvent(QResizeEvent*) override; + void showEvent(QShowEvent*) override; + private: Ui::TileView m_ui; diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui index cd662adb0..d3adae10d 100644 --- a/src/platform/qt/TileView.ui +++ b/src/platform/qt/TileView.ui @@ -6,8 +6,8 @@ 0 0 - 288 - 269 + 509 + 265 @@ -23,43 +23,31 @@ + + + 0 + 0 + + + + + 170 + 192 + + - Preview + - - - - - Qt::Horizontal - - - - - - - - 87 - 87 - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - + + + + 87 + 87 + + + @@ -108,6 +96,12 @@ + + + 170 + 16777215 + + 15 @@ -138,16 +132,10 @@ 0 0 - 16 - 16 + 282 + 768 - - - 0 - 0 - - 0 @@ -167,11 +155,17 @@ - + 0 0 + + + 256 + 768 + + @@ -195,5 +189,22 @@ - + + + palette256 + toggled(bool) + paletteId + setDisabled(bool) + + + 100 + 54 + + + 96 + 22 + + + + From 701869a6ef12c696fd9bbbd836a8c9f8ad97b297 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 22 Jul 2016 23:43:00 -0700 Subject: [PATCH 71/90] Qt: Improve tile view --- src/gba/renderers/video-software.c | 8 ++++++-- src/gba/video.c | 17 ++++++++++------- src/gba/video.h | 1 + src/platform/qt/TileView.cpp | 16 ++++++++++------ src/platform/qt/TileView.h | 1 + src/platform/qt/TileView.ui | 21 +++++++++++++++++---- 6 files changed, 45 insertions(+), 19 deletions(-) diff --git a/src/gba/renderers/video-software.c b/src/gba/renderers/video-software.c index e2f444403..71c8ffa93 100644 --- a/src/gba/renderers/video-software.c +++ b/src/gba/renderers/video-software.c @@ -337,8 +337,9 @@ static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRender } static void GBAVideoSoftwareRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { - UNUSED(renderer); - UNUSED(address); + if (renderer->cache) { + GBAVideoTileCacheWriteVRAM(renderer->cache, address); + } } static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { @@ -370,6 +371,9 @@ static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* render } else if (softwareRenderer->blendEffect == BLEND_DARKEN) { softwareRenderer->variantPalette[address >> 1] = _darken(color, softwareRenderer->bldy); } + if (renderer->cache) { + GBAVideoTileCacheWritePalette(renderer->cache, address); + } } static void _breakWindow(struct GBAVideoSoftwareRenderer* softwareRenderer, struct WindowN* win, int y) { diff --git a/src/gba/video.c b/src/gba/video.c index d4f9d256e..9ec7aa6fe 100644 --- a/src/gba/video.c +++ b/src/gba/video.c @@ -8,6 +8,7 @@ #include "core/sync.h" #include "gba/gba.h" #include "gba/io.h" +#include "gba/renderers/tile-cache.h" #include "gba/rr/rr.h" #include "gba/serialize.h" @@ -55,7 +56,8 @@ static struct GBAVideoRenderer dummyRenderer = { .writeOAM = GBAVideoDummyRendererWriteOAM, .drawScanline = GBAVideoDummyRendererDrawScanline, .finishFrame = GBAVideoDummyRendererFinishFrame, - .getPixels = GBAVideoDummyRendererGetPixels + .getPixels = GBAVideoDummyRendererGetPixels, + .cache = NULL }; void GBAVideoInit(struct GBAVideo* video) { @@ -104,6 +106,7 @@ void GBAVideoDeinit(struct GBAVideo* video) { void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer* renderer) { video->renderer->deinit(video->renderer); + renderer->cache = video->renderer->cache; video->renderer = renderer; renderer->palette = video->palette; renderer->vram = video->vram; @@ -262,16 +265,16 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* } static void GBAVideoDummyRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) { - UNUSED(renderer); - UNUSED(address); - // Nothing to do + if (renderer->cache) { + GBAVideoTileCacheWriteVRAM(renderer->cache, address); + } } static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) { - UNUSED(renderer); - UNUSED(address); UNUSED(value); - // Nothing to do + if (renderer->cache) { + GBAVideoTileCacheWritePalette(renderer->cache, address); + } } static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) { diff --git a/src/gba/video.h b/src/gba/video.h index 04c96ee75..4dac3d55b 100644 --- a/src/gba/video.h +++ b/src/gba/video.h @@ -170,6 +170,7 @@ struct GBAVideoRenderer { uint16_t* palette; uint16_t* vram; union GBAOAM* oam; + struct GBAVideoTileCache* cache; bool disableBG[4]; bool disableOBJ; diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index 86087c4bf..bad8129d9 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -25,14 +25,16 @@ TileView::TileView(GameController* controller, QWidget* parent) GBAVideoTileCacheInit(&m_tileCache); m_ui.preview->setDimensions(QSize(8, 8)); - updateTiles(); + m_updateTimer.setSingleShot(true); + m_updateTimer.setInterval(10); + connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles())); const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_ui.tileId->setFont(font); m_ui.address->setFont(font); - connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(updateTiles())); + connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), &m_updateTimer, SLOT(start())); connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close())); connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(selectIndex(int))); connect(m_ui.paletteId, SIGNAL(valueChanged(int)), this, SLOT(updatePalette(int))); @@ -40,6 +42,11 @@ TileView::TileView(GameController* controller, QWidget* parent) } TileView::~TileView() { + if (m_controller->isLoaded() && m_controller->thread() && m_controller->thread()->core) { + GBA* gba = static_cast(m_controller->thread()->core->board); + gba->video.renderer->cache = nullptr; + } + GBAVideoTileCacheDeinit(&m_tileCache); } @@ -124,8 +131,5 @@ void TileView::resizeEvent(QResizeEvent*) { } void TileView::showEvent(QShowEvent*) { - // XXX: Figure out how to prevent the first resizeEvent - QTimer::singleShot(10, [this]() { - updateTiles(true); - }); + m_updateTimer.start(); } diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h index fec37afd2..28fc4b8a8 100644 --- a/src/platform/qt/TileView.h +++ b/src/platform/qt/TileView.h @@ -42,6 +42,7 @@ private: GameController* m_controller; GBAVideoTileCache m_tileCache; int m_paletteId; + QTimer m_updateTimer; }; } diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui index d3adae10d..87aa22833 100644 --- a/src/platform/qt/TileView.ui +++ b/src/platform/qt/TileView.ui @@ -6,8 +6,8 @@ 0 0 - 509 - 265 + 498 + 335 @@ -116,7 +116,20 @@ - + + + + Qt::Vertical + + + + 0 + 0 + + + + + @@ -132,7 +145,7 @@ 0 0 - 282 + 271 768 From c7f6c499c3edd7f2da5c29455b595db8dd750659 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 22 Jul 2016 23:49:08 -0700 Subject: [PATCH 72/90] Update CHANGES --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 8bf1f3b6b..5bc3f6755 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,7 @@ Features: - Game Boy support - Support for encrypted CodeBreaker GBA cheats - Emulation of Vast Fame protected GBA carts (taizou) + - Tile viewer Bugfixes: - SDL: Fix axes being mapped wrong - GBA Memory: Fix mirror on non-overdumped Classic NES games From f388d65f5fcac60a3b34253d22538b09d62fc9ad Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 22 Jul 2016 23:50:22 -0700 Subject: [PATCH 73/90] GBA Video: Add missing files --- src/gba/renderers/tile-cache.c | 149 +++++++++++++++++++++++++++++++++ src/gba/renderers/tile-cache.h | 37 ++++++++ 2 files changed, 186 insertions(+) create mode 100644 src/gba/renderers/tile-cache.c create mode 100644 src/gba/renderers/tile-cache.h diff --git a/src/gba/renderers/tile-cache.c b/src/gba/renderers/tile-cache.c new file mode 100644 index 000000000..201797263 --- /dev/null +++ b/src/gba/renderers/tile-cache.c @@ -0,0 +1,149 @@ +/* Copyright (c) 2013-2016 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 "tile-cache.h" + +#include "gba/video.h" +#include "util/memory.h" + +#define CACHE_SIZE (8 * 8 * 2 * 1024 * 3 * 16) + +void GBAVideoTileCacheInit(struct GBAVideoTileCache* cache) { + // TODO: Reconfigurable cache for space savings + cache->cache = anonymousMemoryMap(CACHE_SIZE); + memset(cache->status, 0, sizeof(cache->status)); + memset(cache->globalPaletteVersion, 0, sizeof(cache->globalPaletteVersion)); + memset(cache->globalPalette256Version, 0, sizeof(cache->globalPalette256Version)); +} + +void GBAVideoTileCacheDeinit(struct GBAVideoTileCache* cache) { + mappedMemoryFree(cache->cache, CACHE_SIZE); + cache->cache = NULL; +} + +void GBAVideoTileCacheAssociate(struct GBAVideoTileCache* cache, struct GBAVideo* video) { + cache->vram = video->vram; + cache->palette = video->palette; + video->renderer->cache = cache; +} + +void GBAVideoTileCacheWriteVRAM(struct GBAVideoTileCache* cache, uint32_t address) { + cache->status[address >> 5].vramClean = 0; +} + +void GBAVideoTileCacheWritePalette(struct GBAVideoTileCache* cache, uint32_t address) { + ++cache->globalPaletteVersion[address >> 5]; + ++cache->globalPalette256Version[address >> 9]; +} + +static void _regenerateTile16(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + uint16_t* tile = &cache->cache[tileId << 6]; + uint32_t* start = (uint32_t*) &cache->vram[tileId << 4]; + paletteId <<= 4; + uint16_t* palette = &cache->palette[paletteId]; + int i; + for (i = 0; i < 8; ++i) { + uint32_t line = *start; + ++start; + int pixel; + pixel = line & 0xF; + tile[0] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 4) & 0xF; + tile[1] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 8) & 0xF; + tile[2] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 12) & 0xF; + tile[3] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 16) & 0xF; + tile[4] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 20) & 0xF; + tile[5] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 24) & 0xF; + tile[6] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 28) & 0xF; + tile[7] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + tile += 8; + } +} + +static void _regenerateTile256(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + uint16_t* tile = &cache->cache[tileId << 6]; + uint32_t* start = (uint32_t*) &cache->vram[tileId << 5]; + paletteId <<= 8; + uint16_t* palette = &cache->palette[paletteId]; + int i; + for (i = 0; i < 8; ++i) { + uint32_t line = *start; + ++start; + int pixel; + pixel = line & 0xFF; + tile[0] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 8) & 0xFF; + tile[1] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 16) & 0xFF; + tile[2] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 24) & 0xFF; + tile[3] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + + line = *start; + ++start; + pixel = line & 0xFF; + tile[4] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 8) & 0xFF; + tile[5] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 16) & 0xFF; + tile[6] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + pixel = (line >> 24) & 0xFF; + tile[7] = pixel ? palette[pixel] | 0x8000 : palette[pixel] & 0x7FFF; + tile += 8; + } +} + +const uint16_t* GBAVideoTileCacheGetTile16(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; + if (!status->vramClean || status->palette256 || status->paletteVersion[paletteId & 0xF] != cache->globalPaletteVersion[paletteId]) { + _regenerateTile16(cache, tileId, paletteId); + status->paletteVersion[paletteId & 0xF] = cache->globalPaletteVersion[paletteId]; + status->palette256 = 0; + status->vramClean = 1; + } + return &cache->cache[tileId << 6]; +} + +const uint16_t* GBAVideoTileCacheGetTile16IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; + if (!status->vramClean || status->palette256 || status->paletteVersion[paletteId & 0xF] != cache->globalPaletteVersion[paletteId]) { + _regenerateTile16(cache, tileId, paletteId); + status->paletteVersion[paletteId & 0xF] = cache->globalPaletteVersion[paletteId]; + status->palette256 = 0; + status->vramClean = 1; + return &cache->cache[tileId << 6]; + } + return NULL; +} + + +const uint16_t* GBAVideoTileCacheGetTile256(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; + if (!status->vramClean || !status->palette256 || status->paletteVersion[0] != cache->globalPalette256Version[paletteId]) { + _regenerateTile256(cache, tileId, paletteId); + status->paletteVersion[0] = cache->globalPalette256Version[paletteId]; + status->palette256 = 1; + status->vramClean = 1; + } + return &cache->cache[tileId << 6]; +} + +const uint16_t* GBAVideoTileCacheGetTile256IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; + if (!status->vramClean || !status->palette256 || status->paletteVersion[0] != cache->globalPalette256Version[paletteId]) { + _regenerateTile256(cache, tileId, paletteId); + status->paletteVersion[0] = cache->globalPalette256Version[paletteId]; + status->palette256 = 1; + status->vramClean = 1; + return &cache->cache[tileId << 6]; + } + return NULL; +} diff --git a/src/gba/renderers/tile-cache.h b/src/gba/renderers/tile-cache.h new file mode 100644 index 000000000..ef4c06e58 --- /dev/null +++ b/src/gba/renderers/tile-cache.h @@ -0,0 +1,37 @@ +/* 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_TILE_CACHE_H +#define GBA_TILE_CACHE_H + +#include "util/common.h" + +struct GBAVideo; + +struct GBAVideoTileCache { + uint16_t* cache; + struct GBAVideoTileCacheEntry { + uint32_t paletteVersion[16]; + uint8_t vramClean; + uint8_t palette256; + } status[1024 * 3]; + uint32_t globalPaletteVersion[32]; + uint32_t globalPalette256Version[2]; + + uint16_t* vram; + uint16_t* palette; +}; + +void GBAVideoTileCacheInit(struct GBAVideoTileCache* cache); +void GBAVideoTileCacheDeinit(struct GBAVideoTileCache* cache); +void GBAVideoTileCacheAssociate(struct GBAVideoTileCache* cache, struct GBAVideo* video); +void GBAVideoTileCacheWriteVRAM(struct GBAVideoTileCache* cache, uint32_t address); +void GBAVideoTileCacheWritePalette(struct GBAVideoTileCache* cache, uint32_t address); +const uint16_t* GBAVideoTileCacheGetTile16(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId); +const uint16_t* GBAVideoTileCacheGetTile16IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId); +const uint16_t* GBAVideoTileCacheGetTile256(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId); +const uint16_t* GBAVideoTileCacheGetTile256IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId); + +#endif From d175a0ac8536a18175a1bc53bd1d9ebe80da2095 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 23 Jul 2016 00:28:53 -0700 Subject: [PATCH 74/90] GBA Audio: Force audio DMAs to not increment destination --- CHANGES | 1 + src/gba/audio.c | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 5bc3f6755..adaa582a3 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,7 @@ Misc: - Qt: Make -g flag work in Qt build - Qt: Simplify OpenGL context creation - Debugger: Support register and memory writes via GDB stub + - GBA Audio: Force audio DMAs to not increment destination 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/gba/audio.c b/src/gba/audio.c index 5b2900654..8fb6d9942 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -126,12 +126,6 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA* audio->chB.dmaSource = number; break; default: - if (audio->chA.dmaSource == number) { - audio->chA.dmaSource = -1; - } - if (audio->chB.dmaSource == number) { - audio->chB.dmaSource = -1; - } mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest); return; } @@ -262,6 +256,7 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) { dma->nextCount = 4; dma->nextEvent = 0; dma->reg = GBADMARegisterSetWidth(dma->reg, 1); + dma->reg = GBADMARegisterSetDestControl(dma->reg, 2); GBAMemoryUpdateDMAs(audio->p, -cycles); } else { channel->dmaSource = 0; From 4a7dc8bff6fcbf2225634d05a41a18b68bd6b051 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 23 Jul 2016 19:02:28 -0700 Subject: [PATCH 75/90] Qt: Thread startup improvements --- CHANGES | 1 + src/core/thread.c | 21 ++++++++++++++++++--- src/core/thread.h | 1 + src/feature/commandline.c | 9 ++++++--- src/platform/example/client-server/server.c | 4 ++++ src/platform/qt/GDBController.cpp | 9 +++------ src/platform/qt/GameController.cpp | 5 ++++- src/platform/sdl/main.c | 3 +++ src/platform/test/fuzz-main.c | 3 +++ src/platform/test/perf-main.c | 3 +++ 10 files changed, 46 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index adaa582a3..728b6e947 100644 --- a/CHANGES +++ b/CHANGES @@ -22,6 +22,7 @@ Misc: - Qt: Simplify OpenGL context creation - Debugger: Support register and memory writes via GDB stub - GBA Audio: Force audio DMAs to not increment destination + - Qt: Thread startup improvements 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/core/thread.c b/src/core/thread.c index 6c2cf0627..e3018495f 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -109,12 +109,12 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) { core->setSync(core, &threadContext->sync); core->reset(core); + _changeState(threadContext, THREAD_RUNNING, true); + if (threadContext->startCallback) { threadContext->startCallback(threadContext); } - _changeState(threadContext, THREAD_RUNNING, true); - while (threadContext->state < THREAD_EXITING) { struct mDebugger* debugger = core->debugger; if (debugger) { @@ -303,6 +303,22 @@ void mCoreThreadInterrupt(struct mCoreThread* threadContext) { MutexUnlock(&threadContext->stateMutex); } +void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext) { + if (!threadContext) { + return; + } + MutexLock(&threadContext->stateMutex); + ++threadContext->interruptDepth; + if (threadContext->interruptDepth > 1 || !mCoreThreadIsActive(threadContext)) { + MutexUnlock(&threadContext->stateMutex); + return; + } + threadContext->savedState = threadContext->state; + threadContext->state = THREAD_INTERRUPTING; + ConditionWake(&threadContext->stateCond); + MutexUnlock(&threadContext->stateMutex); +} + void mCoreThreadContinue(struct mCoreThread* threadContext) { if (!threadContext) { return; @@ -385,7 +401,6 @@ void mCoreThreadTogglePause(struct mCoreThread* threadContext) { void mCoreThreadPauseFromThread(struct mCoreThread* threadContext) { bool frameOn = true; MutexLock(&threadContext->stateMutex); - _waitOnInterrupt(threadContext); if (threadContext->state == THREAD_RUNNING) { _pauseThread(threadContext, true); frameOn = false; diff --git a/src/core/thread.h b/src/core/thread.h index ec0ed7c6d..870afc606 100644 --- a/src/core/thread.h +++ b/src/core/thread.h @@ -72,6 +72,7 @@ void mCoreThreadJoin(struct mCoreThread* threadContext); bool mCoreThreadIsActive(struct mCoreThread* threadContext); void mCoreThreadInterrupt(struct mCoreThread* threadContext); +void mCoreThreadInterruptFromThread(struct mCoreThread* threadContext); void mCoreThreadContinue(struct mCoreThread* threadContext); void mCoreThreadRunFunction(struct mCoreThread* threadContext, void (*run)(struct mCoreThread*)); diff --git a/src/feature/commandline.c b/src/feature/commandline.c index 9c66d16da..c0d5032d6 100644 --- a/src/feature/commandline.c +++ b/src/feature/commandline.c @@ -124,10 +124,13 @@ bool parseArguments(struct mArguments* args, int argc, char* const* argv, struct } argc -= optind; argv += optind; - if (argc != 1) { - return args->showHelp || args->showVersion; + if (argc > 1) { + return false; + } else if (argc == 1) { + args->fname = strdup(argv[0]); + } else { + args->fname = NULL; } - args->fname = strdup(argv[0]); return true; } diff --git a/src/platform/example/client-server/server.c b/src/platform/example/client-server/server.c index 8267ff1b7..b1e05a81d 100644 --- a/src/platform/example/client-server/server.c +++ b/src/platform/example/client-server/server.c @@ -17,6 +17,10 @@ int main(int argc, char** argv) { // The NULL here shows that we don't give it any arguments beyond the default ones. struct mArguments args = {}; bool parsed = parseArguments(&args, argc, argv, NULL); + // Parsing can succeed without finding a filename, but we need one. + if (!args.fname) { + parsed = false; + } if (!parsed || args.showHelp) { // If parsing failed, or the user passed --help, show usage. usage(argv[0], NULL); diff --git a/src/platform/qt/GDBController.cpp b/src/platform/qt/GDBController.cpp index b47e8269b..2ec02c73a 100644 --- a/src/platform/qt/GDBController.cpp +++ b/src/platform/qt/GDBController.cpp @@ -36,18 +36,15 @@ void GDBController::setBindAddress(uint32_t bindAddress) { } void GDBController::attach() { - if (isAttached() || m_gameController->platform() != PLATFORM_GBA) { + if (isAttached() || (m_gameController->platform() != PLATFORM_GBA && m_gameController->platform() != PLATFORM_NONE)) { return; } - m_gameController->setDebugger(&m_gdbStub.d); if (m_gameController->isLoaded()) { + m_gameController->setDebugger(&m_gdbStub.d); mDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0); } else { QObject::disconnect(m_autoattach); - m_autoattach = connect(m_gameController, &GameController::gameStarted, [this]() { - QObject::disconnect(m_autoattach); - mDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0); - }); + m_autoattach = connect(m_gameController, SIGNAL(gameStarted(mCoreThread*, const QString&)), this, SLOT(attach())); } } diff --git a/src/platform/qt/GameController.cpp b/src/platform/qt/GameController.cpp index c2ad434d0..8bda543de 100644 --- a/src/platform/qt/GameController.cpp +++ b/src/platform/qt/GameController.cpp @@ -130,7 +130,10 @@ GameController::GameController(QObject* parent) if (mCoreLoadState(context->core, 0, controller->m_loadStateFlags)) { mCoreDeleteState(context->core, 0); } - QMetaObject::invokeMethod(controller, "gameStarted", Q_ARG(mCoreThread*, context), Q_ARG(const QString&, controller->m_fname)); + + mCoreThreadInterruptFromThread(context); + QMetaObject::invokeMethod(controller, "gameStarted", Qt::BlockingQueuedConnection, Q_ARG(mCoreThread*, context), Q_ARG(const QString&, controller->m_fname)); + mCoreThreadContinue(context); }; m_threadContext.cleanCallback = [](mCoreThread* context) { diff --git a/src/platform/sdl/main.c b/src/platform/sdl/main.c index dab32bc10..83a4c51a2 100644 --- a/src/platform/sdl/main.c +++ b/src/platform/sdl/main.c @@ -64,6 +64,9 @@ int main(int argc, char** argv) { initParserForGraphics(&subparser, &graphicsOpts); bool parsed = parseArguments(&args, argc, argv, &subparser); + if (!args.fname) { + parsed = false; + } if (!parsed || args.showHelp) { usage(argv[0], subparser.usage); freeArguments(&args); diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index 8d78d954c..201addb59 100644 --- a/src/platform/test/fuzz-main.c +++ b/src/platform/test/fuzz-main.c @@ -58,6 +58,9 @@ int main(int argc, char** argv) { struct mArguments args; bool parsed = parseArguments(&args, argc, argv, &subparser); + if (!args.fname) { + parsed = false; + } if (!parsed || args.showHelp) { usage(argv[0], FUZZ_USAGE); core->deinit(core); diff --git a/src/platform/test/perf-main.c b/src/platform/test/perf-main.c index 06579c313..ea3d3a861 100644 --- a/src/platform/test/perf-main.c +++ b/src/platform/test/perf-main.c @@ -93,6 +93,9 @@ int main(int argc, char** argv) { struct mArguments args = {}; bool parsed = parseArguments(&args, argc, argv, &subparser); + if (!args.fname) { + parsed = false; + } if (!parsed || args.showHelp) { usage(argv[0], PERF_USAGE); didFail = !parsed; From 2f3c4982bd774a944817e7c3c12ef92e55fb454f Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 23 Jul 2016 19:34:43 -0700 Subject: [PATCH 76/90] GBA: Tile cache improvements --- src/gba/renderers/tile-cache.c | 83 ++++++++++++++++++++++------------ src/gba/renderers/tile-cache.h | 13 ++++-- src/platform/qt/TileView.cpp | 2 +- 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/src/gba/renderers/tile-cache.c b/src/gba/renderers/tile-cache.c index 201797263..fd3a1ef0e 100644 --- a/src/gba/renderers/tile-cache.c +++ b/src/gba/renderers/tile-cache.c @@ -13,14 +13,28 @@ void GBAVideoTileCacheInit(struct GBAVideoTileCache* cache) { // TODO: Reconfigurable cache for space savings cache->cache = anonymousMemoryMap(CACHE_SIZE); + cache->config = GBAVideoTileCacheConfigurationFillShouldStore(0); memset(cache->status, 0, sizeof(cache->status)); memset(cache->globalPaletteVersion, 0, sizeof(cache->globalPaletteVersion)); memset(cache->globalPalette256Version, 0, sizeof(cache->globalPalette256Version)); } +void GBAVideoTileCacheConfigure(struct GBAVideoTileCache* cache, GBAVideoTileCacheConfiguration config) { + if (GBAVideoTileCacheConfigurationIsShouldStore(cache->config) || !GBAVideoTileCacheConfigurationIsShouldStore(config)) { + mappedMemoryFree(cache->cache, CACHE_SIZE); + cache->cache = NULL; + } else if (!GBAVideoTileCacheConfigurationIsShouldStore(cache->config) || GBAVideoTileCacheConfigurationIsShouldStore(config)) { + cache->cache = anonymousMemoryMap(CACHE_SIZE); + } + cache->config = config; +} + + void GBAVideoTileCacheDeinit(struct GBAVideoTileCache* cache) { - mappedMemoryFree(cache->cache, CACHE_SIZE); - cache->cache = NULL; + if (GBAVideoTileCacheConfigurationIsShouldStore(cache->config)) { + mappedMemoryFree(cache->cache, CACHE_SIZE); + cache->cache = NULL; + } } void GBAVideoTileCacheAssociate(struct GBAVideoTileCache* cache, struct GBAVideo* video) { @@ -30,7 +44,10 @@ void GBAVideoTileCacheAssociate(struct GBAVideoTileCache* cache, struct GBAVideo } void GBAVideoTileCacheWriteVRAM(struct GBAVideoTileCache* cache, uint32_t address) { - cache->status[address >> 5].vramClean = 0; + size_t i; + for (i = 0; i > 16; ++i) { + cache->status[address >> 5][i].vramClean = 0; + } } void GBAVideoTileCacheWritePalette(struct GBAVideoTileCache* cache, uint32_t address) { @@ -38,8 +55,7 @@ void GBAVideoTileCacheWritePalette(struct GBAVideoTileCache* cache, uint32_t add ++cache->globalPalette256Version[address >> 9]; } -static void _regenerateTile16(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { - uint16_t* tile = &cache->cache[tileId << 6]; +static void _regenerateTile16(struct GBAVideoTileCache* cache, uint16_t* tile, unsigned tileId, unsigned paletteId) { uint32_t* start = (uint32_t*) &cache->vram[tileId << 4]; paletteId <<= 4; uint16_t* palette = &cache->palette[paletteId]; @@ -68,11 +84,10 @@ static void _regenerateTile16(struct GBAVideoTileCache* cache, unsigned tileId, } } -static void _regenerateTile256(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { - uint16_t* tile = &cache->cache[tileId << 6]; +static void _regenerateTile256(struct GBAVideoTileCache* cache, uint16_t* tile, unsigned tileId, unsigned paletteId) { uint32_t* start = (uint32_t*) &cache->vram[tileId << 5]; paletteId <<= 8; - uint16_t* palette = &cache->palette[paletteId]; + uint16_t* palette = &cache->palette[paletteId * 16]; int i; for (i = 0; i < 8; ++i) { uint32_t line = *start; @@ -101,49 +116,61 @@ static void _regenerateTile256(struct GBAVideoTileCache* cache, unsigned tileId, } } +static inline uint16_t* _tileLookup(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { + if (GBAVideoTileCacheConfigurationIsShouldStore(cache->config)) { + return &cache->cache[((tileId << 4) + (paletteId & 0xF)) << 6]; + } else { + return cache->temporaryTile; + } +} + const uint16_t* GBAVideoTileCacheGetTile16(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { - struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; - if (!status->vramClean || status->palette256 || status->paletteVersion[paletteId & 0xF] != cache->globalPaletteVersion[paletteId]) { - _regenerateTile16(cache, tileId, paletteId); - status->paletteVersion[paletteId & 0xF] = cache->globalPaletteVersion[paletteId]; + struct GBAVideoTileCacheEntry* status = &cache->status[tileId][paletteId & 0xF]; + uint16_t* tile = _tileLookup(cache, tileId, paletteId); + if (!GBAVideoTileCacheConfigurationIsShouldStore(cache->config) || !status->vramClean || status->palette256 || status->paletteVersion != cache->globalPaletteVersion[paletteId]) { + _regenerateTile16(cache, tile, tileId, paletteId); + status->paletteVersion = cache->globalPaletteVersion[paletteId]; status->palette256 = 0; status->vramClean = 1; } - return &cache->cache[tileId << 6]; + return tile; } const uint16_t* GBAVideoTileCacheGetTile16IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { - struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; - if (!status->vramClean || status->palette256 || status->paletteVersion[paletteId & 0xF] != cache->globalPaletteVersion[paletteId]) { - _regenerateTile16(cache, tileId, paletteId); - status->paletteVersion[paletteId & 0xF] = cache->globalPaletteVersion[paletteId]; + struct GBAVideoTileCacheEntry* status = &cache->status[tileId][paletteId & 0xF]; + if (!status->vramClean || status->palette256 || status->paletteVersion != cache->globalPaletteVersion[paletteId]) { + uint16_t* tile = _tileLookup(cache, tileId, paletteId); + _regenerateTile16(cache, tile, tileId, paletteId); + status->paletteVersion = cache->globalPaletteVersion[paletteId]; status->palette256 = 0; status->vramClean = 1; - return &cache->cache[tileId << 6]; + return tile; } return NULL; } const uint16_t* GBAVideoTileCacheGetTile256(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { - struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; - if (!status->vramClean || !status->palette256 || status->paletteVersion[0] != cache->globalPalette256Version[paletteId]) { - _regenerateTile256(cache, tileId, paletteId); - status->paletteVersion[0] = cache->globalPalette256Version[paletteId]; + struct GBAVideoTileCacheEntry* status = &cache->status[tileId][paletteId]; + uint16_t* tile = _tileLookup(cache, tileId, paletteId); + if (!GBAVideoTileCacheConfigurationIsShouldStore(cache->config) || !status->vramClean || !status->palette256 || status->paletteVersion != cache->globalPalette256Version[paletteId]) { + _regenerateTile256(cache, tile, tileId, paletteId); + status->paletteVersion = cache->globalPalette256Version[paletteId]; status->palette256 = 1; status->vramClean = 1; } - return &cache->cache[tileId << 6]; + return tile; } const uint16_t* GBAVideoTileCacheGetTile256IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned paletteId) { - struct GBAVideoTileCacheEntry* status = &cache->status[tileId]; - if (!status->vramClean || !status->palette256 || status->paletteVersion[0] != cache->globalPalette256Version[paletteId]) { - _regenerateTile256(cache, tileId, paletteId); - status->paletteVersion[0] = cache->globalPalette256Version[paletteId]; + struct GBAVideoTileCacheEntry* status = &cache->status[tileId][paletteId]; + if (!status->vramClean || !status->palette256 || status->paletteVersion != cache->globalPalette256Version[paletteId]) { + uint16_t* tile = _tileLookup(cache, tileId, paletteId); + _regenerateTile256(cache, tile, tileId, paletteId); + status->paletteVersion = cache->globalPalette256Version[paletteId]; status->palette256 = 1; status->vramClean = 1; - return &cache->cache[tileId << 6]; + return tile; } return NULL; } diff --git a/src/gba/renderers/tile-cache.h b/src/gba/renderers/tile-cache.h index ef4c06e58..2a0d30aad 100644 --- a/src/gba/renderers/tile-cache.h +++ b/src/gba/renderers/tile-cache.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2015 Jeffrey Pfau +/* Copyright (c) 2013-2016 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 @@ -10,22 +10,29 @@ struct GBAVideo; +DECL_BITFIELD(GBAVideoTileCacheConfiguration, uint32_t); +DECL_BIT(GBAVideoTileCacheConfiguration, ShouldStore, 0); + struct GBAVideoTileCache { uint16_t* cache; struct GBAVideoTileCacheEntry { - uint32_t paletteVersion[16]; + uint32_t paletteVersion; uint8_t vramClean; uint8_t palette256; - } status[1024 * 3]; + } status[1024 * 3][16]; uint32_t globalPaletteVersion[32]; uint32_t globalPalette256Version[2]; uint16_t* vram; uint16_t* palette; + uint16_t temporaryTile[64]; + + GBAVideoTileCacheConfiguration config; }; void GBAVideoTileCacheInit(struct GBAVideoTileCache* cache); void GBAVideoTileCacheDeinit(struct GBAVideoTileCache* cache); +void GBAVideoTileCacheConfigure(struct GBAVideoTileCache* cache, GBAVideoTileCacheConfiguration config); void GBAVideoTileCacheAssociate(struct GBAVideoTileCache* cache, struct GBAVideo* video); void GBAVideoTileCacheWriteVRAM(struct GBAVideoTileCache* cache, uint32_t address); void GBAVideoTileCacheWritePalette(struct GBAVideoTileCache* cache, uint32_t address); diff --git a/src/platform/qt/TileView.cpp b/src/platform/qt/TileView.cpp index bad8129d9..8d74b8de0 100644 --- a/src/platform/qt/TileView.cpp +++ b/src/platform/qt/TileView.cpp @@ -123,7 +123,7 @@ void TileView::updateTiles(bool force) { void TileView::updatePalette(int palette) { m_paletteId = palette; - updateTiles(); + updateTiles(true); } void TileView::resizeEvent(QResizeEvent*) { From df695802dd6c105936d32c0e7ae5951d19a4d198 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 24 Jul 2016 13:40:45 -0700 Subject: [PATCH 77/90] PSP2: Initial changes for VPK packaging --- src/platform/psp2/CMakeLists.txt | 23 +++++++++++++++++++---- src/platform/psp2/main.c | 2 +- src/platform/psp2/threading.h | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index b374b61a3..23f24fbf0 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,20 +1,21 @@ find_program(FIXUP vita-elf-create) +find_program(MAKE_FSELF vita-make-fself) +find_program(MAKE_SFO vita-mksfoex) find_program(OBJCOPY ${cross_prefix}objcopy) find_file(NIDDB db.json PATHS ${VITASDK} ${VITASDK}/bin ${VITASDK}/share) -find_file(EXTRADB extra.json PATHS ${VITASDK}${VITASDK}/bin ${VITASDK}/share) find_program(STRIP ${cross_prefix}strip) set(OS_DEFINES IOAPI_NO_64) set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) -file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c) +file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/psp2-*.c) set(OS_SRC ${OS_SRC} PARENT_SCOPE) source_group("PS Vita-specific code" FILES ${OS_SRC}) list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -l${M_LIBRARY}) +set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceCommonDialog_stub -lSceMotion_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) @@ -44,7 +45,21 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o add_custom_target(${BINARY_NAME}.velf ALL ${STRIP} --strip-unneeded -go ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf - COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} ${EXTRADB} + COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} DEPENDS ${BINARY_NAME}.elf) +add_custom_target(sce_sys ${CMAKE_COMMAND} -E make_directory sce_sys) + +add_custom_target(param.sfo + ${MAKE_SFO} ${PROJECT_NAME} -s TITLE_ID=MGBA4VITA sce_sys/param.sfo + DEPENDS sce_sys) + +add_custom_target(eboot.bin + ${MAKE_FSELF} ${BINARY_NAME}.velf eboot.bin + DEPENDS ${BINARY_NAME}.velf) + +add_custom_target(${BINARY_NAME}.vpk ALL + zip -r ${BINARY_NAME}.vpk sce_sys eboot.bin + DEPENDS eboot.bin head.bin param.sfo) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 5855ce041..cf5096220 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -98,7 +98,7 @@ int main() { struct mGUIRunner runner = { .params = { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, - font, "cache0:", _drawStart, _drawEnd, + font, "ux0:", _drawStart, _drawEnd, _pollInput, _pollCursor, _batteryState, 0, 0, diff --git a/src/platform/psp2/threading.h b/src/platform/psp2/threading.h index 9babe0aae..54fdcb243 100644 --- a/src/platform/psp2/threading.h +++ b/src/platform/psp2/threading.h @@ -120,7 +120,7 @@ static inline int _sceThreadEntry(SceSize args, void* argp) { } static inline int ThreadCreate(Thread* thread, ThreadEntry entry, void* context) { - Thread id = sceKernelCreateThread("SceThread", _sceThreadEntry, 0x40, 0x10000, 0, 0x70000, 0); + Thread id = sceKernelCreateThread("SceThread", _sceThreadEntry, 0x10000100, 0x10000, 0, 0, 0); if (id < 0) { *thread = 0; return id; From 100eab76fe6fb1204f5d65600b05a0977648b6ed Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sun, 24 Jul 2016 14:59:21 -0700 Subject: [PATCH 78/90] PSP2: Add LiveArea branding --- src/platform/psp2/CMakeLists.txt | 10 +++++++++- src/platform/psp2/bg.png | Bin 0 -> 111965 bytes src/platform/psp2/icon0.png | Bin 0 -> 6204 bytes src/platform/psp2/startup.png | Bin 0 -> 11118 bytes src/platform/psp2/template.xml | 11 +++++++++++ 5 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/platform/psp2/bg.png create mode 100644 src/platform/psp2/icon0.png create mode 100644 src/platform/psp2/startup.png create mode 100644 src/platform/psp2/template.xml diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 23f24fbf0..9f3fbf8f0 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -58,8 +58,16 @@ add_custom_target(eboot.bin ${MAKE_FSELF} ${BINARY_NAME}.velf eboot.bin DEPENDS ${BINARY_NAME}.velf) +add_custom_target(livearea + ${CMAKE_COMMAND} -E make_directory sce_sys/livearea/contents + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/icon0.png sce_sys + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/template.xml sce_sys/livearea/contents + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bg.png sce_sys/livearea/contents + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents + DEPENDS sce_sys) + add_custom_target(${BINARY_NAME}.vpk ALL zip -r ${BINARY_NAME}.vpk sce_sys eboot.bin - DEPENDS eboot.bin head.bin param.sfo) + DEPENDS livearea eboot.bin head.bin param.sfo) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/psp2/bg.png b/src/platform/psp2/bg.png new file mode 100644 index 0000000000000000000000000000000000000000..a9e687d9910449d9ca9800a36d2796753550f322 GIT binary patch literal 111965 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

;#aJSYmja^)N|g1oG%js#^` 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 literal 0 HcmV?d00001 diff --git a/src/platform/psp2/icon0.png b/src/platform/psp2/icon0.png new file mode 100644 index 0000000000000000000000000000000000000000..e48a324c91acc9f92eba421444c1c27ebf1be67a GIT binary patch literal 6204 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 A=Kufz literal 0 HcmV?d00001 diff --git a/src/platform/psp2/template.xml b/src/platform/psp2/template.xml new file mode 100644 index 000000000..eac3807f5 --- /dev/null +++ b/src/platform/psp2/template.xml @@ -0,0 +1,11 @@ + + + + + bg.png + + + + startup.png + + From 7930c5a350fc21b233cc9cfdab058de1da79619a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 27 Jul 2016 01:04:52 -0700 Subject: [PATCH 79/90] 3DS: Port to using citro3D --- CHANGES | 1 + src/platform/3ds/CMakeLists.txt | 57 ++- src/platform/3ds/ctr-gpu.c | 439 ++++-------------- src/platform/3ds/ctr-gpu.h | 27 +- src/platform/3ds/gui-font.c | 26 +- src/platform/3ds/main.c | 151 +++--- src/platform/3ds/uishader.g.pica | 96 ++++ .../3ds/{uishader.vsh => uishader.v.pica} | 20 +- 8 files changed, 312 insertions(+), 505 deletions(-) create mode 100644 src/platform/3ds/uishader.g.pica rename src/platform/3ds/{uishader.vsh => uishader.v.pica} (58%) diff --git a/CHANGES b/CHANGES index 728b6e947..73c0e813c 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ Misc: - Debugger: Support register and memory writes via GDB stub - GBA Audio: Force audio DMAs to not increment destination - Qt: Thread startup improvements + - 3DS: Port to using citro3D 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 8af378a25..cc6ebbf37 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format" PARENT_SCOPE) set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5 IOAPI_NO_64) include_directories(${CMAKE_CURRENT_BINARY_DIR}) -list(APPEND OS_LIB ctru) +list(APPEND OS_LIB citro3d ctru) file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/3ds-*.c ${CMAKE_CURRENT_SOURCE_DIR}/ctru-heap.c ${CMAKE_CURRENT_SOURCE_DIR}/socket.c) set(OS_SRC ${OS_SRC} PARENT_SCOPE) set(OS_LIB ${OS_LIB} PARENT_SCOPE) @@ -31,9 +31,12 @@ set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/icons.c ${CMAKE_CURRENT_BINARY_DIR}/font.c - ${CMAKE_CURRENT_BINARY_DIR}/uishader.c - ${CMAKE_CURRENT_BINARY_DIR}/uishader.h - ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin.h ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c ${CMAKE_CURRENT_SOURCE_DIR}/ctr-gpu.c @@ -42,10 +45,14 @@ list(APPEND GUI_SRC set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/icons.c ${CMAKE_CURRENT_BINARY_DIR}/font.c - ${CMAKE_CURRENT_BINARY_DIR}/uishader.c - ${CMAKE_CURRENT_BINARY_DIR}/uishader.h - ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin.h PROPERTIES GENERATED ON) + add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c) set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) @@ -67,20 +74,36 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icons.c DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/icons.raw) add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h - MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/uishader.g.pica COMMAND ${PICASSO} - -o ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin - -h ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h - ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh - COMMENT "picasso uishader.vsh") + -o ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin + -h ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin.h + ${CMAKE_CURRENT_SOURCE_DIR}/uishader.g.pica + COMMENT "picasso uishader.g.pica") add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.c ${CMAKE_CURRENT_BINARY_DIR}/uishader.h - MAIN_DEPENDENCY ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin - COMMAND ${RAW2C} ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.c ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin + COMMAND ${RAW2C} ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "raw2c uishader.shbin") + COMMENT "raw2c uishader.g.shbin") + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/uishader.v.pica + COMMAND ${PICASSO} + -o ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin + -h ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin.h + ${CMAKE_CURRENT_SOURCE_DIR}/uishader.v.pica + COMMENT "picasso uishader.v.pica") + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.c ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin + COMMAND ${RAW2C} ${CMAKE_CURRENT_BINARY_DIR}/uishader_v.shbin + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "raw2c uishader.v.shbin") add_custom_target(${BINARY_NAME}.3dsx ALL ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh diff --git a/src/platform/3ds/ctr-gpu.c b/src/platform/3ds/ctr-gpu.c index 63405a848..bd17a606d 100644 --- a/src/platform/3ds/ctr-gpu.c +++ b/src/platform/3ds/ctr-gpu.c @@ -1,4 +1,5 @@ /* Copyright (c) 2015 Yuri Kunde Schlesner + * Copyright (c) 2016 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 @@ -13,348 +14,132 @@ #include "ctr-gpu.h" -#include "uishader.h" -#include "uishader.shbin.h" +#include "uishader_v.h" +#include "uishader_v.shbin.h" +#include "uishader_g.h" +#include "uishader_g.shbin.h" struct ctrUIVertex { - s16 x,y; - s16 u,v; + short x, y; + short w, h; + short u, v; + short uw, vh; u32 abgr; }; -#define VRAM_BASE 0x18000000u - #define MAX_NUM_QUADS 1024 -#define COMMAND_LIST_LENGTH (16 * 1024) -// Each quad requires 4 vertices and 2*3 indices for the two triangles used to draw it -#define VERTEX_INDEX_BUFFER_SIZE (MAX_NUM_QUADS * (4 * sizeof(struct ctrUIVertex) + 6 * sizeof(u16))) +#define VERTEX_BUFFER_SIZE MAX_NUM_QUADS * sizeof(struct ctrUIVertex) static struct ctrUIVertex* ctrVertexBuffer = NULL; -static u16* ctrIndexBuffer = NULL; static u16 ctrNumQuads = 0; -static void* gpuColorBuffer[2] = { NULL, NULL }; -static u32* gpuCommandList = NULL; -static void* screenTexture = NULL; +static C3D_Tex* activeTexture = NULL; static shaderProgram_s gpuShader; -static DVLB_s* passthroughShader = NULL; - -static int pendingEvents = 0; - -static const struct ctrTexture* activeTexture = NULL; - -void ctrClearPending(int events) { - int toClear = events & pendingEvents; - if (toClear & (1 << GSPGPU_EVENT_PSC0)) { - gspWaitForPSC0(); - } - if (toClear & (1 << GSPGPU_EVENT_PPF)) { - gspWaitForPPF(); - } - pendingEvents ^= toClear; -} - -// Replacements for the limiting GPU_SetViewport function in ctrulib -static void _GPU_SetFramebuffer(intptr_t colorBuffer, intptr_t depthBuffer, u16 w, u16 h) { - u32 buf[4]; - - // Unknown - GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_FLUSH, 0x00000001); - GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_INVALIDATE, 0x00000001); - - // Set depth/color buffer address and dimensions - buf[0] = depthBuffer >> 3; - buf[1] = colorBuffer >> 3; - buf[2] = (0x01) << 24 | ((h-1) & 0xFFF) << 12 | (w & 0xFFF) << 0; - GPUCMD_AddIncrementalWrites(GPUREG_DEPTHBUFFER_LOC, buf, 3); - GPUCMD_AddWrite(GPUREG_RENDERBUF_DIM, buf[2]); - - // Set depth/color buffer pixel format - GPUCMD_AddWrite(GPUREG_DEPTHBUFFER_FORMAT, 3 /* D248S */ ); - GPUCMD_AddWrite(GPUREG_COLORBUFFER_FORMAT, 0 /* RGBA8 */ << 16 | 2 /* Unknown */); - GPUCMD_AddWrite(GPUREG_FRAMEBUFFER_BLOCK32, 0); // Unknown - - // Enable color/depth buffers - buf[0] = colorBuffer != 0 ? 0xF : 0x0; - buf[1] = buf[0]; - buf[2] = depthBuffer != 0 ? 0x2 : 0x0; - buf[3] = buf[2]; - GPUCMD_AddIncrementalWrites(GPUREG_COLORBUFFER_READ, buf, 4); -} - -static void _GPU_SetViewportEx(u16 x, u16 y, u16 w, u16 h) { - u32 buf[4]; - - buf[0] = f32tof24(w / 2.0f); - buf[1] = f32tof31(2.0f / w) << 1; - buf[2] = f32tof24(h / 2.0f); - buf[3] = f32tof31(2.0f / h) << 1; - GPUCMD_AddIncrementalWrites(GPUREG_VIEWPORT_WIDTH, buf, 4); - - GPUCMD_AddWrite(GPUREG_VIEWPORT_XY, (y & 0xFFFF) << 16 | (x & 0xFFFF) << 0); - - buf[0] = 0; - buf[1] = 0; - buf[2] = ((h-1) & 0xFFFF) << 16 | ((w-1) & 0xFFFF) << 0; - GPUCMD_AddIncrementalWrites(GPUREG_SCISSORTEST_MODE, buf, 3); -} - -static void _setDummyTexEnv(int id) { - GPU_SetTexEnv(id, - GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0), - GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0), - GPU_TEVOPERANDS(0, 0, 0), - GPU_TEVOPERANDS(0, 0, 0), - GPU_REPLACE, - GPU_REPLACE, - 0x00000000); -} - -Result ctrInitGpu() { - Result res = -1; - - // Allocate buffers - gpuColorBuffer[0] = vramAlloc(400 * 240 * 4); - gpuColorBuffer[1] = vramAlloc(320 * 240 * 4); - gpuCommandList = linearAlloc(COMMAND_LIST_LENGTH * sizeof(u32)); - ctrVertexBuffer = linearAlloc(VERTEX_INDEX_BUFFER_SIZE); - if (gpuColorBuffer[0] == NULL || gpuColorBuffer[1] == NULL || gpuCommandList == NULL || ctrVertexBuffer == NULL) { - res = -1; - goto error_allocs; - } - // Both buffers share the same allocation, index buffer follows the vertex buffer - ctrIndexBuffer = (u16*)(ctrVertexBuffer + (4 * MAX_NUM_QUADS)); +static DVLB_s* vertexShader = NULL; +static DVLB_s* geometryShader = NULL; +bool ctrInitGpu() { // Load vertex shader binary - passthroughShader = DVLB_ParseFile((u32*)uishader, uishader_size); - if (passthroughShader == NULL) { - res = -1; - goto error_dvlb; + vertexShader = DVLB_ParseFile((u32*) uishader_v, uishader_v_size); + if (vertexShader == NULL) { + return false; + } + + // Load geometry shader binary + geometryShader = DVLB_ParseFile((u32*) uishader_g, uishader_g_size); + if (geometryShader == NULL) { + return false; } // Create shader shaderProgramInit(&gpuShader); - res = shaderProgramSetVsh(&gpuShader, &passthroughShader->DVLE[0]); + Result res = shaderProgramSetVsh(&gpuShader, &vertexShader->DVLE[0]); if (res < 0) { - goto error_shader; + return false; + } + res = shaderProgramSetGsh(&gpuShader, &geometryShader->DVLE[0], 3); + if (res < 0) { + return false; + } + C3D_BindProgram(&gpuShader); + + // Allocate buffers + ctrVertexBuffer = linearAlloc(VERTEX_BUFFER_SIZE); + if (ctrVertexBuffer == NULL) { + return false; } - // Initialize the GPU in ctrulib and assign the command buffer to accept submission of commands - GPU_Init(NULL); - GPUCMD_SetBuffer(gpuCommandList, COMMAND_LIST_LENGTH, 0); + // Set up TexEnv and other parameters + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0); + C3D_TexEnvOp(env, C3D_Both, 0, 0, 0); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - return 0; + C3D_CullFace(GPU_CULL_NONE); + C3D_DepthTest(false, GPU_ALWAYS, GPU_WRITE_ALL); + C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA); + C3D_AlphaTest(false, GPU_ALWAYS, 0); + C3D_BlendingColor(0); -error_shader: - shaderProgramFree(&gpuShader); + C3D_AttrInfo* attrInfo = C3D_GetAttrInfo(); + AttrInfo_Init(attrInfo); + AttrInfo_AddLoader(attrInfo, 0, GPU_SHORT, 4); // in_pos + AttrInfo_AddLoader(attrInfo, 1, GPU_SHORT, 4); // in_tc0 + AttrInfo_AddLoader(attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // in_col -error_dvlb: - if (passthroughShader != NULL) { - DVLB_Free(passthroughShader); - passthroughShader = NULL; - } + C3D_BufInfo* bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, ctrVertexBuffer, sizeof(struct ctrUIVertex), 3, 0x210); -error_allocs: - if (ctrVertexBuffer != NULL) { - linearFree(ctrVertexBuffer); - ctrVertexBuffer = NULL; - ctrIndexBuffer = NULL; - } - - if (gpuCommandList != NULL) { - GPUCMD_SetBuffer(NULL, 0, 0); - linearFree(gpuCommandList); - gpuCommandList = NULL; - } - - if (gpuColorBuffer[0] != NULL) { - vramFree(gpuColorBuffer[0]); - gpuColorBuffer[0] = NULL; - } - - if (gpuColorBuffer[1] != NULL) { - vramFree(gpuColorBuffer[1]); - gpuColorBuffer[1] = NULL; - } - return res; + return true; } void ctrDeinitGpu() { + if (ctrVertexBuffer) { + linearFree(ctrVertexBuffer); + ctrVertexBuffer = NULL; + } + shaderProgramFree(&gpuShader); - DVLB_Free(passthroughShader); - passthroughShader = NULL; - - linearFree(screenTexture); - screenTexture = NULL; - - linearFree(ctrVertexBuffer); - ctrVertexBuffer = NULL; - ctrIndexBuffer = NULL; - - GPUCMD_SetBuffer(NULL, 0, 0); - linearFree(gpuCommandList); - gpuCommandList = NULL; - - vramFree(gpuColorBuffer[0]); - gpuColorBuffer[0] = NULL; - - vramFree(gpuColorBuffer[1]); - gpuColorBuffer[1] = NULL; -} - -void ctrGpuBeginFrame(int screen) { - if (screen > 1) { - return; + if (vertexShader) { + DVLB_Free(vertexShader); + vertexShader = NULL; } - int fw; - if (screen == 0) { - fw = 400; - } else { - fw = 320; + if (geometryShader) { + DVLB_Free(geometryShader); + geometryShader = NULL; } - - _GPU_SetFramebuffer(osConvertVirtToPhys(gpuColorBuffer[screen]), 0, 240, fw); -} - -void ctrGpuBeginDrawing(void) { - shaderProgramUse(&gpuShader); - - // Disable depth and stencil testing - GPU_SetDepthTestAndWriteMask(false, GPU_ALWAYS, GPU_WRITE_COLOR); - GPU_SetStencilTest(false, GPU_ALWAYS, 0, 0xFF, 0); - GPU_SetStencilOp(GPU_STENCIL_KEEP, GPU_STENCIL_KEEP, GPU_STENCIL_KEEP); - GPU_DepthMap(-1.0f, 0.0f); - - // Enable alpha blending - GPU_SetAlphaBlending( - GPU_BLEND_ADD, GPU_BLEND_ADD, // Operation RGB, Alpha - GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, // Color src, dst - GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA); // Alpha src, dst - GPU_SetBlendingColor(0, 0, 0, 0); - - // Disable alpha testing - GPU_SetAlphaTest(false, GPU_ALWAYS, 0); - - // Unknown - GPUCMD_AddMaskedWrite(GPUREG_EARLYDEPTH_TEST1, 0x1, 0); - GPUCMD_AddWrite(GPUREG_EARLYDEPTH_TEST2, 0); - - GPU_SetTexEnv(0, - GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // RGB - GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // Alpha - GPU_TEVOPERANDS(0, 0, 0), // RGB - GPU_TEVOPERANDS(0, 0, 0), // Alpha - GPU_MODULATE, GPU_MODULATE, // Operation RGB, Alpha - 0x00000000); // Constant color - _setDummyTexEnv(1); - _setDummyTexEnv(2); - _setDummyTexEnv(3); - _setDummyTexEnv(4); - _setDummyTexEnv(5); - - // Configure vertex attribute format - u32 bufferOffsets[] = { osConvertVirtToPhys(ctrVertexBuffer) - VRAM_BASE }; - u64 arrayTargetAttributes[] = { 0x210 }; - u8 numAttributesInArray[] = { 3 }; - GPU_SetAttributeBuffers( - 3, // Number of attributes - (u32*)VRAM_BASE, // Base address - GPU_ATTRIBFMT(0, 2, GPU_SHORT) | // Attribute format - GPU_ATTRIBFMT(1, 2, GPU_SHORT) | - GPU_ATTRIBFMT(2, 4, GPU_UNSIGNED_BYTE), - 0xFF8, // Non-fixed vertex inputs - 0x210, // Vertex shader input map - 1, // Use 1 vertex array - bufferOffsets, arrayTargetAttributes, numAttributesInArray); -} - -void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h) { - if (screen > 1) { - return; - } - - int fw; - if (screen == 0) { - fw = 400; - } else { - fw = 320; - } - - ctrFlushBatch(); - - void* colorBuffer = (u8*)gpuColorBuffer[screen] + ((fw - w) * 240 * 4); - - const u32 GX_CROP_INPUT_LINES = (1 << 2); - - ctrClearPending(1 << GSPGPU_EVENT_PSC0); - ctrClearPending(1 << GSPGPU_EVENT_PPF); - - GX_DisplayTransfer( - colorBuffer, GX_BUFFER_DIM(240, fw), - outputFramebuffer, GX_BUFFER_DIM(h, w), - GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | - GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | - GX_CROP_INPUT_LINES); - pendingEvents |= (1 << GSPGPU_EVENT_PPF); -} - -void ctrGpuEndDrawing(void) { - ctrClearPending(1 << GSPGPU_EVENT_PPF); - gfxSwapBuffersGpu(); - gspWaitForEvent(GSPGPU_EVENT_VBlank0, false); - - void* gpuColorBuffer0End = (char*)gpuColorBuffer[0] + 240 * 400 * 4; - void* gpuColorBuffer1End = (char*)gpuColorBuffer[1] + 240 * 320 * 4; - GX_MemoryFill( - gpuColorBuffer[0], 0x00000000, gpuColorBuffer0End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER, - gpuColorBuffer[1], 0x00000000, gpuColorBuffer1End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER); - pendingEvents |= 1 << GSPGPU_EVENT_PSC0; } void ctrSetViewportSize(s16 w, s16 h) { - // Set up projection matrix mapping (0,0) to the top-left and (w,h) to the - // bottom-right, taking into account the 3DS' screens' portrait - // orientation. - float projectionMtx[4 * 4] = { - // Rows are in the order w z y x, because ctrulib - 1.0f, 0.0f, -2.0f / h, 0.0f, - 1.0f, 0.0f, 0.0f, -2.0f / w, - -0.5f, 0.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, 0.0f, - }; - - GPU_SetFloatUniform(GPU_VERTEX_SHADER, VSH_FVEC_projectionMtx, (u32*)&projectionMtx, 4); - _GPU_SetViewportEx(0, 0, h, w); + C3D_SetViewport(0, 0, h, w); + C3D_Mtx projectionMtx; + Mtx_OrthoTilt(&projectionMtx, 0.0, w, h, 0.0, 0.0, 1.0); + C3D_FVUnifMtx4x4(GPU_GEOMETRY_SHADER, GSH_FVEC_projectionMtx, &projectionMtx); } -void ctrActivateTexture(const struct ctrTexture* texture) { - if (activeTexture == texture) { +void ctrActivateTexture(C3D_Tex* texture) { + if (texture == activeTexture) { return; } + if (activeTexture) { + ctrFlushBatch(); + } - ctrFlushBatch(); - - GPU_SetTextureEnable(GPU_TEXUNIT0); - GPU_SetTexture( - GPU_TEXUNIT0, (u32*)osConvertVirtToPhys(texture->data), - texture->width, texture->height, - GPU_TEXTURE_MAG_FILTER(texture->filter) | GPU_TEXTURE_MIN_FILTER(texture->filter) | - GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_BORDER) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_BORDER), - texture->format); - GPU_SetTextureBorderColor(GPU_TEXUNIT0, 0x00000000); - - float textureMtx[2 * 4] = { - // Rows are in the order w z y x, because ctrulib - 0.0f, 0.0f, 0.0f, 1.0f / texture->width, - 0.0f, 0.0f, 1.0f / texture->height, 0.0f, - }; - - GPU_SetFloatUniform(GPU_VERTEX_SHADER, VSH_FVEC_textureMtx, (u32*)&textureMtx, 2); - + C3D_TexBind(0, texture); activeTexture = texture; + + C3D_Mtx textureMtx = { + .m = { + // Rows are in the order w z y x, because ctrulib + 0.0f, 0.0f, 0.0f, 1.0f / activeTexture->width, + 0.0f, 0.0f, 1.0f / activeTexture->height, 0.0f + } + }; + C3D_FVUnifMtx2x4(GPU_GEOMETRY_SHADER, GSH_FVEC_textureMtx, &textureMtx); } void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh) { @@ -362,38 +147,22 @@ void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 u ctrFlushBatch(); } - u16 index = ctrNumQuads * 4; - struct ctrUIVertex* vtx = &ctrVertexBuffer[index]; - vtx->x = x; vtx->y = y; - vtx->u = u; vtx->v = v; - vtx->abgr = color; - vtx++; - - vtx->x = x + w; vtx->y = y; - vtx->u = u + uw; vtx->v = v; - vtx->abgr = color; - vtx++; - - vtx->x = x; vtx->y = y + h; - vtx->u = u; vtx->v = v + vh; - vtx->abgr = color; - vtx++; - - vtx->x = x + w; vtx->y = y + h; - vtx->u = u + uw; vtx->v = v + vh; + struct ctrUIVertex* vtx = &ctrVertexBuffer[ctrNumQuads]; + vtx->x = x; + vtx->y = y; + vtx->w = w; + vtx->h = h; + vtx->u = u; + vtx->v = v; + vtx->uw = uw; + vtx->vh = vh; vtx->abgr = color; - u16* i = &ctrIndexBuffer[ctrNumQuads * 6]; - i[0] = index + 0; i[1] = index + 1; i[2] = index + 2; - i[3] = index + 2; i[4] = index + 1; i[5] = index + 3; - - ctrNumQuads += 1; + ++ctrNumQuads; } void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h) { - ctrAddRectScaled(color, - x, y, w, h, - u, v, w, h); + ctrAddRectScaled(color, x, y, w, h, u, v, w, h); } void ctrFlushBatch(void) { @@ -401,19 +170,9 @@ void ctrFlushBatch(void) { return; } - ctrClearPending((1 << GSPGPU_EVENT_PSC0)); - - GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE); - GPU_DrawElements(GPU_GEOMETRY_PRIM, (u32*)(osConvertVirtToPhys(ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6); - - GPU_FinishDrawing(); - GPUCMD_Finalize(); - GSPGPU_FlushDataCache((u8*)gpuCommandList, COMMAND_LIST_LENGTH * sizeof(u32)); - GPUCMD_FlushAndRun(); - - gspWaitForP3D(); - - GPUCMD_SetBufferOffset(0); + GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_BUFFER_SIZE); + C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, ctrNumQuads); + C3D_Flush(); ctrNumQuads = 0; } diff --git a/src/platform/3ds/ctr-gpu.h b/src/platform/3ds/ctr-gpu.h index e49c8cecc..37fae8087 100644 --- a/src/platform/3ds/ctr-gpu.h +++ b/src/platform/3ds/ctr-gpu.h @@ -1,4 +1,5 @@ /* Copyright (c) 2015 Yuri Kunde Schlesner + * Copyright (c) 2016 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 @@ -8,34 +9,14 @@ #define GUI_GPU_H #include <3ds.h> +#include -struct ctrTexture { - void* data; - u32 format; - u32 filter; - u16 width; - u16 height; -}; - -static inline void ctrTexture_Init(struct ctrTexture* tex) { - tex->data = NULL; - tex->format = GPU_RGB565; - tex->filter = GPU_NEAREST; - tex->width = 0; - tex->height = 0; -} - -Result ctrInitGpu(void); +bool ctrInitGpu(void); void ctrDeinitGpu(void); -void ctrGpuBeginDrawing(void); -void ctrGpuBeginFrame(int screen); -void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h); -void ctrGpuEndDrawing(void); - void ctrSetViewportSize(s16 w, s16 h); -void ctrActivateTexture(const struct ctrTexture* texture); +void ctrActivateTexture(C3D_Tex* texture); void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh); void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h); void ctrFlushBatch(void); diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 7bec4ab44..67164290c 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -7,17 +7,18 @@ #include "util/gui/font-metrics.h" #include "util/png-io.h" #include "util/vfs.h" -#include "platform/3ds/ctr-gpu.h" #include "icons.h" #include "font.h" +#include "ctr-gpu.h" + #define CELL_HEIGHT 16 #define CELL_WIDTH 16 #define GLYPH_HEIGHT 12 struct GUIFont { - struct ctrTexture texture; - struct ctrTexture icons; + C3D_Tex texture; + C3D_Tex icons; }; struct GUIFont* GUIFontCreate(void) { @@ -26,23 +27,16 @@ struct GUIFont* GUIFontCreate(void) { return 0; } - struct ctrTexture* tex = &guiFont->texture; - ctrTexture_Init(tex); - tex->data = vramAlloc(256 * 128 * 2); - tex->format = GPU_RGBA5551; - tex->width = 256; - tex->height = 128; + C3D_Tex* tex = &guiFont->texture; + C3D_TexInitVRAM(tex, 256, 128, GPU_RGBA5551); GSPGPU_FlushDataCache(font, font_size); GX_RequestDma((u32*) font, tex->data, font_size); gspWaitForDMA(); tex = &guiFont->icons; - ctrTexture_Init(tex); - tex->data = vramAlloc(256 * 64 * 2); - tex->format = GPU_RGBA5551; - tex->width = 256; - tex->height = 64; + C3D_TexInitVRAM(tex, 256, 64, GPU_RGBA5551); + GSPGPU_FlushDataCache(icons, icons_size); GX_RequestDma((u32*) icons, tex->data, icons_size); @@ -52,8 +46,8 @@ struct GUIFont* GUIFontCreate(void) { } void GUIFontDestroy(struct GUIFont* font) { - vramFree(font->texture.data); - vramFree(font->icons.data); + C3D_TexDelete(&font->texture); + C3D_TexDelete(&font->icons); free(font); } diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index e67668e5f..dea773398 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -61,39 +61,39 @@ static struct mAVStream stream; static int16_t* audioLeft = 0; static int16_t* audioRight = 0; static size_t audioPos = 0; -static struct ctrTexture gbaOutputTexture; -static int guiDrawn; -static int screenCleanup; +static C3D_Tex outputTexture; static ndspWaveBuf dspBuffer[DSP_BUFFERS]; static int bufferId = 0; +static C3D_RenderBuf bottomScreen; +static C3D_RenderBuf topScreen; + static aptHookCookie cookie; -enum { - GUI_ACTIVE = 1, - GUI_THIS_FRAME = 2, -}; - -enum { - SCREEN_CLEANUP_TOP_1 = 1, - SCREEN_CLEANUP_TOP_2 = 2, - SCREEN_CLEANUP_TOP = SCREEN_CLEANUP_TOP_1 | SCREEN_CLEANUP_TOP_2, - SCREEN_CLEANUP_BOTTOM_1 = 4, - SCREEN_CLEANUP_BOTTOM_2 = 8, - SCREEN_CLEANUP_BOTTOM = SCREEN_CLEANUP_BOTTOM_1 | SCREEN_CLEANUP_BOTTOM_2, -}; - extern bool allocateRomBuffer(void); +static bool _initGpu(void) { + if (!C3D_Init(C3D_DEFAULT_CMDBUF_SIZE)) { + return false; + } + + if (!C3D_RenderBufInit(&topScreen, 240, 400, GPU_RB_RGB8, 0) || !C3D_RenderBufInit(&bottomScreen, 240, 320, GPU_RB_RGB8, 0)) { + return false; + } + + return ctrInitGpu(); +} + static void _cleanup(void) { + ctrDeinitGpu(); + if (outputBuffer) { linearFree(outputBuffer); } - if (gbaOutputTexture.data) { - ctrDeinitGpu(); - vramFree(gbaOutputTexture.data); - } + C3D_RenderBufDelete(&topScreen); + C3D_RenderBufDelete(&bottomScreen); + C3D_Fini(); gfxExit(); @@ -173,52 +173,15 @@ static void _csndPlaySound(u32 flags, u32 sampleRate, float vol, void* left, voi static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* right); static void _drawStart(void) { - ctrGpuBeginDrawing(); - if (screenMode < SM_PA_TOP || (guiDrawn & GUI_ACTIVE)) { - ctrGpuBeginFrame(GFX_BOTTOM); - ctrSetViewportSize(320, 240); - } else { - ctrGpuBeginFrame(GFX_TOP); - ctrSetViewportSize(400, 240); - } - guiDrawn &= ~GUI_THIS_FRAME; + C3D_RenderBufClear(&bottomScreen); + C3D_RenderBufClear(&topScreen); } static void _drawEnd(void) { - int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; - u16 width = 0, height = 0; - - void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); - ctrGpuEndFrame(screen, outputFramebuffer, width, height); - - if (screen != GFX_BOTTOM) { - if (guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE)) { - void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); - ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); - } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM) { - ctrGpuBeginFrame(GFX_BOTTOM); - if (screenCleanup & SCREEN_CLEANUP_BOTTOM_1) { - screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_1; - } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM_2) { - screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_2; - } - void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); - ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); - } - } - - if ((screenCleanup & SCREEN_CLEANUP_TOP) && screen != GFX_TOP) { - ctrGpuBeginFrame(GFX_TOP); - if (screenCleanup & SCREEN_CLEANUP_TOP_1) { - screenCleanup &= ~SCREEN_CLEANUP_TOP_1; - } else if (screenCleanup & SCREEN_CLEANUP_TOP_2) { - screenCleanup &= ~SCREEN_CLEANUP_TOP_2; - } - void* outputFramebuffer = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &height, &width); - ctrGpuEndFrame(GFX_TOP, outputFramebuffer, width, height); - } - - ctrGpuEndDrawing(); + C3D_RenderBufTransfer(&topScreen, (u32*) gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)); + C3D_RenderBufTransfer(&bottomScreen, (u32*) gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)); + gfxSwapBuffersGpu(); + gspWaitForEvent(GSPGPU_EVENT_VBlank0, false); } static int _batteryState(void) { @@ -237,20 +200,17 @@ static int _batteryState(void) { } static void _guiPrepare(void) { - guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME; int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; if (screen == GFX_BOTTOM) { return; } - ctrFlushBatch(); - ctrGpuBeginFrame(GFX_BOTTOM); + C3D_RenderBufBind(&bottomScreen); ctrSetViewportSize(320, 240); } static void _guiFinish(void) { - guiDrawn &= ~GUI_ACTIVE; - screenCleanup |= SCREEN_CLEANUP_BOTTOM; + ctrFlushBatch(); } static void _setup(struct mGUIRunner* runner) { @@ -322,7 +282,6 @@ static void _gameLoaded(struct mGUIRunner* runner) { unsigned mode; if (mCoreConfigGetUIntValue(&runner->core->config, "screenMode", &mode) && mode != screenMode) { screenMode = mode; - screenCleanup |= SCREEN_CLEANUP_BOTTOM | SCREEN_CLEANUP_TOP; } } @@ -358,6 +317,15 @@ static void _gameUnloaded(struct mGUIRunner* runner) { } static void _drawTex(struct mCore* core, bool faded) { + if (screenMode < SM_PA_TOP) { + C3D_RenderBufBind(&bottomScreen); + ctrSetViewportSize(320, 240); + } else { + C3D_RenderBufBind(&topScreen); + ctrSetViewportSize(400, 240); + } + ctrActivateTexture(&outputTexture); + u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF; int screen_w = screenMode < SM_PA_TOP ? 320 : 400; @@ -408,15 +376,16 @@ static void _drawTex(struct mCore* core, bool faded) { int y = (screen_h - h) / 2; ctrAddRectScaled(color, x, y, w, h, 0, 0, corew, coreh); + ctrFlushBatch(); } static void _drawFrame(struct mGUIRunner* runner, bool faded) { UNUSED(runner); - struct ctrTexture* tex = &gbaOutputTexture; + C3D_Tex* tex = &outputTexture; GSPGPU_FlushDataCache(outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); - GX_DisplayTransfer( + C3D_SafeDisplayTransfer( outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, 256), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) | @@ -429,14 +398,13 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) { } gspWaitForPPF(); - ctrActivateTexture(tex); _drawTex(runner->core, faded); } static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, bool faded) { UNUSED(runner); - struct ctrTexture* tex = &gbaOutputTexture; + C3D_Tex* tex = &outputTexture; u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100); @@ -454,7 +422,7 @@ static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, b } GSPGPU_FlushDataCache(newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32)); - GX_DisplayTransfer( + C3D_SafeDisplayTransfer( (u32*) newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, 256), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) | @@ -463,7 +431,6 @@ static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, b gspWaitForPPF(); linearFree(newPixels); - ctrActivateTexture(tex); _drawTex(runner->core, faded); } @@ -479,9 +446,11 @@ static uint16_t _pollGameInput(struct mGUIRunner* runner) { static void _incrementScreenMode(struct mGUIRunner* runner) { UNUSED(runner); - screenCleanup |= SCREEN_CLEANUP_TOP | SCREEN_CLEANUP_BOTTOM; screenMode = (screenMode + 1) % SM_MAX; mCoreConfigSetUIntValue(&runner->core->config, "screenMode", screenMode); + + C3D_RenderBufClear(&bottomScreen); + C3D_RenderBufClear(&topScreen); } static uint32_t _pollInput(void) { @@ -636,39 +605,29 @@ int main() { audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); } - gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false); + gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, true); - if (ctrInitGpu() < 0) { - gbaOutputTexture.data = 0; + if (!_initGpu()) { + outputTexture.data = 0; _cleanup(); return 1; } - ctrTexture_Init(&gbaOutputTexture); - gbaOutputTexture.format = GPU_RGB565; - gbaOutputTexture.filter = GPU_LINEAR; - gbaOutputTexture.width = 256; - gbaOutputTexture.height = 256; - gbaOutputTexture.data = vramAlloc(256 * 256 * 2); - void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2; - - if (!gbaOutputTexture.data) { + if (!C3D_TexInitVRAM(&outputTexture, 256, 256, GPU_RGB565)) { _cleanup(); return 1; } + C3D_TexSetWrap(&outputTexture, GPU_CLAMP_TO_EDGE, GPU_CLAMP_TO_EDGE); + C3D_TexSetFilter(&outputTexture, GPU_LINEAR, GPU_LINEAR); + void* outputTextureEnd = (u8*)outputTexture.data + 256 * 256 * 2; // Zero texture data to make sure no garbage around the border interferes with filtering GX_MemoryFill( - gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER, + outputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER, NULL, 0, NULL, 0); gspWaitForPSC0(); - sdmcArchive = (FS_Archive) { - ARCHIVE_SDMC, - (FS_Path) { PATH_EMPTY, 1, "" }, - 0 - }; - FSUSER_OpenArchive(&sdmcArchive); + FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")); struct GUIFont* font = GUIFontCreate(); diff --git a/src/platform/3ds/uishader.g.pica b/src/platform/3ds/uishader.g.pica new file mode 100644 index 000000000..db14f6bf7 --- /dev/null +++ b/src/platform/3ds/uishader.g.pica @@ -0,0 +1,96 @@ +; Copyright (c) 2015 Yuri Kunde Schlesner +; Copyright (c) 2016 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/. + +; Uniforms +.fvec projectionMtx[4] +.fvec textureMtx[2] + +; Constants +.constf consts1(0.0, 1.0, -0.5, -1.0) + +; Outputs : here only position and color +.out out_pos position +.out out_tc0 texcoord0 +.out out_col color + +; Inputs : here we have only vertices +.alias in_pos v0 +.alias in_tc0 v1 +.alias in_col v2 + +.gsh +.proc main + ; Set up the vertex endpoints + mov r0.xy, in_pos.xy + mov r0.zw, consts1.zy + add r1.xy, r0.xy, in_pos.zw + + dp4 r2.x, projectionMtx[0], r0 + dp4 r2.y, projectionMtx[1], r0 + dp4 r2.z, projectionMtx[2], r0 + dp4 r2.w, projectionMtx[3], r0 + + dp4 r3.x, projectionMtx[0], r1 + dp4 r3.y, projectionMtx[1], r1 + dp4 r3.z, projectionMtx[2], r1 + dp4 r3.w, projectionMtx[3], r1 + + ; Set up the texture endpoints + mov r0.xy, in_tc0.xy + mov r0.zw, consts1.xy + add r1.xy, r0.xy, in_tc0.zw + + dp4 r4.x, textureMtx[0], r0 + dp4 r4.y, textureMtx[1], r0 + mov r4.zw, consts1.xy + + dp4 r5.x, textureMtx[0], r1 + dp4 r5.y, textureMtx[1], r1 + mov r5.zw, consts1.xy + + ; Emit top-left + setemit 0 + mov out_pos.xyzw, r2.xyzw + mov out_tc0.xyzw, r4.xyzw + mov out_col, in_col + emit + + ; Emit bottom-left + setemit 1 + mov out_pos.x, r2.x + mov out_pos.y, r3.y + mov out_pos.z, consts1.z + mov out_pos.w, consts1.y + mov out_tc0.x, r5.x + mov out_tc0.y, r4.y + mov out_tc0.z, consts1.x + mov out_tc0.w, consts1.y + mov out_col, in_col + emit + + ; Emit bottom-right + setemit 2, prim + mov out_pos.xyzw, r3.xyzw + mov out_tc0.xyzw, r5.xyzw + mov out_col, in_col + emit + + ; Emit top-right + setemit 1, prim inv + mov out_pos.x, r3.x + mov out_pos.y, r2.y + mov out_pos.z, consts1.z + mov out_pos.w, consts1.y + mov out_tc0.x, r4.x + mov out_tc0.y, r5.y + mov out_tc0.z, consts1.x + mov out_tc0.w, consts1.y + mov out_col, in_col + emit + + end +.end diff --git a/src/platform/3ds/uishader.vsh b/src/platform/3ds/uishader.v.pica similarity index 58% rename from src/platform/3ds/uishader.vsh rename to src/platform/3ds/uishader.v.pica index a86c8eb2b..ddb371502 100644 --- a/src/platform/3ds/uishader.vsh +++ b/src/platform/3ds/uishader.v.pica @@ -1,4 +1,6 @@ ; Copyright (c) 2015 Yuri Kunde Schlesner +; Copyright (c) 2016 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 @@ -8,31 +10,23 @@ ; corresponding matrices before outputting ; Uniforms -.fvec projectionMtx[4] -.fvec textureMtx[2] ; Constants -.constf consts1(0.0, 1.0, 0.0039215686, 0.0) +.constf consts1(0.0, 1.0, 0.0039215686, -1.0) -; Outputs : here only position and color +; Outputs .out out_pos position .out out_tc0 texcoord0 .out out_col color -; Inputs : here we have only vertices +; Inputs .alias in_pos v0 .alias in_tc0 v1 .alias in_col v2 .proc main - dp4 out_pos.x, projectionMtx[0], in_pos - dp4 out_pos.y, projectionMtx[1], in_pos - dp4 out_pos.z, projectionMtx[2], in_pos - dp4 out_pos.w, projectionMtx[3], in_pos - - dp4 out_tc0.x, textureMtx[0], in_tc0 - dp4 out_tc0.y, textureMtx[1], in_tc0 - mov out_tc0.zw, consts1.xxxy + mov out_pos, in_pos + mov out_tc0, in_tc0 ; Normalize color by multiplying by 1 / 255 mul out_col, consts1.z, in_col From 4f1dffa5bd12ca514ee71ea4ac8594f3731a4779 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Wed, 27 Jul 2016 22:19:57 -0700 Subject: [PATCH 80/90] 3DS: Allow UTF-16 filenames --- CHANGES | 1 + src/platform/3ds/3ds-vfs.c | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 728b6e947..3d6aff2df 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ Misc: - Debugger: Support register and memory writes via GDB stub - GBA Audio: Force audio DMAs to not increment destination - Qt: Thread startup improvements + - 3DS: Allow UTF-16 filenames 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/platform/3ds/3ds-vfs.c b/src/platform/3ds/3ds-vfs.c index 24caff97d..11e894c95 100644 --- a/src/platform/3ds/3ds-vfs.c +++ b/src/platform/3ds/3ds-vfs.c @@ -56,8 +56,10 @@ struct VFile* VFileOpen3DS(FS_Archive* archive, const char* path, int flags) { return 0; } - // TODO: Use UTF-16 - FS_Path newPath = fsMakePath(PATH_ASCII, path); + uint16_t utf16Path[PATH_MAX + 1]; + ssize_t units = utf8_to_utf16(utf16Path, (const uint8_t*) path, PATH_MAX); + utf16Path[units] = 0; + FS_Path newPath = fsMakePath(PATH_UTF16, utf16Path); Result res = FSUSER_OpenFile(&vf3d->handle, *archive, newPath, flags, 0); if (res & 0xFFFC03FF) { free(vf3d); @@ -177,8 +179,10 @@ struct VDir* VDirOpen(const char* path) { return 0; } - // TODO: Use UTF-16 - FS_Path newPath = fsMakePath(PATH_ASCII, path); + uint16_t utf16Path[PATH_MAX + 1]; + ssize_t units = utf8_to_utf16(utf16Path, (const uint8_t*) path, PATH_MAX); + utf16Path[units] = 0; + FS_Path newPath = fsMakePath(PATH_UTF16, utf16Path); Result res = FSUSER_OpenDirectory(&vd3d->handle, sdmcArchive, newPath); if (res & 0xFFFC03FF) { free(vd3d); @@ -211,8 +215,10 @@ static bool _vd3dClose(struct VDir* vd) { static void _vd3dRewind(struct VDir* vd) { struct VDir3DS* vd3d = (struct VDir3DS*) vd; FSDIR_Close(vd3d->handle); - // TODO: Use UTF-16 - FS_Path newPath = fsMakePath(PATH_ASCII, vd3d->path); + uint16_t utf16Path[PATH_MAX + 1]; + ssize_t units = utf8_to_utf16(utf16Path, (const uint8_t*) vd3d->path, PATH_MAX); + utf16Path[units] = 0; + FS_Path newPath = fsMakePath(PATH_UTF16, utf16Path); FSUSER_OpenDirectory(&vd3d->handle, sdmcArchive, newPath); } @@ -268,8 +274,10 @@ static bool _vd3dDeleteFile(struct VDir* vd, const char* path) { char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); sprintf(combined, "%s/%s", dir, path); - // TODO: Use UTF-16 - FS_Path newPath = fsMakePath(PATH_ASCII, combined); + uint16_t utf16Path[PATH_MAX + 1]; + ssize_t units = utf8_to_utf16(utf16Path, (const uint8_t*) combined, PATH_MAX); + utf16Path[units] = 0; + FS_Path newPath = fsMakePath(PATH_UTF16, utf16Path); bool ret = !FSUSER_DeleteFile(sdmcArchive, newPath); free(combined); return ret; @@ -278,7 +286,7 @@ static bool _vd3dDeleteFile(struct VDir* vd, const char* path) { static const char* _vd3deName(struct VDirEntry* vde) { struct VDirEntry3DS* vd3de = (struct VDirEntry3DS*) vde; if (!vd3de->utf8Name[0]) { - utf16_to_utf8(vd3de->utf8Name, vd3de->ent.name, sizeof(vd3de->utf8Name)); + utf16_to_utf8((uint8_t*) vd3de->utf8Name, vd3de->ent.name, sizeof(vd3de->utf8Name)); } return vd3de->utf8Name; } From 376c3e715e1b849077febe956aa954be53fad810 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 28 Jul 2016 00:32:00 -0700 Subject: [PATCH 81/90] 3DS: Use system font for menus --- CHANGES | 1 + src/platform/3ds/CMakeLists.txt | 6 ---- src/platform/3ds/ctr-gpu.c | 53 ++++++++++++++++------------ src/platform/3ds/font.raw | Bin 65536 -> 0 bytes src/platform/3ds/gui-font.c | 59 +++++++++++++++++++------------- src/platform/3ds/main.c | 2 ++ 6 files changed, 70 insertions(+), 51 deletions(-) delete mode 100644 src/platform/3ds/font.raw diff --git a/CHANGES b/CHANGES index 73c0e813c..e45502fb0 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Misc: - GBA Audio: Force audio DMAs to not increment destination - Qt: Thread startup improvements - 3DS: Port to using citro3D + - 3DS: Use system font for menus 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index cc6ebbf37..942a8517c 100644 --- a/src/platform/3ds/CMakeLists.txt +++ b/src/platform/3ds/CMakeLists.txt @@ -30,7 +30,6 @@ set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/icons.c - ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.c ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.h ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin.h @@ -44,7 +43,6 @@ list(APPEND GUI_SRC set_source_files_properties( ${CMAKE_CURRENT_BINARY_DIR}/icons.c - ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.c ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.h ${CMAKE_CURRENT_BINARY_DIR}/uishader_g.shbin.h @@ -65,10 +63,6 @@ add_custom_command(OUTPUT ${BINARY_NAME}.bnr COMMAND ${BANNERTOOL} makebanner -i ${CMAKE_CURRENT_SOURCE_DIR}/logo.png -a ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav -o ${BINARY_NAME}.bnr DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/logo.png ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav) -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c - COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw - DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icons.c COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/icons.raw DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/icons.raw) diff --git a/src/platform/3ds/ctr-gpu.c b/src/platform/3ds/ctr-gpu.c index bd17a606d..a17362fd2 100644 --- a/src/platform/3ds/ctr-gpu.c +++ b/src/platform/3ds/ctr-gpu.c @@ -27,11 +27,12 @@ struct ctrUIVertex { u32 abgr; }; -#define MAX_NUM_QUADS 1024 +#define MAX_NUM_QUADS 2048 #define VERTEX_BUFFER_SIZE MAX_NUM_QUADS * sizeof(struct ctrUIVertex) static struct ctrUIVertex* ctrVertexBuffer = NULL; -static u16 ctrNumQuads = 0; +static int ctrNumVerts = 0; +static int ctrVertStart = 0; static C3D_Tex* activeTexture = NULL; @@ -70,12 +71,6 @@ bool ctrInitGpu() { return false; } - // Set up TexEnv and other parameters - C3D_TexEnv* env = C3D_GetTexEnv(0); - C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0); - C3D_TexEnvOp(env, C3D_Both, 0, 0, 0); - C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); - C3D_CullFace(GPU_CULL_NONE); C3D_DepthTest(false, GPU_ALWAYS, GPU_WRITE_ALL); C3D_AlphaBlend(GPU_BLEND_ADD, GPU_BLEND_ADD, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA); @@ -88,10 +83,6 @@ bool ctrInitGpu() { AttrInfo_AddLoader(attrInfo, 1, GPU_SHORT, 4); // in_tc0 AttrInfo_AddLoader(attrInfo, 2, GPU_UNSIGNED_BYTE, 4); // in_col - C3D_BufInfo* bufInfo = C3D_GetBufInfo(); - BufInfo_Init(bufInfo); - BufInfo_Add(bufInfo, ctrVertexBuffer, sizeof(struct ctrUIVertex), 3, 0x210); - return true; } @@ -129,9 +120,20 @@ void ctrActivateTexture(C3D_Tex* texture) { ctrFlushBatch(); } - C3D_TexBind(0, texture); activeTexture = texture; + C3D_TexEnv* env = C3D_GetTexEnv(0); + C3D_TexEnvOp(env, C3D_Both, 0, 0, 0); + if (texture->fmt < GPU_LA8) { + C3D_TexEnvSrc(env, C3D_Both, GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0); + C3D_TexEnvFunc(env, C3D_Both, GPU_MODULATE); + } else { + C3D_TexEnvSrc(env, C3D_RGB, GPU_PRIMARY_COLOR, 0, 0); + C3D_TexEnvSrc(env, C3D_Alpha, GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0); + C3D_TexEnvFunc(env, C3D_RGB, GPU_REPLACE); + C3D_TexEnvFunc(env, C3D_Alpha, GPU_MODULATE); + } + C3D_Mtx textureMtx = { .m = { // Rows are in the order w z y x, because ctrulib @@ -143,11 +145,14 @@ void ctrActivateTexture(C3D_Tex* texture) { } void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh) { - if (ctrNumQuads == MAX_NUM_QUADS) { + if (ctrNumVerts + ctrVertStart == MAX_NUM_QUADS) { ctrFlushBatch(); + C3D_Flush(); + ctrNumVerts = 0; + ctrVertStart = 0; } - struct ctrUIVertex* vtx = &ctrVertexBuffer[ctrNumQuads]; + struct ctrUIVertex* vtx = &ctrVertexBuffer[ctrVertStart + ctrNumVerts]; vtx->x = x; vtx->y = y; vtx->w = w; @@ -158,7 +163,7 @@ void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 u vtx->vh = vh; vtx->abgr = color; - ++ctrNumQuads; + ++ctrNumVerts; } void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h) { @@ -166,13 +171,19 @@ void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h) { } void ctrFlushBatch(void) { - if (ctrNumQuads == 0) { + if (ctrNumVerts == 0) { return; } - GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_BUFFER_SIZE); - C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, ctrNumQuads); - C3D_Flush(); + C3D_TexBind(0, activeTexture); - ctrNumQuads = 0; + C3D_BufInfo* bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, &ctrVertexBuffer[ctrVertStart], sizeof(struct ctrUIVertex), 3, 0x210); + + GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_BUFFER_SIZE); + C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, ctrNumVerts); + + ctrVertStart += ctrNumVerts; + ctrNumVerts = 0; } diff --git a/src/platform/3ds/font.raw b/src/platform/3ds/font.raw deleted file mode 100644 index 05b1436df325f5a4cbc2703202ed694b5fa3d149..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65536 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 j`hCx`t-~S zg8PV!jI4d01ozYP@@eG8&bS#ZPRE_`zB_-InP)J{jNKvkFnD77G;(8S+>92d<1TP{ z($GAfIz?w3>W&z9E%Q8+?|bRkYTK{%3NCYx4y&DU-`i7f<{3OtT@n9}^MBUAR1w0g z9}}`atGPA1UwQwxIQ4#Y6~FiF&ve+M*((IydE45DW$$O!E0yZ=j{ThJuk8Q2s_vJ& zpu5{+5O+pj>BxuZZ_a<_?*4S5{n&@Q&g+bE-D90Ht~3dMUbmDufZz6|9bqMm67V2k;#O`cjm7uJXKLu z3};rsJMig|I~>p)sFl(`j0xtJD$?xI3ldW zQM~#?-J?j(dsWZnK8yM@`uE(Vv+kNTv)}fURWtV|*U5gL*VR0@2WK)WLbh|h_Sk;1 zm^hXE+~w@M`y>3G)!k}O^323a#C_O8)qIF{mt!QiD%2!;jQKT|=kGf({x)%HJ+>Lf z{p>q)=Q`ECzW;@st57%U<@_8M5o`Ch0$VqEoD@|xAALMG8i`<;8chlO`Wf&Pi| z{JnzY3EN`MOrNKlxo1!R{v2`lc^cVKf$sN-Pt2>krDM##MW1w6^~tv5%t$MooXb-& zR@0fEkBl}(g_Tjgr;04}%{Z`%{Jou+bmyH{H9KY9vv8-ZD(FSG)(!>j z%R1QCBX2ROezIqMGOG5On>vok__QMDzGr-*a~1WIQS+X&Q7`ItRhRF&0(la9*)jQe z3b=_~mgjy39TaC6y+eJ|2eYWUhfOSZF~9HS?>p?heGT7!ohlDK&H z%X2-C+fPQ`{QmO2sQ#@wir0PgTI2evHEPRNwmz(2!EnZ%J<2q%{!|hA=rn&@-Te2G zBKQ!Ut>^voOlM?sqFiL;jUg;>~a|&~#MU~i*X)N)~C@^~7zuCAB)9}q2 zHNYBqMc%kml(3u=Q>KgDIlZ}aGK7}<)jr!}zRoz!F(acbi(WxDU(|Jtk>A+A`>g2X znpHjZ@0X{j>edSmT_3%VyjmfU>q%#4WbM&}NmvCepLQtO7yZB_pJ{JYD)~EmYsP33 zxvlRQ{mjZ$)EB*-!z4cI+vh1l$o=W;y*A%D|JcK%nY!yq*lH^=JQj=e z)6j#A9NXVd0!x9Vz*1l-uoPGdECrSVOM#`pQeY{t6j%x@1(pI!fu+DwU@5Q^SPCo!mI6zG zrNB~PDXVd0!x9Vz*1l-uoPGdECrSVOM#`pQeY{t6j%x@1(pI!fu+DwU@5Q^ MSPCo!KC8h00EbAi2LJ#7 diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 67164290c..f044ce35c 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -8,36 +8,43 @@ #include "util/png-io.h" #include "util/vfs.h" #include "icons.h" -#include "font.h" #include "ctr-gpu.h" #define CELL_HEIGHT 16 #define CELL_WIDTH 16 -#define GLYPH_HEIGHT 12 +#define FONT_SIZE 0.52f struct GUIFont { - C3D_Tex texture; + C3D_Tex* sheets; C3D_Tex icons; }; struct GUIFont* GUIFontCreate(void) { + fontEnsureMapped(); struct GUIFont* guiFont = malloc(sizeof(struct GUIFont)); if (!guiFont) { return 0; } + C3D_Tex* tex; - C3D_Tex* tex = &guiFont->texture; - C3D_TexInitVRAM(tex, 256, 128, GPU_RGBA5551); + TGLP_s* glyphInfo = fontGetGlyphInfo(); + guiFont->sheets = malloc(sizeof(*guiFont->sheets) * glyphInfo->nSheets); - GSPGPU_FlushDataCache(font, font_size); - GX_RequestDma((u32*) font, tex->data, font_size); - gspWaitForDMA(); + int i; + for (i = 0; i < glyphInfo->nSheets; ++i) { + tex = &guiFont->sheets[i]; + tex->data = fontGetGlyphSheetTex(i); + tex->fmt = glyphInfo->sheetFmt; + tex->size = glyphInfo->sheetSize; + tex->width = glyphInfo->sheetWidth; + tex->height = glyphInfo->sheetHeight; + tex->param = GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) | GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE); + } tex = &guiFont->icons; C3D_TexInitVRAM(tex, 256, 64, GPU_RGBA5551); - GSPGPU_FlushDataCache(icons, icons_size); GX_RequestDma((u32*) icons, tex->data, icons_size); gspWaitForDMA(); @@ -46,22 +53,24 @@ struct GUIFont* GUIFontCreate(void) { } void GUIFontDestroy(struct GUIFont* font) { - C3D_TexDelete(&font->texture); + free(font->sheets); C3D_TexDelete(&font->icons); free(font); } unsigned GUIFontHeight(const struct GUIFont* font) { UNUSED(font); - return GLYPH_HEIGHT; + return fontGetInfo()->lineFeed * FONT_SIZE; } unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { UNUSED(font); - if (glyph > 0x7F) { - glyph = 0; + int index = fontGlyphIndexFromCodePoint(glyph); + charWidthInfo_s* info = fontGetCharWidthInfo(index); + if (info) { + return info->charWidth * FONT_SIZE; } - return defaultFontMetrics[glyph].width; + return 0; } void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) { @@ -84,19 +93,21 @@ void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* } void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint32_t color, uint32_t glyph) { - ctrActivateTexture(&font->texture); + int index = fontGlyphIndexFromCodePoint(glyph); + fontGlyphPos_s data; + fontCalcGlyphPos(&data, index, GLYPH_POS_CALC_VTXCOORD, 1.0, 1.0); - if (glyph > 0x7F) { - glyph = '?'; - } + C3D_Tex* tex = &font->sheets[data.sheetIndex]; + ctrActivateTexture(tex); - struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; - u16 x = glyph_x - metric.padding.left; - u16 y = glyph_y - GLYPH_HEIGHT; - u16 u = (glyph % 16u) * CELL_WIDTH; - u16 v = (glyph / 16u) * CELL_HEIGHT; + float width = data.texcoord.right - data.texcoord.left; + float height = data.texcoord.top - data.texcoord.bottom; + u16 x = glyph_x; + u16 y = glyph_y + tex->height * height / 8; + u16 u = tex->width * data.texcoord.left; + u16 v = tex->height * data.texcoord.bottom; - ctrAddRect(color, x, y, u, v, CELL_WIDTH, CELL_HEIGHT); + ctrAddRectScaled(color, x, y, tex->width * width * FONT_SIZE, tex->height * height * -FONT_SIZE, u, v, tex->width * width, tex->height * height); } void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) { diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index dea773398..6e9ca6488 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -178,6 +178,8 @@ static void _drawStart(void) { } static void _drawEnd(void) { + ctrFlushBatch(); + C3D_Flush(); C3D_RenderBufTransfer(&topScreen, (u32*) gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)); C3D_RenderBufTransfer(&bottomScreen, (u32*) gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)); gfxSwapBuffersGpu(); From f25a7f7778bcd1f3d7c0ff755bc5d475f546a978 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 28 Jul 2016 22:00:47 -0700 Subject: [PATCH 82/90] 3DS: Improve menu performance --- src/platform/3ds/ctr-gpu.c | 14 ++++++++++---- src/platform/3ds/ctr-gpu.h | 1 + src/platform/3ds/main.c | 3 +-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/platform/3ds/ctr-gpu.c b/src/platform/3ds/ctr-gpu.c index a17362fd2..14446b79d 100644 --- a/src/platform/3ds/ctr-gpu.c +++ b/src/platform/3ds/ctr-gpu.c @@ -27,7 +27,7 @@ struct ctrUIVertex { u32 abgr; }; -#define MAX_NUM_QUADS 2048 +#define MAX_NUM_QUADS 1024 #define VERTEX_BUFFER_SIZE MAX_NUM_QUADS * sizeof(struct ctrUIVertex) static struct ctrUIVertex* ctrVertexBuffer = NULL; @@ -121,6 +121,7 @@ void ctrActivateTexture(C3D_Tex* texture) { } activeTexture = texture; + C3D_TexBind(0, activeTexture); C3D_TexEnv* env = C3D_GetTexEnv(0); C3D_TexEnvOp(env, C3D_Both, 0, 0, 0); @@ -175,15 +176,20 @@ void ctrFlushBatch(void) { return; } - C3D_TexBind(0, activeTexture); - C3D_BufInfo* bufInfo = C3D_GetBufInfo(); BufInfo_Init(bufInfo); BufInfo_Add(bufInfo, &ctrVertexBuffer[ctrVertStart], sizeof(struct ctrUIVertex), 3, 0x210); - GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_BUFFER_SIZE); + GSPGPU_FlushDataCache(&ctrVertexBuffer[ctrVertStart], sizeof(struct ctrUIVertex) * ctrNumVerts); C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, ctrNumVerts); ctrVertStart += ctrNumVerts; ctrNumVerts = 0; } + +void ctrFinalize(void) { + ctrFlushBatch(); + C3D_Flush(); + ctrNumVerts = 0; + ctrVertStart = 0; +} diff --git a/src/platform/3ds/ctr-gpu.h b/src/platform/3ds/ctr-gpu.h index 37fae8087..5f4cb6d3b 100644 --- a/src/platform/3ds/ctr-gpu.h +++ b/src/platform/3ds/ctr-gpu.h @@ -20,5 +20,6 @@ void ctrActivateTexture(C3D_Tex* texture); void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh); void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h); void ctrFlushBatch(void); +void ctrFinalize(void); #endif diff --git a/src/platform/3ds/main.c b/src/platform/3ds/main.c index 6e9ca6488..131a6c64c 100644 --- a/src/platform/3ds/main.c +++ b/src/platform/3ds/main.c @@ -178,8 +178,7 @@ static void _drawStart(void) { } static void _drawEnd(void) { - ctrFlushBatch(); - C3D_Flush(); + ctrFinalize(); C3D_RenderBufTransfer(&topScreen, (u32*) gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)); C3D_RenderBufTransfer(&bottomScreen, (u32*) gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, NULL, NULL), GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB8) | GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8)); gfxSwapBuffersGpu(); From f5e7b313c298b9104aa23f25448dee456923d9e6 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Thu, 28 Jul 2016 22:19:57 -0700 Subject: [PATCH 83/90] PSP2: Packaging fixes --- src/platform/psp2/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 9f3fbf8f0..71d696a09 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -51,7 +51,7 @@ add_custom_target(${BINARY_NAME}.velf ALL add_custom_target(sce_sys ${CMAKE_COMMAND} -E make_directory sce_sys) add_custom_target(param.sfo - ${MAKE_SFO} ${PROJECT_NAME} -s TITLE_ID=MGBA4VITA sce_sys/param.sfo + ${MAKE_SFO} ${PROJECT_NAME} -s TITLE_ID=MGBA00001 sce_sys/param.sfo DEPENDS sce_sys) add_custom_target(eboot.bin @@ -68,6 +68,6 @@ add_custom_target(livearea add_custom_target(${BINARY_NAME}.vpk ALL zip -r ${BINARY_NAME}.vpk sce_sys eboot.bin - DEPENDS livearea eboot.bin head.bin param.sfo) + DEPENDS livearea eboot.bin param.sfo) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.vpk DESTINATION . COMPONENT ${BINARY_NAME}-psp2) From f4e26656e4425f7cc83bedb7cc7aa15ca5e6e0b0 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 29 Jul 2016 21:11:49 -0700 Subject: [PATCH 84/90] PSP2: Use PGF fonts --- CHANGES | 1 + src/platform/psp2/CMakeLists.txt | 2 +- src/platform/psp2/gui-font.c | 25 +++++++++---------------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 8620dd36e..1015a1321 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ Misc: - 3DS: Allow UTF-16 filenames - 3DS: Port to using citro3D - 3DS: Use system font for menus + - PSP2: Use system font for menus 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index 71d696a09..bb0f2b12d 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -15,7 +15,7 @@ source_group("PS Vita-specific code" FILES ${OS_SRC}) list(APPEND CORE_VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) set(CORE_VFS_SRC ${CORE_VFS_SRC} PARENT_SCOPE) -set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceCommonDialog_stub -lSceMotion_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) +set(OS_LIB -lvita2d -lSceCtrl_stub -lScePgf_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceCommonDialog_stub -lSceMotion_stub -lScePower_stub -lSceSysmodule_stub -lSceTouch_stub -l${M_LIBRARY}) set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) diff --git a/src/platform/psp2/gui-font.c b/src/platform/psp2/gui-font.c index aa1e6cdc1..b6452dd5f 100644 --- a/src/platform/psp2/gui-font.c +++ b/src/platform/psp2/gui-font.c @@ -10,13 +10,12 @@ #define CELL_HEIGHT 32 #define CELL_WIDTH 32 -#define GLYPH_HEIGHT 24 +#define FONT_SIZE 1.2f -extern const uint8_t _binary_font2x_png_start[]; extern const uint8_t _binary_icons2x_png_start[]; struct GUIFont { - vita2d_texture* tex; + vita2d_pgf* pgf; vita2d_texture* icons; }; @@ -25,28 +24,27 @@ struct GUIFont* GUIFontCreate(void) { if (!font) { return 0; } - font->tex = vita2d_load_PNG_buffer(_binary_font2x_png_start); + font->pgf = vita2d_load_default_pgf(); font->icons = vita2d_load_PNG_buffer(_binary_icons2x_png_start); return font; } void GUIFontDestroy(struct GUIFont* font) { - vita2d_free_texture(font->tex); + vita2d_free_pgf(font->pgf); vita2d_free_texture(font->icons); free(font); } unsigned GUIFontHeight(const struct GUIFont* font) { - UNUSED(font); - return GLYPH_HEIGHT; + return vita2d_pgf_text_height(font->pgf, FONT_SIZE, "M") + 8; } unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { - UNUSED(font); if (glyph > 0x7F) { glyph = '?'; } - return defaultFontMetrics[glyph].width * 2; + char base[5] = { glyph }; + return vita2d_pgf_text_width(font->pgf, FONT_SIZE, base); } void GUIFontIconMetrics(const struct GUIFont* font, enum GUIIcon icon, unsigned* w, unsigned* h) { @@ -72,13 +70,8 @@ void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, if (glyph > 0x7F) { glyph = '?'; } - struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; - vita2d_draw_texture_tint_part(font->tex, x, y - GLYPH_HEIGHT + metric.padding.top * 2, - (glyph & 15) * CELL_WIDTH + metric.padding.left * 2, - (glyph >> 4) * CELL_HEIGHT + metric.padding.top * 2, - CELL_WIDTH - (metric.padding.left + metric.padding.right) * 2, - CELL_HEIGHT - (metric.padding.top + metric.padding.bottom) * 2, - color); + char base[5] = { glyph }; + vita2d_pgf_draw_text(font->pgf, x, y, color, FONT_SIZE, base); } void GUIFontDrawIcon(const struct GUIFont* font, int x, int y, enum GUIAlignment align, enum GUIOrientation orient, uint32_t color, enum GUIIcon icon) { From 8862dc49aa03691b5cbd614dde1a99420d555cbd Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 29 Jul 2016 21:27:47 -0700 Subject: [PATCH 85/90] Util: Fix realloc semantics in utf16to8 (again) --- src/util/string.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/util/string.c b/src/util/string.c index 08e4a4292..0157d4403 100644 --- a/src/util/string.c +++ b/src/util/string.c @@ -187,12 +187,11 @@ char* utf16to8(const uint16_t* utf16, size_t length) { memcpy(utf8, buffer, bytes); offset = utf8 + bytes; } else if (utf8Length >= utf8TotalBytes) { + ptrdiff_t o = offset - utf8; char* newUTF8 = realloc(utf8, utf8TotalBytes * 2); - offset = offset - utf8 + newUTF8; - if (newUTF8 != utf8) { - free(utf8); - } + offset = o + newUTF8; if (!newUTF8) { + free(utf8); return 0; } utf8 = newUTF8; From da0e39bc596022346881fce3f1584d8adb8054e0 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 29 Jul 2016 21:59:37 -0700 Subject: [PATCH 86/90] PSP2: Cleanup PGF fixes a bit --- src/platform/psp2/CMakeLists.txt | 6 ------ src/platform/psp2/gui-font.c | 5 +++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index bb0f2b12d..20f1f2239 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -21,20 +21,14 @@ set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/icons.o - ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o PROPERTIES GENERATED ON) add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} main.c ${CMAKE_CURRENT_BINARY_DIR}/icons.o - ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o) set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o - COMMAND ${OBJCOPY_CMD} font2x.png ${CMAKE_CURRENT_BINARY_DIR}/font.o - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/icons.o COMMAND ${OBJCOPY_CMD} icons2x.png ${CMAKE_CURRENT_BINARY_DIR}/icons.o WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) diff --git a/src/platform/psp2/gui-font.c b/src/platform/psp2/gui-font.c index b6452dd5f..73c6a0ac7 100644 --- a/src/platform/psp2/gui-font.c +++ b/src/platform/psp2/gui-font.c @@ -5,12 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "util/gui/font.h" #include "util/gui/font-metrics.h" +#include "util/string.h" #include #define CELL_HEIGHT 32 #define CELL_WIDTH 32 -#define FONT_SIZE 1.2f +#define FONT_SIZE 1.25f extern const uint8_t _binary_icons2x_png_start[]; @@ -36,7 +37,7 @@ void GUIFontDestroy(struct GUIFont* font) { } unsigned GUIFontHeight(const struct GUIFont* font) { - return vita2d_pgf_text_height(font->pgf, FONT_SIZE, "M") + 8; + return vita2d_pgf_text_height(font->pgf, FONT_SIZE, "M") + 9; } unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) { From 012c70a0cea381aefbaef9d4cff18bdd4af1e43c Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Fri, 29 Jul 2016 22:03:25 -0700 Subject: [PATCH 87/90] Perf: Fix 3DS build --- src/platform/test/perf-main.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/platform/test/perf-main.c b/src/platform/test/perf-main.c index ea3d3a861..d4e9ad971 100644 --- a/src/platform/test/perf-main.c +++ b/src/platform/test/perf-main.c @@ -69,12 +69,7 @@ int main(int argc, char** argv) { if (!allocateRomBuffer()) { return 1; } - sdmcArchive = (FS_Archive) { - ARCHIVE_SDMC, - (FS_Path) { PATH_EMPTY, 1, "" }, - 0 - }; - FSUSER_OpenArchive(&sdmcArchive); + FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")); #else signal(SIGINT, _mPerfShutdown); #endif From 42f5934b25d5352c8dc185e8d1f133a9ade5ba7a Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 30 Jul 2016 00:51:45 -0700 Subject: [PATCH 88/90] All: Faster memory read/write --- CHANGES | 1 + src/gba/memory.c | 2 +- src/util/common.h | 12 ++++++------ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 1015a1321..deb3e8b9f 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Misc: - 3DS: Port to using citro3D - 3DS: Use system font for menus - PSP2: Use system font for menus + - All: Faster memory read/write 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/gba/memory.c b/src/gba/memory.c index 153d1249d..f3ac0128a 100644 --- a/src/gba/memory.c +++ b/src/gba/memory.c @@ -384,7 +384,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) { if ((address & (SIZE_CART0 - 1)) < memory->romSize) { \ LOAD_32(value, address & (SIZE_CART0 - 4), memory->rom); \ } else if (memory->mirroring && (address & memory->romMask) < memory->romSize) { \ - LOAD_32(value, address & memory->romMask, memory->rom); \ + LOAD_32(value, address & memory->romMask & -4, memory->rom); \ } else if (memory->vfame.cartType) { \ value = GBAVFameGetPatternValue(address, 32); \ } else { \ diff --git a/src/util/common.h b/src/util/common.h index 7dd617e5c..25993cd26 100644 --- a/src/util/common.h +++ b/src/util/common.h @@ -93,12 +93,12 @@ typedef intptr_t ssize_t; #error Big endian build not supported on this platform. #endif #else -#define LOAD_64LE(DEST, ADDR, ARR) DEST = ((uint64_t*) ARR)[(ADDR) >> 3] -#define LOAD_32LE(DEST, ADDR, ARR) DEST = ((uint32_t*) ARR)[(ADDR) >> 2] -#define LOAD_16LE(DEST, ADDR, ARR) DEST = ((uint16_t*) ARR)[(ADDR) >> 1] -#define STORE_64LE(SRC, ADDR, ARR) ((uint64_t*) ARR)[(ADDR) >> 3] = SRC -#define STORE_32LE(SRC, ADDR, ARR) ((uint32_t*) ARR)[(ADDR) >> 2] = SRC -#define STORE_16LE(SRC, ADDR, ARR) ((uint16_t*) ARR)[(ADDR) >> 1] = SRC +#define LOAD_64LE(DEST, ADDR, ARR) DEST = *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) +#define LOAD_32LE(DEST, ADDR, ARR) DEST = *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) +#define LOAD_16LE(DEST, ADDR, ARR) DEST = *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) +#define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC +#define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC #endif #define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START)) From 61bfd9d87de131d4d04532c1afe61bd22a8a1233 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 30 Jul 2016 02:07:52 -0700 Subject: [PATCH 89/90] Qt: Make audio channel/video layer options shortcut mappable --- CHANGES | 1 + src/platform/qt/Window.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index deb3e8b9f..90a8070b6 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,7 @@ Misc: - 3DS: Use system font for menus - PSP2: Use system font for menus - All: Faster memory read/write + - Qt: Make audio channel/video layer options shortcut mappable 0.4.1: (2016-07-11) Bugfixes: diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 6f6fe594d..388dd4d20 100644 --- a/src/platform/qt/Window.cpp +++ b/src/platform/qt/Window.cpp @@ -1218,6 +1218,7 @@ void Window::setupMenu(QMenuBar* menubar) { avMenu->addSeparator(); QMenu* videoLayers = avMenu->addMenu(tr("Video layers")); + m_shortcutController->addMenu(videoLayers, avMenu); for (int i = 0; i < 4; ++i) { QAction* enableBg = new QAction(tr("Background %0").arg(i), videoLayers); @@ -1234,6 +1235,7 @@ void Window::setupMenu(QMenuBar* menubar) { addControlledAction(videoLayers, enableObj, "enableOBJ"); QMenu* audioChannels = avMenu->addMenu(tr("Audio channels")); + m_shortcutController->addMenu(audioChannels, avMenu); for (int i = 0; i < 4; ++i) { QAction* enableCh = new QAction(tr("Channel %0").arg(i + 1), audioChannels); From ce8db376346cfabd1c4efdc8eef6d1d64450cca1 Mon Sep 17 00:00:00 2001 From: Jeffrey Pfau Date: Sat, 30 Jul 2016 09:48:56 -0700 Subject: [PATCH 90/90] PSP2: Fix GPU crash while exiting --- CHANGES | 1 + src/platform/psp2/main.c | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 90a8070b6..ff4c90b27 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ Bugfixes: - SDL: Fix axes being mapped wrong - GBA Memory: Fix mirror on non-overdumped Classic NES games - Util: Fix realloc semantics in utf16to8 + - PSP2: Fix GPU crash while exiting Misc: - 3DS: Use blip_add_delta_fast for a small speed improvement - OpenGL: Log shader compilation failure diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index cf5096220..f757889d2 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -161,10 +162,16 @@ int main() { mGUIInit(&runner, "psvita"); mGUIRunloop(&runner); + + vita2d_fini(); mGUIDeinit(&runner); + int pgfLoaded = sceSysmoduleIsLoaded(SCE_SYSMODULE_PGF); + if (pgfLoaded != SCE_SYSMODULE_LOADED) { + sceSysmoduleLoadModule(SCE_SYSMODULE_PGF); + } GUIFontDestroy(font); - vita2d_fini(); + sceSysmoduleUnloadModule(SCE_SYSMODULE_PGF); sceKernelExitProcess(0); return 0;