Merge branch 'master' into feature/sio-dolphin

Conflicts:
	CMakeLists.txt
This commit is contained in:
Jeffrey Pfau 2015-03-25 21:59:18 -07:00
commit 8176a3aad2
83 changed files with 5125 additions and 1124 deletions

11
CHANGES
View File

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

View File

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

View File

@ -1 +0,0 @@
IDI_ICON1 ICON DISCARDABLE "mgba.ico"

28
res/mgba.rc.in Normal file
View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]) {

View File

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

View File

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

View File

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

View File

@ -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
View 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
View 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
View 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
View 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*);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -21,6 +21,7 @@ struct GBAConfig {
struct GBAOptions {
char* bios;
bool skipBios;
bool useBios;
int logLevel;
int frameskip;
bool rewindEnable;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ struct GBAArguments {
char* patch;
char* cheatsFile;
bool dirmode;
char* movie;
enum DebuggerType debuggerType;
bool debugAtStart;

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}

View 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

View File

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

View File

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

View File

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

View File

@ -23,6 +23,7 @@ public:
private slots:
void setLuminanceValue(int);
void luminanceValueChanged(int);
private:
Ui::SensorView m_ui;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#define UNUSED(V) (void)(V)

View File

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

View File

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

View File

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

View File

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

View File

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