all repos — mgba @ 2b3631dc91ec424429d4dc3259c8b8f81575bf09

mGBA Game Boy Advance Emulator

Merge branch 'master' into qt

Conflicts:
	CMakeLists.txt
Jeffrey Pfau jeffrey@endrift.com
Tue, 12 Aug 2014 23:37:29 -0700
commit

2b3631dc91ec424429d4dc3259c8b8f81575bf09

parent

08fee36c208ecfb8c8298b7ea76d036e4550a769

M CMakeLists.txtCMakeLists.txt

@@ -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 @@ add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)

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

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)
M src/arm/decoder-arm.csrc/arm/decoder-arm.c

@@ -103,7 +103,9 @@ ARM_OPERAND_REGISTER_2; \

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; \ } \
M src/arm/decoder.csrc/arm/decoder.c

@@ -332,6 +332,10 @@ } else if (info->operandFormat & ARM_OPERAND_REGISTER_3) {

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);
M src/gba/gba-audio.csrc/gba/gba-audio.c

@@ -71,7 +71,7 @@ CircleBufferDeinit(&audio->chB.fifo);

} void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) { - if (samples >= GBA_AUDIO_SAMPLES) { + if (samples > GBA_AUDIO_SAMPLES) { return; }

@@ -704,6 +704,10 @@ GBASyncLockAudio(audio->p->sync);

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); }
M src/gba/gba-bios.csrc/gba/gba-bios.c

@@ -76,7 +76,7 @@ // [ 0 sy ] * [ sin(theta) cos(theta) ] = [ C D ]

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);
A src/gba/gba-input.c

@@ -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; +}
A src/gba/gba-input.h

@@ -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
M src/gba/gba-memory.csrc/gba/gba-memory.c

@@ -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;
M src/gba/gba-rr.csrc/gba/gba-rr.c

@@ -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,37 +40,200 @@ if (!gba->rr) {

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 GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) { - if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) { +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; +} - rr->autorecord = autorecord; - if (rr->inputsStream->seek(rr->inputsStream, 0, SEEK_SET) < 0) { +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; } - if (rr->inputsStream->read(rr->inputsStream, &rr->nextInput, sizeof(rr->nextInput)) != sizeof(rr->nextInput)) { + 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) { + if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) { 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 @@ if (GBARRIsRecording(rr) || GBARRIsPlaying(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 @@ return rr && rr->isRecording;

} void GBARRNextFrame(struct GBARRContext* rr) { - if (!GBARRIsRecording(rr)) { + if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) { return; } + 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->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); } - rr->inputThisFrame = false; + 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 @@ if (!GBARRIsRecording(rr)) {

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 @@ if (!GBARRIsPlaying(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) { + if (rr->peekedTag == TAG_INPUT) { + _readTag(rr, rr->movieStream); + } + 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); } - return keys; +} + +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); }
M src/gba/gba-rr.hsrc/gba/gba-rr.h

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

// 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*);
M src/gba/gba-savedata.csrc/gba/gba-savedata.c

@@ -18,6 +18,8 @@ savedata->data = 0;

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

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 @@ end = savedata->vf->seek(savedata->vf, 0, SEEK_END);

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 @@ end = savedata->vf->seek(savedata->vf, 0, SEEK_END);

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 @@ end = savedata->vf->seek(savedata->vf, 0, SEEK_END);

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) {
M src/gba/gba-savedata.hsrc/gba/gba-savedata.h

@@ -58,6 +58,9 @@ uint8_t* data;

enum SavedataCommand command; struct VFile* vf; + int mapMode; + struct VFile* realVf; + int readBitsRemaining; int readAddress; int writeAddress;

@@ -71,6 +74,10 @@ };

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);
M src/gba/gba-serialize.csrc/gba/gba-serialize.c

@@ -2,12 +2,17 @@ #include "gba-serialize.h"

#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 @@ GBAMemorySerialize(&gba->memory, 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 @@ GBAMemoryDeserialize(&gba->memory, 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); +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 = 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 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); - GBASerialize(gba, state); - GBAUnmapState(vf, state); + bool success = GBASaveStateNamed(gba, vf, screenshot); vf->close(vf); - return true; + return success; } -bool GBALoadState(struct GBA* gba, int slot) { - struct VFile* vf = _getStateVf(gba, slot); +bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot) { + struct VFile* vf = _getStateVf(gba, dir, slot, false); if (!vf) { return false; } - struct GBASerializedState* state = GBAMapState(vf); - GBADeserialize(gba, state); - GBAUnmapState(vf, state); + bool success = GBALoadStateNamed(gba, vf); vf->close(vf); - return true; + return success; } -struct GBASerializedState* GBAMapState(struct VFile* vf) { - return vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE); +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; } -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) {
M src/gba/gba-serialize.hsrc/gba/gba-serialize.h

@@ -139,7 +139,9 @@ * | 0x002BE - 0x002BF: Flags

* | 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 @@ unsigned gyroEdge : 1;

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 @@ uint8_t iwram[SIZE_WORKING_IRAM];

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);
M src/gba/gba-thread.csrc/gba/gba-thread.c

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

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

} free(threadContext->rewindBuffer); + GBAInputMapDeinit(&threadContext->inputMap); + if (threadContext->rom) { threadContext->rom->close(threadContext->rom); threadContext->rom = 0;

@@ -517,6 +519,18 @@ return TlsGetValue(_contextKey);

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

MutexUnlock(&sync->videoFrameMutex); struct GBAThread* thread = GBAThreadGetContext(); + if (!thread) { + return; + } + if (thread->rewindBuffer) { --thread->rewindBufferNext; if (thread->rewindBufferNext <= 0) {

@@ -541,6 +559,9 @@ thread->rewindBufferNext = thread->rewindBufferInterval;

GBARecordFrame(thread); } } + if (thread->stream) { + thread->stream->postVideoFrame(thread->stream, thread->renderer); + } if (thread->frameCallback) { thread->frameCallback(thread); }

@@ -553,10 +574,12 @@ }

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;
M src/gba/gba-thread.hsrc/gba/gba-thread.h

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

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 VFile* bios;

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

Condition stateCond; enum ThreadState savedState; - GBALogHandler logHandler; + LogHandler logHandler; int logLevel; ThreadCallback startCallback; ThreadCallback cleanCallback;

@@ -100,6 +111,8 @@ bool GBAThreadIsPaused(struct GBAThread* threadContext);

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);
M src/gba/gba-video.csrc/gba/gba-video.c

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

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

@@ -201,6 +220,14 @@ static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer) {

UNUSED(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);
M src/gba/gba-video.hsrc/gba/gba-video.h

@@ -5,6 +5,12 @@ #include "common.h"

#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,

@@ -177,6 +183,9 @@ void (*writePalette)(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);

void (*writeOAM)(struct GBAVideoRenderer* renderer, uint32_t oam); 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;
M src/gba/gba.csrc/gba/gba.c

@@ -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 @@ gba->springIRQ = 0;

gba->keySource = 0; gba->rotationSource = 0; gba->rumble = 0; + gba->rr = 0; gba->romVf = 0; gba->biosVf = 0;

@@ -182,6 +187,9 @@ ARMSetPrivilegeMode(cpu, MODE_SYSTEM);

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,18 +545,17 @@ gba->cpu->halted = 1;

} 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 (threadContext->logHandler) { + threadContext->logHandler(threadContext, level, format, args); + return; } } - if (gba && gba->logHandler) { - gba->logHandler(gba, level, format, args); - return; - } - if (gba && !(level & gba->logLevel) && level != GBA_LOG_FATAL) { return; }

@@ -576,6 +583,7 @@ }

enum GBALogLevel gbaLevel; switch (level) { + default: // Avoids compiler warning case DEBUGGER_LOG_DEBUG: gbaLevel = GBA_LOG_DEBUG; break;

@@ -626,6 +634,7 @@ }

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

if (_overrides[i].gpio & GPIO_RUMBLE) { GBAGPIOInitRumble(&gba->memory.gpio); } + + gba->busyLoop = _overrides[i].busyLoop; return; } }
M src/gba/gba.hsrc/gba/gba.h

@@ -57,6 +57,7 @@ GBA_KEY_UP = 6,

GBA_KEY_DOWN = 7, GBA_KEY_R = 8, GBA_KEY_L = 9, + GBA_KEY_MAX, GBA_KEY_NONE = -1 };

@@ -64,8 +65,6 @@ struct GBA;

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

int springIRQ; uint32_t biosChecksum; int* keySource; + uint32_t busyLoop; struct GBARotationSource* rotationSource; struct GBARumble* rumble; struct GBARRContext* rr;

@@ -108,7 +108,6 @@

const char* activeFile; int logLevel; - GBALogHandler logHandler; }; struct GBACartridge {
M src/gba/hle-bios.csrc/gba/hle-bios.c

@@ -2,49 +2,47 @@ #include "hle-bios.h"

#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, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, + 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, 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 };
M src/gba/hle-bios.hsrc/gba/hle-bios.h

@@ -3,7 +3,6 @@ #define HLE_BIOS_H

#include "common.h" -extern const size_t hleBiosLength; extern const uint8_t hleBios[]; #endif
M src/gba/hle-bios.ssrc/gba/hle-bios.s

@@ -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}
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -29,6 +29,8 @@ static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);

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 @@ renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;

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 @@ softwareRenderer->bg[3].sx = softwareRenderer->bg[3].refx;

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

#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 @@ if (background->size & 1) { \

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 @@ #define DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_16(BLEND, OBJWIN) \

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

#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 @@ for (x = 0; x < 8; ++x) { \

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

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 @@ charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) >> 2) + (localY << 1); \

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 @@ BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \

} \ } \ \ - 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 @@ charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) >> 2) + (localY << 1); \

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

++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 @@ for (; tileX < tileEnd; ++tileX) { \

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 @@ ++pixel; \

} 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 @@ } else { \

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

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 @@ for (x = 0; x < 8; ++x) { \

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

} #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; \

@@ -1102,6 +1130,9 @@ int end = outX + 0x8 - mod8; \

if (end > renderer->end) { \ /* TODO: ensure tiles are properly aligned from this*/ \ end = renderer->end; \ + } \ + if (end == outX) { \ + return; \ } \ DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \ } \

@@ -1119,17 +1150,7 @@ tileX = (inX & 0x7) != 0; \

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

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)) {
M src/platform/commandline.csrc/platform/commandline.c

@@ -50,7 +50,7 @@ #ifdef USE_GDB_STUB

"g" #endif ; - if (subparser->extraOptions) { + if (subparser && subparser->extraOptions) { // TODO: modularize options to subparsers strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1); }
A src/platform/ffmpeg/ffmpeg-encoder.c

@@ -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); +}
A src/platform/ffmpeg/ffmpeg-encoder.h

@@ -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
M src/platform/perf-main.csrc/platform/perf-main.c

@@ -7,18 +7,20 @@ #include <fcntl.h>

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

struct GBAVideoSoftwareRenderer renderer; GBAVideoSoftwareRendererCreate(&renderer); - struct PerfOpts perfOpts = { 0 }; + struct PerfOpts perfOpts = { false, 0 }; struct SubParser subparser = { .usage = PERF_USAGE, .parse = _parsePerfOpts,

@@ -46,11 +48,14 @@ renderer.outputBuffer = malloc(256 * 256 * 4);

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

@@ -113,13 +118,16 @@ _thread->state = THREAD_EXITING;

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; } }
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -21,11 +21,6 @@ , m_rom(nullptr)

, 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 @@ .renderer = &m_renderer->d,

.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 @@ int numButtons = SDL_JoystickNumButtons(joystick);

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; }
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -17,7 +17,7 @@ find_package(SDL 1.2 REQUIRED)

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 @@ set(EGL_MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/egl-main.c)

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

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)
M src/platform/sdl/egl-main.csrc/platform/sdl/egl-main.c

@@ -68,16 +68,14 @@ 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;

@@ -85,24 +83,31 @@ }

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

@@ -113,9 +118,6 @@ static int _GBAEGLInit(struct GBAVideoEGLRenderer* renderer) {

if (SDL_Init(SDL_INIT_JOYSTICK) < 0) { return 0; } - - GBASDLInitEvents(&renderer->events); - GBASDLInitAudio(&renderer->audio); bcm_host_init(); renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

@@ -238,7 +240,7 @@ }

GBASyncWaitFrameEnd(&context->sync); while (SDL_PollEvent(&event)) { - GBASDLHandleEvent(context, &event); + GBASDLHandleEvent(context, &renderer->events, &event); } } }

@@ -259,6 +261,7 @@

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) {
M src/platform/sdl/gl-main.csrc/platform/sdl/gl-main.c

@@ -100,6 +100,9 @@

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 @@ if (SDL_Init(SDL_INIT_VIDEO) < 0) {

return 0; } - GBASDLInitEvents(&renderer->events); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
M src/platform/sdl/sdl-events.csrc/platform/sdl/sdl-events.c

@@ -5,6 +5,8 @@ #include "gba-io.h"

#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 @@ context->joystick = SDL_JoystickOpen(0);

#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 @@ SDL_JoystickClose(context->joystick);

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

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

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

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

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

} 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; }
M src/platform/sdl/sdl-events.hsrc/platform/sdl/sdl-events.h

@@ -7,7 +7,13 @@ #include "gba-thread.h"

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

@@ -20,7 +26,5 @@ bool GBASDLInitEvents(struct GBASDLEvents*);

void GBASDLDeinitEvents(struct GBASDLEvents*); void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event); - -enum GBAKey GBASDLMapButtonToKey(int button); #endif
M src/platform/sdl/sw-main.csrc/platform/sdl/sw-main.c

@@ -99,11 +99,7 @@ renderer.tex = SDL_CreateTexture(renderer.sdlRenderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);

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

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

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
A src/util/png-io.c

@@ -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); +}
A src/util/png-io.h

@@ -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
M src/util/vfs/vfs-zip.csrc/util/vfs/vfs-zip.c

@@ -104,6 +104,8 @@ return -1;

} position = vfz->fileSize + offset; break; + default: + return -1; } if (position <= vfz->offset) {