mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/dynarec
This commit is contained in:
commit
af27f79c1c
88
CHANGES
88
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:
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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))))
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <png.h>
|
||||
#include <zlib.h>
|
||||
#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;
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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*));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "gdb-stub.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "gba/memory.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
119
src/gb/audio.c
119
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
110
src/gb/cli.c
110
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
99
src/gb/gb.c
99
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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
88
src/gb/io.c
88
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]);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
112
src/gb/memory.c
112
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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 <time.h>
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#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;
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
106
src/gb/video.c
106
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
mLOG_DEFINE_CATEGORY(GBA_IO, "GBA I/O");
|
||||
|
||||
const char* GBAIORegisterNames[] = {
|
||||
const char* const GBAIORegisterNames[] = {
|
||||
// Video
|
||||
"DISPCNT",
|
||||
0,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <sys/time.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_PNG
|
||||
#include "util/png-io.h"
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
#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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -170,6 +170,7 @@ struct GBAVideoRenderer {
|
|||
uint16_t* palette;
|
||||
uint16_t* vram;
|
||||
union GBAOAM* oam;
|
||||
struct GBAVideoTileCache* cache;
|
||||
|
||||
bool disableBG[4];
|
||||
bool disableOBJ;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 <citro3d.h>
|
||||
|
||||
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
|
||||
|
|
Binary file not shown.
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -62,6 +62,7 @@ struct mGLES2Shader {
|
|||
GLuint vertexShader;
|
||||
GLuint program;
|
||||
GLuint texLocation;
|
||||
GLuint texSizeLocation;
|
||||
GLuint positionLocation;
|
||||
|
||||
struct mGLES2Uniform* uniforms;
|
||||
|
|
|
@ -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)
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 109 KiB |
|
@ -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 <vita2d.h>
|
||||
|
||||
#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) {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
|
@ -18,6 +18,7 @@
|
|||
#include <psp2/kernel/threadmgr.h>
|
||||
#include <psp2/moduleinfo.h>
|
||||
#include <psp2/power.h>
|
||||
#include <psp2/sysmodule.h>
|
||||
#include <psp2/touch.h>
|
||||
|
||||
#include <vita2d.h>
|
||||
|
@ -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;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<livearea style="a4" format-ver="01.00" content-rev="1">
|
||||
<livearea-background>
|
||||
<image>bg.png</image>
|
||||
</livearea-background>
|
||||
|
||||
<gate>
|
||||
<startup-image>startup.png</startup-image>
|
||||
</gate>
|
||||
</livearea>
|
|
@ -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;
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -47,6 +47,7 @@ private slots:
|
|||
#ifdef BUILD_SDL
|
||||
void setAxisValue(int axis, int32_t value);
|
||||
void selectGamepad(int index);
|
||||
void updateJoysticks();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<GameController*>(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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -15,8 +15,11 @@
|
|||
#include <QPainter>
|
||||
|
||||
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) {
|
||||
|
|
|
@ -37,12 +37,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -65,12 +59,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -115,12 +103,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -143,12 +125,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -171,12 +147,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -199,12 +169,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -227,12 +191,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -255,12 +213,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
@ -283,12 +235,6 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>242</width>
|
||||
<height>162</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No Save</string>
|
||||
</property>
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
#include "ShortcutView.h"
|
||||
|
||||
extern "C" {
|
||||
#include "gba/serialize.h"
|
||||
#include "core/serialize.h"
|
||||
#include "gba/gba.h"
|
||||
}
|
||||
|
||||
using namespace QGBA;
|
||||
|
|
|
@ -18,7 +18,6 @@ Swatch::Swatch(QWidget* parent)
|
|||
: QWidget(parent)
|
||||
{
|
||||
m_size = 10;
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
}
|
||||
|
||||
void Swatch::setSize(int size) {
|
||||
|
|
|
@ -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 <QImage>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
|
||||
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<const uchar*>(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);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue