diff --git a/CHANGES b/CHANGES index 866b6125c..ff4c90b27 100644 --- a/CHANGES +++ b/CHANGES @@ -2,43 +2,79 @@ Features: - Game Boy support - Support for encrypted CodeBreaker GBA cheats + - Emulation of Vast Fame protected GBA carts (taizou) + - Tile viewer 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 + - Util: Fix realloc semantics in utf16to8 + - PSP2: Fix GPU crash while exiting 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 + - ARM7: Support forcing Thumb mode via MSR + - ARM7: Flush prefetch cache when loading CPSR via MSR + - 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 + - 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 + - 3DS: Port to using citro3D + - 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: + - 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: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3abe0c277..3bf33af19 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() @@ -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) @@ -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}) 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/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/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); } diff --git a/src/arm/isa-arm.c b/src/arm/isa-arm.c index 518ab5134..d1f7b7c29 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])) @@ -629,11 +637,21 @@ 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); } - _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; @@ -641,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; \ @@ -660,11 +678,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; @@ -673,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)) 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)))) diff --git a/src/core/core.c b/src/core/core.c index dbe11b781..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 @@ -108,7 +109,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 +125,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..eba8986c9 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; @@ -66,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); @@ -78,8 +80,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); @@ -119,6 +122,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 new file mode 100644 index 000000000..b64910e54 --- /dev/null +++ b/src/core/serialize.c @@ -0,0 +1,436 @@ +/* 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 "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; + 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; +} + + + + + + + +#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) { + void* sram = NULL; + size_t size = core->savedataClone(core, &sram); + if (size) { + struct mStateExtdataItem item = { + .size = size, + .data = sram, + .clean = free + }; + mStateExtdataPut(&extdata, EXTDATA_SAVEDATA, &item); + } + } + 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)) { + 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)) { + 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 new file mode 100644 index 000000000..d639aaae4 --- /dev/null +++ b/src/core/serialize.h @@ -0,0 +1,47 @@ +/* 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); + +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/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/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index b94c45a8a..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') { @@ -741,7 +742,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 +773,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/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; 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/feature/gui/gui-runner.c b/src/feature/gui/gui-runner.c index 1d0977720..e4686b862 100644 --- a/src/feature/gui/gui-runner.c +++ b/src/feature/gui/gui-runner.c @@ -6,10 +6,11 @@ #include "gui-runner.h" #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 "gba/serialize.h" #include "util/gui/file-select.h" #include "util/gui/font.h" #include "util/gui/menu.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) { diff --git a/src/gb/audio.c b/src/gb/audio.c index 44b8ff1c6..d055d66e9 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 @@ -143,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) { @@ -157,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; @@ -854,8 +856,109 @@ 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; } } + +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 1c3debbf7..6aaae8ac1 100644 --- a/src/gb/cli.c +++ b/src/gb/cli.c @@ -5,27 +5,119 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cli.h" +#include "core/core.h" +#include "core/serialize.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 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 } }; 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 = _GBCLIDebuggerLookupIdentifier; - 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 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; + + struct GBCLIDebugger* gbDebugger = (struct GBCLIDebugger*) debugger->system; + gbDebugger->frameAdvance = true; + 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/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 diff --git a/src/gb/core.c b/src/gb/core.c index 0ca70be10..4ea53e673 100644 --- a/src/gb/core.c +++ b/src/gb/core.c @@ -10,9 +10,11 @@ #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" +#include "util/vfs.h" struct GBCore { struct mCore d; @@ -44,6 +46,7 @@ static bool _GBCoreInit(struct mCore* core) { LR35902Init(cpu); GBVideoSoftwareRendererCreate(&gbcore->renderer); + gbcore->renderer.outputBuffer = NULL; gbcore->keys = 0; gb->keySource = &gbcore->keys; @@ -93,6 +96,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 +161,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) { @@ -197,23 +208,24 @@ 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) { +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) { + GBSerialize(core->board, state); + return true; } static void _GBCoreSetKeys(struct mCore* core, uint32_t keys) { @@ -397,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; @@ -426,6 +465,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; @@ -457,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/gb/gb.c b/src/gb/gb.c index abd3f7e84..9d050867f 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; @@ -65,7 +66,7 @@ static void GBInit(void* cpu, struct mCPUComponent* component) { gb->stream = NULL; - gb->eiPending = false; + gb->eiPending = INT_MAX; gb->doubleSpeed = 0; } @@ -88,6 +89,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 +111,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 +140,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 +180,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) { @@ -216,7 +265,7 @@ void GBUpdateIRQs(struct GB* gb) { } gb->cpu->halted = false; - if (!gb->memory.ime) { + if (!gb->memory.ime || gb->cpu->irqPending) { return; } @@ -253,12 +302,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; } } @@ -301,7 +350,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) { 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/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 a6d67be0a..3317acdf9 100644 --- a/src/gb/io.c +++ b/src/gb/io.c @@ -6,9 +6,68 @@ #include "io.h" #include "gb/gb.h" +#include "gb/serialize.h" 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, @@ -310,6 +369,13 @@ 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) { + free(gb->memory.romBase); + gb->memory.romBase = gb->memory.rom; + } break; case REG_IE: gb->memory.ie = value; @@ -343,7 +409,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; @@ -352,7 +418,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; @@ -472,7 +538,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 @@ -487,3 +555,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 7c5cec231..ffd6f47c9 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); @@ -111,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 c5271fbb7..d999a7bed 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" @@ -34,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)) { @@ -52,7 +54,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; @@ -132,7 +134,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)); @@ -187,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) { @@ -211,7 +217,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: @@ -228,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: @@ -682,9 +690,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; } @@ -719,7 +728,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; } @@ -931,6 +940,99 @@ 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: + _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); + 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)); + + 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) { + 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)); + + 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) { if (gb->memory.rom != gb->pristineRom) { return; diff --git a/src/gb/memory.h b/src/gb/memory.h index f2f4a4d26..48e52459e 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; @@ -148,7 +149,7 @@ struct GBMemory { bool rtcAccess; int activeRtcReg; - int rtcLatched; + bool rtcLatched; uint8_t rtcRegs[5]; struct mRTCSource* rtc; struct mRotationSource* rotation; @@ -173,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/renderers/software.c b/src/gb/renderers/software.c index 41f88cbb4..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; @@ -297,7 +296,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) { diff --git a/src/gb/serialize.c b/src/gb/serialize.c new file mode 100644 index 000000000..e5985ad4e --- /dev/null +++ b/src/gb/serialize.c @@ -0,0 +1,172 @@ +/* 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 "gb/io.h" +#include "gb/timer.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); + + STORE_32LE(gb->eiPending, 0, &state->cpu.eiPending); + + 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); + 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); + + LOAD_32LE(gb->eiPending, 0, &state->cpu.eiPending); + + 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); + + 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..9be4997cc --- /dev/null +++ b/src/gb/serialize.h @@ -0,0 +1,353 @@ +/* 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 - 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 + * | 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 + * 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); +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; +}; + +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; + 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; + GBSerializedCpuFlags flags; + } 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; + GBSerializedVideoFlags flags; + uint16_t reserved; + + 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; + GBSerializedMBC7Flags flags; + } mbc7; + struct { + uint8_t reserved[16]; + } padding; + }; + + 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; + + uint8_t vram[GB_SIZE_VRAM]; + uint8_t wram[GB_SIZE_WORKING_RAM]; +}; +#pragma pack(pop) + +bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state); +void GBSerialize(struct GB* gb, struct GBSerializedState* state); + +#endif diff --git a/src/gb/timer.c b/src/gb/timer.c index 881632205..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 @@ -30,13 +31,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 +58,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,10 +91,26 @@ 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; } } } + +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/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 9aae94110..78ae8430f 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" @@ -50,6 +51,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 +90,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); @@ -104,7 +107,7 @@ 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); } } else { @@ -112,16 +115,13 @@ 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; } - 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) { @@ -153,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; @@ -179,11 +180,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); @@ -198,6 +199,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; } @@ -254,11 +270,16 @@ 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); + 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); @@ -333,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: @@ -348,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; } @@ -411,3 +436,58 @@ 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; + + 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); + + 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; + + 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); + + 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 2c8da3c6b..1555385ea 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, @@ -104,6 +104,8 @@ struct GBVideo { int32_t nextMode; int32_t dotCounter; + int32_t nextFrame; + uint8_t* vram; uint8_t* vramBank; int vramCurrentBank; @@ -136,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..8fb6d9942 100644 --- a/src/gba/audio.c +++ b/src/gba/audio.c @@ -256,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; @@ -332,48 +333,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 +346,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/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: diff --git a/src/gba/core.c b/src/gba/core.c index a2aec489f..4d394c0ca 100644 --- a/src/gba/core.c +++ b/src/gba/core.c @@ -7,11 +7,13 @@ #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" #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" @@ -190,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; @@ -243,12 +250,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) { @@ -422,6 +435,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; @@ -445,12 +493,14 @@ 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; core->runFrame = _GBACoreRunFrame; core->runLoop = _GBACoreRunLoop; core->step = _GBACoreStep; + core->stateSize = _GBACoreStateSize; core->loadState = _GBACoreLoadState; core->saveState = _GBACoreSaveState; core->setKeys = _GBACoreSetKeys; @@ -482,5 +532,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/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/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/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/gba/memory.c b/src/gba/memory.c index a731726ca..69bce0162 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) { @@ -392,7 +394,9 @@ 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 { \ mLOG(GBA_MEM, GAME_ERROR, "Out of bounds ROM Load32: 0x%08X", address); \ value = ((address & ~3) >> 1) & 0xFFFF; \ @@ -525,6 +529,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; @@ -538,6 +544,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; @@ -623,6 +631,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; @@ -883,7 +893,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/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 }, diff --git a/src/gba/renderers/tile-cache.c b/src/gba/renderers/tile-cache.c new file mode 100644 index 000000000..fd3a1ef0e --- /dev/null +++ b/src/gba/renderers/tile-cache.c @@ -0,0 +1,176 @@ +/* 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); + 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) { + if (GBAVideoTileCacheConfigurationIsShouldStore(cache->config)) { + 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) { + size_t i; + for (i = 0; i > 16; ++i) { + cache->status[address >> 5][i].vramClean = 0; + } +} + +void GBAVideoTileCacheWritePalette(struct GBAVideoTileCache* cache, uint32_t address) { + ++cache->globalPaletteVersion[address >> 5]; + ++cache->globalPalette256Version[address >> 9]; +} + +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]; + 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, uint16_t* tile, unsigned tileId, unsigned paletteId) { + uint32_t* start = (uint32_t*) &cache->vram[tileId << 5]; + paletteId <<= 8; + uint16_t* palette = &cache->palette[paletteId * 16]; + 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; + } +} + +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][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 tile; +} + +const uint16_t* GBAVideoTileCacheGetTile16IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned 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 tile; + } + return NULL; +} + + +const uint16_t* GBAVideoTileCacheGetTile256(struct GBAVideoTileCache* cache, unsigned tileId, unsigned 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 tile; +} + +const uint16_t* GBAVideoTileCacheGetTile256IfDirty(struct GBAVideoTileCache* cache, unsigned tileId, unsigned 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 tile; + } + return NULL; +} diff --git a/src/gba/renderers/tile-cache.h b/src/gba/renderers/tile-cache.h new file mode 100644 index 000000000..2a0d30aad --- /dev/null +++ b/src/gba/renderers/tile-cache.h @@ -0,0 +1,44 @@ +/* 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 GBA_TILE_CACHE_H +#define GBA_TILE_CACHE_H + +#include "util/common.h" + +struct GBAVideo; + +DECL_BITFIELD(GBAVideoTileCacheConfiguration, uint32_t); +DECL_BIT(GBAVideoTileCacheConfiguration, ShouldStore, 0); + +struct GBAVideoTileCache { + uint16_t* cache; + struct GBAVideoTileCacheEntry { + uint32_t paletteVersion; + uint8_t vramClean; + uint8_t palette256; + } 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); +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 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/rr/rr.c b/src/gba/rr/rr.c index 88f733859..24edae7c3 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"); @@ -29,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); @@ -53,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/savedata.c b/src/gba/savedata.c index 6a83ff1ce..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) { @@ -125,6 +129,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); diff --git a/src/gba/serialize.c b/src/gba/serialize.c index f5717e1b5..469b616a7 100644 --- a/src/gba/serialize.c +++ b/src/gba/serialize.c @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "serialize.h" -#include "core/sync.h" +#include "core/serialize.h" #include "gba/audio.h" #include "gba/cheats.h" #include "gba/io.h" @@ -22,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; @@ -35,13 +29,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) { @@ -118,7 +106,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); @@ -220,395 +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 GBAExtdata* 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 GBAExtdata* extdata = bundle->extdata; - if (!extdata) { - return 0; - } - struct GBAExtdataItem 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; - GBAExtdataPut(extdata, tag, &item); - return 1; - } - return 0; -} - -static struct GBASerializedState* _loadPNGState(struct VFile* vf, struct GBAExtdata* 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 GBAExtdataItem item = { - .size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4, - .data = pixels, - .clean = free - }; - GBAExtdataPut(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 GBAExtdata extdata; - GBAExtdataInit(&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 = { - .size = svf->seek(svf, 0, SEEK_CUR), - .data = sram, - .clean = free - }; - GBAExtdataPut(&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 GBAExtdataItem item = { - .size = cheatVf->size(cheatVf), - .data = cheatVf->map(cheatVf, cheatVf->size(cheatVf), MAP_READ), - .clean = 0 - }; - GBAExtdataPut(&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) { - GBAExtdataDeinit(&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); - GBAExtdataSerialize(&extdata, vf); - GBAExtdataDeinit(&extdata); - if (cheatVf) { - cheatVf->close(cheatVf); - } - return true; -#ifdef USE_PNG - } - else { - bool success = _savePNGState(gba, vf, &extdata); - GBAExtdataDeinit(&extdata); - return success; - } -#endif - GBAExtdataDeinit(&extdata); - return false; -} - -struct GBASerializedState* GBAExtractState(struct VFile* vf, struct GBAExtdata* 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) { - GBAExtdataDeserialize(extdata, vf); - } - return state; -} - -bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf, int flags) { - struct GBAExtdata extdata; - GBAExtdataInit(&extdata); - struct GBASerializedState* state = GBAExtractState(vf, &extdata); - if (!state) { - return false; - } - bool success = GBADeserialize(gba, state); - GBADeallocateState(state); - - struct GBAExtdataItem item; - if (flags & SAVESTATE_SCREENSHOT && GBAExtdataGet(&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 && GBAExtdataGet(&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 (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); - } - } - } - GBAExtdataDeinit(&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 225a77fbf..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; @@ -70,21 +71,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 @@ -202,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_BIT(GBASerializedAudioFlags, Ch1Dead, 4); -DECL_BIT(GBASerializedAudioFlags, Ch1Hi, 5); -DECL_BIT(GBASerializedAudioFlags, Ch1SweepEnabled, 6); -DECL_BIT(GBASerializedAudioFlags, Ch1SweepOccurred, 7); -DECL_BITS(GBASerializedAudioFlags, Ch2Volume, 8, 4); -DECL_BIT(GBASerializedAudioFlags, Ch2Dead, 12); -DECL_BIT(GBASerializedAudioFlags, Ch2Hi, 13); -DECL_BITS(GBASerializedAudioFlags, Ch4Volume, 16, 4); -DECL_BIT(GBASerializedAudioFlags, Ch4Dead, 20); -DECL_BITS(GBASerializedAudioFlags, Frame, 21, 3); - -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); @@ -259,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 { @@ -358,44 +321,11 @@ 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); 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 GBASerializedState* GBAAllocateState(void); void GBADeallocateState(struct GBASerializedState* state); 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 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/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 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); 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; } diff --git a/src/platform/3ds/CMakeLists.txt b/src/platform/3ds/CMakeLists.txt index 8af378a25..942a8517c 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) @@ -30,10 +30,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 @@ -41,11 +43,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}) @@ -58,29 +63,41 @@ 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) 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..14446b79d 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,407 +14,182 @@ #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 int ctrNumVerts = 0; +static int ctrVertStart = 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); + 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); - return 0; + 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_shader: - shaderProgramFree(&gpuShader); - -error_dvlb: - if (passthroughShader != NULL) { - DVLB_Free(passthroughShader); - passthroughShader = NULL; - } - -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; } - - 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); - - activeTexture = 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 (activeTexture) { 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++; + activeTexture = texture; + C3D_TexBind(0, activeTexture); - vtx->x = x + w; vtx->y = y; - vtx->u = u + uw; vtx->v = v; - vtx->abgr = color; - vtx++; + 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); + } - vtx->x = x; vtx->y = y + h; - vtx->u = u; vtx->v = v + vh; - vtx->abgr = color; - vtx++; + 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); +} - vtx->x = x + w; vtx->y = y + h; - vtx->u = u + uw; vtx->v = v + vh; +void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh) { + if (ctrNumVerts + ctrVertStart == MAX_NUM_QUADS) { + ctrFlushBatch(); + C3D_Flush(); + ctrNumVerts = 0; + ctrVertStart = 0; + } + + struct ctrUIVertex* vtx = &ctrVertexBuffer[ctrVertStart + ctrNumVerts]; + 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; + ++ctrNumVerts; } 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) { - if (ctrNumQuads == 0) { + if (ctrNumVerts == 0) { return; } - ctrClearPending((1 << GSPGPU_EVENT_PSC0)); + C3D_BufInfo* bufInfo = C3D_GetBufInfo(); + BufInfo_Init(bufInfo); + BufInfo_Add(bufInfo, &ctrVertexBuffer[ctrVertStart], sizeof(struct ctrUIVertex), 3, 0x210); - GSPGPU_FlushDataCache(ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE); - GPU_DrawElements(GPU_GEOMETRY_PRIM, (u32*)(osConvertVirtToPhys(ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6); + GSPGPU_FlushDataCache(&ctrVertexBuffer[ctrVertStart], sizeof(struct ctrUIVertex) * ctrNumVerts); + C3D_DrawArrays(GPU_GEOMETRY_PRIM, 0, ctrNumVerts); - GPU_FinishDrawing(); - GPUCMD_Finalize(); - GSPGPU_FlushDataCache((u8*)gpuCommandList, COMMAND_LIST_LENGTH * sizeof(u32)); - GPUCMD_FlushAndRun(); - - gspWaitForP3D(); - - GPUCMD_SetBufferOffset(0); - - ctrNumQuads = 0; + 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 e49c8cecc..5f4cb6d3b 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,36 +9,17 @@ #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); +void ctrFinalize(void); #endif diff --git a/src/platform/3ds/font.raw b/src/platform/3ds/font.raw deleted file mode 100644 index 05b1436df..000000000 Binary files a/src/platform/3ds/font.raw and /dev/null differ diff --git a/src/platform/3ds/gui-font.c b/src/platform/3ds/gui-font.c index 7bec4ab44..f044ce35c 100644 --- a/src/platform/3ds/gui-font.c +++ b/src/platform/3ds/gui-font.c @@ -7,42 +7,43 @@ #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 +#define FONT_SIZE 0.52f struct GUIFont { - struct ctrTexture texture; - struct ctrTexture icons; + 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; - struct ctrTexture* tex = &guiFont->texture; - ctrTexture_Init(tex); - tex->data = vramAlloc(256 * 128 * 2); - tex->format = GPU_RGBA5551; - tex->width = 256; - tex->height = 128; + 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; - 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,22 +53,24 @@ struct GUIFont* GUIFontCreate(void) { } void GUIFontDestroy(struct GUIFont* font) { - vramFree(font->texture.data); - vramFree(font->icons.data); + 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) { @@ -90,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 e67668e5f..131a6c64c 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,16 @@ 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(); + 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(); + gspWaitForEvent(GSPGPU_EVENT_VBlank0, false); } static int _batteryState(void) { @@ -237,20 +201,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 +283,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 +318,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 +377,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 +399,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 +423,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 +432,6 @@ static void _drawScreenshot(struct mGUIRunner* runner, const uint32_t* pixels, b gspWaitForPPF(); linearFree(newPixels); - ctrActivateTexture(tex); _drawTex(runner->core, faded); } @@ -479,9 +447,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 +606,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 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/openemu/mGBAGameCore.m b/src/platform/openemu/mGBAGameCore.m index 9a2b0bb35..91ef7b892 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" @@ -195,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; @@ -211,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; @@ -223,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/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; diff --git a/src/platform/psp2/CMakeLists.txt b/src/platform/psp2/CMakeLists.txt index b374b61a3..20f1f2239 100644 --- a/src/platform/psp2/CMakeLists.txt +++ b/src/platform/psp2/CMakeLists.txt @@ -1,39 +1,34 @@ 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 -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) 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) @@ -44,7 +39,29 @@ 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) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2) +add_custom_target(sce_sys ${CMAKE_COMMAND} -E make_directory sce_sys) + +add_custom_target(param.sfo + ${MAKE_SFO} ${PROJECT_NAME} -s TITLE_ID=MGBA00001 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(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 livearea eboot.bin param.sfo) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.vpk DESTINATION . COMPONENT ${BINARY_NAME}-psp2) diff --git a/src/platform/psp2/bg.png b/src/platform/psp2/bg.png new file mode 100644 index 000000000..a9e687d99 Binary files /dev/null and b/src/platform/psp2/bg.png differ diff --git a/src/platform/psp2/gui-font.c b/src/platform/psp2/gui-font.c index aa1e6cdc1..73c6a0ac7 100644 --- a/src/platform/psp2/gui-font.c +++ b/src/platform/psp2/gui-font.c @@ -5,18 +5,18 @@ * 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 GLYPH_HEIGHT 24 +#define FONT_SIZE 1.25f -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 +25,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") + 9; } 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 +71,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) { diff --git a/src/platform/psp2/icon0.png b/src/platform/psp2/icon0.png new file mode 100644 index 000000000..e48a324c9 Binary files /dev/null and b/src/platform/psp2/icon0.png differ diff --git a/src/platform/psp2/main.c b/src/platform/psp2/main.c index 5855ce041..f757889d2 100644 --- a/src/platform/psp2/main.c +++ b/src/platform/psp2/main.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -98,7 +99,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, @@ -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; diff --git a/src/platform/psp2/startup.png b/src/platform/psp2/startup.png new file mode 100644 index 000000000..eec32d19c Binary files /dev/null and b/src/platform/psp2/startup.png differ 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 + + 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; diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index 0701046e7..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) @@ -167,15 +170,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/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*); 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 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: 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 b724ea09c..8bda543de 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 @@ -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) { @@ -288,14 +291,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(); } @@ -358,6 +359,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(); @@ -392,6 +395,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; @@ -406,7 +426,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(); @@ -785,7 +810,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); @@ -821,7 +846,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/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/LoadSaveState.cpp b/src/platform/qt/LoadSaveState.cpp index 85f63f63f..4c6786a12 100644 --- a/src/platform/qt/LoadSaveState.cpp +++ b/src/platform/qt/LoadSaveState.cpp @@ -15,8 +15,11 @@ #include extern "C" { +#include "core/serialize.h" +#ifdef M_CORE_GBA #include "gba/serialize.h" -#include "gba/video.h" +#endif +#include "util/memory.h" } using namespace QGBA; @@ -40,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); }); } @@ -176,22 +182,24 @@ void LoadSaveState::loadState(int slot) { return; } - GBAExtdata extdata; - GBAExtdataInit(&extdata); - GBASerializedState* state = GBAExtractState(vf, &extdata); + mStateExtdata extdata; + mStateExtdataInit(&extdata); + void* state = mCoreExtractState(thread->core, 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)); + QDateTime creation/*(QDateTime::fromMSecsSinceEpoch(state->creationUsec / 1000LL))*/; // TODO QImage stateImage; - GBAExtdataItem item; - if (GBAExtdataGet(&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(); + unsigned width, height; + thread->core->desiredVideoDimensions(thread->core, &width, &height); + mStateExtdataItem item; + 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()) { @@ -207,7 +215,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/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/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/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 new file mode 100644 index 000000000..e78dd95c1 --- /dev/null +++ b/src/platform/qt/TilePainter.cpp @@ -0,0 +1,54 @@ +/* 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); + resize(256, 768); +} + +void TilePainter::paintEvent(QPaintEvent* event) { + QPainter painter(this); + 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 * (width() / 8) + x); +} + +void TilePainter::setTile(int index, const uint16_t* data) { + QPainter painter(&m_backing); + 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 new file mode 100644 index 000000000..8786aa206 --- /dev/null +++ b/src/platform/qt/TilePainter.h @@ -0,0 +1,39 @@ +/* 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*); + void setTileCount(int tiles); + +signals: + void indexPressed(int index); + +protected: + void paintEvent(QPaintEvent*) override; + void mousePressEvent(QMouseEvent*) override; + void resizeEvent(QResizeEvent*) 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..8d74b8de0 --- /dev/null +++ b/src/platform/qt/TileView.cpp @@ -0,0 +1,135 @@ +/* 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 +#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)); + 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*)), &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))); + connect(m_ui.palette256, SIGNAL(toggled(bool)), this, SLOT(updateTiles())); +} + +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); +} + +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(bool force) { + 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->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->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)); + } + } + } +} + +void TileView::updatePalette(int palette) { + m_paletteId = palette; + updateTiles(true); +} + +void TileView::resizeEvent(QResizeEvent*) { + updateTiles(true); +} + +void TileView::showEvent(QShowEvent*) { + m_updateTimer.start(); +} diff --git a/src/platform/qt/TileView.h b/src/platform/qt/TileView.h new file mode 100644 index 000000000..28fc4b8a8 --- /dev/null +++ b/src/platform/qt/TileView.h @@ -0,0 +1,50 @@ +/* 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(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; + + GameController* m_controller; + GBAVideoTileCache m_tileCache; + int m_paletteId; + QTimer m_updateTimer; +}; + +} + +#endif diff --git a/src/platform/qt/TileView.ui b/src/platform/qt/TileView.ui new file mode 100644 index 000000000..87aa22833 --- /dev/null +++ b/src/platform/qt/TileView.ui @@ -0,0 +1,223 @@ + + + TileView + + + + 0 + 0 + 498 + 335 + + + + Tiles + + + + + + 256 colors + + + + + + + + 0 + 0 + + + + + 170 + 192 + + + + + + + + + + + 87 + 87 + + + + + + + + + + Tile # + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + Address + + + + + + + 0x06000000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + 170 + 16777215 + + + + 15 + + + 1 + + + Qt::Horizontal + + + QSlider::TicksBelow + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + true + + + + + 0 + 0 + 271 + 768 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 256 + 768 + + + + + + + + + + + + + QGBA::TilePainter + QWidget +
TilePainter.h
+ 1 +
+ + QGBA::Swatch + QWidget +
Swatch.h
+ 1 +
+
+ + + + palette256 + toggled(bool) + paletteId + setDisabled(bool) + + + 100 + 54 + + + 96 + 22 + + + + +
diff --git a/src/platform/qt/Window.cpp b/src/platform/qt/Window.cpp index 93a23b6ea..388dd4d20 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" @@ -182,6 +183,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) { @@ -271,11 +281,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 +298,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,13 +314,38 @@ 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); } } +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; @@ -333,11 +366,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()); } } @@ -397,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); @@ -811,6 +850,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"); @@ -835,7 +880,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); @@ -843,7 +887,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")); @@ -855,14 +898,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(); @@ -873,7 +914,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); @@ -881,7 +921,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(); @@ -894,7 +933,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); @@ -902,7 +940,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)); } @@ -1181,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); @@ -1197,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); @@ -1257,6 +1296,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 96674f096..309fdb784 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(); @@ -81,6 +82,7 @@ public slots: void openCheatsWindow(); void openPaletteWindow(); + void openTileWindow(); void openMemoryWindow(); void openIOViewer(); @@ -145,6 +147,8 @@ private: void updateTitle(float fps = -1); + QString getFilters() const; + GameController* m_controller; Display* m_display; // TODO: Move these to a new class 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/main.c b/src/platform/sdl/main.c index d66f85668..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); @@ -146,6 +149,7 @@ int main(int argc, char** argv) { freeArguments(&args); mCoreConfigFreeOpts(&opts); mCoreConfigDeinit(&renderer.core->config); + renderer.core->deinit(renderer.core); return ret; } @@ -184,6 +188,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; 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" 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(); diff --git a/src/platform/test/fuzz-main.c b/src/platform/test/fuzz-main.c index 432ff7d9d..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); @@ -102,7 +105,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..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 @@ -93,6 +88,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; @@ -162,7 +160,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); 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)) 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 diff --git a/src/util/string.c b/src/util/string.c index a2a23dea0..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; @@ -202,8 +201,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; diff --git a/src/util/vfs/vfs-fd.c b/src/util/vfs/vfs-fd.c index 59aa0706d..5b96f58b5 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 @@ -51,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; @@ -159,8 +166,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 } 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 {