all repos — mgba @ 50402c830729f2ba5a6fc3e6facfd8b258f7f97d

mGBA Game Boy Advance Emulator

Merge branch 'master' into feature/thread-proxy-renderer
Jeffrey Pfau jeffrey@endrift.com
Thu, 15 Oct 2015 18:41:56 -0700
commit

50402c830729f2ba5a6fc3e6facfd8b258f7f97d

parent

c14da05d8dca225010677643c32fea5c0ac8517a

107 files changed, 3655 insertions(+), 803 deletions(-)

jump to
A .travis-deps.sh

@@ -0,0 +1,19 @@

+#!/bin/sh +if [ $TRAVIS_OS_NAME = "osx" ]; then + brew update + brew install qt5 ffmpeg imagemagick sdl2 libzip libpng +else + sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y + sudo add-apt-repository ppa:zoogie/sdl2-snapshots -y + sudo add-apt-repository ppa:immerrr-k/qt5-backport -y + sudo add-apt-repository ppa:spvkgn/ffmpeg+mpv -y + sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test + sudo apt-get update -qq + sudo apt-get purge cmake -qq + sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev \ + g++-4.8 libpng-dev libsdl2-dev libzip-dev qtbase5-dev \ + libqt5opengl5-dev qtmultimedia5-dev libavcodec-dev \ + libavutil-dev libavformat-dev libavresample-dev libswscale-dev + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.8 100 + sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 100 +fi
M .travis.yml.travis.yml

@@ -1,13 +1,18 @@

+os: + - linux + - osx + +env: + - CMAKE_PREFIX_PATH=/usr/local/opt/qt5 + language: c compiler: - gcc - clang +sudo: required + before_install: - - sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y - - sudo apt-add-repository ppa:zoogie/sdl2-snapshots -y - - sudo apt-get update -qq - - sudo apt-get purge cmake -qq - - sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev + - ./.travis-deps.sh script: mkdir build && cd build && cmake .. && make
M CHANGESCHANGES

@@ -1,4 +1,6 @@

0.4.0: (Future) +Features: + - I/O viewer Bugfixes: - Qt: Windows no longer spawn in the top left on first launch - Qt: Fix install path of XDG desktop file with DESTDIR

@@ -6,6 +8,19 @@ - Qt: Fix drag and drop on Windows

- Qt: Reenable double buffering, as disabling it broke some Windows configs - GBA Video: Start on the scanline BIOS finishes on if no BIOS is loaded - GBA: Deinit savegame when unloading a ROM + - GBA: Fix BIOS check on big endian + - Libretro: Fix a memory leak with the render buffer + - GBA Audio: Fix 8-bit writes to audio channel 3 and 4 registers + - GBA Audio: Fix audio channels being silenced at the wrong time + - VFS: Fix return values of VFileFILE.read and .write + - Util: Fix PowerPC PNG read/write pixel order + - GBA Video: Fix edge case with sprite blend modes and semitransparency + - GBA Video: Fix objwin and blending interaction on sprites + - GBA Video: Fix OBJ semitransparency improperly interacting with other blending ops + - GBA: Fix autodetect problems with some bad dumps of Super Mario Advance 2 + - GBA Memory: Fix bad BIOS Load16 on big endian + - GBA Memory: Fix bad Load8 on big endian + - ARM7: Fix instruction decoding of Thumb shifts Misc: - Qt: Window size command line options are now supported - Qt: Increase usability of key mapper

@@ -17,6 +32,17 @@ - GBA: Attempting to save a screenshot-style savestate should be allowed without libpng

- GBA: Better memory handling with PNG savestates - GBA Audio: Allow GBAAVStream to have no video callback - ARM7: Force disable LTO on two files to work around a GCC bug + - Libretro: Use anonymous memory mappers for large blocks of memory + - Qt: Add 'Apply' button to settings window + - Qt: Prevent savestate window from opening while in multiplayer + - Qt: Disable menu items in multiplayer that don't make sense to have enabled + - Qt: Dropping multiplayer windows works more cleanly now + - GBA BIOS: Implement RegisterRamReset for SIO registers + - All: Reset next event to cycles instead of zero to interrupt + - GBA Video: Remove lastHblank, as it is implied + - GBA: Check for cycle count being too high + - GBA Config: Add "override" layer for better one-time configuration + - SDL: Allow GBASDLAudio to be used without a thread context 0.3.0: (2015-08-16) Features:
M CMakeLists.txtCMakeLists.txt

@@ -147,32 +147,6 @@

list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-fd.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/posix/*.c) source_group("POSIX-specific code" FILES ${OS_SRC}) -elseif(WII) - set(M_LIBRARY m) - list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) - add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5 -DUSE_VFS_FILE) - include_directories(${CMAKE_BINARY_DIR}) - file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/wii/*.c) - list(APPEND OS_LIB wiiuse bte fat ogc) - source_group("Wii-specific code" FILES ${OS_SRC}) -elseif(3DS) - set(M_LIBRARY m) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format") - add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5) - if (${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw IS_NEWER_THAN ${CMAKE_BINARY_DIR}/font.c) - execute_process(COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) - endif() - include_directories(${CMAKE_BINARY_DIR}) - list(APPEND OS_LIB sf2d ctru) - file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/3ds/*.c ${CMAKE_BINARY_DIR}/font.c) - set(USE_VFS_3DS ON) - if(USE_VFS_3DS) - add_definitions(-DUSE_VFS_3DS) - else() - add_definitions(-DUSE_VFS_FILE) - list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) - endif() - source_group("3DS-specific code" FILES ${OS_SRC}) endif() if(APPLE)

@@ -181,8 +155,9 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")

endif() if(NOT HAIKU AND NOT MSVC AND NOT PSP2) - list(APPEND OS_LIB m) + set(M_LIBRARY m) endif() +list(APPEND OS_LIB ${M_LIBRARY}) if(APPLE OR CMAKE_C_COMPILER_ID STREQUAL "GNU" AND BUILD_LTO) set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -flto")

@@ -196,13 +171,6 @@ add_definitions(-DCOLOR_16_BIT -DCOLOR_5_6_5)

endif() endif() -if(WII) - add_definitions(-U__STRICT_ANSI__) - if (${CMAKE_SOURCE_DIR}/src/platform/wii/font.tpl IS_NEWER_THAN ${CMAKE_BINARY_DIR}/font.c) - execute_process(COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/wii/font.tpl OUTPUT_QUIET ERROR_QUIET) - endif() -endif() - if(BUILD_RASPI) set(BUILD_GL OFF CACHE BOOL "OpenGL not supported" FORCE) endif()

@@ -215,13 +183,18 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*")

enable_language(ASM) endif() -if(PSP2) +if(PSP2 OR WII) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format") endif() +if(WII) + add_definitions(-U__STRICT_ANSI__) +endif() + include(CheckFunctionExists) check_function_exists(strdup HAVE_STRDUP) check_function_exists(strndup HAVE_STRNDUP) +check_function_exists(localtime_r HAVE_LOCALTIME_R) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Generic") check_function_exists(snprintf_l HAVE_SNPRINTF_L) if(CMAKE_SYSTEM_NAME STREQUAL "Linux")

@@ -240,29 +213,43 @@ set(DISABLE_FRONTENDS ON)

set(MINIMAL_CORE ON) endif() +check_function_exists(chmod HAVE_CHMOD) +check_function_exists(umask HAVE_UMASK) + +set(FUNCTION_DEFINES) + if(HAVE_STRDUP) - add_definitions(-DHAVE_STRDUP) + list(APPEND FUNCTION_DEFINES HAVE_STRDUP) endif() if(HAVE_STRNDUP) - add_definitions(-DHAVE_STRNDUP) + list(APPEND FUNCTION_DEFINES HAVE_STRNDUP) +endif() + +if(HAVE_LOCALTIME_R) + list(APPEND FUNCTION_DEFINES HAVE_LOCALTIME_R) endif() if(HAVE_NEWLOCALE AND HAVE_FREELOCALE AND HAVE_USELOCALE) - add_definitions(-DHAVE_LOCALE) + list(APPEND FUNCTION_DEFINES HAVE_LOCALE) if (HAVE_STRTOF_L) - add_definitions(-DHAVE_STRTOF_L) + list(APPEND FUNCTION_DEFINES HAVE_STRTOF_L) endif() if (HAVE_SNPRINTF_L) - add_definitions(-DHAVE_SNPRINTF_L) + list(APPEND FUNCTION_DEFINES HAVE_SNPRINTF_L) endif() endif() if(HAVE_SETLOCALE) - add_definitions(-DHAVE_SETLOCALE) + list(APPEND FUNCTION_DEFINES HAVE_SETLOCALE) endif() -if(DISABLE_DEPS) +if(HAVE_CHMOD) + list(APPEND FUNCTION_DEFINES HAVE_CHMOD) +endif() + +if(HAVE_UMASK) + list(APPEND FUNCTION_DEFINES HAVE_UMASK) endif() # Feature dependencies

@@ -290,11 +277,11 @@ endif()

endif() set(WANT_ZLIB ${USE_ZLIB}) set(WANT_PNG ${USE_PNG}) +set(WANT_LIBZIP ${USE_LIBZIP}) + find_feature(USE_FFMPEG "libavcodec;libavformat;libavresample;libavutil;libswscale") -if(NOT PSP2) - find_feature(USE_ZLIB "ZLIB") - find_feature(USE_PNG "PNG") -endif() +find_feature(USE_ZLIB "ZLIB") +find_feature(USE_PNG "PNG") find_feature(USE_LIBZIP "libzip") find_feature(USE_MAGICK "MagickWand")

@@ -373,6 +360,7 @@ set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libmagickwand${MAGICKWAND_DEB_VERSION}")

endif() if(WANT_ZLIB AND NOT USE_ZLIB) + set(SKIP_INSTALL_ALL ON) add_subdirectory(${CMAKE_SOURCE_DIR}/src/third-party/zlib zlib) set_property(TARGET zlibstatic PROPERTY INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/zlib;${CMAKE_SOURCE_DIR}/src/third-party/zlib) set_property(TARGET zlib PROPERTY EXCLUDE_FROM_ALL ON)

@@ -457,6 +445,19 @@ source_group("Virtual files" FILES ${VFS_SRC})

source_group("Extra features" FILES ${FEATURE_SRC}) source_group("Third-party code" FILES ${THIRD_PARTY_SRC}) +# Platform binaries +if(3DS) + add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/3ds ${CMAKE_BINARY_DIR}/3ds) +endif() + +if(WII) + add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/wii ${CMAKE_BINARY_DIR}/wii) +endif() + +if(PSP2) + add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/psp2 ${CMAKE_BINARY_DIR}/psp2) +endif() + # Binaries set(CORE_SRC ${ARM_SRC}

@@ -487,10 +488,9 @@ endif()

if(BUILD_SHARED) add_library(${BINARY_NAME} SHARED ${SRC}) - set_target_properties(${BINARY_NAME} PROPERTIES SOVERSION ${LIB_VERSION_ABI}) if(BUILD_STATIC) add_library(${BINARY_NAME}-static STATIC ${SRC}) - set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") + set_target_properties(${BINARY_NAME}-static PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") install(TARGETS ${BINARY_NAME}-static DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}) add_dependencies(${BINARY_NAME}-static version-info) endif()

@@ -499,6 +499,7 @@ add_library(${BINARY_NAME} STATIC ${SRC})

endif() add_dependencies(${BINARY_NAME} version-info) +set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME} ${DEBUGGER_LIB} ${DEPENDENCY_LIB} ${OS_LIB}) install(TARGETS ${BINARY_NAME} LIBRARY DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME} NAMELINK_SKIP ARCHIVE DESTINATION ${LIBDIR} RUNTIME DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME})

@@ -513,7 +514,6 @@ install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-128.png DESTINATION share/icons/hicolor/128x128/apps RENAME mgba.png COMPONENT lib${BINARY_NAME})

install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-256.png DESTINATION share/icons/hicolor/256x256/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-512.png DESTINATION share/icons/hicolor/512x512/apps RENAME mgba.png COMPONENT lib${BINARY_NAME}) endif() -set_target_properties(${BINARY_NAME} PROPERTIES VERSION ${LIB_VERSION_STRING} SOVERSION ${LIB_VERSION_ABI} COMPILE_DEFINITIONS "${FEATURE_DEFINES}") if(BUILD_GL) add_definitions(-DBUILD_GL)

@@ -531,7 +531,7 @@

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;DISABLE_THREADING") + set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;${OS_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB}) endif()

@@ -544,18 +544,6 @@ if(BUILD_QT)

add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/qt ${CMAKE_BINARY_DIR}/qt) endif() -if(WII) - add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/wii ${CMAKE_BINARY_DIR}) -endif() - -if(PSP2) - add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/psp2 ${CMAKE_BINARY_DIR}) -endif() - -if(3DS) - add_subdirectory(${CMAKE_SOURCE_DIR}/src/platform/3ds ${CMAKE_BINARY_DIR}) -endif() - if(BUILD_PERF) set(PERF_SRC ${CMAKE_SOURCE_DIR}/src/platform/test/perf-main.c) if(UNIX AND NOT APPLE)

@@ -564,6 +552,7 @@ endif()

add_executable(${BINARY_NAME}-perf ${PERF_SRC}) target_link_libraries(${BINARY_NAME}-perf ${BINARY_NAME} ${PERF_LIB}) + set_target_properties(${BINARY_NAME}-perf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") install(TARGETS ${BINARY_NAME}-perf DESTINATION bin COMPONENT ${BINARY_NAME}-perf) install(FILES ${CMAKE_SOURCE_DIR}/tools/perf.py DESTINATION "${CMAKE_INSTALL_LIBDIR}/${BINARY_NAME}" COMPONENT ${BINARY_NAME}-perf) endif()

@@ -571,6 +560,7 @@

if(BUILD_TEST) add_executable(${BINARY_NAME}-fuzz ${CMAKE_SOURCE_DIR}/src/platform/test/fuzz-main.c) target_link_libraries(${BINARY_NAME}-fuzz ${BINARY_NAME}) + set_target_properties(${BINARY_NAME}-fuzz PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") install(TARGETS ${BINARY_NAME}-fuzz DESTINATION bin COMPONENT ${BINARY_NAME}-test) endif()
M src/arm/decoder-thumb.csrc/arm/decoder-thumb.c

@@ -18,7 +18,7 @@ }

#define DEFINE_IMMEDIATE_5_DECODER_DATA_THUMB(NAME, MNEMONIC) \ DEFINE_THUMB_DECODER(NAME, MNEMONIC, \ - info->op3.immediate = (opcode >> 6) & 0x0007; \ + info->op3.immediate = (opcode >> 6) & 0x001F; \ info->op1.reg = opcode & 0x0007; \ info->op2.reg = (opcode >> 3) & 0x0007; \ info->affectsCPSR = 1; \
M src/arm/isa-inlines.hsrc/arm/isa-inlines.h

@@ -86,7 +86,7 @@ break;

case MODE_THUMB: cpu->cpsr.t = 1; } - cpu->nextEvent = 0; + cpu->nextEvent = cpu->cycles; } static inline void _ARMReadCPSR(struct ARMCore* cpu) {
M src/debugger/debugger.csrc/debugger/debugger.c

@@ -103,7 +103,7 @@

void ARMDebuggerEnter(struct ARMDebugger* debugger, enum DebuggerEntryReason reason, struct DebuggerEntryInfo* info) { debugger->state = DEBUGGER_PAUSED; struct ARMCore* cpu = debugger->cpu; - cpu->nextEvent = 0; + cpu->nextEvent = cpu->cycles; if (reason == DEBUGGER_ENTER_BREAKPOINT) { struct DebugBreakpoint* breakpoint = _lookupBreakpoint(debugger->swBreakpoints, _ARMPCAddress(cpu)); debugger->currentBreakpoint = breakpoint;
M src/gba/audio.csrc/gba/audio.c

@@ -345,12 +345,10 @@ if (!audio->playingCh1) {

audio->nextCh1 = 0; } audio->playingCh1 = 1; - if (audio->ch1.envelope.stepTime) { - audio->ch1.envelope.nextStep = 0; - } else { - audio->ch1.envelope.nextStep = INT_MAX; - } audio->ch1.envelope.currentVolume = audio->ch1.envelope.initialVolume; + if (audio->ch1.envelope.currentVolume > 0) { + audio->ch1.envelope.dead = 0; + } if (audio->ch1.envelope.stepTime) { audio->ch1.envelope.nextStep = 0; } else {

@@ -372,6 +370,9 @@ audio->ch2.control.endTime = (GBA_ARM7TDMI_FREQUENCY * (64 - audio->ch2.envelope.length)) >> 8;

if (GBAAudioRegisterControlIsRestart(value)) { audio->playingCh2 = 1; audio->ch2.envelope.currentVolume = audio->ch2.envelope.initialVolume; + if (audio->ch2.envelope.currentVolume > 0) { + audio->ch2.envelope.dead = 0; + } if (audio->ch2.envelope.stepTime) { audio->ch2.envelope.nextStep = 0; } else {

@@ -419,6 +420,9 @@ audio->ch4.control.endTime = (GBA_ARM7TDMI_FREQUENCY * (64 - audio->ch4.envelope.length)) >> 8;

if (GBAAudioRegisterCh4ControlIsRestart(value)) { audio->playingCh4 = 1; audio->ch4.envelope.currentVolume = audio->ch4.envelope.initialVolume; + if (audio->ch4.envelope.currentVolume > 0) { + audio->ch4.envelope.dead = 0; + } if (audio->ch4.envelope.stepTime) { audio->ch4.envelope.nextStep = 0; } else {
M src/gba/bios.csrc/gba/bios.c

@@ -63,7 +63,12 @@ if (registers & 0x10) {

memset(gba->video.oam.raw, 0, SIZE_OAM); } if (registers & 0x20) { - GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on SIO unimplemented"); + cpu->memory.store16(cpu, BASE_IO | REG_SIOCNT, 0x0000, 0); + cpu->memory.store16(cpu, BASE_IO | REG_RCNT, RCNT_INITIAL, 0); + cpu->memory.store16(cpu, BASE_IO | REG_SIOMLT_SEND, 0, 0); + cpu->memory.store16(cpu, BASE_IO | REG_JOYCNT, 0, 0); + cpu->memory.store32(cpu, BASE_IO | REG_JOY_RECV, 0, 0); + cpu->memory.store32(cpu, BASE_IO | REG_JOY_TRANS, 0, 0); } if (registers & 0x40) { GBALog(gba, GBA_LOG_STUB, "RegisterRamReset on Audio unimplemented");
M src/gba/context/config.csrc/gba/context/config.c

@@ -22,10 +22,24 @@ #ifdef PSP2

#include <psp2/io/stat.h> #endif +#ifdef _3DS +#include "platform/3ds/3ds-vfs.h" +#endif + #define SECTION_NAME_MAX 128 static const char* _lookupValue(const struct GBAConfig* config, const char* key) { const char* value; + if (config->port) { + value = ConfigurationGetValue(&config->overridesTable, config->port, key); + if (value) { + return value; + } + } + value = ConfigurationGetValue(&config->overridesTable, 0, key); + if (value) { + return value; + } if (config->port) { value = ConfigurationGetValue(&config->configTable, config->port, key); if (value) {

@@ -102,6 +116,7 @@

void GBAConfigInit(struct GBAConfig* config, const char* port) { ConfigurationInit(&config->configTable); ConfigurationInit(&config->defaultsTable); + ConfigurationInit(&config->overridesTable); if (port) { config->port = malloc(strlen("ports.") + strlen(port) + 1); snprintf(config->port, strlen("ports.") + strlen(port) + 1, "ports.%s", port);

@@ -113,6 +128,7 @@

void GBAConfigDeinit(struct GBAConfig* config) { ConfigurationDeinit(&config->configTable); ConfigurationDeinit(&config->defaultsTable); + ConfigurationDeinit(&config->overridesTable); free(config->port); }

@@ -151,7 +167,7 @@ PathRemoveFileSpecW(wpath);

WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, MAX_PATH, 0, 0); StringCchCatA(out, MAX_PATH, "\\portable.ini"); portable = VFileOpen(out, O_WRONLY | O_CREAT); -#elif defined(PSP2) +#elif defined(PSP2) || defined(_3DS) || defined(GEKKO) // Already portable #else char out[PATH_MAX];

@@ -189,8 +205,15 @@ }

WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0); #elif defined(PSP2) UNUSED(portable); - snprintf(out, outLength, "cache0:/%s", binaryName); + snprintf(out, outLength, "cache0:/%s", projectName); sceIoMkdir(out, 0777); +#elif defined(GEKKO) + UNUSED(portable); + snprintf(out, outLength, "/%s", projectName); + mkdir(out, 0777); +#elif defined(_3DS) + snprintf(out, outLength, "/%s", projectName); + FSUSER_CreateDirectory(0, sdmcArchive, FS_makePath(PATH_CHAR, out)); #else getcwd(out, outLength); strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));

@@ -213,6 +236,18 @@ const char* GBAConfigGetValue(const struct GBAConfig* config, const char* key) {

return _lookupValue(config, key); } +bool GBAConfigGetIntValue(const struct GBAConfig* config, const char* key, int* value) { + return _lookupIntValue(config, key, value); +} + +bool GBAConfigGetUIntValue(const struct GBAConfig* config, const char* key, unsigned* value) { + return _lookupUIntValue(config, key, value); +} + +bool GBAConfigGetFloatValue(const struct GBAConfig* config, const char* key, float* value) { + return _lookupFloatValue(config, key, value); +} + void GBAConfigSetValue(struct GBAConfig* config, const char* key, const char* value) { ConfigurationSetValue(&config->configTable, config->port, key, value); }

@@ -243,6 +278,22 @@ }

void GBAConfigSetDefaultFloatValue(struct GBAConfig* config, const char* key, float value) { ConfigurationSetFloatValue(&config->defaultsTable, config->port, key, value); +} + +void GBAConfigSetOverrideValue(struct GBAConfig* config, const char* key, const char* value) { + ConfigurationSetValue(&config->overridesTable, config->port, key, value); +} + +void GBAConfigSetOverrideIntValue(struct GBAConfig* config, const char* key, int value) { + ConfigurationSetIntValue(&config->overridesTable, config->port, key, value); +} + +void GBAConfigSetOverrideUIntValue(struct GBAConfig* config, const char* key, unsigned value) { + ConfigurationSetUIntValue(&config->overridesTable, config->port, key, value); +} + +void GBAConfigSetOverrideFloatValue(struct GBAConfig* config, const char* key, float value) { + ConfigurationSetFloatValue(&config->overridesTable, config->port, key, value); } void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts) {
M src/gba/context/config.hsrc/gba/context/config.h

@@ -15,6 +15,7 @@

struct GBAConfig { struct Configuration configTable; struct Configuration defaultsTable; + struct Configuration overridesTable; char* port; };

@@ -59,6 +60,9 @@ void GBAConfigMakePortable(const struct GBAConfig*);

void GBAConfigDirectory(char* out, size_t outLength); const char* GBAConfigGetValue(const struct GBAConfig*, const char* key); +bool GBAConfigGetIntValue(const struct GBAConfig*, const char* key, int* value); +bool GBAConfigGetUIntValue(const struct GBAConfig*, const char* key, unsigned* value); +bool GBAConfigGetFloatValue(const struct GBAConfig*, const char* key, float* value); void GBAConfigSetValue(struct GBAConfig*, const char* key, const char* value); void GBAConfigSetIntValue(struct GBAConfig*, const char* key, int value);

@@ -69,6 +73,11 @@ void GBAConfigSetDefaultValue(struct GBAConfig*, const char* key, const char* value);

void GBAConfigSetDefaultIntValue(struct GBAConfig*, const char* key, int value); void GBAConfigSetDefaultUIntValue(struct GBAConfig*, const char* key, unsigned value); void GBAConfigSetDefaultFloatValue(struct GBAConfig*, const char* key, float value); + +void GBAConfigSetOverrideValue(struct GBAConfig*, const char* key, const char* value); +void GBAConfigSetOverrideIntValue(struct GBAConfig*, const char* key, int value); +void GBAConfigSetOverrideUIntValue(struct GBAConfig*, const char* key, unsigned value); +void GBAConfigSetOverrideFloatValue(struct GBAConfig*, const char* key, float value); void GBAConfigMap(const struct GBAConfig* config, struct GBAOptions* opts); void GBAConfigLoadDefaults(struct GBAConfig* config, const struct GBAOptions* opts);
M src/gba/context/context.csrc/gba/context/context.c

@@ -10,10 +10,14 @@

#include "util/memory.h" #include "util/vfs.h" +static struct VFile* _logFile = 0; +static void _GBAContextLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); + bool GBAContextInit(struct GBAContext* context, const char* port) { context->gba = anonymousMemoryMap(sizeof(struct GBA)); context->cpu = anonymousMemoryMap(sizeof(struct ARMCore)); context->rom = 0; + context->bios = 0; context->fname = 0; context->save = 0; context->renderer = 0;

@@ -28,15 +32,33 @@ mappedMemoryFree(context->cpu, sizeof(struct ARMCore));

} return false; } + GBACreate(context->gba); + ARMSetComponents(context->cpu, &context->gba->d, 0, context->components); + ARMInit(context->cpu); GBAConfigInit(&context->config, port); if (port) { + if (!_logFile) { + char logPath[PATH_MAX]; + GBAConfigDirectory(logPath, PATH_MAX); + strncat(logPath, PATH_SEP "log", PATH_MAX - strlen(logPath)); + _logFile = VFileOpen(logPath, O_WRONLY | O_CREAT | O_TRUNC); + } + context->gba->logHandler = _GBAContextLog; + + char biosPath[PATH_MAX]; + GBAConfigDirectory(biosPath, PATH_MAX); + strncat(biosPath, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(biosPath)); + + struct GBAOptions opts = { + .bios = biosPath, + .useBios = true, + .idleOptimization = IDLE_LOOP_DETECT, + .logLevel = GBA_LOG_WARN | GBA_LOG_ERROR | GBA_LOG_FATAL | GBA_LOG_STATUS + }; GBAConfigLoad(&context->config); + GBAConfigLoadDefaults(&context->config, &opts); } - - GBACreate(context->gba); - ARMSetComponents(context->cpu, &context->gba->d, 0, context->components); - ARMInit(context->cpu); context->gba->sync = 0; return true;

@@ -51,7 +73,25 @@ GBAConfigDeinit(&context->config);

} bool GBAContextLoadROM(struct GBAContext* context, const char* path, bool autoloadSave) { - context->rom = VFileOpen(path, O_RDONLY); + struct VDir* dir = VDirOpenArchive(path); + if (dir) { + struct VDirEntry* de; + while ((de = dir->listNext(dir))) { + struct VFile* vf = dir->openFile(dir, de->name(de), O_RDONLY); + if (!vf) { + continue; + } + if (GBAIsROM(vf)) { + context->rom = vf; + break; + } + vf->close(vf); + } + dir->close(dir); + } else { + context->rom = VFileOpen(path, O_RDONLY); + } + if (!context->rom) { return false; }

@@ -130,6 +170,10 @@ return false;

} GBAConfigMap(&context->config, &opts); + + if (!context->bios && opts.bios) { + GBAContextLoadBIOS(context, opts.bios); + } if (opts.useBios && context->bios) { GBALoadBIOS(context->gba, context->bios); }

@@ -166,3 +210,19 @@ while (frameCounter == context->gba->video.frameCounter) {

ARMRunLoop(context->cpu); } } + +static void _GBAContextLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { + UNUSED(thread); + UNUSED(level); + // TODO: Make this local + if (!_logFile) { + return; + } + char out[256]; + size_t len = vsnprintf(out, sizeof(out), format, args); + if (len >= sizeof(out)) { + len = sizeof(out) - 1; + } + out[len] = '\n'; + _logFile->write(_logFile, out, len + 1); +}
M src/gba/context/context.hsrc/gba/context/context.h

@@ -9,6 +9,7 @@

#include "util/common.h" #include "gba/context/config.h" +#include "gba/context/sync.h" #include "gba/input.h" struct GBAContext {
M src/gba/context/overrides.csrc/gba/context/overrides.c

@@ -128,7 +128,7 @@

// Super Mario Advance 2 { "AA2J", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, { "AA2E", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, - { "AA2P", SAVEDATA_EEPROM, HW_NONE, 0x800052E }, + { "AA2P", SAVEDATA_AUTODETECT, HW_NONE, 0x800052E }, // Super Mario Advance 3 { "A3AJ", SAVEDATA_EEPROM, HW_NONE, 0x8002B9C },
M src/gba/context/sync.csrc/gba/context/sync.c

@@ -22,15 +22,12 @@ }

MutexLock(&sync->videoFrameMutex); ++sync->videoFramePending; - --sync->videoFrameSkip; - if (sync->videoFrameSkip < 0) { - do { - ConditionWake(&sync->videoFrameAvailableCond); - if (sync->videoFrameWait) { - ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); - } - } while (sync->videoFrameWait && sync->videoFramePending); - } + do { + ConditionWake(&sync->videoFrameAvailableCond); + if (sync->videoFrameWait) { + ConditionWait(&sync->videoFrameRequiredCond, &sync->videoFrameMutex); + } + } while (sync->videoFrameWait && sync->videoFramePending); MutexUnlock(&sync->videoFrameMutex); }

@@ -44,7 +41,7 @@ ConditionWake(&sync->videoFrameAvailableCond);

MutexUnlock(&sync->videoFrameMutex); } -bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) { +bool GBASyncWaitFrameStart(struct GBASync* sync) { if (!sync) { return true; }

@@ -60,7 +57,6 @@ return false;

} } sync->videoFramePending = 0; - sync->videoFrameSkip = frameskip; return true; }

@@ -70,14 +66,6 @@ return;

} MutexUnlock(&sync->videoFrameMutex); -} - -bool GBASyncDrawingFrame(struct GBASync* sync) { - if (!sync) { - return true; - } - - return sync->videoFrameSkip <= 0; } void GBASyncSetVideoSync(struct GBASync* sync, bool wait) {
M src/gba/context/sync.hsrc/gba/context/sync.h

@@ -13,7 +13,6 @@

struct GBASync { int videoFramePending; bool videoFrameWait; - int videoFrameSkip; bool videoFrameOn; Mutex videoFrameMutex; Condition videoFrameAvailableCond;

@@ -26,9 +25,8 @@ };

void GBASyncPostFrame(struct GBASync* sync); void GBASyncForceFrame(struct GBASync* sync); -bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip); +bool GBASyncWaitFrameStart(struct GBASync* sync); void GBASyncWaitFrameEnd(struct GBASync* sync); -bool GBASyncDrawingFrame(struct GBASync* sync); void GBASyncSetVideoSync(struct GBASync* sync, bool wait); void GBASyncProduceAudio(struct GBASync* sync, bool wait);
M src/gba/gba.csrc/gba/gba.c

@@ -574,12 +574,12 @@ void GBATestIRQ(struct ARMCore* cpu) {

struct GBA* gba = (struct GBA*) cpu->master; if (gba->memory.io[REG_IME >> 1] && gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) { gba->springIRQ = 1; - gba->cpu->nextEvent = 0; + gba->cpu->nextEvent = gba->cpu->cycles; } } void GBAHalt(struct GBA* gba) { - gba->cpu->nextEvent = 0; + gba->cpu->nextEvent = gba->cpu->cycles; gba->cpu->halted = 1; }

@@ -587,7 +587,7 @@ void GBAStop(struct GBA* gba) {

if (!gba->stopCallback) { return; } - gba->cpu->nextEvent = 0; + gba->cpu->nextEvent = gba->cpu->cycles; gba->stopCallback->stop(gba->stopCallback); }

@@ -682,13 +682,13 @@ bool GBAIsBIOS(struct VFile* vf) {

if (vf->seek(vf, 0, SEEK_SET) < 0) { return false; } - uint32_t interruptTable[7]; + uint8_t interruptTable[7 * 4]; if (vf->read(vf, &interruptTable, sizeof(interruptTable)) != sizeof(interruptTable)) { return false; } int i; for (i = 0; i < 7; ++i) { - if ((interruptTable[i] & 0xFFFF0000) != 0xEA000000) { + if (interruptTable[4 * i + 3] != 0xEA || interruptTable[4 * i + 2]) { return false; } }
A src/gba/gui/gui-config.c

@@ -0,0 +1,107 @@

+/* 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 "gui-config.h" + +#include "gba/gui/gui-runner.h" +#include "util/gui/file-select.h" +#include "util/gui/menu.h" + +void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra) { + struct GUIMenu menu = { + .title = "Configure", + .index = 0, + .background = &runner->background.d + }; + GUIMenuItemListInit(&menu.items, 0); + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Frameskip", + .data = "frameskip", + .submenu = 0, + .state = 0, + .validStates = (const char*[]) { + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 0 + } + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Show framerate", + .data = "fpsCounter", + .submenu = 0, + .state = false, + .validStates = (const char*[]) { + "Off", "On", 0 + } + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Use BIOS if found", + .data = "useBios", + .submenu = 0, + .state = true, + .validStates = (const char*[]) { + "Off", "On", 0 + } + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Select BIOS path", + .data = "bios", + }; + size_t i; + for (i = 0; i < nExtra; ++i) { + *GUIMenuItemListAppend(&menu.items) = extra[i]; + } + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Save", + .data = "[SAVE]", + }; + *GUIMenuItemListAppend(&menu.items) = (struct GUIMenuItem) { + .title = "Cancel", + .data = 0, + }; + enum GUIMenuExitReason reason; + char biosPath[256] = ""; + + struct GUIMenuItem* item; + for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { + item = GUIMenuItemListGetPointer(&menu.items, i); + if (!item->validStates || !item->data) { + continue; + } + GBAConfigGetUIntValue(&runner->context.config, item->data, &item->state); + } + + while (true) { + reason = GUIShowMenu(&runner->params, &menu, &item); + if (reason != GUI_MENU_EXIT_ACCEPT || !item->data) { + break; + } + if (!strcmp(item->data, "[SAVE]")) { + if (biosPath[0]) { + GBAConfigSetValue(&runner->context.config, "bios", biosPath); + } + for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) { + item = GUIMenuItemListGetPointer(&menu.items, i); + if (!item->validStates || !item->data) { + continue; + } + GBAConfigSetUIntValue(&runner->context.config, item->data, item->state); + } + GBAConfigSave(&runner->context.config); + break; + } + if (!strcmp(item->data, "bios")) { + // TODO: show box if failed + if (!GUISelectFile(&runner->params, biosPath, sizeof(biosPath), GBAIsBIOS)) { + biosPath[0] = '\0'; + } + continue; + } + if (item->validStates) { + ++item->state; + if (!item->validStates[item->state]) { + item->state = 0; + } + } + } +}
A src/gba/gui/gui-config.h

@@ -0,0 +1,15 @@

+/* 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 GUI_CONFIG_H +#define GUI_CONFIG_H + +#include "util/common.h" + +struct GBAGUIRunner; +struct GUIMenuItem; +void GBAGUIShowConfig(struct GBAGUIRunner* runner, struct GUIMenuItem* extra, size_t nExtra); + +#endif
M src/gba/gui/gui-runner.csrc/gba/gui/gui-runner.c

@@ -5,6 +5,7 @@ * 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 "gui-runner.h" +#include "gba/gui/gui-config.h" #include "gba/interface.h" #include "gba/serialize.h" #include "util/gui/file-select.h"

@@ -13,12 +14,19 @@ #include "util/gui/menu.h"

#include "util/memory.h" #include "util/png-io.h" #include "util/vfs.h" + +#include <sys/time.h> + +#define FPS_GRANULARITY 120 +#define FPS_BUFFER_SIZE 3 enum { RUNNER_CONTINUE = 1, - RUNNER_EXIT = 2, - RUNNER_SAVE_STATE = 3, - RUNNER_LOAD_STATE = 4, + RUNNER_EXIT, + RUNNER_SAVE_STATE, + RUNNER_LOAD_STATE, + RUNNER_SCREENSHOT, + RUNNER_CONFIG, RUNNER_COMMAND_MASK = 0xFFFF, RUNNER_STATE_1 = 0x10000,

@@ -100,6 +108,10 @@ runner->luminanceSource.luxLevel = 0;

runner->context.gba->luminanceSource = &runner->luminanceSource.d; runner->background.d.draw = _drawBackground; runner->background.p = runner; + runner->fps = 0; + runner->lastFpsCheck = 0; + runner->totalDelta = 0; + CircleBufferInit(&runner->fpsBuffer, FPS_BUFFER_SIZE * sizeof(uint32_t)); if (runner->setup) { runner->setup(runner); }

@@ -109,6 +121,10 @@ void GBAGUIDeinit(struct GBAGUIRunner* runner) {

if (runner->teardown) { runner->teardown(runner); } + if (runner->context.config.port) { + GBAConfigSave(&runner->context.config); + } + CircleBufferDeinit(&runner->fpsBuffer); GBAContextDeinit(&runner->context); }

@@ -165,14 +181,13 @@ *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 7", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_7) };

*GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 8", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_8) }; *GUIMenuItemListAppend(&stateLoadMenu.items) = (struct GUIMenuItem) { .title = "State 9", .data = (void*) (RUNNER_LOAD_STATE | RUNNER_STATE_9) }; #endif + *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Take screenshot", .data = (void*) RUNNER_SCREENSHOT }; + *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Configure", .data = (void*) RUNNER_CONFIG }; *GUIMenuItemListAppend(&pauseMenu.items) = (struct GUIMenuItem) { .title = "Exit game", .data = (void*) RUNNER_EXIT }; while (true) { char path[256]; if (!GUISelectFile(&runner->params, path, sizeof(path), GBAIsROM)) { - if (runner->params.guiFinish) { - runner->params.guiFinish(); - } break; }

@@ -194,6 +209,7 @@ runner->params.drawStart();

GUIFontPrint(runner->params.font, runner->params.width / 2, (GUIFontHeight(runner->params.font) + runner->params.height) / 2, GUI_TEXT_CENTER, 0xFFFFFFFF, "Load failed!"); runner->params.drawEnd(); } + continue; } if (runner->params.guiFinish) { runner->params.guiFinish();

@@ -202,8 +218,16 @@ GBAContextStart(&runner->context);

if (runner->gameLoaded) { runner->gameLoaded(runner); } + bool running = true; while (running) { + CircleBufferClear(&runner->fpsBuffer); + runner->totalDelta = 0; + runner->fps = 0; + struct timeval tv; + gettimeofday(&tv, 0); + runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec; + while (true) { uint32_t guiKeys; GUIPollInput(&runner->params, &guiKeys, 0);

@@ -229,9 +253,43 @@ runner->prepareForFrame(runner);

} GBAContextFrame(&runner->context, keys); if (runner->drawFrame) { + int drawFps = false; + GBAConfigGetIntValue(&runner->context.config, "fpsCounter", &drawFps); + runner->params.drawStart(); runner->drawFrame(runner, false); + if (drawFps) { + if (runner->params.guiPrepare) { + runner->params.guiPrepare(); + } + GUIFontPrintf(runner->params.font, 0, GUIFontHeight(runner->params.font), GUI_TEXT_LEFT, 0x7FFFFFFF, "%.2f fps", runner->fps); + if (runner->params.guiPrepare) { + runner->params.guiFinish(); + } + } runner->params.drawEnd(); + + if (runner->context.gba->video.frameCounter % FPS_GRANULARITY == 0) { + if (drawFps) { + struct timeval tv; + gettimeofday(&tv, 0); + uint64_t t = 1000000LL * tv.tv_sec + tv.tv_usec; + uint64_t delta = t - runner->lastFpsCheck; + runner->lastFpsCheck = t; + if (delta > 0x7FFFFFFFLL) { + CircleBufferClear(&runner->fpsBuffer); + runner->fps = 0; + } + if (CircleBufferSize(&runner->fpsBuffer) == CircleBufferCapacity(&runner->fpsBuffer)) { + int32_t last; + CircleBufferRead32(&runner->fpsBuffer, &last); + runner->totalDelta -= last; + } + CircleBufferWrite32(&runner->fpsBuffer, delta); + runner->totalDelta += delta; + runner->fps = (CircleBufferSize(&runner->fpsBuffer) * FPS_GRANULARITY * 1000000.0f) / (runner->totalDelta * sizeof(uint32_t)); + } + } } }

@@ -240,38 +298,48 @@ runner->paused(runner);

} GUIInvalidateKeys(&runner->params); uint32_t keys = 0xFFFFFFFF; // Huge hack to avoid an extra variable! - struct GUIMenuItem item; + struct GUIMenuItem* item; enum GUIMenuExitReason reason = GUIShowMenu(&runner->params, &pauseMenu, &item); if (reason == GUI_MENU_EXIT_ACCEPT) { struct VFile* vf; - switch (((int) item.data) & RUNNER_COMMAND_MASK) { + switch (((int) item->data) & RUNNER_COMMAND_MASK) { case RUNNER_EXIT: running = false; keys = 0; break; case RUNNER_SAVE_STATE: - vf = GBAGetState(runner->context.gba, 0, ((int) item.data) >> 16, true); + vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, true); if (vf) { GBASaveStateNamed(runner->context.gba, vf, true); vf->close(vf); } break; case RUNNER_LOAD_STATE: - vf = GBAGetState(runner->context.gba, 0, ((int) item.data) >> 16, false); + vf = GBAGetState(runner->context.gba, 0, ((int) item->data) >> 16, false); if (vf) { GBALoadStateNamed(runner->context.gba, vf); vf->close(vf); } break; + case RUNNER_SCREENSHOT: + GBATakeScreenshot(runner->context.gba, 0); + break; + case RUNNER_CONFIG: + GBAGUIShowConfig(runner, runner->configExtra, runner->nConfigExtra); + GBAConfigGetIntValue(&runner->context.config, "frameskip", &runner->context.gba->video.frameskip); + break; case RUNNER_CONTINUE: break; } } - while (keys) { + int frames = 0; + GUIPollInput(&runner->params, 0, &keys); + while (keys && frames < 30) { + ++frames; + runner->params.drawStart(); + runner->drawFrame(runner, true); + runner->params.drawEnd(); GUIPollInput(&runner->params, 0, &keys); - } - if (runner->params.guiFinish) { - runner->params.guiFinish(); } if (runner->unpaused) { runner->unpaused(runner);
M src/gba/gui/gui-runner.hsrc/gba/gui/gui-runner.h

@@ -7,6 +7,7 @@ #ifndef GUI_RUNNER_H

#define GUI_RUNNER_H #include "gba/context/context.h" +#include "util/circle-buffer.h" #include "util/gui.h" enum GBAGUIInput {

@@ -34,6 +35,14 @@ struct GUIParams params;

struct GBAGUIBackground background; struct GBAGUIRunnerLux luminanceSource; + + struct GUIMenuItem* configExtra; + size_t nConfigExtra; + + float fps; + int64_t lastFpsCheck; + int32_t totalDelta; + struct CircleBuffer fpsBuffer; void (*setup)(struct GBAGUIRunner*); void (*teardown)(struct GBAGUIRunner*);
M src/gba/hardware.csrc/gba/hardware.c

@@ -7,11 +7,8 @@ #include "hardware.h"

#include "gba/io.h" #include "gba/serialize.h" +#include "util/formatting.h" #include "util/hash.h" - -#ifdef PSP2 -#include <psp2/rtc.h> -#endif const int GBA_LUX_LEVELS[10] = { 5, 11, 18, 27, 42, 62, 84, 109, 139, 183 };

@@ -281,21 +278,7 @@ } else {

t = time(0); } struct tm date; -#ifdef _WIN32 - localtime_s(&date, &t); -#elif defined(PSP2) - SceRtcTime sceRtc; - sceRtcSetTime_t(&sceRtc, t); - date.tm_year = sceRtc.year; - date.tm_mon = sceRtc.month; - date.tm_mday = sceRtc.day; - date.tm_hour = sceRtc.hour; - date.tm_min = sceRtc.minutes; - date.tm_sec = sceRtc.seconds; - date.tm_wday = sceRtcGetDayOfWeek(sceRtc.year, sceRtc.month, sceRtc.day); -#else localtime_r(&t, &date); -#endif hw->rtc.time[0] = _rtcBCD(date.tm_year - 100); hw->rtc.time[1] = _rtcBCD(date.tm_mon + 1); hw->rtc.time[2] = _rtcBCD(date.tm_mday);
M src/gba/io.csrc/gba/io.c

@@ -328,7 +328,7 @@ value &= 0x00E0;

break; case REG_SOUND3CNT_HI: GBAAudioWriteSOUND3CNT_HI(&gba->audio, value); - value &= 0xE000; + value &= 0xE03F; break; case REG_SOUND3CNT_X: GBAAudioWriteSOUND3CNT_X(&gba->audio, value);

@@ -337,7 +337,7 @@ value &= 0x47FF;

break; case REG_SOUND4CNT_LO: GBAAudioWriteSOUND4CNT_LO(&gba->audio, value); - value &= 0xFF00; + value &= 0xFF3F; break; case REG_SOUND4CNT_HI: GBAAudioWriteSOUND4CNT_HI(&gba->audio, value);
M src/gba/memory.csrc/gba/memory.c

@@ -442,7 +442,7 @@ if (memory->activeRegion == REGION_BIOS) {

LOAD_16(value, address, memory->bios); } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load16: 0x%08X", address); - LOAD_16(value, address & 2, &memory->biosPrefetch); + value = (memory->biosPrefetch >> ((address & 2) * 8)) & 0xFFFF; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load16: 0x%08X", address);

@@ -535,12 +535,12 @@ if (memory->activeRegion == REGION_BIOS) {

value = ((uint8_t*) memory->bios)[address]; } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad BIOS Load8: 0x%08X", address); - value = ((uint8_t*) &memory->biosPrefetch)[address & 3]; + value = (memory->biosPrefetch >> ((address & 3) * 8)) & 0xFF; } } else { GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); LOAD_BAD; - value = ((uint8_t*) &value)[address & 3]; + value = (value >> ((address & 3) * 8)) & 0xFF; } break; case REGION_WORKING_RAM:

@@ -602,7 +602,7 @@ break;

default: GBALog(gba, GBA_LOG_GAME_ERROR, "Bad memory Load8: 0x%08x", address); LOAD_BAD; - value = ((uint8_t*) &value)[address & 3]; + value = (value >> ((address & 3) * 8)) & 0xFF; break; }
M src/gba/renderers/software-bg.csrc/gba/renderers/software-bg.c

@@ -7,6 +7,63 @@ #include "software-private.h"

#include "gba/gba.h" +#define MODE_2_COORD_OVERFLOW \ + localX = x & (sizeAdjusted - 1); \ + localY = y & (sizeAdjusted - 1); \ + +#define MODE_2_COORD_NO_OVERFLOW \ + if ((x | y) & ~(sizeAdjusted - 1)) { \ + continue; \ + } else { \ + localX = x; \ + localY = y; \ + } + +#define MODE_2_MOSAIC(COORD) \ + if (!mosaicWait) { \ + COORD \ + mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \ + pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; \ + \ + mosaicWait = mosaicH; \ + } else { \ + --mosaicWait; \ + } + +#define MODE_2_NO_MOSAIC(COORD) \ + COORD \ + mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; \ + pixelData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; + + +#define MODE_2_LOOP(MOSAIC, COORD, BLEND, OBJWIN) \ + for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { \ + x += background->dx; \ + y += background->dy; \ + \ + MOSAIC(COORD) \ + \ + uint32_t current = *pixel; \ + if (pixelData && IS_WRITABLE(current)) { \ + COMPOSITE_256_ ## OBJWIN (BLEND); \ + } \ + } + +#define DRAW_BACKGROUND_MODE_2(BLEND, OBJWIN) \ + if (background->overflow) { \ + if (mosaicH > 1) { \ + MODE_2_LOOP(MODE_2_MOSAIC, MODE_2_COORD_OVERFLOW, BLEND, OBJWIN); \ + } else { \ + MODE_2_LOOP(MODE_2_NO_MOSAIC, MODE_2_COORD_OVERFLOW, BLEND, OBJWIN); \ + } \ + } else { \ + if (mosaicH > 1) { \ + MODE_2_LOOP(MODE_2_MOSAIC, MODE_2_COORD_NO_OVERFLOW, BLEND, OBJWIN); \ + } else { \ + MODE_2_LOOP(MODE_2_NO_MOSAIC, MODE_2_COORD_NO_OVERFLOW, BLEND, OBJWIN); \ + } \ + } + void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer* renderer, struct GBAVideoSoftwareBackground* background, int inY) { int sizeAdjusted = 0x8000 << background->size;

@@ -15,44 +72,22 @@

uint32_t screenBase = background->screenBase; uint32_t charBase = background->charBase; uint8_t mapData; - uint8_t tileData = 0; + uint8_t pixelData = 0; int outX; uint32_t* pixel; - for (outX = renderer->start, pixel = &renderer->row[outX]; outX < renderer->end; ++outX, ++pixel) { - x += background->dx; - y += background->dy; - if (!mosaicWait) { - if (background->overflow) { - localX = x & (sizeAdjusted - 1); - localY = y & (sizeAdjusted - 1); - } else if ((x | y) & ~(sizeAdjusted - 1)) { - continue; - } else { - localX = x; - localY = y; - } - mapData = ((uint8_t*)renderer->d.vram)[screenBase + (localX >> 11) + (((localY >> 7) & 0x7F0) << background->size)]; - tileData = ((uint8_t*)renderer->d.vram)[charBase + (mapData << 6) + ((localY & 0x700) >> 5) + ((localX & 0x700) >> 8)]; - - mosaicWait = mosaicH; + if (!objwinSlowPath) { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + DRAW_BACKGROUND_MODE_2(NoBlend, NO_OBJWIN); } else { - --mosaicWait; + DRAW_BACKGROUND_MODE_2(Blend, NO_OBJWIN); } - - uint32_t current = *pixel; - if (tileData && IS_WRITABLE(current)) { - if (!objwinSlowPath) { - _compositeBlendNoObjwin(renderer, pixel, palette[tileData] | flags, current); - } else if (objwinForceEnable || !(current & FLAG_OBJWIN) == objwinOnly) { - color_t* currentPalette = (current & FLAG_OBJWIN) ? objwinPalette : palette; - unsigned mergedFlags = flags; - if (current & FLAG_OBJWIN) { - mergedFlags = objwinFlags; - } - _compositeBlendObjwin(renderer, pixel, currentPalette[tileData] | mergedFlags, current); - } + } else { + if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) { + DRAW_BACKGROUND_MODE_2(NoBlend, OBJWIN); + } else { + DRAW_BACKGROUND_MODE_2(Blend, OBJWIN); } } }
M src/gba/renderers/software-mode0.csrc/gba/renderers/software-mode0.c

@@ -473,9 +473,9 @@ flags |= FLAG_TARGET_2 * background->target2;

int objwinFlags = FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->objwin.packed)); objwinFlags |= flags; flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); - if (renderer->blda == 0x10 && renderer->bldb == 0) { + if (renderer->blendEffect == BLEND_ALPHA && renderer->blda == 0x10 && renderer->bldb == 0) { flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); - objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ + objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); } uint32_t screenBase;
M src/gba/renderers/software-obj.csrc/gba/renderers/software-obj.c

@@ -42,6 +42,8 @@ }

#define SPRITE_TRANSFORMED_LOOP(DEPTH, TYPE) \ unsigned tileData; \ + unsigned widthMask = ~(width - 1); \ + unsigned heightMask = ~(height - 1); \ for (; outX < x + totalWidth && outX < end; ++outX, ++inX) { \ if (!(renderer->row[outX] & FLAG_UNWRITTEN)) { \ continue; \

@@ -51,7 +53,7 @@ yAccum += mat.c; \

int localX = (xAccum >> 8) + (width >> 1); \ int localY = (yAccum >> 8) + (height >> 1); \ \ - if (localX < 0 || localX >= width || localY < 0 || localY >= height) { \ + if (localX & widthMask || localY & heightMask) { \ continue; \ } \ \

@@ -75,6 +77,19 @@ renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \

} \ } +#define SPRITE_DRAW_PIXEL_16_NORMAL_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \ + renderer->spriteLayer[outX] = color | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + #define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \

@@ -83,7 +98,7 @@ renderer->row[outX] |= FLAG_OBJWIN; \

} #define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6); -#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * (GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80) + (localY & 0x7) * 8; +#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 8; #define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \ LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \

@@ -92,6 +107,19 @@ current = renderer->spriteLayer[outX]; \

if ((current & FLAG_ORDER_MASK) > flags) { \ if (tileData) { \ renderer->spriteLayer[outX] = palette[tileData] | flags; \ + } else if (current != FLAG_UNWRITTEN) { \ + renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ + } \ + } + +#define SPRITE_DRAW_PIXEL_256_NORMAL_OBJWIN(localX) \ + LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \ + tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \ + current = renderer->spriteLayer[outX]; \ + if ((current & FLAG_ORDER_MASK) > flags) { \ + if (tileData) { \ + unsigned color = (renderer->row[outX] & FLAG_OBJWIN) ? objwinPalette[tileData] : palette[tileData]; \ + renderer->spriteLayer[outX] = color | flags; \ } else if (current != FLAG_UNWRITTEN) { \ renderer->spriteLayer[outX] = (current & ~FLAG_ORDER_MASK) | GBAObjAttributesCGetPriority(sprite->c) << OFFSET_PRIORITY; \ } \

@@ -119,23 +147,32 @@ unsigned charBase = GBAObjAttributesCGetTile(sprite->c) * 0x20;

if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) { return 0; } - int variant = renderer->target1Obj && GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); + int variant = renderer->target1Obj && + GBAWindowControlIsBlendEnable(renderer->currentWindow.packed) && + (renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN); if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT) { int target2 = renderer->target2Bd << 4; target2 |= renderer->bg[0].target2 << (renderer->bg[0].priority); target2 |= renderer->bg[1].target2 << (renderer->bg[1].priority); target2 |= renderer->bg[2].target2 << (renderer->bg[2].priority); target2 |= renderer->bg[3].target2 << (renderer->bg[3].priority); - if (GBAObjAttributesCGetPriority(sprite->c) < target2) { + if ((1 << GBAObjAttributesCGetPriority(sprite->c)) <= target2) { variant = 0; } } color_t* palette = &renderer->normalPalette[0x100]; + color_t* objwinPalette = palette; + int objwinSlowPath = GBARegisterDISPCNTIsObjwinEnable(renderer->dispcnt) && GBAWindowControlGetBlendEnable(renderer->objwin.packed) != GBAWindowControlIsBlendEnable(renderer->currentWindow.packed); + if (variant) { palette = &renderer->variantPalette[0x100]; + if (GBAWindowControlIsBlendEnable(renderer->objwin.packed)) { + objwinPalette = palette; + } } int inY = y - (int) GBAObjAttributesAGetY(sprite->a); + int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? width : 0x80; uint32_t current; if (GBAObjAttributesAIsTransformed(sprite->a)) {

@@ -159,12 +196,17 @@ if (!GBAObjAttributesAIs256Color(sprite->a)) {

palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4]; if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(16, OBJWIN); + } else if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_TRANSFORMED_LOOP(16, NORMAL_OBJWIN); } else { SPRITE_TRANSFORMED_LOOP(16, NORMAL); } } else { if (flags & FLAG_OBJWIN) { SPRITE_TRANSFORMED_LOOP(256, OBJWIN); + } else if (objwinSlowPath) { + SPRITE_TRANSFORMED_LOOP(256, NORMAL_OBJWIN); } else { SPRITE_TRANSFORMED_LOOP(256, NORMAL); }

@@ -199,7 +241,15 @@ palette = &palette[GBAObjAttributesCGetPalette(sprite->c) << 4];

if (flags & FLAG_OBJWIN) { SPRITE_NORMAL_LOOP(16, OBJWIN); } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(16, NORMAL); + if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_MOSAIC_LOOP(16, NORMAL_OBJWIN); + } else { + SPRITE_MOSAIC_LOOP(16, NORMAL); + } + } else if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_NORMAL_LOOP(16, NORMAL_OBJWIN); } else { SPRITE_NORMAL_LOOP(16, NORMAL); }

@@ -207,7 +257,14 @@ } else {

if (flags & FLAG_OBJWIN) { SPRITE_NORMAL_LOOP(256, OBJWIN); } else if (GBAObjAttributesAIsMosaic(sprite->a)) { - SPRITE_MOSAIC_LOOP(256, NORMAL); + if (objwinSlowPath) { + objwinPalette = &objwinPalette[GBAObjAttributesCGetPalette(sprite->c) << 4]; + SPRITE_MOSAIC_LOOP(256, NORMAL_OBJWIN); + } else { + SPRITE_MOSAIC_LOOP(256, NORMAL); + } + } else if (objwinSlowPath) { + SPRITE_NORMAL_LOOP(256, NORMAL_OBJWIN); } else { SPRITE_NORMAL_LOOP(256, NORMAL); }
M src/gba/renderers/software-private.hsrc/gba/renderers/software-private.h

@@ -42,7 +42,7 @@ if (color >= current) {

if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = current & 0x00FFFFFF; + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } } else { color = (color & ~FLAG_TARGET_2) | (current & FLAG_OBJWIN);

@@ -55,7 +55,7 @@ if (color >= current) {

if (current & FLAG_TARGET_1 && color & FLAG_TARGET_2) { color = _mix(renderer->blda, current, renderer->bldb, color); } else { - color = current & 0x00FFFFFF; + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } } else { color = color & ~FLAG_TARGET_2;

@@ -67,16 +67,20 @@ static inline void _compositeNoBlendObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color,

uint32_t current) { UNUSED(renderer); if (color < current) { - *pixel = color | (current & FLAG_OBJWIN); + color |= (current & FLAG_OBJWIN); + } else { + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } + *pixel = color; } static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* renderer, uint32_t* pixel, uint32_t color, uint32_t current) { UNUSED(renderer); - if (color < current) { - *pixel = color; + if (color >= current) { + color = (current & 0x00FFFFFF) | ((current << 1) & FLAG_REBLEND); } + *pixel = color; } #define COMPOSITE_16_OBJWIN(BLEND) \

@@ -180,7 +184,7 @@ GBAWindowControlIsBlendEnable(renderer->objwin.packed)); \

objwinFlags |= flags; \ flags |= FLAG_TARGET_1 * (background->target1 && renderer->blendEffect == BLEND_ALPHA && \ GBAWindowControlIsBlendEnable(renderer->currentWindow.packed)); \ - if (renderer->blda == 0x10 && renderer->bldb == 0) { \ + if (renderer->blendEffect == BLEND_ALPHA && renderer->blda == 0x10 && renderer->bldb == 0) { \ flags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ objwinFlags &= ~(FLAG_TARGET_1 | FLAG_TARGET_2); \ } \
M src/gba/renderers/video-software.csrc/gba/renderers/video-software.c

@@ -523,7 +523,7 @@

if (softwareRenderer->target2Bd) { x = 0; for (w = 0; w < softwareRenderer->nWindows; ++w) { - uint32_t backdrop = FLAG_UNWRITTEN; + uint32_t backdrop = 0; if (!softwareRenderer->target1Bd || softwareRenderer->blendEffect == BLEND_NONE || softwareRenderer->blendEffect == BLEND_ALPHA || !GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { backdrop |= softwareRenderer->normalPalette[0]; } else {

@@ -534,6 +534,30 @@ for (; x < end; ++x) {

uint32_t color = softwareRenderer->row[x]; if (color & FLAG_TARGET_1) { softwareRenderer->row[x] = _mix(softwareRenderer->bldb, backdrop, softwareRenderer->blda, color); + } + } + } + } + if (softwareRenderer->target1Obj && (softwareRenderer->blendEffect == BLEND_DARKEN || softwareRenderer->blendEffect == BLEND_BRIGHTEN)) { + x = 0; + for (w = 0; w < softwareRenderer->nWindows; ++w) { + if (!GBAWindowControlIsBlendEnable(softwareRenderer->windows[w].control.packed)) { + continue; + } + int end = softwareRenderer->windows[w].endX; + if (softwareRenderer->blendEffect == BLEND_DARKEN) { + for (; x < end; ++x) { + uint32_t color = softwareRenderer->row[x]; + if ((color & 0xFF000000) == FLAG_REBLEND) { + softwareRenderer->row[x] = _darken(color, softwareRenderer->bldy); + } + } + } else if (softwareRenderer->blendEffect == BLEND_BRIGHTEN) { + for (; x < end; ++x) { + uint32_t color = softwareRenderer->row[x]; + if ((color & 0xFF000000) == FLAG_REBLEND) { + softwareRenderer->row[x] = _brighten(color, softwareRenderer->bldy); + } } } }
M src/gba/renderers/video-software.hsrc/gba/renderers/video-software.h

@@ -71,6 +71,7 @@ #define FLAG_PRIORITY 0xC0000000

#define FLAG_INDEX 0x30000000 #define FLAG_IS_BACKGROUND 0x08000000 #define FLAG_UNWRITTEN 0xFC000000 +#define FLAG_REBLEND 0x04000000 #define FLAG_TARGET_1 0x02000000 #define FLAG_TARGET_2 0x01000000 #define FLAG_OBJWIN 0x01000000
M src/gba/serialize.csrc/gba/serialize.c

@@ -87,50 +87,12 @@ if (state->cpu.cycles < 0) {

GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are negative"); error = true; } - if (state->cpu.nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: Next event is negative"); + if (state->cpu.cycles >= (int32_t) GBA_ARM7TDMI_FREQUENCY) { + GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: CPU cycles are too high"); error = true; } if (state->video.eventDiff < 0) { GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: video eventDiff is negative"); - error = true; - } - if (state->video.nextHblank - state->video.eventDiff < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: nextHblank is negative"); - error = true; - } - if (state->timers[0].overflowInterval < 0 || state->timers[1].overflowInterval < 0 || state->timers[2].overflowInterval < 0 || state->timers[3].overflowInterval < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: overflowInterval is negative"); - error = true; - } - if (state->timers[0].nextEvent < 0 || state->timers[1].nextEvent < 0 || state->timers[2].nextEvent < 0 || state->timers[3].nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: timer nextEvent is negative"); - error = true; - } - if (state->audio.eventDiff < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio eventDiff is negative"); - error = true; - } - if (!state->audio.ch1Dead && (state->audio.ch1.envelopeNextStep < 0 || - state->audio.ch1.waveNextStep < 0 || - state->audio.ch1.sweepNextStep < 0 || - state->audio.ch1.nextEvent < 0)) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 1 register is negative"); - error = true; - } - if (!state->audio.ch2Dead && (state->audio.ch2.envelopeNextStep < 0 || - state->audio.ch2.waveNextStep < 0 || - state->audio.ch2.nextEvent < 0)) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 2 register is negative"); - error = true; - } - if (state->audio.ch3.nextEvent < 0) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 3 register is negative"); - error = true; - } - if (!state->audio.ch4Dead && (state->audio.ch4.envelopeNextStep < 0 || - state->audio.ch4.nextEvent < 0)) { - GBALog(gba, GBA_LOG_WARN, "Savestate is corrupted: audio channel 4 register is negative"); error = true; } int region = (state->cpu.gprs[ARM_PC] >> BASE_OFFSET);

@@ -432,3 +394,25 @@

void GBARewindAll(struct GBAThread* thread) { GBARewind(thread, thread->rewindBufferSize); } + +void GBATakeScreenshot(struct GBA* gba, struct VDir* dir) { +#ifdef USE_PNG + unsigned stride; + const void* pixels = 0; + struct VFile* vf = VDirOptionalOpenIncrementFile(dir, gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY); + bool success = false; + if (vf) { + gba->video.renderer->getPixels(gba->video.renderer, &stride, &pixels); + png_structp png = PNGWriteOpen(vf); + png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); + success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); + PNGWriteClose(png, info); + vf->close(vf); + } + if (success) { + GBALog(gba, GBA_LOG_STATUS, "Screenshot saved"); + return; + } +#endif + GBALog(gba, GBA_LOG_STATUS, "Failed to take screenshot"); +}
M src/gba/serialize.hsrc/gba/serialize.h

@@ -345,4 +345,6 @@ void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int newInterval);

int GBARewind(struct GBAThread* thread, int nStates); void GBARewindAll(struct GBAThread* thread); +void GBATakeScreenshot(struct GBA* gba, struct VDir* dir); + #endif
M src/gba/supervisor/thread.csrc/gba/supervisor/thread.c

@@ -16,7 +16,6 @@

#include "debugger/debugger.h" #include "util/patch.h" -#include "util/png-io.h" #include "util/vfs.h" #include "platform/commandline.h"

@@ -141,6 +140,7 @@ threadContext->cpu = &cpu;

gba.logLevel = threadContext->logLevel; gba.logHandler = threadContext->logHandler; gba.stream = threadContext->stream; + gba.video.frameskip = threadContext->frameskip; struct GBAThreadStop stop; if (threadContext->stopCallback) {

@@ -387,7 +387,6 @@ // TODO: error check

threadContext->activeKeys = 0; threadContext->state = THREAD_INITIALIZED; threadContext->sync.videoFrameOn = true; - threadContext->sync.videoFrameSkip = 0; threadContext->rewindBuffer = 0; threadContext->rewindScreenBuffer = 0;

@@ -684,16 +683,9 @@

void GBAThreadLoadROM(struct GBAThread* threadContext, const char* fname) { threadContext->rom = VFileOpen(fname, O_RDONLY); threadContext->gameDir = 0; -#if USE_LIBZIP if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpenZip(fname, 0); - } -#endif -#if USE_LZMA - if (!threadContext->gameDir) { - threadContext->gameDir = VDirOpen7z(fname, 0); + threadContext->gameDir = VDirOpenArchive(fname); } -#endif } static void _loadGameDir(struct GBAThread* threadContext) {

@@ -754,22 +746,9 @@ return TlsGetValue(_contextKey);

} #endif -#ifdef USE_PNG void GBAThreadTakeScreenshot(struct GBAThread* threadContext) { - unsigned stride; - const void* pixels = 0; - struct VFile* vf = VDirOptionalOpenIncrementFile(threadContext->stateDir, threadContext->gba->activeFile, "screenshot", "-", ".png", O_CREAT | O_TRUNC | O_WRONLY); - threadContext->gba->video.renderer->getPixels(threadContext->gba->video.renderer, &stride, &pixels); - png_structp png = PNGWriteOpen(vf); - png_infop info = PNGWriteHeader(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); - bool success = PNGWritePixels(png, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, stride, pixels); - PNGWriteClose(png, info); - vf->close(vf); - if (success) { - GBALog(threadContext->gba, GBA_LOG_STATUS, "Screenshot saved"); - } + GBATakeScreenshot(threadContext->gba, threadContext->stateDir); } -#endif #else struct GBAThread* GBAThreadGetContext(void) {
M src/gba/video.csrc/gba/video.c

@@ -59,6 +59,7 @@

void GBAVideoInit(struct GBAVideo* video) { video->renderer = &dummyRenderer; video->vram = 0; + video->frameskip = 0; } void GBAVideoReset(struct GBAVideo* video) {

@@ -70,7 +71,6 @@ video->vcount = 0x7E;

} video->p->memory.io[REG_VCOUNT >> 1] = video->vcount; - video->lastHblank = 0; video->nextHblank = VIDEO_HDRAW_LENGTH; video->nextEvent = video->nextHblank; video->eventDiff = 0;

@@ -80,6 +80,7 @@ video->nextVblankIRQ = 0;

video->nextVcounterIRQ = 0; video->frameCounter = 0; + video->frameskipCounter = 0; if (video->vram) { mappedMemoryFree(video->vram, SIZE_VRAM);

@@ -89,10 +90,10 @@ video->renderer->vram = video->vram;

int i; for (i = 0; i < 128; ++i) { - video->oam.raw[i * 4] = 0x0200; - video->oam.raw[i * 4 + 1] = 0x0000; - video->oam.raw[i * 4 + 2] = 0x0000; - video->oam.raw[i * 4 + 3] = 0x0000; + STORE_16(0x0200, i * 8 + 0, video->oam.raw); + STORE_16(0x0000, i * 8 + 2, video->oam.raw); + STORE_16(0x0000, i * 8 + 4, video->oam.raw); + STORE_16(0x0000, i * 8 + 6, video->oam.raw); } video->renderer->deinit(video->renderer);

@@ -118,7 +119,6 @@ video->nextEvent -= cycles;

video->eventDiff += cycles; if (video->nextEvent <= 0) { int32_t lastEvent = video->nextEvent; - video->lastHblank -= video->eventDiff; video->nextHblank -= video->eventDiff; video->nextHblankIRQ -= video->eventDiff; video->nextVcounterIRQ -= video->eventDiff;

@@ -154,7 +154,7 @@ GBAFrameStarted(video->p);

break; case VIDEO_VERTICAL_PIXELS: video->p->memory.io[REG_DISPSTAT >> 1] = GBARegisterDISPSTATFillInVblank(dispstat); - if (GBASyncDrawingFrame(video->p->sync)) { + if (video->frameskipCounter <= 0) { video->renderer->finishFrame(video->renderer); } video->nextVblankIRQ = video->nextEvent + VIDEO_TOTAL_LENGTH;

@@ -163,7 +163,11 @@ if (GBARegisterDISPSTATIsVblankIRQ(dispstat)) {

GBARaiseIRQ(video->p, IRQ_VBLANK); } GBAFrameEnded(video->p); - GBASyncPostFrame(video->p->sync); + --video->frameskipCounter; + if (video->frameskipCounter < 0) { + GBASyncPostFrame(video->p->sync); + video->frameskipCounter = video->frameskip; + } ++video->frameCounter; break; case VIDEO_VERTICAL_TOTAL_PIXELS - 1:

@@ -173,12 +177,11 @@ }

} else { // Begin Hblank dispstat = GBARegisterDISPSTATFillInHblank(dispstat); - video->lastHblank = video->nextHblank; - video->nextEvent = video->lastHblank + VIDEO_HBLANK_LENGTH; + video->nextEvent = video->nextHblank + VIDEO_HBLANK_LENGTH; video->nextHblank = video->nextEvent + VIDEO_HDRAW_LENGTH; video->nextHblankIRQ = video->nextHblank; - if (video->vcount < VIDEO_VERTICAL_PIXELS && GBASyncDrawingFrame(video->p->sync)) { + if (video->vcount < VIDEO_VERTICAL_PIXELS && video->frameskipCounter <= 0) { video->renderer->drawScanline(video->renderer, video->vcount); }

@@ -273,7 +276,7 @@ memcpy(state->oam, video->oam.raw, SIZE_OAM);

memcpy(state->pram, video->palette, SIZE_PALETTE_RAM); state->video.nextEvent = video->nextEvent; state->video.eventDiff = video->eventDiff; - state->video.lastHblank = video->lastHblank; + state->video.lastHblank = video->nextHblank - VIDEO_HBLANK_LENGTH; state->video.nextHblank = video->nextHblank; state->video.nextHblankIRQ = video->nextHblankIRQ; state->video.nextVblankIRQ = video->nextVblankIRQ;

@@ -283,16 +286,18 @@ }

void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState* state) { memcpy(video->renderer->vram, state->vram, SIZE_VRAM); + uint16_t value; int i; for (i = 0; i < SIZE_OAM; i += 2) { - GBAStore16(video->p->cpu, BASE_OAM | i, state->oam[i >> 1], 0); + LOAD_16(value, i, state->oam); + GBAStore16(video->p->cpu, BASE_OAM | i, value, 0); } for (i = 0; i < SIZE_PALETTE_RAM; i += 2) { - GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, state->pram[i >> 1], 0); + LOAD_16(value, i, state->pram); + GBAStore16(video->p->cpu, BASE_PALETTE_RAM | i, value, 0); } video->nextEvent = state->video.nextEvent; video->eventDiff = state->video.eventDiff; - video->lastHblank = state->video.lastHblank; video->nextHblank = state->video.nextHblank; video->nextHblankIRQ = state->video.nextHblankIRQ; video->nextVblankIRQ = state->video.nextVblankIRQ;
M src/gba/video.hsrc/gba/video.h

@@ -185,7 +185,6 @@

// VCOUNT int vcount; - int32_t lastHblank; int32_t nextHblank; int32_t nextEvent; int32_t eventDiff;

@@ -199,6 +198,8 @@ uint16_t* vram;

union GBAOAM oam; int32_t frameCounter; + int frameskip; + int frameskipCounter; }; void GBAVideoInit(struct GBAVideo* video);
M src/platform/3ds/3ds-memory.csrc/platform/3ds/3ds-memory.c

@@ -5,8 +5,6 @@ * 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/memory.h" -#define asm __asm__ - #include <3ds.h> void* anonymousMemoryMap(size_t size) {
M src/platform/3ds/3ds-vfs.csrc/platform/3ds/3ds-vfs.c

@@ -44,6 +44,7 @@ static bool _vd3dClose(struct VDir* vd);

static void _vd3dRewind(struct VDir* vd); static struct VDirEntry* _vd3dListNext(struct VDir* vd); static struct VFile* _vd3dOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path); static const char* _vd3deName(struct VDirEntry* vde); static enum VFSType _vd3deType(struct VDirEntry* vde);

@@ -185,8 +186,9 @@ vd3d->path = strdup(path);

vd3d->d.close = _vd3dClose; vd3d->d.rewind = _vd3dRewind; - vd3d->d.listNext = _vd3dListNext; //// Crashes here for no good reason + vd3d->d.listNext = _vd3dListNext; vd3d->d.openFile = _vd3dOpenFile; + vd3d->d.openDir = _vd3dOpenDir; vd3d->vde.d.name = _vd3deName; vd3d->vde.d.type = _vd3deType;

@@ -233,6 +235,23 @@

struct VFile* file = VFileOpen(combined, mode); free(combined); return file; +} + +static struct VDir* _vd3dOpenDir(struct VDir* vd, const char* path) { + struct VDir3DS* vd3d = (struct VDir3DS*) vd; + if (!path) { + return 0; + } + const char* dir = vd3d->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s/%s", dir, path); + + struct VDir* vd2 = VDirOpen(combined); + if (!vd2) { + vd2 = VDirOpenArchive(combined); + } + free(combined); + return vd2; } static const char* _vd3deName(struct VDirEntry* vde) {
M src/platform/3ds/3ds-vfs.hsrc/platform/3ds/3ds-vfs.h

@@ -8,8 +8,6 @@ #define N3DS_VFS_H

#include "util/vfs.h" -#define asm __asm__ - #include <3ds.h> extern FS_archive sdmcArchive;
M src/platform/3ds/CMakeLists.txtsrc/platform/3ds/CMakeLists.txt

@@ -1,5 +1,95 @@

-add_executable(${BINARY_NAME}.elf ${GUI_SRC}) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") -target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} m ${OS_LIB}) -add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx) -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx DESTINATION . COMPONENT ${BINARY_NAME}-3ds) +set(USE_VFS_3DS ON CACNE BOOL "Use 3DS-specific file support") +mark_as_advanced(USE_VFS_3DS) + +find_program(3DSLINK 3dslink) +find_program(3DSXTOOL 3dsxtool) +find_program(BANNERTOOL bannertool) +find_program(MAKEROM makerom) +find_program(PICASSO picasso) +find_program(RAW2C raw2c) +find_program(STRIP ${cross_prefix}strip) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-format" PARENT_SCOPE) +set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +list(APPEND OS_LIB ctru) +file(GLOB OS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/3ds-*.c) +set(OS_SRC ${OS_SRC} PARENT_SCOPE) +source_group("3DS-specific code" FILES ${OS_SRC}) + +if(USE_VFS_3DS) + list(APPEND OS_DEFINES USE_VFS_3DS) +else() + list(APPEND OS_DEFINES USE_VFS_FILE) + list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) +endif() +set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) +set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) + +list(APPEND GUI_SRC + ${CMAKE_CURRENT_BINARY_DIR}/font.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + + ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c + ${CMAKE_CURRENT_SOURCE_DIR}/ctr-gpu.c + ${CMAKE_CURRENT_SOURCE_DIR}/ctr-gpu.h) + +set_source_files_properties( + ${CMAKE_CURRENT_BINARY_DIR}/font.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.c + ${CMAKE_CURRENT_BINARY_DIR}/uishader.h + ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c ctru-heap.c) +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") +target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${M_LIBRARY} ${OS_LIB}) + +add_custom_command(OUTPUT ${BINARY_NAME}.smdh + COMMAND ${BANNERTOOL} makesmdh -s "${PROJECT_NAME}" -l "${SUMMARY}" -p "endrift" -i ${CMAKE_SOURCE_DIR}/res/mgba-48.png -o ${BINARY_NAME}.smdh + DEPENDS ${CMAKE_SOURCE_DIR}/res/mgba-48.png) + +add_custom_command(OUTPUT ${BINARY_NAME}.bnr + COMMAND ${BANNERTOOL} makebanner -i ${CMAKE_CURRENT_SOURCE_DIR}/logo.png -a ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav -o ${BINARY_NAME}.bnr + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/logo.png ${CMAKE_CURRENT_SOURCE_DIR}/bios.wav) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c + COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw + DEPENDS ${CMAKE_SOURCE_DIR}/src/platform/3ds/font.raw) + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh + COMMAND ${PICASSO} + -o ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + -h ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin.h + ${CMAKE_CURRENT_SOURCE_DIR}/uishader.vsh + COMMENT "picasso uishader.vsh") + +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/uishader.c ${CMAKE_CURRENT_BINARY_DIR}/uishader.h + MAIN_DEPENDENCY ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + COMMAND ${RAW2C} ${CMAKE_CURRENT_BINARY_DIR}/uishader.shbin + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "raw2c uishader.shbin") + +add_custom_target(${BINARY_NAME}.3dsx ALL + ${3DSXTOOL} ${BINARY_NAME}.elf ${BINARY_NAME}.3dsx --smdh=${BINARY_NAME}.smdh + DEPENDS ${BINARY_NAME}.elf ${BINARY_NAME}.smdh) + +add_custom_target(${BINARY_NAME}.cia ALL + ${STRIP} -o ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf + COMMAND ${MAKEROM} -f cia -o ${BINARY_NAME}.cia -rsf cia.rsf -target t -exefslogo -elf ${BINARY_NAME}-stripped.elf -icon ${BINARY_NAME}.smdh -banner ${BINARY_NAME}.bnr + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf ${BINARY_NAME}.elf ${BINARY_NAME}.smdh ${BINARY_NAME}.bnr) + +add_custom_target(run ${3DSLINK} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx + DEPENDS ${BINARY_NAME}.3dsx) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cia.rsf.in ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf) +install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh + ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.cia + DESTINATION . COMPONENT ${BINARY_NAME}-3ds)
M src/platform/3ds/CMakeToolchain.txtsrc/platform/3ds/CMakeToolchain.txt

@@ -10,33 +10,40 @@ else()

set(DEVKITARM ${DEVKITPRO}/devkitARM) endif() -set(toolchain_bin_dir ${DEVKITARM}/bin) -set(cross_prefix ${toolchain_bin_dir}/arm-none-eabi-) -set(inc_flags -I${DEVKITPRO}/libctru/include) +if(DEFINED ENV{CTRULIB}) + set(CTRULIB $ENV{CTRULIB}) +else() + set(CTRULIB ${DEVKITPRO}/libctru) +endif() + +set(extension) +if (CMAKE_HOST_WIN32) + set(extension .exe) +endif() + +set(CMAKE_PROGRAM_PATH ${DEVKITARM}/bin) +set(cross_prefix ${CMAKE_PROGRAM_PATH}/arm-none-eabi-) set(arch_flags "-march=armv6k -mtune=mpcore -mfpu=vfp -mfloat-abi=hard") -set(link_flags "-L${DEVKITPRO}/libctru/lib -lctru -lm -specs=3dsx.specs ${arch_flags}") +set(inc_flags "-I${CTRULIB}/include ${arch_flags} -mword-relocations") +set(link_flags "-L${CTRULIB}/lib -lctru -specs=3dsx.specs ${arch_flags}") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor") set(CMAKE_LIBRARY_ARCHITECTURE arm-none-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "ranlib") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -set(common_flags "${arch_flags} -mword-relocations ${inc_flags}") -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_C_FLAGS_RELEASE -Ofast CACHE INTERNAL "c compiler flags (release)") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") + +set(CMAKE_AR ${cross_prefix}gcc-ar${extension} CACHE INTERNAL "archiver") +set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib${extension} CACHE INTERNAL "archiver") +set(CMAKE_C_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "c compiler") +set(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension} CACHE INTERNAL "cxx compiler") +set(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "assembler") +set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") - -set(3DSXTOOL ${toolchain_bin_dir}/3dsxtool) -set(RAW2C ${toolchain_bin_dir}/raw2c) set(3DS ON) add_definitions(-D_3DS -DARM11)
A src/platform/3ds/cia.rsf.in

@@ -0,0 +1,239 @@

+BasicInfo: + Title : "${PROJECT_NAME}" + CompanyCode : "00" + ProductCode : "CTR-P-MGBA" + ContentType : Application + Logo : Nintendo # Nintendo / Licensed / Distributed / iQue / iQueForSystem + +#Rom: + # Specifies the root path of the file system to include in the ROM. + # HostRoot : "romfs" + + +TitleInfo: + UniqueId : 0x1A1E + Category : Application + +CardInfo: + MediaSize : 128MB # 128MB / 256MB / 512MB / 1GB / 2GB / 4GB / 8GB / 16GB / 32GB + MediaType : Card1 # Card1 / Card2 + CardDevice : None # NorFlash(Pick this if you use savedata) / None + + +Option: + UseOnSD : true # true if App is to be installed to SD + FreeProductCode : true # Removes limitations on ProductCode + MediaFootPadding : false # If true CCI files are created with padding + EnableCrypt : false # Enables encryption for NCCH and CIA + EnableCompress : true # Compresses exefs code + +ExeFs: # these are the program segments from the ELF, check your elf for the appropriate segment names + ReadOnly: + - .rodata + - RO + ReadWrite: + - .data + - RO + Text: + - .init + - .text + - STUP_ENTRY + +PlainRegion: # only used with SDK ELFs + # - .module_id + +AccessControlInfo: + # UseOtherVariationSaveData : true + # UseExtSaveData : true + # ExtSaveDataId: 0xffffffff + # SystemSaveDataId1: 0x220 + # SystemSaveDataId2: 0x00040010 + # OtherUserSaveDataId1: 0x220 + # OtherUserSaveDataId2: 0x330 + # OtherUserSaveDataId3: 0x440 + # UseExtendedSaveDataAccessControl: true + # AccessibleSaveDataIds: [0x101, 0x202, 0x303, 0x404, 0x505, 0x606] + FileSystemAccess: + # - CategorySystemApplication + # - CategoryHardwareCheck + # - CategoryFileSystemTool + - Debug + # - TwlCardBackup + # - TwlNandData + # - Boss + - DirectSdmc + # - Core + # - CtrNandRo + # - CtrNandRw + # - CtrNandRoWrite + # - CategorySystemSettings + # - CardBoard + # - ExportImportIvs + # - DirectSdmcWrite + # - SwitchCleanup + # - SaveDataMove + # - Shop + # - Shell + # - CategoryHomeMenu + IoAccessControl: + # - FsMountNand + # - FsMountNandRoWrite + # - FsMountTwln + # - FsMountWnand + # - FsMountCardSpi + # - UseSdif3 + # - CreateSeed + # - UseCardSpi + + IdealProcessor : 0 + AffinityMask : 1 + + Priority : 16 + + MaxCpu : 0x9E # Default + CpuSpeed : 804mhz + + DisableDebug : true + EnableForceDebug : false + CanWriteSharedPage : true + CanUsePrivilegedPriority : false + CanUseNonAlphabetAndNumber : true + PermitMainFunctionArgument : true + CanShareDeviceMemory : true + RunnableOnSleep : false + SpecialMemoryArrange : true + + CoreVersion : 2 + DescVersion : 2 + + ReleaseKernelMajor : "02" + ReleaseKernelMinor : "33" + MemoryType : Application # Application / System / Base + HandleTableSize: 512 + IORegisterMapping: + - 1ff50000-1ff57fff + - 1ff70000-1ff77fff + MemoryMapping: + - 1f000000-1f5fffff:r + SystemCallAccess: + ArbitrateAddress: 34 + Break: 60 + CancelTimer: 28 + ClearEvent: 25 + ClearTimer: 29 + CloseHandle: 35 + ConnectToPort: 45 + ControlMemory: 1 + CreateAddressArbiter: 33 + CreateEvent: 23 + CreateMemoryBlock: 30 + CreateMutex: 19 + CreateSemaphore: 21 + CreateThread: 8 + CreateTimer: 26 + DuplicateHandle: 39 + ExitProcess: 3 + ExitThread: 9 + GetCurrentProcessorNumber: 17 + GetHandleInfo: 41 + GetProcessId: 53 + GetProcessIdOfThread: 54 + GetProcessIdealProcessor: 6 + GetProcessInfo: 43 + GetResourceLimit: 56 + GetResourceLimitCurrentValues: 58 + GetResourceLimitLimitValues: 57 + GetSystemInfo: 42 + GetSystemTick: 40 + GetThreadContext: 59 + GetThreadId: 55 + GetThreadIdealProcessor: 15 + GetThreadInfo: 44 + GetThreadPriority: 11 + MapMemoryBlock: 31 + OutputDebugString: 61 + QueryMemory: 2 + ReleaseMutex: 20 + ReleaseSemaphore: 22 + SendSyncRequest1: 46 + SendSyncRequest2: 47 + SendSyncRequest3: 48 + SendSyncRequest4: 49 + SendSyncRequest: 50 + SetThreadPriority: 12 + SetTimer: 27 + SignalEvent: 24 + SleepThread: 10 + UnmapMemoryBlock: 32 + WaitSynchronization1: 36 + WaitSynchronizationN: 37 + InterruptNumbers: + ServiceAccessControl: + - APT:U + - $hioFIO + - $hostio0 + - $hostio1 + - ac:u + - boss:U + - cam:u + - cecd:u + - cfg:u + - dlp:FKCL + - dlp:SRVR + - dsp::DSP + - frd:u + - fs:USER + - gsp::Gpu + - hid:USER + - http:C + - mic:u + - ndm:u + - news:u + - nwm::UDS + - ptm:u + - pxi:dev + - soc:U + - ssl:C + - y2r:u + - ldr:ro + - ir:USER + - ir:u + - csnd:SND + + +SystemControlInfo: + SaveDataSize: 0KB # It doesn't use any save data. + RemasterVersion: 2 + StackSize: 0x40000 + # JumpId: 0 + Dependency: + ac: 0x0004013000002402L + am: 0x0004013000001502L + boss: 0x0004013000003402L + camera: 0x0004013000001602L + cecd: 0x0004013000002602L + cfg: 0x0004013000001702L + codec: 0x0004013000001802L + csnd: 0x0004013000002702L + dlp: 0x0004013000002802L + dsp: 0x0004013000001a02L + friends: 0x0004013000003202L + gpio: 0x0004013000001b02L + gsp: 0x0004013000001c02L + hid: 0x0004013000001d02L + http: 0x0004013000002902L + i2c: 0x0004013000001e02L + ir: 0x0004013000003302L + mcu: 0x0004013000001f02L + mic: 0x0004013000002002L + ndm: 0x0004013000002b02L + news: 0x0004013000003502L + nim: 0x0004013000002c02L + nwm: 0x0004013000002d02L + pdn: 0x0004013000002102L + ps: 0x0004013000003102L + ptm: 0x0004013000002202L + ro: 0x0004013000003702L + socket: 0x0004013000002e02L + spi: 0x0004013000002302L + ssl: 0x0004013000002f02L
A src/platform/3ds/ctr-gpu.c

@@ -0,0 +1,462 @@

+/* Copyright (c) 2015 Yuri Kunde Schlesner + * + * 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 <3ds.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "ctr-gpu.h" + +#include "uishader.h" +#include "uishader.shbin.h" + +struct ctrUIVertex { + s16 x,y; + s16 u,v; + u32 abgr; +}; + +#define VRAM_BASE 0x18000000u + +#define MAX_NUM_QUADS 1024 +#define COMMAND_LIST_LENGTH (16 * 1024) +// Each quad requires 4 vertices and 2*3 indices for the two triangles used to draw it +#define VERTEX_INDEX_BUFFER_SIZE (MAX_NUM_QUADS * (4 * sizeof(struct ctrUIVertex) + 6 * sizeof(u16))) + +static struct ctrUIVertex* ctrVertexBuffer = NULL; +static u16* ctrIndexBuffer = NULL; +static u16 ctrNumQuads = 0; + +static void* gpuColorBuffer[2] = { NULL, NULL }; +static u32* gpuCommandList = NULL; +static void* screenTexture = NULL; + +static shaderProgram_s gpuShader; +static DVLB_s* passthroughShader = NULL; + +static int pendingEvents = 0; + +static const struct ctrTexture* activeTexture = NULL; + +static u32 _f24FromFloat(float f) { + u32 i; + memcpy(&i, &f, 4); + + u32 mantissa = (i << 9) >> 9; + s32 exponent = (i << 1) >> 24; + u32 sign = (i << 0) >> 31; + + // Truncate mantissa + mantissa >>= 7; + + // Re-bias exponent + exponent = exponent - 127 + 63; + if (exponent < 0) { + // Underflow: flush to zero + return sign << 23; + } else if (exponent > 0x7F) { + // Overflow: saturate to infinity + return sign << 23 | 0x7F << 16; + } + + return sign << 23 | exponent << 16 | mantissa; +} + +static u32 _f31FromFloat(float f) { + u32 i; + memcpy(&i, &f, 4); + + u32 mantissa = (i << 9) >> 9; + s32 exponent = (i << 1) >> 24; + u32 sign = (i << 0) >> 31; + + // Re-bias exponent + exponent = exponent - 127 + 63; + if (exponent < 0) { + // Underflow: flush to zero + return sign << 30; + } else if (exponent > 0x7F) { + // Overflow: saturate to infinity + return sign << 30 | 0x7F << 23; + } + + return sign << 30 | exponent << 23 | mantissa; +} + +void ctrClearPending(int events) { + int toClear = events & pendingEvents; + if (toClear & (1 << GSPEVENT_PSC0)) { + gspWaitForPSC0(); + } + if (toClear & (1 << GSPEVENT_PPF)) { + gspWaitForPPF(); + } + pendingEvents ^= toClear; +} + +// Replacements for the limiting GPU_SetViewport function in ctrulib +static void _GPU_SetFramebuffer(intptr_t colorBuffer, intptr_t depthBuffer, u16 w, u16 h) { + u32 buf[4]; + + // Unknown + GPUCMD_AddWrite(GPUREG_0111, 0x00000001); + GPUCMD_AddWrite(GPUREG_0110, 0x00000001); + + // Set depth/color buffer address and dimensions + buf[0] = depthBuffer >> 3; + buf[1] = colorBuffer >> 3; + buf[2] = (0x01) << 24 | ((h-1) & 0xFFF) << 12 | (w & 0xFFF) << 0; + GPUCMD_AddIncrementalWrites(GPUREG_DEPTHBUFFER_LOC, buf, 3); + GPUCMD_AddWrite(GPUREG_006E, buf[2]); + + // Set depth/color buffer pixel format + GPUCMD_AddWrite(GPUREG_DEPTHBUFFER_FORMAT, 3 /* D248S */ ); + GPUCMD_AddWrite(GPUREG_COLORBUFFER_FORMAT, 0 /* RGBA8 */ << 16 | 2 /* Unknown */); + GPUCMD_AddWrite(GPUREG_011B, 0); // Unknown + + // Enable color/depth buffers + buf[0] = colorBuffer != 0 ? 0xF : 0x0; + buf[1] = buf[0]; + buf[2] = depthBuffer != 0 ? 0x2 : 0x0; + buf[3] = buf[2]; + GPUCMD_AddIncrementalWrites(GPUREG_0112, buf, 4); +} + +static void _GPU_SetViewportEx(u16 x, u16 y, u16 w, u16 h) { + u32 buf[4]; + + buf[0] = _f24FromFloat(w / 2.0f); + buf[1] = _f31FromFloat(2.0f / w) << 1; + buf[2] = _f24FromFloat(h / 2.0f); + buf[3] = _f31FromFloat(2.0f / h) << 1; + GPUCMD_AddIncrementalWrites(GPUREG_0041, buf, 4); + + GPUCMD_AddWrite(GPUREG_0068, (y & 0xFFFF) << 16 | (x & 0xFFFF) << 0); + + buf[0] = 0; + buf[1] = 0; + buf[2] = ((h-1) & 0xFFFF) << 16 | ((w-1) & 0xFFFF) << 0; + GPUCMD_AddIncrementalWrites(GPUREG_SCISSORTEST_MODE, buf, 3); +} + +static void _setDummyTexEnv(int id) { + GPU_SetTexEnv(id, + GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0), + GPU_TEVSOURCES(GPU_PREVIOUS, 0, 0), + GPU_TEVOPERANDS(0, 0, 0), + GPU_TEVOPERANDS(0, 0, 0), + GPU_REPLACE, + GPU_REPLACE, + 0x00000000); +} + +Result ctrInitGpu() { + Result res = -1; + + // Allocate buffers + gpuColorBuffer[0] = vramAlloc(400 * 240 * 4); + gpuColorBuffer[1] = vramAlloc(320 * 240 * 4); + gpuCommandList = linearAlloc(COMMAND_LIST_LENGTH * sizeof(u32)); + ctrVertexBuffer = linearAlloc(VERTEX_INDEX_BUFFER_SIZE); + if (gpuColorBuffer[0] == NULL || gpuColorBuffer[1] == NULL || gpuCommandList == NULL || ctrVertexBuffer == NULL) { + res = -1; + goto error_allocs; + } + // Both buffers share the same allocation, index buffer follows the vertex buffer + ctrIndexBuffer = (u16*)(ctrVertexBuffer + (4 * MAX_NUM_QUADS)); + + // Load vertex shader binary + passthroughShader = DVLB_ParseFile((u32*)uishader, uishader_size); + if (passthroughShader == NULL) { + res = -1; + goto error_dvlb; + } + + // Create shader + shaderProgramInit(&gpuShader); + res = shaderProgramSetVsh(&gpuShader, &passthroughShader->DVLE[0]); + if (res < 0) { + goto error_shader; + } + + // Initialize the GPU in ctrulib and assign the command buffer to accept submission of commands + GPU_Init(NULL); + GPUCMD_SetBuffer(gpuCommandList, COMMAND_LIST_LENGTH, 0); + + return 0; + +error_shader: + shaderProgramFree(&gpuShader); + +error_dvlb: + if (passthroughShader != NULL) { + DVLB_Free(passthroughShader); + passthroughShader = NULL; + } + +error_allocs: + if (ctrVertexBuffer != NULL) { + linearFree(ctrVertexBuffer); + ctrVertexBuffer = NULL; + ctrIndexBuffer = NULL; + } + + if (gpuCommandList != NULL) { + GPUCMD_SetBuffer(NULL, 0, 0); + linearFree(gpuCommandList); + gpuCommandList = NULL; + } + + if (gpuColorBuffer[0] != NULL) { + vramFree(gpuColorBuffer[0]); + gpuColorBuffer[0] = NULL; + } + + if (gpuColorBuffer[1] != NULL) { + vramFree(gpuColorBuffer[1]); + gpuColorBuffer[1] = NULL; + } + return res; +} + +void ctrDeinitGpu() { + shaderProgramFree(&gpuShader); + + DVLB_Free(passthroughShader); + passthroughShader = NULL; + + linearFree(screenTexture); + screenTexture = NULL; + + linearFree(ctrVertexBuffer); + ctrVertexBuffer = NULL; + ctrIndexBuffer = NULL; + + GPUCMD_SetBuffer(NULL, 0, 0); + linearFree(gpuCommandList); + gpuCommandList = NULL; + + vramFree(gpuColorBuffer[0]); + gpuColorBuffer[0] = NULL; + + vramFree(gpuColorBuffer[1]); + gpuColorBuffer[1] = NULL; +} + +void ctrGpuBeginFrame(int screen) { + if (screen > 1) { + return; + } + + int fw; + if (screen == 0) { + fw = 400; + } else { + fw = 320; + } + + _GPU_SetFramebuffer(osConvertVirtToPhys((u32)gpuColorBuffer[screen]), 0, 240, fw); +} + +void ctrGpuBeginDrawing(void) { + shaderProgramUse(&gpuShader); + + // Disable depth and stencil testing + GPU_SetDepthTestAndWriteMask(false, GPU_ALWAYS, GPU_WRITE_COLOR); + GPU_SetStencilTest(false, GPU_ALWAYS, 0, 0xFF, 0); + GPU_SetStencilOp(GPU_STENCIL_KEEP, GPU_STENCIL_KEEP, GPU_STENCIL_KEEP); + GPU_DepthMap(-1.0f, 0.0f); + + // Enable alpha blending + GPU_SetAlphaBlending( + GPU_BLEND_ADD, GPU_BLEND_ADD, // Operation RGB, Alpha + GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA, // Color src, dst + GPU_SRC_ALPHA, GPU_ONE_MINUS_SRC_ALPHA); // Alpha src, dst + GPU_SetBlendingColor(0, 0, 0, 0); + + // Disable alpha testing + GPU_SetAlphaTest(false, GPU_ALWAYS, 0); + + // Unknown + GPUCMD_AddMaskedWrite(GPUREG_0062, 0x1, 0); + GPUCMD_AddWrite(GPUREG_0118, 0); + + GPU_SetTexEnv(0, + GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // RGB + GPU_TEVSOURCES(GPU_TEXTURE0, GPU_PRIMARY_COLOR, 0), // Alpha + GPU_TEVOPERANDS(0, 0, 0), // RGB + GPU_TEVOPERANDS(0, 0, 0), // Alpha + GPU_MODULATE, GPU_MODULATE, // Operation RGB, Alpha + 0x00000000); // Constant color + _setDummyTexEnv(1); + _setDummyTexEnv(2); + _setDummyTexEnv(3); + _setDummyTexEnv(4); + _setDummyTexEnv(5); + + // Configure vertex attribute format + u32 bufferOffsets[] = { osConvertVirtToPhys((u32)ctrVertexBuffer) - VRAM_BASE }; + u64 arrayTargetAttributes[] = { 0x210 }; + u8 numAttributesInArray[] = { 3 }; + GPU_SetAttributeBuffers( + 3, // Number of attributes + (u32*)VRAM_BASE, // Base address + GPU_ATTRIBFMT(0, 2, GPU_SHORT) | // Attribute format + GPU_ATTRIBFMT(1, 2, GPU_SHORT) | + GPU_ATTRIBFMT(2, 4, GPU_UNSIGNED_BYTE), + 0xFF8, // Non-fixed vertex inputs + 0x210, // Vertex shader input map + 1, // Use 1 vertex array + bufferOffsets, arrayTargetAttributes, numAttributesInArray); +} + +void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h) { + if (screen > 1) { + return; + } + + int fw; + if (screen == 0) { + fw = 400; + } else { + fw = 320; + } + + ctrFlushBatch(); + + void* colorBuffer = (u8*)gpuColorBuffer[screen] + ((fw - w) * 240 * 4); + + const u32 GX_CROP_INPUT_LINES = (1 << 2); + + ctrClearPending(1 << GSPEVENT_PSC0); + ctrClearPending(1 << GSPEVENT_PPF); + + GX_SetDisplayTransfer(NULL, + colorBuffer, GX_BUFFER_DIM(240, fw), + outputFramebuffer, GX_BUFFER_DIM(h, w), + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGBA8) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB8) | + GX_CROP_INPUT_LINES); + pendingEvents |= (1 << GSPEVENT_PPF); +} + +void ctrGpuEndDrawing(void) { + ctrClearPending(1 << GSPEVENT_PPF); + gfxSwapBuffersGpu(); + gspWaitForEvent(GSPEVENT_VBlank0, false); + + void* gpuColorBuffer0End = (char*)gpuColorBuffer[0] + 240 * 400 * 4; + void* gpuColorBuffer1End = (char*)gpuColorBuffer[1] + 240 * 320 * 4; + GX_SetMemoryFill(NULL, + gpuColorBuffer[0], 0x00000000, gpuColorBuffer0End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER, + gpuColorBuffer[1], 0x00000000, gpuColorBuffer1End, GX_FILL_32BIT_DEPTH | GX_FILL_TRIGGER); + pendingEvents |= 1 << GSPEVENT_PSC0; +} + +void ctrSetViewportSize(s16 w, s16 h) { + // Set up projection matrix mapping (0,0) to the top-left and (w,h) to the + // bottom-right, taking into account the 3DS' screens' portrait + // orientation. + float projectionMtx[4 * 4] = { + // Rows are in the order w z y x, because ctrulib + 1.0f, 0.0f, -2.0f / h, 0.0f, + 1.0f, 0.0f, 0.0f, -2.0f / w, + -0.5f, 0.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, 0.0f, + }; + + GPU_SetFloatUniform(GPU_VERTEX_SHADER, VSH_FVEC_projectionMtx, (u32*)&projectionMtx, 4); + _GPU_SetViewportEx(0, 0, h, w); +} + +void ctrActivateTexture(const struct ctrTexture* texture) { + if (activeTexture == texture) { + return; + } + + ctrFlushBatch(); + + GPU_SetTextureEnable(GPU_TEXUNIT0); + GPU_SetTexture( + GPU_TEXUNIT0, (u32*)osConvertVirtToPhys((u32)texture->data), + texture->width, texture->height, + GPU_TEXTURE_MAG_FILTER(texture->filter) | GPU_TEXTURE_MIN_FILTER(texture->filter) | + GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_BORDER) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_BORDER), + texture->format); + GPU_SetTextureBorderColor(GPU_TEXUNIT0, 0x00000000); + + float textureMtx[2 * 4] = { + // Rows are in the order w z y x, because ctrulib + 0.0f, 0.0f, 0.0f, 1.0f / texture->width, + 0.0f, 0.0f, 1.0f / texture->height, 0.0f, + }; + + GPU_SetFloatUniform(GPU_VERTEX_SHADER, VSH_FVEC_textureMtx, (u32*)&textureMtx, 2); + + activeTexture = texture; +} + +void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh) { + if (ctrNumQuads == MAX_NUM_QUADS) { + ctrFlushBatch(); + } + + u16 index = ctrNumQuads * 4; + struct ctrUIVertex* vtx = &ctrVertexBuffer[index]; + vtx->x = x; vtx->y = y; + vtx->u = u; vtx->v = v; + vtx->abgr = color; + vtx++; + + vtx->x = x + w; vtx->y = y; + vtx->u = u + uw; vtx->v = v; + vtx->abgr = color; + vtx++; + + vtx->x = x; vtx->y = y + h; + vtx->u = u; vtx->v = v + vh; + vtx->abgr = color; + vtx++; + + vtx->x = x + w; vtx->y = y + h; + vtx->u = u + uw; vtx->v = v + vh; + vtx->abgr = color; + + u16* i = &ctrIndexBuffer[ctrNumQuads * 6]; + i[0] = index + 0; i[1] = index + 1; i[2] = index + 2; + i[3] = index + 2; i[4] = index + 1; i[5] = index + 3; + + ctrNumQuads += 1; +} + +void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h) { + ctrAddRectScaled(color, + x, y, w, h, + u, v, w, h); +} + +void ctrFlushBatch(void) { + if (ctrNumQuads == 0) { + return; + } + + ctrClearPending((1 << GSPEVENT_PSC0)); + + GSPGPU_FlushDataCache(NULL, (u8*)ctrVertexBuffer, VERTEX_INDEX_BUFFER_SIZE); + GPU_DrawElements(GPU_UNKPRIM, (u32*)(osConvertVirtToPhys((u32)ctrIndexBuffer) - VRAM_BASE), ctrNumQuads * 6); + + GPU_FinishDrawing(); + GPUCMD_Finalize(); + GSPGPU_FlushDataCache(NULL, (u8*)gpuCommandList, COMMAND_LIST_LENGTH * sizeof(u32)); + GPUCMD_FlushAndRun(NULL); + + gspWaitForP3D(); + + GPUCMD_SetBufferOffset(0); + + ctrNumQuads = 0; +}
A src/platform/3ds/ctr-gpu.h

@@ -0,0 +1,43 @@

+/* Copyright (c) 2015 Yuri Kunde Schlesner + * + * 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 GUI_GPU_H +#define GUI_GPU_H + +#include <3ds.h> + +struct ctrTexture { + void* data; + u32 format; + u32 filter; + u16 width; + u16 height; +}; + +inline void ctrTexture_Init(struct ctrTexture* tex) { + tex->data = NULL; + tex->format = GPU_RGB565; + tex->filter = GPU_NEAREST; + tex->width = 0; + tex->height = 0; +} + +Result ctrInitGpu(void); +void ctrDeinitGpu(void); + +void ctrGpuBeginDrawing(void); +void ctrGpuBeginFrame(int screen); +void ctrGpuEndFrame(int screen, void* outputFramebuffer, int w, int h); +void ctrGpuEndDrawing(void); + +void ctrSetViewportSize(s16 w, s16 h); + +void ctrActivateTexture(const struct ctrTexture* texture); +void ctrAddRectScaled(u32 color, s16 x, s16 y, s16 w, s16 h, s16 u, s16 v, s16 uw, s16 vh); +void ctrAddRect(u32 color, s16 x, s16 y, s16 u, s16 v, s16 w, s16 h); +void ctrFlushBatch(void); + +#endif
M src/platform/3ds/ctru-heap.csrc/platform/3ds/ctru-heap.c

@@ -22,6 +22,8 @@

#include <3ds/types.h> #include <3ds/svc.h> +#include "util/common.h" + extern char* fake_heap_start; extern char* fake_heap_end; u32 __linear_heap;

@@ -69,6 +71,8 @@ }

void __attribute__((noreturn)) __libctru_exit(int rc) { + UNUSED(rc); + u32 tmp=0; // Unmap the linear heap
M src/platform/3ds/gui-font.csrc/platform/3ds/gui-font.c

@@ -8,15 +8,14 @@ #include "util/gui/font-metrics.h"

#include "util/png-io.h" #include "util/vfs.h" #include "font.h" - -#include <sf2d.h> +#include "ctr-gpu.h" #define CELL_HEIGHT 16 #define CELL_WIDTH 16 #define GLYPH_HEIGHT 12 struct GUIFont { - sf2d_texture* tex; + struct ctrTexture texture; }; struct GUIFont* GUIFontCreate(void) {

@@ -24,14 +23,23 @@ struct GUIFont* guiFont = malloc(sizeof(struct GUIFont));

if (!guiFont) { return 0; } - guiFont->tex = sf2d_create_texture(256, 128, TEXFMT_RGB5A1, SF2D_PLACE_RAM); - memcpy(guiFont->tex->data, font, font_size); - guiFont->tex->tiled = 1; + + struct ctrTexture* tex = &guiFont->texture; + ctrTexture_Init(tex); + tex->data = vramAlloc(256 * 128 * 2); + tex->format = GPU_RGBA5551; + tex->width = 256; + tex->height = 128; + + GSPGPU_FlushDataCache(NULL, (u8*)font, font_size); + GX_RequestDma(NULL, (u32*)font, tex->data, font_size); + gspWaitForDMA(); + return guiFont; } void GUIFontDestroy(struct GUIFont* font) { - sf2d_free_texture(font->tex); + vramFree(font->texture.data); free(font); }

@@ -48,18 +56,18 @@ }

return defaultFontMetrics[glyph].width; } -void GUIFontDrawGlyph(const struct GUIFont* font, int x, int y, uint32_t color, uint32_t glyph) { +void GUIFontDrawGlyph(const struct GUIFont* font, int glyph_x, int glyph_y, uint32_t color, uint32_t glyph) { + ctrActivateTexture(&font->texture); + if (glyph > 0x7F) { glyph = 0; } - color = (color >> 24) | (color << 8); + struct GUIFontGlyphMetric metric = defaultFontMetrics[glyph]; - sf2d_draw_texture_part_blend(font->tex, - x - metric.padding.left, - y - GLYPH_HEIGHT, - (glyph & 15) * CELL_WIDTH, - (glyph >> 4) * CELL_HEIGHT, - CELL_WIDTH, - CELL_HEIGHT, - color); + u16 x = glyph_x - metric.padding.left; + u16 y = glyph_y - GLYPH_HEIGHT; + u16 u = (glyph % 16u) * CELL_WIDTH; + u16 v = (glyph / 16u) * CELL_HEIGHT; + + ctrAddRect(color, x, y, u, v, CELL_WIDTH, CELL_HEIGHT); }
M src/platform/3ds/main.csrc/platform/3ds/main.c

@@ -11,12 +11,13 @@ #include "gba/video.h"

#include "util/gui.h" #include "util/gui/file-select.h" #include "util/gui/font.h" +#include "util/gui/menu.h" #include "util/memory.h" #include "3ds-vfs.h" +#include "ctr-gpu.h" #include <3ds.h> -#include <sf2d.h> static enum ScreenMode { SM_PA_BOTTOM,

@@ -26,7 +27,7 @@ SM_PA_TOP,

SM_AF_TOP, SM_SF_TOP, SM_MAX -} screenMode = SM_PA_BOTTOM; +} screenMode = SM_PA_TOP; #define AUDIO_SAMPLES 0x80 #define AUDIO_SAMPLE_BUFFER (AUDIO_SAMPLES * 24)

@@ -39,7 +40,6 @@ accelVector accel;

angularRate gyro; } rotation; -static struct VFile* logFile; static bool hasSound; // TODO: Move into context static struct GBAVideoSoftwareRenderer renderer;

@@ -47,43 +47,124 @@ static struct GBAAVStream stream;

static int16_t* audioLeft = 0; static int16_t* audioRight = 0; static size_t audioPos = 0; -static sf2d_texture* tex; +static struct ctrTexture gbaOutputTexture; +static int guiDrawn; +static int screenCleanup; + +enum { + GUI_ACTIVE = 1, + GUI_THIS_FRAME = 2, +}; + +enum { + SCREEN_CLEANUP_TOP_1 = 1, + SCREEN_CLEANUP_TOP_2 = 2, + SCREEN_CLEANUP_TOP = SCREEN_CLEANUP_TOP_1 | SCREEN_CLEANUP_TOP_2, + SCREEN_CLEANUP_BOTTOM_1 = 4, + SCREEN_CLEANUP_BOTTOM_2 = 8, + SCREEN_CLEANUP_BOTTOM = SCREEN_CLEANUP_BOTTOM_1 | SCREEN_CLEANUP_BOTTOM_2, +}; extern bool allocateRomBuffer(void); -static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio); static void _drawStart(void) { - if (screenMode < SM_PA_TOP) { - sf2d_start_frame(GFX_BOTTOM, GFX_LEFT); + ctrGpuBeginDrawing(); + if (screenMode < SM_PA_TOP || (guiDrawn & GUI_ACTIVE)) { + ctrGpuBeginFrame(GFX_BOTTOM); + ctrSetViewportSize(320, 240); } else { - sf2d_start_frame(GFX_TOP, GFX_LEFT); + ctrGpuBeginFrame(GFX_TOP); + ctrSetViewportSize(400, 240); } + guiDrawn &= ~GUI_THIS_FRAME; } static void _drawEnd(void) { - sf2d_end_frame(); - sf2d_swapbuffers(); + int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + u16 width = 0, height = 0; + + void* outputFramebuffer = gfxGetFramebuffer(screen, GFX_LEFT, &height, &width); + ctrGpuEndFrame(screen, outputFramebuffer, width, height); + + if (screen != GFX_BOTTOM) { + if (guiDrawn & (GUI_THIS_FRAME | GUI_ACTIVE)) { + void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); + } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM) { + ctrGpuBeginFrame(GFX_BOTTOM); + if (screenCleanup & SCREEN_CLEANUP_BOTTOM_1) { + screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_1; + } else if (screenCleanup & SCREEN_CLEANUP_BOTTOM_2) { + screenCleanup &= ~SCREEN_CLEANUP_BOTTOM_2; + } + void* outputFramebuffer = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_BOTTOM, outputFramebuffer, width, height); + } + } + + if ((screenCleanup & SCREEN_CLEANUP_TOP) && screen != GFX_TOP) { + ctrGpuBeginFrame(GFX_TOP); + if (screenCleanup & SCREEN_CLEANUP_TOP_1) { + screenCleanup &= ~SCREEN_CLEANUP_TOP_1; + } else if (screenCleanup & SCREEN_CLEANUP_TOP_2) { + screenCleanup &= ~SCREEN_CLEANUP_TOP_2; + } + void* outputFramebuffer = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &height, &width); + ctrGpuEndFrame(GFX_TOP, outputFramebuffer, width, height); + } + + ctrGpuEndDrawing(); +} + +static int _batteryState(void) { + u8 charge; + u8 adapter; + PTMU_GetBatteryLevel(0, &charge); + PTMU_GetBatteryChargeState(0, &adapter); + int state = 0; + if (adapter) { + state |= BATTERY_CHARGING; + } + if (charge > 0) { + --charge; + } + return state | charge; +} + +static void _guiPrepare(void) { + guiDrawn = GUI_ACTIVE | GUI_THIS_FRAME; + int screen = screenMode < SM_PA_TOP ? GFX_BOTTOM : GFX_TOP; + if (screen == GFX_BOTTOM) { + return; + } + + ctrFlushBatch(); + ctrGpuBeginFrame(GFX_BOTTOM); + ctrSetViewportSize(320, 240); +} + +static void _guiFinish(void) { + guiDrawn &= ~GUI_ACTIVE; + screenCleanup |= SCREEN_CLEANUP_BOTTOM; } static void _setup(struct GBAGUIRunner* runner) { - struct GBAOptions opts = { - .useBios = true, - .logLevel = 0, - .idleOptimization = IDLE_LOOP_DETECT - }; - GBAConfigLoadDefaults(&runner->context.config, &opts); - runner->context.gba->logHandler = GBA3DSLog; runner->context.gba->rotationSource = &rotation.d; if (hasSound) { runner->context.gba->stream = &stream; } GBAVideoSoftwareRendererCreate(&renderer); - renderer.outputBuffer = linearMemAlign(256 * 256 * 2, 0x100); + renderer.outputBuffer = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x80); renderer.outputBufferStride = 256; runner->context.renderer = &renderer.d; + + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode < SM_MAX) { + screenMode = mode; + } GBAAudioResizeBuffer(&runner->context.gba->audio, AUDIO_SAMPLES); }

@@ -108,6 +189,11 @@ audioPos = 0;

csndPlaySound(0x8, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, -1.0, audioLeft, audioLeft, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); csndPlaySound(0x9, SOUND_REPEAT | SOUND_FORMAT_16BIT, 32768, 1.0, 1.0, audioRight, audioRight, AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); } + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { + screenMode = mode; + screenCleanup |= SCREEN_CLEANUP_BOTTOM | SCREEN_CLEANUP_TOP; + } } static void _gameUnloaded(struct GBAGUIRunner* runner) {

@@ -126,62 +212,105 @@ }

} static void _drawTex(bool faded) { + u32 color = faded ? 0x3FFFFFFF : 0xFFFFFFFF; + + int screen_w = screenMode < SM_PA_TOP ? 320 : 400; + int screen_h = 240; + + int w, h; + switch (screenMode) { case SM_PA_TOP: - sf2d_draw_texture_scale_blend(tex, 80, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0)); - break; case SM_PA_BOTTOM: - sf2d_draw_texture_scale_blend(tex, 40, 296, 1, -1, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + default: + w = VIDEO_HORIZONTAL_PIXELS; + h = VIDEO_VERTICAL_PIXELS; break; case SM_AF_TOP: - sf2d_draw_texture_scale_blend(tex, 20, 384, 1.5, -1.5, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + w = 360; + h = 240; break; case SM_AF_BOTTOM: - sf2d_draw_texture_scale_blend(tex, 0, 368 - 40 / 3, 4 / 3.0, -4 / 3.0, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + // Largest possible size with 3:2 aspect ratio and integer dimensions + w = 318; + h = 212; break; case SM_SF_TOP: - sf2d_draw_texture_scale_blend(tex, 0, 384, 5 / 3.0, -1.5, 0xFFFFFF3F | (faded ? 0 : 0xC0)); - break; case SM_SF_BOTTOM: - sf2d_draw_texture_scale_blend(tex, 0, 384, 4 / 3.0, -1.5, 0xFFFFFF3F | (faded ? 0 : 0xC0)); + w = screen_w; + h = screen_h; break; } + + int x = (screen_w - w) / 2; + int y = (screen_h - h) / 2; + + ctrAddRectScaled(color, x, y, w, h, 0, 0, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS); } static void _drawFrame(struct GBAGUIRunner* runner, bool faded) { UNUSED(runner); - GSPGPU_FlushDataCache(0, renderer.outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); - GX_SetDisplayTransfer(0, renderer.outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202); - _drawTex(faded); + + void* outputBuffer = renderer.outputBuffer; + struct ctrTexture* tex = &gbaOutputTexture; + + GSPGPU_FlushDataCache(NULL, outputBuffer, 256 * VIDEO_VERTICAL_PIXELS * 2); + GX_SetDisplayTransfer(NULL, + outputBuffer, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), + tex->data, GX_BUFFER_DIM(256, 256), + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1)); + #if RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF if (!hasSound) { blip_clear(runner->context.gba->audio.left); blip_clear(runner->context.gba->audio.right); } #endif + + gspWaitForPPF(); + ctrActivateTexture(tex); + _drawTex(faded); } static void _drawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded) { UNUSED(runner); - u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * 2, 0x100); - unsigned y, x; - for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { - for (x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { - u16 pixel = (*pixels >> 19) & 0x1F; - pixel |= (*pixels >> 5) & 0x7C0; - pixel |= (*pixels << 8) & 0xF800; - newPixels[y * 256 + x] = pixel; - ++pixels; + + struct ctrTexture* tex = &gbaOutputTexture; + + u16* newPixels = linearMemAlign(256 * VIDEO_VERTICAL_PIXELS * sizeof(u32), 0x100); + + // Convert image from RGBX8 to BGR565 + for (unsigned y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + for (unsigned x = 0; x < VIDEO_HORIZONTAL_PIXELS; ++x) { + // 0xXXBBGGRR -> 0bRRRRRGGGGGGBBBBB + u32 p = *pixels++; + newPixels[y * 256 + x] = + (p << 24 >> (24 + 3) << 11) | // R + (p << 16 >> (24 + 2) << 5) | // G + (p << 8 >> (24 + 3) << 0); // B } - memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, 32); + memset(&newPixels[y * 256 + VIDEO_HORIZONTAL_PIXELS], 0, (256 - VIDEO_HORIZONTAL_PIXELS) * sizeof(u32)); } - GSPGPU_FlushDataCache(0, (void*) newPixels, VIDEO_HORIZONTAL_PIXELS * VIDEO_VERTICAL_PIXELS * 2); - GX_SetDisplayTransfer(0, (void*) newPixels, GX_BUFFER_DIM(VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS), tex->data, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), 0x000002202); + + GSPGPU_FlushDataCache(NULL, (void*)newPixels, 256 * VIDEO_VERTICAL_PIXELS * sizeof(u32)); + GX_SetDisplayTransfer(NULL, + (void*)newPixels, GX_BUFFER_DIM(256, VIDEO_VERTICAL_PIXELS), + tex->data, GX_BUFFER_DIM(256, 256), + GX_TRANSFER_IN_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_FORMAT(GX_TRANSFER_FMT_RGB565) | + GX_TRANSFER_OUT_TILED(1) | GX_TRANSFER_FLIP_VERT(1)); + gspWaitForPPF(); linearFree(newPixels); + + ctrActivateTexture(tex); _drawTex(faded); } static uint16_t _pollGameInput(struct GBAGUIRunner* runner) { + UNUSED(runner); + hidScanInput(); uint32_t activeKeys = hidKeysHeld() & 0xF00003FF; activeKeys |= activeKeys >> 24;

@@ -190,12 +319,9 @@ }

static void _incrementScreenMode(struct GBAGUIRunner* runner) { UNUSED(runner); - // Clear the buffer - _drawStart(); - _drawEnd(); - _drawStart(); - _drawEnd(); + screenCleanup |= SCREEN_CLEANUP_TOP | SCREEN_CLEANUP_BOTTOM; screenMode = (screenMode + 1) % SM_MAX; + GBAConfigSetUIntValue(&runner->context.config, "screenMode", screenMode); } static uint32_t _pollInput(void) {

@@ -235,6 +361,18 @@ }

return keys; } +static enum GUICursorState _pollCursor(int* x, int* y) { + hidScanInput(); + if (!(hidKeysHeld() & KEY_TOUCH)) { + return GUI_CURSOR_NOT_PRESENT; + } + touchPosition pos; + hidTouchRead(&pos); + *x = pos.px; + *y = pos.py; + return GUI_CURSOR_DOWN; +} + static void _sampleRotation(struct GBARotationSource* source) { struct GBA3DSRotationSource* rotation = (struct GBA3DSRotationSource*) source; // Work around ctrulib getting the entries wrong

@@ -280,6 +418,7 @@ }

} int main() { + ptmInit(); hasSound = !csndInit(); rotation.d.sample = _sampleRotation;

@@ -296,13 +435,29 @@ return 1;

} if (hasSound) { - audioLeft = linearAlloc(AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); - audioRight = linearAlloc(AUDIO_SAMPLE_BUFFER * sizeof(int16_t)); + audioLeft = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); + audioRight = linearMemAlign(AUDIO_SAMPLE_BUFFER * sizeof(int16_t), 0x80); } - sf2d_init(); - sf2d_set_clear_color(0); - tex = sf2d_create_texture(256, 256, TEXFMT_RGB565, SF2D_PLACE_VRAM); + gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false); + + if (ctrInitGpu() < 0) { + goto cleanup; + } + + ctrTexture_Init(&gbaOutputTexture); + gbaOutputTexture.format = GPU_RGB565; + gbaOutputTexture.filter = GPU_LINEAR; + gbaOutputTexture.width = 256; + gbaOutputTexture.height = 256; + gbaOutputTexture.data = vramAlloc(256 * 256 * 2); + void* outputTextureEnd = (u8*)gbaOutputTexture.data + 256 * 256 * 2; + + // Zero texture data to make sure no garbage around the border interferes with filtering + GX_SetMemoryFill(NULL, + gbaOutputTexture.data, 0x0000, outputTextureEnd, GX_FILL_16BIT_DEPTH | GX_FILL_TRIGGER, + NULL, 0, NULL, 0); + gspWaitForPSC0(); sdmcArchive = (FS_archive) { ARCH_SDMC,

@@ -311,7 +466,6 @@ 0, 0

}; FSUSER_OpenArchive(0, &sdmcArchive); - logFile = VFileOpen("/mgba.log", O_WRONLY | O_CREAT | O_TRUNC); struct GUIFont* font = GUIFontCreate(); if (!font) {

@@ -322,11 +476,31 @@ struct GBAGUIRunner runner = {

.params = { 320, 240, font, "/", - _drawStart, _drawEnd, _pollInput, - 0, 0, + _drawStart, _drawEnd, + _pollInput, _pollCursor, + _batteryState, + _guiPrepare, _guiFinish, GUI_PARAMS_TRAIL }, + .configExtra = (struct GUIMenuItem[]) { + { + .title = "Screen mode", + .data = "screenMode", + .submenu = 0, + .state = SM_PA_TOP, + .validStates = (const char*[]) { + "Pixel-Accurate/Bottom", + "Aspect-Ratio Fit/Bottom", + "Stretched/Bottom", + "Pixel-Accurate/Top", + "Aspect-Ratio Fit/Top", + "Stretched/Top", + 0 + } + } + }, + .nConfigExtra = 1, .setup = _setup, .teardown = 0, .gameLoaded = _gameLoaded,

@@ -339,39 +513,24 @@ .unpaused = _gameLoaded,

.incrementScreenMode = _incrementScreenMode, .pollGameInput = _pollGameInput }; - GBAGUIInit(&runner, 0); + + GBAGUIInit(&runner, "3ds"); GBAGUIRunloop(&runner); GBAGUIDeinit(&runner); cleanup: linearFree(renderer.outputBuffer); - if (logFile) { - logFile->close(logFile); - } + ctrDeinitGpu(); + vramFree(gbaOutputTexture.data); - sf2d_free_texture(tex); - sf2d_fini(); + gfxExit(); if (hasSound) { linearFree(audioLeft); linearFree(audioRight); } csndExit(); + ptmExit(); return 0; } - -static void GBA3DSLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { - UNUSED(thread); - UNUSED(level); - if (!logFile) { - return; - } - char out[256]; - size_t len = vsnprintf(out, sizeof(out), format, args); - if (len >= sizeof(out)) { - len = sizeof(out) - 1; - } - out[len] = '\n'; - logFile->write(logFile, out, len + 1); -}
A src/platform/3ds/uishader.vsh

@@ -0,0 +1,41 @@

+; Copyright (c) 2015 Yuri Kunde Schlesner +; +; 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/. + +; uishader.vsh - Simply multiplies input position and texcoords with +; corresponding matrices before outputting + +; Uniforms +.fvec projectionMtx[4] +.fvec textureMtx[2] + +; Constants +.constf consts1(0.0, 1.0, 0.0039215686, 0.0) + +; Outputs : here only position and color +.out out_pos position +.out out_tc0 texcoord0 +.out out_col color + +; Inputs : here we have only vertices +.alias in_pos v0 +.alias in_tc0 v1 +.alias in_col v2 + +.proc main + dp4 out_pos.x, projectionMtx[0], in_pos + dp4 out_pos.y, projectionMtx[1], in_pos + dp4 out_pos.z, projectionMtx[2], in_pos + dp4 out_pos.w, projectionMtx[3], in_pos + + dp4 out_tc0.x, textureMtx[0], in_tc0 + dp4 out_tc0.y, textureMtx[1], in_tc0 + mov out_tc0.zw, consts1.xxxy + + ; Normalize color by multiplying by 1 / 255 + mul out_col, consts1.z, in_col + + end +.end
M src/platform/commandline.csrc/platform/commandline.c

@@ -71,7 +71,7 @@ }

while ((ch = getopt_long(argc, argv, options, _options, 0)) != -1) { switch (ch) { case 'b': - GBAConfigSetDefaultValue(config, "bios", optarg); + GBAConfigSetOverrideValue(config, "bios", optarg); break; case 'c': opts->cheatsFile = strdup(optarg);

@@ -99,13 +99,13 @@ case 'h':

opts->showHelp = true; break; case 'l': - GBAConfigSetDefaultValue(config, "logLevel", optarg); + GBAConfigSetOverrideValue(config, "logLevel", optarg); break; case 'p': opts->patch = strdup(optarg); break; case 's': - GBAConfigSetDefaultValue(config, "frameskip", optarg); + GBAConfigSetOverrideValue(config, "frameskip", optarg); break; case 'v': opts->movie = strdup(optarg);

@@ -154,7 +154,7 @@ struct GraphicsOpts* graphicsOpts = parser->opts;

switch (option) { case 'f': graphicsOpts->fullscreen = true; - GBAConfigSetDefaultIntValue(config, "fullscreen", 1); + GBAConfigSetOverrideIntValue(config, "fullscreen", 1); return true; case '1': case '2':

@@ -166,8 +166,8 @@ if (graphicsOpts->multiplier) {

return false; } graphicsOpts->multiplier = option - '0'; - GBAConfigSetDefaultIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier); - GBAConfigSetDefaultIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier); + GBAConfigSetOverrideIntValue(config, "width", VIDEO_HORIZONTAL_PIXELS * graphicsOpts->multiplier); + GBAConfigSetOverrideIntValue(config, "height", VIDEO_VERTICAL_PIXELS * graphicsOpts->multiplier); return true; default: return false;
M src/platform/libretro/libretro.csrc/platform/libretro/libretro.c

@@ -11,6 +11,7 @@ #include "gba/renderers/video-software.h"

#include "gba/serialize.h" #include "gba/context/context.h" #include "util/circle-buffer.h" +#include "util/memory.h" #include "util/vfs.h" #define SAMPLES 1024

@@ -29,7 +30,6 @@

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 void _setRumble(struct GBARumble* rumble, int enable); static uint8_t _readLux(struct GBALuminanceSource* lux); static void _updateLux(struct GBALuminanceSource* lux);

@@ -37,6 +37,7 @@

static struct GBAContext context; static struct GBAVideoSoftwareRenderer renderer; static void* data; +static size_t dataSize; static void* savedata; static struct GBAAVStream stream; static int rumbleLevel;

@@ -153,12 +154,11 @@ }

stream.postAudioFrame = 0; stream.postAudioBuffer = _postAudioBuffer; - stream.postVideoFrame = _postVideoFrame; + stream.postVideoFrame = 0; GBAContextInit(&context, 0); struct GBAOptions opts = { .useBios = true, - .logLevel = 0, .idleOptimization = IDLE_LOOP_REMOVE }; GBAConfigLoadDefaults(&context.config, &opts);

@@ -182,7 +182,7 @@

GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = malloc(256 * VIDEO_VERTICAL_PIXELS * BYTES_PER_PIXEL); renderer.outputBufferStride = 256; - context->renderer = &renderer.d; + context.renderer = &renderer.d; GBAAudioResizeBuffer(&context.gba->audio, SAMPLES);

@@ -194,6 +194,7 @@ }

void retro_deinit(void) { GBAContextDeinit(&context); + free(renderer.outputBuffer); } void retro_run(void) {

@@ -233,6 +234,7 @@ }

} GBAContextFrame(&context, keys); + videoCallback(renderer.outputBuffer, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * renderer.outputBufferStride); } void retro_reset(void) {

@@ -246,7 +248,8 @@

bool retro_load_game(const struct retro_game_info* game) { struct VFile* rom; if (game->data) { - data = malloc(game->size); + data = anonymousMemoryMap(game->size); + dataSize = game->size; memcpy(data, game->data, game->size); rom = VFileFromMemory(data, game->size); } else {

@@ -258,11 +261,11 @@ return false;

} if (!GBAIsROM(rom)) { rom->close(rom); - free(data); + mappedMemoryFree(data, game->size); return false; } - savedata = malloc(SIZE_CART_FLASH1M); + savedata = anonymousMemoryMap(SIZE_CART_FLASH1M); struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M); GBAContextLoadROMFromVFile(&context, rom, save);

@@ -272,9 +275,9 @@ }

void retro_unload_game(void) { GBAContextStop(&context); - free(data); + mappedMemoryFree(data, dataSize); data = 0; - free(savedata); + mappedMemoryFree(savedata, SIZE_CART_FLASH1M); savedata = 0; CircleBufferDeinit(&rumbleHistory); }

@@ -403,14 +406,6 @@ samples[i * 2 + 1] = samplesR[i];

} #endif audioCallback(samples, SAMPLES); -} - -static void _postVideoFrame(struct GBAAVStream* stream, struct GBAVideoRenderer* renderer) { - UNUSED(stream); - const void* pixels; - unsigned stride; - renderer->getPixels(renderer, &stride, &pixels); - videoCallback(pixels, VIDEO_HORIZONTAL_PIXELS, VIDEO_VERTICAL_PIXELS, BYTES_PER_PIXEL * stride); } static void _setRumble(struct GBARumble* rumble, int enable) {
M src/platform/posix/threading.hsrc/platform/posix/threading.h

@@ -82,8 +82,11 @@ return pthread_setname_np(name);

#elif defined(__FreeBSD__) || defined(__OpenBSD__) pthread_set_name_np(pthread_self(), name); return 0; +#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old + return pthread_setname_np(pthread_self(), name); #else - return pthread_setname_np(pthread_self(), name); + UNUSED(name); + return 0; #endif }
M src/platform/psp2/CMakeLists.txtsrc/platform/psp2/CMakeLists.txt

@@ -1,18 +1,37 @@

-file(GLOB PLATFORM_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/*.c) +find_program(FIXUP vita-elf-create) +find_program(OBJCOPY ${cross_prefix}objcopy) +find_file(NIDDB db.json PATHS ${VITASDK} ${VITASDK}/bin ${VITASDK}/share) +find_file(EXTRADB extra.json PATHS ${VITASDK}${VITASDK}/bin ${VITASDK}/share) +find_program(STRIP ${cross_prefix}strip) + +file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/psp2/psp2-*.c) +set(OS_SRC ${OS_SRC} PARENT_SCOPE) +source_group("PS Vita-specific code" FILES ${OS_SRC}) + +list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c) +set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) + +set(OS_LIB -lvita2d -lSceCtrl_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lSceTouch_stub -lSceCommonDialog_stub -lpng -lz -l${M_LIBRARY}) +set(OBJCOPY_CMD ${OBJCOPY} -I binary -O elf32-littlearm -B arm) + +list(APPEND GUI_SRC ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_CURRENT_BINARY_DIR}/font.o ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o main.c) +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") +target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) -if (${CMAKE_SOURCE_DIR}/res/font.png IS_NEWER_THAN ${CMAKE_BINARY_DIR}/font.o) - execute_process(COMMAND ${OBJCOPY} -I binary -O elf32-littlearm -B arm font.png ${CMAKE_BINARY_DIR}/font.o WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) -endif() +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.o + COMMAND ${OBJCOPY_CMD} font.png ${CMAKE_CURRENT_BINARY_DIR}/font.o + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/res) -if (${CMAKE_SOURCE_DIR}/res/backdrop.png IS_NEWER_THAN ${CMAKE_BINARY_DIR}/backdrop.o) - execute_process(COMMAND ${OBJCOPY} -I binary -O elf32-littlearm -B arm backdrop.png ${CMAKE_BINARY_DIR}/backdrop.o WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -endif() +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o + COMMAND ${OBJCOPY_CMD} backdrop.png ${CMAKE_CURRENT_BINARY_DIR}/backdrop.o + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -set(PLATFORM_LIBRARY -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lpng -lz -l${M_LIBRARY}) +add_custom_target(${BINARY_NAME}.velf ALL + ${STRIP} --strip-unneeded -go ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.elf + COMMAND ${FIXUP} ${BINARY_NAME}-stripped.elf ${BINARY_NAME}.velf ${NIDDB} ${EXTRADB} + DEPENDS ${BINARY_NAME}.elf) -add_executable(${BINARY_NAME}.elf ${PLATFORM_SRC} ${GUI_SRC} ${CMAKE_BINARY_DIR}/font.o ${CMAKE_BINARY_DIR}/backdrop.o) -target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${PLATFORM_LIBRARY}) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") -set_target_properties(${BINARY_NAME}.elf PROPERTIES OUTPUT_NAME ${BINARY_NAME}.elf) -add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${FIXUP} ${BINARY_NAME}.elf ${BINARY_NAME}.velf ${NIDDB} MAIN_DEPENDENCY ${BINARY_NAME}.elf) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.velf DESTINATION . COMPONENT ${BINARY_NAME}-psp2)
D src/platform/psp2/CMakeToolchain.psp2sdk

@@ -1,55 +0,0 @@

-if(DEFINED ENV{DEVKITPRO}) - set(DEVKITPRO $ENV{DEVKITPRO}) -else() - message(FATAL_ERROR "Could not find DEVKITPRO in environment") -endif() - -if(DEFINED ENV{DEVKITARM}) - set(DEVKITARM $ENV{DEVKITARM}) -else() - set(DEVKITARM ${DEVKITPRO}/devkitARM) -endif() - -if(NOT DEFINED UNITY) - set(UNITY ON) -endif() - -set(toolchain_dir ${DEVKITARM}/psp2) -set(toolchain_bin_dir ${toolchain_dir}/bin) -set(cross_prefix ${DEVKITARM}/bin/arm-none-eabi-) -set(inc_flags -I${toolchain_dir}/include) -set(arch_flags "-march=armv7-a -mtune=cortex-a9 -specs=psp2.specs") -set(link_flags "-L${toolchain_dir}/lib") - -set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") -set(CMAKE_SYSTEM_PROCESSOR armv7a- CACHE INTERNAL "processor") -set(CMAKE_LIBRARY_ARCHITECTURE arm-none-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "ranlib") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -if(NOT UNITY) - set(common_flags "${arch_flags} -fno-reorder-functions -fno-optimize-sibling-calls ${inc_flags}") -else() - set(common_flags "${arch_flags} -fno-gcse -fno-tree-vectorize ${inc_flags}") -endif() -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") -set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") - -set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") -set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") -set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") - -set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE) - -set(FIXUP ${toolchain_bin_dir}/psp2-fixup -q -S) -set(OBJCOPY ${cross_prefix}objcopy) - -set(PSP2 ON) -add_definitions(-DPSP2) -set(M_LIBRARY m_stub) - -set(CMAKE_C_COMPILER_WORKS 1) # Skip test
M src/platform/psp2/CMakeToolchain.vitasdksrc/platform/psp2/CMakeToolchain.vitasdk

@@ -4,35 +4,38 @@ else()

message(FATAL_ERROR "Could not find VITASDK in environment") endif() +set(extension) +if (CMAKE_HOST_WIN32) + set(extension .exe) +endif() + set(toolchain_dir ${VITASDK}) -set(toolchain_bin_dir ${toolchain_dir}/bin) -set(cross_prefix ${toolchain_bin_dir}/arm-vita-eabi-) +set(CMAKE_PROGRAM_PATH ${toolchain_dir}/bin) + +set(cross_prefix ${CMAKE_PROGRAM_PATH}/arm-vita-eabi-) set(inc_flags -I${toolchain_dir}/include) set(link_flags "-L${toolchain_dir}/lib -Wl,-q") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") set(CMAKE_SYSTEM_PROCESSOR armv7-a CACHE INTERNAL "processor") set(CMAKE_LIBRARY_ARCHITECTURE arm-vita-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "ranlib") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -set(common_flags "${inc_flags}") -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") + +set(CMAKE_AR ${cross_prefix}gcc-ar${extension} CACHE INTERNAL "archiver") +set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib${extension} CACHE INTERNAL "archiver") +set(CMAKE_C_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "c compiler") +set(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension} CACHE INTERNAL "cxx compiler") +set(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "assembler") +set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") +set(CMAKE_PREFIX_PATH ${VITASDK}/arm-vita-eabi) set(PKG_CONFIG_EXECUTABLE "/dev/null" CACHE INTERNAL "" FORCE) - -set(FIXUP ${toolchain_bin_dir}/vita-elf-create) -set(OBJCOPY ${cross_prefix}objcopy) -set(NIDDB ${VITASDK}/db.json) set(PSP2 ON) add_definitions(-DPSP2)
M src/platform/psp2/main.csrc/platform/psp2/main.c

@@ -10,23 +10,32 @@ #include "gba/gui/gui-runner.h"

#include "util/gui.h" #include "util/gui/font.h" #include "util/gui/file-select.h" +#include "util/gui/menu.h" #include <psp2/ctrl.h> +#include <psp2/display.h> #include <psp2/kernel/processmgr.h> #include <psp2/kernel/threadmgr.h> #include <psp2/moduleinfo.h> +#include <psp2/power.h> +#include <psp2/touch.h> #include <vita2d.h> PSP2_MODULE_INFO(0, 0, "mGBA"); static void _drawStart(void) { + vita2d_set_vblank_wait(false); vita2d_start_drawing(); vita2d_clear_screen(); } static void _drawEnd(void) { + static int oldVCount = 0; + int vcount = oldVCount; vita2d_end_drawing(); + oldVCount = sceDisplayGetVcount(); + vita2d_set_vblank_wait(oldVCount == vcount); vita2d_swap_buffers(); }

@@ -63,31 +72,70 @@

return input; } -int main() { - printf("%s initializing", projectName); +static enum GUICursorState _pollCursor(int* x, int* y) { + SceTouchData touch; + sceTouchPeek(0, &touch, 1); + if (touch.reportNum < 1) { + return GUI_CURSOR_NOT_PRESENT; + } + *x = touch.report[0].x / 2; + *y = touch.report[0].y / 2; + return GUI_CURSOR_DOWN; +} +static int _batteryState(void) { + int charge = scePowerGetBatteryLifePercent(); + int adapter = scePowerIsPowerOnline(); + int state = 0; + if (adapter) { + state |= BATTERY_CHARGING; + } + charge /= 25; + return state | charge; +} + +int main() { vita2d_init(); struct GUIFont* font = GUIFontCreate(); struct GBAGUIRunner runner = { .params = { PSP2_HORIZONTAL_PIXELS, PSP2_VERTICAL_PIXELS, - font, "cache0:", _drawStart, _drawEnd, _pollInput, 0, 0, + font, "cache0:", _drawStart, _drawEnd, + _pollInput, _pollCursor, + _batteryState, + 0, 0, GUI_PARAMS_TRAIL }, + .configExtra = (struct GUIMenuItem[]) { + { + .title = "Screen mode", + .data = "screenMode", + .submenu = 0, + .state = 0, + .validStates = (const char*[]) { + "With Background", + "Without Background", + "Stretched", + 0 + } + } + }, + .nConfigExtra = 1, .setup = GBAPSP2Setup, .teardown = GBAPSP2Teardown, .gameLoaded = GBAPSP2LoadROM, .gameUnloaded = GBAPSP2UnloadROM, .prepareForFrame = GBAPSP2PrepareForFrame, .drawFrame = GBAPSP2Draw, + .drawScreenshot = GBAPSP2DrawScreenshot, .paused = 0, .unpaused = 0, .incrementScreenMode = GBAPSP2IncrementScreenMode, .pollGameInput = GBAPSP2PollInput }; - GBAGUIInit(&runner, 0); + GBAGUIInit(&runner, "psvita"); GBAGUIRunloop(&runner); GBAGUIDeinit(&runner);
M src/platform/psp2/psp2-context.csrc/platform/psp2/psp2-context.c

@@ -38,6 +38,7 @@ } screenMode;

static struct GBAVideoSoftwareRenderer renderer; static vita2d_texture* tex; +static vita2d_texture* screenshot; static Thread audioThread; static struct GBASceRotationSource { struct GBARotationSource d;

@@ -139,7 +140,6 @@ void GBAPSP2Setup(struct GBAGUIRunner* runner) {

scePowerSetArmClockFrequency(80); struct GBAOptions opts = { .useBios = true, - .logLevel = 0, .idleOptimization = IDLE_LOOP_DETECT }; GBAConfigLoadDefaults(&runner->context.config, &opts);

@@ -160,6 +160,7 @@ desc = (struct GBAAxis) { GBA_KEY_RIGHT, GBA_KEY_LEFT, 192, 64 };

GBAInputBindAxis(&runner->context.inputMap, PSP2_INPUT, 1, &desc); tex = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); + screenshot = vita2d_create_empty_texture_format(256, 256, SCE_GXM_TEXTURE_FORMAT_X8U8U8U8_1BGR); GBAVideoSoftwareRendererCreate(&renderer); renderer.outputBuffer = vita2d_texture_get_datap(tex);

@@ -221,13 +222,14 @@

void GBAPSP2Teardown(struct GBAGUIRunner* runner) { UNUSED(runner); vita2d_free_texture(tex); - vita2d_free_texture(backdrop); + vita2d_free_texture(screenshot); } void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded) { UNUSED(runner); switch (screenMode) { case SM_BACKDROP: + default: vita2d_draw_texture_tint(backdrop, 0, 0, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); // Fall through case SM_PLAIN:

@@ -239,8 +241,35 @@ break;

} } +void GBAPSP2DrawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded) { + UNUSED(runner); + uint32_t* texpixels = vita2d_texture_get_datap(screenshot); + int y; + for (y = 0; y < VIDEO_VERTICAL_PIXELS; ++y) { + memcpy(&texpixels[256 * y], &pixels[VIDEO_HORIZONTAL_PIXELS * y], VIDEO_HORIZONTAL_PIXELS * 4); + } + switch (screenMode) { + case SM_BACKDROP: + default: + vita2d_draw_texture_tint(backdrop, 0, 0, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); + // Fall through + case SM_PLAIN: + vita2d_draw_texture_tint_part_scale(screenshot, 120, 32, 0, 0, 240, 160, 3.0f, 3.0f, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); + break; + case SM_FULL: + vita2d_draw_texture_tint_scale(screenshot, 0, 0, 960.0f / 240.0f, 544.0f / 160.0f, (faded ? 0 : 0xC0000000) | 0x3FFFFFFF); + break; + } +} + void GBAPSP2IncrementScreenMode(struct GBAGUIRunner* runner) { - screenMode = (screenMode + 1) % SM_MAX; + unsigned mode; + if (GBAConfigGetUIntValue(&runner->context.config, "screenMode", &mode) && mode != screenMode) { + screenMode = mode; + } else { + screenMode = (screenMode + 1) % SM_MAX; + GBAConfigSetUIntValue(&runner->context.config, "screenMode", screenMode); + } } __attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) {
M src/platform/psp2/psp2-context.hsrc/platform/psp2/psp2-context.h

@@ -17,6 +17,7 @@ void GBAPSP2LoadROM(struct GBAGUIRunner* runner);

void GBAPSP2UnloadROM(struct GBAGUIRunner* runner); void GBAPSP2PrepareForFrame(struct GBAGUIRunner* runner); void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded); +void GBAPSP2DrawScreenshot(struct GBAGUIRunner* runner, const uint32_t* pixels, bool faded); void GBAPSP2IncrementScreenMode(struct GBAGUIRunner* runner); uint16_t GBAPSP2PollInput(struct GBAGUIRunner* runner);
M src/platform/psp2/sce-vfs.csrc/platform/psp2/sce-vfs.c

@@ -42,6 +42,7 @@ static bool _vdsceClose(struct VDir* vd);

static void _vdsceRewind(struct VDir* vd); static struct VDirEntry* _vdsceListNext(struct VDir* vd); static struct VFile* _vdsceOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdsceOpenDir(struct VDir* vd, const char* path); static const char* _vdesceName(struct VDirEntry* vde); static enum VFSType _vdesceType(struct VDirEntry* vde);

@@ -150,6 +151,7 @@ vd->d.close = _vdsceClose;

vd->d.rewind = _vdsceRewind; vd->d.listNext = _vdsceListNext; vd->d.openFile = _vdsceOpenFile; + vd->d.openDir = _vdsceOpenDir; vd->path = strdup(path); vd->de.d.name = _vdesceName;

@@ -190,11 +192,27 @@ }

const char* dir = vdsce->path; char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + strlen(PATH_SEP) + 1)); sprintf(combined, "%s%s%s", dir, PATH_SEP, path); - printf("Opening %s\n", combined); struct VFile* file = VFileOpen(combined, mode); free(combined); return file; +} + +struct VDir* _vdsceOpenDir(struct VDir* vd, const char* path) { + struct VDirSce* vdsce = (struct VDirSce*) vd; + if (!path) { + return 0; + } + const char* dir = vdsce->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + strlen(PATH_SEP) + 1)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + struct VDir* vd2 = VDirOpen(combined); + if (!vd2) { + vd2 = VDirOpenArchive(combined); + } + free(combined); + return vd2; } static const char* _vdesceName(struct VDirEntry* vde) {
M src/platform/qt/AudioProcessorQt.hsrc/platform/qt/AudioProcessorQt.h

@@ -19,15 +19,15 @@

public: AudioProcessorQt(QObject* parent = nullptr); - virtual void setInput(GBAThread* input); + virtual void setInput(GBAThread* input) override; virtual unsigned sampleRate() const override; public slots: - virtual void start(); - virtual void pause(); + virtual void start() override; + virtual void pause() override; - virtual void setBufferSamples(int samples); - virtual void inputParametersChanged(); + virtual void setBufferSamples(int samples) override; + virtual void inputParametersChanged() override; virtual void requestSampleRate(unsigned) override;
M src/platform/qt/AudioProcessorSDL.hsrc/platform/qt/AudioProcessorSDL.h

@@ -25,11 +25,11 @@

virtual unsigned sampleRate() const override; public slots: - virtual void start(); - virtual void pause(); + virtual void start() override; + virtual void pause() override; - virtual void setBufferSamples(int samples); - virtual void inputParametersChanged(); + virtual void setBufferSamples(int samples) override; + virtual void inputParametersChanged() override; virtual void requestSampleRate(unsigned) override;
M src/platform/qt/CMakeLists.txtsrc/platform/qt/CMakeLists.txt

@@ -75,6 +75,7 @@ GIFView.cpp

GameController.cpp GamepadAxisEvent.cpp GamepadButtonEvent.cpp + IOViewer.cpp InputController.cpp InputProfile.cpp KeyEditor.cpp

@@ -101,6 +102,7 @@ qt5_wrap_ui(UI_FILES

AboutScreen.ui CheatsView.ui GIFView.ui + IOViewer.ui LoadSaveState.ui LogView.ui MemoryView.ui

@@ -155,7 +157,7 @@ list(APPEND QT_LIBRARIES qwindows imm32)

endif() 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 COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/res/info.plist.in COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::OpenGL) target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
M src/platform/qt/Display.hsrc/platform/qt/Display.h

@@ -52,7 +52,7 @@

void showMessage(const QString& message); protected: - void resizeEvent(QResizeEvent*); + virtual void resizeEvent(QResizeEvent*) override; virtual void mouseMoveEvent(QMouseEvent*) override; MessagePainter* messagePainter() { return &m_messagePainter; }
M src/platform/qt/DisplayGL.cppsrc/platform/qt/DisplayGL.cpp

@@ -208,7 +208,7 @@ void PainterGL::draw() {

if (m_queue.isEmpty()) { return; } - if (GBASyncWaitFrameStart(&m_context->sync, m_context->frameskip) || !m_queue.isEmpty()) { + if (GBASyncWaitFrameStart(&m_context->sync) || !m_queue.isEmpty()) { dequeue(); m_painter.begin(m_gl->context()->device()); performDraw();
M src/platform/qt/GBAApp.cppsrc/platform/qt/GBAApp.cpp

@@ -86,6 +86,7 @@

w->show(); w->controller()->setMultiplayerController(&m_multiplayer); + w->multiplayerChanged(); } bool GBAApp::event(QEvent* event) {

@@ -110,6 +111,7 @@ w->setAttribute(Qt::WA_DeleteOnClose);

w->loadConfig(); w->show(); w->controller()->setMultiplayerController(&m_multiplayer); + w->multiplayerChanged(); return w; }
M src/platform/qt/GameController.cppsrc/platform/qt/GameController.cpp

@@ -124,12 +124,8 @@ };

m_threadContext.frameCallback = [](GBAThread* context) { GameController* controller = static_cast<GameController*>(context->userData); - if (GBASyncDrawingFrame(&controller->m_threadContext.sync)) { - memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); - } else { - QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, nullptr)); - } + memcpy(controller->m_frontBuffer, controller->m_drawContext, 256 * VIDEO_HORIZONTAL_PIXELS * BYTES_PER_PIXEL); + QMetaObject::invokeMethod(controller, "frameAvailable", Q_ARG(const uint32_t*, controller->m_frontBuffer)); if (controller->m_pauseAfterFrame.testAndSetAcquire(true, false)) { GBAThreadPauseFromThread(context); QMetaObject::invokeMethod(controller, "gamePaused", Q_ARG(GBAThread*, context));

@@ -534,6 +530,9 @@ void GameController::startRewinding() {

if (!m_gameOpen || m_rewindTimer.isActive()) { return; } + if (m_multiplayer && m_multiplayer->attached() > 1) { + return; + } m_wasPaused = isPaused(); if (!GBAThreadIsPaused(&m_threadContext)) { GBAThreadPause(&m_threadContext);

@@ -770,7 +769,12 @@ }

} void GameController::setFrameskip(int skip) { + threadInterrupt(); m_threadContext.frameskip = skip; + if (m_gameOpen) { + m_threadContext.gba->video.frameskip = skip; + } + threadContinue(); } void GameController::setVolume(int volume) {
A src/platform/qt/IOViewer.cpp

@@ -0,0 +1,255 @@

+/* 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 "IOViewer.h" + +#include "GameController.h" + +#include <QFontDatabase> +#include <QVBoxLayout> + +extern "C" { +#include "gba/io.h" +} + +using namespace QGBA; + + +QList<IOViewer::RegisterDescription> IOViewer::s_registers; + +const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions() { + if (!s_registers.isEmpty()) { + return s_registers; + } + // 0x04000000: DISPCNT + s_registers.append({ + { tr("Background mode"), 0, 3 }, + { tr("CGB Mode"), 3, 1, true }, + { tr("Frame select"), 4 }, + { tr("Unlocked HBlank"), 5 }, + { tr("Linear OBJ tile mapping"), 6 }, + { tr("Force blank screen"), 7 }, + { tr("Enable background 0"), 8 }, + { tr("Enable background 1"), 9 }, + { tr("Enable background 2"), 10 }, + { tr("Enable background 3"), 11 }, + { tr("Enable OBJ"), 12 }, + { tr("Enable Window 0"), 13 }, + { tr("Enable Window 1"), 14 }, + { tr("Enable OBJ Window"), 15 }, + }); + // 0x04000002: Green swap (undocumented and unimplemented) + s_registers.append(RegisterDescription()); + // 0x04000004: DISPSTAT + s_registers.append({ + { tr("Currently in VBlank"), 0, 1, true }, + { tr("Currently in HBlank"), 1, 1, true }, + { tr("Currently in VCounter"), 2, 1, true }, + { tr("Enable VBlank IRQ generation"), 3 }, + { tr("Enable HBlank IRQ generation"), 4 }, + { tr("Enable VCounter IRQ generation"), 5 }, + { tr("VCounter scanline"), 8, 8 }, + }); + // 0x04000006: VCOUNT + s_registers.append({ + { tr("Current scanline"), 0, 8, true }, + }); + // 0x04000008: BG0CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x0400000A: BG1CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x0400000C: BG2CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Overflow wraps"), 9 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x0400000E: BG3CNT + s_registers.append({ + { tr("Priority"), 0, 2 }, + { tr("Tile data base (* 16kB)"), 2, 2 }, + { tr("Enable mosaic"), 3 }, + { tr("Enable 256-color"), 3 }, + { tr("Tile map base (* 2kB)"), 8, 5 }, + { tr("Overflow wraps"), 9 }, + { tr("Background dimensions"), 14, 2 }, + }); + // 0x04000010: BG0HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x04000012: BG0VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + // 0x04000014: BG1HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x04000016: BG1VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + // 0x04000018: BG2HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x0400001A: BG2VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + // 0x0400001C: BG3HOFS + s_registers.append({ + { tr("Horizontal offset"), 0, 9 }, + }); + // 0x0400001E: BG3VOFS + s_registers.append({ + { tr("Vertical offset"), 0, 9 }, + }); + return s_registers; +} + +IOViewer::IOViewer(GameController* controller, QWidget* parent) + : QDialog(parent) + , m_controller(controller) +{ + m_ui.setupUi(this); + + for (unsigned i = 0; i < REG_MAX >> 1; ++i) { + const char* reg = GBAIORegisterNames[i]; + if (!reg) { + continue; + } + m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1); + } + + const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + m_ui.regValue->setFont(font); + + connect(m_ui.buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(buttonPressed(QAbstractButton*))); + connect(m_ui.buttonBox, SIGNAL(rejected()), this, SLOT(close())); + connect(m_ui.regSelect, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRegister())); + + m_b[0] = m_ui.b0; + m_b[1] = m_ui.b1; + m_b[2] = m_ui.b2; + m_b[3] = m_ui.b3; + m_b[4] = m_ui.b4; + m_b[5] = m_ui.b5; + m_b[6] = m_ui.b6; + m_b[7] = m_ui.b7; + m_b[8] = m_ui.b8; + m_b[9] = m_ui.b9; + m_b[10] = m_ui.bA; + m_b[11] = m_ui.bB; + m_b[12] = m_ui.bC; + m_b[13] = m_ui.bD; + m_b[14] = m_ui.bE; + m_b[15] = m_ui.bF; + + for (int i = 0; i < 16; ++i) { + connect(m_b[i], SIGNAL(toggled(bool)), this, SLOT(bitFlipped())); + } + + selectRegister(0); +} + +void IOViewer::updateRegister() { + m_value = 0; + uint16_t value = 0; + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + value = GBAIORead(m_controller->thread()->gba, m_register); + } + m_controller->threadContinue(); + + for (int i = 0; i < 16; ++i) { + m_b[i]->setChecked(value & (1 << i) ? Qt::Checked : Qt::Unchecked); + } + m_value = value; +} + +void IOViewer::bitFlipped() { + m_value = 0; + for (int i = 0; i < 16; ++i) { + m_value |= m_b[i]->isChecked() << i; + } + m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper()); +} + +void IOViewer::writeback() { + m_controller->threadInterrupt(); + if (m_controller->isLoaded()) { + GBAIOWrite(m_controller->thread()->gba, m_register, m_value); + } + m_controller->threadContinue(); + updateRegister(); +} + +void IOViewer::selectRegister(unsigned address) { + m_register = address; + QLayout* box = m_ui.regDescription->layout(); + if (box) { + // I can't believe there isn't a real way to do this... + while (!box->isEmpty()) { + QLayoutItem* item = box->takeAt(0); + if (item->widget()) { + delete item->widget(); + } + delete item; + } + } else { + box = new QVBoxLayout; + } + if (registerDescriptions().count() > address >> 1) { + // TODO: Remove the check when done filling in register information + const RegisterDescription& description = registerDescriptions().at(address >> 1); + for (const RegisterItem& ri : description) { + QCheckBox* check = new QCheckBox; + check->setText(ri.description); + check->setEnabled(!ri.readonly); + box->addWidget(check); + connect(m_b[ri.start], SIGNAL(toggled(bool)), check, SLOT(setChecked(bool))); + connect(check, SIGNAL(toggled(bool)), m_b[ri.start], SLOT(setChecked(bool))); + } + } + m_ui.regDescription->setLayout(box); + updateRegister(); +} + +void IOViewer::selectRegister() { + selectRegister(m_ui.regSelect->currentData().toUInt()); +} + +void IOViewer::buttonPressed(QAbstractButton* button) { + switch (m_ui.buttonBox->standardButton(button)) { + case QDialogButtonBox::Reset: + updateRegister(); + break; + case QDialogButtonBox::Apply: + writeback(); + break; + default: + break; + } +}
A src/platform/qt/IOViewer.h

@@ -0,0 +1,63 @@

+/* 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_IOVIEWER +#define QGBA_IOVIEWER + +#include <QDialog> +#include <QList> + +#include "ui_IOViewer.h" + +namespace QGBA { + +class GameController; + +class IOViewer : public QDialog { +Q_OBJECT + +public: + struct RegisterItem { + RegisterItem(const QString& description, uint start, uint size = 1, bool readonly = false) + : description(description) + , start(start) + , size(size) + , readonly(readonly) {} + uint start; + uint size; + bool readonly; + QString description; + }; + typedef QList<RegisterItem> RegisterDescription; + + IOViewer(GameController* controller, QWidget* parent = nullptr); + + static const QList<RegisterDescription>& registerDescriptions(); + +public slots: + void updateRegister(); + void selectRegister(unsigned address); + +private slots: + void buttonPressed(QAbstractButton* button); + void bitFlipped(); + void writeback(); + void selectRegister(); + +private: + static QList<RegisterDescription> s_registers; + Ui::IOViewer m_ui; + + unsigned m_register; + uint16_t m_value; + + QCheckBox* m_b[16]; + + GameController* m_controller; +}; + +} + +#endif
A src/platform/qt/IOViewer.ui

@@ -0,0 +1,414 @@

+<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>IOViewer</class> + <widget class="QWidget" name="IOViewer"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>343</width> + <height>342</height> + </rect> + </property> + <property name="windowTitle"> + <string>I/O Viewer</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QComboBox" name="regSelect"/> + </item> + <item alignment="Qt::AlignRight|Qt::AlignTop"> + <widget class="QLabel" name="regValue"> + <property name="text"> + <string>0x0000</string> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="12" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b3"/> + </item> + <item row="0" column="11" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b4"/> + </item> + <item row="0" column="13" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b2"/> + </item> + <item row="0" column="15" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b0"/> + </item> + <item row="1" column="13" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l2"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>2</string> + </property> + </widget> + </item> + <item row="0" column="10" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b5"/> + </item> + <item row="0" column="14" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b1"/> + </item> + <item row="1" column="10" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l5"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>5</string> + </property> + </widget> + </item> + <item row="1" column="11" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l4"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>4</string> + </property> + </widget> + </item> + <item row="1" column="8" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l7"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>7</string> + </property> + </widget> + </item> + <item row="1" column="15" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l0"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>0</string> + </property> + </widget> + </item> + <item row="0" column="9" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b6"/> + </item> + <item row="1" column="6" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="l9"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>9</string> + </property> + </widget> + </item> + <item row="0" column="6" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b9"/> + </item> + <item row="1" column="14" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l1"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>1</string> + </property> + </widget> + </item> + <item row="1" column="12" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l3"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>3</string> + </property> + </widget> + </item> + <item row="0" column="0" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="bF"/> + </item> + <item row="1" column="7" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="l8"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>8</string> + </property> + </widget> + </item> + <item row="0" column="2" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="bD"/> + </item> + <item row="0" column="1" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="bE"/> + </item> + <item row="1" column="3" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="lC"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>C</string> + </property> + </widget> + </item> + <item row="1" column="1" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="lE"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>E</string> + </property> + </widget> + </item> + <item row="0" column="3" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="bC"/> + </item> + <item row="0" column="5"> + <widget class="QCheckBox" name="bA"/> + </item> + <item row="1" column="9" alignment="Qt::AlignHCenter|Qt::AlignTop"> + <widget class="QLabel" name="l6"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>6</string> + </property> + </widget> + </item> + <item row="0" column="7" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b8"/> + </item> + <item row="0" column="4" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="bB"/> + </item> + <item row="0" column="8" alignment="Qt::AlignHCenter"> + <widget class="QCheckBox" name="b7"/> + </item> + <item row="1" column="2" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="lD"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>D</string> + </property> + </widget> + </item> + <item row="1" column="0" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="lF"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>F</string> + </property> + </widget> + </item> + <item row="1" column="5" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="lA"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>A</string> + </property> + </widget> + </item> + <item row="1" column="4" alignment="Qt::AlignHCenter"> + <widget class="QLabel" name="lB"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>80</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> + </property> + <property name="text"> + <string>B</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QWidget" name="regDescription" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Reset</set> + </property> + </widget> + </item> + </layout> + </widget> + <tabstops> + <tabstop>regSelect</tabstop> + <tabstop>b0</tabstop> + <tabstop>b1</tabstop> + <tabstop>b2</tabstop> + <tabstop>b3</tabstop> + <tabstop>b4</tabstop> + <tabstop>b5</tabstop> + <tabstop>b6</tabstop> + <tabstop>b7</tabstop> + <tabstop>bE</tabstop> + <tabstop>b8</tabstop> + <tabstop>b9</tabstop> + <tabstop>bA</tabstop> + <tabstop>bB</tabstop> + <tabstop>bC</tabstop> + <tabstop>bD</tabstop> + <tabstop>bF</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
M src/platform/qt/MultiplayerController.cppsrc/platform/qt/MultiplayerController.cpp

@@ -35,6 +35,7 @@ GBASIOSetDriver(&thread->gba->sio, &node->d, SIO_MULTI);

} thread->sioDrivers.multiplayer = &node->d; controller->threadContinue(); + emit gameAttached(); return true; }

@@ -60,6 +61,21 @@ delete node;

} MutexUnlock(&m_lockstep.mutex); controller->threadContinue(); + emit gameDetached(); +} + +int MultiplayerController::playerId(GameController* controller) { + MutexLock(&m_lockstep.mutex); + int id = -1; + for (int i = 0; i < m_lockstep.attached; ++i) { + GBAThread* thread = controller->thread(); + if (thread->sioDrivers.multiplayer == &m_lockstep.players[i]->d) { + id = i; + break; + } + } + MutexUnlock(&m_lockstep.mutex); + return id; } int MultiplayerController::attached() {
M src/platform/qt/MultiplayerController.hsrc/platform/qt/MultiplayerController.h

@@ -6,6 +6,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef QGBA_MULTIPLAYER_CONTROLLER #define QGBA_MULTIPLAYER_CONTROLLER +#include <QObject> + extern "C" { #include "gba/sio/lockstep.h" }

@@ -14,7 +16,9 @@ namespace QGBA {

class GameController; -class MultiplayerController { +class MultiplayerController : public QObject { +Q_OBJECT + public: MultiplayerController(); ~MultiplayerController();

@@ -23,6 +27,11 @@ bool attachGame(GameController*);

void detachGame(GameController*); int attached(); + int playerId(GameController*); + +signals: + void gameAttached(); + void gameDetached(); private: GBASIOLockstep m_lockstep;
M src/platform/qt/SensorView.hsrc/platform/qt/SensorView.h

@@ -30,7 +30,7 @@ SensorView(GameController* controller, InputController* input, QWidget* parent = nullptr);

protected: bool eventFilter(QObject*, QEvent* event) override; - bool event(QEvent* event); + bool event(QEvent* event) override; private slots: void updateSensors();
M src/platform/qt/SettingsView.cppsrc/platform/qt/SettingsView.cpp

@@ -89,6 +89,11 @@ #endif

connect(m_ui.biosBrowse, SIGNAL(clicked()), this, SLOT(selectBios())); connect(m_ui.buttonBox, SIGNAL(accepted()), this, SLOT(updateConfig())); + connect(m_ui.buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { + if (m_ui.buttonBox->buttonRole(button) == QDialogButtonBox::ApplyRole) { + updateConfig(); + } + }); } void SettingsView::selectBios() {
M src/platform/qt/SettingsView.uisrc/platform/qt/SettingsView.ui

@@ -549,7 +549,7 @@ </item>

<item> <widget class="QDialogButtonBox" name="buttonBox"> <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + <set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> </property> </widget> </item>
M src/platform/qt/ShortcutController.cppsrc/platform/qt/ShortcutController.cpp

@@ -311,9 +311,9 @@ return false;

} int key = keyEvent->key(); if (!isModifierKey(key)) { - key |= keyEvent->modifiers(); + key |= (keyEvent->modifiers() & ~Qt::KeypadModifier); } else { - key = toModifierKey(key | keyEvent->modifiers()); + key = toModifierKey(key | (keyEvent->modifiers() & ~Qt::KeypadModifier)); } auto item = m_heldKeys.find(key); if (item != m_heldKeys.end()) {
M src/platform/qt/ShortcutView.cppsrc/platform/qt/ShortcutView.cpp

@@ -22,10 +22,14 @@ m_ui.setupUi(this);

m_ui.keyEdit->setValueKey(0); connect(m_ui.gamepadButton, &QAbstractButton::pressed, [this]() { + bool signalsBlocked = m_ui.keyEdit->blockSignals(true); m_ui.keyEdit->setValueButton(-1); + m_ui.keyEdit->blockSignals(signalsBlocked); }); connect(m_ui.keyboardButton, &QAbstractButton::pressed, [this]() { + bool signalsBlocked = m_ui.keyEdit->blockSignals(true); m_ui.keyEdit->setValueKey(0); + m_ui.keyEdit->blockSignals(signalsBlocked); }); connect(m_ui.keyEdit, SIGNAL(valueChanged(int)), this, SLOT(updateButton(int))); connect(m_ui.keyEdit, SIGNAL(axisChanged(int, int)), this, SLOT(updateAxis(int, int)));
M src/platform/qt/Window.cppsrc/platform/qt/Window.cpp

@@ -24,6 +24,7 @@ #include "GBAKeyEditor.h"

#include "GDBController.h" #include "GDBWindow.h" #include "GIFView.h" +#include "IOViewer.h" #include "LoadSaveState.h" #include "LogView.h" #include "MultiplayerController.h"

@@ -271,6 +272,23 @@ m_controller->replaceGame(filename);

} } +void Window::multiplayerChanged() { + disconnect(nullptr, this, SLOT(multiplayerChanged())); + int attached = 1; + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer) { + attached = multiplayer->attached(); + connect(multiplayer, SIGNAL(gameAttached()), this, SLOT(multiplayerChanged())); + connect(multiplayer, SIGNAL(gameDetached()), this, SLOT(multiplayerChanged())); + m_playerId = multiplayer->playerId(m_controller); + } + if (m_controller->isLoaded()) { + for (QAction* action : m_nonMpActions) { + action->setDisabled(attached > 1); + } + } +} + void Window::selectBIOS() { QString filename = GBAApp::app()->getOpenFileName(this, tr("Select BIOS")); if (!filename.isEmpty()) {

@@ -357,6 +375,11 @@ MemoryView* memoryWindow = new MemoryView(m_controller);

openView(memoryWindow); } +void Window::openIOViewer() { + IOViewer* ioViewer = new IOViewer(m_controller); + openView(ioViewer); +} + void Window::openAboutScreen() { AboutScreen* about = new AboutScreen(); openView(about);

@@ -569,6 +592,7 @@ MutexUnlock(&context->stateMutex);

foreach (QAction* action, m_gameActions) { action->setDisabled(false); } + multiplayerChanged(); if (context->fname) { setWindowFilePath(context->fname); appendMRU(context->fname);

@@ -693,6 +717,10 @@ void Window::openStateWindow(LoadSave ls) {

if (m_stateWindow) { return; } + MultiplayerController* multiplayer = m_controller->multiplayerController(); + if (multiplayer && multiplayer->attached() > 1) { + return; + } bool wasPaused = m_controller->isPaused(); m_stateWindow = new LoadSaveState(m_controller); connect(this, SIGNAL(shutdown()), m_stateWindow, SLOT(close()));

@@ -736,12 +764,14 @@ QAction* loadState = new QAction(tr("&Load state"), fileMenu);

loadState->setShortcut(tr("F10")); connect(loadState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::LOAD); }); m_gameActions.append(loadState); + m_nonMpActions.append(loadState); addControlledAction(fileMenu, loadState, "loadState"); QAction* saveState = new QAction(tr("&Save state"), fileMenu); saveState->setShortcut(tr("Shift+F10")); connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); }); m_gameActions.append(saveState); + m_nonMpActions.append(saveState); addControlledAction(fileMenu, saveState, "saveState"); QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));

@@ -752,11 +782,13 @@

QAction* quickLoad = new QAction(tr("Load recent"), quickLoadMenu); connect(quickLoad, SIGNAL(triggered()), m_controller, SLOT(loadState())); m_gameActions.append(quickLoad); + m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, "quickLoad"); QAction* quickSave = new QAction(tr("Save recent"), quickSaveMenu); connect(quickSave, SIGNAL(triggered()), m_controller, SLOT(saveState())); m_gameActions.append(quickSave); + m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, "quickSave"); quickLoadMenu->addSeparator();

@@ -766,12 +798,14 @@ QAction* undoLoadState = new QAction(tr("Undo load state"), quickLoadMenu);

undoLoadState->setShortcut(tr("F11")); connect(undoLoadState, SIGNAL(triggered()), m_controller, SLOT(loadBackupState())); m_gameActions.append(undoLoadState); + m_nonMpActions.append(undoLoadState); addControlledAction(quickLoadMenu, undoLoadState, "undoLoadState"); QAction* undoSaveState = new QAction(tr("Undo save state"), quickSaveMenu); undoSaveState->setShortcut(tr("Shift+F11")); connect(undoSaveState, SIGNAL(triggered()), m_controller, SLOT(saveBackupState())); m_gameActions.append(undoSaveState); + m_nonMpActions.append(undoSaveState); addControlledAction(quickSaveMenu, undoSaveState, "undoSaveState"); quickLoadMenu->addSeparator();

@@ -783,12 +817,14 @@ quickLoad = new QAction(tr("State &%1").arg(i), quickLoadMenu);

quickLoad->setShortcut(tr("F%1").arg(i)); connect(quickLoad, &QAction::triggered, [this, i]() { m_controller->loadState(i); }); m_gameActions.append(quickLoad); + m_nonMpActions.append(quickLoad); addControlledAction(quickLoadMenu, quickLoad, QString("quickLoad.%1").arg(i)); quickSave = new QAction(tr("State &%1").arg(i), quickSaveMenu); quickSave->setShortcut(tr("Shift+F%1").arg(i)); connect(quickSave, &QAction::triggered, [this, i]() { m_controller->saveState(i); }); m_gameActions.append(quickSave); + m_nonMpActions.append(quickSave); addControlledAction(quickSaveMenu, quickSave, QString("quickSave.%1").arg(i)); }

@@ -857,6 +893,7 @@ QAction* frameAdvance = new QAction(tr("&Next frame"), emulationMenu);

frameAdvance->setShortcut(tr("Ctrl+N")); connect(frameAdvance, SIGNAL(triggered()), m_controller, SLOT(frameAdvance())); m_gameActions.append(frameAdvance); + m_nonMpActions.append(frameAdvance); addControlledAction(emulationMenu, frameAdvance, "frameAdvance"); emulationMenu->addSeparator();

@@ -897,6 +934,7 @@ QAction* rewind = new QAction(tr("Re&wind"), emulationMenu);

rewind->setShortcut(tr("`")); connect(rewind, SIGNAL(triggered()), m_controller, SLOT(rewind())); m_gameActions.append(rewind); + m_nonMpActions.append(rewind); addControlledAction(emulationMenu, rewind, "rewind"); QAction* frameRewind = new QAction(tr("Step backwards"), emulationMenu);

@@ -905,6 +943,7 @@ connect(frameRewind, &QAction::triggered, [this] () {

m_controller->rewind(1); }); m_gameActions.append(frameRewind); + m_nonMpActions.append(frameRewind); addControlledAction(emulationMenu, frameRewind, "frameRewind"); ConfigOption* videoSync = m_config->addOption("videoSync");

@@ -1130,6 +1169,11 @@ QAction* memoryView = new QAction(tr("View memory..."), toolsMenu);

connect(memoryView, SIGNAL(triggered()), this, SLOT(openMemoryWindow())); m_gameActions.append(memoryView); addControlledAction(toolsMenu, memoryView, "memoryView"); + + QAction* ioViewer = new QAction(tr("View &I/O registers..."), toolsMenu); + connect(ioViewer, SIGNAL(triggered()), this, SLOT(openIOViewer())); + m_gameActions.append(ioViewer); + addControlledAction(toolsMenu, ioViewer, "ioViewer"); ConfigOption* skipBios = m_config->addOption("skipBios"); skipBios->connect([this](const QVariant& value) {
M src/platform/qt/Window.hsrc/platform/qt/Window.h

@@ -69,6 +69,8 @@ void saveConfig();

void replaceROM(); + void multiplayerChanged(); + void importSharkport(); void exportSharkport();

@@ -82,6 +84,7 @@ void openCheatsWindow();

void openPaletteWindow(); void openMemoryWindow(); + void openIOViewer(); void openAboutScreen();

@@ -149,6 +152,7 @@

GameController* m_controller; Display* m_display; QList<QAction*> m_gameActions; + QList<QAction*> m_nonMpActions; QMap<int, QAction*> m_frameSizes; LogController m_log; LogView* m_logView;
M src/platform/sdl/CMakeLists.txtsrc/platform/sdl/CMakeLists.txt

@@ -37,6 +37,10 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework AppKit -framework AudioUnit -framework Carbon -framework CoreAudio -framework ForceFeedback -framework IOKit")

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}" PARENT_SCOPE) endif() +if(NOT SDLMAIN_LIBRARY) + set(SDLMAIN_LIBRARY "") +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)

@@ -57,7 +61,7 @@ set(OPENGLES2_LIBRARY "-lEGL -lGLESv2 -lbcm_host")

set(BUILD_GLES2 ON CACHE BOOL "Using OpenGL|ES 2" FORCE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fgnu89-inline") add_executable(${BINARY_NAME}-rpi ${PLATFORM_SRC} ${MAIN_SRC}) - set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") + set_target_properties(${BINARY_NAME}-rpi PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-rpi ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGLES2_LIBRARY}) install(TARGETS ${BINARY_NAME}-rpi DESTINATION bin COMPONENT ${BINARY_NAME}-rpi) unset(OPENGLES2_INCLUDE_DIR} CACHE) # Clear NOTFOUND

@@ -80,7 +84,7 @@ endif()

endif() add_executable(${BINARY_NAME}-sdl WIN32 ${PLATFORM_SRC} ${MAIN_SRC}) -set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +set_target_properties(${BINARY_NAME}-sdl PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}-sdl ${BINARY_NAME} ${PLATFORM_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY}) set_target_properties(${BINARY_NAME}-sdl PROPERTIES OUTPUT_NAME ${BINARY_NAME}) install(TARGETS ${BINARY_NAME}-sdl DESTINATION bin COMPONENT ${BINARY_NAME}-sdl)
M src/platform/sdl/gl-sdl.csrc/platform/sdl/gl-sdl.c

@@ -61,7 +61,7 @@ }

#endif } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { v->postFrame(v, renderer->d.outputBuffer); } v->drawFrame(v);
M src/platform/sdl/gles2-sdl.csrc/platform/sdl/gles2-sdl.c

@@ -114,7 +114,7 @@ while (SDL_PollEvent(&event)) {

GBASDLHandleEvent(context, &renderer->player, &event); } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { v->postFrame(v, renderer->d.outputBuffer); } v->drawFrame(v);
M src/platform/sdl/pandora-sdl.csrc/platform/sdl/pandora-sdl.c

@@ -12,6 +12,26 @@ #include <linux/fb.h>

#include <sys/ioctl.h> #include <sys/mman.h> +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +#endif + +static bool GBASDLInit(struct SDLSoftwareRenderer* renderer); +static void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* renderer); +static void GBASDLDeinit(struct SDLSoftwareRenderer* renderer); + +void GBASDLGLCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLInit; + renderer->deinit = GBASDLDeinit; + renderer->runloop = GBASDLRunloop; +} + +void GBASDLSWCreate(struct SDLSoftwareRenderer* renderer) { + renderer->init = GBASDLInit; + renderer->deinit = GBASDLDeinit; + renderer->runloop = GBASDLRunloop; +} + bool GBASDLInit(struct SDLSoftwareRenderer* renderer) { SDL_SetVideoMode(800, 480, 16, SDL_FULLSCREEN);

@@ -71,14 +91,14 @@ while (SDL_PollEvent(&event)) {

GBASDLHandleEvent(context, &renderer->player, &event); } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { - int arg = 0; - ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg); - + if (GBASyncWaitFrameStart(&context->sync)) { struct fb_var_screeninfo info; ioctl(renderer->fb, FBIOGET_VSCREENINFO, &info); info.yoffset = VIDEO_VERTICAL_PIXELS * renderer->odd; ioctl(renderer->fb, FBIOPAN_DISPLAY, &info); + + int arg = 0; + ioctl(renderer->fb, FBIO_WAITFORVSYNC, &arg); renderer->odd = !renderer->odd; renderer->d.outputBuffer = renderer->base[renderer->odd];
M src/platform/sdl/sdl-audio.csrc/platform/sdl/sdl-audio.c

@@ -41,19 +41,24 @@ #endif

GBALog(0, GBA_LOG_ERROR, "Could not open SDL sound system"); return false; } - context->thread = threadContext; context->samples = context->obtainedSpec.samples; - float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); - threadContext->audioBuffers = context->samples / ratio; - if (context->samples > threadContext->audioBuffers) { - threadContext->audioBuffers = context->samples * 2; - } + context->gba = 0; + + if (threadContext) { + context->thread = threadContext; + float ratio = GBAAudioCalculateRatio(0x8000, threadContext->fpsTarget, 44100); + threadContext->audioBuffers = context->samples / ratio; + if (context->samples > threadContext->audioBuffers) { + threadContext->audioBuffers = context->samples * 2; + } #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_PauseAudioDevice(context->deviceId, 0); + SDL_PauseAudioDevice(context->deviceId, 0); #else - SDL_PauseAudio(0); + SDL_PauseAudio(0); #endif + } + return true; }

@@ -89,12 +94,15 @@ }

static void _GBASDLAudioCallback(void* context, Uint8* data, int len) { struct GBASDLAudio* audioContext = context; - if (!context || !audioContext->thread || !audioContext->thread->gba) { + if (!context || (!audioContext->gba && (!audioContext->thread || !audioContext->thread->gba))) { memset(data, 0, len); return; + } + if (!audioContext->gba) { + audioContext->gba = audioContext->thread->gba; } #if RESAMPLE_LIBRARY == RESAMPLE_NN - audioContext->ratio = GBAAudioCalculateRatio(audioContext->thread->gba->audio.sampleRate, audioContext->thread->fpsTarget, audioContext->obtainedSpec.freq); + audioContext->ratio = GBAAudioCalculateRatio(audioContext->gba->audio.sampleRate, audioContext->fpsTarget, audioContext->obtainedSpec.freq); if (audioContext->ratio == INFINITY) { memset(data, 0, len); return;

@@ -102,23 +110,29 @@ }

struct GBAStereoSample* ssamples = (struct GBAStereoSample*) data; len /= 2 * audioContext->obtainedSpec.channels; if (audioContext->obtainedSpec.channels == 2) { - GBAAudioResampleNN(&audioContext->thread->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len); + GBAAudioResampleNN(&audioContext->gba->audio, audioContext->ratio, &audioContext->drift, ssamples, len); } #elif RESAMPLE_LIBRARY == RESAMPLE_BLIP_BUF - double fauxClock = GBAAudioCalculateRatio(1, audioContext->thread->fpsTarget, 1); - GBASyncLockAudio(&audioContext->thread->sync); - blip_set_rates(audioContext->thread->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); - blip_set_rates(audioContext->thread->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); + double fauxClock = 1; + if (audioContext->thread) { + GBAAudioCalculateRatio(1, audioContext->thread->fpsTarget, 1); + GBASyncLockAudio(&audioContext->thread->sync); + } + blip_set_rates(audioContext->gba->audio.left, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); + blip_set_rates(audioContext->gba->audio.right, GBA_ARM7TDMI_FREQUENCY, audioContext->obtainedSpec.freq * fauxClock); len /= 2 * audioContext->obtainedSpec.channels; - int available = blip_samples_avail(audioContext->thread->gba->audio.left); + int available = blip_samples_avail(audioContext->gba->audio.left); if (available > len) { available = len; } - blip_read_samples(audioContext->thread->gba->audio.left, (short*) data, available, audioContext->obtainedSpec.channels == 2); + blip_read_samples(audioContext->gba->audio.left, (short*) data, available, audioContext->obtainedSpec.channels == 2); if (audioContext->obtainedSpec.channels == 2) { - blip_read_samples(audioContext->thread->gba->audio.right, ((short*) data) + 1, available, 1); + blip_read_samples(audioContext->gba->audio.right, ((short*) data) + 1, available, 1); } - GBASyncConsumeAudio(&audioContext->thread->sync); + + if (audioContext->thread) { + GBASyncConsumeAudio(&audioContext->thread->sync); + } if (available < len) { memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short)); }
M src/platform/sdl/sdl-audio.hsrc/platform/sdl/sdl-audio.h

@@ -31,6 +31,7 @@ SDL_AudioDeviceID deviceId;

#endif struct GBAThread* thread; + struct GBA* gba; }; bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext);
M src/platform/sdl/sw-sdl.csrc/platform/sdl/sw-sdl.c

@@ -93,7 +93,7 @@ while (SDL_PollEvent(&event)) {

GBASDLHandleEvent(context, &renderer->player, &event); } - if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) { + if (GBASyncWaitFrameStart(&context->sync)) { #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_UnlockTexture(renderer->sdlTex); SDL_RenderCopy(renderer->sdlRenderer, renderer->sdlTex, 0, 0);
M src/platform/test/fuzz-main.csrc/platform/test/fuzz-main.c

@@ -68,24 +68,28 @@ GBAContextDeinit(&context);

return !parsed; } + struct GBAVideoSoftwareRenderer renderer; + renderer.outputBuffer = 0; + + if (!fuzzOpts.noVideo) { + GBAVideoSoftwareRendererCreate(&renderer); + renderer.outputBuffer = malloc(256 * 256 * 4); + renderer.outputBufferStride = 256; + context.renderer = &renderer.d; + } + +#ifdef __AFL_HAVE_MANUAL_CONTROL + __AFL_INIT(); +#endif + struct VFile* rom = VFileOpen(args.fname, O_RDONLY); context.gba->hardCrash = false; GBAContextLoadROMFromVFile(&context, rom, 0); - struct GBAVideoSoftwareRenderer renderer; - renderer.outputBuffer = 0; - struct VFile* savestate = 0; struct VFile* savestateOverlay = 0; size_t overlayOffset; - - if (!fuzzOpts.noVideo) { - GBAVideoSoftwareRendererCreate(&renderer); - renderer.outputBuffer = malloc(256 * 256 * 4); - renderer.outputBufferStride = 256; - context->renderer = &renderer.d; - } GBAContextStart(&context);

@@ -121,18 +125,21 @@ blip_set_rates(context.gba->audio.right, GBA_ARM7TDMI_FREQUENCY, 0x8000);

_GBAFuzzRunloop(&context, fuzzOpts.frames); + GBAContextStop(&context); + GBAContextUnloadROM(&context); + if (savestate) { savestate->close(savestate); } if (savestateOverlay) { savestateOverlay->close(savestateOverlay); } - GBAContextStop(&context); - GBAContextDeinit(&context); + freeArguments(&args); if (renderer.outputBuffer) { free(renderer.outputBuffer); } + GBAContextDeinit(&context); return 0; }
M src/platform/test/perf-main.csrc/platform/test/perf-main.c

@@ -167,7 +167,7 @@ int duration = *frames;

*frames = 0; int lastFrames = 0; while (context->state < THREAD_EXITING) { - if (GBASyncWaitFrameStart(&context->sync, 0)) { + if (GBASyncWaitFrameStart(&context->sync)) { ++*frames; ++lastFrames; if (!quiet) {
M src/platform/wii/CMakeLists.txtsrc/platform/wii/CMakeLists.txt

@@ -1,7 +1,38 @@

-add_executable(${BINARY_NAME}.elf ${GUI_SRC} ${CMAKE_BINARY_DIR}/font.c) -set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES}") +find_program(ELF2DOL elf2dol) +find_program(GXTEXCONV gxtexconv) +find_program(RAW2C raw2c) +find_program(WIILOAD wiiload) + +set(OS_DEFINES COLOR_16_BIT COLOR_5_6_5 USE_VFS_FILE) +list(APPEND VFS_SRC ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-file.c ${CMAKE_SOURCE_DIR}/src/util/vfs/vfs-dirent.c) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +file(GLOB OS_SRC ${CMAKE_SOURCE_DIR}/src/platform/wii/wii-*.c) +list(APPEND OS_LIB wiiuse bte fat ogc) +set(OS_SRC ${OS_SRC} PARENT_SCOPE) +source_group("Wii-specific code" FILES ${OS_SRC}) +set(VFS_SRC ${VFS_SRC} PARENT_SCOPE) +set(OS_DEFINES ${OS_DEFINES} PARENT_SCOPE) + +list(APPEND GUI_SRC ${CMAKE_CURRENT_BINARY_DIR}/font.c ${CMAKE_CURRENT_SOURCE_DIR}/gui-font.c) + +set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/font.c PROPERTIES GENERATED ON) +add_executable(${BINARY_NAME}.elf ${GUI_SRC} main.c) +set_target_properties(${BINARY_NAME}.elf PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}") target_link_libraries(${BINARY_NAME}.elf ${BINARY_NAME} ${OS_LIB}) -add_custom_command(TARGET ${BINARY_NAME}.elf POST_BUILD COMMAND ${ELF2DOL} ${BINARY_NAME}.elf ${BINARY_NAME}.dol) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/font.c + COMMAND ${RAW2C} ${CMAKE_SOURCE_DIR}/src/platform/wii/font.tpl + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + +add_custom_target(${BINARY_NAME}.dol ALL + ${ELF2DOL} ${BINARY_NAME}.elf ${BINARY_NAME}.dol + DEPENDS ${BINARY_NAME}.elf) + +add_custom_target(run ${WIILOAD} ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.dol + DEPENDS ${BINARY_NAME}.dol) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/meta.xml.in ${CMAKE_CURRENT_BINARY_DIR}/meta.xml) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/icon.png ${CMAKE_CURRENT_BINARY_DIR}/meta.xml DESTINATION . COMPONENT ${BINARY_NAME}-wii) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.dol DESTINATION . RENAME boot.dol COMPONENT ${BINARY_NAME}-wii)
M src/platform/wii/CMakeToolchain.txtsrc/platform/wii/CMakeToolchain.txt

@@ -10,33 +10,34 @@ else()

set(DEVKITPPC ${DEVKITPRO}/devkitPPC) endif() -set(toolchain_bin_dir ${DEVKITPPC}/bin) -set(cross_prefix ${toolchain_bin_dir}/powerpc-eabi-) -set(inc_flags -I${DEVKITPRO}/libogc/include) +set(extension) +if (CMAKE_HOST_WIN32) + set(extension .exe) +endif() + +set(CMAKE_PROGRAM_PATH ${DEVKITPPC}/bin) +set(cross_prefix ${CMAKE_PROGRAM_PATH}/powerpc-eabi-) set(arch_flags "-mrvl -mcpu=750 -meabi -mhard-float -g") +set(inc_flags "-I${DEVKITPRO}/libogc/include ${arch_flags}") set(link_flags "-L${DEVKITPRO}/libogc/lib/wii ${arch_flags}") set(CMAKE_SYSTEM_NAME Generic CACHE INTERNAL "system name") -set(CMAKE_SYSTEM_PROCESSOR arm CACHE INTERNAL "processor") +set(CMAKE_SYSTEM_PROCESSOR powerpc CACHE INTERNAL "processor") set(CMAKE_LIBRARY_ARCHITECTURE powerpc-none-eabi CACHE INTERNAL "abi") -set(CMAKE_AR ${cross_prefix}gcc-ar CACHE INTERNAL "archiver") -set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib CACHE INTERNAL "archiver") -set(CMAKE_C_COMPILER ${cross_prefix}gcc CACHE INTERNAL "c compiler") -set(CMAKE_CXX_COMPILER ${cross_prefix}g++ CACHE INTERNAL "cxx compiler") -set(CMAKE_ASM_COMPILER ${cross_prefix}gcc CACHE INTERNAL "assembler") -set(common_flags "${arch_flags} ${inc_flags}") -set(CMAKE_C_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_ASM_FLAGS ${common_flags} CACHE INTERNAL "c compiler flags") -set(CMAKE_CXX_FLAGS ${common_flags} CACHE INTERNAL "cxx compiler flags") + +set(CMAKE_AR ${cross_prefix}gcc-ar${extension} CACHE INTERNAL "archiver") +set(CMAKE_RANLIB ${cross_prefix}gcc-ranlib${extension} CACHE INTERNAL "archiver") +set(CMAKE_C_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "c compiler") +set(CMAKE_CXX_COMPILER ${cross_prefix}g++${extension} CACHE INTERNAL "cxx compiler") +set(CMAKE_ASM_COMPILER ${cross_prefix}gcc${extension} CACHE INTERNAL "assembler") +set(CMAKE_C_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_ASM_FLAGS ${inc_flags} CACHE INTERNAL "c compiler flags") +set(CMAKE_CXX_FLAGS ${inc_flags} CACHE INTERNAL "cxx compiler flags") set(CMAKE_LINKER ${cross_prefix}ld CACHE INTERNAL "linker") set(CMAKE_EXE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "exe link flags") set(CMAKE_MODULE_LINKER_FLAGS ${link_flags} CACHE INTERNAL "module link flags") set(CMAKE_SHARED_LINKER_FLAGS ${link_flags} CACHE INTERNAL "shared link flags") - -set(ELF2DOL ${toolchain_bin_dir}/elf2dol) -set(GXTEXCONV ${toolchain_bin_dir}/gxtexconv) -set(RAW2C ${toolchain_bin_dir}/raw2c) set(WII ON) add_definitions(-DGEKKO)
M src/platform/wii/main.csrc/platform/wii/main.c

@@ -7,6 +7,7 @@ #define asm __asm__

#include <fat.h> #include <gccore.h> +#include <ogc/machine/processor.h> #include <malloc.h> #include <wiiuse/wpad.h>

@@ -22,7 +23,7 @@ #include "util/vfs.h"

#define SAMPLES 1024 -static void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args); +static void _retraceCallback(u32 count); static void _audioDMA(void); static void _setRumble(struct GBARumble* rumble, int enable);

@@ -34,6 +35,7 @@

static void _drawStart(void); static void _drawEnd(void); static uint32_t _pollInput(void); +static enum GUICursorState _pollCursor(int* x, int* y); static void _guiPrepare(void); static void _guiFinish(void);

@@ -43,19 +45,23 @@ static void _gameUnloaded(struct GBAGUIRunner* runner);

static void _drawFrame(struct GBAGUIRunner* runner, bool faded); static uint16_t _pollGameInput(struct GBAGUIRunner* runner); +static s8 WPAD_StickX(u8 chan, u8 right); +static s8 WPAD_StickY(u8 chan, u8 right); + static struct GBAVideoSoftwareRenderer renderer; static struct GBARumble rumble; static struct GBARotationSource rotation; -static FILE* logfile; -static GXRModeObj* mode; +static GXRModeObj* vmode; static Mtx model, view, modelview; static uint16_t* texmem; static GXTexObj tex; static int32_t tiltX; static int32_t tiltY; static int32_t gyroZ; +static uint32_t retraceCount; +static uint32_t referenceRetraceCount; -static void* framebuffer[2]; +static void* framebuffer[2] = { 0, 0 }; static int whichFb = 0; static struct GBAStereoSample audioBuffer[3][SAMPLES] __attribute__((__aligned__(32)));

@@ -64,10 +70,38 @@ static volatile int currentAudioBuffer = 0;

static struct GUIFont* font; +static void reconfigureScreen(GXRModeObj* vmode) { + free(framebuffer[0]); + free(framebuffer[1]); + + framebuffer[0] = SYS_AllocateFramebuffer(vmode); + framebuffer[1] = SYS_AllocateFramebuffer(vmode); + + VIDEO_SetBlack(true); + VIDEO_Configure(vmode); + VIDEO_SetNextFramebuffer(framebuffer[whichFb]); + VIDEO_SetBlack(false); + VIDEO_Flush(); + VIDEO_WaitVSync(); + if (vmode->viTVMode & VI_NON_INTERLACE) { + VIDEO_WaitVSync(); + } + GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1); + + f32 yscale = GX_GetYScaleFactor(vmode->efbHeight, vmode->xfbHeight); + u32 xfbHeight = GX_SetDispCopyYScale(yscale); + GX_SetScissor(0, 0, vmode->viWidth, vmode->viWidth); + GX_SetDispCopySrc(0, 0, vmode->fbWidth, vmode->efbHeight); + GX_SetDispCopyDst(vmode->fbWidth, xfbHeight); + GX_SetCopyFilter(vmode->aa, vmode->sample_pattern, GX_TRUE, vmode->vfilter); + GX_SetFieldMode(vmode->field_rendering, ((vmode->viHeight == 2 * vmode->xfbHeight) ? GX_ENABLE : GX_DISABLE)); +}; + int main() { VIDEO_Init(); PAD_Init(); WPAD_Init(); + WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC_IR); AUDIO_Init(0); AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ); AUDIO_RegisterDMACallback(_audioDMA);

@@ -78,33 +112,15 @@ #if !defined(COLOR_16_BIT) && !defined(COLOR_5_6_5)

#error This pixel format is unsupported. Please use -DCOLOR_16-BIT -DCOLOR_5_6_5 #endif - mode = VIDEO_GetPreferredMode(0); - framebuffer[0] = SYS_AllocateFramebuffer(mode); - framebuffer[1] = SYS_AllocateFramebuffer(mode); - - VIDEO_Configure(mode); - VIDEO_SetNextFramebuffer(framebuffer[whichFb]); - VIDEO_SetBlack(FALSE); - VIDEO_Flush(); - VIDEO_WaitVSync(); - if (mode->viTVMode & VI_NON_INTERLACE) { - VIDEO_WaitVSync(); - } + vmode = VIDEO_GetPreferredMode(0); GXColor bg = { 0, 0, 0, 0xFF }; void* fifo = memalign(32, 0x40000); memset(fifo, 0, 0x40000); GX_Init(fifo, 0x40000); GX_SetCopyClear(bg, 0x00FFFFFF); - GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1); - f32 yscale = GX_GetYScaleFactor(mode->efbHeight, mode->xfbHeight); - u32 xfbHeight = GX_SetDispCopyYScale(yscale); - GX_SetScissor(0, 0, mode->viWidth, mode->viWidth); - GX_SetDispCopySrc(0, 0, mode->fbWidth, mode->efbHeight); - GX_SetDispCopyDst(mode->fbWidth, xfbHeight); - GX_SetCopyFilter(mode->aa, mode->sample_pattern, GX_TRUE, mode->vfilter); - GX_SetFieldMode(mode->field_rendering, ((mode->viHeight == 2 * mode->xfbHeight) ? GX_ENABLE : GX_DISABLE)); + reconfigureScreen(vmode); GX_SetCullMode(GX_CULL_NONE); GX_CopyDisp(framebuffer[whichFb], GX_TRUE);

@@ -142,11 +158,11 @@ texmem = memalign(32, 256 * 256 * BYTES_PER_PIXEL);

memset(texmem, 0, 256 * 256 * BYTES_PER_PIXEL); GX_InitTexObj(&tex, texmem, 256, 256, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, GX_FALSE); + VIDEO_SetPostRetraceCallback(_retraceCallback); + font = GUIFontCreate(); fatInitDefault(); - - logfile = fopen("/mgba.log", "w"); rumble.setRumble = _setRumble;

@@ -159,7 +175,9 @@ struct GBAGUIRunner runner = {

.params = { 352, 230, font, "/", - _drawStart, _drawEnd, _pollInput, + _drawStart, _drawEnd, + _pollInput, _pollCursor, + 0, _guiPrepare, _guiFinish, GUI_PARAMS_TRAIL

@@ -174,28 +192,19 @@ .paused = _gameUnloaded,

.unpaused = 0, .pollGameInput = _pollGameInput }; - GBAGUIInit(&runner, 0); + GBAGUIInit(&runner, "wii"); GBAGUIRunloop(&runner); GBAGUIDeinit(&runner); - fclose(logfile); free(fifo); free(renderer.outputBuffer); GUIFontDestroy(font); - return 0; -} + free(framebuffer[0]); + free(framebuffer[1]); -void GBAWiiLog(struct GBAThread* thread, enum GBALogLevel level, const char* format, va_list args) { - UNUSED(thread); - UNUSED(level); - if (!logfile) { - return; - } - vfprintf(logfile, format, args); - fprintf(logfile, "\n"); - fflush(logfile); + return 0; } static void _audioDMA(void) {

@@ -209,11 +218,17 @@ audioBufferSize = 0;

} static void _drawStart(void) { - VIDEO_WaitVSync(); + u32 level = 0; + _CPU_ISR_Disable(level); + if (referenceRetraceCount >= retraceCount) { + VIDEO_WaitVSync(); + } + _CPU_ISR_Restore(level); + GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE); GX_SetColorUpdate(GX_TRUE); - GX_SetViewport(0, 0, mode->fbWidth, mode->efbHeight, 0, 1); + GX_SetViewport(0, 0, vmode->fbWidth, vmode->efbHeight, 0, 1); } static void _drawEnd(void) {

@@ -224,6 +239,11 @@

GX_CopyDisp(framebuffer[whichFb], GX_TRUE); VIDEO_SetNextFramebuffer(framebuffer[whichFb]); VIDEO_Flush(); + + u32 level = 0; + _CPU_ISR_Disable(level); + ++referenceRetraceCount; + _CPU_ISR_Restore(level); } static uint32_t _pollInput(void) {

@@ -238,23 +258,25 @@

int keys = 0; int x = PAD_StickX(0); int y = PAD_StickY(0); - if (x < -0x40) { + int w_x = WPAD_StickX(0,0); + int w_y = WPAD_StickY(0,0); + if (x < -0x40 || w_x < -0x40) { keys |= 1 << GUI_INPUT_LEFT; } - if (x > 0x40) { + if (x > 0x40 || w_x > 0x40) { keys |= 1 << GUI_INPUT_RIGHT; } - if (y < -0x40) { + if (y < -0x40 || w_y <- 0x40) { keys |= 1 << GUI_INPUT_DOWN; } - if (y > 0x40) { + if (y > 0x40 || w_y > 0x40) { keys |= 1 << GUI_INPUT_UP; } if ((padkeys & PAD_BUTTON_A) || (wiiPad & WPAD_BUTTON_2) || ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_A | WPAD_CLASSIC_BUTTON_Y)))) { keys |= 1 << GUI_INPUT_SELECT; } - if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || + if ((padkeys & PAD_BUTTON_B) || (wiiPad & WPAD_BUTTON_1) || (wiiPad & WPAD_BUTTON_B) || ((ext == WPAD_EXP_CLASSIC) && (wiiPad & (WPAD_CLASSIC_BUTTON_B | WPAD_CLASSIC_BUTTON_X)))) { keys |= 1 << GUI_INPUT_BACK; }

@@ -281,6 +303,22 @@ }

return keys; } +static enum GUICursorState _pollCursor(int* x, int* y) { + ir_t ir; + WPAD_IR(0, &ir); + if (!ir.smooth_valid) { + return GUI_CURSOR_NOT_PRESENT; + } + *x = ir.sx; + *y = ir.sy; + WPAD_ScanPads(); + u32 wiiPad = WPAD_ButtonsHeld(0); + if (wiiPad & WPAD_BUTTON_A) { + return GUI_CURSOR_DOWN; + } + return GUI_CURSOR_UP; +} + void _guiPrepare(void) { Mtx44 proj; guOrtho(proj, -20, 240, 0, 352, 0, 300);

@@ -289,18 +327,13 @@ }

void _guiFinish(void) { Mtx44 proj; - guOrtho(proj, -10, VIDEO_VERTICAL_PIXELS + 10, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300); + short top = (CONF_GetAspectRatio() == CONF_ASPECT_16_9) ? 10 : 20; + short bottom = VIDEO_VERTICAL_PIXELS + top; + guOrtho(proj, -top, bottom, 0, VIDEO_HORIZONTAL_PIXELS, 0, 300); GX_LoadProjectionMtx(proj, GX_ORTHOGRAPHIC); } void _setup(struct GBAGUIRunner* runner) { - struct GBAOptions opts = { - .useBios = true, - .logLevel = 0, - .idleOptimization = IDLE_LOOP_DETECT - }; - GBAConfigLoadDefaults(&runner->context.config, &opts); - runner->context.gba->logHandler = GBAWiiLog; runner->context.gba->rumble = &rumble; runner->context.gba->rotationSource = &rotation;

@@ -324,7 +357,6 @@ AUDIO_StopDMA();

} void _gameLoaded(struct GBAGUIRunner* runner) { - WPAD_SetDataFormat(0, WPAD_FMT_BTNS_ACC); if (runner->context.gba->memory.hw.devices & HW_GYRO) { int i; for (i = 0; i < 6; ++i) {

@@ -335,6 +367,10 @@ }

sleep(1); } } + u32 level = 0; + _CPU_ISR_Disable(level); + referenceRetraceCount = retraceCount; + _CPU_ISR_Restore(level); } void _drawFrame(struct GBAGUIRunner* runner, bool faded) {

@@ -453,16 +489,18 @@ keys |= 1 << GBA_KEY_DOWN;

} int x = PAD_StickX(0); int y = PAD_StickY(0); - if (x < -0x40) { + int w_x = WPAD_StickX(0,0); + int w_y = WPAD_StickY(0,0); + if (x < -0x40 || w_x < -0x40) { keys |= 1 << GBA_KEY_LEFT; } - if (x > 0x40) { + if (x > 0x40 || w_x > 0x40) { keys |= 1 << GBA_KEY_RIGHT; } - if (y < -0x40) { + if (y < -0x40 || w_y < -0x40) { keys |= 1 << GBA_KEY_DOWN; } - if (y > 0x40) { + if (y > 0x40 || w_y > 0x40) { keys |= 1 << GBA_KEY_UP; } return keys;

@@ -510,3 +548,85 @@ int32_t _readGyroZ(struct GBARotationSource* source) {

UNUSED(source); return gyroZ; } + +static s8 WPAD_StickX(u8 chan, u8 right) { + float mag = 0.0; + float ang = 0.0; + WPADData *data = WPAD_Data(chan); + + switch (data->exp.type) { + case WPAD_EXP_NUNCHUK: + case WPAD_EXP_GUITARHERO3: + if (right == 0) { + mag = data->exp.nunchuk.js.mag; + ang = data->exp.nunchuk.js.ang; + } + break; + case WPAD_EXP_CLASSIC: + if (right == 0) { + mag = data->exp.classic.ljs.mag; + ang = data->exp.classic.ljs.ang; + } else { + mag = data->exp.classic.rjs.mag; + ang = data->exp.classic.rjs.ang; + } + break; + default: + break; + } + + /* calculate X value (angle need to be converted into radian) */ + if (mag > 1.0) { + mag = 1.0; + } else if (mag < -1.0) { + mag = -1.0; + } + double val = mag * sinf(M_PI * ang / 180.0f); + + return (s8)(val * 128.0f); +} + + +static s8 WPAD_StickY(u8 chan, u8 right) { + float mag = 0.0; + float ang = 0.0; + WPADData *data = WPAD_Data(chan); + + switch (data->exp.type) { + case WPAD_EXP_NUNCHUK: + case WPAD_EXP_GUITARHERO3: + if (right == 0) { + mag = data->exp.nunchuk.js.mag; + ang = data->exp.nunchuk.js.ang; + } + break; + case WPAD_EXP_CLASSIC: + if (right == 0) { + mag = data->exp.classic.ljs.mag; + ang = data->exp.classic.ljs.ang; + } else { + mag = data->exp.classic.rjs.mag; + ang = data->exp.classic.rjs.ang; + } + break; + default: + break; + } + + /* calculate X value (angle need to be converted into radian) */ + if (mag > 1.0) { + mag = 1.0; + } else if (mag < -1.0) { + mag = -1.0; + } + double val = mag * cosf(M_PI * ang / 180.0f); + + return (s8)(val * 128.0f); +} + +void _retraceCallback(u32 count) { + u32 level = 0; + _CPU_ISR_Disable(level); + retraceCount = count; + _CPU_ISR_Restore(level); +}
M src/util/common.hsrc/util/common.h

@@ -52,13 +52,13 @@

#if defined(__PPC__) || defined(__POWERPC__) #define LOAD_32LE(DEST, ADDR, ARR) { \ uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ + const void* _ptr = (ARR); \ __asm__("lwbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ } #define LOAD_16LE(DEST, ADDR, ARR) { \ uint32_t _addr = (ADDR); \ - void* _ptr = (ARR); \ + const void* _ptr = (ARR); \ __asm__("lhbrx %0, %1, %2" : "=r"(DEST) : "b"(_ptr), "r"(_addr)); \ }
M src/util/formatting.csrc/util/formatting.c

@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "formatting.h" #include <float.h> +#include <time.h> int ftostr_l(char* restrict str, size_t size, float f, locale_t locale) { #ifdef HAVE_SNPRINTF_L

@@ -70,3 +71,17 @@ freelocale(l);

#endif return res; } + +#ifndef HAVE_LOCALTIME_R +struct tm* localtime_r(const time_t* t, struct tm* date) { +#ifdef _WIN32 + localtime_s(date, t); + return date; +#elif defined(PSP2) + return sceKernelLibcLocaltime_r(t, date); +#else +#warning localtime_r not emulated on this platform + return 0; +#endif +} +#endif
M src/util/formatting.hsrc/util/formatting.h

@@ -27,4 +27,8 @@

int ftostr_u(char* restrict str, size_t size, float f); float strtof_u(const char* restrict str, char** restrict end); +#ifndef HAVE_LOCALTIME_R +struct tm* localtime_r(const time_t* timep, struct tm* result); +#endif + #endif
M src/util/gui.csrc/util/gui.c

@@ -30,9 +30,3 @@ if (heldInput) {

*heldInput = input; } } - -void GUIInvalidateKeys(struct GUIParams* params) { - for (int i = 0; i < GUI_INPUT_MAX; ++i) { - params->inputHistory[i] = 0; - } -}
M src/util/gui.hsrc/util/gui.h

@@ -28,6 +28,24 @@

GUI_INPUT_MAX = 0x20 }; +enum GUICursorState { + GUI_CURSOR_NOT_PRESENT, + GUI_CURSOR_UP, + GUI_CURSOR_DOWN, + GUI_CURSOR_CLICKED, + GUI_CURSOR_DRAGGING +}; + +enum { + BATTERY_EMPTY = 0, + BATTERY_LOW = 1, + BATTERY_HALF = 2, + BATTERY_HIGH = 3, + BATTERY_FULL = 4, + + BATTERY_CHARGING = 8 +}; + struct GUIBackground { void (*draw)(struct GUIBackground*, void* context); };

@@ -41,21 +59,26 @@

void (*drawStart)(void); void (*drawEnd)(void); uint32_t (*pollInput)(void); + enum GUICursorState (*pollCursor)(int* x, int* y); + int (*batteryState)(void); void (*guiPrepare)(void); void (*guiFinish)(void); // State int inputHistory[GUI_INPUT_MAX]; + enum GUICursorState cursorState; + int cx, cy; // Directories char currentPath[PATH_MAX]; size_t fileIndex; }; -#define GUI_PARAMS_TRAIL {}, "", 0 +#define GUI_PARAMS_TRAIL {}, GUI_CURSOR_NOT_PRESENT, 0, 0, "", 0 void GUIInit(struct GUIParams* params); void GUIPollInput(struct GUIParams* params, uint32_t* newInput, uint32_t* heldInput); +enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y); void GUIInvalidateKeys(struct GUIParams* params); #endif
M src/util/gui/file-select.csrc/util/gui/file-select.c

@@ -12,7 +12,13 @@

#include <stdlib.h> #define ITERATION_SIZE 5 -#define SCANNING_THRESHOLD 20 +#define SCANNING_THRESHOLD_1 50 +#ifdef _3DS +// 3DS is slooooow at opening files +#define SCANNING_THRESHOLD_2 10 +#else +#define SCANNING_THRESHOLD_2 50 +#endif static void _cleanFiles(struct GUIMenuItemList* currentFiles) { size_t size = GUIMenuItemListSize(currentFiles);

@@ -40,7 +46,7 @@ // TODO: What if there was a trailing slash?

} static int _strpcmp(const void* a, const void* b) { - return strcmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); + return strcasecmp(((const struct GUIMenuItem*) a)->title, ((const struct GUIMenuItem*) b)->title); } static bool _refreshDirectory(struct GUIParams* params, const char* currentPath, struct GUIMenuItemList* currentFiles, bool (*filter)(struct VFile*)) {

@@ -52,10 +58,11 @@ return false;

} *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = "(Up)" }; size_t i = 0; + size_t items = 0; struct VDirEntry* de; while ((de = dir->listNext(dir))) { ++i; - if (!(i % SCANNING_THRESHOLD)) { + if (!(i % SCANNING_THRESHOLD_1)) { uint32_t input = 0; GUIPollInput(params, &input, 0); if (input & (1 << GUI_INPUT_CANCEL)) {

@@ -66,8 +73,8 @@ params->drawStart();

if (params->guiPrepare) { params->guiPrepare(); } - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); - GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %lu)", i); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning for items: %zu)", i); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); if (params->guiFinish) { params->guiFinish(); }

@@ -77,35 +84,71 @@ const char* name = de->name(de);

if (name[0] == '.') { continue; } - if (de->type(de) == VFS_FILE) { - struct VFile* vf = dir->openFile(dir, name, O_RDONLY); - if (!vf) { - continue; + *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) }; + ++items; + } + qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp); + i = 0; + size_t item = 0; + while (item < GUIMenuItemListSize(currentFiles)) { + ++i; + if (!(i % SCANNING_THRESHOLD_2)) { + uint32_t input = 0; + GUIPollInput(params, &input, 0); + if (input & (1 << GUI_INPUT_CANCEL)) { + return false; } - if (!filter || filter(vf)) { - *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) }; + + params->drawStart(); + if (params->guiPrepare) { + params->guiPrepare(); + } + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font), GUI_TEXT_LEFT, 0xFFFFFFFF, "(scanning item %zu of %zu)", i, items); + GUIFontPrintf(params->font, 0, GUIFontHeight(params->font) * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, "%s", currentPath); + if (params->guiFinish) { + params->guiFinish(); + } + params->drawEnd(); + } + if (!filter) { + ++item; + continue; + } + struct VDir* vd = dir->openDir(dir, GUIMenuItemListGetPointer(currentFiles, item)->title); + if (vd) { + vd->close(vd); + ++item; + continue; + } + struct VFile* vf = dir->openFile(dir, GUIMenuItemListGetPointer(currentFiles, item)->title, O_RDONLY); + if (vf) { + if (filter(vf)) { + ++item; + } else { + free(GUIMenuItemListGetPointer(currentFiles, item)->title); + GUIMenuItemListShift(currentFiles, item, 1); } vf->close(vf); - } else { - *GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) }; + continue; } + ++item; } dir->close(dir); - qsort(GUIMenuItemListGetPointer(currentFiles, 1), GUIMenuItemListSize(currentFiles) - 1, sizeof(struct GUIMenuItem), _strpcmp); return true; } bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool (*filter)(struct VFile*)) { struct GUIMenu menu = { - .title = params->currentPath, + .title = "Select file", + .subtitle = params->currentPath, .index = params->fileIndex, }; GUIMenuItemListInit(&menu.items, 0); _refreshDirectory(params, params->currentPath, &menu.items, filter); while (true) { - struct GUIMenuItem item; + struct GUIMenuItem* item; enum GUIMenuExitReason reason = GUIShowMenu(params, &menu, &item); params->fileIndex = menu.index; if (reason == GUI_MENU_EXIT_CANCEL) {

@@ -123,25 +166,16 @@ const char* sep = PATH_SEP;

if (params->currentPath[len - 1] == *sep) { sep = ""; } - snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item.title); + snprintf(outPath, outLen, "%s%s%s", params->currentPath, sep, item->title); struct GUIMenuItemList newFiles; GUIMenuItemListInit(&newFiles, 0); if (!_refreshDirectory(params, outPath, &newFiles, filter)) { _cleanFiles(&newFiles); GUIMenuItemListDeinit(&newFiles); - struct VFile* vf = VFileOpen(outPath, O_RDONLY); - if (!vf) { - continue; - } - if (!filter || filter(vf)) { - vf->close(vf); - _cleanFiles(&menu.items); - GUIMenuItemListDeinit(&menu.items); - return true; - } - vf->close(vf); - break; + _cleanFiles(&menu.items); + GUIMenuItemListDeinit(&menu.items); + return true; } else { _cleanFiles(&menu.items); GUIMenuItemListDeinit(&menu.items);
M src/util/gui/menu.csrc/util/gui/menu.c

@@ -10,19 +10,23 @@ #include "util/gui/font.h"

DEFINE_VECTOR(GUIMenuItemList, struct GUIMenuItem); -enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem* item) { +enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item) { size_t start = 0; - size_t pageSize = params->height / GUIFontHeight(params->font); + size_t lineHeight = GUIFontHeight(params->font); + size_t pageSize = params->height / lineHeight; if (pageSize > 4) { pageSize -= 4; } else { pageSize = 1; } + int cursorOverItem = 0; GUIInvalidateKeys(params); while (true) { uint32_t newInput = 0; GUIPollInput(params, &newInput, 0); + int cx, cy; + enum GUICursorState cursor = GUIPollCursor(params, &cx, &cy); if (newInput & (1 << GUI_INPUT_UP) && menu->index > 0) { --menu->index;

@@ -31,39 +35,63 @@ if (newInput & (1 << GUI_INPUT_DOWN) && menu->index < GUIMenuItemListSize(&menu->items) - 1) {

++menu->index; } if (newInput & (1 << GUI_INPUT_LEFT)) { - if (menu->index >= pageSize) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->validStates) { + if (item->state > 0) { + --item->state; + } + } else if (menu->index >= pageSize) { menu->index -= pageSize; } else { menu->index = 0; } } if (newInput & (1 << GUI_INPUT_RIGHT)) { - if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if (item->validStates) { + if (item->validStates[item->state + 1]) { + ++item->state; + } + } else if (menu->index + pageSize < GUIMenuItemListSize(&menu->items)) { menu->index += pageSize; } else { menu->index = GUIMenuItemListSize(&menu->items) - 1; } } + if (cursor != GUI_CURSOR_NOT_PRESENT) { + int index = (cy / lineHeight) - 2; + if (index >= 0 && index + start < GUIMenuItemListSize(&menu->items)) { + if (menu->index != index + start || !cursorOverItem) { + cursorOverItem = 1; + } + menu->index = index + start; + } else { + cursorOverItem = 0; + } + } if (menu->index < start) { start = menu->index; } - while ((menu->index - start + 4) * GUIFontHeight(params->font) > params->height) { + while ((menu->index - start + 4) * lineHeight > params->height) { ++start; } if (newInput & (1 << GUI_INPUT_CANCEL)) { break; } - if (newInput & (1 << GUI_INPUT_SELECT)) { - *item = *GUIMenuItemListGetPointer(&menu->items, menu->index); - if (item->submenu) { - enum GUIMenuExitReason reason = GUIShowMenu(params, item->submenu, item); + if (newInput & (1 << GUI_INPUT_SELECT) || (cursorOverItem == 2 && cursor == GUI_CURSOR_CLICKED)) { + *item = GUIMenuItemListGetPointer(&menu->items, menu->index); + if ((*item)->submenu) { + enum GUIMenuExitReason reason = GUIShowMenu(params, (*item)->submenu, item); if (reason != GUI_MENU_EXIT_BACK) { return reason; } } else { return GUI_MENU_EXIT_ACCEPT; } + } + if (cursorOverItem == 1 && (cursor == GUI_CURSOR_UP || cursor == GUI_CURSOR_NOT_PRESENT)) { + cursorOverItem = 2; } if (newInput & (1 << GUI_INPUT_BACK)) { return GUI_MENU_EXIT_BACK;

@@ -76,9 +104,12 @@ }

if (params->guiPrepare) { params->guiPrepare(); } - unsigned y = GUIFontHeight(params->font); + unsigned y = lineHeight; GUIFontPrint(params->font, 0, y, GUI_TEXT_LEFT, 0xFFFFFFFF, menu->title); - y += 2 * GUIFontHeight(params->font); + if (menu->subtitle) { + GUIFontPrint(params->font, 0, y * 2, GUI_TEXT_LEFT, 0xFFFFFFFF, menu->subtitle); + } + y += 2 * lineHeight; size_t i; for (i = start; i < GUIMenuItemListSize(&menu->items); ++i) { int color = 0xE0A0A0A0;

@@ -87,18 +118,113 @@ if (i == menu->index) {

color = 0xFFFFFFFF; bullet = '>'; } - GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, GUIMenuItemListGetPointer(&menu->items, i)->title); - y += GUIFontHeight(params->font); - if (y + GUIFontHeight(params->font) > params->height) { + struct GUIMenuItem* item = GUIMenuItemListGetPointer(&menu->items, i); + GUIFontPrintf(params->font, 0, y, GUI_TEXT_LEFT, color, "%c %s", bullet, item->title); + if (item->validStates) { + GUIFontPrintf(params->font, params->width, y, GUI_TEXT_RIGHT, color, "%s ", item->validStates[item->state]); + } + y += lineHeight; + if (y + lineHeight > params->height) { break; } } + + GUIDrawBattery(params); + GUIDrawClock(params); + if (params->guiFinish) { params->guiFinish(); } - y += GUIFontHeight(params->font) * 2; - params->drawEnd(); } return GUI_MENU_EXIT_CANCEL; } + +enum GUICursorState GUIPollCursor(struct GUIParams* params, int* x, int* y) { + if (!params->pollCursor) { + return GUI_CURSOR_NOT_PRESENT; + } + enum GUICursorState state = params->pollCursor(x, y); + if (params->cursorState == GUI_CURSOR_DOWN) { + int dragX = *x - params->cx; + int dragY = *y - params->cy; + if (dragX * dragX + dragY * dragY > 25) { + params->cursorState = GUI_CURSOR_DRAGGING; + return GUI_CURSOR_DRAGGING; + } + if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { + params->cursorState = GUI_CURSOR_UP; + return GUI_CURSOR_CLICKED; + } + } else { + params->cx = *x; + params->cy = *y; + } + if (params->cursorState == GUI_CURSOR_DRAGGING) { + if (state == GUI_CURSOR_UP || state == GUI_CURSOR_NOT_PRESENT) { + params->cursorState = GUI_CURSOR_UP; + return GUI_CURSOR_UP; + } + return GUI_CURSOR_DRAGGING; + } + params->cursorState = state; + return params->cursorState; +} + +void GUIInvalidateKeys(struct GUIParams* params) { + for (int i = 0; i < GUI_INPUT_MAX; ++i) { + params->inputHistory[i] = 0; + } +} + +void GUIDrawBattery(struct GUIParams* params) { + if (!params->batteryState) { + return; + } + int state = params->batteryState(); + uint32_t color = 0xFF000000; + if (state == (BATTERY_CHARGING | BATTERY_FULL)) { + color |= 0xFFC060; + } else if (state & BATTERY_CHARGING) { + color |= 0x60FF60; + } else if (state >= BATTERY_HALF) { + color |= 0xFFFFFF; + } else if (state == BATTERY_LOW) { + color |= 0x30FFFF; + } else { + color |= 0x3030FF; + } + + const char* batteryText; + switch (state & ~BATTERY_CHARGING) { + case BATTERY_EMPTY: + batteryText = "[ ]"; + break; + case BATTERY_LOW: + batteryText = "[I ]"; + break; + case BATTERY_HALF: + batteryText = "[II ]"; + break; + case BATTERY_HIGH: + batteryText = "[III ]"; + break; + case BATTERY_FULL: + batteryText = "[IIII]"; + break; + default: + batteryText = "[????]"; + break; + } + + GUIFontPrint(params->font, params->width, GUIFontHeight(params->font), GUI_TEXT_RIGHT, color, batteryText); +} + +void GUIDrawClock(struct GUIParams* params) { + char buffer[32]; + time_t t = time(0); + struct tm tm; + localtime_r(&t, &tm); + strftime(buffer, sizeof(buffer), "%H:%M:%S", &tm); + GUIFontPrint(params->font, params->width / 2, GUIFontHeight(params->font), GUI_TEXT_CENTER, 0xFFFFFFFF, buffer); +}
M src/util/gui/menu.hsrc/util/gui/menu.h

@@ -12,6 +12,8 @@ struct GUIMenu;

struct GUIMenuItem { const char* title; void* data; + unsigned state; + const char** validStates; struct GUIMenu* submenu; };

@@ -20,6 +22,7 @@

struct GUIBackground; struct GUIMenu { const char* title; + const char* subtitle; struct GUIMenuItemList items; size_t index; struct GUIBackground* background;

@@ -32,6 +35,9 @@ GUI_MENU_EXIT_CANCEL,

}; struct GUIParams; -enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem* item); +enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* menu, struct GUIMenuItem** item); + +void GUIDrawBattery(struct GUIParams* params); +void GUIDrawClock(struct GUIParams* params); #endif
M src/util/png-io.csrc/util/png-io.c

@@ -43,6 +43,9 @@ png_infop info = png_create_info_struct(png);

if (!info) { return 0; } + if (setjmp(png_jmpbuf(png))) { + return 0; + } png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); png_write_info(png, info); return info;

@@ -62,9 +65,15 @@ unsigned i;

for (i = 0; i < height; ++i) { unsigned x; for (x = 0; x < width; ++x) { +#if defined(__POWERPC__) || defined(__PPC__) + row[x * 3] = pixelData[stride * i * 4 + x * 4 + 3]; + row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 2]; + row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 1]; +#else row[x * 3] = pixelData[stride * i * 4 + x * 4]; row[x * 3 + 1] = pixelData[stride * i * 4 + x * 4 + 1]; row[x * 3 + 2] = pixelData[stride * i * 4 + x * 4 + 2]; +#endif } png_write_row(png, row); }

@@ -164,9 +173,16 @@ for (i = 0; i < pngHeight; ++i) {

png_read_row(png, row, 0); unsigned x; for (x = 0; x < pngWidth; ++x) { + +#if defined(__POWERPC__) || defined(__PPC__) + pixelData[stride * i * 4 + x * 4 + 3] = row[x * 3]; + pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 1]; + pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 2]; +#else pixelData[stride * i * 4 + x * 4] = row[x * 3]; pixelData[stride * i * 4 + x * 4 + 1] = row[x * 3 + 1]; pixelData[stride * i * 4 + x * 4 + 2] = row[x * 3 + 2]; +#endif } } free(row);
M src/util/vfs.csrc/util/vfs.c

@@ -5,6 +5,8 @@ * 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 "vfs.h" +#include "util/string.h" + #ifdef PSP2 #include "platform/psp2/sce-vfs.h" #endif

@@ -94,6 +96,21 @@ return VFileOpenFD(path, flags);

#endif } +struct VDir* VDirOpenArchive(const char* path) { + struct VDir* dir = 0; +#if USE_LIBZIP + if (!dir) { + dir = VDirOpenZip(path, 0); + } +#endif +#if USE_LZMA + if (!dir) { + dir = VDirOpen7z(path, 0); + } +#endif + return dir; +} + ssize_t VFileReadline(struct VFile* vf, char* buffer, size_t size) { size_t bytesRead = 0; while (bytesRead < size - 1) {

@@ -167,3 +184,95 @@ vf = dir->openFile(dir, path, mode);

} return vf; } + +struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { + char path[PATH_MAX]; + path[PATH_MAX - 1] = '\0'; + char realPrefix[PATH_MAX]; + realPrefix[PATH_MAX - 1] = '\0'; + if (!dir) { + if (!realPath) { + return 0; + } + const char* separatorPoint = strrchr(realPath, '/'); + const char* dotPoint; + size_t len; + if (!separatorPoint) { + strcpy(path, "./"); + separatorPoint = realPath; + dotPoint = strrchr(realPath, '.'); + } else { + path[0] = '\0'; + dotPoint = strrchr(separatorPoint, '.'); + + if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + len = separatorPoint - realPath; + strncat(path, realPath, len); + path[len] = '\0'; + ++separatorPoint; + } + + if (dotPoint - realPath + 1 >= PATH_MAX - 1) { + return 0; + } + + if (dotPoint >= separatorPoint) { + len = dotPoint - separatorPoint; + } else { + len = PATH_MAX - 1; + } + + strncpy(realPrefix, separatorPoint, len); + realPrefix[len] = '\0'; + + prefix = realPrefix; + dir = VDirOpen(path); + } + if (!dir) { + // This shouldn't be possible + return 0; + } + dir->rewind(dir); + struct VDirEntry* dirent; + size_t prefixLen = strlen(prefix); + size_t infixLen = strlen(infix); + unsigned next = 0; + while ((dirent = dir->listNext(dir))) { + const char* filename = dirent->name(dirent); + char* dotPoint = strrchr(filename, '.'); + size_t len = strlen(filename); + if (dotPoint) { + len = (dotPoint - filename); + } + const char* separator = strnrstr(filename, infix, len); + if (!separator) { + continue; + } + len = separator - filename; + if (len != prefixLen) { + continue; + } + if (strncmp(filename, prefix, prefixLen) == 0) { + int nlen; + separator += infixLen; + snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); + unsigned increment; + if (sscanf(separator, path, &increment, &nlen) < 1) { + continue; + } + len = strlen(separator); + if (nlen < (ssize_t) len) { + continue; + } + if (next <= increment) { + next = increment + 1; + } + } + } + snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); + path[PATH_MAX - 1] = '\0'; + return dir->openFile(dir, path, mode); +}
M src/util/vfs.hsrc/util/vfs.h

@@ -58,6 +58,7 @@ bool (*close)(struct VDir* vd);

void (*rewind)(struct VDir* vd); struct VDirEntry* (*listNext)(struct VDir* vd); struct VFile* (*openFile)(struct VDir* vd, const char* name, int mode); + struct VDir* (*openDir)(struct VDir* vd, const char* name); }; struct VFile* VFileOpen(const char* path, int flags);

@@ -69,6 +70,7 @@ struct VFile* VFileFromMemory(void* mem, size_t size);

struct VFile* VFileFromFILE(FILE* file); struct VDir* VDirOpen(const char* path); +struct VDir* VDirOpenArchive(const char* path); #ifdef USE_LIBZIP struct VDir* VDirOpenZip(const char* path, int flags);
M src/util/vfs/vfs-dirent.csrc/util/vfs/vfs-dirent.c

@@ -14,6 +14,7 @@ static bool _vdClose(struct VDir* vd);

static void _vdRewind(struct VDir* vd); static struct VDirEntry* _vdListNext(struct VDir* vd); static struct VFile* _vdOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdOpenDir(struct VDir* vd, const char* path); static const char* _vdeName(struct VDirEntry* vde); static enum VFSType _vdeType(struct VDirEntry* vde);

@@ -48,6 +49,7 @@ vd->d.close = _vdClose;

vd->d.rewind = _vdRewind; vd->d.listNext = _vdListNext; vd->d.openFile = _vdOpenFile; + vd->d.openDir = _vdOpenDir; vd->path = strdup(path); vd->de = de;

@@ -58,98 +60,6 @@

return &vd->d; } -struct VFile* VDirOptionalOpenIncrementFile(struct VDir* dir, const char* realPath, const char* prefix, const char* infix, const char* suffix, int mode) { - char path[PATH_MAX]; - path[PATH_MAX - 1] = '\0'; - char realPrefix[PATH_MAX]; - realPrefix[PATH_MAX - 1] = '\0'; - if (!dir) { - if (!realPath) { - return 0; - } - const char* separatorPoint = strrchr(realPath, '/'); - const char* dotPoint; - size_t len; - if (!separatorPoint) { - strcpy(path, "./"); - separatorPoint = realPath; - dotPoint = strrchr(realPath, '.'); - } else { - path[0] = '\0'; - dotPoint = strrchr(separatorPoint, '.'); - - if (separatorPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - len = separatorPoint - realPath; - strncat(path, realPath, len); - path[len] = '\0'; - ++separatorPoint; - } - - if (dotPoint - realPath + 1 >= PATH_MAX - 1) { - return 0; - } - - if (dotPoint >= separatorPoint) { - len = dotPoint - separatorPoint; - } else { - len = PATH_MAX - 1; - } - - strncpy(realPrefix, separatorPoint, len); - realPrefix[len] = '\0'; - - prefix = realPrefix; - dir = VDirOpen(path); - } - if (!dir) { - // This shouldn't be possible - return 0; - } - dir->rewind(dir); - struct VDirEntry* dirent; - size_t prefixLen = strlen(prefix); - size_t infixLen = strlen(infix); - unsigned next = 0; - while ((dirent = dir->listNext(dir))) { - const char* filename = dirent->name(dirent); - char* dotPoint = strrchr(filename, '.'); - size_t len = strlen(filename); - if (dotPoint) { - len = (dotPoint - filename); - } - const char* separator = strnrstr(filename, infix, len); - if (!separator) { - continue; - } - len = separator - filename; - if (len != prefixLen) { - continue; - } - if (strncmp(filename, prefix, prefixLen) == 0) { - int nlen; - separator += infixLen; - snprintf(path, PATH_MAX - 1, "%%u%s%%n", suffix); - unsigned increment; - if (sscanf(separator, path, &increment, &nlen) < 1) { - continue; - } - len = strlen(separator); - if (nlen < (ssize_t) len) { - continue; - } - if (next <= increment) { - next = increment + 1; - } - } - } - snprintf(path, PATH_MAX - 1, "%s%s%u%s", prefix, infix, next, suffix); - path[PATH_MAX - 1] = '\0'; - return dir->openFile(dir, path, mode); -} - bool _vdClose(struct VDir* vd) { struct VDirDE* vdde = (struct VDirDE*) vd; if (closedir(vdde->de) < 0) {

@@ -187,6 +97,23 @@

struct VFile* file = VFileOpen(combined, mode); free(combined); return file; +} + +struct VDir* _vdOpenDir(struct VDir* vd, const char* path) { + struct VDirDE* vdde = (struct VDirDE*) vd; + if (!path) { + return 0; + } + const char* dir = vdde->path; + char* combined = malloc(sizeof(char) * (strlen(path) + strlen(dir) + 2)); + sprintf(combined, "%s%s%s", dir, PATH_SEP, path); + + struct VDir* vd2 = VDirOpen(combined); + if (!vd2) { + vd2 = VDirOpenArchive(combined); + } + free(combined); + return vd2; } const char* _vdeName(struct VDirEntry* vde) {
M src/util/vfs/vfs-file.csrc/util/vfs/vfs-file.c

@@ -79,12 +79,12 @@ }

ssize_t _vffRead(struct VFile* vf, void* buffer, size_t size) { struct VFileFILE* vff = (struct VFileFILE*) vf; - return fread(buffer, size, 1, vff->file); + return fread(buffer, 1, size, vff->file); } ssize_t _vffWrite(struct VFile* vf, const void* buffer, size_t size) { struct VFileFILE* vff = (struct VFileFILE*) vf; - return fwrite(buffer, size, 1, vff->file); + return fwrite(buffer, 1, size, vff->file); } static void* _vffMap(struct VFile* vf, size_t size, int flags) {
M src/util/vfs/vfs-lzma.csrc/util/vfs/vfs-lzma.c

@@ -62,6 +62,7 @@ static bool _vd7zClose(struct VDir* vd);

static void _vd7zRewind(struct VDir* vd); static struct VDirEntry* _vd7zListNext(struct VDir* vd); static struct VFile* _vd7zOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vd7zOpenDir(struct VDir* vd, const char* path); static const char* _vde7zName(struct VDirEntry* vde); static enum VFSType _vde7zType(struct VDirEntry* vde);

@@ -111,6 +112,7 @@ vd->d.close = _vd7zClose;

vd->d.rewind = _vd7zRewind; vd->d.listNext = _vd7zListNext; vd->d.openFile = _vd7zOpenFile; + vd->d.openDir = _vd7zOpenDir; return &vd->d; }

@@ -299,6 +301,12 @@ vf->d.size = _vf7zSize;

vf->d.sync = _vf7zSync; return &vf->d; +} + +struct VDir* _vd7zOpenDir(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return 0; } bool _vf7zSync(struct VFile* vf, const void* memory, size_t size) {
M src/util/vfs/vfs-zip.csrc/util/vfs/vfs-zip.c

@@ -49,6 +49,7 @@ static bool _vdzClose(struct VDir* vd);

static void _vdzRewind(struct VDir* vd); static struct VDirEntry* _vdzListNext(struct VDir* vd); static struct VFile* _vdzOpenFile(struct VDir* vd, const char* path, int mode); +static struct VDir* _vdzOpenDir(struct VDir* vd, const char* path); static const char* _vdezName(struct VDirEntry* vde); static enum VFSType _vdezType(struct VDirEntry* vde);

@@ -72,6 +73,7 @@ vd->d.close = _vdzClose;

vd->d.rewind = _vdzRewind; vd->d.listNext = _vdzListNext; vd->d.openFile = _vdzOpenFile; + vd->d.openDir = _vdzOpenDir; vd->z = z; vd->dirent.d.name = _vdezName;

@@ -295,6 +297,12 @@ vfz->d.size = _vfzSize;

vfz->d.sync = _vfzSync; return &vfz->d; +} + +struct VDir* _vdzOpenDir(struct VDir* vd, const char* path) { + UNUSED(vd); + UNUSED(path); + return 0; } bool _vfzSync(struct VFile* vf, const void* memory, size_t size) {
M version.cmakeversion.cmake

@@ -6,6 +6,7 @@ set(LIB_VERSION_MINOR 4)

set(LIB_VERSION_PATCH 0) set(LIB_VERSION_ABI 0.4) set(LIB_VERSION_STRING ${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}) +set(SUMMARY "${PROJECT_NAME} Game Boy Advance Emulator") execute_process(COMMAND git describe --always --abbrev=40 --dirty OUTPUT_VARIABLE GIT_COMMIT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) execute_process(COMMAND git describe --always --dirty OUTPUT_VARIABLE GIT_COMMIT_SHORT ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)