mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/sio-lockstep
Conflicts: CMakeLists.txt src/gba/gba.c
This commit is contained in:
commit
51b8c862b9
6
CHANGES
6
CHANGES
|
@ -26,6 +26,8 @@ Features:
|
|||
- Debugger: Add CLI functions for examining memory regions
|
||||
- Runtime configurable audio driver
|
||||
- Debugger: Add CLI function for writing a register
|
||||
- Libretro core for use with RetroArch and other front-ends
|
||||
- Controller profiles for setting different bindings for different controllers
|
||||
Bugfixes:
|
||||
- ARM7: Extend prefetch by one stage
|
||||
- GBA Audio: Support 16-bit writes to FIFO audio
|
||||
|
@ -46,6 +48,9 @@ Bugfixes:
|
|||
- Qt: Fix crash when starting GDB stub after closing a game
|
||||
- Qt: Fix patch loading while a game is running
|
||||
- Util: Fix sockets on Windows
|
||||
- Qt: Fix crash when loading a game after stopping GDB server
|
||||
- GBA BIOS: Fix BIOS decompression routines with invalid source addresses
|
||||
- GBA: Initialize gba.sync to null
|
||||
Misc:
|
||||
- GBA Audio: Change internal audio sample buffer from 32-bit to 16-bit samples
|
||||
- GBA Memory: Simplify memory API and use fixed bus width
|
||||
|
@ -68,6 +73,7 @@ Misc:
|
|||
- Qt: Move frame upload back onto main thread
|
||||
- All: Enable link-time optimization
|
||||
- GBA Thread: Make GBASyncWaitFrameStart time out
|
||||
- GBA: Move A/V stream interface into core
|
||||
|
||||
0.1.1: (2015-01-24)
|
||||
Bugfixes:
|
||||
|
|
|
@ -12,11 +12,13 @@ set(USE_BLIP ON CACHE BOOL "Whether or not to enable blip_buf support")
|
|||
set(USE_LZMA ON CACHE BOOL "Whether or not to enable 7-Zip support")
|
||||
set(BUILD_QT ON CACHE BOOL "Build Qt frontend")
|
||||
set(BUILD_SDL ON CACHE BOOL "Build SDL frontend")
|
||||
set(BUILD_LIBRETRO OFF CACHE BOOL "Build libretro core")
|
||||
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
||||
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||
file(GLOB ARM_SRC ${CMAKE_SOURCE_DIR}/src/arm/*.c)
|
||||
file(GLOB GBA_SRC ${CMAKE_SOURCE_DIR}/src/gba/*.c)
|
||||
file(GLOB GBA_RR_SRC ${CMAKE_SOURCE_DIR}/src/gba/rr/*.c)
|
||||
file(GLOB GBA_SV_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/*.c)
|
||||
file(GLOB UTIL_SRC ${CMAKE_SOURCE_DIR}/src/util/*.[cSs])
|
||||
file(GLOB VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/*.c)
|
||||
|
@ -26,7 +28,7 @@ file(GLOB THIRD_PARTY_SRC ${CMAKE_SOURCE_DIR}/src/third-party/inih/*.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} ${SIO_SRC})
|
||||
source_group("GBA supervisor" FILES ${GBA_SV_SRC})
|
||||
source_group("GBA supervisor" FILES ${GBA_SV_SRC} ${GBA_RR_SRC})
|
||||
source_group("Utilities" FILES ${UTIL_SRC} ${VFS_SRC}})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/arm)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
|
@ -75,10 +77,11 @@ set(LIB_VERSION_ABI 0.2)
|
|||
set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH})
|
||||
|
||||
# Advanced settings
|
||||
set(BUILD_PGO CACHE BOOL "Build with profiling-guided optimization")
|
||||
set(BUILD_LTO ON CACHE BOOL "Build with link-time optimization")
|
||||
set(BUILD_PGO OFF 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)
|
||||
mark_as_advanced(BUILD_LTO BUILD_PGO PGO_STAGE_2 PGO_DIR)
|
||||
set(PGO_PRE_FLAGS "-pg -fprofile-generate=${PGO_DIR} -fprofile-arcs")
|
||||
set(PGO_POST_FLAGS "-fprofile-use=${PGO_DIR} -fbranch-probabilities")
|
||||
|
||||
|
@ -95,6 +98,7 @@ endif()
|
|||
add_definitions(-DBINARY_NAME="${BINARY_NAME}" -DPROJECT_NAME="${PROJECT_NAME}" -DPROJECT_VERSION="${LIB_VERSION_STRING}")
|
||||
|
||||
# Feature dependencies
|
||||
set(FEATURES)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES .*BSD)
|
||||
set(LIBEDIT_LIBRARIES -ledit)
|
||||
else()
|
||||
|
@ -124,18 +128,21 @@ if(APPLE)
|
|||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
|
||||
endif()
|
||||
|
||||
if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU")
|
||||
if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO)
|
||||
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -flto")
|
||||
endif()
|
||||
|
||||
if(BUILD_BBB OR BUILD_RASPI)
|
||||
enable_language(ASM)
|
||||
if(NOT BUILD_EGL)
|
||||
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*")
|
||||
enable_language(ASM)
|
||||
endif()
|
||||
|
||||
include(CheckFunctionExists)
|
||||
check_function_exists(strndup HAVE_STRNDUP)
|
||||
check_function_exists(snprintf_l HAVE_SNPRINTF_L)
|
||||
|
@ -166,13 +173,14 @@ endif()
|
|||
|
||||
# Features
|
||||
set(DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/debugger.c ${CMAKE_SOURCE_DIR}/src/debugger/memory-debugger.c)
|
||||
set(FEATURE_SRC)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6")
|
||||
|
||||
if(USE_CLI_DEBUGGER)
|
||||
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)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c)
|
||||
list(APPEND FEATURES CLI_DEBUGGER)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/cli-debugger.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/debugger/parser.c)
|
||||
list(APPEND FEATURE_SRC ${CMAKE_SOURCE_DIR}/src/gba/supervisor/cli.c)
|
||||
include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS})
|
||||
link_directories(${LIBEDIT_LIBRARY_DIRS})
|
||||
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
||||
|
@ -182,20 +190,20 @@ else()
|
|||
endif()
|
||||
|
||||
if(USE_GDB_STUB)
|
||||
add_definitions(-DUSE_GDB_STUB)
|
||||
list(APPEND FEATURES GDB_STUB)
|
||||
list(APPEND DEBUGGER_SRC ${CMAKE_SOURCE_DIR}/src/debugger/gdb-stub.c)
|
||||
endif()
|
||||
source_group("ARM debugger" FILES ${DEBUGGER_SRC})
|
||||
|
||||
if(USE_FFMPEG)
|
||||
add_definitions(-DUSE_FFMPEG)
|
||||
list(APPEND FEATURES FFMPEG)
|
||||
pkg_search_module(LIBSWRESAMPLE QUIET libswresample)
|
||||
if(NOT LIBSWRESAMPLE_FOUND)
|
||||
add_definitions(-DUSE_LIBAV)
|
||||
list(APPEND FEATURES LIBAV)
|
||||
endif()
|
||||
include_directories(AFTER ${LIBAVCODEC_INCLUDE_DIRS} ${LIBAVFORMAT_INCLUDE_DIRS} ${LIBAVRESAMPLE_INCLUDE_DIRS} ${LIBAVUTIL_INCLUDE_DIRS} ${LIBSWSCALE_INCLUDE_DIRS})
|
||||
link_directories(${LIBAVCODEC_LIBRARY_DIRS} ${LIBAVFORMAT_LIBRARY_DIRS} ${LIBAVRESAMPLE_LIBRARY_DIRS} ${LIBAVUTIL_LIBRARY_DIRS} ${LIBSWSCALE_LIBRARY_DIRS})
|
||||
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
|
||||
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/ffmpeg/ffmpeg-encoder.c")
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVCODEC_VERSION_MAJOR ${libavcodec_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVFORMAT_VERSION_MAJOR ${libavformat_VERSION})
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
||||
|
@ -207,17 +215,17 @@ if(USE_FFMPEG)
|
|||
endif()
|
||||
|
||||
if(USE_BLIP)
|
||||
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
list(APPEND THIRD_PARTY_SRC "${CMAKE_SOURCE_DIR}/src/third-party/blip_buf/blip_buf.c")
|
||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_BLIP_BUF)
|
||||
else()
|
||||
add_definitions(-DRESAMPLE_LIBRARY=RESAMPLE_NN)
|
||||
endif()
|
||||
|
||||
if(USE_MAGICK)
|
||||
add_definitions(-DUSE_MAGICK)
|
||||
list(APPEND FEATURES MAGICK)
|
||||
include_directories(AFTER ${MAGICKWAND_INCLUDE_DIRS})
|
||||
link_directories(${MAGICKWAND_LIBRARY_DIRS})
|
||||
list(APPEND UTIL_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c")
|
||||
list(APPEND FEATURE_SRC "${CMAKE_SOURCE_DIR}/src/platform/imagemagick/imagemagick-gif-encoder.c")
|
||||
list(APPEND DEPENDENCY_LIB ${MAGICKWAND_LIBRARIES})
|
||||
string(REGEX MATCH "^[0-9]+\\.[0-9]+" MAGICKWAND_VERSION_PARTIAL ${MagickWand_VERSION})
|
||||
if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7")
|
||||
|
@ -229,7 +237,7 @@ if(USE_MAGICK)
|
|||
endif()
|
||||
|
||||
if(USE_PNG)
|
||||
add_definitions(-DUSE_PNG)
|
||||
list(APPEND FEATURES PNG)
|
||||
include_directories(AFTER ${PNG_INCLUDE_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${PNG_LIBRARIES} ${ZLIB_LIBRARIES})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpng12-0,zlib1g")
|
||||
|
@ -239,7 +247,7 @@ if(USE_LIBZIP)
|
|||
include_directories(AFTER ${LIBZIP_INCLUDE_DIRS})
|
||||
link_directories(${LIBZIP_LIBRARY_DIRS})
|
||||
list(APPEND DEPENDENCY_LIB ${LIBZIP_LIBRARIES})
|
||||
add_definitions(-DENABLE_LIBZIP)
|
||||
list(APPEND FEATURES LIBZIP)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libzip2")
|
||||
endif()
|
||||
|
||||
|
@ -247,7 +255,6 @@ if (USE_LZMA)
|
|||
include_directories(AFTER ${CMAKE_SOURCE_DIR}/third-party/lzma)
|
||||
add_definitions(-D_7ZIP_PPMD_SUPPPORT)
|
||||
set(LZMA_SRC
|
||||
${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-lzma.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zAlloc.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zArcIn.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zBuf.c
|
||||
|
@ -265,14 +272,20 @@ if (USE_LZMA)
|
|||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/Ppmd7Dec.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zFile.c
|
||||
${CMAKE_SOURCE_DIR}/src/third-party/lzma/7zStream.c)
|
||||
list(APPEND UTIL_SRC ${LZMA_SRC})
|
||||
add_definitions(-DENABLE_LZMA)
|
||||
list(APPEND FEATURE_SRC ${LZMA_SRC})
|
||||
list(APPEND FEATURES LZMA)
|
||||
endif()
|
||||
|
||||
set(FEATURE_DEFINES)
|
||||
foreach(FEATURE IN LISTS FEATURES)
|
||||
list(APPEND FEATURE_DEFINES "USE_${FEATURE}")
|
||||
endforeach()
|
||||
|
||||
# Binaries
|
||||
set(SRC
|
||||
set(CORE_SRC
|
||||
${ARM_SRC}
|
||||
${GBA_SRC}
|
||||
${GBA_RR_SRC}
|
||||
${GBA_SV_SRC}
|
||||
${DEBUGGER_SRC}
|
||||
${RENDERER_SRC}
|
||||
|
@ -282,6 +295,10 @@ set(SRC
|
|||
${OS_SRC}
|
||||
${THIRD_PARTY_SRC})
|
||||
|
||||
set(SRC
|
||||
${CORE_SRC}
|
||||
${FEATURE_SRC})
|
||||
|
||||
if(NOT BUILD_STATIC AND NOT BUILD_SHARED)
|
||||
set(BUILD_SHARED ON)
|
||||
endif()
|
||||
|
@ -290,6 +307,7 @@ if(BUILD_SHARED)
|
|||
add_library(${BINARY_NAME} SHARED ${SRC})
|
||||
if(BUILD_STATIC)
|
||||
add_library(${BINARY_NAME}-static STATIC ${SRC})
|
||||
set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
install(TARGETS ${BINARY_NAME}-static DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
endif()
|
||||
else()
|
||||
|
@ -298,7 +316,14 @@ endif()
|
|||
|
||||
target_link_libraries(${BINARY_NAME} m ${DEBUGGER_LIB} ${OS_LIB} ${DEPENDENCY_LIB})
|
||||
install(TARGETS ${BINARY_NAME} DESTINATION lib COMPONENT lib${BINARY_NAME})
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI})
|
||||
set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
|
||||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC})
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5")
|
||||
target_link_libraries(${BINARY_NAME}_libretro m ${OS_LIB})
|
||||
endif()
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_definitions(-DBUILD_SDL)
|
||||
|
@ -355,6 +380,7 @@ message(STATUS " Better audio resampling: ${USE_BLIP}")
|
|||
message(STATUS "Frontend summary:")
|
||||
message(STATUS " Qt: ${BUILD_QT}")
|
||||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
|
||||
message(STATUS " Profiling: ${BUILD_PERF}")
|
||||
message(STATUS "Library summary:")
|
||||
message(STATUS " Static: ${BUILD_STATIC}")
|
||||
|
|
|
@ -53,6 +53,7 @@ void ARMDebuggerInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
debugger->cpu = cpu;
|
||||
debugger->state = DEBUGGER_RUNNING;
|
||||
debugger->breakpoints = 0;
|
||||
debugger->swBreakpoints = 0;
|
||||
debugger->originalMemory = cpu->memory;
|
||||
debugger->watchpoints = 0;
|
||||
debugger->currentBreakpoint = 0;
|
||||
|
|
|
@ -822,11 +822,15 @@ static void _sample(struct GBAAudio* audio) {
|
|||
}
|
||||
produced = blip_samples_avail(audio->left);
|
||||
#endif
|
||||
struct GBAThread* thread = GBAThreadGetContext();
|
||||
if (thread && thread->stream) {
|
||||
thread->stream->postAudioFrame(thread->stream, sampleLeft, sampleRight);
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
bool wait = produced >= audio->samples;
|
||||
GBASyncProduceAudio(audio->p->sync, wait);
|
||||
|
||||
if (wait && audio->p->stream && audio->p->stream->postAudioBuffer) {
|
||||
audio->p->stream->postAudioBuffer(audio->p->stream, audio);
|
||||
}
|
||||
GBASyncProduceAudio(audio->p->sync, produced >= audio->samples);
|
||||
}
|
||||
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||
|
|
|
@ -88,8 +88,8 @@ static void _BgAffineSet(struct GBA* gba) {
|
|||
// [ sx 0 0 ] [ cos(theta) -sin(theta) 0 ] [ 1 0 cx - ox ] [ A B rx ]
|
||||
// [ 0 sy 0 ] * [ sin(theta) cos(theta) 0 ] * [ 0 1 cy - oy ] = [ C D ry ]
|
||||
// [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ] [ 0 0 1 ]
|
||||
ox = cpu->memory.load32(cpu, offset, 0) / 256.f;
|
||||
oy = cpu->memory.load32(cpu, offset + 4, 0) / 256.f;
|
||||
ox = (int32_t) cpu->memory.load32(cpu, offset, 0) / 256.f;
|
||||
oy = (int32_t) cpu->memory.load32(cpu, offset + 4, 0) / 256.f;
|
||||
cx = (int16_t) cpu->memory.load16(cpu, offset + 8, 0);
|
||||
cy = (int16_t) cpu->memory.load16(cpu, offset + 10, 0);
|
||||
sx = (int16_t) cpu->memory.load16(cpu, offset + 12, 0) / 256.f;
|
||||
|
@ -233,6 +233,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
case 0x12:
|
||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad LZ77 source");
|
||||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
|
@ -247,6 +248,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
case 0x13:
|
||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad Huffman source");
|
||||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
|
@ -262,6 +264,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
case 0x15:
|
||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad RL source");
|
||||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
|
@ -278,6 +281,7 @@ void GBASwi16(struct ARMCore* cpu, int immediate) {
|
|||
case 0x18:
|
||||
if (cpu->gprs[0] < BASE_WORKING_RAM) {
|
||||
GBALog(gba, GBA_LOG_GAME_ERROR, "Bad UnFilter source");
|
||||
break;
|
||||
}
|
||||
switch (cpu->gprs[1] >> BASE_OFFSET) {
|
||||
default:
|
||||
|
|
|
@ -47,6 +47,7 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
struct GBA* gba = (struct GBA*) component;
|
||||
gba->cpu = cpu;
|
||||
gba->debugger = 0;
|
||||
gba->sync = 0;
|
||||
|
||||
GBAInterruptHandlerInit(&cpu->irqh);
|
||||
GBAMemoryInit(gba);
|
||||
|
@ -77,7 +78,9 @@ static void GBAInit(struct ARMCore* cpu, struct ARMComponent* component) {
|
|||
gba->romVf = 0;
|
||||
gba->biosVf = 0;
|
||||
|
||||
gba->logHandler = 0;
|
||||
gba->logLevel = GBA_LOG_INFO | GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL;
|
||||
gba->stream = 0;
|
||||
|
||||
gba->biosChecksum = GBAChecksum(gba->memory.bios, SIZE_BIOS);
|
||||
|
||||
|
@ -106,7 +109,7 @@ void GBADestroy(struct GBA* gba) {
|
|||
GBAVideoDeinit(&gba->video);
|
||||
GBAAudioDeinit(&gba->audio);
|
||||
GBASIODeinit(&gba->sio);
|
||||
GBARRContextDestroy(gba);
|
||||
gba->rr = 0;
|
||||
}
|
||||
|
||||
void GBAInterruptHandlerInit(struct ARMInterruptHandler* irqh) {
|
||||
|
@ -130,7 +133,7 @@ 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)) {
|
||||
if (!gba->rr || (!gba->rr->isPlaying(gba->rr) && !gba->rr->isRecording(gba->rr))) {
|
||||
GBASavedataUnmask(&gba->memory.savedata);
|
||||
}
|
||||
GBAMemoryReset(gba);
|
||||
|
@ -547,10 +550,10 @@ static void _GBAVLog(struct GBA* gba, enum GBALogLevel level, const char* format
|
|||
threadContext->state = THREAD_CRASHED;
|
||||
MutexUnlock(&threadContext->stateMutex);
|
||||
}
|
||||
if (threadContext->logHandler) {
|
||||
threadContext->logHandler(threadContext, level, format, args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (gba && gba->logHandler) {
|
||||
gba->logHandler(threadContext, level, format, args);
|
||||
return;
|
||||
}
|
||||
|
||||
vprintf(format, args);
|
||||
|
@ -713,10 +716,10 @@ void GBAFrameStarted(struct GBA* gba) {
|
|||
|
||||
void GBAFrameEnded(struct GBA* gba) {
|
||||
if (gba->rr) {
|
||||
GBARRNextFrame(gba->rr);
|
||||
gba->rr->nextFrame(gba->rr);
|
||||
}
|
||||
|
||||
if (gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
||||
if (gba->cpu->components && gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE]) {
|
||||
struct GBACheatDevice* device = (struct GBACheatDevice*) gba->cpu->components[GBA_COMPONENT_CHEAT_DEVICE];
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatSetsSize(&device->cheats); ++i) {
|
||||
|
@ -733,8 +736,8 @@ void GBAFrameEnded(struct GBA* gba) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (thread->stream) {
|
||||
thread->stream->postVideoFrame(thread->stream, thread->renderer);
|
||||
if (gba->stream) {
|
||||
gba->stream->postVideoFrame(gba->stream, gba->video.renderer);
|
||||
}
|
||||
|
||||
if (thread->frameCallback) {
|
||||
|
|
|
@ -90,9 +90,18 @@ enum {
|
|||
|
||||
struct GBA;
|
||||
struct GBARotationSource;
|
||||
struct GBAThread;
|
||||
struct Patch;
|
||||
struct VFile;
|
||||
|
||||
typedef void (*GBALogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
struct GBAAVStream {
|
||||
void (*postVideoFrame)(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
void (*postAudioFrame)(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
void (*postAudioBuffer)(struct GBAAVStream*, struct GBAAudio*);
|
||||
};
|
||||
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
uint16_t oldReload;
|
||||
|
@ -141,7 +150,9 @@ struct GBA {
|
|||
|
||||
const char* activeFile;
|
||||
|
||||
int logLevel;
|
||||
GBALogHandler logHandler;
|
||||
enum GBALogLevel logLevel;
|
||||
struct GBAAVStream* stream;
|
||||
|
||||
enum GBAIdleLoopOptimization idleOptimization;
|
||||
uint32_t idleLoop;
|
||||
|
|
|
@ -81,8 +81,8 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.packed = 0;
|
||||
hw->rtc.control.packed = 0x40;
|
||||
hw->rtc.command = 0;
|
||||
hw->rtc.control = 0x40;
|
||||
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
|
||||
}
|
||||
|
||||
|
@ -139,14 +139,14 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
}
|
||||
break;
|
||||
case 2:
|
||||
if (!hw->p0) {
|
||||
if (!(hw->pinState & 1)) {
|
||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
||||
hw->rtc.bits |= hw->p1 << hw->rtc.bitsRead;
|
||||
hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead;
|
||||
} else {
|
||||
if (hw->p2) {
|
||||
if (hw->pinState & 4) {
|
||||
// GPIO direction should always != reading
|
||||
if (hw->dir1) {
|
||||
if (hw->rtc.command.reading) {
|
||||
if (hw->direction & 2) {
|
||||
if (RTCCommandDataIsReading(hw->rtc.command)) {
|
||||
GBALog(hw->p, GBA_LOG_GAME_ERROR, "Attempting to write to RTC while in read mode");
|
||||
}
|
||||
++hw->rtc.bitsRead;
|
||||
|
@ -160,7 +160,7 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
--hw->rtc.bytesRemaining;
|
||||
if (hw->rtc.bytesRemaining <= 0) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||
}
|
||||
hw->rtc.bitsRead = 0;
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||
hw->rtc.transferStep = 0;
|
||||
}
|
||||
}
|
||||
|
@ -180,16 +180,16 @@ void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
|||
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (!hw->rtc.commandActive) {
|
||||
union RTCCommandData command;
|
||||
command.packed = hw->rtc.bits;
|
||||
if (command.magic == 0x06) {
|
||||
RTCCommandData command;
|
||||
command = hw->rtc.bits;
|
||||
if (RTCCommandDataGetMagic(command) == 0x06) {
|
||||
hw->rtc.command = command;
|
||||
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[hw->rtc.command.command];
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)];
|
||||
hw->rtc.commandActive = hw->rtc.bytesRemaining > 0;
|
||||
switch (command.command) {
|
||||
switch (RTCCommandDataGetCommand(command)) {
|
||||
case RTC_RESET:
|
||||
hw->rtc.control.packed = 0;
|
||||
hw->rtc.control = 0;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
|
@ -203,12 +203,12 @@ void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
|||
GBALog(hw->p, GBA_LOG_WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
||||
}
|
||||
} else {
|
||||
switch (hw->rtc.command.command) {
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
hw->rtc.control.packed = hw->rtc.bits;
|
||||
hw->rtc.control = hw->rtc.bits;
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", hw->rtc.command.command);
|
||||
GBALog(hw->p, GBA_LOG_STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command));
|
||||
break;
|
||||
case RTC_RESET:
|
||||
case RTC_DATETIME:
|
||||
|
@ -221,15 +221,15 @@ void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.bitsRead = 0;
|
||||
if (!hw->rtc.bytesRemaining) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command.reading = 0;
|
||||
hw->rtc.command = RTCCommandDataClearReading(hw->rtc.command);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned _rtcOutput(struct GBACartridgeHardware* hw) {
|
||||
uint8_t outputByte = 0;
|
||||
switch (hw->rtc.command.command) {
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
outputByte = hw->rtc.control.packed;
|
||||
outputByte = hw->rtc.control;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
|
@ -262,7 +262,7 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
|
|||
hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1);
|
||||
hw->rtc.time[2] = _rtcBCD(date.tm_mday);
|
||||
hw->rtc.time[3] = _rtcBCD(date.tm_wday);
|
||||
if (hw->rtc.control.hour24) {
|
||||
if (RTCControlIsHour24(hw->rtc.control)) {
|
||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour);
|
||||
} else {
|
||||
hw->rtc.time[4] = _rtcBCD(date.tm_hour % 12);
|
||||
|
@ -292,7 +292,7 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (hw->p0) {
|
||||
if (hw->pinState & 1) {
|
||||
if (gyro->sample) {
|
||||
gyro->sample(gyro);
|
||||
}
|
||||
|
@ -302,14 +302,14 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative
|
||||
}
|
||||
|
||||
if (hw->gyroEdge && !hw->p1) {
|
||||
if (hw->gyroEdge && !(hw->pinState & 2)) {
|
||||
// Write bit on falling edge
|
||||
unsigned bit = hw->gyroSample >> 15;
|
||||
hw->gyroSample <<= 1;
|
||||
_outputPins(hw, bit << 2);
|
||||
}
|
||||
|
||||
hw->gyroEdge = hw->p1;
|
||||
hw->gyroEdge = !!(hw->pinState & 2);
|
||||
}
|
||||
|
||||
// == Rumble
|
||||
|
@ -324,7 +324,7 @@ void _rumbleReadPins(struct GBACartridgeHardware* hw) {
|
|||
return;
|
||||
}
|
||||
|
||||
rumble->setRumble(rumble, hw->p3);
|
||||
rumble->setRumble(rumble, !!(hw->pinState & 8));
|
||||
}
|
||||
|
||||
// == Light sensor
|
||||
|
@ -337,11 +337,11 @@ void GBAHardwareInitLight(struct GBACartridgeHardware* hw) {
|
|||
}
|
||||
|
||||
void _lightReadPins(struct GBACartridgeHardware* hw) {
|
||||
if (hw->p2) {
|
||||
if (hw->pinState & 4) {
|
||||
// Boktai chip select
|
||||
return;
|
||||
}
|
||||
if (hw->p1) {
|
||||
if (hw->pinState & 2) {
|
||||
struct GBALuminanceSource* lux = hw->p->luminanceSource;
|
||||
GBALog(hw->p, GBA_LOG_DEBUG, "[SOLAR] Got reset");
|
||||
hw->lightCounter = 0;
|
||||
|
@ -352,10 +352,10 @@ void _lightReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->lightSample = 0xFF;
|
||||
}
|
||||
}
|
||||
if (hw->p0 && hw->lightEdge) {
|
||||
if ((hw->pinState & 1) && hw->lightEdge) {
|
||||
++hw->lightCounter;
|
||||
}
|
||||
hw->lightEdge = !hw->p0;
|
||||
hw->lightEdge = !(hw->pinState & 1);
|
||||
|
||||
bool sendBit = hw->lightCounter >= hw->lightSample;
|
||||
_outputPins(hw, sendBit << 3);
|
||||
|
@ -424,7 +424,7 @@ uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) {
|
|||
|
||||
// == Serialization
|
||||
|
||||
void GBAHardwareSerialize(struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
state->hw.readWrite = hw->readWrite;
|
||||
state->hw.pinState = hw->pinState;
|
||||
state->hw.pinDirection = hw->direction;
|
||||
|
@ -440,7 +440,7 @@ void GBAHardwareSerialize(struct GBACartridgeHardware* hw, struct GBASerializedS
|
|||
state->hw.lightEdge = hw->lightEdge;
|
||||
}
|
||||
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASerializedState* state) {
|
||||
hw->readWrite = state->hw.readWrite;
|
||||
hw->pinState = state->hw.pinState;
|
||||
hw->direction = state->hw.pinDirection;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||
|
||||
struct GBARotationSource {
|
||||
|
@ -52,16 +54,10 @@ enum GPIODirection {
|
|||
GPIO_READ_WRITE = 1
|
||||
};
|
||||
|
||||
union RTCControl {
|
||||
struct {
|
||||
unsigned : 3;
|
||||
unsigned minIRQ : 1;
|
||||
unsigned : 2;
|
||||
unsigned hour24 : 1;
|
||||
unsigned poweroff : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
DECL_BITFIELD(RTCControl, uint8_t);
|
||||
DECL_BIT(RTCControl, MinIRQ, 3);
|
||||
DECL_BIT(RTCControl, Hour24, 6);
|
||||
DECL_BIT(RTCControl, Poweroff, 7);
|
||||
|
||||
enum RTCCommand {
|
||||
RTC_RESET = 0,
|
||||
|
@ -71,14 +67,10 @@ enum RTCCommand {
|
|||
RTC_TIME = 6
|
||||
};
|
||||
|
||||
union RTCCommandData {
|
||||
struct {
|
||||
unsigned magic : 4;
|
||||
enum RTCCommand command : 3;
|
||||
unsigned reading : 1;
|
||||
};
|
||||
uint8_t packed;
|
||||
};
|
||||
DECL_BITFIELD(RTCCommandData, uint8_t);
|
||||
DECL_BITS(RTCCommandData, Magic, 0, 4);
|
||||
DECL_BITS(RTCCommandData, Command, 4, 3);
|
||||
DECL_BIT(RTCCommandData, Reading, 7);
|
||||
|
||||
struct GBARTC {
|
||||
int bytesRemaining;
|
||||
|
@ -86,8 +78,8 @@ struct GBARTC {
|
|||
int bitsRead;
|
||||
int bits;
|
||||
int commandActive;
|
||||
union RTCCommandData command;
|
||||
union RTCControl control;
|
||||
RTCCommandData command;
|
||||
RTCControl control;
|
||||
uint8_t time[7];
|
||||
} __attribute__((packed));
|
||||
|
||||
|
@ -95,31 +87,16 @@ struct GBARumble {
|
|||
void (*setRumble)(struct GBARumble*, int enable);
|
||||
};
|
||||
|
||||
DECL_BITFIELD(GPIOPin, uint16_t);
|
||||
|
||||
struct GBACartridgeHardware {
|
||||
struct GBA* p;
|
||||
int devices;
|
||||
enum GPIODirection readWrite;
|
||||
uint16_t* gpioBase;
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned p0 : 1;
|
||||
unsigned p1 : 1;
|
||||
unsigned p2 : 1;
|
||||
unsigned p3 : 1;
|
||||
};
|
||||
uint16_t pinState;
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
unsigned dir0 : 1;
|
||||
unsigned dir1 : 1;
|
||||
unsigned dir2 : 1;
|
||||
unsigned dir3 : 1;
|
||||
};
|
||||
uint16_t direction;
|
||||
};
|
||||
uint16_t pinState;
|
||||
uint16_t direction;
|
||||
|
||||
struct GBARTC rtc;
|
||||
|
||||
|
@ -149,7 +126,7 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* gpio, uint32_t address, u
|
|||
uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* gpio, uint32_t address);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAHardwareSerialize(struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
||||
void GBAHardwareSerialize(const struct GBACartridgeHardware* gpio, struct GBASerializedState* state);
|
||||
void GBAHardwareDeserialize(struct GBACartridgeHardware* gpio, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
||||
|
|
166
src/gba/input.c
166
src/gba/input.c
|
@ -24,7 +24,7 @@ struct GBAInputMapImpl {
|
|||
|
||||
struct GBAAxisSave {
|
||||
struct Configuration* config;
|
||||
uint32_t type;
|
||||
const char* sectionName;
|
||||
};
|
||||
|
||||
struct GBAAxisEnumerate {
|
||||
|
@ -45,6 +45,11 @@ const char* GBAKeyNames[] = {
|
|||
"L"
|
||||
};
|
||||
|
||||
static void _makeSectionName(char* sectionName, size_t len, uint32_t type) {
|
||||
snprintf(sectionName, len, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[len - 1] = '\0';
|
||||
}
|
||||
|
||||
static bool _getIntValue(const struct Configuration* config, const char* section, const char* key, int* value) {
|
||||
const char* strValue = ConfigurationGetValue(config, section, key);
|
||||
if (!strValue) {
|
||||
|
@ -122,11 +127,7 @@ static struct GBAInputMapImpl* _guaranteeMap(struct GBAInputMap* map, uint32_t t
|
|||
return impl;
|
||||
}
|
||||
|
||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _loadKey(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -138,11 +139,7 @@ static void _loadKey(struct GBAInputMap* map, uint32_t type, const struct Config
|
|||
GBAInputBindKey(map, type, value, key);
|
||||
}
|
||||
|
||||
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, enum GBAKey direction, const char* axisName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _loadAxis(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config, enum GBAKey direction, const char* axisName) {
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -179,11 +176,7 @@ static void _loadAxis(struct GBAInputMap* map, uint32_t type, const struct Confi
|
|||
GBAInputBindAxis(map, type, axis, &realDescription);
|
||||
}
|
||||
|
||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _saveKey(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config, enum GBAKey key, const char* keyName) {
|
||||
char keyKey[KEY_NAME_MAX];
|
||||
snprintf(keyKey, KEY_NAME_MAX, "key%s", keyName);
|
||||
keyKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -195,11 +188,7 @@ static void _saveKey(const struct GBAInputMap* map, uint32_t type, struct Config
|
|||
ConfigurationSetValue(config, sectionName, keyKey, keyValue);
|
||||
}
|
||||
|
||||
static void _clearAxis(uint32_t type, struct Configuration* config, const char* axisName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
|
||||
static void _clearAxis(const char* sectionName, struct Configuration* config, const char* axisName) {
|
||||
char axisKey[KEY_NAME_MAX];
|
||||
snprintf(axisKey, KEY_NAME_MAX, "axis%sValue", axisName);
|
||||
axisKey[KEY_NAME_MAX - 1] = '\0';
|
||||
|
@ -214,10 +203,7 @@ static void _saveAxis(uint32_t axis, void* dp, void* up) {
|
|||
struct GBAAxisSave* user = up;
|
||||
const struct GBAAxis* description = dp;
|
||||
|
||||
uint32_t type = user->type;
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input.%c%c%c%c", type >> 24, type >> 16, type >> 8, type);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
const char* sectionName = user->sectionName;
|
||||
|
||||
if (description->lowDirection != GBA_KEY_NONE) {
|
||||
const char* keyName = GBAKeyNames[description->lowDirection];
|
||||
|
@ -271,6 +257,64 @@ void _unbindAxis(uint32_t axis, void* dp, void* user) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _loadAll(struct GBAInputMap* map, uint32_t type, const char* sectionName, const struct Configuration* config) {
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_R, "R");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_START, "Start");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_SELECT, "Select");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_UP, "Up");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_loadKey(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_R, "R");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_START, "Start");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_SELECT, "Select");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_UP, "Up");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_loadAxis(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
}
|
||||
|
||||
static void _saveAll(const struct GBAInputMap* map, uint32_t type, const char* sectionName, struct Configuration* config) {
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_A, "A");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_B, "B");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_L, "L");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_R, "R");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_START, "Start");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_SELECT, "Select");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_UP, "Up");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_DOWN, "Down");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_LEFT, "Left");
|
||||
_saveKey(map, type, sectionName, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_clearAxis(sectionName, config, "A");
|
||||
_clearAxis(sectionName, config, "B");
|
||||
_clearAxis(sectionName, config, "L");
|
||||
_clearAxis(sectionName, config, "R");
|
||||
_clearAxis(sectionName, config, "Start");
|
||||
_clearAxis(sectionName, config, "Select");
|
||||
_clearAxis(sectionName, config, "Up");
|
||||
_clearAxis(sectionName, config, "Down");
|
||||
_clearAxis(sectionName, config, "Left");
|
||||
_clearAxis(sectionName, config, "Right");
|
||||
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct GBAAxisSave save = {
|
||||
config,
|
||||
sectionName
|
||||
};
|
||||
TableEnumerate(&impl->axes, _saveAxis, &save);
|
||||
}
|
||||
|
||||
void GBAInputMapInit(struct GBAInputMap* map) {
|
||||
map->maps = 0;
|
||||
map->numMaps = 0;
|
||||
|
@ -414,59 +458,27 @@ void GBAInputEnumerateAxes(const struct GBAInputMap* map, uint32_t type, void (h
|
|||
}
|
||||
|
||||
void GBAInputMapLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config) {
|
||||
_loadKey(map, type, config, GBA_KEY_A, "A");
|
||||
_loadKey(map, type, config, GBA_KEY_B, "B");
|
||||
_loadKey(map, type, config, GBA_KEY_L, "L");
|
||||
_loadKey(map, type, config, GBA_KEY_R, "R");
|
||||
_loadKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_loadKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_loadKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_loadKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_loadKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_loadKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_loadAxis(map, type, config, GBA_KEY_A, "A");
|
||||
_loadAxis(map, type, config, GBA_KEY_B, "B");
|
||||
_loadAxis(map, type, config, GBA_KEY_L, "L");
|
||||
_loadAxis(map, type, config, GBA_KEY_R, "R");
|
||||
_loadAxis(map, type, config, GBA_KEY_START, "Start");
|
||||
_loadAxis(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_loadAxis(map, type, config, GBA_KEY_UP, "Up");
|
||||
_loadAxis(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_loadAxis(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_loadAxis(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
_loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputMapSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config) {
|
||||
_saveKey(map, type, config, GBA_KEY_A, "A");
|
||||
_saveKey(map, type, config, GBA_KEY_B, "B");
|
||||
_saveKey(map, type, config, GBA_KEY_L, "L");
|
||||
_saveKey(map, type, config, GBA_KEY_R, "R");
|
||||
_saveKey(map, type, config, GBA_KEY_START, "Start");
|
||||
_saveKey(map, type, config, GBA_KEY_SELECT, "Select");
|
||||
_saveKey(map, type, config, GBA_KEY_UP, "Up");
|
||||
_saveKey(map, type, config, GBA_KEY_DOWN, "Down");
|
||||
_saveKey(map, type, config, GBA_KEY_LEFT, "Left");
|
||||
_saveKey(map, type, config, GBA_KEY_RIGHT, "Right");
|
||||
|
||||
_clearAxis(type, config, "A");
|
||||
_clearAxis(type, config, "B");
|
||||
_clearAxis(type, config, "L");
|
||||
_clearAxis(type, config, "R");
|
||||
_clearAxis(type, config, "Start");
|
||||
_clearAxis(type, config, "Select");
|
||||
_clearAxis(type, config, "Up");
|
||||
_clearAxis(type, config, "Down");
|
||||
_clearAxis(type, config, "Left");
|
||||
_clearAxis(type, config, "Right");
|
||||
|
||||
const struct GBAInputMapImpl* impl = _lookupMapConst(map, type);
|
||||
if (!impl) {
|
||||
return;
|
||||
}
|
||||
struct GBAAxisSave save = {
|
||||
config,
|
||||
type
|
||||
};
|
||||
TableEnumerate(&impl->axes, _saveAxis, &save);
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputProfileLoad(struct GBAInputMap* map, uint32_t type, const struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
_loadAll(map, type, sectionName, config);
|
||||
}
|
||||
|
||||
void GBAInputProfileSave(const struct GBAInputMap* map, uint32_t type, struct Configuration* config, const char* profile) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
snprintf(sectionName, SECTION_NAME_MAX, "input-profile.%s", profile);
|
||||
sectionName[SECTION_NAME_MAX - 1] = '\0';
|
||||
_saveAll(map, type, sectionName, config);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ void GBAInputMapDeinit(struct GBAInputMap*);
|
|||
|
||||
enum GBAKey GBAInputMapKey(const struct GBAInputMap*, uint32_t type, int key);
|
||||
void GBAInputBindKey(struct GBAInputMap*, uint32_t type, int key, enum GBAKey input);
|
||||
void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, int key);
|
||||
void GBAInputUnbindKey(struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||
int GBAInputQueryBinding(const struct GBAInputMap*, uint32_t type, enum GBAKey input);
|
||||
|
||||
enum GBAKey GBAInputMapAxis(const struct GBAInputMap*, uint32_t type, int axis, int value);
|
||||
|
@ -45,4 +45,7 @@ void GBAInputEnumerateAxes(const struct GBAInputMap*, uint32_t type, void (handl
|
|||
void GBAInputMapLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*);
|
||||
void GBAInputMapSave(const struct GBAInputMap*, uint32_t type, struct Configuration*);
|
||||
|
||||
void GBAInputProfileLoad(struct GBAInputMap*, uint32_t type, const struct Configuration*, const char* profile);
|
||||
void GBAInputProfileSave(const struct GBAInputMap*, uint32_t type, struct Configuration*, const char* profile);
|
||||
|
||||
#endif
|
||||
|
|
12
src/gba/io.c
12
src/gba/io.c
|
@ -172,7 +172,7 @@ const char* GBAIORegisterNames[] = {
|
|||
"JOY_RECV_LO",
|
||||
"JOY_RECV_HI",
|
||||
"JOY_TRANS_LO",
|
||||
"JOY_RECV_HI",
|
||||
"JOY_TRANS_HI",
|
||||
"JOYSTAT",
|
||||
0,
|
||||
0,
|
||||
|
@ -583,12 +583,12 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
break;
|
||||
|
||||
case REG_KEYINPUT:
|
||||
if (GBARRIsPlaying(gba->rr)) {
|
||||
return 0x3FF ^ GBARRQueryInput(gba->rr);
|
||||
if (gba->rr && gba->rr->isPlaying(gba->rr)) {
|
||||
return 0x3FF ^ gba->rr->queryInput(gba->rr);
|
||||
} else if (gba->keySource) {
|
||||
uint16_t input = *gba->keySource;
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
GBARRLogInput(gba->rr, input);
|
||||
if (gba->rr && gba->rr->isRecording(gba->rr)) {
|
||||
gba->rr->logInput(gba->rr, input);
|
||||
}
|
||||
return 0x3FF ^ input;
|
||||
}
|
||||
|
@ -675,7 +675,7 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBAHardwareSerialize(&gba->memory.hw, state);
|
||||
}
|
||||
|
||||
void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||
int i;
|
||||
for (i = 0; i < REG_MAX; i += 2) {
|
||||
if (_isSpecialRegister[i >> 1]) {
|
||||
|
|
|
@ -161,6 +161,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address);
|
|||
|
||||
struct GBASerializedState;
|
||||
void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
void GBAIODeserialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1461,12 +1461,12 @@ void GBAMemoryServiceDMA(struct GBA* gba, int number, struct GBADMA* info) {
|
|||
cpu->cycles += cycles;
|
||||
}
|
||||
|
||||
void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state) {
|
||||
void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state) {
|
||||
memcpy(state->wram, memory->wram, SIZE_WORKING_RAM);
|
||||
memcpy(state->iwram, memory->iwram, SIZE_WORKING_IRAM);
|
||||
}
|
||||
|
||||
void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state) {
|
||||
void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state) {
|
||||
memcpy(memory->wram, state->wram, SIZE_WORKING_RAM);
|
||||
memcpy(memory->iwram, state->iwram, SIZE_WORKING_IRAM);
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ void GBAMemoryUpdateDMAs(struct GBA* gba, int32_t cycles);
|
|||
int32_t GBAMemoryRunDMAs(struct GBA* gba, int32_t cycles);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAMemorySerialize(struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
void GBAMemoryDeserialize(struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
void GBAMemoryDeserialize(struct GBAMemory* memory, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -37,6 +37,7 @@ static const int _objSizes[32] = {
|
|||
|
||||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererDeinit(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer);
|
||||
static void GBAVideoSoftwareRendererWriteOAM(struct GBAVideoRenderer* renderer, uint32_t oam);
|
||||
static void GBAVideoSoftwareRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
static uint16_t GBAVideoSoftwareRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value);
|
||||
|
@ -77,7 +78,7 @@ static void _breakWindowInner(struct GBAVideoSoftwareRenderer* softwareRenderer,
|
|||
|
||||
void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
||||
renderer->d.init = GBAVideoSoftwareRendererInit;
|
||||
renderer->d.reset = GBAVideoSoftwareRendererInit;
|
||||
renderer->d.reset = GBAVideoSoftwareRendererReset;
|
||||
renderer->d.deinit = GBAVideoSoftwareRendererDeinit;
|
||||
renderer->d.writeVideoRegister = GBAVideoSoftwareRendererWriteVideoRegister;
|
||||
renderer->d.writeOAM = GBAVideoSoftwareRendererWriteOAM;
|
||||
|
@ -89,6 +90,21 @@ void GBAVideoSoftwareRendererCreate(struct GBAVideoSoftwareRenderer* renderer) {
|
|||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererInit(struct GBAVideoRenderer* renderer) {
|
||||
GBAVideoSoftwareRendererReset(renderer);
|
||||
|
||||
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||
|
||||
int y;
|
||||
for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) {
|
||||
color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
|
||||
int x;
|
||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
row[x] = GBA_COLOR_WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
|
||||
struct GBAVideoSoftwareRenderer* softwareRenderer = (struct GBAVideoSoftwareRenderer*) renderer;
|
||||
int i;
|
||||
|
||||
|
@ -544,7 +560,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
}
|
||||
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef __arm__
|
||||
#ifdef __ARM_NEON
|
||||
_to16Bit(row, softwareRenderer->row, VIDEO_HORIZONTAL_PIXELS);
|
||||
#else
|
||||
for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) {
|
||||
|
|
|
@ -0,0 +1,578 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "mgm.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define BINARY_EXT ".mgm"
|
||||
#define BINARY_MAGIC "GBAb"
|
||||
#define METADATA_FILENAME "metadata" BINARY_EXT
|
||||
|
||||
enum {
|
||||
INVALID_INPUT = 0x8000
|
||||
};
|
||||
|
||||
static void GBAMGMContextDestroy(struct GBARRContext*);
|
||||
|
||||
static bool GBAMGMStartPlaying(struct GBARRContext*, bool autorecord);
|
||||
static void GBAMGMStopPlaying(struct GBARRContext*);
|
||||
static bool GBAMGMStartRecording(struct GBARRContext*);
|
||||
static void GBAMGMStopRecording(struct GBARRContext*);
|
||||
|
||||
static bool GBAMGMIsPlaying(const struct GBARRContext*);
|
||||
static bool GBAMGMIsRecording(const struct GBARRContext*);
|
||||
|
||||
static void GBAMGMNextFrame(struct GBARRContext*);
|
||||
static void GBAMGMLogInput(struct GBARRContext*, uint16_t input);
|
||||
static uint16_t GBAMGMQueryInput(struct GBARRContext*);
|
||||
|
||||
static void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||
static void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||
|
||||
static bool _loadStream(struct GBAMGMContext*, uint32_t streamId);
|
||||
static bool _incrementStream(struct GBAMGMContext*, bool recursive);
|
||||
static bool _finishSegment(struct GBAMGMContext*);
|
||||
static bool _skipSegment(struct GBAMGMContext*);
|
||||
static bool _markRerecord(struct GBAMGMContext*);
|
||||
|
||||
static bool _emitMagic(struct GBAMGMContext*, struct VFile* vf);
|
||||
static bool _verifyMagic(struct GBAMGMContext*, struct VFile* vf);
|
||||
static enum GBAMGMTag _readTag(struct GBAMGMContext*, struct VFile* vf);
|
||||
static bool _seekTag(struct GBAMGMContext*, struct VFile* vf, enum GBAMGMTag tag);
|
||||
static bool _emitTag(struct GBAMGMContext*, struct VFile* vf, uint8_t tag);
|
||||
static bool _emitEnd(struct GBAMGMContext*, struct VFile* vf);
|
||||
|
||||
static bool _parseMetadata(struct GBAMGMContext*, struct VFile* vf);
|
||||
|
||||
static bool _markStreamNext(struct GBAMGMContext*, uint32_t newStreamId, bool recursive);
|
||||
static void _streamEndReached(struct GBAMGMContext*);
|
||||
|
||||
static struct VFile* GBAMGMOpenSavedata(struct GBARRContext*, int flags);
|
||||
static struct VFile* GBAMGMOpenSavestate(struct GBARRContext*, int flags);
|
||||
|
||||
void GBAMGMContextCreate(struct GBAMGMContext* mgm) {
|
||||
memset(mgm, 0, sizeof(*mgm));
|
||||
|
||||
mgm->d.destroy = GBAMGMContextDestroy;
|
||||
|
||||
mgm->d.startPlaying = GBAMGMStartPlaying;
|
||||
mgm->d.stopPlaying = GBAMGMStopPlaying;
|
||||
mgm->d.startRecording = GBAMGMStartRecording;
|
||||
mgm->d.stopRecording = GBAMGMStopRecording;
|
||||
|
||||
mgm->d.isPlaying = GBAMGMIsPlaying;
|
||||
mgm->d.isRecording = GBAMGMIsRecording;
|
||||
|
||||
mgm->d.nextFrame = GBAMGMNextFrame;
|
||||
mgm->d.logInput = GBAMGMLogInput;
|
||||
mgm->d.queryInput = GBAMGMQueryInput;
|
||||
|
||||
mgm->d.stateSaved = GBAMGMStateSaved;
|
||||
mgm->d.stateLoaded = GBAMGMStateLoaded;
|
||||
|
||||
mgm->d.openSavedata = GBAMGMOpenSavedata;
|
||||
mgm->d.openSavestate = GBAMGMOpenSavestate;
|
||||
}
|
||||
|
||||
void GBAMGMContextDestroy(struct GBARRContext* rr) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (mgm->metadataFile) {
|
||||
mgm->metadataFile->close(mgm->metadataFile);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream) {
|
||||
if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mgm->metadataFile && !mgm->metadataFile->close(mgm->metadataFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mgm->streamDir = stream;
|
||||
mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_RDWR);
|
||||
mgm->currentInput = INVALID_INPUT;
|
||||
if (!_parseMetadata(mgm, mgm->metadataFile)) {
|
||||
mgm->metadataFile->close(mgm->metadataFile);
|
||||
mgm->metadataFile = 0;
|
||||
mgm->maxStreamId = 0;
|
||||
}
|
||||
mgm->streamId = 1;
|
||||
mgm->movieStream = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom) {
|
||||
if (mgm->metadataFile) {
|
||||
mgm->metadataFile->truncate(mgm->metadataFile, 0);
|
||||
} else {
|
||||
mgm->metadataFile = mgm->streamDir->openFile(mgm->streamDir, METADATA_FILENAME, O_CREAT | O_TRUNC | O_RDWR);
|
||||
}
|
||||
_emitMagic(mgm, mgm->metadataFile);
|
||||
|
||||
mgm->d.initFrom = initFrom;
|
||||
mgm->initFromOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_INIT | initFrom);
|
||||
|
||||
mgm->streamId = 0;
|
||||
mgm->maxStreamId = 0;
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM);
|
||||
mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
|
||||
mgm->d.rrCount = 0;
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_RR_COUNT);
|
||||
mgm->rrCountOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _loadStream(struct GBAMGMContext* mgm, uint32_t streamId) {
|
||||
if (mgm->movieStream && !mgm->movieStream->close(mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
mgm->movieStream = 0;
|
||||
mgm->streamId = streamId;
|
||||
mgm->currentInput = INVALID_INPUT;
|
||||
char buffer[14];
|
||||
snprintf(buffer, sizeof(buffer), "%u" BINARY_EXT, streamId);
|
||||
if (mgm->d.isRecording(&mgm->d)) {
|
||||
int flags = O_CREAT | O_RDWR;
|
||||
if (streamId > mgm->maxStreamId) {
|
||||
flags |= O_TRUNC;
|
||||
}
|
||||
mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, flags);
|
||||
} else if (mgm->d.isPlaying(&mgm->d)) {
|
||||
mgm->movieStream = mgm->streamDir->openFile(mgm->streamDir, buffer, O_RDONLY);
|
||||
mgm->peekedTag = TAG_INVALID;
|
||||
if (!mgm->movieStream || !_verifyMagic(mgm, mgm->movieStream) || !_seekTag(mgm, mgm->movieStream, TAG_BEGIN)) {
|
||||
mgm->d.stopPlaying(&mgm->d);
|
||||
}
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Loading segment: %u", streamId);
|
||||
mgm->d.frames = 0;
|
||||
mgm->d.lagFrames = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _incrementStream(struct GBAMGMContext* mgm, bool recursive) {
|
||||
uint32_t newStreamId = mgm->maxStreamId + 1;
|
||||
uint32_t oldStreamId = mgm->streamId;
|
||||
if (mgm->d.isRecording(&mgm->d) && mgm->movieStream) {
|
||||
if (!_markStreamNext(mgm, newStreamId, recursive)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!_loadStream(mgm, newStreamId)) {
|
||||
return false;
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] New segment: %u", newStreamId);
|
||||
_emitMagic(mgm, mgm->movieStream);
|
||||
mgm->maxStreamId = newStreamId;
|
||||
_emitTag(mgm, mgm->movieStream, TAG_PREVIOUSLY);
|
||||
mgm->movieStream->write(mgm->movieStream, &oldStreamId, sizeof(oldStreamId));
|
||||
_emitTag(mgm, mgm->movieStream, TAG_BEGIN);
|
||||
|
||||
mgm->metadataFile->seek(mgm->metadataFile, mgm->maxStreamIdOffset, SEEK_SET);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
mgm->previously = oldStreamId;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBAMGMStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||
if (rr->isRecording(rr) || rr->isPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
mgm->isPlaying = true;
|
||||
if (!_loadStream(mgm, 1)) {
|
||||
mgm->isPlaying = false;
|
||||
return false;
|
||||
}
|
||||
mgm->autorecord = autorecord;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAMGMStopPlaying(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
mgm->isPlaying = false;
|
||||
if (mgm->movieStream) {
|
||||
mgm->movieStream->close(mgm->movieStream);
|
||||
mgm->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAMGMStartRecording(struct GBARRContext* rr) {
|
||||
if (rr->isRecording(rr) || rr->isPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (!mgm->maxStreamIdOffset) {
|
||||
_emitTag(mgm, mgm->metadataFile, TAG_MAX_STREAM);
|
||||
mgm->maxStreamIdOffset = mgm->metadataFile->seek(mgm->metadataFile, 0, SEEK_CUR);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
}
|
||||
|
||||
mgm->isRecording = true;
|
||||
return _incrementStream(mgm, false);
|
||||
}
|
||||
|
||||
void GBAMGMStopRecording(struct GBARRContext* rr) {
|
||||
if (!rr->isRecording(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
mgm->isRecording = false;
|
||||
if (mgm->movieStream) {
|
||||
_emitEnd(mgm, mgm->movieStream);
|
||||
mgm->movieStream->close(mgm->movieStream);
|
||||
mgm->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAMGMIsPlaying(const struct GBARRContext* rr) {
|
||||
const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr;
|
||||
return mgm->isPlaying;
|
||||
}
|
||||
|
||||
bool GBAMGMIsRecording(const struct GBARRContext* rr) {
|
||||
const struct GBAMGMContext* mgm = (const struct GBAMGMContext*) rr;
|
||||
return mgm->isRecording;
|
||||
}
|
||||
|
||||
void GBAMGMNextFrame(struct GBARRContext* rr) {
|
||||
if (!rr->isRecording(rr) && !rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isPlaying(rr)) {
|
||||
while (mgm->peekedTag == TAG_INPUT) {
|
||||
_readTag(mgm, mgm->movieStream);
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
|
||||
}
|
||||
if (mgm->peekedTag == TAG_LAG) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
|
||||
if (mgm->inputThisFrame) {
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++mgm->d.frames;
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", mgm->d.frames);
|
||||
if (!mgm->inputThisFrame) {
|
||||
++mgm->d.lagFrames;
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", mgm->d.lagFrames);
|
||||
}
|
||||
|
||||
if (rr->isRecording(rr)) {
|
||||
if (!mgm->inputThisFrame) {
|
||||
_emitTag(mgm, mgm->movieStream, TAG_LAG);
|
||||
}
|
||||
_emitTag(mgm, mgm->movieStream, TAG_FRAME);
|
||||
mgm->inputThisFrame = false;
|
||||
} else {
|
||||
if (!_seekTag(mgm, mgm->movieStream, TAG_FRAME)) {
|
||||
_streamEndReached(mgm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GBAMGMLogInput(struct GBARRContext* rr, uint16_t keys) {
|
||||
if (!rr->isRecording(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (keys != mgm->currentInput) {
|
||||
_emitTag(mgm, mgm->movieStream, TAG_INPUT);
|
||||
mgm->movieStream->write(mgm->movieStream, &keys, sizeof(keys));
|
||||
mgm->currentInput = keys;
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input log: %03X", mgm->currentInput);
|
||||
mgm->inputThisFrame = true;
|
||||
}
|
||||
|
||||
uint16_t GBAMGMQueryInput(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (mgm->peekedTag == TAG_INPUT) {
|
||||
_readTag(mgm, mgm->movieStream);
|
||||
}
|
||||
mgm->inputThisFrame = true;
|
||||
if (mgm->currentInput == INVALID_INPUT) {
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", mgm->currentInput);
|
||||
return mgm->currentInput;
|
||||
}
|
||||
|
||||
void GBAMGMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isRecording(rr)) {
|
||||
state->associatedStreamId = mgm->streamId;
|
||||
_finishSegment(mgm);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAMGMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
if (rr->isRecording(rr)) {
|
||||
if (state->associatedStreamId != mgm->streamId) {
|
||||
_loadStream(mgm, state->associatedStreamId);
|
||||
_incrementStream(mgm, true);
|
||||
} else {
|
||||
_finishSegment(mgm);
|
||||
}
|
||||
_markRerecord(mgm);
|
||||
} else if (rr->isPlaying(rr)) {
|
||||
_loadStream(mgm, state->associatedStreamId);
|
||||
_skipSegment(mgm);
|
||||
}
|
||||
}
|
||||
|
||||
bool _finishSegment(struct GBAMGMContext* mgm) {
|
||||
if (mgm->movieStream) {
|
||||
if (!_emitEnd(mgm, mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return _incrementStream(mgm, false);
|
||||
}
|
||||
|
||||
bool _skipSegment(struct GBAMGMContext* mgm) {
|
||||
mgm->nextTime = 0;
|
||||
while (_readTag(mgm, mgm->movieStream) != TAG_EOF);
|
||||
if (!mgm->nextTime || !_loadStream(mgm, mgm->nextTime)) {
|
||||
_streamEndReached(mgm);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _markRerecord(struct GBAMGMContext* mgm) {
|
||||
++mgm->d.rrCount;
|
||||
mgm->metadataFile->seek(mgm->metadataFile, mgm->rrCountOffset, SEEK_SET);
|
||||
mgm->metadataFile->write(mgm->metadataFile, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitMagic(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
UNUSED(mgm);
|
||||
return vf->write(vf, BINARY_MAGIC, 4) == 4;
|
||||
}
|
||||
|
||||
bool _verifyMagic(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
UNUSED(mgm);
|
||||
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 GBAMGMTag _readTag(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
if (!mgm || !vf) {
|
||||
return TAG_EOF;
|
||||
}
|
||||
|
||||
enum GBAMGMTag tag = mgm->peekedTag;
|
||||
switch (tag) {
|
||||
case TAG_INPUT:
|
||||
vf->read(vf, &mgm->currentInput, sizeof(uint16_t));
|
||||
break;
|
||||
case TAG_PREVIOUSLY:
|
||||
vf->read(vf, &mgm->previously, sizeof(mgm->previously));
|
||||
break;
|
||||
case TAG_NEXT_TIME:
|
||||
vf->read(vf, &mgm->nextTime, sizeof(mgm->nextTime));
|
||||
break;
|
||||
case TAG_MAX_STREAM:
|
||||
vf->read(vf, &mgm->maxStreamId, sizeof(mgm->maxStreamId));
|
||||
break;
|
||||
case TAG_FRAME_COUNT:
|
||||
vf->read(vf, &mgm->d.frames, sizeof(mgm->d.frames));
|
||||
break;
|
||||
case TAG_LAG_COUNT:
|
||||
vf->read(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames));
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
vf->read(vf, &mgm->d.rrCount, sizeof(mgm->d.rrCount));
|
||||
break;
|
||||
|
||||
case TAG_INIT_EX_NIHILO:
|
||||
mgm->d.initFrom = INIT_EX_NIHILO;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVEGAME:
|
||||
mgm->d.initFrom = INIT_FROM_SAVEGAME;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVESTATE:
|
||||
mgm->d.initFrom = INIT_FROM_SAVESTATE;
|
||||
break;
|
||||
case TAG_INIT_FROM_BOTH:
|
||||
mgm->d.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) {
|
||||
mgm->peekedTag = TAG_EOF;
|
||||
} else {
|
||||
mgm->peekedTag = tagBuffer;
|
||||
}
|
||||
|
||||
if (mgm->peekedTag == TAG_END) {
|
||||
_skipSegment(mgm);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
bool _seekTag(struct GBAMGMContext* mgm, struct VFile* vf, enum GBAMGMTag tag) {
|
||||
enum GBAMGMTag readTag;
|
||||
while ((readTag = _readTag(mgm, vf)) != tag) {
|
||||
if (readTag == TAG_EOF) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitTag(struct GBAMGMContext* mgm, struct VFile* vf, uint8_t tag) {
|
||||
UNUSED(mgm);
|
||||
return vf->write(vf, &tag, sizeof(tag)) == sizeof(tag);
|
||||
}
|
||||
|
||||
bool _parseMetadata(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
if (!_verifyMagic(mgm, vf)) {
|
||||
return false;
|
||||
}
|
||||
while (_readTag(mgm, vf) != TAG_EOF) {
|
||||
switch (mgm->peekedTag) {
|
||||
case TAG_MAX_STREAM:
|
||||
mgm->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:
|
||||
mgm->initFromOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
mgm->rrCountOffset = vf->seek(vf, 0, SEEK_CUR);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitEnd(struct GBAMGMContext* mgm, struct VFile* vf) {
|
||||
// TODO: Error check
|
||||
_emitTag(mgm, vf, TAG_END);
|
||||
_emitTag(mgm, vf, TAG_FRAME_COUNT);
|
||||
vf->write(vf, &mgm->d.frames, sizeof(mgm->d.frames));
|
||||
_emitTag(mgm, vf, TAG_LAG_COUNT);
|
||||
vf->write(vf, &mgm->d.lagFrames, sizeof(mgm->d.lagFrames));
|
||||
_emitTag(mgm, vf, TAG_NEXT_TIME);
|
||||
|
||||
uint32_t newStreamId = 0;
|
||||
vf->write(vf, &newStreamId, sizeof(newStreamId));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _markStreamNext(struct GBAMGMContext* mgm, uint32_t newStreamId, bool recursive) {
|
||||
if (mgm->movieStream->seek(mgm->movieStream, -sizeof(newStreamId) - 1, SEEK_END) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t tagBuffer;
|
||||
if (mgm->movieStream->read(mgm->movieStream, &tagBuffer, 1) != 1) {
|
||||
return false;
|
||||
}
|
||||
if (tagBuffer != TAG_NEXT_TIME) {
|
||||
return false;
|
||||
}
|
||||
if (mgm->movieStream->write(mgm->movieStream, &newStreamId, sizeof(newStreamId)) != sizeof(newStreamId)) {
|
||||
return false;
|
||||
}
|
||||
if (recursive) {
|
||||
if (mgm->movieStream->seek(mgm->movieStream, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (!_verifyMagic(mgm, mgm->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
_readTag(mgm, mgm->movieStream);
|
||||
if (_readTag(mgm, mgm->movieStream) != TAG_PREVIOUSLY) {
|
||||
return false;
|
||||
}
|
||||
if (mgm->previously == 0) {
|
||||
return true;
|
||||
}
|
||||
uint32_t currentStreamId = mgm->streamId;
|
||||
if (!_loadStream(mgm, mgm->previously)) {
|
||||
return false;
|
||||
}
|
||||
return _markStreamNext(mgm, currentStreamId, mgm->previously);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _streamEndReached(struct GBAMGMContext* mgm) {
|
||||
if (!mgm->d.isPlaying(&mgm->d)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t endStreamId = mgm->streamId;
|
||||
mgm->d.stopPlaying(&mgm->d);
|
||||
if (mgm->autorecord) {
|
||||
mgm->isRecording = true;
|
||||
_loadStream(mgm, endStreamId);
|
||||
_incrementStream(mgm, false);
|
||||
}
|
||||
}
|
||||
|
||||
struct VFile* GBAMGMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
return mgm->streamDir->openFile(mgm->streamDir, "movie.sav", flags);
|
||||
}
|
||||
|
||||
struct VFile* GBAMGMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||
struct GBAMGMContext* mgm = (struct GBAMGMContext*) rr;
|
||||
return mgm->streamDir->openFile(mgm->streamDir, "movie.ssm", flags);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef RR_MGM_H
|
||||
#define RR_MGM_H
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/supervisor/rr.h"
|
||||
|
||||
struct GBA;
|
||||
struct VDir;
|
||||
struct VFile;
|
||||
|
||||
enum GBAMGMTag {
|
||||
// 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 GBAMGMContext {
|
||||
struct GBARRContext d;
|
||||
|
||||
// Playback state
|
||||
bool isPlaying;
|
||||
bool autorecord;
|
||||
|
||||
// Recording state
|
||||
bool isRecording;
|
||||
bool inputThisFrame;
|
||||
|
||||
// Metadata
|
||||
uint32_t streamId;
|
||||
|
||||
uint32_t maxStreamId;
|
||||
off_t maxStreamIdOffset;
|
||||
off_t initFromOffset;
|
||||
off_t rrCountOffset;
|
||||
|
||||
// Streaming state
|
||||
struct VDir* streamDir;
|
||||
struct VFile* metadataFile;
|
||||
struct VFile* movieStream;
|
||||
uint16_t currentInput;
|
||||
enum GBAMGMTag peekedTag;
|
||||
uint32_t nextTime;
|
||||
uint32_t previously;
|
||||
};
|
||||
|
||||
void GBAMGMContextCreate(struct GBAMGMContext*);
|
||||
|
||||
bool GBAMGMSetStream(struct GBAMGMContext* mgm, struct VDir* stream);
|
||||
bool GBAMGMCreateStream(struct GBAMGMContext* mgm, enum GBARRInitFrom initFrom);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,183 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "vbm.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
static const char VBM_MAGIC[] = "VBM\x1A";
|
||||
|
||||
static void GBAVBMContextDestroy(struct GBARRContext*);
|
||||
|
||||
static bool GBAVBMStartPlaying(struct GBARRContext*, bool autorecord);
|
||||
static void GBAVBMStopPlaying(struct GBARRContext*);
|
||||
static bool GBAVBMStartRecording(struct GBARRContext*);
|
||||
static void GBAVBMStopRecording(struct GBARRContext*);
|
||||
|
||||
static bool GBAVBMIsPlaying(const struct GBARRContext*);
|
||||
static bool GBAVBMIsRecording(const struct GBARRContext*);
|
||||
|
||||
static void GBAVBMNextFrame(struct GBARRContext*);
|
||||
static uint16_t GBAVBMQueryInput(struct GBARRContext*);
|
||||
|
||||
static void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state);
|
||||
static void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state);
|
||||
|
||||
static struct VFile* GBAVBMOpenSavedata(struct GBARRContext*, int flags);
|
||||
static struct VFile* GBAVBMOpenSavestate(struct GBARRContext*, int flags);
|
||||
|
||||
void GBAVBMContextCreate(struct GBAVBMContext* vbm) {
|
||||
memset(vbm, 0, sizeof(*vbm));
|
||||
|
||||
vbm->d.destroy = GBAVBMContextDestroy;
|
||||
|
||||
vbm->d.startPlaying = GBAVBMStartPlaying;
|
||||
vbm->d.stopPlaying = GBAVBMStopPlaying;
|
||||
vbm->d.startRecording = GBAVBMStartRecording;
|
||||
vbm->d.stopRecording = GBAVBMStopRecording;
|
||||
|
||||
vbm->d.isPlaying = GBAVBMIsPlaying;
|
||||
vbm->d.isRecording = GBAVBMIsRecording;
|
||||
|
||||
vbm->d.nextFrame = GBAVBMNextFrame;
|
||||
vbm->d.logInput = 0;
|
||||
vbm->d.queryInput = GBAVBMQueryInput;
|
||||
|
||||
vbm->d.stateSaved = GBAVBMStateSaved;
|
||||
vbm->d.stateLoaded = GBAVBMStateLoaded;
|
||||
|
||||
vbm->d.openSavedata = GBAVBMOpenSavedata;
|
||||
vbm->d.openSavestate = GBAVBMOpenSavestate;
|
||||
}
|
||||
|
||||
bool GBAVBMStartPlaying(struct GBARRContext* rr, bool autorecord) {
|
||||
if (rr->isRecording(rr) || rr->isPlaying(rr) || autorecord) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->isPlaying = true;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, vbm->inputOffset, SEEK_SET);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBAVBMStopPlaying(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->isPlaying = false;
|
||||
}
|
||||
|
||||
bool GBAVBMStartRecording(struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void GBAVBMStopRecording(struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
}
|
||||
|
||||
bool GBAVBMIsPlaying(const struct GBARRContext* rr) {
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
return vbm->isPlaying;
|
||||
}
|
||||
|
||||
bool GBAVBMIsRecording(const struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
return false;
|
||||
}
|
||||
|
||||
void GBAVBMNextFrame(struct GBARRContext* rr) {
|
||||
UNUSED(rr);
|
||||
}
|
||||
|
||||
uint16_t GBAVBMQueryInput(struct GBARRContext* rr) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
uint16_t input;
|
||||
vbm->vbmFile->read(vbm->vbmFile, &input, sizeof(input));
|
||||
return input & 0x3FF;
|
||||
}
|
||||
|
||||
void GBAVBMStateSaved(struct GBARRContext* rr, struct GBASerializedState* state) {
|
||||
UNUSED(rr);
|
||||
UNUSED(state);
|
||||
}
|
||||
|
||||
void GBAVBMStateLoaded(struct GBARRContext* rr, const struct GBASerializedState* state) {
|
||||
UNUSED(rr);
|
||||
UNUSED(state);
|
||||
}
|
||||
|
||||
struct VFile* GBAVBMOpenSavedata(struct GBARRContext* rr, int flags) {
|
||||
UNUSED(rr);
|
||||
UNUSED(flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct VFile* GBAVBMOpenSavestate(struct GBARRContext* rr, int flags) {
|
||||
UNUSED(rr);
|
||||
UNUSED(flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GBAVBMContextDestroy(struct GBARRContext* rr) {
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
if (vbm->vbmFile) {
|
||||
vbm->vbmFile->close(vbm->vbmFile);
|
||||
}
|
||||
}
|
||||
|
||||
bool GBAVBMSetStream(struct GBAVBMContext* vbm, struct VFile* vf) {
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
char magic[4];
|
||||
vf->read(vf, magic, sizeof(magic));
|
||||
if (memcmp(magic, VBM_MAGIC, sizeof(magic)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t id;
|
||||
vf->read(vf, &id, sizeof(id));
|
||||
if (id != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vf->seek(vf, 4, SEEK_CUR);
|
||||
vf->read(vf, &vbm->d.frames, sizeof(vbm->d.frames));
|
||||
vf->read(vf, &vbm->d.rrCount, sizeof(vbm->d.rrCount));
|
||||
|
||||
uint8_t flags;
|
||||
vf->read(vf, &flags, sizeof(flags));
|
||||
if (flags & 1) {
|
||||
// Incompatible savestate format
|
||||
return false;
|
||||
}
|
||||
if (flags & 2) {
|
||||
// TODO: Implement SRAM loading
|
||||
return false;
|
||||
}
|
||||
|
||||
vf->seek(vf, 1, SEEK_CUR);
|
||||
vf->read(vf, &flags, sizeof(flags));
|
||||
if ((flags & 0x7) != 1) {
|
||||
// Non-GBA movie
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: parse more flags
|
||||
|
||||
vf->seek(vf, 0x3C, SEEK_SET);
|
||||
vf->read(vf, &vbm->inputOffset, sizeof(vbm->inputOffset));
|
||||
vf->seek(vf, vbm->inputOffset, SEEK_SET);
|
||||
vbm->vbmFile = vf;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/common.h"
|
||||
|
||||
#include "gba/supervisor/rr.h"
|
||||
|
||||
struct GBAVBMContext {
|
||||
struct GBARRContext d;
|
||||
|
||||
bool isPlaying;
|
||||
|
||||
struct VFile* vbmFile;
|
||||
int32_t inputOffset;
|
||||
};
|
||||
|
||||
void GBAVBMContextCreate(struct GBAVBMContext*);
|
||||
|
||||
bool GBAVBMSetStream(struct GBAVBMContext*, struct VFile*);
|
|
@ -49,15 +49,13 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* 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;
|
||||
state->associatedStreamId = 0;
|
||||
if (gba->rr) {
|
||||
gba->rr->stateSaved(gba->rr, state);
|
||||
}
|
||||
}
|
||||
|
||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
||||
if (state->versionMagic != GBA_SAVESTATE_MAGIC) {
|
||||
GBALog(gba, GBA_LOG_WARN, "Invalid or too new savestate");
|
||||
return;
|
||||
|
@ -114,17 +112,8 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* 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);
|
||||
if (gba->rr) {
|
||||
gba->rr->stateLoaded(gba->rr, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -292,7 +292,7 @@ struct VDir;
|
|||
struct GBAThread;
|
||||
|
||||
void GBASerialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
void GBADeserialize(struct GBA* gba, struct GBASerializedState* state);
|
||||
void GBADeserialize(struct GBA* gba, const struct GBASerializedState* state);
|
||||
|
||||
bool GBASaveState(struct GBAThread* thread, struct VDir* dir, int slot, bool screenshot);
|
||||
bool GBALoadState(struct GBAThread* thread, struct VDir* dir, int slot);
|
||||
|
|
|
@ -36,11 +36,11 @@ static void _switchMode(struct GBASIO* sio) {
|
|||
} else {
|
||||
sio->mode = (enum GBASIOMode) (mode & 0xC);
|
||||
}
|
||||
if (oldMode != mode) {
|
||||
if (oldMode != sio->mode) {
|
||||
if (sio->activeDriver && sio->activeDriver->unload) {
|
||||
sio->activeDriver->unload(sio->activeDriver);
|
||||
}
|
||||
sio->activeDriver = _lookupDriver(sio, mode);
|
||||
sio->activeDriver = _lookupDriver(sio, sio->mode);
|
||||
if (sio->activeDriver && sio->activeDriver->load) {
|
||||
sio->activeDriver->load(sio->activeDriver);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,13 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "U32E", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
{ "U32P", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
// Dragon Ball Z - The Legacy of Goku
|
||||
{ "ALGP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Dragon Ball Z - Taiketsu
|
||||
{ "BDBE", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BDBP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Drill Dozer
|
||||
{ "V49J", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||
{ "V49E", SAVEDATA_SRAM, HW_RUMBLE, IDLE_LOOP_NONE },
|
||||
|
@ -28,6 +35,9 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
// Final Fantasy Tactics Advance
|
||||
{ "AFXE", SAVEDATA_FLASH512, HW_NONE, 0x8000428 },
|
||||
|
||||
// F-Zero - Climax
|
||||
{ "BFTJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Golden Sun: The Lost Age
|
||||
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A },
|
||||
|
||||
|
@ -77,15 +87,30 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRI", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRS", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRD", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPRF", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Pokemon LeafGreen
|
||||
{ "BPGJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGE", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGP", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGI", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGS", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGD", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "BPGF", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// RockMan EXE 4.5 - Real Operation
|
||||
{ "BR4J", SAVEDATA_FLASH512, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Rocky
|
||||
{ "AR8E", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
{ "AROP", SAVEDATA_EEPROM, HW_NONE, IDLE_LOOP_NONE },
|
||||
|
||||
// Sennen Kazoku
|
||||
{ "BKAJ", SAVEDATA_FLASH1M, HW_RTC, IDLE_LOOP_NONE },
|
||||
|
||||
// Shin Bokura no Taiyou: Gyakushuu no Sabata
|
||||
{ "U33J", SAVEDATA_EEPROM, HW_RTC | HW_LIGHT_SENSOR, IDLE_LOOP_NONE },
|
||||
|
||||
|
|
|
@ -5,64 +5,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "rr.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#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) {
|
||||
return;
|
||||
}
|
||||
|
||||
gba->rr = calloc(1, sizeof(*gba->rr));
|
||||
}
|
||||
|
||||
void GBARRContextDestroy(struct GBA* gba) {
|
||||
if (!gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GBARRIsPlaying(gba->rr)) {
|
||||
GBARRStopPlaying(gba->rr);
|
||||
}
|
||||
if (GBARRIsRecording(gba->rr)) {
|
||||
GBARRStopRecording(gba->rr);
|
||||
}
|
||||
if (gba->rr->metadataFile) {
|
||||
gba->rr->metadataFile->close(gba->rr->metadataFile);
|
||||
}
|
||||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
|
||||
free(gba->rr);
|
||||
gba->rr = 0;
|
||||
}
|
||||
|
||||
void GBARRSaveState(struct GBA* gba) {
|
||||
void GBARRInitRecord(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
@ -71,17 +16,17 @@ void GBARRSaveState(struct GBA* gba) {
|
|||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_TRUNC | O_CREAT | O_WRONLY);
|
||||
gba->rr->savedata = gba->rr->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);
|
||||
gba->rr->savedata = gba->rr->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);
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_TRUNC | O_CREAT | O_RDWR);
|
||||
GBASaveStateNamed(gba, vf, false);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
|
@ -89,7 +34,7 @@ void GBARRSaveState(struct GBA* gba) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBARRLoadState(struct GBA* gba) {
|
||||
void GBARRInitPlay(struct GBA* gba) {
|
||||
if (!gba || !gba->rr) {
|
||||
return;
|
||||
}
|
||||
|
@ -98,14 +43,14 @@ void GBARRLoadState(struct GBA* gba) {
|
|||
if (gba->rr->savedata) {
|
||||
gba->rr->savedata->close(gba->rr->savedata);
|
||||
}
|
||||
gba->rr->savedata = _openSavedata(gba->rr, O_RDONLY);
|
||||
gba->rr->savedata = gba->rr->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);
|
||||
struct VFile* vf = gba->rr->openSavestate(gba->rr, O_RDONLY);
|
||||
GBALoadStateNamed(gba, vf);
|
||||
vf->close(vf);
|
||||
} else {
|
||||
|
@ -113,460 +58,16 @@ void GBARRLoadState(struct GBA* gba) {
|
|||
}
|
||||
}
|
||||
|
||||
bool GBARRInitStream(struct GBARRContext* rr, struct VDir* stream) {
|
||||
if (rr->movieStream && !rr->movieStream->close(rr->movieStream)) {
|
||||
return false;
|
||||
void GBARRDestroy(struct GBARRContext* rr) {
|
||||
if (rr->isPlaying(rr)) {
|
||||
rr->stopPlaying(rr);
|
||||
}
|
||||
|
||||
if (rr->metadataFile && !rr->metadataFile->close(rr->metadataFile)) {
|
||||
return false;
|
||||
if (rr->isRecording(rr)) {
|
||||
rr->stopRecording(rr);
|
||||
}
|
||||
|
||||
rr->streamDir = stream;
|
||||
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;
|
||||
if (rr->savedata) {
|
||||
rr->savedata->close(rr->savedata);
|
||||
rr->savedata = 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) {
|
||||
if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rr->isPlaying = true;
|
||||
if (!GBARRLoadStream(rr, 1)) {
|
||||
rr->isPlaying = false;
|
||||
return false;
|
||||
}
|
||||
rr->autorecord = autorecord;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBARRStopPlaying(struct GBARRContext* rr) {
|
||||
if (!GBARRIsPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
rr->isPlaying = false;
|
||||
if (rr->movieStream) {
|
||||
rr->movieStream->close(rr->movieStream);
|
||||
rr->movieStream = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBARRStartRecording(struct GBARRContext* rr) {
|
||||
if (GBARRIsRecording(rr) || GBARRIsPlaying(rr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!rr->maxStreamIdOffset) {
|
||||
_emitTag(rr, rr->metadataFile, TAG_MAX_STREAM);
|
||||
rr->maxStreamIdOffset = rr->metadataFile->seek(rr->metadataFile, 0, SEEK_CUR);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
||||
}
|
||||
|
||||
rr->isRecording = true;
|
||||
return 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) {
|
||||
return rr && rr->isPlaying;
|
||||
}
|
||||
|
||||
bool GBARRIsRecording(struct GBARRContext* rr) {
|
||||
return rr && rr->isRecording;
|
||||
}
|
||||
|
||||
void GBARRNextFrame(struct GBARRContext* rr) {
|
||||
if (!GBARRIsRecording(rr) && !GBARRIsPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (GBARRIsPlaying(rr)) {
|
||||
while (rr->peekedTag == TAG_INPUT) {
|
||||
_readTag(rr, rr->movieStream);
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Desync detected!");
|
||||
}
|
||||
if (rr->peekedTag == TAG_LAG) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame marked in stream");
|
||||
if (rr->inputThisFrame) {
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Lag frame in stream does not match movie");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++rr->frames;
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Frame: %u", rr->frames);
|
||||
if (!rr->inputThisFrame) {
|
||||
++rr->lagFrames;
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Lag frame: %u", rr->lagFrames);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!GBARRIsRecording(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t GBARRQueryInput(struct GBARRContext* rr) {
|
||||
if (!GBARRIsPlaying(rr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rr->peekedTag == TAG_INPUT) {
|
||||
_readTag(rr, rr->movieStream);
|
||||
}
|
||||
rr->inputThisFrame = true;
|
||||
if (rr->currentInput == INVALID_INPUT) {
|
||||
GBALog(0, GBA_LOG_WARN, "[RR] Stream did not specify input");
|
||||
}
|
||||
GBALog(0, GBA_LOG_DEBUG, "[RR] Input replay: %03X", rr->currentInput);
|
||||
return rr->currentInput;
|
||||
}
|
||||
|
||||
bool GBARRFinishSegment(struct GBARRContext* rr) {
|
||||
if (rr->movieStream) {
|
||||
if (!_emitEnd(rr, rr->movieStream)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return GBARRIncrementStream(rr, false);
|
||||
}
|
||||
|
||||
bool GBARRSkipSegment(struct GBARRContext* rr) {
|
||||
rr->nextTime = 0;
|
||||
while (_readTag(rr, rr->movieStream) != TAG_EOF);
|
||||
if (!rr->nextTime || !GBARRLoadStream(rr, rr->nextTime)) {
|
||||
_streamEndReached(rr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBARRMarkRerecord(struct GBARRContext* rr) {
|
||||
++rr->rrCount;
|
||||
rr->metadataFile->seek(rr->metadataFile, rr->rrCountOffset, SEEK_SET);
|
||||
rr->metadataFile->write(rr->metadataFile, &rr->rrCount, sizeof(rr->rrCount));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _emitMagic(struct GBARRContext* rr, struct VFile* vf) {
|
||||
UNUSED(rr);
|
||||
return vf->write(vf, BINARY_MAGIC, 4) == 4;
|
||||
}
|
||||
|
||||
bool _verifyMagic(struct GBARRContext* rr, struct VFile* vf) {
|
||||
UNUSED(rr);
|
||||
char buffer[4];
|
||||
if (vf->read(vf, buffer, sizeof(buffer)) != sizeof(buffer)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(buffer, BINARY_MAGIC, sizeof(buffer)) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
enum GBARRTag _readTag(struct GBARRContext* rr, struct VFile* vf) {
|
||||
if (!rr || !vf) {
|
||||
return TAG_EOF;
|
||||
}
|
||||
|
||||
enum GBARRTag tag = rr->peekedTag;
|
||||
switch (tag) {
|
||||
case TAG_INPUT:
|
||||
vf->read(vf, &rr->currentInput, sizeof(uint16_t));
|
||||
break;
|
||||
case TAG_PREVIOUSLY:
|
||||
vf->read(vf, &rr->previously, sizeof(rr->previously));
|
||||
break;
|
||||
case TAG_NEXT_TIME:
|
||||
vf->read(vf, &rr->nextTime, sizeof(rr->nextTime));
|
||||
break;
|
||||
case TAG_MAX_STREAM:
|
||||
vf->read(vf, &rr->maxStreamId, sizeof(rr->maxStreamId));
|
||||
break;
|
||||
case TAG_FRAME_COUNT:
|
||||
vf->read(vf, &rr->frames, sizeof(rr->frames));
|
||||
break;
|
||||
case TAG_LAG_COUNT:
|
||||
vf->read(vf, &rr->lagFrames, sizeof(rr->lagFrames));
|
||||
break;
|
||||
case TAG_RR_COUNT:
|
||||
vf->read(vf, &rr->rrCount, sizeof(rr->rrCount));
|
||||
break;
|
||||
|
||||
case TAG_INIT_EX_NIHILO:
|
||||
rr->initFrom = INIT_EX_NIHILO;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVEGAME:
|
||||
rr->initFrom = INIT_FROM_SAVEGAME;
|
||||
break;
|
||||
case TAG_INIT_FROM_SAVESTATE:
|
||||
rr->initFrom = INIT_FROM_SAVESTATE;
|
||||
break;
|
||||
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);
|
||||
rr->destroy(rr);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
#include "util/common.h"
|
||||
|
||||
struct GBA;
|
||||
struct VDir;
|
||||
#include "gba/serialize.h"
|
||||
|
||||
struct VFile;
|
||||
|
||||
enum GBARRInitFrom {
|
||||
|
@ -19,95 +19,39 @@ enum GBARRInitFrom {
|
|||
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;
|
||||
void (*destroy)(struct GBARRContext*);
|
||||
|
||||
// Recording state
|
||||
bool isRecording;
|
||||
bool inputThisFrame;
|
||||
bool (*startPlaying)(struct GBARRContext*, bool autorecord);
|
||||
void (*stopPlaying)(struct GBARRContext*);
|
||||
bool (*startRecording)(struct GBARRContext*);
|
||||
void (*stopRecording)(struct GBARRContext*);
|
||||
|
||||
bool (*isPlaying)(const struct GBARRContext*);
|
||||
bool (*isRecording)(const struct GBARRContext*);
|
||||
|
||||
void (*nextFrame)(struct GBARRContext*);
|
||||
void (*logInput)(struct GBARRContext*, uint16_t input);
|
||||
uint16_t (*queryInput)(struct GBARRContext*);
|
||||
|
||||
void (*stateSaved)(struct GBARRContext*, struct GBASerializedState*);
|
||||
void (*stateLoaded)(struct GBARRContext*, const struct GBASerializedState*);
|
||||
|
||||
struct VFile* (*openSavedata)(struct GBARRContext* mgm, int flags);
|
||||
struct VFile* (*openSavestate)(struct GBARRContext* mgm, int flags);
|
||||
|
||||
// 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* 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*);
|
||||
void GBARRDestroy(struct GBARRContext*);
|
||||
|
||||
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*);
|
||||
bool GBARRStartRecording(struct GBARRContext*);
|
||||
void GBARRStopRecording(struct GBARRContext*);
|
||||
|
||||
bool GBARRIsPlaying(struct GBARRContext*);
|
||||
bool GBARRIsRecording(struct GBARRContext*);
|
||||
|
||||
void GBARRNextFrame(struct GBARRContext*);
|
||||
void GBARRLogInput(struct GBARRContext*, uint16_t input);
|
||||
uint16_t GBARRQueryInput(struct GBARRContext*);
|
||||
void GBARRInitRecord(struct GBA*);
|
||||
void GBARRInitPlay(struct GBA*);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "gba/cheats.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/config.h"
|
||||
#include "gba/rr/mgm.h"
|
||||
#include "gba/rr/vbm.h"
|
||||
|
||||
#include "debugger/debugger.h"
|
||||
|
||||
|
@ -117,6 +119,7 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
struct GBACheatDevice cheatDevice;
|
||||
struct GBAThread* threadContext = context;
|
||||
struct ARMComponent* components[GBA_COMPONENT_MAX] = {};
|
||||
struct GBARRContext* movie = 0;
|
||||
int numComponents = GBA_COMPONENT_MAX;
|
||||
|
||||
#if !defined(_WIN32) && defined(USE_PTHREADS)
|
||||
|
@ -131,6 +134,8 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
gba.sync = &threadContext->sync;
|
||||
threadContext->gba = &gba;
|
||||
gba.logLevel = threadContext->logLevel;
|
||||
gba.logHandler = threadContext->logHandler;
|
||||
gba.stream = threadContext->stream;
|
||||
gba.idleOptimization = threadContext->idleOptimization;
|
||||
#ifdef USE_PTHREADS
|
||||
pthread_setspecific(_contextKey, threadContext);
|
||||
|
@ -170,7 +175,43 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
}
|
||||
}
|
||||
|
||||
if (threadContext->movie) {
|
||||
struct VDir* movieDir = VDirOpen(threadContext->movie);
|
||||
#ifdef USE_LIBZIP
|
||||
if (!movieDir) {
|
||||
movieDir = VDirOpenZip(threadContext->movie, 0);
|
||||
}
|
||||
#endif
|
||||
if (movieDir) {
|
||||
struct GBAMGMContext* mgm = malloc(sizeof(*mgm));
|
||||
GBAMGMContextCreate(mgm);
|
||||
if (!GBAMGMSetStream(mgm, movieDir)) {
|
||||
mgm->d.destroy(&mgm->d);
|
||||
} else {
|
||||
movie = &mgm->d;
|
||||
}
|
||||
} else {
|
||||
struct VFile* movieFile = VFileOpen(threadContext->movie, O_RDONLY);
|
||||
if (movieFile) {
|
||||
struct GBAVBMContext* vbm = malloc(sizeof(*vbm));
|
||||
GBAVBMContextCreate(vbm);
|
||||
if (!GBAVBMSetStream(vbm, movieFile)) {
|
||||
vbm->d.destroy(&vbm->d);
|
||||
} else {
|
||||
movie = &vbm->d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ARMReset(&cpu);
|
||||
|
||||
if (movie) {
|
||||
gba.rr = movie;
|
||||
movie->startPlaying(movie, false);
|
||||
GBARRInitPlay(&gba);
|
||||
}
|
||||
|
||||
if (threadContext->skipBios) {
|
||||
GBASkipBIOS(&cpu);
|
||||
}
|
||||
|
@ -256,6 +297,11 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
|||
GBACheatDeviceDestroy(&cheatDevice);
|
||||
}
|
||||
|
||||
if (movie) {
|
||||
movie->destroy(movie);
|
||||
free(movie);
|
||||
}
|
||||
|
||||
threadContext->sync.videoFrameOn = false;
|
||||
ConditionWake(&threadContext->sync.videoFrameAvailableCond);
|
||||
ConditionWake(&threadContext->sync.audioRequiredCond);
|
||||
|
@ -295,12 +341,12 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
|
|||
} else {
|
||||
threadContext->rom = VFileOpen(args->fname, O_RDONLY);
|
||||
threadContext->gameDir = 0;
|
||||
#if ENABLE_LIBZIP
|
||||
#if USE_LIBZIP
|
||||
if (!threadContext->gameDir) {
|
||||
threadContext->gameDir = VDirOpenZip(args->fname, 0);
|
||||
}
|
||||
#endif
|
||||
#if ENABLE_LZMA
|
||||
#if USE_LZMA
|
||||
if (!threadContext->gameDir) {
|
||||
threadContext->gameDir = VDirOpen7z(args->fname, 0);
|
||||
}
|
||||
|
@ -309,6 +355,7 @@ void GBAMapArgumentsToContext(const struct GBAArguments* args, struct GBAThread*
|
|||
threadContext->fname = args->fname;
|
||||
threadContext->patch = VFileOpen(args->patch, O_RDONLY);
|
||||
threadContext->cheatsFile = VFileOpen(args->cheatsFile, O_RDONLY);
|
||||
threadContext->movie = args->movie;
|
||||
}
|
||||
|
||||
bool GBAThreadStart(struct GBAThread* threadContext) {
|
||||
|
|
|
@ -20,7 +20,6 @@ struct GBACheatSet;
|
|||
struct GBAOptions;
|
||||
|
||||
typedef void (*ThreadCallback)(struct GBAThread* threadContext);
|
||||
typedef void (*LogHandler)(struct GBAThread*, enum GBALogLevel, const char* format, va_list args);
|
||||
|
||||
enum ThreadState {
|
||||
THREAD_INITIALIZED = -1,
|
||||
|
@ -49,11 +48,6 @@ 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;
|
||||
|
@ -72,6 +66,7 @@ struct GBAThread {
|
|||
struct VFile* patch;
|
||||
struct VFile* cheatsFile;
|
||||
const char* fname;
|
||||
const char* movie;
|
||||
int activeKeys;
|
||||
struct GBAAVStream* stream;
|
||||
struct Configuration* overrides;
|
||||
|
@ -94,7 +89,7 @@ struct GBAThread {
|
|||
enum ThreadState savedState;
|
||||
int interruptDepth;
|
||||
|
||||
LogHandler logHandler;
|
||||
GBALogHandler logHandler;
|
||||
int logLevel;
|
||||
ThreadCallback startCallback;
|
||||
ThreadCallback cleanCallback;
|
||||
|
|
|
@ -235,7 +235,7 @@ static void GBAVideoDummyRendererGetPixels(struct GBAVideoRenderer* renderer, un
|
|||
}
|
||||
|
||||
|
||||
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state) {
|
||||
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state) {
|
||||
memcpy(state->vram, video->renderer->vram, SIZE_VRAM);
|
||||
memcpy(state->oam, video->oam.raw, SIZE_OAM);
|
||||
memcpy(state->pram, video->palette, SIZE_PALETTE_RAM);
|
||||
|
@ -249,7 +249,7 @@ void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state)
|
|||
state->video.frameCounter = video->frameCounter;
|
||||
}
|
||||
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state) {
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) {
|
||||
memcpy(video->renderer->vram, state->vram, SIZE_VRAM);
|
||||
int i;
|
||||
for (i = 0; i < SIZE_OAM; i += 2) {
|
||||
|
|
|
@ -201,7 +201,7 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles);
|
|||
void GBAVideoWriteDISPSTAT(struct GBAVideo* video, uint16_t value);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAVideoSerialize(struct GBAVideo* video, struct GBASerializedState* state);
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, struct GBASerializedState* state);
|
||||
void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState* state);
|
||||
void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
|
||||
static const struct option _options[] = {
|
||||
{ "bios", required_argument, 0, 'b' },
|
||||
{ "cheats", required_argument, 0, 'c' },
|
||||
{ "dirmode", required_argument, 0, 'D' },
|
||||
{ "cheats", required_argument, 0, 'c' },
|
||||
{ "dirmode", required_argument, 0, 'D' },
|
||||
{ "frameskip", required_argument, 0, 's' },
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
{ "debug", no_argument, 0, 'd' },
|
||||
|
@ -43,6 +43,7 @@ static const struct option _options[] = {
|
|||
#ifdef USE_GDB_STUB
|
||||
{ "gdb", no_argument, 0, 'g' },
|
||||
#endif
|
||||
{ "movie", required_argument, 0, 'v' },
|
||||
{ "patch", required_argument, 0, 'p' },
|
||||
{ 0, 0, 0, 0 }
|
||||
};
|
||||
|
@ -52,7 +53,7 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
|
|||
bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int argc, char* const* argv, struct SubParser* subparser) {
|
||||
int ch;
|
||||
char options[64] =
|
||||
"b:c:Dl:p:s:"
|
||||
"b:c:Dl:p:s:v:"
|
||||
#ifdef USE_CLI_DEBUGGER
|
||||
"d"
|
||||
#endif
|
||||
|
@ -101,6 +102,9 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
|
|||
case 's':
|
||||
GBAConfigSetDefaultValue(config, "frameskip", optarg);
|
||||
break;
|
||||
case 'v':
|
||||
opts->movie = strdup(optarg);
|
||||
break;
|
||||
default:
|
||||
if (subparser) {
|
||||
if (!subparser->parse(subparser, config, ch, optarg)) {
|
||||
|
@ -125,6 +129,9 @@ void freeArguments(struct GBAArguments* opts) {
|
|||
|
||||
free(opts->patch);
|
||||
opts->patch = 0;
|
||||
|
||||
free(opts->movie);
|
||||
opts->movie = 0;
|
||||
}
|
||||
|
||||
void initParserForGraphics(struct SubParser* parser, struct GraphicsOpts* opts) {
|
||||
|
@ -211,6 +218,7 @@ void usage(const char* arg0, const char* extraOptions) {
|
|||
#ifdef USE_GDB_STUB
|
||||
puts(" -g, --gdb Start GDB session (default port 2345)");
|
||||
#endif
|
||||
puts(" -v, --movie FILE Play back a movie of recorded input");
|
||||
puts(" -p, --patch FILE Apply a specified patch file when running");
|
||||
puts(" -s, --frameskip N Skip every N frames");
|
||||
if (extraOptions) {
|
||||
|
|
|
@ -26,6 +26,7 @@ struct GBAArguments {
|
|||
char* patch;
|
||||
char* cheatsFile;
|
||||
bool dirmode;
|
||||
char* movie;
|
||||
|
||||
enum DebuggerType debuggerType;
|
||||
bool debugAtStart;
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#include <libswscale/swscale.h>
|
||||
|
||||
static void _ffmpegPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
static void _ffmpegPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right);
|
||||
static void _ffmpegPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
|
||||
enum {
|
||||
PREFERRED_SAMPLE_RATE = 0x8000
|
||||
|
@ -33,6 +33,7 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
|||
|
||||
encoder->d.postVideoFrame = _ffmpegPostVideoFrame;
|
||||
encoder->d.postAudioFrame = _ffmpegPostAudioFrame;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
|
||||
encoder->audioCodec = 0;
|
||||
encoder->videoCodec = 0;
|
||||
|
@ -288,10 +289,18 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->videoFrame->height = encoder->video->height;
|
||||
encoder->videoFrame->pts = 0;
|
||||
encoder->scaleContext = sws_getContext(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS,
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
AV_PIX_FMT_RGB565,
|
||||
#else
|
||||
AV_PIX_FMT_BGR555,
|
||||
#endif
|
||||
#else
|
||||
#ifndef USE_LIBAV
|
||||
AV_PIX_FMT_0BGR32,
|
||||
#else
|
||||
AV_PIX_FMT_BGR32,
|
||||
#endif
|
||||
#endif
|
||||
encoder->videoFrame->width, encoder->videoFrame->height, encoder->video->pix_fmt,
|
||||
SWS_POINT, 0, 0, 0);
|
||||
|
@ -349,7 +358,7 @@ bool FFmpegEncoderIsOpen(struct FFmpegEncoder* encoder) {
|
|||
return !!encoder->context;
|
||||
}
|
||||
|
||||
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) {
|
||||
void _ffmpegPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||
struct FFmpegEncoder* encoder = (struct FFmpegEncoder*) stream;
|
||||
if (!encoder->context || !encoder->audioCodec) {
|
||||
return;
|
||||
|
@ -416,7 +425,7 @@ void _ffmpegPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer*
|
|||
uint8_t* pixels;
|
||||
unsigned stride;
|
||||
renderer->getPixels(renderer, &stride, (void**) &pixels);
|
||||
stride *= 4;
|
||||
stride *= BYTES_PER_PIXEL;
|
||||
|
||||
AVPacket packet;
|
||||
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
#include "gba/video.h"
|
||||
|
||||
static void _magickPostVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
static void _magickPostAudioFrame(struct GBAAVStream*, int32_t left, int32_t right);
|
||||
static void _magickPostAudioFrame(struct GBAAVStream*, int16_t left, int16_t right);
|
||||
|
||||
void ImageMagickGIFEncoderInit(struct ImageMagickGIFEncoder* encoder) {
|
||||
encoder->wand = 0;
|
||||
|
||||
encoder->d.postVideoFrame = _magickPostVideoFrame;
|
||||
encoder->d.postAudioFrame = _magickPostAudioFrame;
|
||||
encoder->d.postAudioBuffer = 0;
|
||||
|
||||
encoder->frameskip = 2;
|
||||
}
|
||||
|
@ -70,7 +71,7 @@ static void _magickPostVideoFrame(struct GBAAVStream* stream, struct GBAVideoRen
|
|||
++encoder->currentFrame;
|
||||
}
|
||||
|
||||
static void _magickPostAudioFrame(struct GBAAVStream* stream, int32_t left, int32_t right) {
|
||||
static void _magickPostAudioFrame(struct GBAAVStream* stream, int16_t left, int16_t right) {
|
||||
UNUSED(stream);
|
||||
UNUSED(left);
|
||||
UNUSED(right);
|
||||
|
|
|
@ -0,0 +1,353 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "libretro.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/supervisor/overrides.h"
|
||||
#include "gba/video.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define SAMPLES 1024
|
||||
|
||||
static retro_environment_t environCallback;
|
||||
static retro_video_refresh_t videoCallback;
|
||||
static retro_audio_sample_batch_t audioCallback;
|
||||
static retro_input_poll_t inputPollCallback;
|
||||
static retro_input_state_t inputCallback;
|
||||
static retro_log_printf_t logCallback;
|
||||
|
||||
static void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args);
|
||||
|
||||
static void _postAudioBuffer(struct GBAAVStream*, struct GBAAudio* audio);
|
||||
static void _postVideoFrame(struct GBAAVStream*, struct GBAVideoRenderer* renderer);
|
||||
|
||||
static struct GBA gba;
|
||||
static struct ARMCore cpu;
|
||||
static struct GBAVideoSoftwareRenderer renderer;
|
||||
static struct VFile* rom;
|
||||
static void* data;
|
||||
static struct VFile* save;
|
||||
static void* savedata;
|
||||
static struct GBAAVStream stream;
|
||||
|
||||
unsigned retro_api_version(void) {
|
||||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t env) {
|
||||
environCallback = env;
|
||||
}
|
||||
|
||||
void retro_set_video_refresh(retro_video_refresh_t video) {
|
||||
videoCallback = video;
|
||||
}
|
||||
|
||||
void retro_set_audio_sample(retro_audio_sample_t audio) {
|
||||
UNUSED(audio);
|
||||
}
|
||||
|
||||
void retro_set_audio_sample_batch(retro_audio_sample_batch_t audioBatch) {
|
||||
audioCallback = audioBatch;
|
||||
}
|
||||
|
||||
void retro_set_input_poll(retro_input_poll_t inputPoll) {
|
||||
inputPollCallback = inputPoll;
|
||||
}
|
||||
|
||||
void retro_set_input_state(retro_input_state_t input) {
|
||||
inputCallback = input;
|
||||
}
|
||||
|
||||
void retro_get_system_info(struct retro_system_info* info) {
|
||||
info->need_fullpath = false;
|
||||
info->valid_extensions = "gba";
|
||||
info->library_version = PROJECT_VERSION;
|
||||
info->library_name = PROJECT_NAME;
|
||||
info->block_extract = false;
|
||||
}
|
||||
|
||||
void retro_get_system_av_info(struct retro_system_av_info* info) {
|
||||
info->geometry.base_width = VIDEO_HORIZONTAL_PIXELS;
|
||||
info->geometry.base_height = VIDEO_VERTICAL_PIXELS;
|
||||
info->geometry.max_width = VIDEO_HORIZONTAL_PIXELS;
|
||||
info->geometry.max_height = VIDEO_VERTICAL_PIXELS;
|
||||
info->timing.fps = GBA_ARM7TDMI_FREQUENCY / (float) VIDEO_TOTAL_LENGTH;
|
||||
info->timing.sample_rate = 32768;
|
||||
}
|
||||
|
||||
void retro_init(void) {
|
||||
enum retro_pixel_format fmt;
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
fmt = RETRO_PIXEL_FORMAT_RGB565;
|
||||
#else
|
||||
#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
|
||||
fmt = RETRO_PIXEL_FORMAT_0RGB1555;
|
||||
#endif
|
||||
#else
|
||||
#warning This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5
|
||||
fmt = RETRO_PIXEL_FORMAT_XRGB8888;
|
||||
#endif
|
||||
environCallback(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt);
|
||||
|
||||
struct retro_input_descriptor inputDescriptors[] = {
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A, "A" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B, "B" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT, "Select" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START, "Start" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT, "Right" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT, "Left" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP, "Up" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN, "Down" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R, "R" },
|
||||
{ 0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L, "L" }
|
||||
};
|
||||
environCallback(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, &inputDescriptors);
|
||||
|
||||
// TODO: RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME when BIOS booting is supported
|
||||
// TODO: RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE
|
||||
|
||||
struct retro_log_callback log;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
|
||||
logCallback = log.log;
|
||||
} else {
|
||||
logCallback = 0;
|
||||
}
|
||||
|
||||
stream.postAudioFrame = 0;
|
||||
stream.postAudioBuffer = _postAudioBuffer;
|
||||
stream.postVideoFrame = _postVideoFrame;
|
||||
|
||||
GBACreate(&gba);
|
||||
ARMSetComponents(&cpu, &gba.d, 0, 0);
|
||||
ARMInit(&cpu);
|
||||
gba.logLevel = 0; // TODO: Settings
|
||||
gba.logHandler = GBARetroLog;
|
||||
gba.stream = &stream;
|
||||
gba.idleOptimization = IDLE_LOOP_REMOVE; // TODO: Settings
|
||||
rom = 0;
|
||||
|
||||
GBAVideoSoftwareRendererCreate(&renderer);
|
||||
renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||
renderer.outputBufferStride = 256;
|
||||
GBAVideoAssociateRenderer(&gba.video, &renderer.d);
|
||||
|
||||
GBAAudioResizeBuffer(&gba.audio, SAMPLES);
|
||||
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
blip_set_rates(gba.audio.left, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
blip_set_rates(gba.audio.right, GBA_ARM7TDMI_FREQUENCY, 32768);
|
||||
#endif
|
||||
}
|
||||
|
||||
void retro_deinit(void) {
|
||||
GBADestroy(&gba);
|
||||
}
|
||||
|
||||
void retro_run(void) {
|
||||
int keys;
|
||||
gba.keySource = &keys;
|
||||
inputPollCallback();
|
||||
|
||||
keys = 0;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A)) << 0;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B)) << 1;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_SELECT)) << 2;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_START)) << 3;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) << 4;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) << 5;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) << 6;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) << 7;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R)) << 8;
|
||||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
|
||||
|
||||
int frameCount = gba.video.frameCounter;
|
||||
while (gba.video.frameCounter == frameCount) {
|
||||
ARMRunLoop(&cpu);
|
||||
}
|
||||
videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * 256);
|
||||
}
|
||||
|
||||
void retro_reset(void) {
|
||||
ARMReset(&cpu);
|
||||
}
|
||||
|
||||
bool retro_load_game(const struct retro_game_info* game) {
|
||||
if (game->data) {
|
||||
data = malloc(game->size);
|
||||
memcpy(data, game->data, game->size);
|
||||
rom = VFileFromMemory(data, game->size);
|
||||
} else {
|
||||
data = 0;
|
||||
rom = VFileOpen(game->path, O_RDONLY);
|
||||
}
|
||||
if (!rom) {
|
||||
return false;
|
||||
}
|
||||
if (!GBAIsROM(rom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
savedata = malloc(SIZE_CART_FLASH1M);
|
||||
save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||
|
||||
GBALoadROM(&gba, rom, save, game->path);
|
||||
|
||||
struct GBACartridgeOverride override;
|
||||
const struct GBACartridge* cart = (const struct GBACartridge*) gba.memory.rom;
|
||||
memcpy(override.id, &cart->id, sizeof(override.id));
|
||||
if (GBAOverrideFind(0, &override)) {
|
||||
GBAOverrideApply(&gba, &override);
|
||||
}
|
||||
|
||||
ARMReset(&cpu);
|
||||
return true;
|
||||
}
|
||||
|
||||
void retro_unload_game(void) {
|
||||
rom->close(rom);
|
||||
rom = 0;
|
||||
free(data);
|
||||
data = 0;
|
||||
save->close(save);
|
||||
save = 0;
|
||||
free(savedata);
|
||||
savedata = 0;
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void) {
|
||||
return sizeof(struct GBASerializedState);
|
||||
}
|
||||
|
||||
bool retro_serialize(void* data, size_t size) {
|
||||
if (size != retro_serialize_size()) {
|
||||
return false;
|
||||
}
|
||||
GBASerialize(&gba, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool retro_unserialize(const void* data, size_t size) {
|
||||
if (size != retro_serialize_size()) {
|
||||
return false;
|
||||
}
|
||||
GBADeserialize(&gba, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void retro_cheat_reset(void) {
|
||||
// TODO: Cheats
|
||||
}
|
||||
|
||||
void retro_cheat_set(unsigned index, bool enabled, const char* code) {
|
||||
// TODO: Cheats
|
||||
UNUSED(index);
|
||||
UNUSED(enabled);
|
||||
UNUSED(code);
|
||||
}
|
||||
|
||||
unsigned retro_get_region(void) {
|
||||
return RETRO_REGION_NTSC; // TODO: This isn't strictly true
|
||||
}
|
||||
|
||||
void retro_set_controller_port_device(unsigned port, unsigned device) {
|
||||
UNUSED(port);
|
||||
UNUSED(device);
|
||||
}
|
||||
|
||||
bool retro_load_game_special(unsigned game_type, const struct retro_game_info* info, size_t num_info) {
|
||||
UNUSED(game_type);
|
||||
UNUSED(info);
|
||||
UNUSED(num_info);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* retro_get_memory_data(unsigned id) {
|
||||
if (id != RETRO_MEMORY_SAVE_RAM) {
|
||||
return 0;
|
||||
}
|
||||
return savedata;
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id) {
|
||||
if (id != RETRO_MEMORY_SAVE_RAM) {
|
||||
return 0;
|
||||
}
|
||||
switch (gba.memory.savedata.type) {
|
||||
case SAVEDATA_AUTODETECT:
|
||||
case SAVEDATA_FLASH1M:
|
||||
return SIZE_CART_FLASH1M;
|
||||
case SAVEDATA_FLASH512:
|
||||
return SIZE_CART_FLASH512;
|
||||
case SAVEDATA_EEPROM:
|
||||
return SIZE_CART_EEPROM;
|
||||
case SAVEDATA_SRAM:
|
||||
return SIZE_CART_SRAM;
|
||||
case SAVEDATA_FORCE_NONE:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GBARetroLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) {
|
||||
UNUSED(thread);
|
||||
if (!logCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
char message[128];
|
||||
vsnprintf(message, sizeof(message), format, args);
|
||||
|
||||
enum retro_log_level retroLevel = RETRO_LOG_INFO;
|
||||
switch (level) {
|
||||
case GBA_LOG_ALL:
|
||||
case GBA_LOG_ERROR:
|
||||
case GBA_LOG_FATAL:
|
||||
retroLevel = RETRO_LOG_ERROR;
|
||||
break;
|
||||
case GBA_LOG_WARN:
|
||||
retroLevel = RETRO_LOG_WARN;
|
||||
break;
|
||||
case GBA_LOG_INFO:
|
||||
case GBA_LOG_GAME_ERROR:
|
||||
case GBA_LOG_SWI:
|
||||
retroLevel = RETRO_LOG_INFO;
|
||||
break;
|
||||
case GBA_LOG_DEBUG:
|
||||
case GBA_LOG_STUB:
|
||||
retroLevel = RETRO_LOG_DEBUG;
|
||||
break;
|
||||
}
|
||||
logCallback(retroLevel, "%s\n", message);
|
||||
}
|
||||
|
||||
static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio) {
|
||||
UNUSED(stream);
|
||||
int16_t samples[SAMPLES * 2];
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF
|
||||
blip_read_samples(audio->left, samples, SAMPLES, true);
|
||||
blip_read_samples(audio->right, samples + 1, SAMPLES, true);
|
||||
#else
|
||||
int16_t samplesR[SAMPLES];
|
||||
GBAAudioCopy(audio, &samples[SAMPLES], samplesR, SAMPLES);
|
||||
size_t i;
|
||||
for (i = 0; i < SAMPLES; ++i) {
|
||||
samples[i * 2] = samples[SAMPLES + i];
|
||||
samples[i * 2 + 1] = samplesR[i];
|
||||
}
|
||||
#endif
|
||||
audioCallback(samples, SAMPLES);
|
||||
}
|
||||
|
||||
static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) {
|
||||
UNUSED(stream);
|
||||
void* pixels;
|
||||
unsigned stride;
|
||||
renderer->getPixels(renderer, &stride, &pixels);
|
||||
videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -47,6 +47,7 @@ void AudioProcessorQt::start() {
|
|||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
|
||||
m_audioOutput = new QAudioOutput(format, this);
|
||||
m_audioOutput->setCategory("game");
|
||||
}
|
||||
|
||||
m_device->setInput(input());
|
||||
|
|
|
@ -110,7 +110,7 @@ if(WIN32)
|
|||
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
|
||||
endif()
|
||||
add_executable(${BINARY_NAME}-qt WIN32 MACOSX_BUNDLE main.cpp ${CMAKE_SOURCE_DIR}/res/mgba.icns ${SOURCE_FILES} ${PLATFORM_SRC} ${UI_FILES} ${AUDIO_SRC} ${RESOURCES})
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in)
|
||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
|
||||
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL)
|
||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||
|
|
|
@ -271,7 +271,15 @@ void Painter::performDraw() {
|
|||
}
|
||||
}
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, m_backing);
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, m_backing);
|
||||
#endif
|
||||
#else
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_backing);
|
||||
#endif
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
if (m_context->sync.videoFrameWait) {
|
||||
glFlush();
|
||||
|
|
|
@ -20,9 +20,10 @@ const qreal GBAKeyEditor::DPAD_CENTER_Y = 0.431;
|
|||
const qreal GBAKeyEditor::DPAD_WIDTH = 0.1;
|
||||
const qreal GBAKeyEditor::DPAD_HEIGHT = 0.1;
|
||||
|
||||
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* parent)
|
||||
GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, const char* profile, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_type(type)
|
||||
, m_profile(profile)
|
||||
, m_controller(controller)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
|
||||
|
@ -163,6 +164,10 @@ void GBAKeyEditor::save() {
|
|||
bindKey(m_keyL, GBA_KEY_L);
|
||||
bindKey(m_keyR, GBA_KEY_R);
|
||||
m_controller->saveConfiguration(m_type);
|
||||
|
||||
if (m_profile) {
|
||||
m_controller->saveProfile(m_type, m_profile);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) {
|
||||
|
|
|
@ -26,7 +26,7 @@ class GBAKeyEditor : public QWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GBAKeyEditor(InputController* controller, int type, QWidget* parent = nullptr);
|
||||
GBAKeyEditor(InputController* controller, int type, const char* profile = nullptr, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setAll();
|
||||
|
@ -76,6 +76,7 @@ private:
|
|||
QList<KeyEditor*>::iterator m_currentKey;
|
||||
|
||||
uint32_t m_type;
|
||||
const char* m_profile;
|
||||
InputController* m_controller;
|
||||
|
||||
QPicture m_background;
|
||||
|
|
|
@ -43,14 +43,16 @@ void GDBController::attach() {
|
|||
if (m_gameController->isLoaded()) {
|
||||
ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||
} else {
|
||||
connect(m_gameController, &GameController::gameStarted, [this] () {
|
||||
disconnect(m_gameController);
|
||||
QObject::disconnect(m_autoattach);
|
||||
m_autoattach = connect(m_gameController, &GameController::gameStarted, [this] () {
|
||||
QObject::disconnect(m_autoattach);
|
||||
ARMDebuggerEnter(&m_gdbStub.d, DEBUGGER_ENTER_ATTACHED, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GDBController::detach() {
|
||||
QObject::disconnect(m_autoattach);
|
||||
if (!isAttached()) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -45,6 +45,8 @@ private:
|
|||
|
||||
ushort m_port;
|
||||
Address m_bindAddress;
|
||||
|
||||
QMetaObject::Connection m_autoattach;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -236,12 +236,12 @@ void GameController::openGame() {
|
|||
m_threadContext.stateDir = m_threadContext.gameDir;
|
||||
} else {
|
||||
m_threadContext.rom = VFileOpen(m_threadContext.fname, O_RDONLY);
|
||||
#if ENABLE_LIBZIP
|
||||
#if USE_LIBZIP
|
||||
if (!m_threadContext.gameDir) {
|
||||
m_threadContext.gameDir = VDirOpenZip(m_threadContext.fname, 0);
|
||||
}
|
||||
#endif
|
||||
#if ENABLE_LZMA
|
||||
#if USE_LZMA
|
||||
if (!m_threadContext.gameDir) {
|
||||
m_threadContext.gameDir = VDirOpen7z(m_threadContext.fname, 0);
|
||||
}
|
||||
|
@ -509,6 +509,7 @@ void GameController::setLuminanceValue(uint8_t value) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
emit luminanceValueChanged(m_luxValue);
|
||||
}
|
||||
|
||||
void GameController::setLuminanceLevel(int level) {
|
||||
|
|
|
@ -86,6 +86,8 @@ signals:
|
|||
void gameFailed();
|
||||
void stateLoaded(GBAThread*);
|
||||
|
||||
void luminanceValueChanged(int);
|
||||
|
||||
void postLog(int level, const QString& log);
|
||||
|
||||
public slots:
|
||||
|
@ -116,6 +118,7 @@ public slots:
|
|||
void reloadAudioDriver();
|
||||
|
||||
void setLuminanceValue(uint8_t value);
|
||||
uint8_t luminanceValue() const { return m_luxValue; }
|
||||
void setLuminanceLevel(int level);
|
||||
void increaseLuminanceLevel() { setLuminanceLevel(m_luxLevel + 1); }
|
||||
void decreaseLuminanceLevel() { setLuminanceLevel(m_luxLevel - 1); }
|
||||
|
|
|
@ -63,6 +63,7 @@ void InputController::setConfiguration(ConfigController* config) {
|
|||
loadConfiguration(KEYBOARD);
|
||||
#ifdef BUILD_SDL
|
||||
loadConfiguration(SDL_BINDING_BUTTON);
|
||||
loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -70,11 +71,34 @@ void InputController::loadConfiguration(uint32_t type) {
|
|||
GBAInputMapLoad(&m_inputMap, type, m_config->configuration());
|
||||
}
|
||||
|
||||
void InputController::loadProfile(uint32_t type, const char* profile) {
|
||||
GBAInputProfileLoad(&m_inputMap, type, m_config->configuration(), profile);
|
||||
}
|
||||
|
||||
void InputController::saveConfiguration(uint32_t type) {
|
||||
GBAInputMapSave(&m_inputMap, type, m_config->configuration());
|
||||
m_config->write();
|
||||
}
|
||||
|
||||
void InputController::saveProfile(uint32_t type, const char* profile) {
|
||||
GBAInputProfileSave(&m_inputMap, type, m_config->configuration(), profile);
|
||||
m_config->write();
|
||||
}
|
||||
|
||||
const char* InputController::profileForType(uint32_t type) {
|
||||
UNUSED(type);
|
||||
#ifdef BUILD_SDL
|
||||
if (type == SDL_BINDING_BUTTON) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
return SDL_JoystickName(m_sdlEvents.joystick);
|
||||
#else
|
||||
return SDL_JoystickName(SDL_JoystickIndex(m_sdlEvents.joystick));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
GBAKey InputController::mapKeyboard(int key) const {
|
||||
return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,10 @@ public:
|
|||
|
||||
void setConfiguration(ConfigController* config);
|
||||
void loadConfiguration(uint32_t type);
|
||||
void loadProfile(uint32_t type, const char* profile);
|
||||
void saveConfiguration(uint32_t type = KEYBOARD);
|
||||
void saveProfile(uint32_t type, const char* profile);
|
||||
const char* profileForType(uint32_t type);
|
||||
|
||||
GBAKey mapKeyboard(int key) const;
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@ void OverrideView::gameStopped() {
|
|||
m_ui.hwTilt->setEnabled(!m_ui.hwAutodetect->isChecked());
|
||||
m_ui.hwRumble->setEnabled(!m_ui.hwAutodetect->isChecked());
|
||||
|
||||
m_ui.hwAutodetect->setChecked(true);
|
||||
m_ui.hwRTC->setChecked(false);
|
||||
m_ui.hwGyro->setChecked(false);
|
||||
m_ui.hwLight->setChecked(false);
|
||||
|
@ -150,7 +151,9 @@ void OverrideView::gameStopped() {
|
|||
m_ui.hwRumble->setChecked(false);
|
||||
|
||||
m_ui.idleLoop->setEnabled(true);
|
||||
m_ui.idleLoop->clear();
|
||||
|
||||
m_ui.clear->setEnabled(false);
|
||||
m_ui.save->setEnabled(false);
|
||||
|
||||
updateOverrides();
|
||||
}
|
||||
|
|
|
@ -35,16 +35,6 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clear">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="enabled">
|
||||
|
|
|
@ -31,12 +31,17 @@ SensorView::SensorView(GameController* controller, QWidget* parent)
|
|||
connect(m_ui.timeNow, &QPushButton::clicked, [controller, this] () {
|
||||
m_ui.time->setDateTime(QDateTime::currentDateTime());
|
||||
});
|
||||
|
||||
connect(m_controller, SIGNAL(luminanceValueChanged(int)), this, SLOT(luminanceValueChanged(int)));
|
||||
}
|
||||
|
||||
void SensorView::setLuminanceValue(int value) {
|
||||
bool oldState;
|
||||
value = std::max(0, std::min(value, 255));
|
||||
m_controller->setLuminanceValue(value);
|
||||
}
|
||||
|
||||
void SensorView::luminanceValueChanged(int value) {
|
||||
bool oldState;
|
||||
oldState = m_ui.lightSpin->blockSignals(true);
|
||||
m_ui.lightSpin->setValue(value);
|
||||
m_ui.lightSpin->blockSignals(oldState);
|
||||
|
@ -44,6 +49,4 @@ void SensorView::setLuminanceValue(int value) {
|
|||
oldState = m_ui.lightSlide->blockSignals(true);
|
||||
m_ui.lightSlide->setValue(value);
|
||||
m_ui.lightSlide->blockSignals(oldState);
|
||||
|
||||
m_controller->setLuminanceValue(value);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ public:
|
|||
|
||||
private slots:
|
||||
void setLuminanceValue(int);
|
||||
void luminanceValueChanged(int);
|
||||
|
||||
private:
|
||||
Ui::SensorView m_ui;
|
||||
|
|
|
@ -256,7 +256,8 @@ void Window::openCheatsWindow() {
|
|||
|
||||
#ifdef BUILD_SDL
|
||||
void Window::openGamepadWindow() {
|
||||
GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON);
|
||||
const char* profile = m_inputController.profileForType(SDL_BINDING_BUTTON);
|
||||
GBAKeyEditor* keyEditor = new GBAKeyEditor(&m_inputController, SDL_BINDING_BUTTON, profile);
|
||||
connect(this, SIGNAL(shutdown()), keyEditor, SLOT(close()));
|
||||
keyEditor->setAttribute(Qt::WA_DeleteOnClose);
|
||||
keyEditor->show();
|
||||
|
@ -714,22 +715,22 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addControlledAction(toolsMenu, gdbWindow, "gdbWindow");
|
||||
#endif
|
||||
|
||||
toolsMenu->addSeparator();
|
||||
QAction* solarIncrease = new QAction(tr("Increase solar level"), toolsMenu);
|
||||
QMenu* solarMenu = toolsMenu->addMenu(tr("Solar sensor"));
|
||||
QAction* solarIncrease = new QAction(tr("Increase solar level"), solarMenu);
|
||||
connect(solarIncrease, SIGNAL(triggered()), m_controller, SLOT(increaseLuminanceLevel()));
|
||||
addControlledAction(toolsMenu, solarIncrease, "increaseLuminanceLevel");
|
||||
addControlledAction(solarMenu, solarIncrease, "increaseLuminanceLevel");
|
||||
|
||||
QAction* solarDecrease = new QAction(tr("Decrease solar level"), toolsMenu);
|
||||
QAction* solarDecrease = new QAction(tr("Decrease solar level"), solarMenu);
|
||||
connect(solarDecrease, SIGNAL(triggered()), m_controller, SLOT(decreaseLuminanceLevel()));
|
||||
addControlledAction(toolsMenu, solarDecrease, "decreaseLuminanceLevel");
|
||||
addControlledAction(solarMenu, solarDecrease, "decreaseLuminanceLevel");
|
||||
|
||||
QAction* maxSolar = new QAction(tr("Brightest solar level"), toolsMenu);
|
||||
QAction* maxSolar = new QAction(tr("Brightest solar level"), solarMenu);
|
||||
connect(maxSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(10); });
|
||||
addControlledAction(toolsMenu, maxSolar, "maxLuminanceLevel");
|
||||
addControlledAction(solarMenu, maxSolar, "maxLuminanceLevel");
|
||||
|
||||
QAction* minSolar = new QAction(tr("Darkest solar level"), toolsMenu);
|
||||
QAction* minSolar = new QAction(tr("Darkest solar level"), solarMenu);
|
||||
connect(minSolar, &QAction::triggered, [this]() { m_controller->setLuminanceLevel(0); });
|
||||
addControlledAction(toolsMenu, minSolar, "minLuminanceLevel");
|
||||
addControlledAction(solarMenu, minSolar, "minLuminanceLevel");
|
||||
|
||||
toolsMenu->addSeparator();
|
||||
addControlledAction(toolsMenu, toolsMenu->addAction(tr("Settings..."), this, SLOT(openSettingsWindow())), "settings");
|
||||
|
|
|
@ -38,6 +38,7 @@ if(BUILD_RASPI)
|
|||
set(EGL_LIBRARY "-lEGL -lGLESv2 -lbcm_host")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline")
|
||||
add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC} ${EGL_MAIN_SRC})
|
||||
set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${EGL_LIBRARY})
|
||||
install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi)
|
||||
endif()
|
||||
|
@ -52,6 +53,7 @@ else()
|
|||
endif()
|
||||
|
||||
add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC})
|
||||
set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}")
|
||||
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 COMPONENT ${BINARY_NAME}-sdl)
|
||||
|
|
|
@ -59,7 +59,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
|||
#endif
|
||||
#endif
|
||||
|
||||
renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||
renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS;
|
||||
glGenTextures(1, &renderer->tex);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->tex);
|
||||
|
|
|
@ -78,7 +78,14 @@ void GBASDLInitBindings(struct GBAInputMap* inputMap) {
|
|||
|
||||
void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) {
|
||||
GBAInputMapLoad(context->bindings, SDL_BINDING_KEY, config);
|
||||
GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config);
|
||||
if (context->joystick) {
|
||||
GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config);
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, SDL_JoystickName(context->joystick));
|
||||
#else
|
||||
GBAInputProfileLoad(context->bindings, SDL_BINDING_BUTTON, config, SDL_JoystickName(SDL_JoystickIndex(context->joystick)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void GBASDLDeinitEvents(struct GBASDLEvents* context) {
|
||||
|
@ -140,14 +147,6 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
GBARewind(context, 10);
|
||||
GBAThreadContinue(context);
|
||||
return;
|
||||
case SDLK_ESCAPE:
|
||||
GBAThreadInterrupt(context);
|
||||
if (context->gba->rr) {
|
||||
GBARRStopPlaying(context->gba->rr);
|
||||
GBARRStopRecording(context->gba->rr);
|
||||
}
|
||||
GBAThreadContinue(context);
|
||||
return;
|
||||
default:
|
||||
if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
|
||||
switch (event->keysym.sym) {
|
||||
|
@ -169,31 +168,6 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
|||
case SDLK_r:
|
||||
GBAThreadReset(context);
|
||||
break;
|
||||
case SDLK_t:
|
||||
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:
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# Copyright (c) 2013-2014 Jeffrey Pfau
|
||||
# Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
#ifdef __ARM_NEON
|
||||
# r0: Destination
|
||||
# r1: Source
|
||||
# r2: Number of words to copy as halfwords
|
||||
|
@ -13,28 +14,22 @@ mov r8, r0
|
|||
mov r9, r1
|
||||
mov r10, r2
|
||||
.L0:
|
||||
tst r10, #7
|
||||
tst r10, #15
|
||||
beq .L1
|
||||
ldr r0, [r9], #4
|
||||
strh r0, [r8], #2
|
||||
sub r10, #1
|
||||
b .L0
|
||||
.L1:
|
||||
ldmia r9!, {r0-r7}
|
||||
strh r0, [r8], #2
|
||||
strh r1, [r8], #2
|
||||
strh r2, [r8], #2
|
||||
strh r3, [r8], #2
|
||||
strh r4, [r8], #2
|
||||
strh r5, [r8], #2
|
||||
strh r6, [r8], #2
|
||||
strh r7, [r8], #2
|
||||
subs r10, #8
|
||||
vld4.16 {d0, d1, d2, d3}, [r9]!
|
||||
vld4.16 {d4, d5, d6, d7}, [r9]!
|
||||
vst2.16 {d0, d2}, [r8]!
|
||||
vst2.16 {d4, d6}, [r8]!
|
||||
subs r10, #16
|
||||
bne .L1
|
||||
pop {r4-r10}
|
||||
bx lr
|
||||
|
||||
#ifdef __ARM_NEON
|
||||
# r0: Destination
|
||||
# r1: Source
|
||||
# r2: Width
|
||||
|
@ -97,3 +92,5 @@ bne .n40
|
|||
pop {r4-r7}
|
||||
bx lr
|
||||
#endif
|
||||
|
||||
.section .note.GNU-stack,"",%progbits
|
||||
|
|
|
@ -253,7 +253,7 @@ static inline int SocketPoll(size_t nSockets, Socket* reads, Socket* writes, Soc
|
|||
struct timeval tv;
|
||||
tv.tv_sec = timeoutMillis / 1000;
|
||||
tv.tv_usec = (timeoutMillis % 1000) * 1000;
|
||||
int result = select(maxFd, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv);
|
||||
int result = select(maxFd + 1, &rset, &wset, &eset, timeoutMillis < 0 ? 0 : &tv);
|
||||
int r = 0;
|
||||
int w = 0;
|
||||
int e = 0;
|
||||
|
|
|
@ -378,3 +378,15 @@ const char* _vdeName(struct VDirEntry* vde) {
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) {
|
||||
size_t bytesRead = 0;
|
||||
while (bytesRead < size - 1) {
|
||||
size_t newRead = vf->read(vf, &buffer[bytesRead], 1);
|
||||
bytesRead += newRead;
|
||||
if (!newRead || buffer[bytesRead] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buffer[bytesRead] = '\0';
|
||||
}
|
||||
|
|
|
@ -38,18 +38,21 @@ struct VDir {
|
|||
|
||||
struct VFile* VFileOpen(const char* path, int flags);
|
||||
struct VFile* VFileFromFD(int fd);
|
||||
struct VFile* VFileFromMemory(void* mem, size_t size);
|
||||
|
||||
struct VDir* VDirOpen(const char* path);
|
||||
|
||||
#ifdef ENABLE_LIBZIP
|
||||
#ifdef USE_LIBZIP
|
||||
struct VDir* VDirOpenZip(const char* path, int flags);
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LZMA
|
||||
#ifdef USE_LZMA
|
||||
struct VDir* VDirOpen7z(const char* path, int flags);
|
||||
#endif
|
||||
|
||||
struct VFile* VDirOptionalOpenFile(struct VDir* dir, const char* realPath, const char* prefix, const char* suffix, int mode);
|
||||
struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode);
|
||||
|
||||
ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/vfs.h"
|
||||
|
||||
#ifdef ENABLE_LZMA
|
||||
#ifdef USE_LZMA
|
||||
|
||||
#include "util/string.h"
|
||||
|
||||
|
@ -51,7 +51,6 @@ struct VFile7z {
|
|||
static bool _vf7zClose(struct VFile* vf);
|
||||
static off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence);
|
||||
static ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size);
|
||||
static ssize_t _vf7zReadline(struct VFile* vf, char* buffer, size_t size);
|
||||
static ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size);
|
||||
static void* _vf7zMap(struct VFile* vf, size_t size, int flags);
|
||||
static void _vf7zUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
|
@ -142,16 +141,12 @@ off_t _vf7zSeek(struct VFile* vf, off_t offset, int whence) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
if (position <= vf7z->offset) {
|
||||
vf7z->offset = position;
|
||||
return position;
|
||||
if (position > vf7z->size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (position <= vf7z->size) {
|
||||
return vf7z->offset;
|
||||
}
|
||||
|
||||
return -1;
|
||||
vf7z->offset = position;
|
||||
return position;
|
||||
}
|
||||
|
||||
ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size) {
|
||||
|
@ -162,21 +157,10 @@ ssize_t _vf7zRead(struct VFile* vf, void* buffer, size_t size) {
|
|||
}
|
||||
|
||||
memcpy(buffer, vf7z->outBuffer + vf7z->offset + vf7z->bufferOffset, size);
|
||||
vf7z->offset += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
ssize_t _vf7zReadline(struct VFile* vf, char* buffer, size_t size) {
|
||||
size_t bytesRead = 0;
|
||||
while (bytesRead < size - 1) {
|
||||
size_t newRead = vf->read(vf, &buffer[bytesRead], 1);
|
||||
bytesRead += newRead;
|
||||
if (!newRead || buffer[bytesRead] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buffer[bytesRead] = '\0';
|
||||
}
|
||||
|
||||
ssize_t _vf7zWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||
// TODO
|
||||
UNUSED(vf);
|
||||
|
@ -301,7 +285,7 @@ struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode) {
|
|||
vf->d.close = _vf7zClose;
|
||||
vf->d.seek = _vf7zSeek;
|
||||
vf->d.read = _vf7zRead;
|
||||
vf->d.readline = _vf7zReadline;
|
||||
vf->d.readline = VFileReadline;
|
||||
vf->d.write = _vf7zWrite;
|
||||
vf->d.map = _vf7zMap;
|
||||
vf->d.unmap = _vf7zUnmap;
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/vfs.h"
|
||||
|
||||
struct VFileMem {
|
||||
struct VFile d;
|
||||
void* mem;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
};
|
||||
|
||||
static bool _vfmClose(struct VFile* vf);
|
||||
static off_t _vfmSeek(struct VFile* vf, off_t offset, int whence);
|
||||
static ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size);
|
||||
static ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size);
|
||||
static void* _vfmMap(struct VFile* vf, size_t size, int flags);
|
||||
static void _vfmUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void _vfmTruncate(struct VFile* vf, size_t size);
|
||||
static ssize_t _vfmSize(struct VFile* vf);
|
||||
|
||||
struct VFile* VFileFromMemory(void* mem, size_t size) {
|
||||
if (!mem || !size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct VFileMem* vfm = malloc(sizeof(struct VFileMem));
|
||||
if (!vfm) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
vfm->mem = mem;
|
||||
vfm->size = size;
|
||||
vfm->offset = 0;
|
||||
vfm->d.close = _vfmClose;
|
||||
vfm->d.seek = _vfmSeek;
|
||||
vfm->d.read = _vfmRead;
|
||||
vfm->d.readline = VFileReadline;
|
||||
vfm->d.write = _vfmWrite;
|
||||
vfm->d.map = _vfmMap;
|
||||
vfm->d.unmap = _vfmUnmap;
|
||||
vfm->d.truncate = _vfmTruncate;
|
||||
vfm->d.size = _vfmSize;
|
||||
|
||||
return &vfm->d;
|
||||
}
|
||||
|
||||
bool _vfmClose(struct VFile* vf) {
|
||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||
vfm->mem = 0;
|
||||
free(vfm);
|
||||
return true;
|
||||
}
|
||||
|
||||
off_t _vfmSeek(struct VFile* vf, off_t offset, int whence) {
|
||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||
|
||||
size_t position;
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
position = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
if (offset < 0 && ((vfm->offset < (size_t) -offset) || (offset == INT_MIN))) {
|
||||
return -1;
|
||||
}
|
||||
position = vfm->offset + offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
if (offset < 0 && ((vfm->size < (size_t) -offset) || (offset == INT_MIN))) {
|
||||
return -1;
|
||||
}
|
||||
position = vfm->size + offset;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (position > vfm->size) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
vfm->offset = position;
|
||||
return position;
|
||||
}
|
||||
|
||||
ssize_t _vfmRead(struct VFile* vf, void* buffer, size_t size) {
|
||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||
|
||||
if (size + vfm->offset >= vfm->size) {
|
||||
size = vfm->size - vfm->offset;
|
||||
}
|
||||
|
||||
memcpy(buffer, vfm->mem + vfm->offset, size);
|
||||
vfm->offset += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
ssize_t _vfmWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||
|
||||
if (size + vfm->offset >= vfm->size) {
|
||||
size = vfm->size - vfm->offset;
|
||||
}
|
||||
|
||||
memcpy(vfm->mem + vfm->offset, buffer, size);
|
||||
vfm->offset += size;
|
||||
return size;
|
||||
}
|
||||
|
||||
void* _vfmMap(struct VFile* vf, size_t size, int flags) {
|
||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||
|
||||
UNUSED(flags);
|
||||
if (size > vfm->size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return vfm->mem;
|
||||
}
|
||||
|
||||
void _vfmUnmap(struct VFile* vf, void* memory, size_t size) {
|
||||
UNUSED(vf);
|
||||
UNUSED(memory);
|
||||
UNUSED(size);
|
||||
}
|
||||
|
||||
void _vfmTruncate(struct VFile* vf, size_t size) {
|
||||
// TODO: Return value?
|
||||
UNUSED(vf);
|
||||
UNUSED(size);
|
||||
}
|
||||
|
||||
ssize_t _vfmSize(struct VFile* vf) {
|
||||
struct VFileMem* vfm = (struct VFileMem*) vf;
|
||||
return vfm->size;
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "util/vfs.h"
|
||||
|
||||
#ifdef ENABLE_LIBZIP
|
||||
#ifdef USE_LIBZIP
|
||||
#include <zip.h>
|
||||
|
||||
|
||||
|
@ -38,7 +38,6 @@ struct VFileZip {
|
|||
static bool _vfzClose(struct VFile* vf);
|
||||
static off_t _vfzSeek(struct VFile* vf, off_t offset, int whence);
|
||||
static ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size);
|
||||
static ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size);
|
||||
static ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size);
|
||||
static void* _vfzMap(struct VFile* vf, size_t size, int flags);
|
||||
static void _vfzUnmap(struct VFile* vf, void* memory, size_t size);
|
||||
|
@ -188,18 +187,6 @@ ssize_t _vfzRead(struct VFile* vf, void* buffer, size_t size) {
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
ssize_t _vfzReadline(struct VFile* vf, char* buffer, size_t size) {
|
||||
size_t bytesRead = 0;
|
||||
while (bytesRead < size - 1) {
|
||||
size_t newRead = vf->read(vf, &buffer[bytesRead], 1);
|
||||
bytesRead += newRead;
|
||||
if (!newRead || buffer[bytesRead] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return buffer[bytesRead] = '\0';
|
||||
}
|
||||
|
||||
ssize_t _vfzWrite(struct VFile* vf, const void* buffer, size_t size) {
|
||||
// TODO
|
||||
UNUSED(vf);
|
||||
|
@ -296,7 +283,7 @@ struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode) {
|
|||
vfz->d.close = _vfzClose;
|
||||
vfz->d.seek = _vfzSeek;
|
||||
vfz->d.read = _vfzRead;
|
||||
vfz->d.readline = _vfzReadline;
|
||||
vfz->d.readline = VFileReadline;
|
||||
vfz->d.write = _vfzWrite;
|
||||
vfz->d.map = _vfzMap;
|
||||
vfz->d.unmap = _vfzUnmap;
|
||||
|
|
Loading…
Reference in New Issue