Merge branch 'master' into feature/thread-proxy-renderer

This commit is contained in:
Jeffrey Pfau 2015-10-15 18:41:56 -07:00
commit 50402c8307
107 changed files with 3657 additions and 805 deletions

19
.travis-deps.sh Executable file
View File

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

View File

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

26
CHANGES
View File

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

View File

@ -147,32 +147,6 @@ elseif(UNIX)
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 @@ if(APPLE)
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 @@ if(BUILD_BBB OR BUILD_RASPI OR BUILD_PANDORA)
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 @@ else()
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 @@ if(BUILD_GLES2 AND NOT BUILD_RASPI)
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 @@ if(USE_MAGICK)
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 @@ else()
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 @@ if(UNIX AND NOT APPLE)
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 @@ endif()
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 @@ if(BUILD_PERF)
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 @@ endif()
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()

View File

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

View File

@ -86,7 +86,7 @@ static inline void _ARMSetMode(struct ARMCore* cpu, enum ExecutionMode execution
case MODE_THUMB:
cpu->cpsr.t = 1;
}
cpu->nextEvent = 0;
cpu->nextEvent = cpu->cycles;
}
static inline void _ARMReadCPSR(struct ARMCore* cpu) {

View File

@ -103,7 +103,7 @@ void ARMDebuggerRun(struct ARMDebugger* debugger) {
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;

View File

@ -345,12 +345,10 @@ void GBAAudioWriteSOUND1CNT_X(struct GBAAudio* audio, uint16_t value) {
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 @@ void GBAAudioWriteSOUND2CNT_HI(struct GBAAudio* audio, uint16_t value) {
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 @@ void GBAAudioWriteSOUND4CNT_HI(struct GBAAudio* audio, uint16_t value) {
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 {

View File

@ -63,7 +63,12 @@ static void _RegisterRamReset(struct GBA* gba) {
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");

View File

@ -22,10 +22,24 @@
#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 @@ static bool _lookupFloatValue(const struct GBAConfig* config, const char* key, f
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 GBAConfigInit(struct GBAConfig* config, const char* port) {
void GBAConfigDeinit(struct GBAConfig* config) {
ConfigurationDeinit(&config->configTable);
ConfigurationDeinit(&config->defaultsTable);
ConfigurationDeinit(&config->overridesTable);
free(config->port);
}
@ -151,7 +167,7 @@ void GBAConfigMakePortable(const struct GBAConfig* config) {
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 @@ void GBAConfigDirectory(char* out, size_t outLength) {
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);
}
@ -245,6 +280,22 @@ void GBAConfigSetDefaultFloatValue(struct GBAConfig* config, const char* key, fl
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) {
_lookupCharValue(config, "bios", &opts->bios);
_lookupIntValue(config, "logLevel", &opts->logLevel);

View File

@ -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);
@ -70,6 +74,11 @@ 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);

View File

@ -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,16 +32,34 @@ bool GBAContextInit(struct GBAContext* context, const char* port) {
}
return false;
}
GBAConfigInit(&context->config, port);
if (port) {
GBAConfigLoad(&context->config);
}
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);
}
context->gba->sync = 0;
return true;
}
@ -51,7 +73,25 @@ void GBAContextDeinit(struct GBAContext* context) {
}
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 @@ bool GBAContextStart(struct GBAContext* context) {
}
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 @@ void GBAContextFrame(struct GBAContext* context, uint16_t keys) {
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);
}

View File

@ -9,6 +9,7 @@
#include "util/common.h"
#include "gba/context/config.h"
#include "gba/context/sync.h"
#include "gba/input.h"
struct GBAContext {

View File

@ -128,7 +128,7 @@ static const struct GBACartridgeOverride _overrides[] = {
// 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 },

View File

@ -22,15 +22,12 @@ void GBASyncPostFrame(struct GBASync* sync) {
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 @@ void GBASyncForceFrame(struct GBASync* sync) {
MutexUnlock(&sync->videoFrameMutex);
}
bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
bool GBASyncWaitFrameStart(struct GBASync* sync) {
if (!sync) {
return true;
}
@ -60,7 +57,6 @@ bool GBASyncWaitFrameStart(struct GBASync* sync, int frameskip) {
}
}
sync->videoFramePending = 0;
sync->videoFrameSkip = frameskip;
return true;
}
@ -72,14 +68,6 @@ void GBASyncWaitFrameEnd(struct GBASync* sync) {
MutexUnlock(&sync->videoFrameMutex);
}
bool GBASyncDrawingFrame(struct GBASync* sync) {
if (!sync) {
return true;
}
return sync->videoFrameSkip <= 0;
}
void GBASyncSetVideoSync(struct GBASync* sync, bool wait) {
if (!sync) {
return;

View File

@ -13,7 +13,6 @@
struct GBASync {
int videoFramePending;
bool videoFrameWait;
int videoFrameSkip;
bool videoFrameOn;
Mutex videoFrameMutex;
Condition videoFrameAvailableCond;
@ -26,9 +25,8 @@ struct GBASync {
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);

View File

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

107
src/gba/gui/gui-config.c Normal file
View File

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

15
src/gba/gui/gui-config.h Normal file
View File

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

View File

@ -5,6 +5,7 @@
* 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"
@ -14,11 +15,18 @@
#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 @@ void GBAGUIInit(struct GBAGUIRunner* runner, const char* port) {
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 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
*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 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
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 @@ void GBAGUIRunloop(struct GBAGUIRunner* runner) {
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 @@ void GBAGUIRunloop(struct GBAGUIRunner* 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,39 +298,49 @@ void GBAGUIRunloop(struct GBAGUIRunner* 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);
}

View File

@ -7,6 +7,7 @@
#define GUI_RUNNER_H
#include "gba/context/context.h"
#include "util/circle-buffer.h"
#include "util/gui.h"
enum GBAGUIInput {
@ -35,6 +36,14 @@ struct GBAGUIRunner {
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*);
void (*gameLoaded)(struct GBAGUIRunner*);

View File

@ -7,12 +7,9 @@
#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 };
static void _readPins(struct GBACartridgeHardware* hw);
@ -281,21 +278,7 @@ void _rtcUpdateClock(struct GBACartridgeHardware* hw) {
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);

View File

@ -328,7 +328,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
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 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
break;
case REG_SOUND4CNT_LO:
GBAAudioWriteSOUND4CNT_LO(&gba->audio, value);
value &= 0xFF00;
value &= 0xFF3F;
break;
case REG_SOUND4CNT_HI:
GBAAudioWriteSOUND4CNT_HI(&gba->audio, value);

View File

@ -442,7 +442,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
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;
}

View File

@ -7,6 +7,63 @@
#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 @@ void GBAVideoSoftwareRendererDrawBackgroundMode2(struct GBAVideoSoftwareRenderer
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);
}
}
}

View File

@ -473,9 +473,9 @@ void GBAVideoSoftwareRendererDrawBackgroundMode0(struct GBAVideoSoftwareRenderer
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;

View File

@ -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 @@
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 @@
} \
}
#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 @@
}
#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); \
@ -97,6 +112,19 @@
} \
}
#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; \
} \
}
#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
@ -119,23 +147,32 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
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 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
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 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
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 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
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);
}

View File

@ -42,7 +42,7 @@ static inline void _compositeBlendObjwin(struct GBAVideoSoftwareRenderer* render
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 @@ static inline void _compositeBlendNoObjwin(struct GBAVideoSoftwareRenderer* rend
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* rend
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 @@ static inline void _compositeNoBlendNoObjwin(struct GBAVideoSoftwareRenderer* re
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); \
} \

View File

@ -523,7 +523,7 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
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 {
@ -538,6 +538,30 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
}
}
}
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);
}
}
}
}
}
#ifdef COLOR_16_BIT
#if defined(__ARM_NEON) && !defined(__APPLE__)

View File

@ -71,6 +71,7 @@ enum {
#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

View File

@ -87,52 +87,14 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
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);
if ((region == REGION_CART0 || region == REGION_CART1 || region == REGION_CART2) && ((state->cpu.gprs[ARM_PC] - WORD_SIZE_ARM) & SIZE_CART0) >= gba->memory.romSize - WORD_SIZE_ARM) {
GBALog(gba, GBA_LOG_WARN, "Savestate created using a differently sized version of the ROM");
@ -432,3 +394,25 @@ int GBARewind(struct GBAThread* thread, int nStates) {
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");
}

View File

@ -345,4 +345,6 @@ void GBARewindSettingsChanged(struct GBAThread* thread, int newCapacity, int new
int GBARewind(struct GBAThread* thread, int nStates);
void GBARewindAll(struct GBAThread* thread);
void GBATakeScreenshot(struct GBA* gba, struct VDir* dir);
#endif

View File

@ -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 @@ static THREAD_ENTRY _GBAThreadRun(void* context) {
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 @@ bool GBAThreadStart(struct GBAThread* threadContext) {
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 GBAThreadPauseFromThread(struct GBAThread* threadContext) {
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);
threadContext->gameDir = VDirOpenArchive(fname);
}
#endif
#if USE_LZMA
if (!threadContext->gameDir) {
threadContext->gameDir = VDirOpen7z(fname, 0);
}
#endif
}
static void _loadGameDir(struct GBAThread* threadContext) {
@ -754,22 +746,9 @@ struct GBAThread* GBAThreadGetContext(void) {
}
#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) {

View File

@ -59,6 +59,7 @@ static struct GBAVideoRenderer dummyRenderer = {
void GBAVideoInit(struct GBAVideo* video) {
video->renderer = &dummyRenderer;
video->vram = 0;
video->frameskip = 0;
}
void GBAVideoReset(struct GBAVideo* video) {
@ -70,7 +71,6 @@ void GBAVideoReset(struct GBAVideo* video) {
}
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 @@ void GBAVideoReset(struct GBAVideo* video) {
video->nextVcounterIRQ = 0;
video->frameCounter = 0;
video->frameskipCounter = 0;
if (video->vram) {
mappedMemoryFree(video->vram, SIZE_VRAM);
@ -89,10 +90,10 @@ void GBAVideoReset(struct GBAVideo* video) {
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 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t 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 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
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 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
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 @@ int32_t GBAVideoProcessEvents(struct GBAVideo* video, int32_t cycles) {
} 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 @@ void GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState*
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 GBAVideoSerialize(const struct GBAVideo* video, struct GBASerializedState*
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;

View File

@ -185,7 +185,6 @@ struct GBAVideo {
// VCOUNT
int vcount;
int32_t lastHblank;
int32_t nextHblank;
int32_t nextEvent;
int32_t eventDiff;
@ -199,6 +198,8 @@ struct GBAVideo {
union GBAOAM oam;
int32_t frameCounter;
int frameskip;
int frameskipCounter;
};
void GBAVideoInit(struct GBAVideo* video);

View File

@ -5,8 +5,6 @@
* 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) {

View File

@ -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 @@ struct VDir* VDirOpen(const char* 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;
@ -235,6 +237,23 @@ static struct VFile* _vd3dOpenFile(struct VDir* vd, const char* path, int mode)
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) {
struct VDirEntry3DS* vd3de = (struct VDirEntry3DS*) vde;
if (!vd3de->utf8Name[0]) {

View File

@ -8,8 +8,6 @@
#include "util/vfs.h"
#define asm __asm__
#include <3ds.h>
extern FS_archive sdmcArchive;

View File

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

View File

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

BIN
src/platform/3ds/bios.wav Normal file

Binary file not shown.

239
src/platform/3ds/cia.rsf.in Normal file
View File

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

462
src/platform/3ds/ctr-gpu.c Normal file
View File

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

View File

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

View File

@ -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 __system_allocateHeaps() {
void __attribute__((noreturn)) __libctru_exit(int rc)
{
UNUSED(rc);
u32 tmp=0;
// Unmap the linear heap

View File

@ -8,15 +8,14 @@
#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* GUIFontCreate(void) {
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 @@ unsigned GUIFontGlyphWidth(const struct GUIFont* font, uint32_t glyph) {
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);
}

BIN
src/platform/3ds/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -11,12 +11,13 @@
#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 @@ static enum ScreenMode {
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 @@ static struct GBA3DSRotationSource {
angularRate gyro;
} rotation;
static struct VFile* logFile;
static bool hasSound;
// TODO: Move into context
static struct GBAVideoSoftwareRenderer renderer;
@ -47,44 +47,125 @@ 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 @@ static void _gameLoaded(struct GBAGUIRunner* runner) {
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 _gameUnloaded(struct GBAGUIRunner* runner) {
}
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 uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
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 @@ static uint32_t _pollInput(void) {
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 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio)
}
int main() {
ptmInit();
hasSound = !csndInit();
rotation.d.sample = _sampleRotation;
@ -296,13 +435,29 @@ int main() {
}
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 @@ int main() {
};
FSUSER_OpenArchive(0, &sdmcArchive);
logFile = VFileOpen("/mgba.log", O_WRONLY | O_CREAT | O_TRUNC);
struct GUIFont* font = GUIFontCreate();
if (!font) {
@ -322,11 +476,31 @@ int main() {
.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 @@ int main() {
.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);
}

View File

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

View File

@ -71,7 +71,7 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
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 @@ bool parseArguments(struct GBAArguments* opts, struct GBAConfig* config, int arg
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 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
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 @@ bool _parseGraphicsArg(struct SubParser* parser, struct GBAConfig* config, int o
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;

View File

@ -11,6 +11,7 @@
#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 retro_set_rumble_state_t rumbleCallback;
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 void _updateLux(struct GBALuminanceSource* lux);
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 @@ void retro_init(void) {
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 @@ void retro_init(void) {
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_init(void) {
void retro_deinit(void) {
GBAContextDeinit(&context);
free(renderer.outputBuffer);
}
void retro_run(void) {
@ -233,6 +234,7 @@ void retro_run(void) {
}
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 @@ void retro_reset(void) {
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 @@ bool retro_load_game(const struct retro_game_info* game) {
}
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 @@ bool retro_load_game(const struct retro_game_info* game) {
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);
}
@ -405,14 +408,6 @@ static void _postAudioBuffer(struct GBAAVStream* stream, struct GBAAudio* audio)
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) {
UNUSED(rumble);
if (!rumbleCallback) {

View File

@ -82,8 +82,11 @@ static inline int ThreadSetName(const char* name) {
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
pthread_set_name_np(pthread_self(), name);
return 0;
#else
#elif !defined(BUILD_PANDORA) // Pandora's glibc is too old
return pthread_setname_np(pthread_self(), name);
#else
UNUSED(name);
return 0;
#endif
}

View File

@ -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)
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()
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})
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()
list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sce-vfs.c)
set(VFS_SRC ${VFS_SRC} PARENT_SCOPE)
set(PLATFORM_LIBRARY -lvita2d -lSceCtrl_stub -lSceRtc_stub -lSceGxm_stub -lSceDisplay_stub -lSceAudio_stub -lSceMotion_stub -lScePower_stub -lpng -lz -l${M_LIBRARY})
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})
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)
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})
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)

View File

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

View File

@ -4,36 +4,39 @@ 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)
set(M_LIBRARY m)

View File

@ -10,23 +10,32 @@
#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 @@ static uint32_t _pollInput(void) {
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);

View File

@ -38,6 +38,7 @@ static enum 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 @@ void GBAPSP2Setup(struct GBAGUIRunner* runner) {
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 GBAPSP2UnloadROM(struct GBAGUIRunner* runner) {
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 @@ void GBAPSP2Draw(struct GBAGUIRunner* runner, bool faded) {
}
}
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) {

View File

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

View File

@ -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 @@ struct VDir* VDirOpen(const char* path) {
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,13 +192,29 @@ struct VFile* _vdsceOpenFile(struct VDir* vd, const char* path, int mode) {
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) {
struct VDirEntrySce* vdesce = (struct VDirEntrySce*) vde;
return vdesce->ent.d_name;

View File

@ -19,15 +19,15 @@ Q_OBJECT
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;

View File

@ -25,11 +25,11 @@ public:
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;

View File

@ -75,6 +75,7 @@ set(SOURCE_FILES
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 @@ if(WIN32)
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})

View File

@ -52,7 +52,7 @@ public slots:
void showMessage(const QString& message);
protected:
void resizeEvent(QResizeEvent*);
virtual void resizeEvent(QResizeEvent*) override;
virtual void mouseMoveEvent(QMouseEvent*) override;
MessagePainter* messagePainter() { return &m_messagePainter; }

View File

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

View File

@ -86,6 +86,7 @@ GBAApp::GBAApp(int& argc, char* argv[])
w->show();
w->controller()->setMultiplayerController(&m_multiplayer);
w->multiplayerChanged();
}
bool GBAApp::event(QEvent* event) {
@ -110,6 +111,7 @@ Window* GBAApp::newWindow() {
w->loadConfig();
w->show();
w->controller()->setMultiplayerController(&m_multiplayer);
w->multiplayerChanged();
return w;
}

View File

@ -124,12 +124,8 @@ GameController::GameController(QObject* parent)
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::setAudioSync(bool set) {
}
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) {

View File

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

View File

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

414
src/platform/qt/IOViewer.ui Normal file
View File

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

View File

@ -35,6 +35,7 @@ bool MultiplayerController::attachGame(GameController* controller) {
}
thread->sioDrivers.multiplayer = &node->d;
controller->threadContinue();
emit gameAttached();
return true;
}
@ -60,6 +61,21 @@ void MultiplayerController::detachGame(GameController* controller) {
}
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() {

View File

@ -6,6 +6,8 @@
#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 @@ public:
void detachGame(GameController*);
int attached();
int playerId(GameController*);
signals:
void gameAttached();
void gameDetached();
private:
GBASIOLockstep m_lockstep;

View File

@ -30,7 +30,7 @@ public:
protected:
bool eventFilter(QObject*, QEvent* event) override;
bool event(QEvent* event);
bool event(QEvent* event) override;
private slots:
void updateSensors();

View File

@ -89,6 +89,11 @@ SettingsView::SettingsView(ConfigController* controller, QWidget* parent)
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() {

View File

@ -549,7 +549,7 @@
<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>

View File

@ -311,9 +311,9 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
}
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()) {

View File

@ -22,10 +22,14 @@ ShortcutView::ShortcutView(QWidget* parent)
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)));

View File

@ -24,6 +24,7 @@
#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 @@ void Window::replaceROM() {
}
}
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 @@ void Window::openMemoryWindow() {
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 @@ void Window::gameStarted(GBAThread* context) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
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 @@ void Window::setupMenu(QMenuBar* menubar) {
m_controller->rewind(1);
});
m_gameActions.append(frameRewind);
m_nonMpActions.append(frameRewind);
addControlledAction(emulationMenu, frameRewind, "frameRewind");
ConfigOption* videoSync = m_config->addOption("videoSync");
@ -1131,6 +1170,11 @@ void Window::setupMenu(QMenuBar* menubar) {
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_controller->setSkipBIOS(value.toBool());

View File

@ -69,6 +69,8 @@ public slots:
void replaceROM();
void multiplayerChanged();
void importSharkport();
void exportSharkport();
@ -82,6 +84,7 @@ public slots:
void openPaletteWindow();
void openMemoryWindow();
void openIOViewer();
void openAboutScreen();
@ -149,6 +152,7 @@ private:
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;

View File

@ -37,6 +37,10 @@ elseif(APPLE)
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 @@ if(BUILD_RASPI)
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 @@ else()
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)

View File

@ -61,7 +61,7 @@ void GBASDLGLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* rend
#endif
}
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
if (GBASyncWaitFrameStart(&context->sync)) {
v->postFrame(v, renderer->d.outputBuffer);
}
v->drawFrame(v);

View File

@ -114,7 +114,7 @@ void GBASDLGLES2Runloop(struct GBAThread* context, struct SDLSoftwareRenderer* r
GBASDLHandleEvent(context, &renderer->player, &event);
}
if (GBASyncWaitFrameStart(&context->sync, context->frameskip)) {
if (GBASyncWaitFrameStart(&context->sync)) {
v->postFrame(v, renderer->d.outputBuffer);
}
v->drawFrame(v);

View File

@ -12,6 +12,26 @@
#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,15 +91,15 @@ void GBASDLRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* render
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];
}

View File

@ -41,19 +41,24 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
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 @@ void GBASDLResumeAudio(struct GBASDLAudio* context) {
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 @@ static void _GBASDLAudioCallback(void* context, Uint8* data, int len) {
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);
}
if (audioContext->thread) {
GBASyncConsumeAudio(&audioContext->thread->sync);
}
GBASyncConsumeAudio(&audioContext->thread->sync);
if (available < len) {
memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short));
}

View File

@ -31,6 +31,7 @@ struct GBASDLAudio {
#endif
struct GBAThread* thread;
struct GBA* gba;
};
bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext);

View File

@ -93,7 +93,7 @@ void GBASDLSWRunloop(struct GBAThread* context, struct SDLSoftwareRenderer* rend
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);

View File

@ -68,25 +68,29 @@ int main(int argc, char** argv) {
return !parsed;
}
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;
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 VFile* savestate = 0;
struct VFile* savestateOverlay = 0;
size_t overlayOffset;
GBAContextStart(&context);
if (fuzzOpts.savestate) {
@ -121,18 +125,21 @@ int main(int argc, char** argv) {
_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;
}

View File

@ -167,7 +167,7 @@ static void _GBAPerfRunloop(struct GBAThread* context, int* frames, bool quiet)
*frames = 0;
int lastFrames = 0;
while (context->state < THREAD_EXITING) {
if (GBASyncWaitFrameStart(&context->sync, 0)) {
if (GBASyncWaitFrameStart(&context->sync)) {
++*frames;
++lastFrames;
if (!quiet) {

View File

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

View File

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

View File

@ -7,6 +7,7 @@
#include <fat.h>
#include <gccore.h>
#include <ogc/machine/processor.h>
#include <malloc.h>
#include <wiiuse/wpad.h>
@ -22,7 +23,7 @@
#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 int32_t _readGyroZ(struct GBARotationSource* source);
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 @@ int main() {
#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,12 +158,12 @@ int main() {
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;
rotation.sample = _sampleRotation;
@ -159,7 +175,9 @@ int main() {
.params = {
352, 230,
font, "/",
_drawStart, _drawEnd, _pollInput,
_drawStart, _drawEnd,
_pollInput, _pollCursor,
0,
_guiPrepare, _guiFinish,
GUI_PARAMS_TRAIL
@ -174,28 +192,19 @@ int main() {
.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 @@ static void _audioDMA(void) {
}
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 @@ static void _drawEnd(void) {
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 @@ static uint32_t _pollInput(void) {
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 @@ static uint32_t _pollInput(void) {
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 _guiPrepare(void) {
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 @@ void _gameUnloaded(struct GBAGUIRunner* runner) {
}
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 @@ void _gameLoaded(struct GBAGUIRunner* runner) {
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 @@ uint16_t _pollGameInput(struct GBAGUIRunner* runner) {
}
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);
}

View File

@ -52,13 +52,13 @@ typedef intptr_t ssize_t;
#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)); \
}

View File

@ -6,6 +6,7 @@
#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 @@ float strtof_u(const char* restrict str, char** restrict end) {
#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

View File

@ -27,4 +27,8 @@ float strtof_l(const char* restrict str, char** restrict end, locale_t locale);
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

View File

@ -30,9 +30,3 @@ void GUIPollInput(struct GUIParams* params, uint32_t* newInputOut, uint32_t* hel
*heldInput = input;
}
}
void GUIInvalidateKeys(struct GUIParams* params) {
for (int i = 0; i < GUI_INPUT_MAX; ++i) {
params->inputHistory[i] = 0;
}
}

View File

@ -28,6 +28,24 @@ enum GUIInput {
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 @@ struct GUIParams {
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

View File

@ -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 @@ static void _upDirectory(char* currentPath) {
}
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 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
}
*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 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
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 @@ static bool _refreshDirectory(struct GUIParams* params, const char* currentPath,
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 @@ bool GUISelectFile(struct GUIParams* params, char* outPath, size_t outLen, bool
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);

View File

@ -10,19 +10,23 @@
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,33 +35,54 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
++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;
}
@ -65,6 +90,9 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
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 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
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 @@ enum GUIMenuExitReason GUIShowMenu(struct GUIParams* params, struct GUIMenu* men
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);
}

View File

@ -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 @@ DECLARE_VECTOR(GUIMenuItemList, struct GUIMenuItem);
struct GUIBackground;
struct GUIMenu {
const char* title;
const char* subtitle;
struct GUIMenuItemList items;
size_t index;
struct GUIBackground* background;
@ -32,6 +35,9 @@ enum GUIMenuExitReason {
};
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

View File

@ -43,6 +43,9 @@ png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height) {
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 @@ bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned s
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 @@ bool PNGReadPixels(png_structp png, png_infop info, void* pixels, unsigned width
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);

Some files were not shown because too many files have changed in this diff Show More