Merge branch 'master' into feature/dynarec

This commit is contained in:
Jeffrey Pfau 2016-07-30 11:59:05 -07:00
commit af27f79c1c
117 changed files with 3971 additions and 1551 deletions

88
CHANGES
View File

@ -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:

View File

@ -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})

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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))

View File

@ -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))))

View File

@ -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);

View File

@ -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

436
src/core/serialize.c Normal file
View File

@ -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;
}

47
src/core/serialize.h Normal file
View File

@ -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

View File

@ -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;

View File

@ -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*));

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);

View File

@ -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

View File

@ -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]);
}

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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) {

172
src/gb/serialize.c Normal file
View File

@ -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;
}

353
src/gb/serialize.h Normal file
View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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:

View File

@ -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;
}

View File

@ -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"

View File

@ -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;
}

View File

@ -12,7 +12,7 @@
mLOG_DEFINE_CATEGORY(GBA_IO, "GBA I/O");
const char* GBAIORegisterNames[] = {
const char* const GBAIORegisterNames[] = {
// Video
"DISPCNT",
0,

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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 },

View File

@ -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;
}

View File

@ -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

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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));
}

View File

@ -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);

298
src/gba/vfame.c Normal file
View File

@ -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;
}

34
src/gba/vfame.h Normal file
View File

@ -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

View File

@ -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) {

View File

@ -170,6 +170,7 @@ struct GBAVideoRenderer {
uint16_t* palette;
uint16_t* vram;
union GBAOAM* oam;
struct GBAVideoTileCache* cache;
bool disableBG[4];
bool disableOBJ;

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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.

View File

@ -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) {

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);
}

View File

@ -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) {

View File

@ -62,6 +62,7 @@ struct mGLES2Shader {
GLuint vertexShader;
GLuint program;
GLuint texLocation;
GLuint texSizeLocation;
GLuint positionLocation;
struct mGLES2Uniform* uniforms;

View File

@ -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)

BIN
src/platform/psp2/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@ -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) {

BIN
src/platform/psp2/icon0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -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})

View File

@ -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

View File

@ -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;

View File

@ -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*);

View File

@ -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

View File

@ -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

View File

@ -47,6 +47,7 @@ private slots:
#ifdef BUILD_SDL
void setAxisValue(int axis, int32_t value);
void selectGamepad(int index);
void updateJoysticks();
#endif
private:

View File

@ -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()));
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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) {

View File

@ -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>

View File

@ -14,7 +14,8 @@
#include "ShortcutView.h"
extern "C" {
#include "gba/serialize.h"
#include "core/serialize.h"
#include "gba/gba.h"
}
using namespace QGBA;

View File

@ -18,7 +18,6 @@ Swatch::Swatch(QWidget* parent)
: QWidget(parent)
{
m_size = 10;
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
}
void Swatch::setSize(int size) {

View File

@ -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