mirror of https://github.com/mgba-emu/mgba.git
commit
2b3631dc91
|
@ -1,10 +1,11 @@
|
|||
cmake_minimum_required(VERSION 2.6)
|
||||
project(GBAc)
|
||||
set(BINARY_NAME gbac CACHE INTERNAL "Name of output binaries")
|
||||
set(CMAKE_C_FLAGS_DEBUG "-g -Wall -Wextra -std=c99")
|
||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra -std=c99")
|
||||
project(mGBA)
|
||||
set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries")
|
||||
set(CMAKE_C_FLAGS_DEBUG "-g -Wall -Wextra -std=gnu99")
|
||||
set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra -std=gnu99")
|
||||
set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger")
|
||||
set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger")
|
||||
set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
|
||||
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
|
||||
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
|
||||
set(BUILD_PERF ON CACHE BOOL "Build performance profiling tool")
|
||||
|
@ -13,7 +14,7 @@ file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
|
|||
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cS])
|
||||
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
|
||||
file(GLOB RENDERER_SRC ${CMAKE_SOURCE_DIR}/src/gba/renderers/video-software.c)
|
||||
set(UTIL_SRC ${UTIL_SRC};${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
||||
list(APPEND UTIL_SRC ${CMAKE_SOURCE_DIR}/src/platform/commandline.c)
|
||||
source_group("ARM core" FILES ${ARM_SRC})
|
||||
source_group("GBA board" FILES ${GBA_SRC} ${RENDERER_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
|
||||
|
@ -21,6 +22,29 @@ include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
|||
include_directories(${CMAKE_SOURCE_DIR}/src/gba)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
|
||||
set(LIB_VERSION_MAJOR 0)
|
||||
set(LIB_VERSION_MINOR 1)
|
||||
set(LIB_VERSION_PATCH 0)
|
||||
set(LIB_VERSION_ABI 0.1)
|
||||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
|
||||
set(BUILD_PGO CACHE BOOL "Build with profiling-guided optimization")
|
||||
set(PGO_STAGE_2 CACHE BOOL "Rebuild for profiling-guided optimization after profiles have been generated")
|
||||
set(PGO_DIR "/tmp/gba-pgo/" CACHE PATH "Profiling-guided optimization profiles path")
|
||||
mark_as_advanced(BUILD_PGO PGO_STAGE_2 PGO_DIR)
|
||||
set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR}")
|
||||
set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR}")
|
||||
|
||||
if(BUILD_PGO AND NOT PGO_STAGE_2)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${PGO_PRE_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PGO_PRE_FLAGS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PGO_PRE_FLAGS}")
|
||||
elseif(BUILD_PGO AND PGO_STAGE_2)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${PGO_POST_FLAGS}")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PGO_POST_FLAGS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${PGO_POST_FLAGS}")
|
||||
endif()
|
||||
|
||||
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}")
|
||||
|
||||
include(FindPkgConfig)
|
||||
|
@ -35,12 +59,12 @@ endif()
|
|||
|
||||
if(WIN32)
|
||||
add_definitions(-D_WIN32_WINNT=0x0600)
|
||||
set(OS_LIB "${OS_LIB};Ws2_32")
|
||||
list(APPEND OS_LIB Ws2_32)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
|
||||
source_group("Windows-specific code" FILES ${OS_SRC})
|
||||
else()
|
||||
add_definitions(-DUSE_PTHREADS)
|
||||
set(OS_LIB "${OS_LIB};pthread")
|
||||
list(APPEND OS_LIB pthread)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
|
||||
source_group("POSIX-specific code" FILES ${OS_SRC})
|
||||
endif()
|
||||
|
@ -52,26 +76,46 @@ if(BUILD_BBB OR BUILD_RASPI)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
set(DEBUGGER_SRC "${CMAKE_SOURCE_DIR}/src/debugger/debugger.c;${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c")
|
||||
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
|
||||
|
||||
if(USE_CLI_DEBUGGER AND NOT WIN32)
|
||||
# Win32 doesn't have a usable command line, nor libedit, so this is useless on Windows
|
||||
add_definitions(-DUSE_CLI_DEBUGGER)
|
||||
list(APPEND DEBUGGER_SRC "${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c")
|
||||
list(APPEND DEBUGGER_SRC "${CMAKE_SOURCE_DIR}/src/debugger/parser.c")
|
||||
set(DEBUGGER_LIB "edit")
|
||||
if(USE_CLI_DEBUGGER)
|
||||
pkg_search_module(EDIT libedit)
|
||||
if(EDIT_FOUND)
|
||||
add_definitions(-DUSE_CLI_DEBUGGER)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
||||
set(DEBUGGER_LIB ${EDIT_LIBRARIES})
|
||||
else()
|
||||
message(WARNING "Could not find libedit for CLI debugger support")
|
||||
set(USE_CLI_DEBUGGER OFF)
|
||||
endif()
|
||||
else()
|
||||
set(DEBUGGER_LIB "")
|
||||
endif()
|
||||
|
||||
if(USE_GDB_STUB)
|
||||
add_definitions(-DUSE_GDB_STUB)
|
||||
set(DEBUGGER_SRC "${DEBUGGER_SRC};${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c")
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c)
|
||||
endif()
|
||||
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
|
||||
|
||||
if(USE_FFMPEG)
|
||||
pkg_search_module(LIBAVCODEC libavcodec REQUIRED)
|
||||
pkg_search_module(LIBAVFORMAT libavformat REQUIRED)
|
||||
pkg_search_module(LIBAVUTIL libavutil REQUIRED)
|
||||
add_definitions(-DUSE_FFMPEG)
|
||||
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
|
||||
list(APPEND DEPENDENCY_LIB ${LIBAVCODEC_LIBRARIES} ${LIBAVFORMAT_LIBRARIES} ${LIBAVUTIL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
find_package(PNG REQUIRED)
|
||||
find_package(ZLIB REQUIRED)
|
||||
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
|
||||
add_library(${BINARY_NAME} SHARED ${ARM_SRC} ${GBA_SRC} ${DEBUGGER_SRC} ${RENDERER_SRC} ${UTIL_SRC} ${VFS_SRC} ${OS_SRC})
|
||||
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
|
||||
install(TARGETS ${BINARY_NAME} DESTINATION lib)
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_definitions(-DBUILD_SDL)
|
||||
|
@ -81,11 +125,12 @@ endif()
|
|||
if(BUILD_PERF)
|
||||
set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/perf-main.c)
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(PERF_LIB "${PERF_LIB};rt")
|
||||
list(APPEND PERF_LIB rt)
|
||||
endif()
|
||||
|
||||
add_executable(${BINARY_NAME}-perf ${PERF_SRC})
|
||||
target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB})
|
||||
install(TARGETS ${BINARY_NAME}-perf DESTINATION bin)
|
||||
endif()
|
||||
|
||||
if(BUILD_QT)
|
||||
|
|
|
@ -103,7 +103,9 @@
|
|||
info->affectsCPSR = S; \
|
||||
SHIFTER; \
|
||||
if (SKIPPED == 1) { \
|
||||
info->operandFormat &= ~ARM_OPERAND_1; \
|
||||
info->operandFormat >>= 8; \
|
||||
info->op1 = info->op2; \
|
||||
info->op2 = info->op3; \
|
||||
} else if (SKIPPED == 2) { \
|
||||
info->operandFormat &= ~ARM_OPERAND_2; \
|
||||
} \
|
||||
|
|
|
@ -332,6 +332,10 @@ int ARMDisassemble(struct ARMInstructionInfo* info, uint32_t pc, char* buffer, i
|
|||
written = _decodeRegister(info->op3.reg, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_4) {
|
||||
strncpy(buffer, ", ", blen - 1);
|
||||
ADVANCE(2);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_IMMEDIATE_4) {
|
||||
written = snprintf(buffer, blen - 1, "#%i", info->op4.immediate);
|
||||
ADVANCE(written);
|
||||
|
|
|
@ -71,7 +71,7 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
|
|||
}
|
||||
|
||||
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
|
||||
if (samples >= GBA_AUDIO_SAMPLES) {
|
||||
if (samples > GBA_AUDIO_SAMPLES) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -704,6 +704,10 @@ static void _sample(struct GBAAudio* audio) {
|
|||
CircleBufferWrite32(&audio->left, sampleLeft);
|
||||
CircleBufferWrite32(&audio->right, sampleRight);
|
||||
unsigned produced = CircleBufferSize(&audio->left);
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (thread && thread->stream) {
|
||||
thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
GBASyncProduceAudio(audio->p->sync, produced >= CircleBufferCapacity(&audio->left) / sizeof(int32_t) * 3);
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ static void _ObjAffineSet(struct GBA* gba) {
|
|||
sx = cpu->memory.load16(cpu, offset, 0) / 256.f;
|
||||
sy = cpu->memory.load16(cpu, offset + 2, 0) / 256.f;
|
||||
theta = (cpu->memory.loadU16(cpu, offset + 4, 0) >> 8) / 128.f * M_PI;
|
||||
offset += 6;
|
||||
offset += 8;
|
||||
// Rotation
|
||||
a = d = cosf(theta);
|
||||
b = c = sinf(theta);
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
|
|
506
src/gba/gba-rr.c
506
src/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,17 +40,175 @@ void GBARRContextDestroy(struct GBA* gba) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (GBARRIsPlaying(gba->rr)) {
|
||||
GBARRStopPlaying(gba->rr);
|
||||
}
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
GBARRStopRecording(gba->rr);
|
||||
}
|
||||
if (gba->rr->metadataFile) {
|
||||
gba->rr->metadataFile->close(gba->rr->metadataFile);
|
||||
}
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
|
||||
free(gba->rr);
|
||||
gba->rr = 0;
|
||||
}
|
||||
|
||||
bool GBARRSetStream(struct GBARRContext* rr, struct VDir* stream) {
|
||||
if (rr->inputsStream && !rr->inputsStream->close(rr->inputsStream)) {
|
||||
void GBARRSaveState(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
||||
GBASavedataClone(&gba->memory.savedata, gba->rr->savedata);
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = _openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
GBASaveStateNamed(gba, vf, false);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRLoadState(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVEGAME) {
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
||||
GBASavedataMask(&gba->memory.savedata, gba->rr->savedata);
|
||||
} else {
|
||||
GBASavedataMask(&gba->memory.savedata, 0);
|
||||
}
|
||||
|
||||
if (gba->rr->initFrom & INIT_FROM_SAVESTATE) {
|
||||
struct VFile* vf = _openSavestate(gba->rr, O_RDONLY);
|
||||
GBALoadStateNamed(gba, vf);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
ARMReset(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
|
||||
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rr->streamDir = stream;
|
||||
rr->inputsStream = stream->openFile(stream, FILE_INPUTS, O_CREAT | O_RDWR);
|
||||
return !!rr->inputsStream;
|
||||
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
|
||||
rr->currentInput = INVALID_INPUT;
|
||||
if (!_parseMetadata(rr, rr->metadataFile)) {
|
||||
rr->metadataFile->close(rr->metadataFile);
|
||||
rr->metadataFile = 0;
|
||||
rr->maxStreamId = 0;
|
||||
}
|
||||
rr->streamId = 1;
|
||||
rr->movieStream = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBARRReinitStream(struct GBARRContext* rr, enum GBARRInitFrom initFrom) {
|
||||
if (!rr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rr->metadataFile) {
|
||||
rr->metadataFile->truncate(rr->metadataFile, 0);
|
||||
} else {
|
||||
rr->metadataFile = rr->streamDir->openFile(rr->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
|
||||
}
|
||||
_emitMagic(rr, rr->metadataFile);
|
||||
|
||||
rr->initFrom = initFrom;
|
||||
rr->initFromOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
||||
_emitTag(rr, rr->metadataFile, TAG_INIT | initFrom);
|
||||
|
||||
rr->streamId = 0;
|
||||
rr->maxStreamId = 0;
|
||||
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
|
||||
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
||||
|
||||
rr->rrCount = 0;
|
||||
_emitTag(rr, rr->metadataFile, TAG_RR_COUNT);
|
||||
rr->rrCountOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBARRLoadStream(struct GBARRContext* rr, uint32_t streamId) {
|
||||
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
rr->movieStream = 0;
|
||||
rr->streamId = streamId;
|
||||
rr->currentInput = INVALID_INPUT;
|
||||
char buffer[14];
|
||||
snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
|
||||
if (GBARRIsRecording(rr)) {
|
||||
int flags = O_CREAT | O_RDWR;
|
||||
if (streamId > rr->maxStreamId) {
|
||||
flags |= O_TRUNC;
|
||||
}
|
||||
rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, flags);
|
||||
} else if (GBARRIsPlaying(rr)) {
|
||||
rr->movieStream = rr->streamDir->openFile(rr->streamDir, buffer, O_RDONLY);
|
||||
rr->peekedTag = TAG_INVALID;
|
||||
if (!rr->movieStream || !_verifyMagic(rr, rr->movieStream) || !_seekTag(rr, rr->movieStream, TAG_BEGIN)) {
|
||||
GBARRStopPlaying(rr);
|
||||
}
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
|
||||
rr->frames = 0;
|
||||
rr->lagFrames = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBARRIncrementStream(struct GBARRContext* rr, bool recursive) {
|
||||
uint32_t newStreamId = rr->maxStreamId + 1;
|
||||
uint32_t oldStreamId = rr->streamId;
|
||||
if (GBARRIsRecording(rr) && rr->movieStream) {
|
||||
if (!_markStreamNext(rr, newStreamId, recursive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!GBARRLoadStream(rr, newStreamId)) {
|
||||
return false;
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
|
||||
_emitMagic(rr, rr->movieStream);
|
||||
rr->maxStreamId = newStreamId;
|
||||
_emitTag(rr, rr->movieStream, TAG_PREVIOUSLY);
|
||||
rr->movieStream->write(rr->movieStream, &oldStreamId, sizeof(oldStreamId));
|
||||
_emitTag(rr, rr->movieStream, TAG_BEGIN);
|
||||
|
||||
rr->metadataFile->seek(rr->metadataFile, rr->maxStreamIdOffset, SEEK_SET);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
||||
rr->previously = oldStreamId;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||
|
@ -36,19 +216,24 @@ bool GBARRStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
|||
return false;
|
||||
}
|
||||
|
||||
rr->autorecord = autorecord;
|
||||
if (rr->inputsStream->seek(rr->inputsStream, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (rr->inputsStream->read(rr->inputsStream, &rr->nextInput, sizeof(rr->nextInput)) != sizeof(rr->nextInput)) {
|
||||
return false;
|
||||
}
|
||||
rr->isPlaying = true;
|
||||
if (!GBARRLoadStream(rr, 1)) {
|
||||
rr->isPlaying = false;
|
||||
return false;
|
||||
}
|
||||
rr->autorecord = autorecord;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBARRStopPlaying(struct GBARRContext* rr) {
|
||||
if (!GBARRIsPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
rr->isPlaying = false;
|
||||
if (rr->movieStream) {
|
||||
rr->movieStream->close(rr->movieStream);
|
||||
rr->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBARRStartRecording(struct GBARRContext* rr) {
|
||||
|
@ -56,12 +241,26 @@ bool GBARRStartRecording(struct GBARRContext* rr) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!rr->maxStreamIdOffset) {
|
||||
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
|
||||
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
||||
}
|
||||
|
||||
rr->isRecording = true;
|
||||
return true;
|
||||
return GBARRIncrementStream(rr, false);
|
||||
}
|
||||
|
||||
void GBARRStopRecording(struct GBARRContext* rr) {
|
||||
if (!GBARRIsRecording(rr)) {
|
||||
return;
|
||||
}
|
||||
rr->isRecording = false;
|
||||
if (rr->movieStream) {
|
||||
_emitEnd(rr, rr->movieStream);
|
||||
rr->movieStream->close(rr->movieStream);
|
||||
rr->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBARRIsPlaying(struct GBARRContext* rr) {
|
||||
|
@ -73,16 +272,41 @@ bool GBARRIsRecording(struct GBARRContext* rr) {
|
|||
}
|
||||
|
||||
void GBARRNextFrame(struct GBARRContext* rr) {
|
||||
if (!GBARRIsRecording(rr)) {
|
||||
if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
++rr->frames;
|
||||
if (!rr->inputThisFrame) {
|
||||
++rr->lagFrames;
|
||||
if (GBARRIsPlaying(rr)) {
|
||||
while (rr->peekedTag == TAG_INPUT) {
|
||||
_readTag(rr, rr->movieStream);
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
|
||||
}
|
||||
if (rr->peekedTag == TAG_LAG) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
|
||||
if (rr->inputThisFrame) {
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rr->inputThisFrame = false;
|
||||
++rr->frames;
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames);
|
||||
if (!rr->inputThisFrame) {
|
||||
++rr->lagFrames;
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames);
|
||||
}
|
||||
|
||||
if (GBARRIsRecording(rr)) {
|
||||
if (!rr->inputThisFrame) {
|
||||
_emitTag(rr, rr->movieStream, TAG_LAG);
|
||||
}
|
||||
_emitTag(rr, rr->movieStream, TAG_FRAME);
|
||||
rr->inputThisFrame = false;
|
||||
} else {
|
||||
if (!_seekTag(rr, rr->movieStream, TAG_FRAME)) {
|
||||
_streamEndReached(rr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
|
||||
|
@ -90,7 +314,12 @@ void GBARRLogInput(struct GBARRContext* rr, uint16_t keys) {
|
|||
return;
|
||||
}
|
||||
|
||||
rr->inputsStream->write(rr->inputsStream, &keys, sizeof(keys));
|
||||
if (keys != rr->currentInput) {
|
||||
_emitTag(rr, rr->movieStream, TAG_INPUT);
|
||||
rr->movieStream->write(rr->movieStream, &keys, sizeof(keys));
|
||||
rr->currentInput = keys;
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", rr->currentInput);
|
||||
rr->inputThisFrame = true;
|
||||
}
|
||||
|
||||
|
@ -99,10 +328,239 @@ uint16_t GBARRQueryInput(struct GBARRContext* rr) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint16_t keys = rr->nextInput;
|
||||
rr->isPlaying = rr->inputsStream->read(rr->inputsStream, &rr->nextInput, sizeof(rr->nextInput)) == sizeof(rr->nextInput);
|
||||
if (!rr->isPlaying && rr->autorecord) {
|
||||
rr->isRecording = true;
|
||||
if (rr->peekedTag == TAG_INPUT) {
|
||||
_readTag(rr, rr->movieStream);
|
||||
}
|
||||
return keys;
|
||||
rr->inputThisFrame = true;
|
||||
if (rr->currentInput == INVALID_INPUT) {
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput);
|
||||
return rr->currentInput;
|
||||
}
|
||||
|
||||
bool GBARRFinishSegment(struct GBARRContext* rr) {
|
||||
if (rr->movieStream) {
|
||||
if (!_emitEnd(rr, rr->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return GBARRIncrementStream(rr, false);
|
||||
}
|
||||
|
||||
bool GBARRSkipSegment(struct GBARRContext* rr) {
|
||||
rr->nextTime = 0;
|
||||
while (_readTag(rr, rr->movieStream) != TAG_EOF);
|
||||
if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
|
||||
_streamEndReached(rr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBARRMarkRerecord(struct GBARRContext* rr) {
|
||||
++rr->rrCount;
|
||||
rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
|
||||
UNUSED(rr);
|
||||
return vf->write(vf, BINARY_MAGIC, 4) == 4;
|
||||
}
|
||||
|
||||
bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
|
||||
UNUSED(rr);
|
||||
char buffer[4];
|
||||
if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
|
||||
if (!rr || !vf) {
|
||||
return TAG_EOF;
|
||||
}
|
||||
|
||||
enum GBARRTag tag = rr->peekedTag;
|
||||
switch (tag) {
|
||||
case TAG_INPUT:
|
||||
vf->read(vf, &rr->currentInput, sizeof(uint16_t));
|
||||
break;
|
||||
case TAG_PREVIOUSLY:
|
||||
vf->read(vf, &rr->previously, sizeof(rr->previously));
|
||||
break;
|
||||
case TAG_NEXT_TIME:
|
||||
vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
|
||||
break;
|
||||
case TAG_MAX_STREAM:
|
||||
vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
||||
break;
|
||||
case TAG_FRAME_COUNT:
|
||||
vf->read(vf, &rr->frames, sizeof(rr->frames));
|
||||
break;
|
||||
case TAG_LAG_COUNT:
|
||||
vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
vf->read(vf, &rr->rrCount, sizeof(rr->rrCount));
|
||||
break;
|
||||
|
||||
case TAG_INIT_EX_NIHILO:
|
||||
rr->initFrom = INIT_EX_NIHILO;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVEGAME:
|
||||
rr->initFrom = INIT_FROM_SAVEGAME;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVESTATE:
|
||||
rr->initFrom = INIT_FROM_SAVESTATE;
|
||||
case TAG_INIT_FROM_BOTH:
|
||||
rr->initFrom = INIT_FROM_BOTH;
|
||||
break;
|
||||
|
||||
// To be spec'd
|
||||
case TAG_AUTHOR:
|
||||
case TAG_COMMENT:
|
||||
break;
|
||||
|
||||
// Empty markers
|
||||
case TAG_FRAME:
|
||||
case TAG_LAG:
|
||||
case TAG_BEGIN:
|
||||
case TAG_END:
|
||||
case TAG_INVALID:
|
||||
case TAG_EOF:
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t tagBuffer;
|
||||
if (vf->read(vf, &tagBuffer, 1) != 1) {
|
||||
rr->peekedTag = TAG_EOF;
|
||||
} else {
|
||||
rr->peekedTag = tagBuffer;
|
||||
}
|
||||
|
||||
if (rr->peekedTag == TAG_END) {
|
||||
GBARRSkipSegment(rr);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
bool _seekTag(struct GBARRContext* rr, struct VFile* vf, enum GBARRTag tag) {
|
||||
enum GBARRTag readTag;
|
||||
while ((readTag = _readTag(rr, vf)) != tag) {
|
||||
if (readTag == TAG_EOF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitTag(struct GBARRContext* rr, struct VFile* vf, uint8_t tag) {
|
||||
UNUSED(rr);
|
||||
return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
|
||||
}
|
||||
|
||||
bool _parseMetadata(struct GBARRContext* rr, struct VFile* vf) {
|
||||
if (!_verifyMagic(rr, vf)) {
|
||||
return false;
|
||||
}
|
||||
while (_readTag(rr, vf) != TAG_EOF) {
|
||||
switch (rr->peekedTag) {
|
||||
case TAG_MAX_STREAM:
|
||||
rr->maxStreamIdOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
case TAG_INIT_EX_NIHILO:
|
||||
case TAG_INIT_FROM_SAVEGAME:
|
||||
case TAG_INIT_FROM_SAVESTATE:
|
||||
case TAG_INIT_FROM_BOTH:
|
||||
rr->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
rr->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitEnd(struct GBARRContext* rr, struct VFile* vf) {
|
||||
// TODO: Error check
|
||||
_emitTag(rr, vf, TAG_END);
|
||||
_emitTag(rr, vf, TAG_FRAME_COUNT);
|
||||
vf->write(vf, &rr->frames, sizeof(rr->frames));
|
||||
_emitTag(rr, vf, TAG_LAG_COUNT);
|
||||
vf->write(vf, &rr->lagFrames, sizeof(rr->lagFrames));
|
||||
_emitTag(rr, vf, TAG_NEXT_TIME);
|
||||
|
||||
uint32_t newStreamId = 0;
|
||||
vf->write(vf, &newStreamId, sizeof(newStreamId));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _markStreamNext(struct GBARRContext* rr, uint32_t newStreamId, bool recursive) {
|
||||
if (rr->movieStream->seek(rr->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t tagBuffer;
|
||||
if (rr->movieStream->read(rr->movieStream, &tagBuffer, 1) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (tagBuffer != TAG_NEXT_TIME) {
|
||||
return false;
|
||||
}
|
||||
if (rr->movieStream->write(rr->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
|
||||
return false;
|
||||
}
|
||||
if (recursive) {
|
||||
if (rr->movieStream->seek(rr->movieStream, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!_verifyMagic(rr, rr->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
_readTag(rr, rr->movieStream);
|
||||
if (_readTag(rr, rr->movieStream) != TAG_PREVIOUSLY) {
|
||||
return false;
|
||||
}
|
||||
if (rr->previously == 0) {
|
||||
return true;
|
||||
}
|
||||
uint32_t currentStreamId = rr->streamId;
|
||||
if (!GBARRLoadStream(rr, rr->previously)) {
|
||||
return false;
|
||||
}
|
||||
return _markStreamNext(rr, currentStreamId, rr->previously);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _streamEndReached(struct GBARRContext* rr) {
|
||||
if (!GBARRIsPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t endStreamId = rr->streamId;
|
||||
GBARRStopPlaying(rr);
|
||||
if (rr->autorecord) {
|
||||
rr->isRecording = true;
|
||||
GBARRLoadStream(rr, endStreamId);
|
||||
GBARRIncrementStream(rr, false);
|
||||
}
|
||||
}
|
||||
|
||||
struct VFile* _openSavedata(struct GBARRContext* rr, int flags) {
|
||||
return rr->streamDir->openFile(rr->streamDir, "movie.sav", flags);
|
||||
}
|
||||
|
||||
struct VFile* _openSavestate(struct GBARRContext* rr, int flags) {
|
||||
return rr->streamDir->openFile(rr->streamDir, "movie.ssm", flags);
|
||||
}
|
||||
|
|
|
@ -7,11 +7,48 @@ struct GBA;
|
|||
struct VDir;
|
||||
struct VFile;
|
||||
|
||||
enum GBARRInitFrom {
|
||||
INIT_EX_NIHILO = 0,
|
||||
INIT_FROM_SAVEGAME = 1,
|
||||
INIT_FROM_SAVESTATE = 2,
|
||||
INIT_FROM_BOTH = 3,
|
||||
};
|
||||
|
||||
enum GBARRTag {
|
||||
// Playback tags
|
||||
TAG_INVALID = 0x00,
|
||||
TAG_INPUT = 0x01,
|
||||
TAG_FRAME = 0x02,
|
||||
TAG_LAG = 0x03,
|
||||
|
||||
// Stream chunking tags
|
||||
TAG_BEGIN = 0x10,
|
||||
TAG_END = 0x11,
|
||||
TAG_PREVIOUSLY = 0x12,
|
||||
TAG_NEXT_TIME = 0x13,
|
||||
TAG_MAX_STREAM = 0x14,
|
||||
|
||||
// Recording information tags
|
||||
TAG_FRAME_COUNT = 0x20,
|
||||
TAG_LAG_COUNT = 0x21,
|
||||
TAG_RR_COUNT = 0x22,
|
||||
TAG_INIT = 0x24,
|
||||
TAG_INIT_EX_NIHILO = 0x24 | INIT_EX_NIHILO,
|
||||
TAG_INIT_FROM_SAVEGAME = 0x24 | INIT_FROM_SAVEGAME,
|
||||
TAG_INIT_FROM_SAVESTATE = 0x24 | INIT_FROM_SAVESTATE,
|
||||
TAG_INIT_FROM_BOTH = 0x24 | INIT_FROM_BOTH,
|
||||
|
||||
// User metadata tags
|
||||
TAG_AUTHOR = 0x30,
|
||||
TAG_COMMENT = 0x31,
|
||||
|
||||
TAG_EOF = INT_MAX
|
||||
};
|
||||
|
||||
struct GBARRContext {
|
||||
// Playback state
|
||||
bool isPlaying;
|
||||
bool autorecord;
|
||||
uint16_t nextInput;
|
||||
|
||||
// Recording state
|
||||
bool isRecording;
|
||||
|
@ -20,16 +57,41 @@ struct GBARRContext {
|
|||
// Metadata
|
||||
uint32_t frames;
|
||||
uint32_t lagFrames;
|
||||
uint32_t streamId;
|
||||
|
||||
uint32_t maxStreamId;
|
||||
off_t maxStreamIdOffset;
|
||||
|
||||
enum GBARRInitFrom initFrom;
|
||||
off_t initFromOffset;
|
||||
|
||||
uint32_t rrCount;
|
||||
off_t rrCountOffset;
|
||||
|
||||
struct VFile* savedata;
|
||||
|
||||
// Streaming state
|
||||
struct VDir* streamDir;
|
||||
struct VFile* inputsStream;
|
||||
struct VFile* metadataFile;
|
||||
struct VFile* movieStream;
|
||||
uint16_t currentInput;
|
||||
enum GBARRTag peekedTag;
|
||||
uint32_t nextTime;
|
||||
uint32_t previously;
|
||||
};
|
||||
|
||||
void GBARRContextCreate(struct GBA*);
|
||||
void GBARRContextDestroy(struct GBA*);
|
||||
void GBARRSaveState(struct GBA*);
|
||||
void GBARRLoadState(struct GBA*);
|
||||
|
||||
bool GBARRSetStream(struct GBARRContext*, struct VDir*);
|
||||
bool GBARRInitStream(struct GBARRContext*, struct VDir*);
|
||||
bool GBARRReinitStream(struct GBARRContext*, enum GBARRInitFrom);
|
||||
bool GBARRLoadStream(struct GBARRContext*, uint32_t streamId);
|
||||
bool GBARRIncrementStream(struct GBARRContext*, bool recursive);
|
||||
bool GBARRFinishSegment(struct GBARRContext*);
|
||||
bool GBARRSkipSegment(struct GBARRContext*);
|
||||
bool GBARRMarkRerecord(struct GBARRContext*);
|
||||
|
||||
bool GBARRStartPlaying(struct GBARRContext*, bool autorecord);
|
||||
void GBARRStopPlaying(struct GBARRContext*);
|
||||
|
|
|
@ -18,6 +18,8 @@ void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf) {
|
|||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
savedata->flashState = FLASH_STATE_RAW;
|
||||
savedata->vf = vf;
|
||||
savedata->realVf = vf;
|
||||
savedata->mapMode = MAP_WRITE;
|
||||
}
|
||||
|
||||
void GBASavedataDeinit(struct GBASavedata* savedata) {
|
||||
|
@ -57,9 +59,48 @@ void GBASavedataDeinit(struct GBASavedata* savedata) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
savedata->data = 0;
|
||||
savedata->type = SAVEDATA_NONE;
|
||||
}
|
||||
|
||||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf) {
|
||||
GBASavedataDeinit(savedata);
|
||||
savedata->vf = vf;
|
||||
savedata->mapMode = MAP_READ;
|
||||
}
|
||||
|
||||
void GBASavedataUnmask(struct GBASavedata* savedata) {
|
||||
GBASavedataDeinit(savedata);
|
||||
savedata->vf = savedata->realVf;
|
||||
savedata->mapMode = MAP_WRITE;
|
||||
}
|
||||
|
||||
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out) {
|
||||
if (savedata->data) {
|
||||
switch (savedata->type) {
|
||||
case SAVEDATA_SRAM:
|
||||
return out->write(out, savedata->data, SIZE_CART_SRAM) == SIZE_CART_SRAM;
|
||||
case SAVEDATA_FLASH512:
|
||||
return out->write(out, savedata->data, SIZE_CART_FLASH512) == SIZE_CART_FLASH512;
|
||||
case SAVEDATA_FLASH1M:
|
||||
return out->write(out, savedata->data, SIZE_CART_FLASH1M) == SIZE_CART_FLASH1M;
|
||||
case SAVEDATA_EEPROM:
|
||||
return out->write(out, savedata->data, SIZE_CART_EEPROM) == SIZE_CART_EEPROM;
|
||||
case SAVEDATA_NONE:
|
||||
return true;
|
||||
}
|
||||
} else if (savedata->vf) {
|
||||
off_t read = 0;
|
||||
uint8_t buffer[2048];
|
||||
do {
|
||||
read = savedata->vf->read(savedata->vf, buffer, sizeof(buffer));
|
||||
out->write(out, buffer, read);
|
||||
} while (read == sizeof(buffer));
|
||||
return read >= 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata) {
|
||||
if (savedata->type == SAVEDATA_NONE) {
|
||||
savedata->type = SAVEDATA_FLASH512;
|
||||
|
@ -77,7 +118,7 @@ void GBASavedataInitFlash(struct GBASavedata* savedata) {
|
|||
if (end < SIZE_CART_FLASH512) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_FLASH1M);
|
||||
}
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, MAP_WRITE);
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_FLASH1M, savedata->mapMode);
|
||||
}
|
||||
|
||||
savedata->currentBank = savedata->data;
|
||||
|
@ -102,7 +143,7 @@ void GBASavedataInitEEPROM(struct GBASavedata* savedata) {
|
|||
if (end < SIZE_CART_EEPROM) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_EEPROM);
|
||||
}
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_EEPROM, MAP_WRITE);
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_EEPROM, savedata->mapMode);
|
||||
}
|
||||
if (end < SIZE_CART_EEPROM) {
|
||||
memset(&savedata->data[end], 0xFF, SIZE_CART_EEPROM - end);
|
||||
|
@ -125,7 +166,7 @@ void GBASavedataInitSRAM(struct GBASavedata* savedata) {
|
|||
if (end < SIZE_CART_SRAM) {
|
||||
savedata->vf->truncate(savedata->vf, SIZE_CART_SRAM);
|
||||
}
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_SRAM, MAP_WRITE);
|
||||
savedata->data = savedata->vf->map(savedata->vf, SIZE_CART_SRAM, savedata->mapMode);
|
||||
}
|
||||
|
||||
if (end < SIZE_CART_SRAM) {
|
||||
|
|
|
@ -58,6 +58,9 @@ struct GBASavedata {
|
|||
enum SavedataCommand command;
|
||||
struct VFile* vf;
|
||||
|
||||
int mapMode;
|
||||
struct VFile* realVf;
|
||||
|
||||
int readBitsRemaining;
|
||||
int readAddress;
|
||||
int writeAddress;
|
||||
|
@ -72,6 +75,10 @@ struct GBASavedata {
|
|||
void GBASavedataInit(struct GBASavedata* savedata, struct VFile* vf);
|
||||
void GBASavedataDeinit(struct GBASavedata* savedata);
|
||||
|
||||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf);
|
||||
void GBASavedataUnmask(struct GBASavedata* savedata);
|
||||
bool GBASavedataClone(struct GBASavedata* savedata, struct VFile* out);
|
||||
|
||||
void GBASavedataInitFlash(struct GBASavedata* savedata);
|
||||
void GBASavedataInitEEPROM(struct GBASavedata* savedata);
|
||||
void GBASavedataInitSRAM(struct GBASavedata* savedata);
|
||||
|
|
|
@ -2,12 +2,17 @@
|
|||
|
||||
#include "gba-audio.h"
|
||||
#include "gba-io.h"
|
||||
#include "gba-rr.h"
|
||||
#include "gba-thread.h"
|
||||
#include "gba-video.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
#include "util/png-io.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <png.h>
|
||||
#include <zlib.h>
|
||||
|
||||
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
|
||||
|
||||
|
@ -31,6 +36,13 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAIOSerialize(gba, state);
|
||||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
state->associatedStreamId = gba->rr->streamId;
|
||||
GBARRFinishSegment(gba->rr);
|
||||
} else {
|
||||
state->associatedStreamId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
|
@ -66,49 +78,138 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAIODeserialize(gba, state);
|
||||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAAudioDeserialize(&gba->audio, state);
|
||||
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
if (state->associatedStreamId != gba->rr->streamId) {
|
||||
GBARRLoadStream(gba->rr, state->associatedStreamId);
|
||||
GBARRIncrementStream(gba->rr, true);
|
||||
} else {
|
||||
GBARRFinishSegment(gba->rr);
|
||||
}
|
||||
GBARRMarkRerecord(gba->rr);
|
||||
} else if (GBARRIsPlaying(gba->rr)) {
|
||||
GBARRLoadStream(gba->rr, state->associatedStreamId);
|
||||
GBARRSkipSegment(gba->rr);
|
||||
}
|
||||
}
|
||||
|
||||
static struct VFile* _getStateVf(struct GBA* gba, int slot) {
|
||||
static struct VFile* _getStateVf(struct GBA* gba, struct VDir* dir, int slot, bool write) {
|
||||
char path[PATH_MAX];
|
||||
path[PATH_MAX - 1] = '\0';
|
||||
snprintf(path, PATH_MAX - 1, "%s.ss%d", gba->activeFile, slot);
|
||||
struct VFile* vf = VFileOpen(path, O_CREAT | O_RDWR);
|
||||
if (vf) {
|
||||
vf->truncate(vf, sizeof(struct GBASerializedState));
|
||||
struct VFile* vf;
|
||||
if (!dir) {
|
||||
snprintf(path, PATH_MAX - 1, "%s.ss%d", gba->activeFile, slot);
|
||||
vf = VFileOpen(path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
} else {
|
||||
snprintf(path, PATH_MAX - 1, "savestate.ss%d", slot);
|
||||
vf = dir->openFile(dir, path, write ? (O_CREAT | O_TRUNC | O_RDWR) : O_RDONLY);
|
||||
}
|
||||
return vf;
|
||||
}
|
||||
|
||||
bool GBASaveState(struct GBA* gba, int slot) {
|
||||
struct VFile* vf = _getStateVf(gba, slot);
|
||||
if (!vf) {
|
||||
static bool _savePNGState(struct GBA* gba, struct VFile* vf) {
|
||||
unsigned stride;
|
||||
void* pixels = 0;
|
||||
gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels);
|
||||
if (!pixels) {
|
||||
return false;
|
||||
}
|
||||
struct GBASerializedState* state = GBAMapState(vf);
|
||||
GBASerialize(gba, state);
|
||||
GBAUnmapState(vf, state);
|
||||
vf->close(vf);
|
||||
|
||||
struct GBASerializedState* state = GBAAllocateState();
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
uLongf len = compressBound(sizeof(*state));
|
||||
void* buffer = malloc(len);
|
||||
if (state && png && info && buffer) {
|
||||
GBASerialize(gba, state);
|
||||
compress(buffer, &len, (const Bytef*) state, sizeof(*state));
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
PNGWriteCustomChunk(png, "gbAs", len, buffer);
|
||||
}
|
||||
PNGWriteClose(png, info);
|
||||
free(buffer);
|
||||
GBADeallocateState(state);
|
||||
return state && png && info && buffer;
|
||||
}
|
||||
|
||||
static int _loadPNGChunkHandler(png_structp png, png_unknown_chunkp chunk) {
|
||||
if (strcmp((const char*) chunk->name, "gbAs") != 0) {
|
||||
return 0;
|
||||
}
|
||||
struct GBASerializedState state;
|
||||
uLongf len = sizeof(state);
|
||||
uncompress((Bytef*) &state, &len, chunk->data, chunk->size);
|
||||
GBADeserialize(png_get_user_chunk_ptr(png), &state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool _loadPNGState(struct GBA* gba, struct VFile* vf) {
|
||||
png_structp png = PNGReadOpen(vf, PNG_HEADER_BYTES);
|
||||
png_infop info = png_create_info_struct(png);
|
||||
png_infop end = png_create_info_struct(png);
|
||||
if (!png || !info || !end) {
|
||||
PNGReadClose(png, info, end);
|
||||
return false;
|
||||
}
|
||||
uint32_t pixels[VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4];
|
||||
|
||||
PNGInstallChunkHandler(png, gba, _loadPNGChunkHandler, "gbAs");
|
||||
PNGReadHeader(png, info);
|
||||
PNGReadPixels(png, info, &pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, VIDEO_HORIZONTAL_PIXELS);
|
||||
PNGReadFooter(png, end);
|
||||
PNGReadClose(png, info, end);
|
||||
gba->video.renderer->putPixels(gba->video.renderer, VIDEO_HORIZONTAL_PIXELS, pixels);
|
||||
GBASyncPostFrame(gba->sync);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBALoadState(struct GBA* gba, int slot) {
|
||||
struct VFile* vf = _getStateVf(gba, slot);
|
||||
bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot) {
|
||||
struct VFile* vf = _getStateVf(gba, dir, slot, true);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
struct GBASerializedState* state = GBAMapState(vf);
|
||||
GBADeserialize(gba, state);
|
||||
GBAUnmapState(vf, state);
|
||||
bool success = GBASaveStateNamed(gba, vf, screenshot);
|
||||
vf->close(vf);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot) {
|
||||
struct VFile* vf = _getStateVf(gba, dir, slot, false);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool success = GBALoadStateNamed(gba, vf);
|
||||
vf->close(vf);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot) {
|
||||
if (!screenshot) {
|
||||
vf->truncate(vf, sizeof(struct GBASerializedState));
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
GBASerialize(gba, state);
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
} else {
|
||||
return _savePNGState(gba, vf);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct GBASerializedState* GBAMapState(struct VFile* vf) {
|
||||
return vf->map(vf, sizeof(struct GBASerializedState), MAP_WRITE);
|
||||
}
|
||||
|
||||
void GBAUnmapState(struct VFile* vf, struct GBASerializedState* state) {
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf) {
|
||||
if (!isPNG(vf)) {
|
||||
struct GBASerializedState* state = vf->map(vf, sizeof(struct GBASerializedState), MAP_READ);
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
GBADeserialize(gba, state);
|
||||
vf->unmap(vf, state, sizeof(struct GBASerializedState));
|
||||
} else {
|
||||
return _loadPNGState(gba, vf);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
struct GBASerializedState* GBAAllocateState(void) {
|
||||
|
|
|
@ -139,7 +139,9 @@ const uint32_t GBA_SAVESTATE_MAGIC;
|
|||
* | bit 0: Is read enabled
|
||||
* | bit 1: Gyroscope sample is edge
|
||||
* | bits 2 - 15: Reserved
|
||||
* 0x002C0 - 0x003FF: Reserved (leave zero)
|
||||
* 0x002C0 - 0x002FF: Reserved (leave zero)
|
||||
* 0x00300 - 0x00303: Associated movie stream ID for record/replay (or 0 if no stream)
|
||||
* 0x00304 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00400 - 0x007FF: I/O memory
|
||||
* 0x00800 - 0x00BFF: Palette
|
||||
* 0x00C00 - 0x00FFF: OAM
|
||||
|
@ -248,7 +250,11 @@ struct GBASerializedState {
|
|||
unsigned reserved : 14;
|
||||
} gpio;
|
||||
|
||||
uint32_t reserved[80];
|
||||
uint32_t reservedGpio[16];
|
||||
|
||||
uint32_t associatedStreamId;
|
||||
|
||||
uint32_t reserved[63];
|
||||
|
||||
uint16_t io[SIZE_IO >> 1];
|
||||
uint16_t pram[SIZE_PALETTE_RAM >> 1];
|
||||
|
@ -258,16 +264,16 @@ struct GBASerializedState {
|
|||
uint8_t wram[SIZE_WORKING_RAM];
|
||||
};
|
||||
|
||||
struct VFile;
|
||||
struct VDir;
|
||||
|
||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
|
||||
bool GBASaveState(struct GBA* gba, int slot);
|
||||
bool GBALoadState(struct GBA* gba, int slot);
|
||||
bool GBASaveState(struct GBA* gba, struct VDir* dir, int slot, bool screenshot);
|
||||
bool GBALoadState(struct GBA* gba, struct VDir* dir, int slot);
|
||||
|
||||
struct GBASerializedState* GBAMapState(struct VFile* vf);
|
||||
void GBAUnmapState(struct VFile* vf, struct GBASerializedState* state);
|
||||
bool GBASaveStateNamed(struct GBA* gba, struct VFile* vf, bool screenshot);
|
||||
bool GBALoadStateNamed(struct GBA* gba, struct VFile* vf);
|
||||
|
||||
struct GBASerializedState* GBAAllocateState(void);
|
||||
void GBADeallocateState(struct GBASerializedState* state);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "debugger/debugger.h"
|
||||
|
||||
#include "util/patch.h"
|
||||
#include "util/png-io.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#include <signal.h>
|
||||
|
@ -110,7 +111,6 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
pthread_sigmask(SIG_SETMASK, &signals, 0);
|
||||
#endif
|
||||
|
||||
gba.logHandler = threadContext->logHandler;
|
||||
GBACreate(&gba);
|
||||
ARMSetComponents(&cpu, &gba.d, numComponents, components);
|
||||
ARMInit(&cpu);
|
||||
|
@ -391,6 +391,8 @@ void GBAThreadJoin(struct GBAThread* threadContext) {
|
|||
}
|
||||
free(threadContext->rewindBuffer);
|
||||
|
||||
GBAInputMapDeinit(&threadContext->inputMap);
|
||||
|
||||
if (threadContext->rom) {
|
||||
threadContext->rom->close(threadContext->rom);
|
||||
threadContext->rom = 0;
|
||||
|
@ -517,6 +519,18 @@ struct GBAThread* GBAThreadGetContext(void) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void GBAThreadTakeScreenshot(struct GBAThread* threadContext) {
|
||||
unsigned stride;
|
||||
void* pixels = 0;
|
||||
struct VFile* vf = threadContext->stateDir->openFile(threadContext->stateDir, "screenshot.png", O_CREAT | O_WRONLY);
|
||||
threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels);
|
||||
png_structp png = PNGWriteOpen(vf);
|
||||
png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS);
|
||||
PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels);
|
||||
PNGWriteClose(png, info);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync) {
|
||||
if (!sync) {
|
||||
return;
|
||||
|
@ -534,6 +548,10 @@ void GBASyncPostFrame(struct GBASync* sync) {
|
|||
MutexUnlock(&sync->videoFrameMutex);
|
||||
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (thread->rewindBuffer) {
|
||||
--thread->rewindBufferNext;
|
||||
if (thread->rewindBufferNext <= 0) {
|
||||
|
@ -541,6 +559,9 @@ void GBASyncPostFrame(struct GBASync* sync) {
|
|||
GBARecordFrame(thread);
|
||||
}
|
||||
}
|
||||
if (thread->stream) {
|
||||
thread->stream->postVideoFrame(thread->stream, thread->renderer);
|
||||
}
|
||||
if (thread->frameCallback) {
|
||||
thread->frameCallback(thread);
|
||||
}
|
||||
|
@ -553,10 +574,12 @@ bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
|
|||
|
||||
MutexLock(&sync->videoFrameMutex);
|
||||
ConditionWake(&sync->videoFrameRequiredCond);
|
||||
if (!sync->videoFrameOn) {
|
||||
if (!sync->videoFrameOn && !sync->videoFramePending) {
|
||||
return false;
|
||||
}
|
||||
ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
|
||||
if (!sync->videoFramePending) {
|
||||
ConditionWait(&sync->videoFrameAvailableCond, &sync->videoFrameMutex);
|
||||
}
|
||||
sync->videoFramePending = 0;
|
||||
sync->videoFrameSkip = frameskip;
|
||||
return true;
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
#include "common.h"
|
||||
|
||||
#include "gba.h"
|
||||
#include "gba-input.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
#include "platform/commandline.h"
|
||||
|
||||
struct GBAThread;
|
||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
||||
typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
enum ThreadState {
|
||||
THREAD_INITIALIZED = -1,
|
||||
|
@ -37,6 +39,11 @@ struct GBASync {
|
|||
Mutex audioBufferMutex;
|
||||
};
|
||||
|
||||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int32_t left, int32_t right);
|
||||
};
|
||||
|
||||
struct GBAThread {
|
||||
// Output
|
||||
enum ThreadState state;
|
||||
|
@ -55,6 +62,10 @@ struct GBAThread {
|
|||
struct VFile* patch;
|
||||
const char* fname;
|
||||
int activeKeys;
|
||||
struct GBAInputMap inputMap;
|
||||
struct GBAAVStream* stream;
|
||||
|
||||
// Run-time options
|
||||
int frameskip;
|
||||
float fpsTarget;
|
||||
size_t audioBuffers;
|
||||
|
@ -66,7 +77,7 @@ struct GBAThread {
|
|||
Condition stateCond;
|
||||
enum ThreadState savedState;
|
||||
|
||||
GBALogHandler logHandler;
|
||||
LogHandler logHandler;
|
||||
int logLevel;
|
||||
ThreadCallback startCallback;
|
||||
ThreadCallback cleanCallback;
|
||||
|
@ -101,6 +112,8 @@ void GBAThreadTogglePause(struct GBAThread* threadContext);
|
|||
void GBAThreadPauseFromThread(struct GBAThread* threadContext);
|
||||
struct GBAThread* GBAThreadGetContext(void);
|
||||
|
||||
void GBAThreadTakeScreenshot(struct GBAThread* threadContext);
|
||||
|
||||
void GBASyncPostFrame(struct GBASync* sync);
|
||||
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip);
|
||||
void GBASyncWaitFrameEnd(struct GBASync* sync);
|
||||
|
|
|
@ -12,16 +12,22 @@ static void GBAVideoDummyRendererInit(struct GBAVideoRenderer* renderer);
|
|||
static void GBAVideoDummyRendererReset(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoDummyRendererDeinit(struct GBAVideoRenderer* renderer);
|
||||
static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
|
||||
static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
|
||||
static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
|
||||
|
||||
static struct GBAVideoRenderer dummyRenderer = {
|
||||
.init = GBAVideoDummyRendererInit,
|
||||
.reset = GBAVideoDummyRendererReset,
|
||||
.deinit = GBAVideoDummyRendererDeinit,
|
||||
.writeVideoRegister = GBAVideoDummyRendererWriteVideoRegister,
|
||||
.writePalette = GBAVideoDummyRendererWritePalette,
|
||||
.writeOAM = GBAVideoDummyRendererWriteOAM,
|
||||
.drawScanline = GBAVideoDummyRendererDrawScanline,
|
||||
.finishFrame = GBAVideoDummyRendererFinishFrame
|
||||
.finishFrame = GBAVideoDummyRendererFinishFrame,
|
||||
.getPixels = GBAVideoDummyRendererGetPixels
|
||||
};
|
||||
|
||||
void GBAVideoInit(struct GBAVideo* video) {
|
||||
|
@ -191,6 +197,19 @@ static uint16_t GBAVideoDummyRendererWriteVideoRegister(struct GBAVideoRenderer*
|
|||
return value;
|
||||
}
|
||||
|
||||
static void GBAVideoDummyRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(address);
|
||||
UNUSED(value);
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
static void GBAVideoDummyRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(oam);
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
static void GBAVideoDummyRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(y);
|
||||
|
@ -202,6 +221,14 @@ static void GBAVideoDummyRendererFinishFrame(struct GBAVideoRenderer* renderer)
|
|||
// Nothing to do
|
||||
}
|
||||
|
||||
static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
|
||||
UNUSED(renderer);
|
||||
UNUSED(stride);
|
||||
UNUSED(pixels);
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
|
||||
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state) {
|
||||
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
|
||||
memcpy(state->oam, video->oam.raw, SIZE_OAM);
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
|
||||
#include "gba-memory.h"
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
#define BYTES_PER_PIXEL 2
|
||||
#else
|
||||
#define BYTES_PER_PIXEL 4
|
||||
#endif
|
||||
|
||||
enum {
|
||||
VIDEO_CYCLES_PER_PIXEL = 4,
|
||||
|
||||
|
@ -178,6 +184,9 @@ struct GBAVideoRenderer {
|
|||
void (*drawScanline)(struct GBAVideoRenderer* renderer, int y);
|
||||
void (*finishFrame)(struct GBAVideoRenderer* renderer);
|
||||
|
||||
void (*getPixels)(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
|
||||
void (*putPixels)(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
|
||||
|
||||
uint16_t* palette;
|
||||
uint16_t* vram;
|
||||
union GBAOAM* oam;
|
||||
|
|
109
src/gba/gba.c
109
src/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 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
gba->keySource = 0;
|
||||
gba->rotationSource = 0;
|
||||
gba->rumble = 0;
|
||||
gba->rr = 0;
|
||||
|
||||
gba->romVf = 0;
|
||||
gba->biosVf = 0;
|
||||
|
@ -182,6 +187,9 @@ void GBAReset(struct ARMCore* cpu) {
|
|||
cpu->gprs[ARM_SP] = SP_BASE_SYSTEM;
|
||||
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
if (!GBARRIsPlaying(gba->rr) && !GBARRIsRecording(gba->rr)) {
|
||||
GBASavedataUnmask(&gba->memory.savedata);
|
||||
}
|
||||
GBAMemoryReset(gba);
|
||||
GBAVideoReset(&gba->video);
|
||||
GBAAudioReset(&gba->audio);
|
||||
|
@ -537,16 +545,15 @@ void GBAHalt(struct GBA* gba) {
|
|||
}
|
||||
|
||||
static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format, va_list args) {
|
||||
if (!gba) {
|
||||
struct GBAThread* threadContext = GBAThreadGetContext();
|
||||
if (threadContext) {
|
||||
struct GBAThread* threadContext = GBAThreadGetContext();
|
||||
if (threadContext) {
|
||||
if (!gba) {
|
||||
gba = threadContext->gba;
|
||||
}
|
||||
}
|
||||
|
||||
if (gba && gba->logHandler) {
|
||||
gba->logHandler(gba, level, format, args);
|
||||
return;
|
||||
if (threadContext->logHandler) {
|
||||
threadContext->logHandler(threadContext, level, format, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (gba && !(level & gba->logLevel) && level != GBA_LOG_FATAL) {
|
||||
|
@ -576,6 +583,7 @@ void GBADebuggerLogShim(struct ARMDebugger* debugger, enum DebuggerLogLevel leve
|
|||
|
||||
enum GBALogLevel gbaLevel;
|
||||
switch (level) {
|
||||
default: // Avoids compiler warning
|
||||
case DEBUGGER_LOG_DEBUG:
|
||||
gbaLevel = GBA_LOG_DEBUG;
|
||||
break;
|
||||
|
@ -626,6 +634,7 @@ void GBAIllegal(struct ARMCore* cpu, uint32_t opcode) {
|
|||
|
||||
void _checkOverrides(struct GBA* gba, uint32_t id) {
|
||||
int i;
|
||||
gba->busyLoop = -1;
|
||||
for (i = 0; _overrides[i].id[0]; ++i) {
|
||||
const uint32_t* overrideId = (const uint32_t*) _overrides[i].id;
|
||||
if (*overrideId == id) {
|
||||
|
@ -656,6 +665,8 @@ void _checkOverrides(struct GBA* gba, uint32_t id) {
|
|||
if (_overrides[i].gpio & GPIO_RUMBLE) {
|
||||
GBAGPIOInitRumble(&gba->memory.gpio);
|
||||
}
|
||||
|
||||
gba->busyLoop = _overrides[i].busyLoop;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ enum GBAKey {
|
|||
GBA_KEY_DOWN = 7,
|
||||
GBA_KEY_R = 8,
|
||||
GBA_KEY_L = 9,
|
||||
GBA_KEY_MAX,
|
||||
GBA_KEY_NONE = -1
|
||||
};
|
||||
|
||||
|
@ -65,8 +66,6 @@ struct GBARotationSource;
|
|||
struct Patch;
|
||||
struct VFile;
|
||||
|
||||
typedef void (*GBALogHandler)(struct GBA*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
struct GBA {
|
||||
struct ARMComponent d;
|
||||
|
||||
|
@ -96,6 +95,7 @@ struct GBA {
|
|||
int springIRQ;
|
||||
uint32_t biosChecksum;
|
||||
int* keySource;
|
||||
uint32_t busyLoop;
|
||||
struct GBARotationSource* rotationSource;
|
||||
struct GBARumble* rumble;
|
||||
struct GBARRContext* rr;
|
||||
|
@ -108,7 +108,6 @@ struct GBA {
|
|||
const char* activeFile;
|
||||
|
||||
int logLevel;
|
||||
GBALogHandler logHandler;
|
||||
};
|
||||
|
||||
struct GBACartridge {
|
||||
|
|
|
@ -2,49 +2,47 @@
|
|||
|
||||
#include "gba-memory.h"
|
||||
|
||||
const size_t hleBiosLength = 516;
|
||||
const uint8_t hleBios[SIZE_BIOS] = {
|
||||
0x06, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x05, 0x00, 0x00, 0xea,
|
||||
0xfe, 0xff, 0xff, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x00, 0x00, 0xa0, 0xe1,
|
||||
0x1a, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
|
||||
0x24, 0x00, 0x00, 0xea, 0xfe, 0xff, 0xff, 0xea, 0x02, 0xf3, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x5d, 0xe3, 0x01, 0xd3, 0xa0, 0x03, 0x20, 0xd0, 0x4d, 0x02,
|
||||
0x30, 0x40, 0x2d, 0xe9, 0x02, 0x40, 0x5e, 0xe5, 0x54, 0x50, 0xa0, 0xe3,
|
||||
0x04, 0x41, 0x95, 0xe7, 0x00, 0x00, 0x54, 0xe3, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0x14, 0xff, 0x2f, 0x11, 0x30, 0x40, 0xbd, 0xe8, 0x0e, 0xf0, 0xb0, 0xe1,
|
||||
0x30, 0x40, 0x2d, 0xe9, 0x02, 0x40, 0x5e, 0xe5, 0x7c, 0x50, 0xa0, 0xe3,
|
||||
0x04, 0x41, 0x95, 0xe7, 0x00, 0x00, 0x54, 0xe3, 0x00, 0x50, 0x4f, 0xe1,
|
||||
0x20, 0x00, 0x2d, 0xe9, 0x80, 0x50, 0x05, 0xe2, 0x1f, 0x50, 0x85, 0xe3,
|
||||
0x05, 0xf0, 0x29, 0xe1, 0x00, 0x40, 0x2d, 0xe9, 0x0f, 0xe0, 0xa0, 0xe1,
|
||||
0x14, 0xff, 0x2f, 0x11, 0x00, 0x40, 0xbd, 0xe8, 0x93, 0xf0, 0x29, 0xe3,
|
||||
0x20, 0x00, 0xbd, 0xe8, 0x05, 0xf0, 0x69, 0xe1, 0x30, 0x40, 0xbd, 0xe8,
|
||||
0x0e, 0xf0, 0xb0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00,
|
||||
0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00,
|
||||
0x94, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9, 0x01, 0x03, 0xa0, 0xe3,
|
||||
0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5, 0x0f, 0x50, 0xbd, 0xe8,
|
||||
0x04, 0xf0, 0x5e, 0xe2, 0x01, 0x00, 0xa0, 0xe3, 0x01, 0x10, 0xa0, 0xe3,
|
||||
0x0c, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0x4f, 0xe1, 0x1f, 0xf0, 0x29, 0xe3,
|
||||
0x01, 0x43, 0xa0, 0xe3, 0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3,
|
||||
0x01, 0x20, 0xa0, 0xe3, 0x00, 0x00, 0x00, 0x0a, 0x01, 0x03, 0xc4, 0xe5,
|
||||
0x08, 0x02, 0xc4, 0xe5, 0xb8, 0x30, 0x54, 0xe1, 0x01, 0x30, 0x13, 0xe0,
|
||||
0x01, 0x30, 0x23, 0x10, 0xb8, 0x30, 0x44, 0x11, 0x08, 0x22, 0xc4, 0xe5,
|
||||
0xf7, 0xff, 0xff, 0x0a, 0x93, 0xf0, 0x29, 0xe3, 0x05, 0xf0, 0x69, 0xe1,
|
||||
0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x00, 0x50, 0x4f, 0xe1,
|
||||
0x1f, 0xf0, 0x29, 0xe3, 0x02, 0x36, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3,
|
||||
0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
|
||||
0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8, 0x03, 0x00, 0x51, 0xe1,
|
||||
0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x16, 0x00, 0x00, 0xea,
|
||||
0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0xa3, 0x35, 0x81, 0xe0,
|
||||
0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1, 0xb2, 0x20, 0xc1, 0xb0,
|
||||
0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3,
|
||||
0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x03, 0x00, 0x51, 0xe1,
|
||||
0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba,
|
||||
0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0, 0x01, 0x00, 0xc0, 0xe3,
|
||||
0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1, 0xb2, 0x20, 0xd0, 0xb0,
|
||||
0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba, 0x93, 0xf0, 0x29, 0xe3,
|
||||
0x05, 0xf0, 0x69, 0xe1, 0x00, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9,
|
||||
0x00, 0x50, 0x4f, 0xe1, 0x1f, 0xf0, 0x29, 0xe3, 0xf0, 0x07, 0x2d, 0xe9,
|
||||
0x01, 0x04, 0x12, 0xe3, 0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0,
|
||||
0x0b, 0x00, 0x00, 0x0a, 0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1,
|
||||
0x03, 0x50, 0xa0, 0xe1, 0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1,
|
||||
0x03, 0x80, 0xa0, 0xe1, 0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1,
|
||||
0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba,
|
||||
0x03, 0x00, 0x00, 0xea, 0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8,
|
||||
0xf8, 0x07, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba, 0xf0, 0x07, 0xbd, 0xe8,
|
||||
0x93, 0xf0, 0x29, 0xe3, 0x05, 0xf0, 0x69, 0xe1, 0x00, 0x80, 0xbd, 0xe8
|
||||
0x0c, 0x01, 0x00, 0x00, 0x9c, 0x01, 0x00, 0x00, 0x0f, 0x50, 0x2d, 0xe9,
|
||||
0x01, 0x03, 0xa0, 0xe3, 0x00, 0xe0, 0x8f, 0xe2, 0x04, 0xf0, 0x10, 0xe5,
|
||||
0x0f, 0x50, 0xbd, 0xe8, 0x04, 0xf0, 0x5e, 0xe2, 0x01, 0x00, 0xa0, 0xe3,
|
||||
0x01, 0x10, 0xa0, 0xe3, 0x0c, 0x40, 0x2d, 0xe9, 0x01, 0x43, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x50, 0xe3, 0x00, 0x00, 0xa0, 0xe3, 0x01, 0x20, 0xa0, 0xe3,
|
||||
0x00, 0x00, 0x00, 0x0a, 0x01, 0x03, 0xc4, 0xe5, 0x08, 0x02, 0xc4, 0xe5,
|
||||
0xb8, 0x30, 0x54, 0xe1, 0x01, 0x30, 0x13, 0xe0, 0x01, 0x30, 0x23, 0x10,
|
||||
0xb8, 0x30, 0x44, 0x11, 0x08, 0x22, 0xc4, 0xe5, 0xf7, 0xff, 0xff, 0x0a,
|
||||
0x0c, 0x80, 0xbd, 0xe8, 0x00, 0x40, 0x2d, 0xe9, 0x02, 0x36, 0xa0, 0xe1,
|
||||
0x01, 0x04, 0x12, 0xe3, 0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3,
|
||||
0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0, 0x04, 0x00, 0xb0, 0xe8,
|
||||
0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba,
|
||||
0x16, 0x00, 0x00, 0xea, 0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3,
|
||||
0xa3, 0x35, 0x81, 0xe0, 0xb0, 0x20, 0xd0, 0xe1, 0x03, 0x00, 0x51, 0xe1,
|
||||
0xb2, 0x20, 0xc1, 0xb0, 0xfc, 0xff, 0xff, 0xba, 0x0e, 0x00, 0x00, 0xea,
|
||||
0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a, 0x23, 0x35, 0x81, 0xe0,
|
||||
0x03, 0x00, 0x51, 0xe1, 0x04, 0x00, 0xb0, 0xb8, 0x04, 0x00, 0xa1, 0xb8,
|
||||
0xfb, 0xff, 0xff, 0xba, 0x06, 0x00, 0x00, 0xea, 0xa3, 0x35, 0x81, 0xe0,
|
||||
0x01, 0x00, 0xc0, 0xe3, 0x01, 0x10, 0xc1, 0xe3, 0x03, 0x00, 0x51, 0xe1,
|
||||
0xb2, 0x20, 0xd0, 0xb0, 0xb2, 0x20, 0xc1, 0xb0, 0xfb, 0xff, 0xff, 0xba,
|
||||
0x00, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9, 0x01, 0x04, 0x12, 0xe3,
|
||||
0x02, 0x36, 0xa0, 0xe1, 0x23, 0x25, 0x81, 0xe0, 0x0b, 0x00, 0x00, 0x0a,
|
||||
0x00, 0x30, 0x90, 0xe5, 0x03, 0x40, 0xa0, 0xe1, 0x03, 0x50, 0xa0, 0xe1,
|
||||
0x03, 0x60, 0xa0, 0xe1, 0x03, 0x70, 0xa0, 0xe1, 0x03, 0x80, 0xa0, 0xe1,
|
||||
0x03, 0x90, 0xa0, 0xe1, 0x03, 0xa0, 0xa0, 0xe1, 0x02, 0x00, 0x51, 0xe1,
|
||||
0xf8, 0x07, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x03, 0x00, 0x00, 0xea,
|
||||
0x02, 0x00, 0x51, 0xe1, 0xf8, 0x07, 0xb0, 0xb8, 0xf8, 0x07, 0xa1, 0xb8,
|
||||
0xfb, 0xff, 0xff, 0xba, 0xf0, 0x87, 0xbd, 0xe8
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "common.h"
|
||||
|
||||
extern const size_t hleBiosLength;
|
||||
extern const uint8_t hleBios[];
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -29,6 +29,8 @@ static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* render
|
|||
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* renderer, int y);
|
||||
static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels);
|
||||
static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels);
|
||||
|
||||
static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererWriteBGCNT(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* bg, uint16_t value);
|
||||
|
@ -67,6 +69,8 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
|||
renderer->d.writePalette = GBAVideoSoftwareRendererWritePalette;
|
||||
renderer->d.drawScanline = GBAVideoSoftwareRendererDrawScanline;
|
||||
renderer->d.finishFrame = GBAVideoSoftwareRendererFinishFrame;
|
||||
renderer->d.getPixels = GBAVideoSoftwareRendererGetPixels;
|
||||
renderer->d.putPixels = GBAVideoSoftwareRendererPutPixels;
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
|
||||
|
@ -501,6 +505,23 @@ static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* rendere
|
|||
softwareRenderer->bg[3].sy = softwareRenderer->bg[3].refy;
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererGetPixels(struct GBAVideoRenderer* renderer, unsigned* stride, void** pixels) {
|
||||
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||
|
||||
*stride = softwareRenderer->outputBufferStride;
|
||||
*pixels = softwareRenderer->outputBuffer;
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer, unsigned stride, void* pixels) {
|
||||
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||
|
||||
uint32_t* colorPixels = pixels;
|
||||
unsigned i;
|
||||
for (i = 0; i < VIDEO_VERTICAL_PIXELS; ++i) {
|
||||
memmove(&softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * i], &colorPixels[stride * i], VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererUpdateDISPCNT(struct GBAVideoSoftwareRenderer* renderer) {
|
||||
renderer->bg[0].enabled = renderer->dispcnt.bg0Enable;
|
||||
renderer->bg[1].enabled = renderer->dispcnt.bg1Enable;
|
||||
|
@ -767,10 +788,6 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
#define COMPOSITE_256_NO_OBJWIN(BLEND) \
|
||||
COMPOSITE_16_NO_OBJWIN(BLEND)
|
||||
|
||||
|
||||
#define COMPOSITE_16_NO_OBJWIN(BLEND) \
|
||||
_composite ## BLEND ## NoObjwin(renderer, pixel, palette[pixelData] | flags, current);
|
||||
|
||||
#define BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN) \
|
||||
pixelData = tileData & 0xF; \
|
||||
current = *pixel; \
|
||||
|
@ -794,7 +811,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
xBase += (localX & 0x100) << 5; \
|
||||
} \
|
||||
screenBase = yBase + (xBase >> 3); \
|
||||
mapData = renderer->d.vram[screenBase]; \
|
||||
mapData = vram[screenBase]; \
|
||||
localY = inY & 0x7; \
|
||||
if (GBA_TEXT_MAP_VFLIP(mapData)) { \
|
||||
localY = 7 - localY; \
|
||||
|
@ -835,7 +852,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) >> 2) + localY; \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= 4 * mod8; \
|
||||
for (; outX < end; ++outX) { \
|
||||
|
@ -851,7 +868,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
|
||||
#define DRAW_BACKGROUND_MODE_0_TILE_PREFIX_16(BLEND, OBJWIN) \
|
||||
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) >> 2) + localY; \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
|
@ -885,7 +902,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
if (!mosaicWait) { \
|
||||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData >>= x * 4; \
|
||||
} else { \
|
||||
|
@ -914,7 +931,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
paletteData = GBA_TEXT_MAP_PALETTE(mapData) << 4; \
|
||||
palette = &mainPalette[paletteData]; \
|
||||
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 5)) >> 2) + localY; \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
if (tileData) { \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
BACKGROUND_DRAW_PIXEL_16(BLEND, OBJWIN); \
|
||||
|
@ -963,7 +980,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
int end2 = end - 4; \
|
||||
int shift = inX & 0x3; \
|
||||
if (end2 > 0) { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
tileData >>= 8 * shift; \
|
||||
shift = 0; \
|
||||
for (; outX < end2; ++outX) { \
|
||||
|
@ -972,7 +989,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
} \
|
||||
} \
|
||||
\
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
|
||||
tileData = ((uint32_t*) vram)[charBase + 1]; \
|
||||
tileData >>= 8 * shift; \
|
||||
for (; outX < end; ++outX) { \
|
||||
uint32_t* pixel = &renderer->row[outX]; \
|
||||
|
@ -985,7 +1002,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
outX = renderer->end - 8 + end; \
|
||||
int end2 = 4 - end; \
|
||||
if (end2 > 0) { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
for (; outX < renderer->end - end2; ++outX) { \
|
||||
uint32_t* pixel = &renderer->row[outX]; \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
|
@ -993,7 +1010,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
++charBase; \
|
||||
} \
|
||||
\
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
for (; outX < renderer->end; ++outX) { \
|
||||
uint32_t* pixel = &renderer->row[outX]; \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
|
@ -1004,7 +1021,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
charBase = ((background->charBase + (GBA_TEXT_MAP_TILE(mapData) << 6)) >> 2) + (localY << 1); \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
if (tileData) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
++pixel; \
|
||||
|
@ -1017,7 +1034,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
} else { \
|
||||
pixel += 4; \
|
||||
} \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
|
||||
tileData = ((uint32_t*) vram)[charBase + 1]; \
|
||||
if (tileData) { \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
++pixel; \
|
||||
|
@ -1031,7 +1048,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
pixel += 4; \
|
||||
} \
|
||||
} else { \
|
||||
uint32_t tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
|
||||
uint32_t tileData = ((uint32_t*) vram)[charBase + 1]; \
|
||||
if (tileData) { \
|
||||
pixel += 3; \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
|
@ -1043,7 +1060,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
} \
|
||||
pixel += 4; \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
if (tileData) { \
|
||||
pixel += 3; \
|
||||
BACKGROUND_DRAW_PIXEL_256(BLEND, OBJWIN); \
|
||||
|
@ -1067,18 +1084,18 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
if (!mosaicWait) { \
|
||||
if (!GBA_TEXT_MAP_HFLIP(mapData)) { \
|
||||
if (x >= 4) { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
|
||||
tileData = ((uint32_t*) vram)[charBase + 1]; \
|
||||
tileData >>= (x - 4) * 8; \
|
||||
} else { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
tileData >>= x * 8; \
|
||||
} \
|
||||
} else { \
|
||||
if (x >= 4) { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase]; \
|
||||
tileData = ((uint32_t*) vram)[charBase]; \
|
||||
tileData >>= (7 - x) * 8; \
|
||||
} else { \
|
||||
tileData = ((uint32_t*)renderer->d.vram)[charBase + 1]; \
|
||||
tileData = ((uint32_t*) vram)[charBase + 1]; \
|
||||
tileData >>= (3 - x) * 8; \
|
||||
} \
|
||||
} \
|
||||
|
@ -1094,6 +1111,17 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
}
|
||||
|
||||
#define DRAW_BACKGROUND_MODE_0(BPP, BLEND, OBJWIN) \
|
||||
uint32_t* pixel = &renderer->row[outX]; \
|
||||
if (background->mosaic && renderer->mosaic.bgH) { \
|
||||
int mosaicH = renderer->mosaic.bgH + 1; \
|
||||
int x; \
|
||||
int mosaicWait = outX % mosaicH; \
|
||||
int carryData = 0; \
|
||||
paletteData = 0; /* Quiets compiler warning */ \
|
||||
DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
if (inX & 0x7) { \
|
||||
int mod8 = inX & 0x7; \
|
||||
BACKGROUND_TEXT_SELECT_CHARACTER; \
|
||||
|
@ -1103,6 +1131,9 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
/* TODO: ensure tiles are properly aligned from this*/ \
|
||||
end = renderer->end; \
|
||||
} \
|
||||
if (end == outX) { \
|
||||
return; \
|
||||
} \
|
||||
DRAW_BACKGROUND_MODE_0_TILE_SUFFIX_ ## BPP (BLEND, OBJWIN) \
|
||||
} \
|
||||
if (inX & 0x7 || (renderer->end - renderer->start) & 0x7) { \
|
||||
|
@ -1119,17 +1150,7 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
|
|||
outX = renderer->start + tileX * 8 - (inX & 0x7); \
|
||||
} \
|
||||
\
|
||||
uint32_t* pixel = &renderer->row[outX]; \
|
||||
if (background->mosaic && renderer->mosaic.bgH) { \
|
||||
int mosaicH = renderer->mosaic.bgH + 1; \
|
||||
int x; \
|
||||
int mosaicWait = outX % mosaicH; \
|
||||
int carryData = 0; \
|
||||
paletteData = 0; /* Quiets compiler warning */ \
|
||||
DRAW_BACKGROUND_MODE_0_MOSAIC_ ## BPP (BLEND, OBJWIN) \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
pixel = &renderer->row[outX]; \
|
||||
DRAW_BACKGROUND_MODE_0_TILES_ ## BPP (BLEND, OBJWIN)
|
||||
|
||||
static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int y) {
|
||||
|
@ -1176,6 +1197,7 @@ static void _drawBackgroundMode0(struct GBAVideoSoftwareRenderer* renderer, stru
|
|||
int paletteData;
|
||||
int tileX = 0;
|
||||
int tileEnd = (renderer->end - renderer->start + (inX & 0x7)) >> 3;
|
||||
uint16_t* vram = renderer->d.vram;
|
||||
|
||||
if (!objwinSlowPath) {
|
||||
if (!(flags & FLAG_TARGET_2)) {
|
||||
|
|
|
@ -50,7 +50,7 @@ bool parseCommandArgs(struct StartupOptions* opts, int argc, char* const* argv,
|
|||
"g"
|
||||
#endif
|
||||
;
|
||||
if (subparser->extraOptions) {
|
||||
if (subparser && subparser->extraOptions) {
|
||||
// TODO: modularize options to subparsers
|
||||
strncat(options, subparser->extraOptions, sizeof(options) - strlen(options) - 1);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -7,18 +7,20 @@
|
|||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
#define PERF_OPTIONS "S:"
|
||||
#define PERF_OPTIONS "NS:"
|
||||
#define PERF_USAGE \
|
||||
"\nBenchmark options:\n" \
|
||||
" -N Disable video rendering entirely" \
|
||||
" -S SEC Run for SEC in-game seconds before exiting"
|
||||
|
||||
struct PerfOpts {
|
||||
bool noVideo;
|
||||
int duration;
|
||||
};
|
||||
|
||||
static void _GBAPerfRunloop(struct GBAThread* context, int* frames);
|
||||
static void _GBAPerfShutdown(int signal);
|
||||
static int _parsePerfOpts(struct SubParser* parser, int option, const char* arg);
|
||||
static bool _parsePerfOpts(struct SubParser* parser, int option, const char* arg);
|
||||
|
||||
static struct GBAThread* _thread;
|
||||
|
||||
|
@ -28,7 +30,7 @@ int main(int argc, char** argv) {
|
|||
struct GBAVideoSoftwareRenderer renderer;
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
|
||||
struct PerfOpts perfOpts = { 0 };
|
||||
struct PerfOpts perfOpts = { false, 0 };
|
||||
struct SubParser subparser = {
|
||||
.usage = PERF_USAGE,
|
||||
.parse = _parsePerfOpts,
|
||||
|
@ -46,12 +48,15 @@ int main(int argc, char** argv) {
|
|||
renderer.outputBufferStride = 256;
|
||||
|
||||
struct GBAThread context = {
|
||||
.renderer = &renderer.d,
|
||||
.sync.videoFrameWait = 0,
|
||||
.sync.audioWait = 0
|
||||
};
|
||||
_thread = &context;
|
||||
|
||||
if (!perfOpts.noVideo) {
|
||||
context.renderer = &renderer.d;
|
||||
}
|
||||
|
||||
context.debugger = createDebugger(&opts);
|
||||
|
||||
GBAMapOptionsToContext(&opts, &context);
|
||||
|
@ -113,13 +118,16 @@ static void _GBAPerfShutdown(int signal) {
|
|||
pthread_mutex_unlock(&_thread->stateMutex);
|
||||
}
|
||||
|
||||
static int _parsePerfOpts(struct SubParser* parser, int option, const char* arg) {
|
||||
static bool _parsePerfOpts(struct SubParser* parser, int option, const char* arg) {
|
||||
struct PerfOpts* opts = parser->opts;
|
||||
switch (option) {
|
||||
case 'N':
|
||||
opts->noVideo = true;
|
||||
return true;
|
||||
case 'S':
|
||||
opts->duration = strtol(arg, 0, 10);
|
||||
return !errno;
|
||||
default:
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,11 +21,6 @@ GameController::GameController(QObject* parent)
|
|||
, m_audioThread(new QThread(this))
|
||||
, m_audioProcessor(new AudioProcessor)
|
||||
{
|
||||
#ifdef BUILD_SDL
|
||||
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
|
||||
GBASDLInitEvents(&m_sdlEvents);
|
||||
SDL_JoystickEventState(SDL_QUERY);
|
||||
#endif
|
||||
m_renderer = new GBAVideoSoftwareRenderer;
|
||||
GBAVideoSoftwareRendererCreate(m_renderer);
|
||||
m_renderer->outputBuffer = (color_t*) m_drawContext;
|
||||
|
@ -39,6 +34,16 @@ GameController::GameController(QObject* parent)
|
|||
.userData = this,
|
||||
.rewindBufferCapacity = 0
|
||||
};
|
||||
|
||||
GBAInputMapInit(&m_threadContext.inputMap);
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE);
|
||||
m_sdlEvents.bindings = &m_threadContext.inputMap;
|
||||
GBASDLInitEvents(&m_sdlEvents);
|
||||
SDL_JoystickEventState(SDL_QUERY);
|
||||
#endif
|
||||
|
||||
m_threadContext.startCallback = [] (GBAThread* context) {
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
controller->m_audioProcessor->setInput(context);
|
||||
|
@ -204,7 +209,7 @@ void GameController::testSDLEvents() {
|
|||
m_activeButtons = 0;
|
||||
int i;
|
||||
for (i = 0; i < numButtons; ++i) {
|
||||
GBAKey key = GBASDLMapButtonToKey(i);
|
||||
GBAKey key = GBAInputMapKey(&m_threadContext.inputMap, SDL_BINDING_BUTTON, i);
|
||||
if (key == GBA_KEY_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND)
|
|||
endif()
|
||||
|
||||
file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c)
|
||||
set(PLATFORM_LIBRARY "${SDL_LIBRARY};${SDLMAIN_LIBRARY}")
|
||||
set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR})
|
||||
|
||||
if(BUILD_RASPI)
|
||||
|
@ -26,6 +26,7 @@ if(BUILD_RASPI)
|
|||
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
|
||||
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${EGL_MAIN_SRC})
|
||||
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
|
||||
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin)
|
||||
endif()
|
||||
|
||||
if(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL)
|
||||
|
@ -39,3 +40,4 @@ endif()
|
|||
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
|
||||
target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY})
|
||||
set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME})
|
||||
install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin)
|
||||
|
|
|
@ -68,41 +68,46 @@ static void _GBASDLStart(struct GBAThread* context);
|
|||
static void _GBASDLClean(struct GBAThread* context);
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
const char* fname = "test.rom";
|
||||
if (argc > 1) {
|
||||
fname = argv[1];
|
||||
}
|
||||
int fd = open(fname, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
struct GBAVideoEGLRenderer renderer;
|
||||
|
||||
struct StartupOptions opts;
|
||||
if (!parseCommandArgs(&opts, argc, argv, 0)) {
|
||||
usage(argv[0], 0);
|
||||
freeOptions(&opts);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct GBAVideoEGLRenderer renderer;
|
||||
|
||||
if (!_GBAEGLInit(&renderer)) {
|
||||
return 1;
|
||||
}
|
||||
GBAVideoSoftwareRendererCreate(&renderer.d);
|
||||
|
||||
struct GBAThread context = {
|
||||
.fd = fd,
|
||||
.fname = fname,
|
||||
.biosFd = -1,
|
||||
.useDebugger = 0,
|
||||
.renderer = &renderer.d.d,
|
||||
.frameskip = 0,
|
||||
.sync.videoFrameWait = 0,
|
||||
.sync.audioWait = 0,
|
||||
.sync.audioWait = 1,
|
||||
.startCallback = _GBASDLStart,
|
||||
.cleanCallback = _GBASDLClean,
|
||||
.userData = &renderer
|
||||
};
|
||||
|
||||
context.debugger = createDebugger(&opts);
|
||||
|
||||
GBAMapOptionsToContext(&opts, &context);
|
||||
|
||||
renderer.audio.samples = context.audioBuffers;
|
||||
GBASDLInitAudio(&renderer.audio);
|
||||
|
||||
renderer.events.bindings = &context.inputMap;
|
||||
GBASDLInitEvents(&renderer.events);
|
||||
|
||||
GBAThreadStart(&context);
|
||||
|
||||
_GBAEGLRunloop(&context, &renderer);
|
||||
|
||||
GBAThreadJoin(&context);
|
||||
close(fd);
|
||||
freeOptions(&opts);
|
||||
free(context.debugger);
|
||||
|
||||
_GBAEGLDeinit(&renderer);
|
||||
|
||||
|
@ -114,9 +119,6 @@ static int _GBAEGLInit(struct GBAVideoEGLRenderer* renderer) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
GBASDLInitEvents(&renderer->events);
|
||||
GBASDLInitAudio(&renderer->audio);
|
||||
|
||||
bcm_host_init();
|
||||
renderer->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
int major, minor;
|
||||
|
@ -238,7 +240,7 @@ static void _GBAEGLRunloop(struct GBAThread* context, struct GBAVideoEGLRenderer
|
|||
GBASyncWaitFrameEnd(&context->sync);
|
||||
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &event);
|
||||
GBASDLHandleEvent(context, &renderer->events, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,6 +261,7 @@ static void _GBAEGLDeinit(struct GBAVideoEGLRenderer* renderer) {
|
|||
static void _GBASDLStart(struct GBAThread* threadContext) {
|
||||
struct GBAVideoEGLRenderer* renderer = threadContext->userData;
|
||||
renderer->audio.audio = &threadContext->gba->audio;
|
||||
renderer->audio.thread = threadContext;
|
||||
}
|
||||
|
||||
static void _GBASDLClean(struct GBAThread* threadContext) {
|
||||
|
|
|
@ -100,6 +100,9 @@ int main(int argc, char** argv) {
|
|||
renderer.audio.samples = context.audioBuffers;
|
||||
GBASDLInitAudio(&renderer.audio);
|
||||
|
||||
renderer.events.bindings = &context.inputMap;
|
||||
GBASDLInitEvents(&renderer.events);
|
||||
|
||||
GBAThreadStart(&context);
|
||||
|
||||
_GBASDLRunloop(&context, &renderer);
|
||||
|
@ -118,7 +121,6 @@ static int _GBASDLInit(struct GLSoftwareRenderer* renderer) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
GBASDLInitEvents(&renderer->events);
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
#include "gba-rr.h"
|
||||
#include "gba-serialize.h"
|
||||
#include "gba-video.h"
|
||||
#include "renderers/video-software.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__)
|
||||
#define GUI_MOD KMOD_GUI
|
||||
|
@ -21,6 +23,28 @@ bool GBASDLInitEvents(struct GBASDLEvents* context) {
|
|||
#if !SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
|
||||
#endif
|
||||
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_z, GBA_KEY_A);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_x, GBA_KEY_B);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_a, GBA_KEY_L);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_s, GBA_KEY_R);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_RETURN, GBA_KEY_START);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_BACKSPACE, GBA_KEY_SELECT);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_UP, GBA_KEY_UP);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_DOWN, GBA_KEY_DOWN);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_LEFT, GBA_KEY_LEFT);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_KEY, SDLK_RIGHT, GBA_KEY_RIGHT);
|
||||
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 13, GBA_KEY_A);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 14, GBA_KEY_B);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 10, GBA_KEY_L);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 11, GBA_KEY_R);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 3, GBA_KEY_START);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 0, GBA_KEY_SELECT);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 4, GBA_KEY_UP);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 6, GBA_KEY_DOWN);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 7, GBA_KEY_LEFT);
|
||||
GBAInputBindKey(context->bindings, SDL_BINDING_BUTTON, 5, GBA_KEY_RIGHT);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -29,72 +53,47 @@ void GBASDLDeinitEvents(struct GBASDLEvents* context) {
|
|||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
|
||||
enum GBAKey GBASDLMapButtonToKey(int button) {
|
||||
// Sorry, hardcoded to my gamepad for now
|
||||
switch (button) {
|
||||
case 2:
|
||||
return GBA_KEY_A;
|
||||
case 1:
|
||||
return GBA_KEY_B;
|
||||
case 6:
|
||||
return GBA_KEY_L;
|
||||
case 7:
|
||||
return GBA_KEY_R;
|
||||
case 8:
|
||||
return GBA_KEY_START;
|
||||
case 9:
|
||||
return GBA_KEY_SELECT;
|
||||
default:
|
||||
return GBA_KEY_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void _pauseAfterFrame(struct GBAThread* context) {
|
||||
context->frameCallback = 0;
|
||||
GBAThreadPauseFromThread(context);
|
||||
}
|
||||
|
||||
static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_KeyboardEvent* event) {
|
||||
enum GBAKey key = 0;
|
||||
enum GBAKey key = GBA_KEY_NONE;
|
||||
if (!event->keysym.mod) {
|
||||
key = GBAInputMapKey(&context->inputMap, SDL_BINDING_KEY, event->keysym.sym);
|
||||
}
|
||||
if (key != GBA_KEY_NONE) {
|
||||
if (event->type == SDL_KEYDOWN) {
|
||||
context->activeKeys |= 1 << key;
|
||||
} else {
|
||||
context->activeKeys &= ~(1 << key);
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (event->keysym.sym) {
|
||||
case SDLK_z:
|
||||
key = GBA_KEY_A;
|
||||
break;
|
||||
case SDLK_x:
|
||||
key = GBA_KEY_B;
|
||||
break;
|
||||
case SDLK_a:
|
||||
key = GBA_KEY_L;
|
||||
break;
|
||||
case SDLK_s:
|
||||
key = GBA_KEY_R;
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
key = GBA_KEY_START;
|
||||
break;
|
||||
case SDLK_BACKSPACE:
|
||||
key = GBA_KEY_SELECT;
|
||||
break;
|
||||
case SDLK_UP:
|
||||
key = GBA_KEY_UP;
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
key = GBA_KEY_DOWN;
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
key = GBA_KEY_LEFT;
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
key = GBA_KEY_RIGHT;
|
||||
break;
|
||||
case SDLK_F11:
|
||||
if (event->type == SDL_KEYDOWN && context->debugger) {
|
||||
ARMDebuggerEnter(context->debugger, DEBUGGER_ENTER_MANUAL);
|
||||
}
|
||||
return;
|
||||
case SDLK_F12:
|
||||
if (event->type == SDL_KEYDOWN) {
|
||||
GBAThreadInterrupt(context);
|
||||
GBAThreadTakeScreenshot(context);
|
||||
GBAThreadContinue(context);
|
||||
}
|
||||
return;
|
||||
case SDLK_TAB:
|
||||
context->sync.audioWait = event->type != SDL_KEYDOWN;
|
||||
return;
|
||||
case SDLK_BACKSLASH:
|
||||
if (event->type == SDL_KEYDOWN) {
|
||||
GBAThreadPause(context);
|
||||
context->frameCallback = _pauseAfterFrame;
|
||||
GBAThreadUnpause(context);
|
||||
}
|
||||
return;
|
||||
case SDLK_LEFTBRACKET:
|
||||
GBAThreadInterrupt(context);
|
||||
GBARewind(context, 10);
|
||||
|
@ -102,13 +101,15 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
return;
|
||||
case SDLK_ESCAPE:
|
||||
GBAThreadInterrupt(context);
|
||||
GBARRStopPlaying(context->gba->rr);
|
||||
GBARRStopRecording(context->gba->rr);
|
||||
if (context->gba->rr) {
|
||||
GBARRStopPlaying(context->gba->rr);
|
||||
GBARRStopRecording(context->gba->rr);
|
||||
}
|
||||
GBAThreadContinue(context);
|
||||
return;
|
||||
default:
|
||||
if (event->type == SDL_KEYDOWN) {
|
||||
if (event->keysym.mod & GUI_MOD) {
|
||||
if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
|
||||
switch (event->keysym.sym) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
case SDLK_f:
|
||||
|
@ -129,22 +130,29 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
GBAThreadReset(context);
|
||||
break;
|
||||
case SDLK_t:
|
||||
GBAThreadReset(context);
|
||||
GBAThreadInterrupt(context);
|
||||
GBARRContextCreate(context->gba);
|
||||
GBARRSetStream(context->gba->rr, context->stateDir);
|
||||
GBARRStopPlaying(context->gba->rr);
|
||||
GBARRStartRecording(context->gba->rr);
|
||||
GBAThreadContinue(context);
|
||||
if (context->stateDir) {
|
||||
GBAThreadInterrupt(context);
|
||||
GBARRContextCreate(context->gba);
|
||||
if (!GBARRIsRecording(context->gba->rr)) {
|
||||
GBARRStopPlaying(context->gba->rr);
|
||||
GBARRInitStream(context->gba->rr, context->stateDir);
|
||||
GBARRReinitStream(context->gba->rr, INIT_EX_NIHILO);
|
||||
GBARRStartRecording(context->gba->rr);
|
||||
GBARRSaveState(context->gba);
|
||||
}
|
||||
GBAThreadContinue(context);
|
||||
}
|
||||
break;
|
||||
case SDLK_y:
|
||||
GBAThreadReset(context);
|
||||
GBAThreadInterrupt(context);
|
||||
GBARRContextCreate(context->gba);
|
||||
GBARRSetStream(context->gba->rr, context->stateDir);
|
||||
GBARRStopRecording(context->gba->rr);
|
||||
GBARRStartPlaying(context->gba->rr, event->keysym.mod & KMOD_SHIFT);
|
||||
GBAThreadContinue(context);
|
||||
if (context->stateDir) {
|
||||
GBAThreadInterrupt(context);
|
||||
GBARRContextCreate(context->gba);
|
||||
GBARRStopRecording(context->gba->rr);
|
||||
GBARRInitStream(context->gba->rr, context->stateDir);
|
||||
GBARRStartPlaying(context->gba->rr, false);
|
||||
GBARRLoadState(context->gba);
|
||||
GBAThreadContinue(context);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -163,7 +171,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
case SDLK_F9:
|
||||
case SDLK_F10:
|
||||
GBAThreadInterrupt(context);
|
||||
GBASaveState(context->gba, event->keysym.sym - SDLK_F1);
|
||||
GBASaveState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1, true);
|
||||
GBAThreadContinue(context);
|
||||
break;
|
||||
default:
|
||||
|
@ -182,7 +190,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
case SDLK_F9:
|
||||
case SDLK_F10:
|
||||
GBAThreadInterrupt(context);
|
||||
GBALoadState(context->gba, event->keysym.sym - SDLK_F1);
|
||||
GBALoadState(context->gba, context->stateDir, event->keysym.sym - SDLK_F1);
|
||||
GBAThreadContinue(context);
|
||||
break;
|
||||
default:
|
||||
|
@ -192,17 +200,11 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->type == SDL_KEYDOWN) {
|
||||
context->activeKeys |= 1 << key;
|
||||
} else {
|
||||
context->activeKeys &= ~(1 << key);
|
||||
}
|
||||
}
|
||||
|
||||
static void _GBASDLHandleJoyButton(struct GBAThread* context, const struct SDL_JoyButtonEvent* event) {
|
||||
enum GBAKey key = 0;
|
||||
key = GBASDLMapButtonToKey(event->button);
|
||||
key = GBAInputMapKey(&context->inputMap, SDL_BINDING_BUTTON, event->button);
|
||||
if (key == GBA_KEY_NONE) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,13 @@
|
|||
|
||||
#include <SDL.h>
|
||||
|
||||
#define SDL_BINDING_KEY 0x53444C4B
|
||||
#define SDL_BINDING_BUTTON 0x53444C42
|
||||
|
||||
struct GBAVideoSoftwareRenderer;
|
||||
|
||||
struct GBASDLEvents {
|
||||
struct GBAInputMap* bindings;
|
||||
SDL_Joystick* joystick;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_Window* window;
|
||||
|
@ -21,6 +27,4 @@ void GBASDLDeinitEvents(struct GBASDLEvents*);
|
|||
|
||||
void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event);
|
||||
|
||||
enum GBAKey GBASDLMapButtonToKey(int button);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -99,11 +99,7 @@ int main(int argc, char** argv) {
|
|||
#endif
|
||||
|
||||
SDL_LockTexture(renderer.tex, 0, &renderer.d.outputBuffer, &renderer.d.outputBufferStride);
|
||||
#ifdef COLOR_16_BIT
|
||||
renderer.d.outputBufferStride /= 2;
|
||||
#else
|
||||
renderer.d.outputBufferStride /= 4;
|
||||
#endif
|
||||
renderer.d.outputBufferStride /= BYTES_PER_PIXEL;
|
||||
#else
|
||||
SDL_Surface* surface = SDL_GetVideoSurface();
|
||||
SDL_LockSurface(surface);
|
||||
|
@ -117,11 +113,7 @@ int main(int argc, char** argv) {
|
|||
renderer.d.outputBufferStride = surface->pitch / 4;
|
||||
#endif
|
||||
} else {
|
||||
#ifdef COLOR_16_BIT
|
||||
renderer.d.outputBuffer = malloc(240 * 160 * 2);
|
||||
#else
|
||||
renderer.d.outputBuffer = malloc(240 * 160 * 4);
|
||||
#endif
|
||||
renderer.d.outputBuffer = malloc(240 * 160 * BYTES_PER_PIXEL);
|
||||
renderer.d.outputBufferStride = 240;
|
||||
}
|
||||
#endif
|
||||
|
@ -173,11 +165,7 @@ static void _GBASDLRunloop(struct GBAThread* context, struct SoftwareRenderer* r
|
|||
SDL_RenderCopy(renderer->sdlRenderer, renderer->tex, 0, 0);
|
||||
SDL_RenderPresent(renderer->sdlRenderer);
|
||||
SDL_LockTexture(renderer->tex, 0, &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
|
||||
#ifdef COLOR_16_BIT
|
||||
renderer->d.outputBufferStride /= 2;
|
||||
#else
|
||||
renderer->d.outputBufferStride /= 4;
|
||||
#endif
|
||||
renderer->d.outputBufferStride /= BYTES_PER_PIXEL;
|
||||
#else
|
||||
switch (renderer->ratio) {
|
||||
#if defined(__ARM_NEON) && COLOR_16_BIT
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -104,6 +104,8 @@ off_t _vfzSeek(struct VFile* vf, off_t offset, int whence) {
|
|||
}
|
||||
position = vfz->fileSize + offset;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (position <= vfz->offset) {
|
||||
|
|
Loading…
Reference in New Issue