mirror of
https://github.com/libretro/mgba.git
synced 2024-11-26 17:50:26 +00:00
Merge branch 'master' into feature/sio-dolphin
Conflicts: CMakeLists.txt
This commit is contained in:
commit
8176a3aad2
11
CHANGES
11
CHANGES
@ -26,6 +26,13 @@ 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
|
||||
- Ability to lock aspect ratio
|
||||
- Local link cable support
|
||||
- Ability to switch which game controller is in use per instance
|
||||
- Ability to prevent opposing directional input
|
||||
- Warning dialog if an unimplemented BIOS feature is called
|
||||
Bugfixes:
|
||||
- ARM7: Extend prefetch by one stage
|
||||
- GBA Audio: Support 16-bit writes to FIFO audio
|
||||
@ -47,6 +54,8 @@ Bugfixes:
|
||||
- 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
|
||||
@ -69,6 +78,8 @@ 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
|
||||
- GBA: Savestates now take into account savedata state machines (fixes #109)
|
||||
|
||||
0.1.1: (2015-01-24)
|
||||
Bugfixes:
|
||||
|
@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
project(mGBA C)
|
||||
set(BINARY_NAME mgba CACHE INTERNAL "Name of output binaries")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=gnu99")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -std=c99")
|
||||
set(USE_CLI_DEBUGGER ON CACHE BOOL "Whether or not to enable the CLI-mode ARM debugger")
|
||||
set(USE_GDB_STUB ON CACHE BOOL "Whether or not to enable the GDB stub ARM debugger")
|
||||
set(USE_FFMPEG ON CACHE BOOL "Whether or not to enable FFmpeg support")
|
||||
@ -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,8 +98,12 @@ 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)
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL OpenBSD)
|
||||
list(APPEND LIBEDIT_LIBRARIES -ltermcap)
|
||||
endif()
|
||||
else()
|
||||
find_feature(USE_CLI_DEBUGGER "libedit")
|
||||
endif()
|
||||
@ -107,35 +114,49 @@ find_feature(USE_MAGICK "MagickWand")
|
||||
|
||||
# Platform support
|
||||
if(WIN32)
|
||||
set(WIN32_VERSION "${LIB_VERSION_MAJOR},${LIB_VERSION_MINOR},${LIB_VERSION_PATCH}")
|
||||
add_definitions(-D_WIN32_WINNT=0x0600)
|
||||
list(APPEND OS_LIB ws2_32)
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/windows/*.c)
|
||||
source_group("Windows-specific code" FILES ${OS_SRC})
|
||||
else()
|
||||
add_definitions(-DUSE_PTHREADS)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_definitions(-D_BSD_SOURCE -D_POSIX_SOURCE -D_POSIX_C_SOURCE=200809L)
|
||||
endif()
|
||||
if(NOT APPLE)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
|
||||
endif()
|
||||
|
||||
file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c)
|
||||
source_group("POSIX-specific code" FILES ${OS_SRC})
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_definitions(-D_DARWIN_C_SOURCE)
|
||||
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(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
|
||||
if(NOT BUILD_EGL)
|
||||
add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_PANDORA)
|
||||
add_definitions(-DBUILD_PANDORA)
|
||||
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 +187,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 +204,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 +229,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 +251,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 +261,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 +269,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 +286,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 +309,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 +321,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 +330,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 +394,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}")
|
||||
|
@ -1 +0,0 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "mgba.ico"
|
28
res/mgba.rc.in
Normal file
28
res/mgba.rc.in
Normal file
@ -0,0 +1,28 @@
|
||||
IDI_ICON1 ICON DISCARDABLE "${CMAKE_SOURCE_DIR}/res/mgba.ico"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION ${WIN32_VERSION},0
|
||||
PRODUCTVERSION ${WIN32_VERSION},0
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "endrift"
|
||||
VALUE "FileDescription", "mGBA Game Boy Advance emulator"
|
||||
VALUE "FileVersion", "${LIB_VERSION_STRING}.0"
|
||||
VALUE "InternalName", "${BINARY_NAME}"
|
||||
VALUE "LegalCopyright", "(c) 2013 - 2015 Jeffrey Pfau"
|
||||
VALUE "OriginalFilename", "${BINARY_NAME}"
|
||||
VALUE "ProductName", "${PROJECT_NAME}"
|
||||
VALUE "ProductVersion", "${BINARY_NAME}"
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
@ -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) {
|
||||
|
@ -10,6 +10,10 @@
|
||||
#include "gba/memory.h"
|
||||
#include "isa-inlines.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.141592654f
|
||||
#endif
|
||||
|
||||
const uint32_t GBA_BIOS_CHECKSUM = 0xBAAE187F;
|
||||
const uint32_t GBA_DS_BIOS_CHECKSUM = 0xBAAE1880;
|
||||
|
||||
@ -88,8 +92,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 +237,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 +252,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 +268,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 +285,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;
|
||||
|
@ -7,8 +7,6 @@
|
||||
|
||||
#include "gba/serialize.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static void _readPins(struct GBACartridgeHardware* hw);
|
||||
static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);
|
||||
|
||||
@ -81,8 +79,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 +137,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 +158,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 +167,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 +178,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 +201,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 +219,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 +260,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 +290,7 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hw->p0) {
|
||||
if (hw->pinState & 1) {
|
||||
if (gyro->sample) {
|
||||
gyro->sample(gyro);
|
||||
}
|
||||
@ -302,14 +300,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 +322,7 @@ void _rumbleReadPins(struct GBACartridgeHardware* hw) {
|
||||
return;
|
||||
}
|
||||
|
||||
rumble->setRumble(rumble, hw->p3);
|
||||
rumble->setRumble(rumble, !!(hw->pinState & 8));
|
||||
}
|
||||
|
||||
// == Light sensor
|
||||
@ -337,11 +335,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 +350,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 +422,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 +438,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,10 @@
|
||||
|
||||
#include "util/common.h"
|
||||
|
||||
#include "macros.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#define IS_GPIO_REGISTER(reg) ((reg) == GPIO_REG_DATA || (reg) == GPIO_REG_DIRECTION || (reg) == GPIO_REG_CONTROL)
|
||||
|
||||
struct GBARotationSource {
|
||||
@ -52,16 +56,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, uint32_t);
|
||||
DECL_BIT(RTCControl, MinIRQ, 3);
|
||||
DECL_BIT(RTCControl, Hour24, 6);
|
||||
DECL_BIT(RTCControl, Poweroff, 7);
|
||||
|
||||
enum RTCCommand {
|
||||
RTC_RESET = 0,
|
||||
@ -71,14 +69,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, uint32_t);
|
||||
DECL_BITS(RTCCommandData, Magic, 0, 4);
|
||||
DECL_BITS(RTCCommandData, Command, 4, 3);
|
||||
DECL_BIT(RTCCommandData, Reading, 7);
|
||||
|
||||
struct GBARTC {
|
||||
int bytesRemaining;
|
||||
@ -86,8 +80,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 +89,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 +128,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
|
||||
|
184
src/gba/input.c
184
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,45 @@ 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);
|
||||
}
|
||||
|
||||
const char* GBAInputGetPreferredDevice(const struct Configuration* config, uint32_t type, int playerId) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
|
||||
char deviceId[KEY_NAME_MAX];
|
||||
snprintf(deviceId, sizeof(deviceId), "device%i", playerId);
|
||||
return ConfigurationGetValue(config, sectionName, deviceId);
|
||||
}
|
||||
|
||||
void GBAInputSetPreferredDevice(struct Configuration* config, uint32_t type, int playerId, const char* deviceName) {
|
||||
char sectionName[SECTION_NAME_MAX];
|
||||
_makeSectionName(sectionName, SECTION_NAME_MAX, type);
|
||||
|
||||
char deviceId[KEY_NAME_MAX];
|
||||
snprintf(deviceId, sizeof(deviceId), "device%i", playerId);
|
||||
return ConfigurationSetValue(config, sectionName, deviceId, deviceName);
|
||||
}
|
||||
|
@ -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,10 @@ 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);
|
||||
|
||||
const char* GBAInputGetPreferredDevice(const struct Configuration*, uint32_t type, int playerId);
|
||||
void GBAInputSetPreferredDevice(struct Configuration*, uint32_t type, int playerId, const char* deviceName);
|
||||
|
||||
#endif
|
||||
|
10
src/gba/io.c
10
src/gba/io.c
@ -589,12 +589,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;
|
||||
}
|
||||
@ -689,7 +689,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]) {
|
||||
|
@ -163,6 +163,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) {
|
||||
@ -1510,6 +1526,12 @@ static void _drawBackgroundMode3(struct GBAVideoSoftwareRenderer* renderer, stru
|
||||
color32 |= (color << 6) & 0xF800;
|
||||
color32 |= (color << 9) & 0xF80000;
|
||||
color = color32;
|
||||
#elif COLOR_5_6_5
|
||||
uint16_t color16 = 0;
|
||||
color16 |= (color & 0x001F) << 11;
|
||||
color16 |= (color & 0x03E0) << 1;
|
||||
color16 |= (color & 0x7C00) >> 10;
|
||||
color = color16;
|
||||
#endif
|
||||
mosaicWait = mosaicH;
|
||||
} else {
|
||||
@ -1593,6 +1615,12 @@ static void _drawBackgroundMode5(struct GBAVideoSoftwareRenderer* renderer, stru
|
||||
color32 |= (color << 3) & 0xF8;
|
||||
color32 |= (color << 6) & 0xF800;
|
||||
color = color32;
|
||||
#elif COLOR_5_6_5
|
||||
uint16_t color16 = 0;
|
||||
color16 |= (color & 0x001F) << 11;
|
||||
color16 |= (color & 0x03E0) << 1;
|
||||
color16 |= (color & 0x7C00) >> 10;
|
||||
color = color16;
|
||||
#endif
|
||||
mosaicWait = mosaicH;
|
||||
} else {
|
||||
|
578
src/gba/rr/mgm.c
Normal file
578
src/gba/rr/mgm.c
Normal file
@ -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);
|
||||
}
|
82
src/gba/rr/mgm.h
Normal file
82
src/gba/rr/mgm.h
Normal file
@ -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
|
189
src/gba/rr/vbm.c
Normal file
189
src/gba/rr/vbm.c
Normal file
@ -0,0 +1,189 @@
|
||||
/* 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) {
|
||||
if (!rr->isPlaying(rr)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct GBAVBMContext* vbm = (struct GBAVBMContext*) rr;
|
||||
vbm->vbmFile->seek(vbm->vbmFile, sizeof(uint16_t), SEEK_CUR);
|
||||
}
|
||||
|
||||
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));
|
||||
vbm->vbmFile->seek(vbm->vbmFile, -sizeof(input), SEEK_CUR);
|
||||
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;
|
||||
}
|
21
src/gba/rr/vbm.h
Normal file
21
src/gba/rr/vbm.h
Normal file
@ -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*);
|
@ -6,6 +6,7 @@
|
||||
#include "savedata.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/serialize.h"
|
||||
|
||||
#include "util/memory.h"
|
||||
#include "util/vfs.h"
|
||||
@ -323,10 +324,8 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
||||
savedata->command <<= 1;
|
||||
savedata->command |= value & 0x1;
|
||||
if (savedata->command == EEPROM_COMMAND_WRITE) {
|
||||
savedata->addressBits = writeSize - 64 - 2;
|
||||
savedata->writeAddress = 0;
|
||||
} else {
|
||||
savedata->addressBits = writeSize - 2;
|
||||
savedata->readAddress = 0;
|
||||
}
|
||||
break;
|
||||
@ -338,7 +337,6 @@ void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32
|
||||
savedata->writeAddress |= (value & 0x1) << 6;
|
||||
} else if (writeSize == 1) {
|
||||
savedata->command = EEPROM_COMMAND_NULL;
|
||||
savedata->writePending = 1;
|
||||
} else {
|
||||
uint8_t current = savedata->data[savedata->writeAddress >> 3];
|
||||
current &= ~(1 << (0x7 - (savedata->writeAddress & 0x7)));
|
||||
@ -378,6 +376,37 @@ uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData) {
|
||||
state->savedata.type = savedata->type;
|
||||
state->savedata.command = savedata->command;
|
||||
state->savedata.flashState = savedata->flashState;
|
||||
state->savedata.flashBank = savedata->currentBank == &savedata->data[0x10000];
|
||||
state->savedata.readBitsRemaining = savedata->readBitsRemaining;
|
||||
state->savedata.readAddress = savedata->readAddress;
|
||||
state->savedata.writeAddress = savedata->writeAddress;
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
}
|
||||
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData) {
|
||||
if (state->savedata.type == SAVEDATA_FORCE_NONE) {
|
||||
return;
|
||||
}
|
||||
if (savedata->type != state->savedata.type) {
|
||||
GBASavedataForceType(savedata, state->savedata.type);
|
||||
}
|
||||
savedata->command = state->savedata.command;
|
||||
savedata->flashState = state->savedata.flashState;
|
||||
savedata->readBitsRemaining = state->savedata.readBitsRemaining;
|
||||
savedata->readAddress = state->savedata.readAddress;
|
||||
savedata->writeAddress = state->savedata.writeAddress;
|
||||
if (savedata->type == SAVEDATA_FLASH1M) {
|
||||
_flashSwitchBank(savedata, state->savedata.flashBank);
|
||||
}
|
||||
|
||||
UNUSED(includeData); // TODO
|
||||
}
|
||||
|
||||
void _flashSwitchBank(struct GBASavedata* savedata, int bank) {
|
||||
GBALog(0, GBA_LOG_DEBUG, "Performing flash bank switch to bank %i", bank);
|
||||
savedata->currentBank = &savedata->data[bank << 16];
|
||||
|
@ -13,10 +13,10 @@ struct VFile;
|
||||
enum SavedataType {
|
||||
SAVEDATA_AUTODETECT = -1,
|
||||
SAVEDATA_FORCE_NONE = 0,
|
||||
SAVEDATA_SRAM,
|
||||
SAVEDATA_FLASH512,
|
||||
SAVEDATA_FLASH1M,
|
||||
SAVEDATA_EEPROM
|
||||
SAVEDATA_SRAM = 1,
|
||||
SAVEDATA_FLASH512 = 2,
|
||||
SAVEDATA_FLASH1M = 3,
|
||||
SAVEDATA_EEPROM = 4
|
||||
};
|
||||
|
||||
enum SavedataCommand {
|
||||
@ -42,8 +42,8 @@ enum SavedataCommand {
|
||||
|
||||
enum FlashStateMachine {
|
||||
FLASH_STATE_RAW = 0,
|
||||
FLASH_STATE_START,
|
||||
FLASH_STATE_CONTINUE
|
||||
FLASH_STATE_START = 1,
|
||||
FLASH_STATE_CONTINUE = 2,
|
||||
};
|
||||
|
||||
enum FlashManufacturer {
|
||||
@ -67,11 +67,9 @@ struct GBASavedata {
|
||||
int mapMode;
|
||||
struct VFile* realVf;
|
||||
|
||||
int readBitsRemaining;
|
||||
int readAddress;
|
||||
int writeAddress;
|
||||
int writePending;
|
||||
int addressBits;
|
||||
int32_t readBitsRemaining;
|
||||
uint32_t readAddress;
|
||||
uint32_t writeAddress;
|
||||
|
||||
uint8_t* currentBank;
|
||||
|
||||
@ -96,4 +94,8 @@ void GBASavedataWriteFlash(struct GBASavedata* savedata, uint16_t address, uint8
|
||||
uint16_t GBASavedataReadEEPROM(struct GBASavedata* savedata);
|
||||
void GBASavedataWriteEEPROM(struct GBASavedata* savedata, uint16_t value, uint32_t writeSize);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state, bool includeData);
|
||||
void GBASavedataDeserialize(struct GBASavedata* savedata, const struct GBASerializedState* state, bool includeData);
|
||||
|
||||
#endif
|
||||
|
@ -48,16 +48,15 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
GBAIOSerialize(gba, state);
|
||||
GBAVideoSerialize(&gba->video, state);
|
||||
GBAAudioSerialize(&gba->audio, state);
|
||||
GBASavedataSerialize(&gba->memory.savedata, state, false);
|
||||
|
||||
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;
|
||||
@ -113,18 +112,10 @@ void GBADeserialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
GBAIODeserialize(gba, state);
|
||||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAAudioDeserialize(&gba->audio, state);
|
||||
GBASavedataDeserialize(&gba->memory.savedata, state, false);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||
* 0x00290 - 0x002C3: GPIO state
|
||||
* | 0x00290 - 0x00291: Pin state
|
||||
* | 0x00292 - 0x00293: Direction state
|
||||
* | 0x00294 - 0x002B6: RTC state (see gba-hardware.h for format)
|
||||
* | 0x00294 - 0x002B6: RTC state (see hardware.h for format)
|
||||
* | 0x002B7 - 0x002B7: GPIO devices
|
||||
* | bit 0: Has RTC values
|
||||
* | bit 1: Has rumble value (reserved)
|
||||
@ -150,7 +150,20 @@ extern const uint32_t GBA_SAVESTATE_MAGIC;
|
||||
* | 0x002C1 - 0x002C3: Flags
|
||||
* | bits 0 - 1: Tilt state machine
|
||||
* | bits 2 - 31: Reserved
|
||||
* 0x002C4 - 0x002F3: Reserved (leave zero)
|
||||
* 0x002C4 - 0x002DF: Reserved (leave zero)
|
||||
* 0x002E0 - 0x002EF: Savedata state
|
||||
* | 0x002E0 - 0x002E0: Savedata type
|
||||
* | 0x002E1 - 0x002E1: Savedata command (see savedata.h)
|
||||
* | 0x002E2 - 0x002E2: Flags
|
||||
* | bits 0 - 1: Flash state machine
|
||||
* | bits 2 - 3: Reserved
|
||||
* | bit 4: Flash bank
|
||||
* | bits 5 - 7: Reserved
|
||||
* | 0x002E3 - 0x002E3: Reserved
|
||||
* | 0x002E4 - 0x002E7: EEPROM read bits remaining
|
||||
* | 0x002E8 - 0x002EB: EEPROM read address
|
||||
* | 0x002EC - 0x002EBF EEPROM write address
|
||||
* 0x002F0 - 0x002F3: Reserved (leave zero)
|
||||
* 0x002F4 - 0x002FF: Prefetch
|
||||
* | 0x002F4 - 0x002F7: GBA BIOS bus prefetch
|
||||
* | 0x002F8 - 0x002FB: CPU prefecth (decode slot)
|
||||
@ -271,7 +284,22 @@ struct GBASerializedState {
|
||||
unsigned : 22;
|
||||
} hw;
|
||||
|
||||
uint32_t reservedHardware[12];
|
||||
uint32_t reservedHardware[7];
|
||||
|
||||
struct {
|
||||
unsigned type : 8;
|
||||
unsigned command : 8;
|
||||
unsigned flashState : 2;
|
||||
unsigned : 2;
|
||||
unsigned flashBank : 1;
|
||||
unsigned : 3;
|
||||
unsigned : 8;
|
||||
int32_t readBitsRemaining;
|
||||
uint32_t readAddress;
|
||||
uint32_t writeAddress;
|
||||
} savedata;
|
||||
|
||||
uint32_t reservedPadding;
|
||||
|
||||
uint32_t biosPrefetch;
|
||||
uint32_t cpuPrefetch[2];
|
||||
@ -292,7 +320,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);
|
||||
|
193
src/gba/sio/lockstep.c
Normal file
193
src/gba/sio/lockstep.c
Normal file
@ -0,0 +1,193 @@
|
||||
/* 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 "lockstep.h"
|
||||
|
||||
#include "gba/gba.h"
|
||||
#include "gba/io.h"
|
||||
|
||||
#define LOCKSTEP_INCREMENT 2048
|
||||
|
||||
static bool GBASIOLockstepNodeInit(struct GBASIODriver* driver);
|
||||
static void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver);
|
||||
static bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver);
|
||||
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value);
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles);
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep* lockstep) {
|
||||
lockstep->players[0] = 0;
|
||||
lockstep->players[1] = 0;
|
||||
lockstep->players[2] = 0;
|
||||
lockstep->players[3] = 0;
|
||||
lockstep->multiRecv[0] = 0xFFFF;
|
||||
lockstep->multiRecv[1] = 0xFFFF;
|
||||
lockstep->multiRecv[2] = 0xFFFF;
|
||||
lockstep->multiRecv[3] = 0xFFFF;
|
||||
lockstep->attached = 0;
|
||||
lockstep->loaded = 0;
|
||||
lockstep->transferActive = false;
|
||||
lockstep->waiting = 0;
|
||||
lockstep->nextEvent = LOCKSTEP_INCREMENT;
|
||||
ConditionInit(&lockstep->barrier);
|
||||
MutexInit(&lockstep->mutex);
|
||||
}
|
||||
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep* lockstep) {
|
||||
ConditionDeinit(&lockstep->barrier);
|
||||
MutexDeinit(&lockstep->mutex);
|
||||
}
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode* node) {
|
||||
node->d.init = GBASIOLockstepNodeInit;
|
||||
node->d.deinit = GBASIOLockstepNodeDeinit;
|
||||
node->d.load = GBASIOLockstepNodeLoad;
|
||||
node->d.unload = GBASIOLockstepNodeUnload;
|
||||
node->d.writeRegister = GBASIOLockstepNodeWriteRegister;
|
||||
node->d.processEvents = GBASIOLockstepNodeProcessEvents;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
if (lockstep->attached == MAX_GBAS) {
|
||||
return false;
|
||||
}
|
||||
lockstep->players[lockstep->attached] = node;
|
||||
node->p = lockstep;
|
||||
node->id = lockstep->attached;
|
||||
++lockstep->attached;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep* lockstep, struct GBASIOLockstepNode* node) {
|
||||
if (lockstep->attached == 0) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < lockstep->attached; ++i) {
|
||||
if (lockstep->players[i] != node) {
|
||||
continue;
|
||||
}
|
||||
for (++i; i < lockstep->attached; ++i) {
|
||||
lockstep->players[i - 1] = lockstep->players[i];
|
||||
lockstep->players[i - 1]->id = i - 1;
|
||||
}
|
||||
--lockstep->attached;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeInit(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent = LOCKSTEP_INCREMENT;
|
||||
node->d.p->multiplayerControl.slave = node->id > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASIOLockstepNodeDeinit(struct GBASIODriver* driver) {
|
||||
UNUSED(driver);
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->loaded;
|
||||
node->d.p->rcnt |= 3;
|
||||
if (node->id) {
|
||||
node->d.p->rcnt |= 4;
|
||||
node->d.p->multiplayerControl.slave = 1;
|
||||
}
|
||||
MutexUnlock(&node->p->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GBASIOLockstepNodeUnload(struct GBASIODriver* driver) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
MutexLock(&node->p->mutex);
|
||||
--node->p->loaded;
|
||||
ConditionWake(&node->p->barrier);
|
||||
MutexUnlock(&node->p->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t GBASIOLockstepNodeWriteRegister(struct GBASIODriver* driver, uint32_t address, uint16_t value) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
if (address == REG_SIOCNT) {
|
||||
if (value & 0x0080) {
|
||||
value &= ~0x0080;
|
||||
if (!node->id) {
|
||||
MutexLock(&node->p->mutex);
|
||||
node->p->transferActive = true;
|
||||
node->p->transferCycles = GBASIOCyclesPerTransfer[node->d.p->multiplayerControl.baud][node->p->attached - 1];
|
||||
MutexUnlock(&node->p->mutex);
|
||||
}
|
||||
}
|
||||
value &= 0xFF03;
|
||||
value |= driver->p->siocnt & 0x007C;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int32_t GBASIOLockstepNodeProcessEvents(struct GBASIODriver* driver, int32_t cycles) {
|
||||
struct GBASIOLockstepNode* node = (struct GBASIOLockstepNode*) driver;
|
||||
node->nextEvent -= cycles;
|
||||
while (node->nextEvent <= 0) {
|
||||
MutexLock(&node->p->mutex);
|
||||
++node->p->waiting;
|
||||
if (node->p->waiting < node->p->loaded) {
|
||||
ConditionWait(&node->p->barrier, &node->p->mutex);
|
||||
} else {
|
||||
if (node->p->transferActive) {
|
||||
node->p->transferCycles -= node->p->nextEvent;
|
||||
if (node->p->transferCycles > 0) {
|
||||
if (node->p->transferCycles < LOCKSTEP_INCREMENT) {
|
||||
node->p->nextEvent = node->p->transferCycles;
|
||||
}
|
||||
} else {
|
||||
node->p->nextEvent = LOCKSTEP_INCREMENT;
|
||||
node->p->transferActive = false;
|
||||
int i;
|
||||
for (i = 0; i < node->p->attached; ++i) {
|
||||
node->p->multiRecv[i] = node->p->players[i]->multiSend;
|
||||
node->p->players[i]->state = LOCKSTEP_FINISHED;
|
||||
}
|
||||
for (; i < MAX_GBAS; ++i) {
|
||||
node->p->multiRecv[i] = 0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
node->p->waiting = 0;
|
||||
ConditionWake(&node->p->barrier);
|
||||
}
|
||||
if (node->state == LOCKSTEP_FINISHED) {
|
||||
node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = node->p->multiRecv[0];
|
||||
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = node->p->multiRecv[1];
|
||||
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = node->p->multiRecv[2];
|
||||
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = node->p->multiRecv[3];
|
||||
node->d.p->rcnt |= 1;
|
||||
node->state = LOCKSTEP_IDLE;
|
||||
if (node->d.p->multiplayerControl.irq) {
|
||||
GBARaiseIRQ(node->d.p->p, IRQ_SIO);
|
||||
}
|
||||
node->d.p->multiplayerControl.id = node->id;
|
||||
node->d.p->multiplayerControl.busy = 0;
|
||||
} else if (node->state == LOCKSTEP_IDLE && node->p->transferActive) {
|
||||
node->state = LOCKSTEP_STARTED;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI0 >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI1 >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI2 >> 1] = 0xFFFF;
|
||||
node->d.p->p->memory.io[REG_SIOMULTI3 >> 1] = 0xFFFF;
|
||||
node->d.p->rcnt &= ~1;
|
||||
node->multiSend = node->d.p->p->memory.io[REG_SIOMLT_SEND >> 1];
|
||||
if (node->id) {
|
||||
node->d.p->multiplayerControl.busy = 1;
|
||||
}
|
||||
}
|
||||
node->d.p->multiplayerControl.ready = node->p->loaded == node->p->attached;
|
||||
node->nextEvent += node->p->nextEvent;
|
||||
MutexUnlock(&node->p->mutex);
|
||||
}
|
||||
return node->nextEvent;
|
||||
}
|
52
src/gba/sio/lockstep.h
Normal file
52
src/gba/sio/lockstep.h
Normal file
@ -0,0 +1,52 @@
|
||||
/* 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 SIO_LOCKSTEP_H
|
||||
#define SIO_LOCKSTEP_H
|
||||
|
||||
#include "gba/sio.h"
|
||||
|
||||
#include "util/threading.h"
|
||||
|
||||
enum LockstepState {
|
||||
LOCKSTEP_IDLE = 0,
|
||||
LOCKSTEP_STARTED = 1,
|
||||
LOCKSTEP_FINISHED = 2
|
||||
};
|
||||
|
||||
struct GBASIOLockstep {
|
||||
struct GBASIOLockstepNode* players[MAX_GBAS];
|
||||
int attached;
|
||||
int loaded;
|
||||
|
||||
uint16_t multiRecv[MAX_GBAS];
|
||||
bool transferActive;
|
||||
int32_t transferCycles;
|
||||
int32_t nextEvent;
|
||||
|
||||
int waiting;
|
||||
Mutex mutex;
|
||||
Condition barrier;
|
||||
};
|
||||
|
||||
struct GBASIOLockstepNode {
|
||||
struct GBASIODriver d;
|
||||
struct GBASIOLockstep* p;
|
||||
|
||||
int32_t nextEvent;
|
||||
uint16_t multiSend;
|
||||
enum LockstepState state;
|
||||
int id;
|
||||
};
|
||||
|
||||
void GBASIOLockstepInit(struct GBASIOLockstep*);
|
||||
void GBASIOLockstepDeinit(struct GBASIOLockstep*);
|
||||
|
||||
void GBASIOLockstepNodeCreate(struct GBASIOLockstepNode*);
|
||||
|
||||
bool GBASIOLockstepAttachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
||||
void GBASIOLockstepDetachNode(struct GBASIOLockstep*, struct GBASIOLockstepNode*);
|
||||
|
||||
#endif
|
@ -190,6 +190,9 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
||||
}
|
||||
|
||||
int fakeBool;
|
||||
if (_lookupIntValue(config, "useSync", &fakeBool)) {
|
||||
opts->useBios = fakeBool;
|
||||
}
|
||||
if (_lookupIntValue(config, "audioSync", &fakeBool)) {
|
||||
opts->audioSync = fakeBool;
|
||||
}
|
||||
@ -229,6 +232,7 @@ void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
|
||||
void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts) {
|
||||
ConfigurationSetValue(&config->defaultsTable, 0, "bios", opts->bios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "skipBios", opts->skipBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "useBios", opts->useBios);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "logLevel", opts->logLevel);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "frameskip", opts->frameskip);
|
||||
ConfigurationSetIntValue(&config->defaultsTable, 0, "rewindEnable", opts->rewindEnable);
|
||||
|
@ -21,6 +21,7 @@ struct GBAConfig {
|
||||
struct GBAOptions {
|
||||
char* bios;
|
||||
bool skipBios;
|
||||
bool useBios;
|
||||
int logLevel;
|
||||
int frameskip;
|
||||
bool rewindEnable;
|
||||
|
@ -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);
|
||||
@ -264,7 +310,11 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
|
||||
}
|
||||
|
||||
void GBAMapOptionsToContext(const struct GBAOptions* opts, struct GBAThread* threadContext) {
|
||||
threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
|
||||
if (opts->useBios) {
|
||||
threadContext->bios = VFileOpen(opts->bios, O_RDONLY);
|
||||
} else {
|
||||
threadContext->bios = 0;
|
||||
}
|
||||
threadContext->frameskip = opts->frameskip;
|
||||
threadContext->logLevel = opts->logLevel;
|
||||
if (opts->rewindEnable) {
|
||||
@ -295,12 +345,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 +359,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);
|
||||
|
353
src/platform/libretro/libretro.c
Normal file
353
src/platform/libretro/libretro.c
Normal file
@ -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);
|
||||
}
|
1926
src/platform/libretro/libretro.h
Normal file
1926
src/platform/libretro/libretro.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@ extern "C" {
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
#ifndef BUILD_SDL
|
||||
AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::QT_MULTIMEDIA;
|
||||
#else
|
||||
AudioProcessor::Driver AudioProcessor::s_driver = AudioProcessor::Driver::SDL;
|
||||
|
@ -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());
|
||||
|
@ -51,6 +51,7 @@ set(SOURCE_FILES
|
||||
KeyEditor.cpp
|
||||
LoadSaveState.cpp
|
||||
LogView.cpp
|
||||
MultiplayerController.cpp
|
||||
OverrideView.cpp
|
||||
SavestateButton.cpp
|
||||
SensorView.cpp
|
||||
@ -106,10 +107,11 @@ set_source_files_properties(${CMAKE_SOURCE_DIR}/res/mgba.icns PROPERTIES MACOSX_
|
||||
|
||||
qt5_add_resources(RESOURCES resources.qrc)
|
||||
if(WIN32)
|
||||
list(APPEND RESOURCES ${CMAKE_SOURCE_DIR}/res/mgba.rc)
|
||||
configure_file(${CMAKE_SOURCE_DIR}/res/mgba.rc.in ${CMAKE_BINARY_DIR}/res/mgba.rc)
|
||||
list(APPEND RESOURCES ${CMAKE_BINARY_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})
|
||||
|
@ -23,8 +23,11 @@ ConfigOption::ConfigOption(QObject* parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> slot) {
|
||||
m_slot = slot;
|
||||
void ConfigOption::connect(std::function<void(const QVariant&)> slot, QObject* parent) {
|
||||
m_slots[parent] = slot;
|
||||
QObject::connect(parent, &QAction::destroyed, [this, slot, parent]() {
|
||||
m_slots.remove(parent);
|
||||
});
|
||||
}
|
||||
|
||||
QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMenu* parent) {
|
||||
@ -33,6 +36,9 @@ QAction* ConfigOption::addValue(const QString& text, const QVariant& value, QMen
|
||||
QObject::connect(action, &QAction::triggered, [this, value]() {
|
||||
emit valueChanged(value);
|
||||
});
|
||||
QObject::connect(parent, &QAction::destroyed, [this, action, value]() {
|
||||
m_actions.removeAll(qMakePair(action, value));
|
||||
});
|
||||
parent->addAction(action);
|
||||
m_actions.append(qMakePair(action, value));
|
||||
return action;
|
||||
@ -48,6 +54,9 @@ QAction* ConfigOption::addBoolean(const QString& text, QMenu* parent) {
|
||||
QObject::connect(action, &QAction::triggered, [this, action]() {
|
||||
emit valueChanged(action->isChecked());
|
||||
});
|
||||
QObject::connect(parent, &QAction::destroyed, [this, action]() {
|
||||
m_actions.removeAll(qMakePair(action, 1));
|
||||
});
|
||||
parent->addAction(action);
|
||||
m_actions.append(qMakePair(action, 1));
|
||||
return action;
|
||||
@ -76,7 +85,10 @@ void ConfigOption::setValue(const QVariant& value) {
|
||||
action.first->setChecked(value == action.second);
|
||||
action.first->blockSignals(signalsEnabled);
|
||||
}
|
||||
m_slot(value);
|
||||
std::function<void(const QVariant&)> slot;
|
||||
foreach(slot, m_slots.values()) {
|
||||
slot(value);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigController::ConfigController(QObject* parent)
|
||||
@ -100,6 +112,7 @@ ConfigController::ConfigController(QObject* parent)
|
||||
m_opts.rewindEnable = false;
|
||||
m_opts.rewindBufferInterval = 0;
|
||||
m_opts.rewindBufferCapacity = 0;
|
||||
m_opts.useBios = true;
|
||||
GBAConfigLoadDefaults(&m_config, &m_opts);
|
||||
GBAConfigLoad(&m_config);
|
||||
GBAConfigMap(&m_config, &m_opts);
|
||||
|
@ -32,11 +32,11 @@ Q_OBJECT
|
||||
public:
|
||||
ConfigOption(QObject* parent = nullptr);
|
||||
|
||||
void connect(std::function<void(const QVariant&)>);
|
||||
void connect(std::function<void(const QVariant&)>, QObject* parent = nullptr);
|
||||
|
||||
QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = 0);
|
||||
QAction* addValue(const QString& text, const char* value, QMenu* parent = 0);
|
||||
QAction* addBoolean(const QString& text, QMenu* parent = 0);
|
||||
QAction* addValue(const QString& text, const QVariant& value, QMenu* parent = nullptr);
|
||||
QAction* addValue(const QString& text, const char* value, QMenu* parent = nullptr);
|
||||
QAction* addBoolean(const QString& text, QMenu* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setValue(bool value);
|
||||
@ -49,7 +49,7 @@ signals:
|
||||
void valueChanged(const QVariant& value);
|
||||
|
||||
private:
|
||||
std::function<void(const QVariant&)> m_slot;
|
||||
QMap<QObject*, std::function<void(const QVariant&)>> m_slots;
|
||||
QList<QPair<QAction*, QVariant>> m_actions;
|
||||
};
|
||||
|
||||
@ -79,6 +79,8 @@ public:
|
||||
Configuration* overrides() { return GBAConfigGetOverrides(&m_config); }
|
||||
void saveOverride(const GBACartridgeOverride&);
|
||||
|
||||
Configuration* input() { return GBAConfigGetInput(&m_config); }
|
||||
|
||||
public slots:
|
||||
void setOption(const char* key, bool value);
|
||||
void setOption(const char* key, int value);
|
||||
@ -90,11 +92,8 @@ public slots:
|
||||
void write();
|
||||
|
||||
private:
|
||||
Configuration* configuration() { return &m_config.configTable; }
|
||||
Configuration* defaults() { return &m_config.defaultsTable; }
|
||||
|
||||
friend class InputController; // TODO: Do this without friends
|
||||
|
||||
GBAConfig m_config;
|
||||
GBAOptions m_opts;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "GBAKeyEditor.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPaintEvent>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
@ -20,9 +21,11 @@ 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 QString& profile, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_profileSelect(nullptr)
|
||||
, m_type(type)
|
||||
, m_profile(profile)
|
||||
, m_controller(controller)
|
||||
{
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowFullscreenButtonHint);
|
||||
@ -41,19 +44,26 @@ GBAKeyEditor::GBAKeyEditor(InputController* controller, int type, QWidget* paren
|
||||
m_keyL = new KeyEditor(this);
|
||||
m_keyR = new KeyEditor(this);
|
||||
|
||||
lookupBinding(map, m_keyDU, GBA_KEY_UP);
|
||||
lookupBinding(map, m_keyDD, GBA_KEY_DOWN);
|
||||
lookupBinding(map, m_keyDL, GBA_KEY_LEFT);
|
||||
lookupBinding(map, m_keyDR, GBA_KEY_RIGHT);
|
||||
lookupBinding(map, m_keySelect, GBA_KEY_SELECT);
|
||||
lookupBinding(map, m_keyStart, GBA_KEY_START);
|
||||
lookupBinding(map, m_keyA, GBA_KEY_A);
|
||||
lookupBinding(map, m_keyB, GBA_KEY_B);
|
||||
lookupBinding(map, m_keyL, GBA_KEY_L);
|
||||
lookupBinding(map, m_keyR, GBA_KEY_R);
|
||||
refresh();
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
lookupAxes(map);
|
||||
|
||||
if (type == SDL_BINDING_BUTTON) {
|
||||
m_profileSelect = new QComboBox(this);
|
||||
m_profileSelect->addItems(controller->connectedGamepads(type));
|
||||
int activeGamepad = controller->gamepad(type);
|
||||
if (activeGamepad > 0) {
|
||||
m_profileSelect->setCurrentIndex(activeGamepad);
|
||||
}
|
||||
|
||||
connect(m_profileSelect, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [this] (int i) {
|
||||
m_controller->setGamepad(m_type, i);
|
||||
m_profile = m_profileSelect->currentText();
|
||||
m_controller->loadProfile(m_type, m_profile);
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
connect(m_keyDU, SIGNAL(valueChanged(int)), this, SLOT(setNext()));
|
||||
@ -128,6 +138,10 @@ void GBAKeyEditor::resizeEvent(QResizeEvent* event) {
|
||||
setLocation(m_keyB, 0.667, 0.490);
|
||||
setLocation(m_keyL, 0.1, 0.1);
|
||||
setLocation(m_keyR, 0.9, 0.1);
|
||||
|
||||
if (m_profileSelect) {
|
||||
setLocation(m_profileSelect, 0.5, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAKeyEditor::paintEvent(QPaintEvent* event) {
|
||||
@ -163,6 +177,30 @@ void GBAKeyEditor::save() {
|
||||
bindKey(m_keyL, GBA_KEY_L);
|
||||
bindKey(m_keyR, GBA_KEY_R);
|
||||
m_controller->saveConfiguration(m_type);
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
if (m_profileSelect) {
|
||||
m_controller->setPreferredGamepad(m_type, m_profileSelect->currentText());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!m_profile.isNull()) {
|
||||
m_controller->saveProfile(m_type, m_profile);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAKeyEditor::refresh() {
|
||||
const GBAInputMap* map = m_controller->map();
|
||||
lookupBinding(map, m_keyDU, GBA_KEY_UP);
|
||||
lookupBinding(map, m_keyDD, GBA_KEY_DOWN);
|
||||
lookupBinding(map, m_keyDL, GBA_KEY_LEFT);
|
||||
lookupBinding(map, m_keyDR, GBA_KEY_RIGHT);
|
||||
lookupBinding(map, m_keySelect, GBA_KEY_SELECT);
|
||||
lookupBinding(map, m_keyStart, GBA_KEY_START);
|
||||
lookupBinding(map, m_keyA, GBA_KEY_A);
|
||||
lookupBinding(map, m_keyB, GBA_KEY_B);
|
||||
lookupBinding(map, m_keyL, GBA_KEY_L);
|
||||
lookupBinding(map, m_keyR, GBA_KEY_R);
|
||||
}
|
||||
|
||||
void GBAKeyEditor::lookupBinding(const GBAInputMap* map, KeyEditor* keyEditor, GBAKey key) {
|
||||
@ -199,11 +237,15 @@ void GBAKeyEditor::lookupAxes(const GBAInputMap* map) {
|
||||
#endif
|
||||
|
||||
void GBAKeyEditor::bindKey(const KeyEditor* keyEditor, GBAKey key) {
|
||||
#ifdef BUILD_SDL
|
||||
if (keyEditor->direction() != GamepadAxisEvent::NEUTRAL) {
|
||||
m_controller->bindAxis(m_type, keyEditor->value(), keyEditor->direction(), key);
|
||||
} else {
|
||||
#endif
|
||||
m_controller->bindKey(m_type, keyEditor->value(), key);
|
||||
#ifdef BUILD_SDL
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GBAKeyEditor::findFocus() {
|
||||
|
@ -15,6 +15,7 @@ extern "C" {
|
||||
#include "gba/input.h"
|
||||
}
|
||||
|
||||
class QComboBox;
|
||||
class QTimer;
|
||||
|
||||
namespace QGBA {
|
||||
@ -26,7 +27,7 @@ class GBAKeyEditor : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GBAKeyEditor(InputController* controller, int type, QWidget* parent = nullptr);
|
||||
GBAKeyEditor(InputController* controller, int type, const QString& profile = QString(), QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setAll();
|
||||
@ -38,6 +39,7 @@ protected:
|
||||
private slots:
|
||||
void setNext();
|
||||
void save();
|
||||
void refresh();
|
||||
#ifdef BUILD_SDL
|
||||
void setAxisValue(int axis, int32_t value);
|
||||
#endif
|
||||
@ -61,6 +63,7 @@ private:
|
||||
|
||||
KeyEditor* keyById(GBAKey);
|
||||
|
||||
QComboBox* m_profileSelect;
|
||||
QWidget* m_buttons;
|
||||
KeyEditor* m_keyDU;
|
||||
KeyEditor* m_keyDD;
|
||||
@ -76,6 +79,7 @@ private:
|
||||
QList<KeyEditor*>::iterator m_currentKey;
|
||||
|
||||
uint32_t m_type;
|
||||
QString m_profile;
|
||||
InputController* m_controller;
|
||||
|
||||
QPicture m_background;
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "AudioProcessor.h"
|
||||
#include "InputController.h"
|
||||
#include "MultiplayerController.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QThread>
|
||||
@ -32,6 +33,7 @@ GameController::GameController(QObject* parent)
|
||||
, m_drawContext(new uint32_t[256 * 256])
|
||||
, m_threadContext()
|
||||
, m_activeKeys(0)
|
||||
, m_inactiveKeys(0)
|
||||
, m_logLevels(0)
|
||||
, m_gameOpen(false)
|
||||
, m_audioThread(new QThread(this))
|
||||
@ -41,6 +43,7 @@ GameController::GameController(QObject* parent)
|
||||
, m_turbo(false)
|
||||
, m_turboForced(false)
|
||||
, m_inputController(nullptr)
|
||||
, m_multiplayer(nullptr)
|
||||
{
|
||||
m_renderer = new GBAVideoSoftwareRenderer;
|
||||
GBAVideoSoftwareRendererCreate(m_renderer);
|
||||
@ -115,7 +118,14 @@ GameController::GameController(QObject* parent)
|
||||
};
|
||||
|
||||
m_threadContext.logHandler = [] (GBAThread* context, enum GBALogLevel level, const char* format, va_list args) {
|
||||
static const char* stubMessage = "Stub software interrupt";
|
||||
GameController* controller = static_cast<GameController*>(context->userData);
|
||||
if (level == GBA_LOG_STUB && strncmp(stubMessage, format, strlen(stubMessage)) == 0) {
|
||||
va_list argc;
|
||||
va_copy(argc, args);
|
||||
int immediate = va_arg(argc, int);
|
||||
controller->unimplementedBiosCall(immediate);
|
||||
}
|
||||
if (level == GBA_LOG_FATAL) {
|
||||
QMetaObject::invokeMethod(controller, "crashGame", Q_ARG(const QString&, QString().vsprintf(format, args)));
|
||||
} else if (!(controller->m_logLevels & level)) {
|
||||
@ -140,12 +150,30 @@ GameController::~GameController() {
|
||||
m_audioThread->quit();
|
||||
m_audioThread->wait();
|
||||
disconnect();
|
||||
clearMultiplayerController();
|
||||
closeGame();
|
||||
GBACheatDeviceDestroy(&m_cheatDevice);
|
||||
delete m_renderer;
|
||||
delete[] m_drawContext;
|
||||
}
|
||||
|
||||
void GameController::setMultiplayerController(std::shared_ptr<MultiplayerController> controller) {
|
||||
if (controller == m_multiplayer) {
|
||||
return;
|
||||
}
|
||||
clearMultiplayerController();
|
||||
m_multiplayer = controller;
|
||||
controller->attachGame(this);
|
||||
}
|
||||
|
||||
void GameController::clearMultiplayerController() {
|
||||
if (!m_multiplayer) {
|
||||
return;
|
||||
}
|
||||
m_multiplayer->detachGame(this);
|
||||
m_multiplayer.reset();
|
||||
}
|
||||
|
||||
void GameController::setOverride(const GBACartridgeOverride& override) {
|
||||
m_threadContext.override = override;
|
||||
m_threadContext.hasOverride = true;
|
||||
@ -156,6 +184,7 @@ void GameController::setOptions(const GBAOptions* opts) {
|
||||
setAudioSync(opts->audioSync);
|
||||
setVideoSync(opts->videoSync);
|
||||
setSkipBIOS(opts->skipBios);
|
||||
setUseBIOS(opts->useBios);
|
||||
setRewind(opts->rewindEnable, opts->rewindBufferCapacity, opts->rewindBufferInterval);
|
||||
|
||||
threadInterrupt();
|
||||
@ -216,20 +245,22 @@ 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);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!m_bios.isNull()) {
|
||||
if (!m_bios.isNull() &&m_useBios) {
|
||||
m_threadContext.bios = VFileOpen(m_bios.toLocal8Bit().constData(), O_RDONLY);
|
||||
} else {
|
||||
m_threadContext.bios = nullptr;
|
||||
}
|
||||
|
||||
if (!m_patch.isNull()) {
|
||||
@ -367,17 +398,38 @@ void GameController::rewind(int states) {
|
||||
void GameController::keyPressed(int key) {
|
||||
int mappedKey = 1 << key;
|
||||
m_activeKeys |= mappedKey;
|
||||
if (!m_inputController->allowOpposing()) {
|
||||
if ((m_activeKeys & 0x30) == 0x30) {
|
||||
m_inactiveKeys |= mappedKey ^ 0x30;
|
||||
m_activeKeys ^= mappedKey ^ 0x30;
|
||||
}
|
||||
if ((m_activeKeys & 0xC0) == 0xC0) {
|
||||
m_inactiveKeys |= mappedKey ^ 0xC0;
|
||||
m_activeKeys ^= mappedKey ^ 0xC0;
|
||||
}
|
||||
}
|
||||
updateKeys();
|
||||
}
|
||||
|
||||
void GameController::keyReleased(int key) {
|
||||
int mappedKey = 1 << key;
|
||||
m_activeKeys &= ~mappedKey;
|
||||
if (!m_inputController->allowOpposing()) {
|
||||
if (mappedKey & 0x30) {
|
||||
m_activeKeys |= m_inactiveKeys & (0x30 ^ mappedKey);
|
||||
m_inactiveKeys &= ~0x30;
|
||||
}
|
||||
if (mappedKey & 0xC0) {
|
||||
m_activeKeys |= m_inactiveKeys & (0xC0 ^ mappedKey);
|
||||
m_inactiveKeys &= ~0xC0;
|
||||
}
|
||||
}
|
||||
updateKeys();
|
||||
}
|
||||
|
||||
void GameController::clearKeys() {
|
||||
m_activeKeys = 0;
|
||||
m_inactiveKeys = 0;
|
||||
updateKeys();
|
||||
}
|
||||
|
||||
@ -402,6 +454,12 @@ void GameController::setSkipBIOS(bool set) {
|
||||
threadContinue();
|
||||
}
|
||||
|
||||
void GameController::setUseBIOS(bool use) {
|
||||
threadInterrupt();
|
||||
m_useBios = use;
|
||||
threadContinue();
|
||||
}
|
||||
|
||||
void GameController::loadState(int slot) {
|
||||
threadInterrupt();
|
||||
GBALoadState(&m_threadContext, m_threadContext.stateDir, slot);
|
||||
@ -489,6 +547,7 @@ void GameController::setLuminanceValue(uint8_t value) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit luminanceValueChanged(m_luxValue);
|
||||
}
|
||||
|
||||
void GameController::setLuminanceLevel(int level) {
|
||||
@ -519,6 +578,7 @@ void GameController::updateKeys() {
|
||||
#ifdef BUILD_SDL
|
||||
activeKeys |= m_activeButtons;
|
||||
#endif
|
||||
activeKeys &= ~m_inactiveKeys;
|
||||
m_threadContext.activeKeys = activeKeys;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
#include <memory>
|
||||
|
||||
extern "C" {
|
||||
#include "gba/cheats.h"
|
||||
#include "gba/hardware.h"
|
||||
@ -32,6 +34,7 @@ namespace QGBA {
|
||||
|
||||
class AudioProcessor;
|
||||
class InputController;
|
||||
class MultiplayerController;
|
||||
|
||||
class GameController : public QObject {
|
||||
Q_OBJECT
|
||||
@ -59,6 +62,10 @@ public:
|
||||
void setInputController(InputController* controller) { m_inputController = controller; }
|
||||
void setOverrides(Configuration* overrides) { m_threadContext.overrides = overrides; }
|
||||
|
||||
void setMultiplayerController(std::shared_ptr<MultiplayerController> controller);
|
||||
std::shared_ptr<MultiplayerController> multiplayerController() { return m_multiplayer; }
|
||||
void clearMultiplayerController();
|
||||
|
||||
void setOverride(const GBACartridgeOverride& override);
|
||||
void clearOverride() { m_threadContext.hasOverride = false; }
|
||||
|
||||
@ -78,6 +85,9 @@ signals:
|
||||
void gameCrashed(const QString& errorMessage);
|
||||
void gameFailed();
|
||||
void stateLoaded(GBAThread*);
|
||||
void unimplementedBiosCall(int);
|
||||
|
||||
void luminanceValueChanged(int);
|
||||
|
||||
void postLog(int level, const QString& log);
|
||||
|
||||
@ -85,6 +95,7 @@ public slots:
|
||||
void loadGame(const QString& path, bool dirmode = false);
|
||||
void loadBIOS(const QString& path);
|
||||
void setSkipBIOS(bool);
|
||||
void setUseBIOS(bool);
|
||||
void loadPatch(const QString& path);
|
||||
void openGame();
|
||||
void closeGame();
|
||||
@ -109,6 +120,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); }
|
||||
@ -141,6 +153,7 @@ private:
|
||||
GBAVideoSoftwareRenderer* m_renderer;
|
||||
GBACheatDevice m_cheatDevice;
|
||||
int m_activeKeys;
|
||||
int m_inactiveKeys;
|
||||
int m_logLevels;
|
||||
|
||||
bool m_gameOpen;
|
||||
@ -148,6 +161,7 @@ private:
|
||||
|
||||
QString m_fname;
|
||||
QString m_bios;
|
||||
bool m_useBios;
|
||||
QString m_patch;
|
||||
|
||||
QThread* m_audioThread;
|
||||
@ -162,6 +176,7 @@ private:
|
||||
bool m_turboForced;
|
||||
|
||||
InputController* m_inputController;
|
||||
std::shared_ptr<MultiplayerController> m_multiplayer;
|
||||
|
||||
struct GameControllerLux : GBALuminanceSource {
|
||||
GameController* p;
|
||||
|
@ -19,18 +19,30 @@ extern "C" {
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
InputController::InputController(QObject* parent)
|
||||
#ifdef BUILD_SDL
|
||||
int InputController::s_sdlInited = 0;
|
||||
GBASDLEvents InputController::s_sdlEvents;
|
||||
#endif
|
||||
|
||||
InputController::InputController(int playerId, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_playerId(playerId)
|
||||
, m_config(nullptr)
|
||||
, m_gamepadTimer(nullptr)
|
||||
#ifdef BUILD_SDL
|
||||
, m_playerAttached(false)
|
||||
#endif
|
||||
, m_allowOpposing(false)
|
||||
{
|
||||
GBAInputMapInit(&m_inputMap);
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
m_sdlEvents.bindings = &m_inputMap;
|
||||
GBASDLInitEvents(&m_sdlEvents);
|
||||
if (s_sdlInited == 0) {
|
||||
GBASDLInitEvents(&s_sdlEvents);
|
||||
}
|
||||
++s_sdlInited;
|
||||
m_sdlPlayer.bindings = &m_inputMap;
|
||||
GBASDLInitBindings(&m_inputMap);
|
||||
SDL_JoystickEventState(SDL_QUERY);
|
||||
|
||||
m_gamepadTimer = new QTimer(this);
|
||||
connect(m_gamepadTimer, SIGNAL(timeout()), this, SLOT(testGamepad()));
|
||||
@ -54,27 +66,92 @@ InputController::~InputController() {
|
||||
GBAInputMapDeinit(&m_inputMap);
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
GBASDLDeinitEvents(&m_sdlEvents);
|
||||
--s_sdlInited;
|
||||
if (s_sdlInited == 0) {
|
||||
GBASDLDeinitEvents(&s_sdlEvents);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::setConfiguration(ConfigController* config) {
|
||||
m_config = config;
|
||||
setAllowOpposing(config->getOption("allowOpposingDirections").toInt());
|
||||
loadConfiguration(KEYBOARD);
|
||||
#ifdef BUILD_SDL
|
||||
GBASDLEventsLoadConfig(&s_sdlEvents, config->input());
|
||||
if (!m_playerAttached) {
|
||||
GBASDLAttachPlayer(&s_sdlEvents, &m_sdlPlayer);
|
||||
m_playerAttached = true;
|
||||
}
|
||||
loadConfiguration(SDL_BINDING_BUTTON);
|
||||
loadProfile(SDL_BINDING_BUTTON, profileForType(SDL_BINDING_BUTTON));
|
||||
#endif
|
||||
}
|
||||
|
||||
void InputController::loadConfiguration(uint32_t type) {
|
||||
GBAInputMapLoad(&m_inputMap, type, m_config->configuration());
|
||||
GBAInputMapLoad(&m_inputMap, type, m_config->input());
|
||||
}
|
||||
|
||||
void InputController::loadProfile(uint32_t type, const QString& profile) {
|
||||
GBAInputProfileLoad(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
void InputController::saveConfiguration(uint32_t type) {
|
||||
GBAInputMapSave(&m_inputMap, type, m_config->configuration());
|
||||
GBAInputMapSave(&m_inputMap, type, m_config->input());
|
||||
m_config->write();
|
||||
}
|
||||
|
||||
void InputController::saveProfile(uint32_t type, const QString& profile) {
|
||||
GBAInputProfileSave(&m_inputMap, type, m_config->input(), profile.toLocal8Bit().constData());
|
||||
m_config->write();
|
||||
}
|
||||
|
||||
const char* InputController::profileForType(uint32_t type) {
|
||||
UNUSED(type);
|
||||
#ifdef BUILD_SDL
|
||||
if (type == SDL_BINDING_BUTTON && m_sdlPlayer.joystick) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
return SDL_JoystickName(m_sdlPlayer.joystick);
|
||||
#else
|
||||
return SDL_JoystickName(SDL_JoystickIndex(m_sdlPlayer.joystick));
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
QStringList InputController::connectedGamepads(uint32_t type) const {
|
||||
UNUSED(type);
|
||||
if (type != SDL_BINDING_BUTTON) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
QStringList pads;
|
||||
for (size_t i = 0; i < s_sdlEvents.nJoysticks; ++i) {
|
||||
const char* name;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
name = SDL_JoystickName(s_sdlEvents.joysticks[i]);
|
||||
#else
|
||||
name = SDL_JoystickName(SDL_JoystickIndex(s_sdlEvents.joysticks[i]));
|
||||
#endif
|
||||
if (name) {
|
||||
pads.append(QString(name));
|
||||
} else {
|
||||
pads.append(QString());
|
||||
}
|
||||
}
|
||||
return pads;
|
||||
}
|
||||
|
||||
void InputController::setPreferredGamepad(uint32_t type, const QString& device) {
|
||||
if (!m_config) {
|
||||
return;
|
||||
}
|
||||
GBAInputSetPreferredDevice(m_config->input(), type, m_sdlPlayer.playerId, device.toLocal8Bit().constData());
|
||||
}
|
||||
#endif
|
||||
|
||||
GBAKey InputController::mapKeyboard(int key) const {
|
||||
return GBAInputMapKey(&m_inputMap, KEYBOARD, key);
|
||||
}
|
||||
@ -85,7 +162,7 @@ void InputController::bindKey(uint32_t type, int key, GBAKey gbaKey) {
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
int InputController::testSDLEvents() {
|
||||
SDL_Joystick* joystick = m_sdlEvents.joystick;
|
||||
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
||||
SDL_JoystickUpdate();
|
||||
int numButtons = SDL_JoystickNumButtons(joystick);
|
||||
int activeButtons = 0;
|
||||
@ -132,7 +209,7 @@ int InputController::testSDLEvents() {
|
||||
}
|
||||
|
||||
QSet<int> InputController::activeGamepadButtons() {
|
||||
SDL_Joystick* joystick = m_sdlEvents.joystick;
|
||||
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
||||
SDL_JoystickUpdate();
|
||||
int numButtons = SDL_JoystickNumButtons(joystick);
|
||||
QSet<int> activeButtons;
|
||||
@ -146,7 +223,7 @@ QSet<int> InputController::activeGamepadButtons() {
|
||||
}
|
||||
|
||||
QSet<QPair<int, GamepadAxisEvent::Direction>> InputController::activeGamepadAxes() {
|
||||
SDL_Joystick* joystick = m_sdlEvents.joystick;
|
||||
SDL_Joystick* joystick = m_sdlPlayer.joystick;
|
||||
SDL_JoystickUpdate();
|
||||
int numButtons = SDL_JoystickNumAxes(joystick);
|
||||
QSet<QPair<int, GamepadAxisEvent::Direction>> activeAxes;
|
||||
|
@ -31,12 +31,18 @@ Q_OBJECT
|
||||
public:
|
||||
static const uint32_t KEYBOARD = 0x51545F4B;
|
||||
|
||||
InputController(QObject* parent = nullptr);
|
||||
InputController(int playerId = 0, QObject* parent = nullptr);
|
||||
~InputController();
|
||||
|
||||
void setConfiguration(ConfigController* config);
|
||||
void loadConfiguration(uint32_t type);
|
||||
void loadProfile(uint32_t type, const QString& profile);
|
||||
void saveConfiguration(uint32_t type = KEYBOARD);
|
||||
void saveProfile(uint32_t type, const QString& profile);
|
||||
const char* profileForType(uint32_t type);
|
||||
|
||||
bool allowOpposing() const { return m_allowOpposing; }
|
||||
void setAllowOpposing(bool allowOpposing) { m_allowOpposing = allowOpposing; }
|
||||
|
||||
GBAKey mapKeyboard(int key) const;
|
||||
|
||||
@ -52,6 +58,11 @@ public:
|
||||
QSet<QPair<int, GamepadAxisEvent::Direction>> activeGamepadAxes();
|
||||
|
||||
void bindAxis(uint32_t type, int axis, GamepadAxisEvent::Direction, GBAKey);
|
||||
|
||||
QStringList connectedGamepads(uint32_t type) const;
|
||||
int gamepad(uint32_t type) const { return m_sdlPlayer.joystickIndex; }
|
||||
void setGamepad(uint32_t type, int index) { GBASDLPlayerChangeJoystick(&s_sdlEvents, &m_sdlPlayer, index); }
|
||||
void setPreferredGamepad(uint32_t type, const QString& device);
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
@ -64,9 +75,14 @@ private:
|
||||
|
||||
GBAInputMap m_inputMap;
|
||||
ConfigController* m_config;
|
||||
int m_playerId;
|
||||
bool m_allowOpposing;
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
GBASDLEvents m_sdlEvents;
|
||||
static int s_sdlInited;
|
||||
static GBASDLEvents s_sdlEvents;
|
||||
GBASDLPlayer m_sdlPlayer;
|
||||
bool m_playerAttached;
|
||||
#endif
|
||||
|
||||
QSet<int> m_activeButtons;
|
||||
|
71
src/platform/qt/MultiplayerController.cpp
Normal file
71
src/platform/qt/MultiplayerController.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
/* 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 "MultiplayerController.h"
|
||||
|
||||
#include "GameController.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
MultiplayerController::MultiplayerController() {
|
||||
GBASIOLockstepInit(&m_lockstep);
|
||||
}
|
||||
|
||||
MultiplayerController::~MultiplayerController() {
|
||||
GBASIOLockstepDeinit(&m_lockstep);
|
||||
}
|
||||
|
||||
bool MultiplayerController::attachGame(GameController* controller) {
|
||||
MutexLock(&m_lockstep.mutex);
|
||||
if (m_lockstep.attached == MAX_GBAS) {
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
return false;
|
||||
}
|
||||
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
||||
GBASIOLockstepNodeCreate(node);
|
||||
GBASIOLockstepAttachNode(&m_lockstep, node);
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
|
||||
controller->threadInterrupt();
|
||||
GBAThread* thread = controller->thread();
|
||||
if (controller->isLoaded()) {
|
||||
GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_MULTI);
|
||||
}
|
||||
thread->sioDrivers.multiplayer = &node->d;
|
||||
controller->threadContinue();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MultiplayerController::detachGame(GameController* controller) {
|
||||
controller->threadInterrupt();
|
||||
MutexLock(&m_lockstep.mutex);
|
||||
GBAThread* thread = nullptr;
|
||||
for (int i = 0; i < m_lockstep.attached; ++i) {
|
||||
thread = controller->thread();
|
||||
if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) {
|
||||
break;
|
||||
}
|
||||
thread = nullptr;
|
||||
}
|
||||
if (thread) {
|
||||
GBASIOLockstepNode* node = reinterpret_cast<GBASIOLockstepNode*>(thread->sioDrivers.multiplayer);
|
||||
if (controller->isLoaded()) {
|
||||
GBASIOSetDriver(&thread->gba->sio, nullptr, SIO_MULTI);
|
||||
}
|
||||
thread->sioDrivers.multiplayer = nullptr;
|
||||
GBASIOLockstepDetachNode(&m_lockstep, node);
|
||||
delete node;
|
||||
}
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
controller->threadContinue();
|
||||
}
|
||||
|
||||
int MultiplayerController::attached() {
|
||||
int num;
|
||||
MutexLock(&m_lockstep.mutex);
|
||||
num = m_lockstep.attached;
|
||||
MutexUnlock(&m_lockstep.mutex);
|
||||
return num;
|
||||
}
|
32
src/platform/qt/MultiplayerController.h
Normal file
32
src/platform/qt/MultiplayerController.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* 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 QGBA_MULTIPLAYER_CONTROLLER
|
||||
#define QGBA_MULTIPLAYER_CONTROLLER
|
||||
|
||||
extern "C" {
|
||||
#include "gba/sio/lockstep.h"
|
||||
}
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class GameController;
|
||||
|
||||
class MultiplayerController {
|
||||
public:
|
||||
MultiplayerController();
|
||||
~MultiplayerController();
|
||||
|
||||
bool attachGame(GameController*);
|
||||
void detachGame(GameController*);
|
||||
|
||||
int attached();
|
||||
|
||||
private:
|
||||
GBASIOLockstep m_lockstep;
|
||||
};
|
||||
|
||||
}
|
||||
#endif
|
@ -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;
|
||||
|
@ -19,6 +19,7 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
|
||||
m_ui.setupUi(this);
|
||||
|
||||
loadSetting("bios", m_ui.bios);
|
||||
loadSetting("useBios", m_ui.useBios);
|
||||
loadSetting("skipBios", m_ui.skipBios);
|
||||
loadSetting("audioBuffers", m_ui.audioBufferSize);
|
||||
loadSetting("videoSync", m_ui.videoSync);
|
||||
@ -30,6 +31,7 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
|
||||
loadSetting("rewindBufferInterval", m_ui.rewindInterval);
|
||||
loadSetting("rewindBufferCapacity", m_ui.rewindCapacity);
|
||||
loadSetting("resampleVideo", m_ui.resampleVideo);
|
||||
loadSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
||||
|
||||
QString idleOptimization = loadSetting("idleOptimization");
|
||||
if (idleOptimization == "ignore") {
|
||||
@ -40,17 +42,17 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
|
||||
m_ui.idleOptimization->setCurrentIndex(2);
|
||||
}
|
||||
|
||||
int audioDriver = m_controller->getQtOption("audioDriver").toInt();
|
||||
QVariant audioDriver = m_controller->getQtOption("audioDriver");
|
||||
#ifdef BUILD_QT_MULTIMEDIA
|
||||
m_ui.audioDriver->addItem(tr("Qt Multimedia"), static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA));
|
||||
if (audioDriver == static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)) {
|
||||
if (!audioDriver.isNull() && audioDriver.toInt() == static_cast<int>(AudioProcessor::Driver::QT_MULTIMEDIA)) {
|
||||
m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
m_ui.audioDriver->addItem(tr("SDL"), static_cast<int>(AudioProcessor::Driver::SDL));
|
||||
if (audioDriver == static_cast<int>(AudioProcessor::Driver::SDL)) {
|
||||
if (audioDriver.isNull() || audioDriver.toInt() == static_cast<int>(AudioProcessor::Driver::SDL)) {
|
||||
m_ui.audioDriver->setCurrentIndex(m_ui.audioDriver->count() - 1);
|
||||
}
|
||||
#endif
|
||||
@ -68,6 +70,7 @@ void SettingsView::selectBios() {
|
||||
|
||||
void SettingsView::updateConfig() {
|
||||
saveSetting("bios", m_ui.bios);
|
||||
saveSetting("useBios", m_ui.useBios);
|
||||
saveSetting("skipBios", m_ui.skipBios);
|
||||
saveSetting("audioBuffers", m_ui.audioBufferSize);
|
||||
saveSetting("videoSync", m_ui.videoSync);
|
||||
@ -79,6 +82,7 @@ void SettingsView::updateConfig() {
|
||||
saveSetting("rewindBufferInterval", m_ui.rewindInterval);
|
||||
saveSetting("rewindBufferCapacity", m_ui.rewindCapacity);
|
||||
saveSetting("resampleVideo", m_ui.resampleVideo);
|
||||
saveSetting("allowOpposingDirections", m_ui.allowOpposingDirections);
|
||||
|
||||
switch (m_ui.idleOptimization->currentIndex() + IDLE_LOOP_IGNORE) {
|
||||
case IDLE_LOOP_IGNORE:
|
||||
@ -128,7 +132,7 @@ void SettingsView::saveSetting(const char* key, const QString& field) {
|
||||
|
||||
void SettingsView::loadSetting(const char* key, QAbstractButton* field) {
|
||||
QString option = loadSetting(key);
|
||||
field->setChecked(option != "0");
|
||||
field->setChecked(!option.isNull() && option != "0");
|
||||
}
|
||||
|
||||
void SettingsView::loadSetting(const char* key, QComboBox* field) {
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>355</width>
|
||||
<height>501</height>
|
||||
<width>360</width>
|
||||
<height>569</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -62,9 +62,6 @@
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="useBios">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use BIOS file</string>
|
||||
</property>
|
||||
@ -201,6 +198,34 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>FPS target:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="fpsTarget">
|
||||
<property name="maximum">
|
||||
<number>240</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>frames per second</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
@ -285,35 +310,21 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>FPS target:</string>
|
||||
<item row="17" column="0" colspan="2">
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="fpsTarget">
|
||||
<property name="maximum">
|
||||
<number>240</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>60</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>frames per second</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item row="18" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<property name="text">
|
||||
<string>Idle loops</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="1">
|
||||
<item row="18" column="1">
|
||||
<widget class="QComboBox" name="idleOptimization">
|
||||
<item>
|
||||
<property name="text">
|
||||
@ -332,17 +343,10 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="0" colspan="2">
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="0">
|
||||
<widget class="QLabel" name="label_15">
|
||||
<item row="16" column="1">
|
||||
<widget class="QCheckBox" name="allowOpposingDirections">
|
||||
<property name="text">
|
||||
<string>Idle loops</string>
|
||||
<string>Allow opposing input directions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "GIFView.h"
|
||||
#include "LoadSaveState.h"
|
||||
#include "LogView.h"
|
||||
#include "MultiplayerController.h"
|
||||
#include "OverrideView.h"
|
||||
#include "SensorView.h"
|
||||
#include "SettingsView.h"
|
||||
@ -36,13 +37,14 @@ extern "C" {
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
Window::Window(ConfigController* config, QWidget* parent)
|
||||
Window::Window(ConfigController* config, int playerId, QWidget* parent)
|
||||
: QMainWindow(parent)
|
||||
, m_logView(new LogView())
|
||||
, m_stateWindow(nullptr)
|
||||
, m_screenWidget(new WindowBackground())
|
||||
, m_logo(":/res/mgba-1024.png")
|
||||
, m_config(config)
|
||||
, m_inputController(playerId)
|
||||
#ifdef USE_FFMPEG
|
||||
, m_videoView(nullptr)
|
||||
#endif
|
||||
@ -54,6 +56,7 @@ Window::Window(ConfigController* config, QWidget* parent)
|
||||
#endif
|
||||
, m_mruMenu(nullptr)
|
||||
, m_shortcutController(new ShortcutController(this))
|
||||
, m_playerId(playerId)
|
||||
{
|
||||
setWindowTitle(PROJECT_NAME);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
@ -94,6 +97,7 @@ Window::Window(ConfigController* config, QWidget* parent)
|
||||
connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), this, SLOT(recordFrame()));
|
||||
connect(m_controller, SIGNAL(gameCrashed(const QString&)), this, SLOT(gameCrashed(const QString&)));
|
||||
connect(m_controller, SIGNAL(gameFailed()), this, SLOT(gameFailed()));
|
||||
connect(m_controller, SIGNAL(unimplementedBiosCall(int)), this, SLOT(unimplementedBiosCall(int)));
|
||||
connect(m_logView, SIGNAL(levelsSet(int)), m_controller, SLOT(setLogLevel(int)));
|
||||
connect(m_logView, SIGNAL(levelsEnabled(int)), m_controller, SLOT(enableLogLevel(int)));
|
||||
connect(m_logView, SIGNAL(levelsDisabled(int)), m_controller, SLOT(disableLogLevel(int)));
|
||||
@ -196,6 +200,8 @@ void Window::selectBIOS() {
|
||||
m_config->setQtOption("lastDirectory", QFileInfo(filename).dir().path());
|
||||
m_config->setOption("bios", filename);
|
||||
m_config->updateOption("bios");
|
||||
m_config->setOption("useBios", true);
|
||||
m_config->updateOption("useBios");
|
||||
m_controller->loadBIOS(filename);
|
||||
}
|
||||
}
|
||||
@ -255,7 +261,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();
|
||||
@ -367,6 +374,14 @@ void Window::dropEvent(QDropEvent* event) {
|
||||
m_controller->loadGame(url.path());
|
||||
}
|
||||
|
||||
void Window::exitFullScreen() {
|
||||
if (!isFullScreen()) {
|
||||
return;
|
||||
}
|
||||
showNormal();
|
||||
menuBar()->show();
|
||||
}
|
||||
|
||||
void Window::toggleFullScreen() {
|
||||
if (isFullScreen()) {
|
||||
showNormal();
|
||||
@ -405,6 +420,7 @@ void Window::gameStarted(GBAThread* context) {
|
||||
}
|
||||
#endif
|
||||
|
||||
m_hitUnimplementedBiosCall = false;
|
||||
m_fpsTimer.start();
|
||||
}
|
||||
|
||||
@ -436,6 +452,19 @@ void Window::gameFailed() {
|
||||
fail->show();
|
||||
}
|
||||
|
||||
void Window::unimplementedBiosCall(int call) {
|
||||
if (m_hitUnimplementedBiosCall) {
|
||||
return;
|
||||
}
|
||||
m_hitUnimplementedBiosCall = true;
|
||||
|
||||
QMessageBox* fail = new QMessageBox(QMessageBox::Warning, tr("Unimplemented BIOS call"),
|
||||
tr("This game uses a BIOS call that is not implemented. Please use the official BIOS for best experience."),
|
||||
QMessageBox::Ok, this, Qt::Sheet);
|
||||
fail->setAttribute(Qt::WA_DeleteOnClose);
|
||||
fail->show();
|
||||
}
|
||||
|
||||
void Window::recordFrame() {
|
||||
m_frameList.append(QDateTime::currentDateTime());
|
||||
while (m_frameList.count() > FRAME_LIST_SIZE) {
|
||||
@ -522,8 +551,23 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
quickSaveMenu->addAction(quickSave);
|
||||
}
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
fileMenu->addSeparator();
|
||||
QAction* multiWindow = new QAction(tr("New multiplayer window"), fileMenu);
|
||||
connect(multiWindow, &QAction::triggered, [this]() {
|
||||
std::shared_ptr<MultiplayerController> multiplayer = m_controller->multiplayerController();
|
||||
if (!multiplayer) {
|
||||
multiplayer = std::make_shared<MultiplayerController>();
|
||||
m_controller->setMultiplayerController(multiplayer);
|
||||
}
|
||||
Window* w2 = new Window(m_config, multiplayer->attached());
|
||||
w2->setAttribute(Qt::WA_DeleteOnClose);
|
||||
w2->loadConfig();
|
||||
w2->controller()->setMultiplayerController(multiplayer);
|
||||
w2->show();
|
||||
});
|
||||
addControlledAction(fileMenu, multiWindow, "multiWindow");
|
||||
|
||||
#ifndef Q_OS_MAC
|
||||
addControlledAction(fileMenu, fileMenu->addAction(tr("E&xit"), this, SLOT(close()), QKeySequence::Quit), "quit");
|
||||
#endif
|
||||
|
||||
@ -582,12 +626,16 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
|
||||
ConfigOption* videoSync = m_config->addOption("videoSync");
|
||||
videoSync->addBoolean(tr("Sync to &video"), emulationMenu);
|
||||
videoSync->connect([this](const QVariant& value) { m_controller->setVideoSync(value.toBool()); });
|
||||
videoSync->connect([this](const QVariant& value) {
|
||||
m_controller->setVideoSync(value.toBool());
|
||||
}, this);
|
||||
m_config->updateOption("videoSync");
|
||||
|
||||
ConfigOption* audioSync = m_config->addOption("audioSync");
|
||||
audioSync->addBoolean(tr("Sync to &audio"), emulationMenu);
|
||||
audioSync->connect([this](const QVariant& value) { m_controller->setAudioSync(value.toBool()); });
|
||||
audioSync->connect([this](const QVariant& value) {
|
||||
m_controller->setAudioSync(value.toBool());
|
||||
}, this);
|
||||
m_config->updateOption("audioSync");
|
||||
|
||||
QMenu* avMenu = menubar->addMenu(tr("Audio/&Video"));
|
||||
@ -606,17 +654,23 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
|
||||
ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio");
|
||||
lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu);
|
||||
lockAspectRatio->connect([this](const QVariant& value) { m_display->lockAspectRatio(value.toBool()); });
|
||||
lockAspectRatio->connect([this](const QVariant& value) {
|
||||
m_display->lockAspectRatio(value.toBool());
|
||||
}, this);
|
||||
m_config->updateOption("lockAspectRatio");
|
||||
|
||||
ConfigOption* resampleVideo = m_config->addOption("resampleVideo");
|
||||
resampleVideo->addBoolean(tr("Resample video"), avMenu);
|
||||
resampleVideo->connect([this](const QVariant& value) { m_display->filter(value.toBool()); });
|
||||
resampleVideo->connect([this](const QVariant& value) {
|
||||
m_display->filter(value.toBool());
|
||||
}, this);
|
||||
m_config->updateOption("resampleVideo");
|
||||
|
||||
QMenu* skipMenu = avMenu->addMenu(tr("Frame&skip"));
|
||||
ConfigOption* skip = m_config->addOption("frameskip");
|
||||
skip->connect([this](const QVariant& value) { m_controller->setFrameskip(value.toInt()); });
|
||||
skip->connect([this](const QVariant& value) {
|
||||
m_controller->setFrameskip(value.toInt());
|
||||
}, this);
|
||||
for (int i = 0; i <= 10; ++i) {
|
||||
skip->addValue(QString::number(i), i, skipMenu);
|
||||
}
|
||||
@ -626,7 +680,9 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
|
||||
QMenu* buffersMenu = avMenu->addMenu(tr("Audio buffer &size"));
|
||||
ConfigOption* buffers = m_config->addOption("audioBuffers");
|
||||
buffers->connect([this](const QVariant& value) { emit audioBufferSamplesChanged(value.toInt()); });
|
||||
buffers->connect([this](const QVariant& value) {
|
||||
emit audioBufferSamplesChanged(value.toInt());
|
||||
}, this);
|
||||
buffers->addValue(tr("512"), 512, buffersMenu);
|
||||
buffers->addValue(tr("768"), 768, buffersMenu);
|
||||
buffers->addValue(tr("1024"), 1024, buffersMenu);
|
||||
@ -638,7 +694,9 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
|
||||
QMenu* target = avMenu->addMenu("FPS target");
|
||||
ConfigOption* fpsTargetOption = m_config->addOption("fpsTarget");
|
||||
fpsTargetOption->connect([this](const QVariant& value) { emit fpsTargetChanged(value.toInt()); });
|
||||
fpsTargetOption->connect([this](const QVariant& value) {
|
||||
emit fpsTargetChanged(value.toInt());
|
||||
}, this);
|
||||
fpsTargetOption->addValue(tr("15"), 15, target);
|
||||
fpsTargetOption->addValue(tr("30"), 30, target);
|
||||
fpsTargetOption->addValue(tr("45"), 45, target);
|
||||
@ -698,22 +756,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");
|
||||
@ -730,16 +788,34 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
#endif
|
||||
|
||||
ConfigOption* skipBios = m_config->addOption("skipBios");
|
||||
skipBios->connect([this](const QVariant& value) { m_controller->setSkipBIOS(value.toBool()); });
|
||||
skipBios->connect([this](const QVariant& value) {
|
||||
m_controller->setSkipBIOS(value.toBool());
|
||||
}, this);
|
||||
|
||||
ConfigOption* useBios = m_config->addOption("useBios");
|
||||
useBios->connect([this](const QVariant& value) {
|
||||
m_controller->setUseBIOS(value.toBool());
|
||||
}, this);
|
||||
|
||||
ConfigOption* rewindEnable = m_config->addOption("rewindEnable");
|
||||
rewindEnable->connect([this](const QVariant& value) { m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt()); });
|
||||
rewindEnable->connect([this](const QVariant& value) {
|
||||
m_controller->setRewind(value.toBool(), m_config->getOption("rewindBufferCapacity").toInt(), m_config->getOption("rewindBufferInterval").toInt());
|
||||
}, this);
|
||||
|
||||
ConfigOption* rewindBufferCapacity = m_config->addOption("rewindBufferCapacity");
|
||||
rewindBufferCapacity->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt()); });
|
||||
rewindBufferCapacity->connect([this](const QVariant& value) {
|
||||
m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), value.toInt(), m_config->getOption("rewindBufferInterval").toInt());
|
||||
}, this);
|
||||
|
||||
ConfigOption* rewindBufferInterval = m_config->addOption("rewindBufferInterval");
|
||||
rewindBufferInterval->connect([this](const QVariant& value) { m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt()); });
|
||||
rewindBufferInterval->connect([this](const QVariant& value) {
|
||||
m_controller->setRewind(m_config->getOption("rewindEnable").toInt(), m_config->getOption("rewindBufferCapacity").toInt(), value.toInt());
|
||||
}, this);
|
||||
|
||||
ConfigOption* allowOpposingDirections = m_config->addOption("allowOpposingDirections");
|
||||
allowOpposingDirections->connect([this](const QVariant& value) {
|
||||
m_inputController.setAllowOpposing(value.toBool());
|
||||
}, this);
|
||||
|
||||
QMenu* other = new QMenu(tr("Other"), this);
|
||||
m_shortcutController->addMenu(other);
|
||||
@ -749,6 +825,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||
m_controller->setTurbo(false, false);
|
||||
}, QKeySequence(Qt::Key_Tab), tr("Fast Forward (held)"), "holdFastForward");
|
||||
|
||||
addControlledAction(other, other->addAction(tr("Exit fullscreen"), this, SLOT(exitFullScreen()), QKeySequence("Esc")), "exitFullscreen");
|
||||
|
||||
foreach (QAction* action, m_gameActions) {
|
||||
action->setDisabled(true);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class Window : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Window(ConfigController* config, QWidget* parent = nullptr);
|
||||
Window(ConfigController* config, int playerId = 0, QWidget* parent = nullptr);
|
||||
virtual ~Window();
|
||||
|
||||
GameController* controller() { return m_controller; }
|
||||
@ -59,6 +59,7 @@ public slots:
|
||||
void selectROM();
|
||||
void selectBIOS();
|
||||
void selectPatch();
|
||||
void exitFullScreen();
|
||||
void toggleFullScreen();
|
||||
void loadConfig();
|
||||
void saveConfig();
|
||||
@ -101,6 +102,7 @@ private slots:
|
||||
void gameStopped();
|
||||
void gameCrashed(const QString&);
|
||||
void gameFailed();
|
||||
void unimplementedBiosCall(int);
|
||||
|
||||
void recordFrame();
|
||||
void showFPS();
|
||||
@ -134,6 +136,9 @@ private:
|
||||
QList<QString> m_mruFiles;
|
||||
QMenu* m_mruMenu;
|
||||
ShortcutController* m_shortcutController;
|
||||
int m_playerId;
|
||||
|
||||
bool m_hitUnimplementedBiosCall;
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
VideoView* m_videoView;
|
||||
|
@ -5,9 +5,9 @@ if (SDL_VERSION EQUAL "2")
|
||||
include(FindPkgConfig)
|
||||
pkg_search_module(SDL2 sdl2)
|
||||
if (SDL2_FOUND)
|
||||
set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS} CACHE INTERNAL "")
|
||||
set(SDL_LIBRARY ${SDL2_LIBRARIES} CACHE INTERNAL "")
|
||||
set(SDLMAIN_LIBRARY "" CACHE INTERNAL "")
|
||||
set(SDL_INCLUDE_DIR ${SDL2_INCLUDE_DIRS})
|
||||
set(SDL_LIBRARY ${SDL2_LIBRARIES})
|
||||
set(SDLMAIN_LIBRARY)
|
||||
link_directories(${SDL2_LIBDIR})
|
||||
set(SDL_VERSION_DEBIAN "2-2.0-0")
|
||||
endif()
|
||||
@ -17,6 +17,7 @@ if(SDL_VERSION EQUAL "1.2" OR NOT SDL2_FOUND)
|
||||
find_package(SDL 1.2)
|
||||
set(SDL_VERSION "1.2" PARENT_SCOPE)
|
||||
set(SDL_VERSION_DEBIAN "1.2debian")
|
||||
set(USE_PIXMAN ON)
|
||||
endif()
|
||||
|
||||
if (NOT SDL2_FOUND AND NOT SDL_FOUND)
|
||||
@ -24,12 +25,21 @@ if (NOT SDL2_FOUND AND NOT SDL_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_feature(USE_PIXMAN "pixman-1")
|
||||
if(USE_PIXMAN)
|
||||
add_definitions(-DUSE_PIXMAN)
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libpixman-1.0" PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libsdl${SDL_VERSION_DEBIAN}" PARENT_SCOPE)
|
||||
|
||||
file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sdl-*.c)
|
||||
set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY})
|
||||
set(PLATFORM_LIBRARY ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${PIXMAN-1_LIBRARIES})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/platform/sdl ${SDL_INCLUDE_DIR})
|
||||
|
||||
set(SDL_INCLUDE_DIR "${SDL_INCLUDE_DIR}" PARENT_SCOPE)
|
||||
set(SDL_LIBRARY "${SDL_LIBRARY}" PARENT_SCOPE)
|
||||
|
||||
set(MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/main.c)
|
||||
|
||||
if(BUILD_RASPI)
|
||||
@ -38,11 +48,14 @@ 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()
|
||||
|
||||
if(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL)
|
||||
if(BUILD_PANDORA)
|
||||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/pandora-sdl.c)
|
||||
elseif(BUILD_BBB OR BUILD_RASPI OR NOT BUILD_GL)
|
||||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/sw-sdl.c)
|
||||
else()
|
||||
list(APPEND MAIN_SRC ${CMAKE_SOURCE_DIR}/src/platform/sdl/gl-sdl.c)
|
||||
@ -52,6 +65,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)
|
||||
|
@ -136,7 +136,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
|
||||
while (context->state < THREAD_EXITING) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &renderer->events, &event);
|
||||
GBASDLHandleEvent(context, &renderer->player, &event);
|
||||
}
|
||||
|
||||
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
|
||||
|
@ -29,6 +29,26 @@ static const GLint _glTexCoords[] = {
|
||||
};
|
||||
#endif
|
||||
|
||||
static void _doViewport(int w, int h, struct SDLSoftwareRenderer* renderer) {
|
||||
int drawW = w;
|
||||
int drawH = h;
|
||||
if (renderer->lockAspectRatio) {
|
||||
if (w * 2 > h * 3) {
|
||||
drawW = h * 3 / 2;
|
||||
} else if (w * 2 < h * 3) {
|
||||
drawH = w * 2 / 3;
|
||||
}
|
||||
}
|
||||
glViewport((w - drawW) / 2, (h - drawH) / 2, drawW, drawH);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_GL_SwapWindow(renderer->window);
|
||||
#else
|
||||
SDL_GL_SwapBuffers();
|
||||
#endif
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
#ifndef COLOR_16_BIT
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
@ -45,11 +65,11 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
renderer->window = SDL_CreateWindow(PROJECT_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->events.fullscreen));
|
||||
renderer->window = SDL_CreateWindow(PROJECT_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
|
||||
SDL_GL_CreateContext(renderer->window);
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
renderer->events.window = renderer->window;
|
||||
renderer->player.window = renderer->window;
|
||||
#else
|
||||
SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1);
|
||||
#ifdef COLOR_16_BIT
|
||||
@ -59,7 +79,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);
|
||||
@ -86,9 +106,7 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
|
||||
#endif
|
||||
|
||||
|
||||
glViewport(0, 0, renderer->viewportWidth, renderer->viewportHeight);
|
||||
|
||||
_doViewport(renderer->viewportWidth, renderer->viewportHeight, renderer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -105,13 +123,13 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
glOrtho(0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, 0, 0, 1);
|
||||
while (context->state < THREAD_EXITING) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &renderer->events, &event);
|
||||
GBASDLHandleEvent(context, &renderer->player, &event);
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
// Event handling can change the size of the screen
|
||||
if (renderer->events.windowUpdated) {
|
||||
if (renderer->player.windowUpdated) {
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
glViewport(0, 0, renderer->viewportWidth, renderer->viewportHeight);
|
||||
renderer->events.windowUpdated = 0;
|
||||
_doViewport(renderer->viewportWidth, renderer->viewportHeight, renderer);
|
||||
renderer->player.windowUpdated = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -142,5 +160,5 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
}
|
||||
|
||||
void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
|
||||
UNUSED(renderer);
|
||||
free(renderer->d.outputBuffer);
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ int main(int argc, char** argv) {
|
||||
struct GBAOptions opts = {
|
||||
.width = VIDEO_HORIZONTAL_PIXELS,
|
||||
.height = VIDEO_VERTICAL_PIXELS,
|
||||
.useBios = true,
|
||||
.rewindEnable = true,
|
||||
.audioBuffers = 512,
|
||||
.videoSync = false,
|
||||
@ -71,10 +72,13 @@ int main(int argc, char** argv) {
|
||||
renderer.viewportWidth = opts.width;
|
||||
renderer.viewportHeight = opts.height;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
renderer.events.fullscreen = opts.fullscreen;
|
||||
renderer.events.windowUpdated = 0;
|
||||
renderer.player.fullscreen = opts.fullscreen;
|
||||
renderer.player.windowUpdated = 0;
|
||||
#endif
|
||||
renderer.ratio = graphicsOpts.multiplier;
|
||||
if (renderer.ratio == 0) {
|
||||
renderer.ratio = 1;
|
||||
}
|
||||
|
||||
renderer.lockAspectRatio = opts.lockAspectRatio;
|
||||
renderer.filter = opts.resampleVideo;
|
||||
@ -99,10 +103,12 @@ int main(int argc, char** argv) {
|
||||
renderer.audio.samples = context.audioBuffers;
|
||||
GBASDLInitAudio(&renderer.audio, &context);
|
||||
|
||||
renderer.events.bindings = &inputMap;
|
||||
renderer.player.bindings = &inputMap;
|
||||
GBASDLInitBindings(&inputMap);
|
||||
GBASDLInitEvents(&renderer.events);
|
||||
GBASDLEventsLoadConfig(&renderer.events, GBAConfigGetInput(&config));
|
||||
GBASDLAttachPlayer(&renderer.events, &renderer.player);
|
||||
GBASDLPlayerLoadConfig(&renderer.player, GBAConfigGetInput(&config));
|
||||
context.overrides = GBAConfigGetOverrides(&config);
|
||||
|
||||
int didFail = 0;
|
||||
@ -139,8 +145,6 @@ static bool _GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
}
|
||||
|
||||
static void _GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
|
||||
free(renderer->d.outputBuffer);
|
||||
|
||||
GBASDLDeinitEvents(&renderer->events);
|
||||
GBASDLDeinitAudio(&renderer->audio);
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
|
@ -31,10 +31,15 @@
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#ifdef USE_PIXMAN
|
||||
#include <pixman.h>
|
||||
#endif
|
||||
|
||||
struct SDLSoftwareRenderer {
|
||||
struct GBAVideoSoftwareRenderer d;
|
||||
struct GBASDLAudio audio;
|
||||
struct GBASDLEvents events;
|
||||
struct GBASDLPlayer player;
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_Window* window;
|
||||
@ -55,6 +60,11 @@ struct SDLSoftwareRenderer {
|
||||
GLuint tex;
|
||||
#endif
|
||||
|
||||
#ifdef USE_PIXMAN
|
||||
pixman_image_t* pix;
|
||||
pixman_image_t* screenpix;
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_RASPI
|
||||
EGLDisplay display;
|
||||
EGLSurface surface;
|
||||
@ -68,6 +78,12 @@ struct SDLSoftwareRenderer {
|
||||
GLuint texLocation;
|
||||
GLuint positionLocation;
|
||||
#endif
|
||||
|
||||
#ifdef BUILD_PANDORA
|
||||
int fb;
|
||||
int odd;
|
||||
void* base[2];
|
||||
#endif
|
||||
};
|
||||
|
||||
bool GBASDLInit(struct SDLSoftwareRenderer* renderer);
|
||||
|
109
src/platform/sdl/pandora-sdl.c
Normal file
109
src/platform/sdl/pandora-sdl.c
Normal file
@ -0,0 +1,109 @@
|
||||
/* 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 "main.h"
|
||||
|
||||
#include "gba/supervisor/thread.h"
|
||||
|
||||
#include <linux/omapfb.h>
|
||||
#include <linux/fb.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
SDL_SetVideoMode(800, 480, 16, SDL_FULLSCREEN);
|
||||
|
||||
renderer->odd = 0;
|
||||
renderer->fb = open("/dev/fb1", O_RDWR);
|
||||
if (renderer->fb < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct omapfb_plane_info plane;
|
||||
struct omapfb_mem_info mem;
|
||||
if (ioctl(renderer->fb, OMAPFB_QUERY_PLANE, &plane) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (ioctl(renderer->fb, OMAPFB_QUERY_MEM, &mem) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plane.enabled) {
|
||||
plane.enabled = 0;
|
||||
ioctl(renderer->fb, OMAPFB_SETUP_PLANE, &plane);
|
||||
}
|
||||
|
||||
mem.size = VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4;
|
||||
ioctl(renderer->fb, OMAPFB_SETUP_MEM, &mem);
|
||||
|
||||
plane.enabled = 1;
|
||||
plane.pos_x = 40;
|
||||
plane.pos_y = 0;
|
||||
plane.out_width = 720;
|
||||
plane.out_height = 480;
|
||||
ioctl(renderer->fb, OMAPFB_SETUP_PLANE, &plane);
|
||||
|
||||
struct fb_var_screeninfo info;
|
||||
ioctl(renderer->fb, FBIOGET_VSCREENINFO, &info);
|
||||
info.xres = VIDEO_HORIZONTAL_PIXELS;
|
||||
info.yres = VIDEO_VERTICAL_PIXELS;
|
||||
info.xres_virtual = VIDEO_HORIZONTAL_PIXELS;
|
||||
info.yres_virtual = VIDEO_VERTICAL_PIXELS * 2;
|
||||
info.bits_per_pixel = 16;
|
||||
ioctl(renderer->fb, FBIOPUT_VSCREENINFO, &info);
|
||||
|
||||
renderer->odd = 0;
|
||||
renderer->base[0] = mmap(0, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4, PROT_READ | PROT_WRITE, MAP_SHARED, renderer->fb, 0);
|
||||
renderer->base[1] = (uint16_t*) renderer->base[0] + VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS;
|
||||
|
||||
renderer->d.outputBuffer = renderer->base[0];
|
||||
renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer) {
|
||||
SDL_Event event;
|
||||
|
||||
while (context->state < THREAD_EXITING) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &renderer->player, &event);
|
||||
}
|
||||
|
||||
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
|
||||
int arg = 0;
|
||||
ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg);
|
||||
|
||||
struct fb_var_screeninfo info;
|
||||
ioctl(renderer->fb, FBIOGET_VSCREENINFO, &info);
|
||||
info.yoffset = VIDEO_VERTICAL_PIXELS * renderer->odd;
|
||||
ioctl(renderer->fb, FBIOPAN_DISPLAY, &info);
|
||||
|
||||
renderer->odd = !renderer->odd;
|
||||
renderer->d.outputBuffer = renderer->base[renderer->odd];
|
||||
}
|
||||
GBASyncWaitFrameEnd(&context->sync);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
|
||||
munmap(renderer->base[0], VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 4);
|
||||
|
||||
struct omapfb_plane_info plane;
|
||||
struct omapfb_mem_info mem;
|
||||
ioctl(renderer->fb, OMAPFB_QUERY_PLANE, &plane);
|
||||
ioctl(renderer->fb, OMAPFB_QUERY_MEM, &mem);
|
||||
|
||||
mem.size = 0;
|
||||
ioctl(renderer->fb, OMAPFB_SETUP_MEM, &mem);
|
||||
|
||||
plane.enabled = 0;
|
||||
plane.pos_x = 0;
|
||||
plane.pos_y = 0;
|
||||
plane.out_width = 0;
|
||||
plane.out_height = 0;
|
||||
ioctl(renderer->fb, OMAPFB_SETUP_PLANE, &plane);
|
||||
|
||||
close(renderer->fb);
|
||||
}
|
@ -31,7 +31,13 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
context->drift = 0.f;
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
context->deviceId = SDL_OpenAudioDevice(0, 0, &context->desiredSpec, &context->obtainedSpec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
||||
if (context->deviceId == 0) {
|
||||
#else
|
||||
if (SDL_OpenAudio(&context->desiredSpec, &context->obtainedSpec) < 0) {
|
||||
#endif
|
||||
GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system");
|
||||
return false;
|
||||
}
|
||||
@ -43,25 +49,43 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
|
||||
threadContext->audioBuffers = context->samples * 2;
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_PauseAudioDevice(context->deviceId, 0);
|
||||
#else
|
||||
SDL_PauseAudio(0);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASDLDeinitAudio(struct GBASDLAudio* context) {
|
||||
UNUSED(context);
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_PauseAudioDevice(context->deviceId, 1);
|
||||
SDL_CloseAudioDevice(context->deviceId);
|
||||
#else
|
||||
SDL_PauseAudio(1);
|
||||
SDL_CloseAudio();
|
||||
#endif
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
void GBASDLPauseAudio(struct GBASDLAudio* context) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_PauseAudioDevice(context->deviceId, 1);
|
||||
#else
|
||||
UNUSED(context);
|
||||
SDL_PauseAudio(1);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void GBASDLResumeAudio(struct GBASDLAudio* context) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_PauseAudioDevice(context->deviceId, 0);
|
||||
#else
|
||||
UNUSED(context);
|
||||
SDL_PauseAudio(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _GBASDLAudioCallback(void* context, Uint8* data, int len) {
|
||||
|
@ -25,6 +25,9 @@ struct GBASDLAudio {
|
||||
#if RESAMPLE_LIBRARY == RESAMPLE_NN
|
||||
float drift;
|
||||
#endif
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_AudioDeviceID deviceId;
|
||||
#endif
|
||||
|
||||
struct GBAThread* thread;
|
||||
};
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include "gba/serialize.h"
|
||||
#include "gba/video.h"
|
||||
#include "gba/renderers/video-software.h"
|
||||
#include "util/configuration.h"
|
||||
#include "util/vfs.h"
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0) && defined(__APPLE__)
|
||||
@ -19,23 +20,68 @@
|
||||
#define GUI_MOD KMOD_CTRL
|
||||
#endif
|
||||
|
||||
static int _openContexts = 0;
|
||||
|
||||
bool GBASDLInitEvents(struct GBASDLEvents* context) {
|
||||
if (!_openContexts && SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
|
||||
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
|
||||
return false;
|
||||
}
|
||||
++_openContexts;
|
||||
|
||||
SDL_JoystickEventState(SDL_ENABLE);
|
||||
context->joystick = SDL_JoystickOpen(0);
|
||||
int nJoysticks = SDL_NumJoysticks();
|
||||
if (nJoysticks > 0) {
|
||||
context->nJoysticks = nJoysticks;
|
||||
context->joysticks = calloc(context->nJoysticks, sizeof(SDL_Joystick*));
|
||||
size_t i;
|
||||
for (i = 0; i < context->nJoysticks; ++i) {
|
||||
context->joysticks[i] = SDL_JoystickOpen(i);
|
||||
}
|
||||
} else {
|
||||
context->nJoysticks = 0;
|
||||
context->joysticks = 0;
|
||||
}
|
||||
|
||||
context->playersAttached = 0;
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < MAX_PLAYERS; ++i) {
|
||||
context->preferredJoysticks[i] = 0;
|
||||
context->joysticksClaimed[i] = SIZE_MAX;
|
||||
}
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASDLDeinitEvents(struct GBASDLEvents* context) {
|
||||
size_t i;
|
||||
for (i = 0; i < context->nJoysticks; ++i) {
|
||||
SDL_JoystickClose(context->joysticks[i]);
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
|
||||
void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) {
|
||||
context->preferredJoysticks[0] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 0);
|
||||
context->preferredJoysticks[1] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 1);
|
||||
context->preferredJoysticks[2] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 2);
|
||||
context->preferredJoysticks[3] = GBAInputGetPreferredDevice(config, SDL_BINDING_BUTTON, 3);
|
||||
}
|
||||
|
||||
void GBASDLInitBindings(struct GBAInputMap* inputMap) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
#ifdef BUILD_PANDORA
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_PAGEDOWN, GBA_KEY_A);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_END, GBA_KEY_B);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RSHIFT, GBA_KEY_L);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RCTRL, GBA_KEY_R);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LALT, GBA_KEY_START);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LCTRL, GBA_KEY_SELECT);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_UP, GBA_KEY_UP);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_DOWN, GBA_KEY_DOWN);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_LEFT, GBA_KEY_LEFT);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDLK_RIGHT, GBA_KEY_RIGHT);
|
||||
#elif SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDL_SCANCODE_X, GBA_KEY_A);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDL_SCANCODE_Z, GBA_KEY_B);
|
||||
GBAInputBindKey(inputMap, SDL_BINDING_KEY, SDL_SCANCODE_A, GBA_KEY_L);
|
||||
@ -76,29 +122,91 @@ void GBASDLInitBindings(struct GBAInputMap* inputMap) {
|
||||
GBAInputBindAxis(inputMap, SDL_BINDING_BUTTON, 1, &description);
|
||||
}
|
||||
|
||||
void GBASDLEventsLoadConfig(struct GBASDLEvents* context, const struct Configuration* config) {
|
||||
GBAInputMapLoad(context->bindings, SDL_BINDING_KEY, config);
|
||||
GBAInputMapLoad(context->bindings, SDL_BINDING_BUTTON, config);
|
||||
bool GBASDLAttachPlayer(struct GBASDLEvents* events, struct GBASDLPlayer* player) {
|
||||
player->joystick = 0;
|
||||
player->joystickIndex = SIZE_MAX;
|
||||
|
||||
if (events->playersAttached >= MAX_PLAYERS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
player->playerId = events->playersAttached;
|
||||
size_t firstUnclaimed = SIZE_MAX;
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < events->nJoysticks; ++i) {
|
||||
bool claimed = false;
|
||||
|
||||
int p;
|
||||
for (p = 0; p < events->playersAttached; ++p) {
|
||||
if (events->joysticksClaimed[p] == i) {
|
||||
claimed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (claimed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstUnclaimed == SIZE_MAX) {
|
||||
firstUnclaimed = i;
|
||||
}
|
||||
|
||||
const char* joystickName;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
joystickName = SDL_JoystickName(events->joysticks[i]);
|
||||
#else
|
||||
joystickName = SDL_JoystickName(SDL_JoystickIndex(events->joysticks[i]));
|
||||
#endif
|
||||
if (events->preferredJoysticks[player->playerId] && strcmp(events->preferredJoysticks[player->playerId], joystickName) == 0) {
|
||||
player->joystickIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (player->joystickIndex == SIZE_MAX && firstUnclaimed != SIZE_MAX) {
|
||||
player->joystickIndex = firstUnclaimed;
|
||||
}
|
||||
|
||||
if (player->joystickIndex != SIZE_MAX) {
|
||||
player->joystick = events->joysticks[player->joystickIndex];
|
||||
events->joysticksClaimed[player->playerId] = player->joystickIndex;
|
||||
}
|
||||
|
||||
++events->playersAttached;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GBASDLDeinitEvents(struct GBASDLEvents* context) {
|
||||
SDL_JoystickClose(context->joystick);
|
||||
|
||||
--_openContexts;
|
||||
if (!_openContexts) {
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
void GBASDLPlayerLoadConfig(struct GBASDLPlayer* context, const struct Configuration* config) {
|
||||
GBAInputMapLoad(context->bindings, SDL_BINDING_KEY, 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 GBASDLPlayerChangeJoystick(struct GBASDLEvents* events, struct GBASDLPlayer* player, size_t index) {
|
||||
if (player->playerId > MAX_PLAYERS || index >= events->nJoysticks) {
|
||||
return;
|
||||
}
|
||||
events->joysticksClaimed[player->playerId] = index;
|
||||
player->joystickIndex = index;
|
||||
player->joystick = events->joysticks[index];
|
||||
}
|
||||
|
||||
static void _pauseAfterFrame(struct GBAThread* context) {
|
||||
context->frameCallback = 0;
|
||||
GBAThreadPauseFromThread(context);
|
||||
}
|
||||
|
||||
static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_KeyboardEvent* event) {
|
||||
static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_KeyboardEvent* event) {
|
||||
enum GBAKey key = GBA_KEY_NONE;
|
||||
if (!event->keysym.mod) {
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
#if !defined(BUILD_PANDORA) && SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
key = GBAInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.scancode);
|
||||
#else
|
||||
key = GBAInputMapKey(sdlContext->bindings, SDL_BINDING_KEY, event->keysym.sym);
|
||||
@ -140,14 +248,11 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
||||
GBARewind(context, 10);
|
||||
GBAThreadContinue(context);
|
||||
return;
|
||||
#ifdef BUILD_PANDORA
|
||||
case SDLK_ESCAPE:
|
||||
GBAThreadInterrupt(context);
|
||||
if (context->gba->rr) {
|
||||
GBARRStopPlaying(context->gba->rr);
|
||||
GBARRStopRecording(context->gba->rr);
|
||||
}
|
||||
GBAThreadContinue(context);
|
||||
GBAThreadEnd(context);
|
||||
return;
|
||||
#endif
|
||||
default:
|
||||
if ((event->keysym.mod & GUI_MOD) && (event->keysym.mod & GUI_MOD) == event->keysym.mod) {
|
||||
switch (event->keysym.sym) {
|
||||
@ -169,31 +274,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;
|
||||
}
|
||||
@ -240,7 +320,7 @@ static void _GBASDLHandleKeypress(struct GBAThread* context, struct GBASDLEvents
|
||||
}
|
||||
}
|
||||
|
||||
static void _GBASDLHandleJoyButton(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_JoyButtonEvent* event) {
|
||||
static void _GBASDLHandleJoyButton(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_JoyButtonEvent* event) {
|
||||
enum GBAKey key = 0;
|
||||
key = GBAInputMapKey(sdlContext->bindings, SDL_BINDING_BUTTON, event->button);
|
||||
if (key == GBA_KEY_NONE) {
|
||||
@ -274,7 +354,7 @@ static void _GBASDLHandleJoyHat(struct GBAThread* context, const struct SDL_JoyH
|
||||
context->activeKeys |= key;
|
||||
}
|
||||
|
||||
static void _GBASDLHandleJoyAxis(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_JoyAxisEvent* event) {
|
||||
static void _GBASDLHandleJoyAxis(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_JoyAxisEvent* event) {
|
||||
int keys = context->activeKeys;
|
||||
|
||||
keys = GBAInputClearAxis(sdlContext->bindings, SDL_BINDING_BUTTON, event->axis, keys);
|
||||
@ -287,7 +367,7 @@ static void _GBASDLHandleJoyAxis(struct GBAThread* context, struct GBASDLEvents*
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
static void _GBASDLHandleWindowEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const struct SDL_WindowEvent* event) {
|
||||
static void _GBASDLHandleWindowEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const struct SDL_WindowEvent* event) {
|
||||
UNUSED(context);
|
||||
switch (event->event) {
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
@ -297,7 +377,7 @@ static void _GBASDLHandleWindowEvent(struct GBAThread* context, struct GBASDLEve
|
||||
}
|
||||
#endif
|
||||
|
||||
void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event) {
|
||||
void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const union SDL_Event* event) {
|
||||
switch (event->type) {
|
||||
case SDL_QUIT:
|
||||
GBAThreadEnd(context);
|
||||
|
@ -12,15 +12,27 @@
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#define SDL_BINDING_KEY 0x53444C4B
|
||||
#define SDL_BINDING_BUTTON 0x53444C42
|
||||
#define SDL_BINDING_KEY 0x53444C4BU
|
||||
#define SDL_BINDING_BUTTON 0x53444C42U
|
||||
|
||||
#define MAX_PLAYERS 4
|
||||
|
||||
struct GBAVideoSoftwareRenderer;
|
||||
struct Configuration;
|
||||
|
||||
struct GBASDLEvents {
|
||||
SDL_Joystick** joysticks;
|
||||
size_t nJoysticks;
|
||||
const char* preferredJoysticks[MAX_PLAYERS];
|
||||
int playersAttached;
|
||||
size_t joysticksClaimed[MAX_PLAYERS];
|
||||
};
|
||||
|
||||
struct GBASDLPlayer {
|
||||
size_t playerId;
|
||||
struct GBAInputMap* bindings;
|
||||
SDL_Joystick* joystick;
|
||||
size_t joystickIndex;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_Window* window;
|
||||
int fullscreen;
|
||||
@ -31,9 +43,13 @@ struct GBASDLEvents {
|
||||
bool GBASDLInitEvents(struct GBASDLEvents*);
|
||||
void GBASDLDeinitEvents(struct GBASDLEvents*);
|
||||
|
||||
void GBASDLInitBindings(struct GBAInputMap* inputMap);
|
||||
bool GBASDLAttachPlayer(struct GBASDLEvents*, struct GBASDLPlayer*);
|
||||
void GBASDLEventsLoadConfig(struct GBASDLEvents*, const struct Configuration*);
|
||||
void GBASDLPlayerChangeJoystick(struct GBASDLEvents*, struct GBASDLPlayer*, size_t index);
|
||||
|
||||
void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLEvents* sdlContext, const union SDL_Event* event);
|
||||
void GBASDLInitBindings(struct GBAInputMap* inputMap);
|
||||
void GBASDLPlayerLoadConfig(struct GBASDLPlayer*, const struct Configuration*);
|
||||
|
||||
void GBASDLHandleEvent(struct GBAThread* context, struct GBASDLPlayer* sdlContext, const union SDL_Event* event);
|
||||
|
||||
#endif
|
||||
|
@ -18,9 +18,9 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
renderer->window = SDL_CreateWindow(PROJECT_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->events.fullscreen));
|
||||
renderer->window = SDL_CreateWindow(PROJECT_NAME, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, renderer->viewportWidth, renderer->viewportHeight, SDL_WINDOW_OPENGL | (SDL_WINDOW_FULLSCREEN_DESKTOP * renderer->player.fullscreen));
|
||||
SDL_GetWindowSize(renderer->window, &renderer->viewportWidth, &renderer->viewportHeight);
|
||||
renderer->events.window = renderer->window;
|
||||
renderer->player.window = renderer->window;
|
||||
renderer->sdlRenderer = SDL_CreateRenderer(renderer->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
@ -40,14 +40,32 @@ bool GBASDLInit(struct SDLSoftwareRenderer* renderer) {
|
||||
|
||||
if (renderer->ratio == 1) {
|
||||
renderer->d.outputBuffer = surface->pixels;
|
||||
#ifdef COLOR_16_BIT
|
||||
renderer->d.outputBufferStride = surface->pitch / 2;
|
||||
#else
|
||||
renderer->d.outputBufferStride = surface->pitch / 4;
|
||||
#endif
|
||||
renderer->d.outputBufferStride = surface->pitch / BYTES_PER_PIXEL;
|
||||
} else {
|
||||
renderer->d.outputBuffer = malloc(240 * 160 * BYTES_PER_PIXEL);
|
||||
renderer->d.outputBufferStride = 240;
|
||||
#ifdef USE_PIXMAN
|
||||
renderer->d.outputBuffer = malloc(VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL);
|
||||
renderer->d.outputBufferStride = VIDEO_HORIZONTAL_PIXELS;
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
pixman_format_code_t format = PIXMAN_r5g6b5;
|
||||
#else
|
||||
pixman_format_code_t format = PIXMAN_x1b5g5r5;
|
||||
#endif
|
||||
#else
|
||||
pixman_format_code_t format = PIXMAN_x8b8g8r8;
|
||||
#endif
|
||||
renderer->pix = pixman_image_create_bits(format, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS,
|
||||
renderer->d.outputBuffer, renderer->d.outputBufferStride * BYTES_PER_PIXEL);
|
||||
renderer->screenpix = pixman_image_create_bits(format, renderer->viewportWidth, renderer->viewportHeight, surface->pixels, surface->pitch);
|
||||
|
||||
pixman_transform_t transform;
|
||||
pixman_transform_init_identity(&transform);
|
||||
pixman_transform_scale(0, &transform, pixman_int_to_fixed(renderer->ratio), pixman_int_to_fixed(renderer->ratio));
|
||||
pixman_image_set_transform(renderer->pix, &transform);
|
||||
pixman_image_set_filter(renderer->pix, PIXMAN_FILTER_NEAREST, 0, 0);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -62,7 +80,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
|
||||
while (context->state < THREAD_EXITING) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
GBASDLHandleEvent(context, &renderer->events, &event);
|
||||
GBASDLHandleEvent(context, &renderer->player, &event);
|
||||
}
|
||||
|
||||
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
|
||||
@ -72,6 +90,13 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
SDL_RenderPresent(renderer->sdlRenderer);
|
||||
SDL_LockTexture(renderer->tex, 0, (void**) &renderer->d.outputBuffer, &renderer->d.outputBufferStride);
|
||||
renderer->d.outputBufferStride /= BYTES_PER_PIXEL;
|
||||
#else
|
||||
#ifdef USE_PIXMAN
|
||||
if (renderer->ratio > 1) {
|
||||
pixman_image_composite32(PIXMAN_OP_SRC, renderer->pix, 0, renderer->screenpix,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
renderer->viewportWidth, renderer->viewportHeight);
|
||||
}
|
||||
#else
|
||||
switch (renderer->ratio) {
|
||||
#if defined(__ARM_NEON) && COLOR_16_BIT
|
||||
@ -87,6 +112,7 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
SDL_UnlockSurface(surface);
|
||||
SDL_Flip(surface);
|
||||
SDL_LockSurface(surface);
|
||||
@ -97,5 +123,15 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
|
||||
}
|
||||
|
||||
void GBASDLDeinit(struct SDLSoftwareRenderer* renderer) {
|
||||
UNUSED(renderer);
|
||||
if (renderer->ratio > 1) {
|
||||
free(renderer->d.outputBuffer);
|
||||
}
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_Surface* surface = SDL_GetVideoSurface();
|
||||
SDL_UnlockSurface(surface);
|
||||
#ifdef USE_PIXMAN
|
||||
pixman_image_unref(renderer->pix);
|
||||
pixman_image_unref(renderer->screenpix);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define UNUSED(V) (void)(V)
|
||||
|
@ -23,6 +23,7 @@ typedef SOCKET Socket;
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#define INVALID_SOCKET (-1)
|
||||
|
@ -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;
|
||||
|
139
src/util/vfs/vfs-mem.c
Normal file
139
src/util/vfs/vfs-mem.c
Normal file
@ -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
Block a user