mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into feature/thread-proxy-renderer
This commit is contained in:
commit
50402c8307
|
@ -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
|
15
.travis.yml
15
.travis.yml
|
@ -1,13 +1,18 @@
|
|||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
env:
|
||||
- CMAKE_PREFIX_PATH=/usr/local/opt/qt5
|
||||
|
||||
language: c
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
|
||||
sudo: required
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:smspillaz/cmake-2.8.12 -y
|
||||
- sudo apt-add-repository ppa:zoogie/sdl2-snapshots -y
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get purge cmake -qq
|
||||
- sudo apt-get install -y -qq cmake libedit-dev libmagickwand-dev libpng-dev libsdl2-dev libzip-dev
|
||||
- ./.travis-deps.sh
|
||||
|
||||
script: mkdir build && cd build && cmake .. && make
|
||||
|
|
26
CHANGES
26
CHANGES
|
@ -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:
|
||||
|
|
114
CMakeLists.txt
114
CMakeLists.txt
|
@ -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()
|
||||
|
||||
|
|
|
@ -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; \
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "util/common.h"
|
||||
|
||||
#include "gba/context/config.h"
|
||||
#include "gba/context/sync.h"
|
||||
#include "gba/input.h"
|
||||
|
||||
struct GBAContext {
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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,38 +298,48 @@ 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);
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
} else {
|
||||
--mosaicWait;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
|
||||
DRAW_BACKGROUND_MODE_2(NoBlend, NO_OBJWIN);
|
||||
} else {
|
||||
DRAW_BACKGROUND_MODE_2(Blend, NO_OBJWIN);
|
||||
}
|
||||
} else {
|
||||
if (!(flags & FLAG_TARGET_2) && renderer->blendEffect != BLEND_ALPHA) {
|
||||
DRAW_BACKGROUND_MODE_2(NoBlend, OBJWIN);
|
||||
} else {
|
||||
DRAW_BACKGROUND_MODE_2(Blend, OBJWIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)) {
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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); \
|
||||
} \
|
||||
|
|
|
@ -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__)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
--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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
#include "util/vfs.h"
|
||||
|
||||
#define asm __asm__
|
||||
|
||||
#include <3ds.h>
|
||||
|
||||
extern FS_archive sdmcArchive;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Binary file not shown.
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -41,8 +41,11 @@ 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;
|
||||
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) {
|
||||
|
@ -54,6 +57,8 @@ bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContex
|
|||
#else
|
||||
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);
|
||||
double fauxClock = 1;
|
||||
if (audioContext->thread) {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (available < len) {
|
||||
memset(((short*) data) + audioContext->obtainedSpec.channels * available, 0, (len - available) * audioContext->obtainedSpec.channels * sizeof(short));
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ struct GBASDLAudio {
|
|||
#endif
|
||||
|
||||
struct GBAThread* thread;
|
||||
struct GBA* gba;
|
||||
};
|
||||
|
||||
bool GBASDLInitAudio(struct GBASDLAudio* context, struct GBAThread* threadContext);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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)); \
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
*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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
if (!filter || filter(vf)) {
|
||||
*GUIMenuItemListAppend(currentFiles) = (struct GUIMenuItem) { .title = strdup(name) };
|
||||
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;
|
||||
} else {
|
||||
_cleanFiles(&menu.items);
|
||||
GUIMenuItemListDeinit(&menu.items);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue