Merge branch 'master' into qt

Conflicts:
	CMakeLists.txt
This commit is contained in:
Jeffrey Pfau 2014-08-12 23:37:29 -07:00
commit 2b3631dc91
38 changed files with 1749 additions and 353 deletions

View File

@ -1,10 +1,11 @@
cmake_minimum_required(VERSION 2.6)
project(GBAc)
set(BINARY_NAME gbac CACHE INTERNAL "Name of output binaries")
set(CMAKE_C_FLAGS_DEBUG "-g -Wall -Wextra -std=c99")
set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra -std=c99")
project(mGBA)
set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries")
set(CMAKE_C_FLAGS_DEBUG "-g -Wall -Wextra -std=gnu99")
set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra -std=gnu99")
set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger")
set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger")
set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
set(BUILD_PERF ON CACHE BOOL "Build performance profiling tool")
@ -13,7 +14,7 @@ file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cS])
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)
set(UTIL_SRC ${UTIL_SRC};${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
source_group("ARM core" FILES ${ARM_SRC})
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC})
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
@ -21,6 +22,29 @@ include_directories(${CMAKE_SOURCE_DIR}/src/arm)
include_directories(${CMAKE_SOURCE_DIR}/src/gba)
include_directories(${CMAKE_SOURCE_DIR}/src)
set(LIB_VERSION_MAJOR 0)
set(LIB_VERSION_MINOR 1)
set(LIB_VERSION_PATCH 0)
set(LIB_VERSION_ABI 0.1)
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
set(BUILD_PGO CACHE BOOL "Build with profiling-guided optimization")
set(PGO_STAGE_2 CACHE BOOL "Rebuild for profiling-guided optimization after profiles have been generated")
set(PGO_DIR "/tmp/gba-pgo/" CACHE PATH "Profiling-guided optimization profiles path")
mark_as_advanced(BUILD_PGO PGO_STAGE_2 PGO_DIR)
set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR}")
set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR}")
if(BUILD_PGO AND NOT PGO_STAGE_2)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${PGO_PRE_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PGO_PRE_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PGO_PRE_FLAGS}")
elseif(BUILD_PGO AND PGO_STAGE_2)
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${PGO_POST_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PGO_POST_FLAGS}")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PGO_POST_FLAGS}")
endif()
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}")
include(FindPkgConfig)
@ -35,12 +59,12 @@ endif()
if(WIN32)
add_definitions(-D_WIN32_WINNT=0x0600)
set(OS_LIB "${OS_LIB};Ws2_32")
list(APPEND OS_LIB Ws2_32)
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
source_group("Windows-specific code" FILES ${OS_SRC})
else()
add_definitions(-DUSE_PTHREADS)
set(OS_LIB "${OS_LIB};pthread")
list(APPEND OS_LIB pthread)
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
source_group("POSIX-specific code" FILES ${OS_SRC})
endif()
@ -52,26 +76,46 @@ if(BUILD_BBB OR BUILD_RASPI)
endif()
endif()
set(DEBUGGER_SRC "${CMAKE_SOURCE_DIR}/src/debugger/debugger.c;${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c")
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
if(USE_CLI_DEBUGGER AND NOT WIN32)
# Win32 doesn't have a usable command line, nor libedit, so this is useless on Windows
add_definitions(-DUSE_CLI_DEBUGGER)
list(APPEND DEBUGGER_SRC "${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c")
list(APPEND DEBUGGER_SRC "${CMAKE_SOURCE_DIR}/src/debugger/parser.c")
set(DEBUGGER_LIB "edit")
if(USE_CLI_DEBUGGER)
pkg_search_module(EDIT libedit)
if(EDIT_FOUND)
add_definitions(-DUSE_CLI_DEBUGGER)
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
set(DEBUGGER_LIB ${EDIT_LIBRARIES})
else()
message(WARNING "Could not find libedit for CLI debugger support")
set(USE_CLI_DEBUGGER OFF)
endif()
else()
set(DEBUGGER_LIB "")
endif()
if(USE_GDB_STUB)
add_definitions(-DUSE_GDB_STUB)
set(DEBUGGER_SRC "${DEBUGGER_SRC};${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c")
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c)
endif()
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
if(USE_FFMPEG)
pkg_search_module(LIBAVCODEC libavcodec REQUIRED)
pkg_search_module(LIBAVFORMAT libavformat REQUIRED)
pkg_search_module(LIBAVUTIL libavutil REQUIRED)
add_definitions(-DUSE_FFMPEG)
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVUTIL_LIBRARIES})
endif()
find_package(PNG REQUIRED)
find_package(ZLIB REQUIRED)
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
add_library(${BINARY_NAME} SHARED ${ARM_SRC} ${GBA_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} ${UTIL_SRC} ${VFS_SRC} ${OS_SRC})
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
install(TARGETS ${BINARY_NAME} DESTINATION lib)
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
if(BUILD_SDL)
add_definitions(-DBUILD_SDL)
@ -81,11 +125,12 @@ endif()
if(BUILD_PERF)
set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/perf-main.c)
if(UNIX AND NOT APPLE)
set(PERF_LIB "${PERF_LIB};rt")
list(APPEND PERF_LIB rt)
endif()
add_executable(${BINARY_NAME}-perf ${PERF_SRC})
target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB})
install(TARGETS ${BINARY_NAME}-perf DESTINATION bin)
endif()
if(BUILD_QT)

View File

@ -103,7 +103,9 @@
info->affectsCPSR = S; \
SHIFTER; \
if (SKIPPED == 1) { \
info->operandFormat &= ~ARM_OPERAND_1; \
info->operandFormat >>= 8; \
info->op1 = info->op2; \
info->op2 = info->op3; \
} else if (SKIPPED == 2) { \
info->operandFormat &= ~ARM_OPERAND_2; \
} \

View File

@ -332,6 +332,10 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i
written = _decodeRegister(info->op3.reg, buffer, blen);
ADVANCE(written);
}
if (info->operandFormat & ARM_OPERAND_4) {
strncpy(buffer, ", ", blen - 1);
ADVANCE(2);
}
if (info->operandFormat & ARM_OPERAND_IMMEDIATE_4) {
written = snprintf(buffer, blen - 1, "#%i", info->op4.immediate);
ADVANCE(written);

View File

@ -71,7 +71,7 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
}
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
if (samples >= GBA_AUDIO_SAMPLES) {
if (samples > GBA_AUDIO_SAMPLES) {
return;
}
@ -704,6 +704,10 @@ static void _sample(struct GBAAudio* audio) {
CircleBufferWrite32(&audio->left, sampleLeft);
CircleBufferWrite32(&audio->right, sampleRight);
unsigned produced = CircleBufferSize(&audio->left);
struct GBAThread* thread = GBAThreadGetContext();
if (thread && thread->stream) {
thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight);
}
GBASyncProduceAudio(audio->p->sync, produced >= CircleBufferCapacity(&audio->left) / sizeof(int32_t) * 3);
}

View File

@ -76,7 +76,7 @@ static void _ObjAffineSet(struct GBA* gba) {
sx = cpu->memory.load16(cpu, offset, 0) / 256.f;
sy = cpu->memory.load16(cpu, offset + 2, 0) / 256.f;
theta = (cpu->memory.loadU16(cpu, offset + 4, 0) >> 8) / 128.f * M_PI;
offset += 6;
offset += 8;
// Rotation
a = d = cosf(theta);
b = c = sinf(theta);

85
src/gba/gba-input.c Normal file
View File

@ -0,0 +1,85 @@
#include "gba-input.h"
struct GBAInputMapImpl {
int* map;
uint32_t type;
};
void GBAInputMapInit(struct GBAInputMap* map) {
map->maps = 0;
map->numMaps = 0;
}
void GBAInputMapDeinit(struct GBAInputMap* map) {
size_t m;
for (m = 0; m < map->numMaps; ++m) {
free(map->maps[m].map);
}
free(map->maps);
map->maps = 0;
map->numMaps = 0;
}
enum GBAKey GBAInputMapKey(struct GBAInputMap* map, uint32_t type, int key) {
size_t m;
struct GBAInputMapImpl* impl = 0;
for (m = 0; m < map->numMaps; ++m) {
if (map->maps[m].type == type) {
impl = &map->maps[m];
break;
}
}
if (!impl || !impl->map) {
return GBA_KEY_NONE;
}
for (m = 0; m < GBA_KEY_MAX; ++m) {
if (impl->map[m] == key) {
return m;
}
}
return GBA_KEY_NONE;
}
void GBAInputBindKey(struct GBAInputMap* map, uint32_t type, int key, enum GBAKey input) {
struct GBAInputMapImpl* impl = 0;
if (map->numMaps == 0) {
map->maps = malloc(sizeof(*map->maps));
map->numMaps = 1;
impl = &map->maps[0];
impl->type = type;
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
} else {
size_t m;
for (m = 0; m < map->numMaps; ++m) {
if (map->maps[m].type == type) {
impl = &map->maps[m];
break;
}
}
}
if (!impl) {
size_t m;
for (m = 0; m < map->numMaps; ++m) {
if (!map->maps[m].type) {
impl = &map->maps[m];
break;
}
}
if (impl) {
impl->type = type;
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
} else {
map->maps = realloc(map->maps, sizeof(*map->maps) * map->numMaps * 2);
for (m = map->numMaps * 2 - 1; m > map->numMaps; --m) {
map->maps[m].type = 0;
map->maps[m].map = 0;
}
map->numMaps *= 2;
impl = &map->maps[m];
impl->type = type;
impl->map = calloc(GBA_KEY_MAX, sizeof(enum GBAKey));
}
}
impl->map[input] = key;
}

17
src/gba/gba-input.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef GBA_INPUT_H
#define GBA_INPUT_H
#include "gba.h"
struct GBAInputMap {
struct GBAInputMapImpl* maps;
size_t numMaps;
};
void GBAInputMapInit(struct GBAInputMap*);
void GBAInputMapDeinit(struct GBAInputMap*);
enum GBAKey GBAInputMapKey(struct GBAInputMap*, uint32_t type, int key);
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
#endif

View File

@ -109,6 +109,10 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
struct GBA* gba = (struct GBA*) cpu->master;
struct GBAMemory* memory = &gba->memory;
if (cpu->currentPC == gba->busyLoop) {
GBAHalt(gba);
}
int newRegion = address >> BASE_OFFSET;
if (newRegion == memory->activeRegion) {
return;

View File

@ -1,9 +1,31 @@
#include "gba-rr.h"
#include "gba.h"
#include "gba-serialize.h"
#include "util/vfs.h"
#define FILE_INPUTS "input.log"
#define BINARY_EXT ".dat"
#define BINARY_MAGIC "GBAb"
#define METADATA_FILENAME "metadata" BINARY_EXT
enum {
INVALID_INPUT = 0x8000
};
static bool _emitMagic(struct GBARRContext* rr, struct VFile* vf);
static bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf);
static enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf);
static bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag);
static bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag);
static bool _emitEnd(struct GBARRContext* rr, struct VFile* vf);
static bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf);
static bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive);
static void _streamEndReached(struct GBARRContext* rr);
static struct VFile* _openSavedata(struct GBARRContext* rr, int flags);
static struct VFile* _openSavestate(struct GBARRContext* rr, int flags);
void GBARRContextCreate(struct GBA* gba) {
if (gba->rr) {
@ -18,17 +40,175 @@ void GBARRContextDestroy(struct GBA* gba) {
return;
}
if (GBARRIsPlaying(gba->rr)) {
GBARRStopPlaying(gba->rr);
}
if (GBARRIsRecording(gba->rr)) {
GBARRStopRecording(gba->rr);
}
if (gba->rr->metadataFile) {
gba->rr->metadataFile->close(gba->rr->metadataFile);
}
if (gba->rr->savedata) {
gba->rr->savedata->close(gba->rr->savedata);
}
free(gba->rr);
gba->rr = 0;
}
bool GBARRSetStream(struct GBARRContext* rr, struct VDir* stream) {
if (rr->inputsStream && !rr->inputsStream->close(rr->inputsStream)) {
void GBARRSaveState(struct GBA* gba) {
if (!gba || !gba->rr) {
return;
}
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
if (gba->rr->savedata) {
gba->rr->savedata->close(gba->rr->savedata);
}
gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
gba->rr->savedata->close(gba->rr->savedata);
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
} else {
GBASavedataMask(&gba->memory.savedata, 0);
}
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
GBASaveStateNamed(gba, vf, false);
vf->close(vf);
} else {
ARMReset(gba->cpu);
}
}
void GBARRLoadState(struct GBA* gba) {
if (!gba || !gba->rr) {
return;
}
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
if (gba->rr->savedata) {
gba->rr->savedata->close(gba->rr->savedata);
}
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
} else {
GBASavedataMask(&gba->memory.savedata, 0);
}
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
struct VFile* vf = _openSavestate(gba->rr, O_RDONLY);
GBALoadStateNamed(gba, vf);
vf->close(vf);
} else {
ARMReset(gba->cpu);
}
}
bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
return false;
}
if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
return false;
}
rr->streamDir = stream;
rr->inputsStream = stream->openFile(stream, FILE_INPUTS, O_CREAT | O_RDWR);
return !!rr->inputsStream;
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
rr->currentInput = INVALID_INPUT;
if (!_parseMetadata(rr, rr->metadataFile)) {
rr->metadataFile->close(rr->metadataFile);
rr->metadataFile = 0;
rr->maxStreamId = 0;
}
rr->streamId = 1;
rr->movieStream = 0;
return true;
}
bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
if (!rr) {
return false;
}
if (rr->metadataFile) {
rr->metadataFile->truncate(rr->metadataFile, 0);
} else {
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
}
_emitMagic(rr, rr->metadataFile);
rr->initFrom = initFrom;
rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
_emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
rr->streamId = 0;
rr->maxStreamId = 0;
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
rr->rrCount = 0;
_emitTag(rr, rr->metadataFile, TAG_RR_COUNT);
rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
return true;
}
bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
return false;
}
rr->movieStream = 0;
rr->streamId = streamId;
rr->currentInput = INVALID_INPUT;
char buffer[14];
snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
if (GBARRIsRecording(rr)) {
int flags = O_CREAT | O_RDWR;
if (streamId > rr->maxStreamId) {
flags |= O_TRUNC;
}
rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
} else if (GBARRIsPlaying(rr)) {
rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
rr->peekedTag = TAG_INVALID;
if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
GBARRStopPlaying(rr);
}
}
GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
rr->frames = 0;
rr->lagFrames = 0;
return true;
}
bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) {
uint32_t newStreamId = rr->maxStreamId + 1;
uint32_t oldStreamId = rr->streamId;
if (GBARRIsRecording(rr) && rr->movieStream) {
if (!_markStreamNext(rr, newStreamId, recursive)) {
return false;
}
}
if (!GBARRLoadStream(rr, newStreamId)) {
return false;
}
GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
_emitMagic(rr, rr->movieStream);
rr->maxStreamId = newStreamId;
_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
_emitTag(rr, rr->movieStream, TAG_BEGIN);
rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
rr->previously = oldStreamId;
return true;
}
bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
@ -36,19 +216,24 @@ bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
return false;
}
rr->autorecord = autorecord;
if (rr->inputsStream->seek(rr->inputsStream, 0, SEEK_SET) < 0) {
return false;
}
if (rr->inputsStream->read(rr->inputsStream, &rr->nextInput, sizeof(rr->nextInput)) != sizeof(rr->nextInput)) {
return false;
}
rr->isPlaying = true;
if (!GBARRLoadStream(rr, 1)) {
rr->isPlaying = false;
return false;
}
rr->autorecord = autorecord;
return true;
}
void GBARRStopPlaying(struct GBARRContext* rr) {
if (!GBARRIsPlaying(rr)) {
return;
}
rr->isPlaying = false;
if (rr->movieStream) {
rr->movieStream->close(rr->movieStream);
rr->movieStream = 0;
}
}
bool GBARRStartRecording(struct GBARRContext* rr) {
@ -56,12 +241,26 @@ bool GBARRStartRecording(struct GBARRContext* rr) {
return false;
}
if (!rr->maxStreamIdOffset) {
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
}
rr->isRecording = true;
return true;
return GBARRIncrementStream(rr, false);
}
void GBARRStopRecording(struct GBARRContext* rr) {
if (!GBARRIsRecording(rr)) {
return;
}
rr->isRecording = false;
if (rr->movieStream) {
_emitEnd(rr, rr->movieStream);
rr->movieStream->close(rr->movieStream);
rr->movieStream = 0;
}
}
bool GBARRIsPlaying(struct GBARRContext* rr) {
@ -73,16 +272,41 @@ bool GBARRIsRecording(struct GBARRContext* rr) {
}
void GBARRNextFrame(struct GBARRContext* rr) {
if (!GBARRIsRecording(rr)) {
if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
return;
}
++rr->frames;
if (!rr->inputThisFrame) {
++rr->lagFrames;
if (GBARRIsPlaying(rr)) {
while (rr->peekedTag == TAG_INPUT) {
_readTag(rr, rr->movieStream);
GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
}
if (rr->peekedTag == TAG_LAG) {
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
if (rr->inputThisFrame) {
GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
}
}
}
rr->inputThisFrame = false;
++rr->frames;
GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames);
if (!rr->inputThisFrame) {
++rr->lagFrames;
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames);
}
if (GBARRIsRecording(rr)) {
if (!rr->inputThisFrame) {
_emitTag(rr, rr->movieStream, TAG_LAG);
}
_emitTag(rr, rr->movieStream, TAG_FRAME);
rr->inputThisFrame = false;
} else {
if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
_streamEndReached(rr);
}
}
}
void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
@ -90,7 +314,12 @@ void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
return;
}
rr->inputsStream->write(rr->inputsStream, &keys, sizeof(keys));
if (keys != rr->currentInput) {
_emitTag(rr, rr->movieStream, TAG_INPUT);
rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
rr->currentInput = keys;
}
GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput);
rr->inputThisFrame = true;
}
@ -99,10 +328,239 @@ uint16_t GBARRQueryInput(struct GBARRContext* rr) {
return 0;
}
uint16_t keys = rr->nextInput;
rr->isPlaying = rr->inputsStream->read(rr->inputsStream, &rr->nextInput, sizeof(rr->nextInput)) == sizeof(rr->nextInput);
if (!rr->isPlaying && rr->autorecord) {
rr->isRecording = true;
if (rr->peekedTag == TAG_INPUT) {
_readTag(rr, rr->movieStream);
}
return keys;
rr->inputThisFrame = true;
if (rr->currentInput == INVALID_INPUT) {
GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
}
GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput);
return rr->currentInput;
}
bool GBARRFinishSegment(struct GBARRContext* rr) {
if (rr->movieStream) {
if (!_emitEnd(rr, rr->movieStream)) {
return false;
}
}
return GBARRIncrementStream(rr, false);
}
bool GBARRSkipSegment(struct GBARRContext* rr) {
rr->nextTime = 0;
while (_readTag(rr, rr->movieStream) != TAG_EOF);
if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
_streamEndReached(rr);
return false;
}
return true;
}
bool GBARRMarkRerecord(struct GBARRContext* rr) {
++rr->rrCount;
rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET);
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
return true;
}
bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
UNUSED(rr);
return vf->write(vf, BINARY_MAGIC, 4) == 4;
}
bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
UNUSED(rr);
char buffer[4];
if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
return false;
}
if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
return false;
}
return true;
}
enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
if (!rr || !vf) {
return TAG_EOF;
}
enum GBARRTag tag = rr->peekedTag;
switch (tag) {
case TAG_INPUT:
vf->read(vf, &rr->currentInput, sizeof(uint16_t));
break;
case TAG_PREVIOUSLY:
vf->read(vf, &rr->previously, sizeof(rr->previously));
break;
case TAG_NEXT_TIME:
vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
break;
case TAG_MAX_STREAM:
vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
break;
case TAG_FRAME_COUNT:
vf->read(vf, &rr->frames, sizeof(rr->frames));
break;
case TAG_LAG_COUNT:
vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
break;
case TAG_RR_COUNT:
vf->read(vf, &rr->rrCount, sizeof(rr->rrCount));
break;
case TAG_INIT_EX_NIHILO:
rr->initFrom = INIT_EX_NIHILO;
break;
case TAG_INIT_FROM_SAVEGAME:
rr->initFrom = INIT_FROM_SAVEGAME;
break;
case TAG_INIT_FROM_SAVESTATE:
rr->initFrom = INIT_FROM_SAVESTATE;
case TAG_INIT_FROM_BOTH:
rr->initFrom = INIT_FROM_BOTH;
break;
// To be spec'd
case TAG_AUTHOR:
case TAG_COMMENT:
break;
// Empty markers
case TAG_FRAME:
case TAG_LAG:
case TAG_BEGIN:
case TAG_END:
case TAG_INVALID:
case TAG_EOF:
break;
}
uint8_t tagBuffer;
if (vf->read(vf, &tagBuffer, 1) != 1) {
rr->peekedTag = TAG_EOF;
} else {
rr->peekedTag = tagBuffer;
}
if (rr->peekedTag == TAG_END) {
GBARRSkipSegment(rr);
}
return tag;
}
bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
enum GBARRTag readTag;
while ((readTag = _readTag(rr, vf)) != tag) {
if (readTag == TAG_EOF) {
return false;
}
}
return true;
}
bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
UNUSED(rr);
return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
}
bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
if (!_verifyMagic(rr, vf)) {
return false;
}
while (_readTag(rr, vf) != TAG_EOF) {
switch (rr->peekedTag) {
case TAG_MAX_STREAM:
rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
break;
case TAG_INIT_EX_NIHILO:
case TAG_INIT_FROM_SAVEGAME:
case TAG_INIT_FROM_SAVESTATE:
case TAG_INIT_FROM_BOTH:
rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
break;
case TAG_RR_COUNT:
rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
break;
default:
break;
}
}
return true;
}
bool _emitEnd(struct GBARRContext* rr, struct VFile* vf) {
// TODO: Error check
_emitTag(rr, vf, TAG_END);
_emitTag(rr, vf, TAG_FRAME_COUNT);
vf->write(vf, &rr->frames, sizeof(rr->frames));
_emitTag(rr, vf, TAG_LAG_COUNT);
vf->write(vf, &rr->lagFrames, sizeof(rr->lagFrames));
_emitTag(rr, vf, TAG_NEXT_TIME);
uint32_t newStreamId = 0;
vf->write(vf, &newStreamId, sizeof(newStreamId));
return true;
}
bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive) {
if (rr->movieStream->seek(rr->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
return false;
}
uint8_t tagBuffer;
if (rr->movieStream->read(rr->movieStream, &tagBuffer, 1) != 1) {
return false;
}
if (tagBuffer != TAG_NEXT_TIME) {
return false;
}
if (rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
return false;
}
if (recursive) {
if (rr->movieStream->seek(rr->movieStream, 0, SEEK_SET) < 0) {
return false;
}
if (!_verifyMagic(rr, rr->movieStream)) {
return false;
}
_readTag(rr, rr->movieStream);
if (_readTag(rr, rr->movieStream) != TAG_PREVIOUSLY) {
return false;
}
if (rr->previously == 0) {
return true;
}
uint32_t currentStreamId = rr->streamId;
if (!GBARRLoadStream(rr, rr->previously)) {
return false;
}
return _markStreamNext(rr, currentStreamId, rr->previously);
}
return true;
}
void _streamEndReached(struct GBARRContext* rr) {
if (!GBARRIsPlaying(rr)) {
return;
}
uint32_t endStreamId = rr->streamId;
GBARRStopPlaying(rr);
if (rr->autorecord) {
rr->isRecording = true;
GBARRLoadStream(rr, endStreamId);
GBARRIncrementStream(rr, false);
}
}
struct VFile* _openSavedata(struct GBARRContext* rr, int flags) {
return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags);
}
struct VFile* _openSavestate(struct GBARRContext* rr, int flags) {
return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags);
}

View File

@ -7,11 +7,48 @@ struct GBA;
struct VDir;
struct VFile;
enum GBARRInitFrom {
INIT_EX_NIHILO = 0,
INIT_FROM_SAVEGAME = 1,
INIT_FROM_SAVESTATE = 2,
INIT_FROM_BOTH = 3,
};
enum GBARRTag {
// Playback tags
TAG_INVALID = 0x00,
TAG_INPUT = 0x01,
TAG_FRAME = 0x02,
TAG_LAG = 0x03,
// Stream chunking tags
TAG_BEGIN = 0x10,
TAG_END = 0x11,
TAG_PREVIOUSLY = 0x12,
TAG_NEXT_TIME = 0x13,
TAG_MAX_STREAM = 0x14,
// Recording information tags
TAG_FRAME_COUNT = 0x20,
TAG_LAG_COUNT = 0x21,
TAG_RR_COUNT = 0x22,
TAG_INIT = 0x24,
TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO,
TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME,
TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE,
TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH,
// User metadata tags
TAG_AUTHOR = 0x30,
TAG_COMMENT = 0x31,
TAG_EOF = INT_MAX
};
struct GBARRContext {
// Playback state
bool isPlaying;
bool autorecord;
uint16_t nextInput;
// Recording state
bool isRecording;
@ -20,16 +57,41 @@ struct GBARRContext {
// Metadata
uint32_t frames;
uint32_t lagFrames;
uint32_t streamId;
uint32_t maxStreamId;
off_t maxStreamIdOffset;
enum GBARRInitFrom initFrom;
off_t initFromOffset;
uint32_t rrCount;
off_t rrCountOffset;
struct VFile* savedata;
// Streaming state
struct VDir* streamDir;
struct VFile* inputsStream;
struct VFile* metadataFile;
struct VFile* movieStream;
uint16_t currentInput;
enum GBARRTag peekedTag;
uint32_t nextTime;
uint32_t previously;
};
void GBARRContextCreate(struct GBA*);
void GBARRContextDestroy(struct GBA*);
void GBARRSaveState(struct GBA*);
void GBARRLoadState(struct GBA*);
bool GBARRSetStream(struct GBARRContext*, struct VDir*);
bool GBARRInitStream(struct GBARRContext*, struct VDir*);
bool GBARRReinitStream(struct GBARRContext*, enum GBARRInitFrom);
bool GBARRLoadStream(struct GBARRContext*, uint32_t streamId);
bool GBARRIncrementStream(struct GBARRContext*, bool recursive);
bool GBARRFinishSegment(struct GBARRContext*);
bool GBARRSkipSegment(struct GBARRContext*);
bool GBARRMarkRerecord(struct GBARRContext*);
bool GBARRStartPlaying(struct GBARRContext*, bool autorecord);
void GBARRStopPlaying(struct GBARRContext*);

View File

@ -18,6 +18,8 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
savedata->command = EEPROM_COMMAND_NULL;
savedata->flashState = FLASH_STATE_RAW;
savedata->vf = vf;
savedata->realVf = vf;
savedata->mapMode = MAP_WRITE;
}
void GBASavedataDeinit(struct GBASavedata* savedata) {
@ -57,9 +59,48 @@ void GBASavedataDeinit(struct GBASavedata* savedata) {
break;
}
}
savedata->data = 0;
savedata->type = SAVEDATA_NONE;
}
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf) {
GBASavedataDeinit(savedata);
savedata->vf = vf;
savedata->mapMode = MAP_READ;
}
void GBASavedataUnmask(struct GBASavedata* savedata) {
GBASavedataDeinit(savedata);
savedata->vf = savedata->realVf;
savedata->mapMode = MAP_WRITE;
}
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
if (savedata->data) {
switch (savedata->type) {
case SAVEDATA_SRAM:
return out->write(out, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
case SAVEDATA_FLASH512:
return out->write(out, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
case SAVEDATA_FLASH1M:
return out->write(out, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
case SAVEDATA_EEPROM:
return out->write(out, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
case SAVEDATA_NONE:
return true;
}
} else if (savedata->vf) {
off_t read = 0;
uint8_t buffer[2048];
do {
read = savedata->vf->read(savedata->vf, buffer, sizeof(buffer));
out->write(out, buffer, read);
} while (read == sizeof(buffer));
return read >= 0;
}
return true;
}
void GBASavedataInitFlash(struct GBASavedata* savedata) {
if (savedata->type == SAVEDATA_NONE) {
savedata->type = SAVEDATA_FLASH512;
@ -77,7 +118,7 @@ void GBASavedataInitFlash(struct GBASavedata* savedata) {
if (end < SIZE_CART_FLASH512) {
savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
}
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, MAP_WRITE);
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode);
}
savedata->currentBank = savedata->data;
@ -102,7 +143,7 @@ void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
if (end < SIZE_CART_EEPROM) {
savedata->vf->truncate(savedata->vf, SIZE_CART_EEPROM);
}
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_EEPROM, MAP_WRITE);
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_EEPROM, savedata->mapMode);
}
if (end < SIZE_CART_EEPROM) {
memset(&savedata->data[end], 0xFF, SIZE_CART_EEPROM - end);
@ -125,7 +166,7 @@ void GBASavedataInitSRAM(struct GBASavedata* savedata) {
if (end < SIZE_CART_SRAM) {
savedata->vf->truncate(savedata->vf, SIZE_CART_SRAM);
}
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_SRAM, MAP_WRITE);
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_SRAM, savedata->mapMode);
}
if (end < SIZE_CART_SRAM) {

View File

@ -58,6 +58,9 @@ struct GBASavedata {
enum SavedataCommand command;
struct VFile* vf;
int mapMode;
struct VFile* realVf;
int readBitsRemaining;
int readAddress;
int writeAddress;
@ -72,6 +75,10 @@ struct GBASavedata {
void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf);
void GBASavedataDeinit(struct GBASavedata* savedata);
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
void GBASavedataUnmask(struct GBASavedata* savedata);
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out);
void GBASavedataInitFlash(struct GBASavedata* savedata);
void GBASavedataInitEEPROM(struct GBASavedata* savedata);
void GBASavedataInitSRAM(struct GBASavedata* savedata);

View File

@ -2,12 +2,17 @@
#include "gba-audio.h"
#include "gba-io.h"
#include "gba-rr.h"
#include "gba-thread.h"
#include "gba-video.h"
#include "util/memory.h"
#include "util/png-io.h"
#include "util/vfs.h"
#include <fcntl.h>
#include <png.h>
#include <zlib.h>
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
@ -31,6 +36,13 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
GBAIOSerialize(gba, state);
GBAVideoSerialize(&gba->video, state);
GBAAudioSerialize(&gba->audio, state);
if (GBARRIsRecording(gba->rr)) {
state->associatedStreamId = gba->rr->streamId;
GBARRFinishSegment(gba->rr);
} else {
state->associatedStreamId = 0;
}
}
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
@ -66,49 +78,138 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
GBAIODeserialize(gba, state);
GBAVideoDeserialize(&gba->video, state);
GBAAudioDeserialize(&gba->audio, state);
if (GBARRIsRecording(gba->rr)) {
if (state->associatedStreamId != gba->rr->streamId) {
GBARRLoadStream(gba->rr, state->associatedStreamId);
GBARRIncrementStream(gba->rr, true);
} else {
GBARRFinishSegment(gba->rr);
}
GBARRMarkRerecord(gba->rr);
} else if (GBARRIsPlaying(gba->rr)) {
GBARRLoadStream(gba->rr, state->associatedStreamId);
GBARRSkipSegment(gba->rr);
}
}
static struct VFile* _getStateVf(struct GBA* gba, int slot) {
static struct VFile* _getStateVf(struct GBA* gba, struct VDir* dir, int slot, bool write) {
char path[PATH_MAX];
path[PATH_MAX - 1] = '\0';
snprintf(path, PATH_MAX - 1, "%s.ss%d", gba->activeFile, slot);
struct VFile* vf = VFileOpen(path, O_CREAT | O_RDWR);
if (vf) {
vf->truncate(vf, sizeof(struct GBASerializedState));
struct VFile* vf;
if (!dir) {
snprintf(path, PATH_MAX - 1, "%s.ss%d", gba->activeFile, slot);
vf = VFileOpen(path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
} else {
snprintf(path, PATH_MAX - 1, "savestate.ss%d", slot);
vf = dir->openFile(dir, path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
}
return vf;
}
bool GBASaveState(struct GBA* gba, int slot) {
struct VFile* vf = _getStateVf(gba, slot);
if (!vf) {
static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
unsigned stride;
void* pixels = 0;
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
if (!pixels) {
return false;
}
struct GBASerializedState* state = GBAMapState(vf);
GBASerialize(gba, state);
GBAUnmapState(vf, state);
vf->close(vf);
struct GBASerializedState* state = GBAAllocateState();
png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
uLongf len = compressBound(sizeof(*state));
void* buffer = malloc(len);
if (state && png && info && buffer) {
GBASerialize(gba, state);
compress(buffer, &len, (const Bytef*) state, sizeof(*state));
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
PNGWriteCustomChunk(png, "gbAs", len, buffer);
}
PNGWriteClose(png, info);
free(buffer);
GBADeallocateState(state);
return state && png && info && buffer;
}
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
if (strcmp((const char*) chunk->name, "gbAs") != 0) {
return 0;
}
struct GBASerializedState state;
uLongf len = sizeof(state);
uncompress((Bytef*) &state, &len, chunk->data, chunk->size);
GBADeserialize(png_get_user_chunk_ptr(png), &state);
return 1;
}
static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
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[VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4];
PNGInstallChunkHandler(png, gba, _loadPNGChunkHandler, "gbAs");
PNGReadHeader(png, info);
PNGReadPixels(png, info, &pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
PNGReadFooter(png, end);
PNGReadClose(png, info, end);
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels);
GBASyncPostFrame(gba->sync);
return true;
}
bool GBALoadState(struct GBA* gba, int slot) {
struct VFile* vf = _getStateVf(gba, slot);
bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot) {
struct VFile* vf = _getStateVf(gba, dir, slot, true);
if (!vf) {
return false;
}
struct GBASerializedState* state = GBAMapState(vf);
GBADeserialize(gba, state);
GBAUnmapState(vf, state);
bool success = GBASaveStateNamed(gba, vf, screenshot);
vf->close(vf);
return success;
}
bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot) {
struct VFile* vf = _getStateVf(gba, dir, slot, false);
if (!vf) {
return false;
}
bool success = GBALoadStateNamed(gba, vf);
vf->close(vf);
return success;
}
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) {
if (!screenshot) {
vf->truncate(vf, sizeof(struct GBASerializedState));
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
if (!state) {
return false;
}
GBASerialize(gba, state);
vf->unmap(vf, state, sizeof(struct GBASerializedState));
} else {
return _savePNGState(gba, vf);
}
return true;
}
struct GBASerializedState* GBAMapState(struct VFile* vf) {
return vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
}
void GBAUnmapState(struct VFile* vf, struct GBASerializedState* state) {
vf->unmap(vf, state, sizeof(struct GBASerializedState));
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
if (!isPNG(vf)) {
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ);
if (!state) {
return false;
}
GBADeserialize(gba, state);
vf->unmap(vf, state, sizeof(struct GBASerializedState));
} else {
return _loadPNGState(gba, vf);
}
return true;
}
struct GBASerializedState* GBAAllocateState(void) {

View File

@ -139,7 +139,9 @@ const uint32_t GBA_SAVESTATE_MAGIC;
* | bit 0: Is read enabled
* | bit 1: Gyroscope sample is edge
* | bits 2 - 15: Reserved
* 0x002C0 - 0x003FF: Reserved (leave zero)
* 0x002C0 - 0x002FF: Reserved (leave zero)
* 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream)
* 0x00304 - 0x003FF: Reserved (leave zero)
* 0x00400 - 0x007FF: I/O memory
* 0x00800 - 0x00BFF: Palette
* 0x00C00 - 0x00FFF: OAM
@ -248,7 +250,11 @@ struct GBASerializedState {
unsigned reserved : 14;
} gpio;
uint32_t reserved[80];
uint32_t reservedGpio[16];
uint32_t associatedStreamId;
uint32_t reserved[63];
uint16_t io[SIZE_IO >> 1];
uint16_t pram[SIZE_PALETTE_RAM >> 1];
@ -258,16 +264,16 @@ struct GBASerializedState {
uint8_t wram[SIZE_WORKING_RAM];
};
struct VFile;
struct VDir;
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state);
bool GBASaveState(struct GBA* gba, int slot);
bool GBALoadState(struct GBA* gba, int slot);
bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot);
bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot);
struct GBASerializedState* GBAMapState(struct VFile* vf);
void GBAUnmapState(struct VFile* vf, struct GBASerializedState* state);
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot);
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf);
struct GBASerializedState* GBAAllocateState(void);
void GBADeallocateState(struct GBASerializedState* state);

View File

@ -7,6 +7,7 @@
#include "debugger/debugger.h"
#include "util/patch.h"
#include "util/png-io.h"
#include "util/vfs.h"
#include <signal.h>
@ -110,7 +111,6 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
pthread_sigmask(SIG_SETMASK, &signals, 0);
#endif
gba.logHandler = threadContext->logHandler;
GBACreate(&gba);
ARMSetComponents(&cpu, &gba.d, numComponents, components);
ARMInit(&cpu);
@ -391,6 +391,8 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
}
free(threadContext->rewindBuffer);
GBAInputMapDeinit(&threadContext->inputMap);
if (threadContext->rom) {
threadContext->rom->close(threadContext->rom);
threadContext->rom = 0;
@ -517,6 +519,18 @@ struct GBAThread* GBAThreadGetContext(void) {
}
#endif
void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
unsigned stride;
void* pixels = 0;
struct VFile* vf = threadContext->stateDir->openFile(threadContext->stateDir, "screenshot.png", O_CREAT | O_WRONLY);
threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
png_structp png = PNGWriteOpen(vf);
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
PNGWriteClose(png, info);
vf->close(vf);
}
void GBASyncPostFrame(struct GBASync* sync) {
if (!sync) {
return;
@ -534,6 +548,10 @@ void GBASyncPostFrame(struct GBASync* sync) {
MutexUnlock(&sync->videoFrameMutex);
struct GBAThread* thread = GBAThreadGetContext();
if (!thread) {
return;
}
if (thread->rewindBuffer) {
--thread->rewindBufferNext;
if (thread->rewindBufferNext <= 0) {
@ -541,6 +559,9 @@ void GBASyncPostFrame(struct GBASync* sync) {
GBARecordFrame(thread);
}
}
if (thread->stream) {
thread->stream->postVideoFrame(thread->stream, thread->renderer);
}
if (thread->frameCallback) {
thread->frameCallback(thread);
}
@ -553,10 +574,12 @@ bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
MutexLock(&sync->videoFrameMutex);
ConditionWake(&sync->videoFrameRequiredCond);
if (!sync->videoFrameOn) {
if (!sync->videoFrameOn && !sync->videoFramePending) {
return false;
}
ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
if (!sync->videoFramePending) {
ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
}
sync->videoFramePending = 0;
sync->videoFrameSkip = frameskip;
return true;

View File

@ -4,12 +4,14 @@
#include "common.h"
#include "gba.h"
#include "gba-input.h"
#include "util/threading.h"
#include "platform/commandline.h"
struct GBAThread;
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
enum ThreadState {
THREAD_INITIALIZED = -1,
@ -37,6 +39,11 @@ struct GBASync {
Mutex audioBufferMutex;
};
struct GBAAVStream {
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
void (*postAudioFrame)(struct GBAAVStream*, int32_t left, int32_t right);
};
struct GBAThread {
// Output
enum ThreadState state;
@ -55,6 +62,10 @@ struct GBAThread {
struct VFile* patch;
const char* fname;
int activeKeys;
struct GBAInputMap inputMap;
struct GBAAVStream* stream;
// Run-time options
int frameskip;
float fpsTarget;
size_t audioBuffers;
@ -66,7 +77,7 @@ struct GBAThread {
Condition stateCond;
enum ThreadState savedState;
GBALogHandler logHandler;
LogHandler logHandler;
int logLevel;
ThreadCallback startCallback;
ThreadCallback cleanCallback;
@ -101,6 +112,8 @@ void GBAThreadTogglePause(struct GBAThread* threadContext);
void GBAThreadPauseFromThread(struct GBAThread* threadContext);
struct GBAThread* GBAThreadGetContext(void);
void GBAThreadTakeScreenshot(struct GBAThread* threadContext);
void GBASyncPostFrame(struct GBASync* sync);
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip);
void GBASyncWaitFrameEnd(struct GBASync* sync);

View File

@ -12,16 +12,22 @@ static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer);
static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer);
static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer);
static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer);
static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
static struct GBAVideoRenderer dummyRenderer = {
.init = GBAVideoDummyRendererInit,
.reset = GBAVideoDummyRendererReset,
.deinit = GBAVideoDummyRendererDeinit,
.writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister,
.writePalette = GBAVideoDummyRendererWritePalette,
.writeOAM = GBAVideoDummyRendererWriteOAM,
.drawScanline = GBAVideoDummyRendererDrawScanline,
.finishFrame = GBAVideoDummyRendererFinishFrame
.finishFrame = GBAVideoDummyRendererFinishFrame,
.getPixels = GBAVideoDummyRendererGetPixels
};
void GBAVideoInit(struct GBAVideo* video) {
@ -191,6 +197,19 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer*
return value;
}
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
UNUSED(renderer);
UNUSED(address);
UNUSED(value);
// Nothing to do
}
static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
UNUSED(renderer);
UNUSED(oam);
// Nothing to do
}
static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
UNUSED(renderer);
UNUSED(y);
@ -202,6 +221,14 @@ static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer)
// Nothing to do
}
static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
UNUSED(renderer);
UNUSED(stride);
UNUSED(pixels);
// Nothing to do
}
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state) {
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
memcpy(state->oam, video->oam.raw, SIZE_OAM);

View File

@ -5,6 +5,12 @@
#include "gba-memory.h"
#ifdef COLOR_16_BIT
#define BYTES_PER_PIXEL 2
#else
#define BYTES_PER_PIXEL 4
#endif
enum {
VIDEO_CYCLES_PER_PIXEL = 4,
@ -178,6 +184,9 @@ struct GBAVideoRenderer {
void (*drawScanline)(struct GBAVideoRenderer* renderer, int y);
void (*finishFrame)(struct GBAVideoRenderer* renderer);
void (*getPixels)(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
void (*putPixels)(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
uint16_t* palette;
uint16_t* vram;
union GBAOAM* oam;

View File

@ -26,72 +26,76 @@ struct GBACartridgeOverride {
const char id[4];
enum SavedataType type;
int gpio;
uint32_t busyLoop;
};
static const struct GBACartridgeOverride _overrides[] = {
// Boktai: The Sun is in Your Hand
{ "U3IE", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR },
{ "U3IP", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR },
{ "U3IE", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
{ "U3IP", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
// Boktai 2: Solar Boy Django
{ "U32E", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR },
{ "U32P", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR },
{ "U32E", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
{ "U32P", SAVEDATA_EEPROM, GPIO_RTC | GPIO_LIGHT_SENSOR, -1 },
// Drill Dozer
{ "V49J", SAVEDATA_SRAM, GPIO_RUMBLE },
{ "V49E", SAVEDATA_SRAM, GPIO_RUMBLE },
{ "V49J", SAVEDATA_SRAM, GPIO_RUMBLE, -1 },
{ "V49E", SAVEDATA_SRAM, GPIO_RUMBLE, -1 },
// Mega Man Battle Network
{ "AREE", SAVEDATA_SRAM, GPIO_NONE, 0x8000338 },
// Pokemon Ruby
{ "AXVJ", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVE", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVP", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVI", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVS", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVD", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVF", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXVJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXVE", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXVP", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXVI", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXVS", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXVD", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXVF", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
// Pokemon Sapphire
{ "AXPJ", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPE", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPP", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPI", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPS", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPD", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPF", SAVEDATA_FLASH1M, GPIO_RTC },
{ "AXPJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXPE", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXPP", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXPI", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXPS", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXPD", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "AXPF", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
// Pokemon Emerald
{ "BPEJ", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPEE", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPEP", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPEI", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPES", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPED", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPEF", SAVEDATA_FLASH1M, GPIO_RTC },
{ "BPEJ", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "BPEE", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "BPEP", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "BPEI", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "BPES", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "BPED", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
{ "BPEF", SAVEDATA_FLASH1M, GPIO_RTC, -1 },
// Pokemon FireRed
{ "BPRJ", SAVEDATA_FLASH1M, GPIO_NONE },
{ "BPRE", SAVEDATA_FLASH1M, GPIO_NONE },
{ "BPRP", SAVEDATA_FLASH1M, GPIO_NONE },
{ "BPRJ", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
{ "BPRE", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
{ "BPRP", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
// Pokemon LeafGreen
{ "BPGJ", SAVEDATA_FLASH1M, GPIO_NONE },
{ "BPGE", SAVEDATA_FLASH1M, GPIO_NONE },
{ "BPGP", SAVEDATA_FLASH1M, GPIO_NONE },
{ "BPGJ", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
{ "BPGE", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
{ "BPGP", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
// RockMan EXE 4.5 - Real Operation
{ "BR4J", SAVEDATA_FLASH512, GPIO_RTC },
{ "BR4J", SAVEDATA_FLASH512, GPIO_RTC, -1 },
// Super Mario Advance 4
{ "AX4J", SAVEDATA_FLASH1M, GPIO_NONE },
{ "AX4E", SAVEDATA_FLASH1M, GPIO_NONE },
{ "AX4P", SAVEDATA_FLASH1M, GPIO_NONE },
{ "AX4J", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
{ "AX4E", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
{ "AX4P", SAVEDATA_FLASH1M, GPIO_NONE, -1 },
// Wario Ware Twisted
{ "RWZJ", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO },
{ "RWZE", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO },
{ "RWZP", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO },
{ "RZWJ", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 },
{ "RZWE", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 },
{ "RZWP", SAVEDATA_SRAM, GPIO_RUMBLE | GPIO_GYRO, -1 },
{ { 0, 0, 0, 0 }, 0, 0 }
{ { 0, 0, 0, 0 }, 0, 0, -1 }
};
static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component);
@ -135,6 +139,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
gba->keySource = 0;
gba->rotationSource = 0;
gba->rumble = 0;
gba->rr = 0;
gba->romVf = 0;
gba->biosVf = 0;
@ -182,6 +187,9 @@ void GBAReset(struct ARMCore* cpu) {
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
struct GBA* gba = (struct GBA*) cpu->master;
if (!GBARRIsPlaying(gba->rr) && !GBARRIsRecording(gba->rr)) {
GBASavedataUnmask(&gba->memory.savedata);
}
GBAMemoryReset(gba);
GBAVideoReset(&gba->video);
GBAAudioReset(&gba->audio);
@ -537,16 +545,15 @@ void GBAHalt(struct GBA* gba) {
}
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
if (!gba) {
struct GBAThread* threadContext = GBAThreadGetContext();
if (threadContext) {
struct GBAThread* threadContext = GBAThreadGetContext();
if (threadContext) {
if (!gba) {
gba = threadContext->gba;
}
}
if (gba && gba->logHandler) {
gba->logHandler(gba, level, format, args);
return;
if (threadContext->logHandler) {
threadContext->logHandler(threadContext, level, format, args);
return;
}
}
if (gba && !(level & gba->logLevel) && level != GBA_LOG_FATAL) {
@ -576,6 +583,7 @@ void GBADebuggerLogShim(struct ARMDebugger* debugger, enum DebuggerLogLevel leve
enum GBALogLevel gbaLevel;
switch (level) {
default: // Avoids compiler warning
case DEBUGGER_LOG_DEBUG:
gbaLevel = GBA_LOG_DEBUG;
break;
@ -626,6 +634,7 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) {
void _checkOverrides(struct GBA* gba, uint32_t id) {
int i;
gba->busyLoop = -1;
for (i = 0; _overrides[i].id[0]; ++i) {
const uint32_t* overrideId = (const uint32_t*) _overrides[i].id;
if (*overrideId == id) {
@ -656,6 +665,8 @@ void _checkOverrides(struct GBA* gba, uint32_t id) {
if (_overrides[i].gpio & GPIO_RUMBLE) {
GBAGPIOInitRumble(&gba->memory.gpio);
}
gba->busyLoop = _overrides[i].busyLoop;
return;
}
}

View File

@ -57,6 +57,7 @@ enum GBAKey {
GBA_KEY_DOWN = 7,
GBA_KEY_R = 8,
GBA_KEY_L = 9,
GBA_KEY_MAX,
GBA_KEY_NONE = -1
};
@ -65,8 +66,6 @@ struct GBARotationSource;
struct Patch;
struct VFile;
typedef void (*GBALogHandler)(struct GBA*, enum GBALogLevel, const char* format, va_list args);
struct GBA {
struct ARMComponent d;
@ -96,6 +95,7 @@ struct GBA {
int springIRQ;
uint32_t biosChecksum;
int* keySource;
uint32_t busyLoop;
struct GBARotationSource* rotationSource;
struct GBARumble* rumble;
struct GBARRContext* rr;
@ -108,7 +108,6 @@ struct GBA {
const char* activeFile;
int logLevel;
GBALogHandler logHandler;
};
struct GBACartridge {

View File

@ -2,49 +2,47 @@
#include "gba-memory.h"
const size_t hleBiosLength = 516;
const uint8_t hleBios[SIZE_BIOS] = {
0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x05, 0x00, 0x00, 0xea,
0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1,
0x1a, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
0x24, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02,
0x30, 0x40, 0x2d, 0xe9, 0x02, 0x40, 0x5e, 0xe5, 0x54, 0x50, 0xa0, 0xe3,
0x04, 0x41, 0x95, 0xe7, 0x00, 0x00, 0x54, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1,
0x14, 0xff, 0x2f, 0x11, 0x30, 0x40, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1,
0x30, 0x40, 0x2d, 0xe9, 0x02, 0x40, 0x5e, 0xe5, 0x7c, 0x50, 0xa0, 0xe3,
0x04, 0x41, 0x95, 0xe7, 0x00, 0x00, 0x54, 0xe3, 0x00, 0x50, 0x4f, 0xe1,
0x20, 0x00, 0x2d, 0xe9, 0x80, 0x50, 0x05, 0xe2, 0x1f, 0x50, 0x85, 0xe3,
0x05, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1,
0x14, 0xff, 0x2f, 0x11, 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3,
0x20, 0x00, 0xbd, 0xe8, 0x05, 0xf0, 0x69, 0xe1, 0x30, 0x40, 0xbd, 0xe8,
0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00,
0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00,
0x94, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3,
0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8,
0x04, 0xf0, 0x5e, 0xe2, 0x01, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3,
0x0c, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0x4f, 0xe1, 0x1f, 0xf0, 0x29, 0xe3,
0x01, 0x43, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3,
0x01, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x03, 0xc4, 0xe5,
0x08, 0x02, 0xc4, 0xe5, 0xb8, 0x30, 0x54, 0xe1, 0x01, 0x30, 0x13, 0xe0,
0x01, 0x30, 0x23, 0x10, 0xb8, 0x30, 0x44, 0x11, 0x08, 0x22, 0xc4, 0xe5,
0xf7, 0xff, 0xff, 0x0a, 0x93, 0xf0, 0x29, 0xe3, 0x05, 0xf0, 0x69, 0xe1,
0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0x4f, 0xe1,
0x1f, 0xf0, 0x29, 0xe3, 0x02, 0x36, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3,
0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8, 0x03, 0x00, 0x51, 0xe1,
0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x16, 0x00, 0x00, 0xea,
0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0xa3, 0x35, 0x81, 0xe0,
0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1, 0xb2, 0x20, 0xc1, 0xb0,
0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3,
0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x03, 0x00, 0x51, 0xe1,
0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba,
0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0, 0x01, 0x00, 0xc0, 0xe3,
0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1, 0xb2, 0x20, 0xd0, 0xb0,
0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba, 0x93, 0xf0, 0x29, 0xe3,
0x05, 0xf0, 0x69, 0xe1, 0x00, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9,
0x00, 0x50, 0x4f, 0xe1, 0x1f, 0xf0, 0x29, 0xe3, 0xf0, 0x07, 0x2d, 0xe9,
0x01, 0x04, 0x12, 0xe3, 0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0,
0x0b, 0x00, 0x00, 0x0a, 0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1,
0x03, 0x50, 0xa0, 0xe1, 0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1,
0x03, 0x80, 0xa0, 0xe1, 0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1,
0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba,
0x03, 0x00, 0x00, 0xea, 0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8,
0xf8, 0x07, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba, 0xf0, 0x07, 0xbd, 0xe8,
0x93, 0xf0, 0x29, 0xe3, 0x05, 0xf0, 0x69, 0xe1, 0x00, 0x80, 0xbd, 0xe8
0x0c, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9,
0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5,
0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x01, 0x00, 0xa0, 0xe3,
0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0x43, 0xa0, 0xe3,
0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3,
0x00, 0x00, 0x00, 0x0a, 0x01, 0x03, 0xc4, 0xe5, 0x08, 0x02, 0xc4, 0xe5,
0xb8, 0x30, 0x54, 0xe1, 0x01, 0x30, 0x13, 0xe0, 0x01, 0x30, 0x23, 0x10,
0xb8, 0x30, 0x44, 0x11, 0x08, 0x22, 0xc4, 0xe5, 0xf7, 0xff, 0xff, 0x0a,
0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x02, 0x36, 0xa0, 0xe1,
0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3,
0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8,
0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba,
0x16, 0x00, 0x00, 0xea, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3,
0xa3, 0x35, 0x81, 0xe0, 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1,
0xb2, 0x20, 0xc1, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea,
0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0,
0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8,
0xfb, 0xff, 0xff, 0xba, 0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0,
0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1,
0xb2, 0x20, 0xd0, 0xb0, 0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba,
0x00, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, 0x01, 0x04, 0x12, 0xe3,
0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0, 0x0b, 0x00, 0x00, 0x0a,
0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1,
0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1,
0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1, 0x02, 0x00, 0x51, 0xe1,
0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x03, 0x00, 0x00, 0xea,
0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8, 0xf8, 0x07, 0xa1, 0xb8,
0xfb, 0xff, 0xff, 0xba, 0xf0, 0x87, 0xbd, 0xe8
};

View File

@ -3,7 +3,6 @@
#include "common.h"
extern const size_t hleBiosLength;
extern const uint8_t hleBios[];
#endif

View File

@ -23,8 +23,18 @@ ldrb r4, [lr, #-2]
mov r5, #swiTable
ldr r4, [r5, r4, lsl #2]
cmp r4, #0
mrs r5, spsr
stmfd sp!, {r5}
and r5, #0x80
orr r5, #0x1F
msr cpsr, r5
stmfd sp!, {lr}
mov lr, pc
bxne r4
ldmfd sp!, {lr}
msr cpsr, #0x93
ldmfd sp!, {r5}
msr spsr, r5
ldmfd sp!, {r4-r5, lr}
movs pc, lr
@ -57,8 +67,6 @@ mov r0, #1
mov r1, #1
IntrWait:
stmfd sp!, {r2-r3, lr}
mrs r5, spsr
msr cpsr, #0x1F
# Pull current interrupts enabled and add the ones we need
mov r4, #0x04000000
# See if we want to return immediately
@ -78,14 +86,10 @@ eorne r3, r1
strneh r3, [r4, #-8]
strb r2, [r4, #0x208]
beq 0b
msr cpsr, #0x93
msr spsr, r5
ldmfd sp!, {r2-r3, pc}
CpuSet:
stmfd sp!, {lr}
mrs r5, spsr
msr cpsr, #0x1F
mov r3, r2, lsl #12
tst r2, #0x01000000
beq 0f
@ -134,15 +138,10 @@ ldrlth r2, [r0], #2
strlth r2, [r1], #2
blt 2b
3:
msr cpsr, #0x93
msr spsr, r5
ldmfd sp!, {pc}
CpuFastSet:
stmfd sp!, {lr}
mrs r5, spsr
msr cpsr, #0x1F
stmfd sp!, {r4-r10}
stmfd sp!, {r4-r10, lr}
tst r2, #0x01000000
mov r3, r2, lsl #12
add r2, r1, r3, lsr #10
@ -168,7 +167,4 @@ ldmltia r0!, {r3-r10}
stmltia r1!, {r3-r10}
blt 0b
2:
ldmfd sp!, {r4-r10}
msr cpsr, #0x93
msr spsr, r5
ldmfd sp!, {pc}
ldmfd sp!, {r4-r10, pc}

View File

@ -29,6 +29,8 @@ static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* render
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
@ -67,6 +69,8 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
}
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
@ -501,6 +505,23 @@ static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* rendere
softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
}
static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
*stride = softwareRenderer->outputBufferStride;
*pixels = softwareRenderer->outputBuffer;
}
static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
uint32_t* colorPixels = pixels;
unsigned i;
for (i = 0; i < VIDEO_VERTICAL_PIXELS; ++i) {
memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
}
}
static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
@ -767,10 +788,6 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
#define COMPOSITE_256_NO_OBJWIN(BLEND) \
COMPOSITE_16_NO_OBJWIN(BLEND)
#define COMPOSITE_16_NO_OBJWIN(BLEND) \
_composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current);
#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \
pixelData = tileData & 0xF; \
current = *pixel; \
@ -794,7 +811,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
xBase += (localX & 0x100) << 5; \
} \
screenBase = yBase + (xBase >> 3); \
mapData = renderer->d.vram[screenBase]; \
mapData = vram[screenBase]; \
localY = inY & 0x7; \
if (GBA_TEXT_MAP_VFLIP(mapData)) { \
localY = 7 - localY; \
@ -835,7 +852,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) >> 2) + localY; \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
tileData >>= 4 * mod8; \
for (; outX < end; ++outX) { \
@ -851,7 +868,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) >> 2) + localY; \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
@ -885,7 +902,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
if (!mosaicWait) { \
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
tileData >>= x * 4; \
} else { \
@ -914,7 +931,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
palette = &mainPalette[paletteData]; \
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) >> 2) + localY; \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
if (tileData) { \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
@ -963,7 +980,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
int end2 = end - 4; \
int shift = inX & 0x3; \
if (end2 > 0) { \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
tileData >>= 8 * shift; \
shift = 0; \
for (; outX < end2; ++outX) { \
@ -972,7 +989,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
} \
} \
\
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
tileData = ((uint32_t*) vram)[charBase + 1]; \
tileData >>= 8 * shift; \
for (; outX < end; ++outX) { \
uint32_t* pixel = &renderer->row[outX]; \
@ -985,7 +1002,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
outX = renderer->end - 8 + end; \
int end2 = 4 - end; \
if (end2 > 0) { \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
for (; outX < renderer->end - end2; ++outX) { \
uint32_t* pixel = &renderer->row[outX]; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
@ -993,7 +1010,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
++charBase; \
} \
\
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
for (; outX < renderer->end; ++outX) { \
uint32_t* pixel = &renderer->row[outX]; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
@ -1004,7 +1021,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
BACKGROUND_TEXT_SELECT_CHARACTER; \
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) >> 2) + (localY << 1); \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
if (tileData) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
@ -1017,7 +1034,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
} else { \
pixel += 4; \
} \
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
tileData = ((uint32_t*) vram)[charBase + 1]; \
if (tileData) { \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
++pixel; \
@ -1031,7 +1048,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
pixel += 4; \
} \
} else { \
uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
uint32_t tileData = ((uint32_t*) vram)[charBase + 1]; \
if (tileData) { \
pixel += 3; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
@ -1043,7 +1060,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
} \
pixel += 4; \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
if (tileData) { \
pixel += 3; \
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
@ -1067,18 +1084,18 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
if (!mosaicWait) { \
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
if (x >= 4) { \
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
tileData = ((uint32_t*) vram)[charBase + 1]; \
tileData >>= (x - 4) * 8; \
} else { \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
tileData >>= x * 8; \
} \
} else { \
if (x >= 4) { \
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
tileData = ((uint32_t*) vram)[charBase]; \
tileData >>= (7 - x) * 8; \
} else { \
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
tileData = ((uint32_t*) vram)[charBase + 1]; \
tileData >>= (3 - x) * 8; \
} \
} \
@ -1094,6 +1111,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
}
#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \
uint32_t* pixel = &renderer->row[outX]; \
if (background->mosaic && renderer->mosaic.bgH) { \
int mosaicH = renderer->mosaic.bgH + 1; \
int x; \
int mosaicWait = outX % mosaicH; \
int carryData = 0; \
paletteData = 0; /* Quiets compiler warning */ \
DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
return; \
} \
\
if (inX & 0x7) { \
int mod8 = inX & 0x7; \
BACKGROUND_TEXT_SELECT_CHARACTER; \
@ -1103,6 +1131,9 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
/* TODO: ensure tiles are properly aligned from this*/ \
end = renderer->end; \
} \
if (end == outX) { \
return; \
} \
DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
} \
if (inX & 0x7 || (renderer->end - renderer->start) & 0x7) { \
@ -1119,17 +1150,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
outX = renderer->start + tileX * 8 - (inX & 0x7); \
} \
\
uint32_t* pixel = &renderer->row[outX]; \
if (background->mosaic && renderer->mosaic.bgH) { \
int mosaicH = renderer->mosaic.bgH + 1; \
int x; \
int mosaicWait = outX % mosaicH; \
int carryData = 0; \
paletteData = 0; /* Quiets compiler warning */ \
DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
return; \
} \
\
pixel = &renderer->row[outX]; \
DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN)
static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
@ -1176,6 +1197,7 @@ static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, stru
int paletteData;
int tileX = 0;
int tileEnd = (renderer->end - renderer->start + (inX & 0x7)) >> 3;
uint16_t* vram = renderer->d.vram;
if (!objwinSlowPath) {
if (!(flags & FLAG_TARGET_2)) {

View File

@ -50,7 +50,7 @@ bool parseCommandArgs(struct StartupOptions* opts, int argc, char* const* argv,
"g"
#endif
;
if (subparser->extraOptions) {
if (subparser && subparser->extraOptions) {
// TODO: modularize options to subparsers
strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);
}

View File

@ -0,0 +1,197 @@
#include "ffmpeg-encoder.h"
#include "gba-video.h"
#include <libavutil/imgutils.h>
static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
static void _ffmpegPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right);
void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
av_register_all();
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
FFmpegEncoderSetAudio(encoder, "flac", 0);
FFmpegEncoderSetVideo(encoder, "png", 0);
encoder->currentAudioSample = 0;
encoder->currentAudioFrame = 0;
encoder->currentVideoFrame = 0;
}
bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, unsigned abr) {
if (!avcodec_find_encoder_by_name(acodec)) {
return false;
}
encoder->audioCodec = acodec;
encoder->audioBitrate = abr;
return true;
}
bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, unsigned vbr) {
AVCodec* codec = avcodec_find_encoder_by_name(vcodec);
if (!codec) {
return false;
}
size_t i;
encoder->pixFormat = AV_PIX_FMT_NONE;
for (i = 0; codec->pix_fmts[i] != AV_PIX_FMT_NONE; ++i) {
if (codec->pix_fmts[i] == AV_PIX_FMT_RGB24) {
encoder->pixFormat = AV_PIX_FMT_RGB24;
break;
}
if (codec->pix_fmts[i] == AV_PIX_FMT_BGR0) {
encoder->pixFormat = AV_PIX_FMT_BGR0;
}
}
if (encoder->pixFormat == AV_PIX_FMT_NONE) {
return false;
}
encoder->videoCodec = vcodec;
encoder->videoBitrate = vbr;
return true;
}
bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
if (!acodec || !vcodec) {
return false;
}
avformat_alloc_output_context2(&encoder->context, 0, 0, outfile);
encoder->audioStream = avformat_new_stream(encoder->context, acodec);
encoder->audio = encoder->audioStream->codec;
encoder->audio->bit_rate = encoder->audioBitrate;
encoder->audio->sample_rate = 0x8000;
encoder->audio->channels = 2;
encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO;
encoder->audio->sample_fmt = AV_SAMPLE_FMT_S16;
avcodec_open2(encoder->audio, acodec, 0);
encoder->audioFrame = av_frame_alloc();
encoder->audioFrame->nb_samples = encoder->audio->frame_size;
encoder->audioFrame->format = encoder->audio->sample_fmt;
encoder->audioFrame->pts = 0;
encoder->audioBufferSize = av_samples_get_buffer_size(0, encoder->audio->channels, encoder->audio->frame_size, encoder->audio->sample_fmt, 0);
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
avcodec_fill_audio_frame(encoder->audioFrame, encoder->audio->channels, encoder->audio->sample_fmt, (const uint8_t*) encoder->audioBuffer, encoder->audioBufferSize, 0);
encoder->videoStream = avformat_new_stream(encoder->context, vcodec);
encoder->video = encoder->videoStream->codec;
encoder->video->bit_rate = encoder->videoBitrate;
encoder->video->width = VIDEO_HORIZONTAL_PIXELS;
encoder->video->height = VIDEO_VERTICAL_PIXELS;
encoder->video->time_base = (AVRational) { VIDEO_TOTAL_LENGTH, GBA_ARM7TDMI_FREQUENCY };
encoder->video->pix_fmt = encoder->pixFormat;
encoder->video->gop_size = 15;
encoder->video->max_b_frames = 0;
avcodec_open2(encoder->video, vcodec, 0);
encoder->videoFrame = av_frame_alloc();
encoder->videoFrame->format = encoder->video->pix_fmt;
encoder->videoFrame->width = encoder->video->width;
encoder->videoFrame->height = encoder->video->height;
encoder->videoFrame->pts = 0;
av_image_alloc(encoder->videoFrame->data, encoder->videoFrame->linesize, encoder->video->width, encoder->video->height, encoder->video->pix_fmt, 32);
if (encoder->context->oformat->flags & AVFMT_GLOBALHEADER) {
encoder->audio->flags |= CODEC_FLAG_GLOBAL_HEADER;
encoder->video->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
avio_open(&encoder->context->pb, outfile, AVIO_FLAG_WRITE);
avformat_write_header(encoder->context, 0);
return true;
}
void FFmpegEncoderClose(struct FFmpegEncoder* encoder) {
av_write_trailer(encoder->context);
avio_close(encoder->context->pb);
av_free(encoder->audioBuffer);
av_frame_free(&encoder->audioFrame);
avcodec_close(encoder->audio);
av_frame_free(&encoder->videoFrame);
avcodec_close(encoder->video);
avformat_free_context(encoder->context);
}
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
av_frame_make_writable(encoder->audioFrame);
encoder->audioBuffer[encoder->currentAudioSample * 2] = left;
encoder->audioBuffer[encoder->currentAudioSample * 2 + 1] = right;
encoder->audioFrame->pts = av_rescale_q(encoder->currentAudioFrame, encoder->audio->time_base, encoder->audioStream->time_base);
++encoder->currentAudioFrame;
++encoder->currentAudioSample;
if ((encoder->currentAudioSample * 4) < encoder->audioBufferSize) {
return;
}
encoder->currentAudioSample = 0;
AVPacket packet;
av_init_packet(&packet);
packet.data = 0;
packet.size = 0;
int gotData;
avcodec_encode_audio2(encoder->audio, &packet, encoder->audioFrame, &gotData);
if (gotData) {
packet.stream_index = encoder->audioStream->index;
av_interleaved_write_frame(encoder->context, &packet);
}
av_free_packet(&packet);
}
void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
uint32_t* pixels;
unsigned stride;
renderer->getPixels(renderer, &stride, (void**) &pixels);
AVPacket packet;
av_init_packet(&packet);
packet.data = 0;
packet.size = 0;
av_frame_make_writable(encoder->videoFrame);
encoder->videoFrame->pts = av_rescale_q(encoder->currentVideoFrame, encoder->video->time_base, encoder->videoStream->time_base);
++encoder->currentVideoFrame;
unsigned x, y;
if (encoder->videoFrame->format == AV_PIX_FMT_BGR0) {
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
uint32_t pixel = pixels[stride * y + x];
encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 4] = pixel >> 16;
encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 4 + 1] = pixel >> 8;
encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 4 + 2] = pixel;
}
}
} else if (encoder->videoFrame->format == AV_PIX_FMT_RGB24) {
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
uint32_t pixel = pixels[stride * y + x];
encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 3] = pixel;
encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 3 + 1] = pixel >> 8;
encoder->videoFrame->data[0][y * encoder->videoFrame->linesize[0] + x * 3 + 2] = pixel >> 16;
}
}
}
int gotData;
avcodec_encode_video2(encoder->video, &packet, encoder->videoFrame, &gotData);
if (gotData) {
if (encoder->videoStream->codec->coded_frame->key_frame) {
packet.flags |= AV_PKT_FLAG_KEY;
}
packet.stream_index = encoder->videoStream->index;
av_interleaved_write_frame(encoder->context, &packet);
}
av_free_packet(&packet);
}

View File

@ -0,0 +1,40 @@
#ifndef FFMPEG_ENCODER
#define FFMPEG_ENCODER
#include "gba-thread.h"
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
struct FFmpegEncoder {
struct GBAAVStream d;
AVFormatContext* context;
unsigned audioBitrate;
const char* audioCodec;
unsigned videoBitrate;
const char* videoCodec;
AVCodecContext* audio;
uint16_t* audioBuffer;
size_t audioBufferSize;
AVFrame* audioFrame;
size_t currentAudioSample;
int64_t currentAudioFrame;
AVStream* audioStream;
AVCodecContext* video;
enum AVPixelFormat pixFormat;
AVFrame* videoFrame;
int64_t currentVideoFrame;
AVStream* videoStream;
};
void FFmpegEncoderInit(struct FFmpegEncoder*);
bool FFmpegEncoderSetAudio(struct FFmpegEncoder*, const char* acodec, unsigned abr);
bool FFmpegEncoderSetVideo(struct FFmpegEncoder*, const char* vcodec, unsigned vbr);
bool FFmpegEncoderOpen(struct FFmpegEncoder*, const char* outfile);
void FFmpegEncoderClose(struct FFmpegEncoder*);
#endif

View File

@ -7,18 +7,20 @@
#include <signal.h>
#include <sys/time.h>
#define PERF_OPTIONS "S:"
#define PERF_OPTIONS "NS:"
#define PERF_USAGE \
"\nBenchmark options:\n" \
" -N Disable video rendering entirely" \
" -S SEC Run for SEC in-game seconds before exiting"
struct PerfOpts {
bool noVideo;
int duration;
};
static void _GBAPerfRunloop(struct GBAThread* context, int* frames);
static void _GBAPerfShutdown(int signal);
static int _parsePerfOpts(struct SubParser* parser, int option, const char* arg);
static bool _parsePerfOpts(struct SubParser* parser, int option, const char* arg);
static struct GBAThread* _thread;
@ -28,7 +30,7 @@ int main(int argc, char** argv) {
struct GBAVideoSoftwareRenderer renderer;
GBAVideoSoftwareRendererCreate(&renderer);
struct PerfOpts perfOpts = { 0 };
struct PerfOpts perfOpts = { false, 0 };
struct SubParser subparser = {
.usage = PERF_USAGE,
.parse = _parsePerfOpts,
@ -46,12 +48,15 @@ int main(int argc, char** argv) {
renderer.outputBufferStride = 256;
struct GBAThread context = {
.renderer = &renderer.d,
.sync.videoFrameWait = 0,
.sync.audioWait = 0
};
_thread = &context;
if (!perfOpts.noVideo) {
context.renderer = &renderer.d;
}
context.debugger = createDebugger(&opts);
GBAMapOptionsToContext(&opts, &context);
@ -113,13 +118,16 @@ static void _GBAPerfShutdown(int signal) {
pthread_mutex_unlock(&_thread->stateMutex);
}
static int _parsePerfOpts(struct SubParser* parser, int option, const char* arg) {
static bool _parsePerfOpts(struct SubParser* parser, int option, const char* arg) {
struct PerfOpts* opts = parser->opts;
switch (option) {
case 'N':
opts->noVideo = true;
return true;
case 'S':
opts->duration = strtol(arg, 0, 10);
return !errno;
default:
return 0;
return false;
}
}

View File

@ -21,11 +21,6 @@ GameController::GameController(QObject* parent)
, m_audioThread(new QThread(this))
, m_audioProcessor(new AudioProcessor)
{
#ifdef BUILD_SDL
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
GBASDLInitEvents(&m_sdlEvents);
SDL_JoystickEventState(SDL_QUERY);
#endif
m_renderer = new GBAVideoSoftwareRenderer;
GBAVideoSoftwareRendererCreate(m_renderer);
m_renderer->outputBuffer = (color_t*) m_drawContext;
@ -39,6 +34,16 @@ GameController::GameController(QObject* parent)
.userData = this,
.rewindBufferCapacity = 0
};
GBAInputMapInit(&m_threadContext.inputMap);
#ifdef BUILD_SDL
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
m_sdlEvents.bindings = &m_threadContext.inputMap;
GBASDLInitEvents(&m_sdlEvents);
SDL_JoystickEventState(SDL_QUERY);
#endif
m_threadContext.startCallback = [] (GBAThread* context) {
GameController* controller = static_cast<GameController*>(context->userData);
controller->m_audioProcessor->setInput(context);
@ -204,7 +209,7 @@ void GameController::testSDLEvents() {
m_activeButtons = 0;
int i;
for (i = 0; i < numButtons; ++i) {
GBAKey key = GBASDLMapButtonToKey(i);
GBAKey key = GBAInputMapKey(&m_threadContext.inputMap, SDL_BINDING_BUTTON, i);
if (key == GBA_KEY_NONE) {
continue;
}

View File

@ -17,7 +17,7 @@ if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND)
endif()
file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c)
set(PLATFORM_LIBRARY "${SDL_LIBRARY};${SDLMAIN_LIBRARY}")
set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY})
include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR})
if(BUILD_RASPI)
@ -26,6 +26,7 @@ if(BUILD_RASPI)
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${EGL_MAIN_SRC})
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin)
endif()
if(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL)
@ -39,3 +40,4 @@ endif()
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY})
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin)

View File

@ -68,41 +68,46 @@ static void _GBASDLStart(struct GBAThread* context);
static void _GBASDLClean(struct GBAThread* context);
int main(int argc, char** argv) {
const char* fname = "test.rom";
if (argc > 1) {
fname = argv[1];
}
int fd = open(fname, O_RDONLY);
if (fd < 0) {
struct GBAVideoEGLRenderer renderer;
struct StartupOptions opts;
if (!parseCommandArgs(&opts, argc, argv, 0)) {
usage(argv[0], 0);
freeOptions(&opts);
return 1;
}
struct GBAVideoEGLRenderer renderer;
if (!_GBAEGLInit(&renderer)) {
return 1;
}
GBAVideoSoftwareRendererCreate(&renderer.d);
struct GBAThread context = {
.fd = fd,
.fname = fname,
.biosFd = -1,
.useDebugger = 0,
.renderer = &renderer.d.d,
.frameskip = 0,
.sync.videoFrameWait = 0,
.sync.audioWait = 0,
.sync.audioWait = 1,
.startCallback = _GBASDLStart,
.cleanCallback = _GBASDLClean,
.userData = &renderer
};
context.debugger = createDebugger(&opts);
GBAMapOptionsToContext(&opts, &context);
renderer.audio.samples = context.audioBuffers;
GBASDLInitAudio(&renderer.audio);
renderer.events.bindings = &context.inputMap;
GBASDLInitEvents(&renderer.events);
GBAThreadStart(&context);
_GBAEGLRunloop(&context, &renderer);
GBAThreadJoin(&context);
close(fd);
freeOptions(&opts);
free(context.debugger);
_GBAEGLDeinit(&renderer);
@ -114,9 +119,6 @@ static int _GBAEGLInit(struct GBAVideoEGLRenderer* renderer) {
return 0;
}
GBASDLInitEvents(&renderer->events);
GBASDLInitAudio(&renderer->audio);
bcm_host_init();
renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
int major, minor;
@ -238,7 +240,7 @@ static void _GBAEGLRunloop(struct GBAThread* context, struct GBAVideoEGLRenderer
GBASyncWaitFrameEnd(&context->sync);
while (SDL_PollEvent(&event)) {
GBASDLHandleEvent(context, &event);
GBASDLHandleEvent(context, &renderer->events, &event);
}
}
}
@ -259,6 +261,7 @@ static void _GBAEGLDeinit(struct GBAVideoEGLRenderer* renderer) {
static void _GBASDLStart(struct GBAThread* threadContext) {
struct GBAVideoEGLRenderer* renderer = threadContext->userData;
renderer->audio.audio = &threadContext->gba->audio;
renderer->audio.thread = threadContext;
}
static void _GBASDLClean(struct GBAThread* threadContext) {

View File

@ -100,6 +100,9 @@ int main(int argc, char** argv) {
renderer.audio.samples = context.audioBuffers;
GBASDLInitAudio(&renderer.audio);
renderer.events.bindings = &context.inputMap;
GBASDLInitEvents(&renderer.events);
GBAThreadStart(&context);
_GBASDLRunloop(&context, &renderer);
@ -118,7 +121,6 @@ static int _GBASDLInit(struct GLSoftwareRenderer* renderer) {
return 0;
}
GBASDLInitEvents(&renderer->events);
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

View File

@ -5,6 +5,8 @@
#include "gba-rr.h"
#include "gba-serialize.h"
#include "gba-video.h"
#include "renderers/video-software.h"
#include "util/vfs.h"
#if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__)
#define GUI_MOD KMOD_GUI
@ -21,6 +23,28 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) {
#if !SDL_VERSION_ATLEAST(2, 0, 0)
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
#endif
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_z, GBA_KEY_A);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_x, GBA_KEY_B);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_a, GBA_KEY_L);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_s, GBA_KEY_R);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_RETURN, GBA_KEY_START);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_BACKSPACE, GBA_KEY_SELECT);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_UP, GBA_KEY_UP);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_DOWN, GBA_KEY_DOWN);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_LEFT, GBA_KEY_LEFT);
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_RIGHT, GBA_KEY_RIGHT);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 13, GBA_KEY_A);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 14, GBA_KEY_B);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 10, GBA_KEY_L);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 11, GBA_KEY_R);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 3, GBA_KEY_START);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 0, GBA_KEY_SELECT);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 4, GBA_KEY_UP);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 6, GBA_KEY_DOWN);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 7, GBA_KEY_LEFT);
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 5, GBA_KEY_RIGHT);
return true;
}
@ -29,72 +53,47 @@ void GBASDLDeinitEvents(struct GBASDLEvents* context) {
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
}
enum GBAKey GBASDLMapButtonToKey(int button) {
// Sorry, hardcoded to my gamepad for now
switch (button) {
case 2:
return GBA_KEY_A;
case 1:
return GBA_KEY_B;
case 6:
return GBA_KEY_L;
case 7:
return GBA_KEY_R;
case 8:
return GBA_KEY_START;
case 9:
return GBA_KEY_SELECT;
default:
return GBA_KEY_NONE;
}
}
static void _pauseAfterFrame(struct GBAThread* context) {
context->frameCallback = 0;
GBAThreadPauseFromThread(context);
}
static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_KeyboardEvent* event) {
enum GBAKey key = 0;
enum GBAKey key = GBA_KEY_NONE;
if (!event->keysym.mod) {
key = GBAInputMapKey(&context->inputMap, SDL_BINDING_KEY, event->keysym.sym);
}
if (key != GBA_KEY_NONE) {
if (event->type == SDL_KEYDOWN) {
context->activeKeys |= 1 << key;
} else {
context->activeKeys &= ~(1 << key);
}
return;
}
switch (event->keysym.sym) {
case SDLK_z:
key = GBA_KEY_A;
break;
case SDLK_x:
key = GBA_KEY_B;
break;
case SDLK_a:
key = GBA_KEY_L;
break;
case SDLK_s:
key = GBA_KEY_R;
break;
case SDLK_RETURN:
key = GBA_KEY_START;
break;
case SDLK_BACKSPACE:
key = GBA_KEY_SELECT;
break;
case SDLK_UP:
key = GBA_KEY_UP;
break;
case SDLK_DOWN:
key = GBA_KEY_DOWN;
break;
case SDLK_LEFT:
key = GBA_KEY_LEFT;
break;
case SDLK_RIGHT:
key = GBA_KEY_RIGHT;
break;
case SDLK_F11:
if (event->type == SDL_KEYDOWN && context->debugger) {
ARMDebuggerEnter(context->debugger, DEBUGGER_ENTER_MANUAL);
}
return;
case SDLK_F12:
if (event->type == SDL_KEYDOWN) {
GBAThreadInterrupt(context);
GBAThreadTakeScreenshot(context);
GBAThreadContinue(context);
}
return;
case SDLK_TAB:
context->sync.audioWait = event->type != SDL_KEYDOWN;
return;
case SDLK_BACKSLASH:
if (event->type == SDL_KEYDOWN) {
GBAThreadPause(context);
context->frameCallback = _pauseAfterFrame;
GBAThreadUnpause(context);
}
return;
case SDLK_LEFTBRACKET:
GBAThreadInterrupt(context);
GBARewind(context, 10);
@ -102,13 +101,15 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
return;
case SDLK_ESCAPE:
GBAThreadInterrupt(context);
GBARRStopPlaying(context->gba->rr);
GBARRStopRecording(context->gba->rr);
if (context->gba->rr) {
GBARRStopPlaying(context->gba->rr);
GBARRStopRecording(context->gba->rr);
}
GBAThreadContinue(context);
return;
default:
if (event->type == SDL_KEYDOWN) {
if (event->keysym.mod & GUI_MOD) {
if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
switch (event->keysym.sym) {
#if SDL_VERSION_ATLEAST(2, 0, 0)
case SDLK_f:
@ -129,22 +130,29 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
GBAThreadReset(context);
break;
case SDLK_t:
GBAThreadReset(context);
GBAThreadInterrupt(context);
GBARRContextCreate(context->gba);
GBARRSetStream(context->gba->rr, context->stateDir);
GBARRStopPlaying(context->gba->rr);
GBARRStartRecording(context->gba->rr);
GBAThreadContinue(context);
if (context->stateDir) {
GBAThreadInterrupt(context);
GBARRContextCreate(context->gba);
if (!GBARRIsRecording(context->gba->rr)) {
GBARRStopPlaying(context->gba->rr);
GBARRInitStream(context->gba->rr, context->stateDir);
GBARRReinitStream(context->gba->rr, INIT_EX_NIHILO);
GBARRStartRecording(context->gba->rr);
GBARRSaveState(context->gba);
}
GBAThreadContinue(context);
}
break;
case SDLK_y:
GBAThreadReset(context);
GBAThreadInterrupt(context);
GBARRContextCreate(context->gba);
GBARRSetStream(context->gba->rr, context->stateDir);
GBARRStopRecording(context->gba->rr);
GBARRStartPlaying(context->gba->rr, event->keysym.mod & KMOD_SHIFT);
GBAThreadContinue(context);
if (context->stateDir) {
GBAThreadInterrupt(context);
GBARRContextCreate(context->gba);
GBARRStopRecording(context->gba->rr);
GBARRInitStream(context->gba->rr, context->stateDir);
GBARRStartPlaying(context->gba->rr, false);
GBARRLoadState(context->gba);
GBAThreadContinue(context);
}
break;
default:
break;
@ -163,7 +171,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
case SDLK_F9:
case SDLK_F10:
GBAThreadInterrupt(context);
GBASaveState(context->gba, event->keysym.sym - SDLK_F1);
GBASaveState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1, true);
GBAThreadContinue(context);
break;
default:
@ -182,7 +190,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
case SDLK_F9:
case SDLK_F10:
GBAThreadInterrupt(context);
GBALoadState(context->gba, event->keysym.sym - SDLK_F1);
GBALoadState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1);
GBAThreadContinue(context);
break;
default:
@ -192,17 +200,11 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
}
return;
}
if (event->type == SDL_KEYDOWN) {
context->activeKeys |= 1 << key;
} else {
context->activeKeys &= ~(1 << key);
}
}
static void _GBASDLHandleJoyButton(struct GBAThread* context, const struct SDL_JoyButtonEvent* event) {
enum GBAKey key = 0;
key = GBASDLMapButtonToKey(event->button);
key = GBAInputMapKey(&context->inputMap, SDL_BINDING_BUTTON, event->button);
if (key == GBA_KEY_NONE) {
return;
}

View File

@ -7,7 +7,13 @@
#include <SDL.h>
#define SDL_BINDING_KEY 0x53444C4B
#define SDL_BINDING_BUTTON 0x53444C42
struct GBAVideoSoftwareRenderer;
struct GBASDLEvents {
struct GBAInputMap* bindings;
SDL_Joystick* joystick;
#if SDL_VERSION_ATLEAST(2, 0, 0)
SDL_Window* window;
@ -21,6 +27,4 @@ void GBASDLDeinitEvents(struct GBASDLEvents*);
void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event);
enum GBAKey GBASDLMapButtonToKey(int button);
#endif

View File

@ -99,11 +99,7 @@ int main(int argc, char** argv) {
#endif
SDL_LockTexture(renderer.tex, 0, &renderer.d.outputBuffer, &renderer.d.outputBufferStride);
#ifdef COLOR_16_BIT
renderer.d.outputBufferStride /= 2;
#else
renderer.d.outputBufferStride /= 4;
#endif
renderer.d.outputBufferStride /= BYTES_PER_PIXEL;
#else
SDL_Surface* surface = SDL_GetVideoSurface();
SDL_LockSurface(surface);
@ -117,11 +113,7 @@ int main(int argc, char** argv) {
renderer.d.outputBufferStride = surface->pitch / 4;
#endif
} else {
#ifdef COLOR_16_BIT
renderer.d.outputBuffer = malloc(240 * 160 * 2);
#else
renderer.d.outputBuffer = malloc(240 * 160 * 4);
#endif
renderer.d.outputBuffer = malloc(240 * 160 * BYTES_PER_PIXEL);
renderer.d.outputBufferStride = 240;
}
#endif
@ -173,11 +165,7 @@ static void _GBASDLRunloop(struct GBAThread* context, struct SoftwareRenderer* r
SDL_RenderCopy(renderer->sdlRenderer, renderer->tex, 0, 0);
SDL_RenderPresent(renderer->sdlRenderer);
SDL_LockTexture(renderer->tex, 0, &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
#ifdef COLOR_16_BIT
renderer->d.outputBufferStride /= 2;
#else
renderer->d.outputBufferStride /= 4;
#endif
renderer->d.outputBufferStride /= BYTES_PER_PIXEL;
#else
switch (renderer->ratio) {
#if defined(__ARM_NEON) && COLOR_16_BIT

179
src/util/png-io.c Normal file
View File

@ -0,0 +1,179 @@
#include "util/png-io.h"
#include "vfs.h"
static void _pngWrite(png_structp png, png_bytep buffer, png_size_t size) {
struct VFile* vf = png_get_io_ptr(png);
size_t written = vf->write(vf, buffer, size);
if (written != size) {
png_error(png, "Could not write PNG");
}
}
static void _pngRead(png_structp png, png_bytep buffer, png_size_t size) {
struct VFile* vf = png_get_io_ptr(png);
size_t read = vf->read(vf, buffer, size);
if (read != size) {
png_error(png, "Could not read PNG");
}
}
png_structp PNGWriteOpen(struct VFile* source) {
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png) {
return 0;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_write_struct(&png, 0);
return 0;
}
png_set_write_fn(png, source, _pngWrite, 0);
return png;
}
png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) {
png_infop info = png_create_info_struct(png);
if (!info) {
return 0;
}
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
png_write_info(png, info);
return info;
}
bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, void* pixels) {
png_bytep row = malloc(sizeof(png_bytep) * width * 3);
if (!row) {
return false;
}
png_bytep pixelData = pixels;
if (setjmp(png_jmpbuf(png))) {
free(row);
return false;
}
unsigned i;
for (i = 0; i < height; ++i) {
unsigned x;
for (x = 0; x < width; ++x) {
row[x * 3] = pixelData[stride * i * 4 + x * 4];
row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 1];
row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 2];
}
png_write_row(png, row);
}
free(row);
return true;
}
bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data) {
char realName[5];
strncpy(realName, name, 4);
realName[0] = tolower(realName[0]);
realName[1] = tolower(realName[1]);
realName[4] = '\0';
if (setjmp(png_jmpbuf(png))) {
return false;
}
png_write_chunk(png, (png_const_bytep) realName, data, size);
return true;
}
void PNGWriteClose(png_structp png, png_infop info) {
if (!setjmp(png_jmpbuf(png))) {
png_write_end(png, info);
}
png_destroy_write_struct(&png, &info);
}
bool isPNG(struct VFile* source) {
png_byte header[PNG_HEADER_BYTES];
source->read(source, header, PNG_HEADER_BYTES);
return !png_sig_cmp(header, 0, PNG_HEADER_BYTES);
}
png_structp PNGReadOpen(struct VFile* source, unsigned offset) {
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!png) {
return 0;
}
if (setjmp(png_jmpbuf(png))) {
png_destroy_read_struct(&png, 0, 0);
return 0;
}
png_set_read_fn(png, source, _pngRead);
png_set_sig_bytes(png, offset);
return png;
}
bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler, const char* chunkName) {
if (setjmp(png_jmpbuf(png))) {
return false;
}
png_set_read_user_chunk_fn(png, context, handler);
png_set_keep_unknown_chunks(png, PNG_HANDLE_CHUNK_ALWAYS, (png_const_bytep) chunkName, 1);
return true;
}
bool PNGReadHeader(png_structp png, png_infop info) {
if (setjmp(png_jmpbuf(png))) {
return false;
}
png_read_info(png, info);
return true;
}
bool PNGIgnorePixels(png_structp png, png_infop info) {
if (setjmp(png_jmpbuf(png))) {
return false;
}
unsigned height = png_get_image_height(png, info);
unsigned i;
for (i = 0; i < height; ++i) {
png_read_row(png, 0, 0);
}
return true;
}
bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride) {
if (setjmp(png_jmpbuf(png))) {
return false;
}
uint8_t* pixelData = pixels;
unsigned pngHeight = png_get_image_height(png, info);
if (height > pngHeight) {
height = pngHeight;
}
unsigned pngWidth = png_get_image_width(png, info);
if (width > pngWidth) {
width = pngWidth;
}
unsigned i;
png_bytep row = malloc(png_get_rowbytes(png, info));
for (i = 0; i < height; ++i) {
png_read_row(png, row, 0);
unsigned x;
for (x = 0; x < width; ++x) {
pixelData[stride * i * 4 + x * 4] = row[x * 3];
pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 1];
pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 2];
}
}
free(row);
return true;
}
bool PNGReadFooter(png_structp png, png_infop end) {
if (setjmp(png_jmpbuf(png))) {
return false;
}
png_read_end(png, end);
return true;
}
void PNGReadClose(png_structp png, png_infop info, png_infop end) {
png_destroy_read_struct(&png, &info, &end);
}

31
src/util/png-io.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef PNG_IO_H
#define PNG_IO_H
#include "common.h"
#include <png.h>
struct VFile;
enum {
PNG_HEADER_BYTES = 8
};
png_structp PNGWriteOpen(struct VFile* source);
png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height);
bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, void* pixels);
bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data);
void PNGWriteClose(png_structp png, png_infop info);
typedef int (*ChunkHandler)(png_structp, png_unknown_chunkp);
bool isPNG(struct VFile* source);
png_structp PNGReadOpen(struct VFile* source, unsigned offset);
bool PNGInstallChunkHandler(png_structp png, void* context, ChunkHandler handler, const char* chunkName);
bool PNGReadHeader(png_structp png, png_infop info);
bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width, unsigned height, unsigned stride);
bool PNGIgnorePixels(png_structp png, png_infop info);
bool PNGReadFooter(png_structp png, png_infop end);
void PNGReadClose(png_structp png, png_infop info, png_infop end);
#endif

View File

@ -104,6 +104,8 @@ off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
}
position = vfz->fileSize + offset;
break;
default:
return -1;
}
if (position <= vfz->offset) {