Merge branch 'master' (early part) into medusa
|
@ -6,8 +6,8 @@ services:
|
|||
os: linux
|
||||
env:
|
||||
- DOCKER_TAG=ubuntu:xenial
|
||||
- DOCKER_TAG=ubuntu:artful
|
||||
- DOCKER_TAG=ubuntu:bionic
|
||||
- DOCKER_TAG=ubuntu:cosmic
|
||||
- DOCKER_TAG=3ds
|
||||
- DOCKER_TAG=wii
|
||||
- DOCKER_TAG=vita
|
||||
|
|
33
CHANGES
|
@ -18,6 +18,10 @@ Misc:
|
|||
- DS Core: Add symbol loading
|
||||
- DS Video: Simplify VRAM mapping
|
||||
|
||||
0.8.0: (Future)
|
||||
Bugfixes:
|
||||
- GBA: All IRQs have 7 cycle delay (fixes mgba.io/i/539, mgba.io/i/1208)
|
||||
|
||||
0.7.0: (Future)
|
||||
Features:
|
||||
- ELF support
|
||||
|
@ -121,15 +125,40 @@ Misc:
|
|||
Changes from beta 1:
|
||||
Features:
|
||||
- Libretro: Add Game Boy cheat support
|
||||
- Qt: Separate fast forward volume control (fixes mgba.io/i/846, mgba.io/i/1143)
|
||||
- Switch: Rumble support
|
||||
- Switch: Rotation support
|
||||
- Qt: State file load/save menu options
|
||||
- Windows installer
|
||||
- Tile viewer now has adjustable width
|
||||
Bugfixes:
|
||||
- PSP2: Fix audio crackling after fast forward
|
||||
- PSP2: Fix audio crackling when buffer is full
|
||||
- 3DS: Fix unused screens not clearing (fixes mgba.io/i/1184)
|
||||
- GBA Video: Fix caching with background toggling (fixes mgba.io/i/1118)
|
||||
- Wii: Fix drawing caching regression (fixes mgba.io/i/1185)
|
||||
- Switch: Fix incorrect mapping for fast forward cap
|
||||
- GB, GBA: Fix broken opposing button filter (fixes mgba.io/i/1191)
|
||||
- Qt: Fix jumbled background when paused
|
||||
- Qt: Fix FPS counter on Windows
|
||||
- GB, GBA Savedata: Fix leaks when loading masked save (fixes mgba.io/i/1197)
|
||||
- Qt: Fix focus issues with load/save state overlay
|
||||
- GB Video: Fix SGB border hole size
|
||||
Misc:
|
||||
- mGUI: Add SGB border configuration option
|
||||
- mGUI: Add support for different settings types
|
||||
- Wii: Define _GNU_SOURCE (fixes mgba.io/i/1106)
|
||||
- Wii: Expose stretch configuration in settings
|
||||
- Wii: Stretch now sets pixel-accurate mode size cap
|
||||
- Qt: Ensure camera image is valid
|
||||
- GB: Improved SGB2 support
|
||||
- Libretro: Reduce rumble callbacks
|
||||
- Debugger: Minor text fixes
|
||||
- Qt: Debugger console history
|
||||
- Qt: Detect presence of GL_ARB_framebuffer_object
|
||||
|
||||
0.6 beta 1: (2018-09-24)
|
||||
- Initial beta for 0.6
|
||||
0.7 beta 1: (2018-09-24)
|
||||
- Initial beta for 0.7
|
||||
|
||||
0.6.3: (2018-04-14)
|
||||
Bugfixes:
|
||||
|
|
102
CMakeLists.txt
|
@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 3.1)
|
|||
if(POLICY CMP0025)
|
||||
cmake_policy(SET CMP0025 NEW)
|
||||
endif()
|
||||
if(POLICY CMP0072)
|
||||
cmake_policy(SET CMP0072 NEW)
|
||||
endif()
|
||||
project(medusa)
|
||||
set(BINARY_NAME medusa-emu CACHE INTERNAL "Name of output binaries")
|
||||
if(NOT MSVC)
|
||||
|
@ -326,7 +329,7 @@ if(WII)
|
|||
add_definitions(-U__STRICT_ANSI__)
|
||||
endif()
|
||||
|
||||
if(DEFINED 3DS)
|
||||
if(3DS OR WII)
|
||||
add_definitions(-D_GNU_SOURCE)
|
||||
endif()
|
||||
|
||||
|
@ -550,7 +553,11 @@ if(USE_FFMPEG)
|
|||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}")
|
||||
if(USE_LIBSWRESAMPLE)
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION})
|
||||
math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1")
|
||||
if(${libswresample_VERSION} EQUAL "3.1.100")
|
||||
set(LIBSWRESAMPLE_VERSION_DEBIAN 3)
|
||||
else()
|
||||
math(EXPR LIBSWRESAMPLE_VERSION_DEBIAN "${LIBSWRESAMPLE_VERSION_MAJOR} - 1")
|
||||
endif()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_DEBIAN}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_DEBIAN}")
|
||||
else()
|
||||
string(REGEX MATCH "^[0-9]+" LIBAVRESAMPLE_VERSION_MAJOR ${libavresample_VERSION})
|
||||
|
@ -577,6 +584,8 @@ if(USE_MAGICK)
|
|||
string(REGEX MATCH "^[0-9]+" MAGICKWAND_VERSION_MAJOR ${MagickWand_VERSION})
|
||||
if(${MAGICKWAND_VERSION_PARTIAL} EQUAL "6.7")
|
||||
set(MAGICKWAND_DEB_VERSION "5")
|
||||
elseif(${MagickWand_VERSION} EQUAL "6.9.10")
|
||||
set(MAGICKWAND_DEB_VERSION "-6.q16-6")
|
||||
elseif(${MagickWand_VERSION} EQUAL "6.9.7")
|
||||
set(MAGICKWAND_DEB_VERSION "-6.q16-3")
|
||||
else()
|
||||
|
@ -944,7 +953,11 @@ if(BUILD_LIBRETRO)
|
|||
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;${OS_DEFINES};${FUNCTION_DEFINES};MINIMAL_CORE=2")
|
||||
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
||||
install(TARGETS ${BINARY_NAME}_libretro LIBRARY DESTINATION ${LIBRETRO_LIBDIR} COMPONENT ${BINARY_NAME}_libretro NAMELINK_SKIP)
|
||||
if(MSVC)
|
||||
install(TARGETS ${BINARY_NAME}_libretro RUNTIME DESTINATION ${LIBRETRO_LIBDIR} COMPONENT ${BINARY_NAME}_libretro)
|
||||
else()
|
||||
install(TARGETS ${BINARY_NAME}_libretro LIBRARY DESTINATION ${LIBRETRO_LIBDIR} COMPONENT ${BINARY_NAME}_libretro NAMELINK_SKIP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_OPENEMU)
|
||||
|
@ -1030,6 +1043,26 @@ endif()
|
|||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/flags.h.in ${CMAKE_CURRENT_BINARY_DIR}/flags.h)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/flags.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mgba COMPONENT lib${BINARY_NAME})
|
||||
|
||||
if(WIN32)
|
||||
set(BIN_DIR ".\\")
|
||||
string(REGEX REPLACE "[^-A-Za-z0-9_.]" "-" CLEAN_VERSION_STRING "${VERSION_STRING}")
|
||||
file(RELATIVE_PATH SETUP_DIR_SLASH "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/setup")
|
||||
file(RELATIVE_PATH RES_DIR_SLASH "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/res")
|
||||
string(REPLACE "/" "\\" SETUP_DIR "${SETUP_DIR_SLASH}")
|
||||
string(REPLACE "/" "\\" RES_DIR "${RES_DIR_SLASH}")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES ".*64$")
|
||||
set(WIN_BITS 64)
|
||||
else()
|
||||
set(WIN_BITS 32)
|
||||
endif()
|
||||
if(GIT_TAG)
|
||||
set(IS_RELEASE 1)
|
||||
else()
|
||||
set(IS_RELEASE 0)
|
||||
endif()
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/platform/windows/setup/setup.iss.in" ${CMAKE_CURRENT_BINARY_DIR}/setup.iss)
|
||||
endif()
|
||||
|
||||
# Packaging
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/res/licenses DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME})
|
||||
if(EXTRA_LICENSES)
|
||||
|
@ -1038,20 +1071,45 @@ endif()
|
|||
|
||||
file(GLOB READMES ${CMAKE_CURRENT_SOURCE_DIR}/README*.md)
|
||||
|
||||
find_program(DOS2UNIX NAMES dos2unix)
|
||||
find_program(UNIX2DOS NAMES unix2dos)
|
||||
find_program(MARKDOWN NAMES markdown kramdown pandoc)
|
||||
|
||||
if(UNIX OR NOT DOS2UNIX)
|
||||
if(UNIX OR NOT UNIX2DOS)
|
||||
if(UNIX OR NOT MARKDOWN)
|
||||
install(FILES ${READMES} DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME})
|
||||
endif()
|
||||
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES" "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME})
|
||||
else()
|
||||
add_custom_command(OUTPUT CHANGES.txt COMMAND ${DOS2UNIX} -n "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES" "${CMAKE_CURRENT_BINARY_DIR}/CHANGES.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES")
|
||||
add_custom_command(OUTPUT LICENSE.txt COMMAND ${DOS2UNIX} -n "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
add_custom_command(OUTPUT CHANGES.txt COMMAND ${UNIX2DOS} -n "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES" "${CMAKE_CURRENT_BINARY_DIR}/CHANGES.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/CHANGES")
|
||||
add_custom_command(OUTPUT LICENSE.txt COMMAND ${UNIX2DOS} -n "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
add_custom_target(CHANGES ALL DEPENDS CHANGES.txt)
|
||||
add_custom_target(LICENSE ALL DEPENDS LICENSE.txt)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/CHANGES.txt ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt DESTINATION ${CMAKE_INSTALL_DOCDIR} COMPONENT ${BINARY_NAME})
|
||||
if(DISTBUILD AND WIN32)
|
||||
if(INSTALLER_NAME)
|
||||
set(INSTALLER_TARGET "${INSTALLER_NAME}.exe")
|
||||
set(ISCC_FLAGS "/F${INSTALLER_NAME}")
|
||||
else()
|
||||
set(INSTALLER_TARGET "${PROJECT_NAME}-setup-${CLEAN_VERSION_STRING}-win${WIN_BITS}.exe")
|
||||
endif()
|
||||
if(CMAKE_CROSSCOMPILING)
|
||||
find_program(WINE NAMES wine wine-stable wine-development)
|
||||
find_file(ISCC ISCC.exe HINTS "$ENV{HOME}/.wine/drive_c/Program Files/" PATH_SUFFIXES "Inno Setup 5")
|
||||
message(STATUS "${WINE}" "${ISCC}" setup.iss /Q ${ISCC_FLAGS})
|
||||
add_custom_command(OUTPUT ${INSTALLER_TARGET}
|
||||
COMMAND "${WINE}" "${ISCC}" setup.iss /Q ${ISCC_FLAGS}
|
||||
DEPENDS ${BINARY_NAME}-qt ${BINARY_NAME}-sdl CHANGES LICENSE)
|
||||
else()
|
||||
find_program(ISCC NAMES ISCC ISCC.exe PATH_SUFFIXES "Inno Setup 5")
|
||||
add_custom_command(OUTPUT ${INSTALLER_TARGET}
|
||||
COMMAND "${ISCC}" setup.iss /Q ${ISCC_FLAGS}
|
||||
DEPENDS ${BINARY_NAME}-qt ${BINARY_NAME}-sdl CHANGES LICENSE)
|
||||
endif()
|
||||
if(ISCC)
|
||||
add_custom_target(installer ALL DEPENDS ${INSTALLER_TARGET})
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${INSTALLER_TARGET}" DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT installer)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MARKDOWN)
|
||||
|
@ -1076,9 +1134,11 @@ set(CPACK_PACKAGE_CONTACT "Jeffrey Pfau <jeffrey@endrift.com>")
|
|||
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
set(CPACK_DEBIAN_PACKAGE_SECTION "games")
|
||||
|
||||
SET(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
set(CPACK_DEB_COMPONENT_INSTALL ON)
|
||||
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
endif()
|
||||
|
||||
if(DISTBUILD)
|
||||
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
|
||||
|
@ -1089,19 +1149,19 @@ if(DISTBUILD)
|
|||
install(FILES "$<TARGET_FILE:${BINARY_NAME}>.dSYM" DESTINATION ${LIBDIR} COMPONENT lib${BINARY_NAME}-dbg)
|
||||
endif()
|
||||
endif()
|
||||
if(WIN32 OR APPLE)
|
||||
if(APPLE)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-qt ${BINARY_NAME}-sdl ${BINARY_NAME}-qt-dbg ${BINARY_NAME}-sdl-dbg ${BINARY_NAME}-perf)
|
||||
if(APPLE)
|
||||
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
|
||||
endif()
|
||||
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)
|
||||
elseif(WIN32)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-qt ${BINARY_NAME}-sdl ${BINARY_NAME}-qt-dbg ${BINARY_NAME}-sdl-dbg ${BINARY_NAME}-perf installer)
|
||||
elseif(3DS)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-3ds ${BINARY_NAME}-perf)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-3ds ${BINARY_NAME}-perf)
|
||||
elseif(WII)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-wii)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-wii)
|
||||
elseif(PSP2)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-psp2)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-psp2)
|
||||
elseif(SWITCH)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-switch)
|
||||
set(CPACK_COMPONENTS_ALL ${BINARY_NAME} ${BINARY_NAME}-dbg ${BINARY_NAME}-switch)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -1140,17 +1200,23 @@ if(SDL_FOUND)
|
|||
cpack_add_component(${BINARY_NAME}-sdl GROUP sdl)
|
||||
endif()
|
||||
|
||||
if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
if(DISTBUILD)
|
||||
cpack_add_component_group(debug PARENT_GROUP dev)
|
||||
if(BUILD_SHARED AND NOT IS_EMBEDDED)
|
||||
cpack_add_component(lib${BINARY_NAME}-dbg GROUP debug)
|
||||
endif()
|
||||
if(IS_EMBEDDED)
|
||||
cpack_add_component(${BINARY_NAME}-dbg GROUP debug)
|
||||
endif()
|
||||
if(BUILD_QT)
|
||||
cpack_add_component(${BINARY_NAME}-qt-dbg GROUP debug)
|
||||
endif()
|
||||
if(SDL_FOUND)
|
||||
cpack_add_component(${BINARY_NAME}-sdl-dbg GROUP debug)
|
||||
endif()
|
||||
if(WIN32)
|
||||
cpack_add_component_group(installer PARENT_GROUP base)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
cpack_add_component_group(test PARENT_GROUP dev)
|
||||
|
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
|
@ -178,8 +178,12 @@ typedef intptr_t ssize_t;
|
|||
#define STORE_64LE(SRC, ADDR, ARR) *(uint64_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC
|
||||
#define STORE_32LE(SRC, ADDR, ARR) *(uint32_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC
|
||||
#define STORE_16LE(SRC, ADDR, ARR) *(uint16_t*) ((uintptr_t) (ARR) + (size_t) (ADDR)) = SRC
|
||||
#ifdef _MSC_VER
|
||||
#define LOAD_32BE(DEST, ADDR, ARR) DEST = _byteswap_ulong(((uint32_t*) ARR)[(ADDR) >> 2])
|
||||
#else
|
||||
#define LOAD_32BE(DEST, ADDR, ARR) DEST = __builtin_bswap32(((uint32_t*) ARR)[(ADDR) >> 2])
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define MAKE_MASK(START, END) (((1 << ((END) - (START))) - 1) << (START))
|
||||
#define CHECK_BITS(SRC, START, END) ((SRC) & MAKE_MASK(START, END))
|
||||
|
|
|
@ -12,12 +12,37 @@ CXX_GUARD_START
|
|||
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
#define GUI_V_V (struct GUIVariant) { .type = GUI_VARIANT_VOID }
|
||||
#define GUI_V_U(U) (struct GUIVariant) { .type = GUI_VARIANT_UNSIGNED, .v.u = (U) }
|
||||
#define GUI_V_I(I) (struct GUIVariant) { .type = GUI_VARIANT_INT, .v.i = (I) }
|
||||
#define GUI_V_F(F) (struct GUIVariant) { .type = GUI_VARIANT_FLOAT, .v.f = (F) }
|
||||
#define GUI_V_S(S) (struct GUIVariant) { .type = GUI_VARIANT_STRING, .v.s = (S) }
|
||||
|
||||
enum GUIVariantType {
|
||||
GUI_VARIANT_VOID = 0,
|
||||
GUI_VARIANT_UNSIGNED,
|
||||
GUI_VARIANT_INT,
|
||||
GUI_VARIANT_FLOAT,
|
||||
GUI_VARIANT_STRING
|
||||
};
|
||||
|
||||
struct GUIVariant {
|
||||
enum GUIVariantType type;
|
||||
union {
|
||||
unsigned u;
|
||||
int i;
|
||||
float f;
|
||||
const char* s;
|
||||
} v;
|
||||
};
|
||||
|
||||
struct GUIMenu;
|
||||
struct GUIMenuItem {
|
||||
const char* title;
|
||||
void* data;
|
||||
unsigned state;
|
||||
const char* const* validStates;
|
||||
const struct GUIVariant* stateMappings;
|
||||
unsigned nStates;
|
||||
struct GUIMenu* submenu;
|
||||
};
|
||||
|
|
|
@ -117,6 +117,8 @@ struct GB {
|
|||
bool earlyExit;
|
||||
struct mTimingEvent eiPending;
|
||||
unsigned doubleSpeed;
|
||||
|
||||
bool allowOpposingDirections;
|
||||
};
|
||||
|
||||
struct GBCartridge {
|
||||
|
|
|
@ -82,6 +82,8 @@ struct GBA {
|
|||
struct GBATimer timers[4];
|
||||
|
||||
int springIRQ;
|
||||
struct mTimingEvent irqEvent;
|
||||
|
||||
uint32_t biosChecksum;
|
||||
int* keySource;
|
||||
struct mRotationSource* rotationSource;
|
||||
|
@ -141,8 +143,6 @@ void GBADestroy(struct GBA* gba);
|
|||
void GBAReset(struct ARMCore* cpu);
|
||||
void GBASkipBIOS(struct GBA* gba);
|
||||
|
||||
void GBAWriteIE(struct GBA* gba, uint16_t value);
|
||||
void GBAWriteIME(struct GBA* gba, uint16_t value);
|
||||
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq);
|
||||
void GBATestIRQ(struct ARMCore* cpu);
|
||||
void GBAHalt(struct GBA* gba);
|
||||
|
|
|
@ -98,28 +98,28 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | 0x00202 - 0x00203: Old reload value
|
||||
* | 0x00204 - 0x00207: Last event
|
||||
* | 0x00208 - 0x0020B: Next event
|
||||
* | 0x0020C - 0x0020F: Next IRQ
|
||||
* | 0x0020C - 0x0020F: Reserved
|
||||
* | 0x00210 - 0x00213: Miscellaneous flags
|
||||
* 0x00214 - 0x00227: Timer 1
|
||||
* | 0x00214 - 0x00215: Reload value
|
||||
* | 0x00216 - 0x00217: Old reload value
|
||||
* | 0x00218 - 0x0021B: Last event
|
||||
* | 0x0021C - 0x0021F: Next event
|
||||
* | 0x00220 - 0x00223: Next IRQ
|
||||
* | 0x00220 - 0x00223: Reserved
|
||||
* | 0x00224 - 0x00227: Miscellaneous flags
|
||||
* 0x00228 - 0x0023B: Timer 2
|
||||
* | 0x00228 - 0x00229: Reload value
|
||||
* | 0x0022A - 0x0022B: Old reload value
|
||||
* | 0x0022C - 0x0022F: Last event
|
||||
* | 0x00230 - 0x00233: Next event
|
||||
* | 0x00234 - 0x00237: Next IRQ
|
||||
* | 0x00234 - 0x00237: Reserved
|
||||
* | 0x00238 - 0x0023B: Miscellaneous flags
|
||||
* 0x0023C - 0x00250: Timer 3
|
||||
* | 0x0023C - 0x0023D: Reload value
|
||||
* | 0x0023E - 0x0023F: Old reload value
|
||||
* | 0x00240 - 0x00243: Last event
|
||||
* | 0x00244 - 0x00247: Next event
|
||||
* | 0x00248 - 0x0024B: Next IRQ
|
||||
* | 0x00248 - 0x0024B: Reserved
|
||||
* | 0x0024C - 0x0024F: Miscellaneous flags
|
||||
* 0x00250 - 0x0025F: DMA 0
|
||||
* | 0x00250 - 0x00253: DMA next source
|
||||
|
@ -196,7 +196,9 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* 0x0031C - 0x0031F: Miscellaneous flags
|
||||
* | bit 0: Is CPU halted?
|
||||
* | bit 1: POSTFLG
|
||||
* 0x00320 - 0x003FF: Reserved (leave zero)
|
||||
* | bit 2: Is IRQ pending?
|
||||
* 0x00320 - 0x00323: Next IRQ event
|
||||
* 0x00324 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00400 - 0x007FF: I/O memory
|
||||
* 0x00800 - 0x00BFF: Palette
|
||||
* 0x00C00 - 0x00FFF: OAM
|
||||
|
@ -227,6 +229,7 @@ DECL_BIT(GBASerializedSavedataFlags, DustSettling, 5);
|
|||
DECL_BITFIELD(GBASerializedMiscFlags, uint32_t);
|
||||
DECL_BIT(GBASerializedMiscFlags, Halted, 0);
|
||||
DECL_BIT(GBASerializedMiscFlags, POSTFLG, 1);
|
||||
DECL_BIT(GBASerializedMiscFlags, IrqPending, 2);
|
||||
|
||||
struct GBASerializedState {
|
||||
uint32_t versionMagic;
|
||||
|
@ -267,10 +270,10 @@ struct GBASerializedState {
|
|||
|
||||
struct {
|
||||
uint16_t reload;
|
||||
uint16_t reserved;
|
||||
uint16_t reserved0;
|
||||
uint32_t lastEvent;
|
||||
uint32_t nextEvent;
|
||||
uint32_t nextIrq;
|
||||
uint32_t reserved1;
|
||||
GBATimerFlags flags;
|
||||
} timers[4];
|
||||
|
||||
|
@ -320,8 +323,9 @@ struct GBASerializedState {
|
|||
|
||||
uint32_t lastPrefetchedPc;
|
||||
GBASerializedMiscFlags miscFlags;
|
||||
uint32_t nextIrq;
|
||||
|
||||
uint32_t reserved[56];
|
||||
uint32_t reserved[55];
|
||||
|
||||
uint16_t io[SIZE_IO >> 1];
|
||||
uint16_t pram[SIZE_PALETTE_RAM >> 1];
|
||||
|
|
|
@ -17,13 +17,11 @@ DECL_BITS(GBATimerFlags, PrescaleBits, 0, 4);
|
|||
DECL_BIT(GBATimerFlags, CountUp, 4);
|
||||
DECL_BIT(GBATimerFlags, DoIrq, 5);
|
||||
DECL_BIT(GBATimerFlags, Enable, 6);
|
||||
DECL_BIT(GBATimerFlags, IrqPending, 7);
|
||||
|
||||
struct GBATimer {
|
||||
uint16_t reload;
|
||||
int32_t lastEvent;
|
||||
struct mTimingEvent event;
|
||||
struct mTimingEvent irq;
|
||||
GBATimerFlags flags;
|
||||
int forcedPrescale;
|
||||
};
|
||||
|
@ -33,7 +31,7 @@ struct GBA;
|
|||
void GBATimerInit(struct GBA* gba);
|
||||
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate);
|
||||
void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate);
|
||||
bool GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate);
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate);
|
||||
void GBATimerUpdateRegisterInternal(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, int32_t skew);
|
||||
void GBATimerWriteTMCNT_LO(struct GBATimer* timer, uint16_t reload);
|
||||
|
|
|
@ -175,6 +175,7 @@ void ARMRaiseIRQ(struct ARMCore* cpu) {
|
|||
cpu->spsr = cpsr;
|
||||
cpu->cpsr.i = 1;
|
||||
cpu->cycles += currentCycles;
|
||||
cpu->halted = 0;
|
||||
}
|
||||
|
||||
void ARMRaiseSWI(struct ARMCore* cpu) {
|
||||
|
|
|
@ -66,8 +66,8 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
|
|||
{ "break", _setBreakpoint, "Is", "Set a breakpoint" },
|
||||
{ "c", _continue, "", "Continue execution" },
|
||||
{ "continue", _continue, "", "Continue execution" },
|
||||
{ "d", _clearBreakpoint, "I", "Delete a breakpoint" },
|
||||
{ "delete", _clearBreakpoint, "I", "Delete a breakpoint" },
|
||||
{ "d", _clearBreakpoint, "I", "Delete a breakpoint or watchpoint" },
|
||||
{ "delete", _clearBreakpoint, "I", "Delete a breakpoint or watchpoint" },
|
||||
{ "dis", _disassemble, "Ii", "Disassemble instructions" },
|
||||
{ "disasm", _disassemble, "Ii", "Disassemble instructions" },
|
||||
{ "disassemble", _disassemble, "Ii", "Disassemble instructions" },
|
||||
|
@ -841,9 +841,9 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r
|
|||
case DEBUGGER_ENTER_WATCHPOINT:
|
||||
if (info) {
|
||||
if (info->type.wp.accessType & WATCHPOINT_WRITE) {
|
||||
cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08x, old value = 0x%08X)\n", info->address, info->type.wp.newValue, info->type.wp.oldValue);
|
||||
cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (new value = 0x%08X, old value = 0x%08X)\n", info->address, info->type.wp.newValue, info->type.wp.oldValue);
|
||||
} else {
|
||||
cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08x)\n", info->address, info->type.wp.oldValue);
|
||||
cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint at 0x%08X: (value = 0x%08X)\n", info->address, info->type.wp.oldValue);
|
||||
}
|
||||
} else {
|
||||
cliDebugger->backend->printf(cliDebugger->backend, "Hit watchpoint\n");
|
||||
|
|
|
@ -489,6 +489,19 @@ static void DSVideoSoftwareRendererDrawGBAScanline(struct GBAVideoRenderer* rend
|
|||
}
|
||||
}
|
||||
|
||||
if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) {
|
||||
++softwareRenderer->bg[0].enabled;
|
||||
}
|
||||
if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) {
|
||||
++softwareRenderer->bg[1].enabled;
|
||||
}
|
||||
if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) {
|
||||
++softwareRenderer->bg[2].enabled;
|
||||
}
|
||||
if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) {
|
||||
++softwareRenderer->bg[3].enabled;
|
||||
}
|
||||
|
||||
GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
|
||||
}
|
||||
|
||||
|
|
|
@ -10,57 +10,42 @@
|
|||
|
||||
static void DSTimerIrq(struct DSCommon* dscore, int timerId) {
|
||||
struct GBATimer* timer = &dscore->timers[timerId];
|
||||
if (GBATimerFlagsIsIrqPending(timer->flags)) {
|
||||
timer->flags = GBATimerFlagsClearIrqPending(timer->flags);
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
DSRaiseIRQ(dscore->cpu, dscore->memory.io, DS_IRQ_TIMER0 + timerId);
|
||||
}
|
||||
}
|
||||
|
||||
static void DSTimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 0);
|
||||
}
|
||||
|
||||
static void DSTimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 1);
|
||||
}
|
||||
|
||||
static void DSTimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 2);
|
||||
}
|
||||
|
||||
static void DSTimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
DSTimerIrq(context, 3);
|
||||
}
|
||||
|
||||
static void DSTimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
GBATimerUpdate(timing, &dscore->timers[0], &dscore->memory.io[DS_REG_TM0CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
DSTimerIrq(dscore, 0);
|
||||
if (GBATimerUpdateCountUp(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate)) {
|
||||
DSTimerIrq(dscore, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void DSTimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
GBATimerUpdate(timing, &dscore->timers[1], &dscore->memory.io[DS_REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
DSTimerIrq(dscore, 1);
|
||||
if (GBATimerUpdateCountUp(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate)) {
|
||||
DSTimerIrq(dscore, 2);
|
||||
}
|
||||
}
|
||||
|
||||
static void DSTimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
GBATimerUpdate(timing, &dscore->timers[2], &dscore->memory.io[DS_REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
DSTimerIrq(dscore, 2);
|
||||
if (GBATimerUpdateCountUp(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate)) {
|
||||
DSTimerIrq(dscore, 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void DSTimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct DSCommon* dscore = context;
|
||||
GBATimerUpdate(timing, &dscore->timers[3], &dscore->memory.io[DS_REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
DSTimerIrq(dscore, 3);
|
||||
}
|
||||
|
||||
void DSTimerInit(struct DS* ds) {
|
||||
|
@ -81,22 +66,6 @@ void DSTimerInit(struct DS* ds) {
|
|||
ds->ds7.timers[3].event.callback = DSTimerUpdate3;
|
||||
ds->ds7.timers[3].event.context = &ds->ds7;
|
||||
ds->ds7.timers[3].event.priority = 0x23;
|
||||
ds->ds7.timers[0].irq.name = "DS7 Timer 0 IRQ";
|
||||
ds->ds7.timers[0].irq.callback = DSTimerIrq0;
|
||||
ds->ds7.timers[0].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[0].irq.priority = 0x28;
|
||||
ds->ds7.timers[1].irq.name = "DS7 Timer 1 IRQ";
|
||||
ds->ds7.timers[1].irq.callback = DSTimerIrq1;
|
||||
ds->ds7.timers[1].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[1].irq.priority = 0x29;
|
||||
ds->ds7.timers[2].irq.name = "DS7 Timer 2 IRQ";
|
||||
ds->ds7.timers[2].irq.callback = DSTimerIrq2;
|
||||
ds->ds7.timers[2].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[2].irq.priority = 0x2A;
|
||||
ds->ds7.timers[3].irq.name = "DS7 Timer 3 IRQ";
|
||||
ds->ds7.timers[3].irq.callback = DSTimerIrq3;
|
||||
ds->ds7.timers[3].irq.context = &ds->ds7;
|
||||
ds->ds7.timers[3].irq.priority = 0x2B;
|
||||
|
||||
memset(ds->ds9.timers, 0, sizeof(ds->ds9.timers));
|
||||
ds->ds9.timers[0].event.name = "DS9 Timer 0";
|
||||
|
@ -119,22 +88,6 @@ void DSTimerInit(struct DS* ds) {
|
|||
ds->ds9.timers[3].event.context = &ds->ds9;
|
||||
ds->ds9.timers[3].event.priority = 0x23;
|
||||
ds->ds9.timers[3].forcedPrescale = 1;
|
||||
ds->ds9.timers[0].irq.name = "DS9 Timer 0 IRQ";
|
||||
ds->ds9.timers[0].irq.callback = DSTimerIrq0;
|
||||
ds->ds9.timers[0].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[0].irq.priority = 0x28;
|
||||
ds->ds9.timers[1].irq.name = "DS9 Timer 1 IRQ";
|
||||
ds->ds9.timers[1].irq.callback = DSTimerIrq1;
|
||||
ds->ds9.timers[1].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[1].irq.priority = 0x29;
|
||||
ds->ds9.timers[2].irq.name = "DS9 Timer 2 IRQ";
|
||||
ds->ds9.timers[2].irq.callback = DSTimerIrq2;
|
||||
ds->ds9.timers[2].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[2].irq.priority = 0x2A;
|
||||
ds->ds9.timers[3].irq.name = "DS9 Timer 3 IRQ";
|
||||
ds->ds9.timers[3].irq.callback = DSTimerIrq3;
|
||||
ds->ds9.timers[3].irq.context = &ds->ds9;
|
||||
ds->ds9.timers[3].irq.priority = 0x2B;
|
||||
}
|
||||
|
||||
void DSTimerWriteTMCNT_HI(struct GBATimer* timer, struct mTiming* timing, uint16_t* io, uint16_t value) {
|
||||
|
|
|
@ -162,7 +162,50 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
|||
if (!item->validStates || !item->data) {
|
||||
continue;
|
||||
}
|
||||
mCoreConfigGetUIntValue(&runner->config, item->data, &item->state);
|
||||
if (item->stateMappings) {
|
||||
item->state = 0;
|
||||
|
||||
size_t j;
|
||||
for (j = 0; j < item->nStates; ++j) {
|
||||
const struct GUIVariant* v = &item->stateMappings[j];
|
||||
struct GUIVariant test;
|
||||
switch (v->type) {
|
||||
case GUI_VARIANT_VOID:
|
||||
if (!mCoreConfigGetValue(&runner->config, item->data)) {
|
||||
item->state = j;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GUI_VARIANT_UNSIGNED:
|
||||
if (mCoreConfigGetUIntValue(&runner->config, item->data, &test.v.u) && test.v.u == v->v.u) {
|
||||
item->state = j;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GUI_VARIANT_INT:
|
||||
if (mCoreConfigGetIntValue(&runner->config, item->data, &test.v.i) && test.v.i == v->v.i) {
|
||||
item->state = j;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GUI_VARIANT_FLOAT:
|
||||
if (mCoreConfigGetFloatValue(&runner->config, item->data, &test.v.f) && fabsf(test.v.f - v->v.f) <= 1e-3f) {
|
||||
item->state = j;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case GUI_VARIANT_STRING:
|
||||
test.v.s = mCoreConfigGetValue(&runner->config, item->data);
|
||||
if (test.v.s && strcmp(test.v.s, v->v.s) == 0) {
|
||||
item->state = j;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mCoreConfigGetUIntValue(&runner->config, item->data, &item->state);
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
|
@ -185,10 +228,31 @@ void mGUIShowConfig(struct mGUIRunner* runner, struct GUIMenuItem* extra, size_t
|
|||
}
|
||||
for (i = 0; i < GUIMenuItemListSize(&menu.items); ++i) {
|
||||
item = GUIMenuItemListGetPointer(&menu.items, i);
|
||||
if (!item->validStates || !item->data) {
|
||||
if (!item->validStates || !item->data || ((const char*) item->data)[0] == '*') {
|
||||
continue;
|
||||
}
|
||||
mCoreConfigSetUIntValue(&runner->config, item->data, item->state);
|
||||
if (item->stateMappings) {
|
||||
const struct GUIVariant* v = &item->stateMappings[item->state];
|
||||
switch (v->type) {
|
||||
case GUI_VARIANT_VOID:
|
||||
mCoreConfigSetValue(&runner->config, item->data, NULL);
|
||||
break;
|
||||
case GUI_VARIANT_UNSIGNED:
|
||||
mCoreConfigSetUIntValue(&runner->config, item->data, v->v.u);
|
||||
break;
|
||||
case GUI_VARIANT_INT:
|
||||
mCoreConfigSetUIntValue(&runner->config, item->data, v->v.i);
|
||||
break;
|
||||
case GUI_VARIANT_FLOAT:
|
||||
mCoreConfigSetFloatValue(&runner->config, item->data, v->v.f);
|
||||
break;
|
||||
case GUI_VARIANT_STRING:
|
||||
mCoreConfigSetValue(&runner->config, item->data, v->v.s);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mCoreConfigSetUIntValue(&runner->config, item->data, item->state);
|
||||
}
|
||||
}
|
||||
if (runner->keySources) {
|
||||
size_t i;
|
||||
|
|
|
@ -150,7 +150,7 @@ void GBAudioReset(struct GBAudio* audio) {
|
|||
audio->playingCh2 = false;
|
||||
audio->playingCh3 = false;
|
||||
audio->playingCh4 = false;
|
||||
if (audio->p && audio->p->model != GB_MODEL_SGB) {
|
||||
if (audio->p && !(audio->p->model & GB_MODEL_SGB)) {
|
||||
audio->playingCh1 = true;
|
||||
audio->enable = true;
|
||||
*audio->nr52 |= 0x01;
|
||||
|
|
|
@ -219,8 +219,12 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf
|
|||
mCoreConfigCopyValue(&core->config, config, "gb.model");
|
||||
mCoreConfigCopyValue(&core->config, config, "sgb.model");
|
||||
mCoreConfigCopyValue(&core->config, config, "cgb.model");
|
||||
mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections");
|
||||
|
||||
int fakeBool = 0;
|
||||
mCoreConfigGetIntValue(config, "allowOpposingDirections", &fakeBool);
|
||||
gb->allowOpposingDirections = fakeBool;
|
||||
|
||||
int fakeBool;
|
||||
if (mCoreConfigGetIntValue(config, "sgb.borders", &fakeBool)) {
|
||||
gb->video.sgbBorders = fakeBool;
|
||||
gb->video.renderer->enableSGBBorder(gb->video.renderer, fakeBool);
|
||||
|
@ -234,7 +238,7 @@ static void _GBCoreLoadConfig(struct mCore* core, const struct mCoreConfig* conf
|
|||
|
||||
static void _GBCoreDesiredVideoDimensions(struct mCore* core, unsigned* width, unsigned* height) {
|
||||
struct GB* gb = core->board;
|
||||
if (gb && (gb->model != GB_MODEL_SGB || !gb->video.sgbBorders)) {
|
||||
if (gb && (!(gb->model & GB_MODEL_SGB) || !gb->video.sgbBorders)) {
|
||||
*width = GB_VIDEO_HORIZONTAL_PIXELS;
|
||||
*height = GB_VIDEO_VERTICAL_PIXELS;
|
||||
} else {
|
||||
|
@ -381,9 +385,9 @@ static void _GBCoreReset(struct mCore* core) {
|
|||
GBDetectModel(gb);
|
||||
if (gb->model == GB_MODEL_DMG && modelGB) {
|
||||
gb->model = GBNameToModel(modelGB);
|
||||
} else if (gb->model == GB_MODEL_CGB && modelCGB) {
|
||||
} else if ((gb->model & GB_MODEL_CGB) && modelCGB) {
|
||||
gb->model = GBNameToModel(modelCGB);
|
||||
} else if (gb->model == GB_MODEL_SGB && modelSGB) {
|
||||
} else if ((gb->model & GB_MODEL_SGB) && modelSGB) {
|
||||
gb->model = GBNameToModel(modelSGB);
|
||||
}
|
||||
}
|
||||
|
@ -1069,6 +1073,7 @@ static bool _GBVLPLoadState(struct mCore* core, const void* buffer) {
|
|||
gb->cpu->pc = GB_BASE_HRAM;
|
||||
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
|
||||
|
||||
GBVideoReset(&gb->video);
|
||||
GBVideoDeserialize(&gb->video, state);
|
||||
GBIODeserialize(gb, state);
|
||||
GBAudioReset(&gb->audio);
|
||||
|
|
|
@ -30,6 +30,7 @@ static const uint8_t _knownHeader[4] = { 0xCE, 0xED, 0x66, 0x66};
|
|||
#define DMG_2_BIOS_CHECKSUM 0x59C8598E
|
||||
#define MGB_BIOS_CHECKSUM 0xE6920754
|
||||
#define SGB_BIOS_CHECKSUM 0xEC8A83B9
|
||||
#define SGB2_BIOS_CHECKSUM 0X53D0DD63
|
||||
#define CGB_BIOS_CHECKSUM 0x41884E46
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GB, "GB", "gb");
|
||||
|
@ -249,7 +250,11 @@ void GBSramClean(struct GB* gb, uint32_t frameCount) {
|
|||
}
|
||||
|
||||
void GBSavedataMask(struct GB* gb, struct VFile* vf, bool writeback) {
|
||||
struct VFile* oldVf = gb->sramVf;
|
||||
GBSramDeinit(gb);
|
||||
if (oldVf && oldVf != gb->sramRealVf) {
|
||||
oldVf->close(oldVf);
|
||||
}
|
||||
gb->sramVf = vf;
|
||||
gb->sramMaskWriteback = writeback;
|
||||
gb->memory.sram = vf->map(vf, gb->sramSize, MAP_READ);
|
||||
|
@ -396,6 +401,7 @@ bool GBIsBIOS(struct VFile* vf) {
|
|||
case DMG_2_BIOS_CHECKSUM:
|
||||
case MGB_BIOS_CHECKSUM:
|
||||
case SGB_BIOS_CHECKSUM:
|
||||
case SGB2_BIOS_CHECKSUM:
|
||||
case CGB_BIOS_CHECKSUM:
|
||||
return true;
|
||||
default:
|
||||
|
@ -584,6 +590,9 @@ void GBDetectModel(struct GB* gb) {
|
|||
case SGB_BIOS_CHECKSUM:
|
||||
gb->model = GB_MODEL_SGB;
|
||||
break;
|
||||
case SGB2_BIOS_CHECKSUM:
|
||||
gb->model = GB_MODEL_SGB2;
|
||||
break;
|
||||
case CGB_BIOS_CHECKSUM:
|
||||
gb->model = GB_MODEL_CGB;
|
||||
break;
|
||||
|
|
26
src/gb/io.c
|
@ -105,6 +105,7 @@ static const uint8_t _registerMask[] = {
|
|||
};
|
||||
|
||||
static uint8_t _readKeys(struct GB* gb);
|
||||
static uint8_t _readKeysFiltered(struct GB* gb);
|
||||
|
||||
static void _writeSGBBits(struct GB* gb, int bits) {
|
||||
if (!bits) {
|
||||
|
@ -198,7 +199,7 @@ void GBIOReset(struct GB* gb) {
|
|||
}
|
||||
GBIOWrite(gb, REG_WY, 0x00);
|
||||
GBIOWrite(gb, REG_WX, 0x00);
|
||||
if (gb->model >= GB_MODEL_CGB) {
|
||||
if (gb->model & GB_MODEL_CGB) {
|
||||
GBIOWrite(gb, REG_UNK4C, 0);
|
||||
GBIOWrite(gb, REG_JOYP, 0xFF);
|
||||
GBIOWrite(gb, REG_VBK, 0);
|
||||
|
@ -210,7 +211,7 @@ void GBIOReset(struct GB* gb) {
|
|||
GBIOWrite(gb, REG_HDMA3, 0xFF);
|
||||
GBIOWrite(gb, REG_HDMA4, 0xFF);
|
||||
gb->memory.io[REG_HDMA5] = 0xFF;
|
||||
} else if (gb->model == GB_MODEL_SGB) {
|
||||
} else if (gb->model & GB_MODEL_SGB) {
|
||||
GBIOWrite(gb, REG_JOYP, 0xFF);
|
||||
}
|
||||
GBIOWrite(gb, REG_IE, 0x00);
|
||||
|
@ -403,7 +404,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
|||
case REG_JOYP:
|
||||
gb->memory.io[REG_JOYP] = value | 0x0F;
|
||||
_readKeys(gb);
|
||||
if (gb->model == GB_MODEL_SGB) {
|
||||
if (gb->model & GB_MODEL_SGB) {
|
||||
_writeSGBBits(gb, (value >> 4) & 3);
|
||||
}
|
||||
return;
|
||||
|
@ -557,10 +558,25 @@ static uint8_t _readKeys(struct GB* gb) {
|
|||
return gb->memory.io[REG_JOYP];
|
||||
}
|
||||
|
||||
static uint8_t _readKeysFiltered(struct GB* gb) {
|
||||
uint8_t keys = _readKeys(gb);
|
||||
if (!gb->allowOpposingDirections && (keys & 0x30) == 0x20) {
|
||||
unsigned rl = keys & 0x03;
|
||||
unsigned ud = keys & 0x0C;
|
||||
if (!rl) {
|
||||
keys |= 0x03;
|
||||
}
|
||||
if (!ud) {
|
||||
keys |= 0x0C;
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
uint8_t GBIORead(struct GB* gb, unsigned address) {
|
||||
switch (address) {
|
||||
case REG_JOYP:
|
||||
return _readKeys(gb);
|
||||
return _readKeysFiltered(gb);
|
||||
case REG_IE:
|
||||
return gb->memory.ie;
|
||||
case REG_WAVE_0:
|
||||
|
@ -703,7 +719,7 @@ void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_SCX, state->io[REG_SCX]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WY, state->io[REG_WY]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_WX, state->io[REG_WX]);
|
||||
if (gb->model == GB_MODEL_SGB) {
|
||||
if (gb->model & GB_MODEL_SGB) {
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_BGP, state->io[REG_BGP]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_OBP0, state->io[REG_OBP0]);
|
||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, REG_OBP1, state->io[REG_OBP1]);
|
||||
|
|
|
@ -30,7 +30,7 @@ static void GBVideoSoftwareRendererDrawObj(struct GBVideoSoftwareRenderer* rende
|
|||
|
||||
static void _clearScreen(struct GBVideoSoftwareRenderer* renderer) {
|
||||
size_t sgbOffset = 0;
|
||||
if (renderer->model == GB_MODEL_SGB) {
|
||||
if (renderer->model & GB_MODEL_SGB) {
|
||||
return;
|
||||
}
|
||||
int y;
|
||||
|
@ -56,7 +56,7 @@ static void _regenerateSGBBorder(struct GBVideoSoftwareRenderer* renderer) {
|
|||
int x, y;
|
||||
for (y = 0; y < 224; ++y) {
|
||||
for (x = 0; x < 256; x += 8) {
|
||||
if (x >= 48 && x < 208 && y >= 40 && y < 104) {
|
||||
if (x >= 48 && x < 208 && y >= 40 && y < 184) {
|
||||
continue;
|
||||
}
|
||||
uint16_t mapData;
|
||||
|
@ -79,16 +79,13 @@ static void _regenerateSGBBorder(struct GBVideoSoftwareRenderer* renderer) {
|
|||
int paletteBase = SGBBgAttributesGetPalette(mapData) * 0x10;
|
||||
int colorSelector;
|
||||
|
||||
int flip = 0;
|
||||
if (SGBBgAttributesIsXFlip(mapData)) {
|
||||
for (i = 0; i < 8; ++i) {
|
||||
colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3;
|
||||
renderer->outputBuffer[base + i] = renderer->palette[paletteBase | colorSelector];
|
||||
}
|
||||
} else {
|
||||
for (i = 7; i >= 0; --i) {
|
||||
colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3;
|
||||
renderer->outputBuffer[base + 7 - i] = renderer->palette[paletteBase | colorSelector];
|
||||
}
|
||||
flip = 7;
|
||||
}
|
||||
for (i = 7; i >= 0; --i) {
|
||||
colorSelector = (tileData[0] >> i & 0x1) << 0 | (tileData[1] >> i & 0x1) << 1 | (tileData[2] >> i & 0x1) << 2 | (tileData[3] >> i & 0x1) << 3;
|
||||
renderer->outputBuffer[(base + 7 - i) ^ flip] = renderer->palette[paletteBase | colorSelector];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -432,7 +429,7 @@ static void GBVideoSoftwareRendererWriteSGBPacket(struct GBVideoRenderer* render
|
|||
static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer, int index, uint16_t value) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
color_t color = mColorFrom555(value);
|
||||
if (softwareRenderer->model == GB_MODEL_SGB) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB) {
|
||||
if (index < 0x10 && index && !(index & 3)) {
|
||||
color = softwareRenderer->palette[0];
|
||||
} else if (index >= 0x40 && !(index & 0xF)) {
|
||||
|
@ -463,7 +460,7 @@ static void GBVideoSoftwareRendererWritePalette(struct GBVideoRenderer* renderer
|
|||
}
|
||||
softwareRenderer->palette[index] = color;
|
||||
|
||||
if (softwareRenderer->model == GB_MODEL_SGB && !index && GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB && !index && GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
|
||||
renderer->writePalette(renderer, 0x04, value);
|
||||
renderer->writePalette(renderer, 0x08, value);
|
||||
renderer->writePalette(renderer, 0x0C, value);
|
||||
|
@ -528,7 +525,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
|
|||
}
|
||||
|
||||
size_t sgbOffset = 0;
|
||||
if (softwareRenderer->model == GB_MODEL_SGB && softwareRenderer->sgbBorders) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB && softwareRenderer->sgbBorders) {
|
||||
sgbOffset = softwareRenderer->outputBufferStride * 40 + 48;
|
||||
}
|
||||
color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y + sgbOffset];
|
||||
|
@ -536,7 +533,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
|
|||
int p = 0;
|
||||
switch (softwareRenderer->d.sgbRenderMode) {
|
||||
case 0:
|
||||
if (softwareRenderer->model == GB_MODEL_SGB) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB) {
|
||||
p = softwareRenderer->d.sgbAttributes[(startX >> 5) + 5 * (y >> 3)];
|
||||
p >>= 6 - ((x / 4) & 0x6);
|
||||
p &= 3;
|
||||
|
@ -546,7 +543,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
|
|||
row[x] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x] & 0x7F]];
|
||||
}
|
||||
for (; x + 7 < (endX & ~7); x += 8) {
|
||||
if (softwareRenderer->model == GB_MODEL_SGB) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB) {
|
||||
p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
|
||||
p >>= 6 - ((x / 4) & 0x6);
|
||||
p &= 3;
|
||||
|
@ -561,7 +558,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
|
|||
row[x + 6] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 6] & 0x7F]];
|
||||
row[x + 7] = softwareRenderer->palette[p | softwareRenderer->lookup[softwareRenderer->row[x + 7] & 0x7F]];
|
||||
}
|
||||
if (softwareRenderer->model == GB_MODEL_SGB) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB) {
|
||||
p = softwareRenderer->d.sgbAttributes[(x >> 5) + 5 * (y >> 3)];
|
||||
p >>= 6 - ((x / 4) & 0x6);
|
||||
p &= 3;
|
||||
|
@ -678,7 +675,7 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer)
|
|||
if (!GBRegisterLCDCIsEnable(softwareRenderer->lcdc)) {
|
||||
_clearScreen(softwareRenderer);
|
||||
}
|
||||
if (softwareRenderer->model == GB_MODEL_SGB) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB) {
|
||||
switch (softwareRenderer->sgbCommandHeader >> 3) {
|
||||
case SGB_PAL_SET:
|
||||
case SGB_ATTR_SET:
|
||||
|
@ -711,7 +708,10 @@ static void GBVideoSoftwareRendererFinishFrame(struct GBVideoRenderer* renderer)
|
|||
|
||||
static void GBVideoSoftwareRendererEnableSGBBorder(struct GBVideoRenderer* renderer, bool enable) {
|
||||
struct GBVideoSoftwareRenderer* softwareRenderer = (struct GBVideoSoftwareRenderer*) renderer;
|
||||
if (softwareRenderer->model == GB_MODEL_SGB) {
|
||||
if (softwareRenderer->model & GB_MODEL_SGB) {
|
||||
if (enable == softwareRenderer->sgbBorders) {
|
||||
return;
|
||||
}
|
||||
softwareRenderer->sgbBorders = enable;
|
||||
if (softwareRenderer->sgbBorders && !renderer->sgbRenderMode) {
|
||||
_regenerateSGBBorder(softwareRenderer);
|
||||
|
|
|
@ -64,7 +64,7 @@ void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
|
|||
GBTimerSerialize(&gb->timer, state);
|
||||
GBAudioSerialize(&gb->audio, state);
|
||||
|
||||
if (gb->model == GB_MODEL_SGB) {
|
||||
if (gb->model & GB_MODEL_SGB) {
|
||||
GBSGBSerialize(gb, state);
|
||||
}
|
||||
}
|
||||
|
@ -187,7 +187,7 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
|||
GBTimerDeserialize(&gb->timer, state);
|
||||
GBAudioDeserialize(&gb->audio, state);
|
||||
|
||||
if (gb->model == GB_MODEL_SGB && canSgb) {
|
||||
if (gb->model & GB_MODEL_SGB && canSgb) {
|
||||
GBSGBDeserialize(gb, state);
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ void GBVideoReset(struct GBVideo* video) {
|
|||
video->renderer->oam = &video->oam;
|
||||
memset(&video->palette, 0, sizeof(video->palette));
|
||||
|
||||
if (video->p->model == GB_MODEL_SGB) {
|
||||
if (video->p->model & GB_MODEL_SGB) {
|
||||
video->renderer->sgbCharRam = anonymousMemoryMap(SGB_SIZE_CHAR_RAM);
|
||||
video->renderer->sgbMapRam = anonymousMemoryMap(SGB_SIZE_MAP_RAM);
|
||||
video->renderer->sgbPalRam = anonymousMemoryMap(SGB_SIZE_PAL_RAM);
|
||||
|
@ -491,7 +491,7 @@ void GBVideoWritePalette(struct GBVideo* video, uint16_t address, uint8_t value)
|
|||
video->renderer->writePalette(video->renderer, 9 * 4 + 3, video->palette[9 * 4 + 3]);
|
||||
break;
|
||||
}
|
||||
} else if (video->p->model == GB_MODEL_SGB) {
|
||||
} else if (video->p->model & GB_MODEL_SGB) {
|
||||
video->renderer->writeVideoRegister(video->renderer, address, value);
|
||||
} else {
|
||||
switch (address) {
|
||||
|
|
|
@ -247,6 +247,11 @@ static void _GBACoreLoadConfig(struct mCore* core, const struct mCoreConfig* con
|
|||
}
|
||||
}
|
||||
|
||||
int fakeBool = 0;
|
||||
mCoreConfigGetIntValue(config, "allowOpposingDirections", &fakeBool);
|
||||
gba->allowOpposingDirections = fakeBool;
|
||||
|
||||
mCoreConfigCopyValue(&core->config, config, "allowOpposingDirections");
|
||||
mCoreConfigCopyValue(&core->config, config, "gba.bios");
|
||||
|
||||
#ifndef DISABLE_THREADING
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <mgba-util/elf-read.h>
|
||||
#endif
|
||||
|
||||
#define GBA_IRQ_DELAY 7
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA, "GBA", "gba");
|
||||
mLOG_DEFINE_CATEGORY(GBA_DEBUG, "GBA Debug", "gba.debug");
|
||||
|
||||
|
@ -45,6 +47,8 @@ static void GBAHitStub(struct ARMCore* cpu, uint32_t opcode);
|
|||
static void GBAIllegal(struct ARMCore* cpu, uint32_t opcode);
|
||||
static void GBABreakpoint(struct ARMCore* cpu, int immediate);
|
||||
|
||||
static void _triggerIRQ(struct mTiming*, void* user, uint32_t cyclesLate);
|
||||
|
||||
#ifdef USE_DEBUGGERS
|
||||
static bool _setSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t* opcode);
|
||||
static bool _clearSoftwareBreakpoint(struct ARMDebugger*, uint32_t address, enum ExecutionMode mode, uint32_t opcode);
|
||||
|
@ -86,7 +90,6 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
|
|||
|
||||
GBAHardwareInit(&gba->memory.hw, NULL);
|
||||
|
||||
gba->springIRQ = 0;
|
||||
gba->keySource = 0;
|
||||
gba->rotationSource = 0;
|
||||
gba->luminanceSource = 0;
|
||||
|
@ -116,6 +119,11 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
|
|||
gba->yankedRomSize = 0;
|
||||
|
||||
mTimingInit(&gba->timing, &gba->cpu->cycles, &gba->cpu->nextEvent);
|
||||
|
||||
gba->irqEvent.name = "GBA IRQ Event";
|
||||
gba->irqEvent.callback = _triggerIRQ;
|
||||
gba->irqEvent.context = gba;
|
||||
gba->irqEvent.priority = 0;
|
||||
}
|
||||
|
||||
void GBAUnloadROM(struct GBA* gba) {
|
||||
|
@ -138,6 +146,8 @@ void GBAUnloadROM(struct GBA* gba) {
|
|||
gba->memory.rom = NULL;
|
||||
gba->isPristine = false;
|
||||
|
||||
gba->memory.savedata.maskWriteback = false;
|
||||
GBASavedataUnmask(&gba->memory.savedata);
|
||||
GBASavedataDeinit(&gba->memory.savedata);
|
||||
if (gba->memory.savedata.realVf) {
|
||||
gba->memory.savedata.realVf->close(gba->memory.savedata.realVf);
|
||||
|
@ -245,11 +255,6 @@ static void GBAProcessEvents(struct ARMCore* cpu) {
|
|||
gba->bus |= cpu->prefetch[1] << 16;
|
||||
}
|
||||
|
||||
if (gba->springIRQ && !cpu->cpsr.i) {
|
||||
ARMRaiseIRQ(cpu);
|
||||
gba->springIRQ = 0;
|
||||
}
|
||||
|
||||
int32_t nextEvent = cpu->nextEvent;
|
||||
while (cpu->cycles >= nextEvent) {
|
||||
cpu->nextEvent = INT_MAX;
|
||||
|
@ -472,34 +477,17 @@ void GBAApplyPatch(struct GBA* gba, struct Patch* patch) {
|
|||
gba->romCrc32 = doCrc32(gba->memory.rom, gba->memory.romSize);
|
||||
}
|
||||
|
||||
void GBAWriteIE(struct GBA* gba, uint16_t value) {
|
||||
if (gba->memory.io[REG_IME >> 1] && value & gba->memory.io[REG_IF >> 1]) {
|
||||
ARMRaiseIRQ(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAWriteIME(struct GBA* gba, uint16_t value) {
|
||||
if (value && gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) {
|
||||
ARMRaiseIRQ(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBARaiseIRQ(struct GBA* gba, enum GBAIRQ irq) {
|
||||
gba->memory.io[REG_IF >> 1] |= 1 << irq;
|
||||
|
||||
if (gba->memory.io[REG_IE >> 1] & 1 << irq) {
|
||||
gba->cpu->halted = 0;
|
||||
if (gba->memory.io[REG_IME >> 1]) {
|
||||
ARMRaiseIRQ(gba->cpu);
|
||||
}
|
||||
}
|
||||
GBATestIRQ(gba->cpu);
|
||||
}
|
||||
|
||||
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 = gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1];
|
||||
gba->cpu->nextEvent = gba->cpu->cycles;
|
||||
if (gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1]) {
|
||||
if (!mTimingIsScheduled(&gba->timing, &gba->irqEvent)) {
|
||||
mTimingSchedule(&gba->timing, &gba->irqEvent, GBA_IRQ_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -854,6 +842,20 @@ void GBATestKeypadIRQ(struct GBA* gba) {
|
|||
}
|
||||
}
|
||||
|
||||
static void _triggerIRQ(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
struct GBA* gba = user;
|
||||
gba->cpu->halted = 0;
|
||||
if (!(gba->memory.io[REG_IE >> 1] & gba->memory.io[REG_IF >> 1])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (gba->memory.io[REG_IME >> 1] && !gba->cpu->cpsr.i) {
|
||||
ARMRaiseIRQ(gba->cpu);
|
||||
}
|
||||
}
|
||||
|
||||
void GBASetBreakpoint(struct GBA* gba, struct mCPUComponent* component, uint32_t address, enum ExecutionMode mode, uint32_t* opcode) {
|
||||
size_t immediate;
|
||||
for (immediate = 0; immediate < gba->cpu->numComponents; ++immediate) {
|
||||
|
|
20
src/gba/io.c
|
@ -547,15 +547,18 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
GBAAdjustWaitstates(gba, value);
|
||||
break;
|
||||
case REG_IE:
|
||||
GBAWriteIE(gba, value);
|
||||
break;
|
||||
gba->memory.io[REG_IE >> 1] = value;
|
||||
GBATestIRQ(gba->cpu);
|
||||
return;
|
||||
case REG_IF:
|
||||
gba->springIRQ &= ~value;
|
||||
value = gba->memory.io[REG_IF >> 1] & ~value;
|
||||
break;
|
||||
gba->memory.io[REG_IF >> 1] = value;
|
||||
GBATestIRQ(gba->cpu);
|
||||
return;
|
||||
case REG_IME:
|
||||
GBAWriteIME(gba, value);
|
||||
break;
|
||||
gba->memory.io[REG_IME >> 1] = value;
|
||||
GBATestIRQ(gba->cpu);
|
||||
return;
|
||||
case REG_MAX:
|
||||
// Some bad interrupt libraries will write to this
|
||||
break;
|
||||
|
@ -931,7 +934,6 @@ void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
STORE_16(gba->timers[i].reload, 0, &state->timers[i].reload);
|
||||
STORE_32(gba->timers[i].lastEvent - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].lastEvent);
|
||||
STORE_32(gba->timers[i].event.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextEvent);
|
||||
STORE_32(gba->timers[i].irq.when - mTimingCurrentTime(&gba->timing), 0, &state->timers[i].nextIrq);
|
||||
STORE_32(gba->timers[i].flags, 0, &state->timers[i].flags);
|
||||
STORE_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
|
||||
STORE_32(gba->memory.dma[i].nextDest, 0, &state->dma[i].nextDest);
|
||||
|
@ -971,10 +973,6 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
if (GBATimerFlagsIsEnable(gba->timers[i].flags)) {
|
||||
mTimingSchedule(&gba->timing, &gba->timers[i].event, when);
|
||||
}
|
||||
LOAD_32(when, 0, &state->timers[i].nextIrq);
|
||||
if (GBATimerFlagsIsIrqPending(gba->timers[i].flags)) {
|
||||
mTimingSchedule(&gba->timing, &gba->timers[i].irq, when);
|
||||
}
|
||||
|
||||
LOAD_16(gba->memory.dma[i].reg, (REG_DMA0CNT_HI + i * 12), state->io);
|
||||
LOAD_32(gba->memory.dma[i].nextSource, 0, &state->dma[i].nextSource);
|
||||
|
|
|
@ -99,13 +99,6 @@ void GBAMemoryDeinit(struct GBA* gba) {
|
|||
if (gba->memory.rom) {
|
||||
mappedMemoryFree(gba->memory.rom, gba->memory.romSize);
|
||||
}
|
||||
gba->memory.savedata.maskWriteback = false;
|
||||
GBASavedataUnmask(&gba->memory.savedata);
|
||||
GBASavedataDeinit(&gba->memory.savedata);
|
||||
if (gba->memory.savedata.realVf) {
|
||||
gba->memory.savedata.realVf->close(gba->memory.savedata.realVf);
|
||||
}
|
||||
|
||||
if (gba->memory.agbPrintBuffer) {
|
||||
mappedMemoryFree(gba->memory.agbPrintBuffer, SIZE_AGB_PRINT);
|
||||
}
|
||||
|
|
|
@ -582,6 +582,8 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
return;
|
||||
}
|
||||
|
||||
CLEAN_SCANLINE(softwareRenderer, y);
|
||||
|
||||
color_t* row = &softwareRenderer->outputBuffer[softwareRenderer->outputBufferStride * y];
|
||||
if (GBARegisterDISPCNTIsForcedBlank(softwareRenderer->dispcnt)) {
|
||||
int x;
|
||||
|
@ -694,15 +696,19 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
|
||||
if (softwareRenderer->bg[0].enabled > 0 && softwareRenderer->bg[0].enabled < 4) {
|
||||
++softwareRenderer->bg[0].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
if (softwareRenderer->bg[1].enabled > 0 && softwareRenderer->bg[1].enabled < 4) {
|
||||
++softwareRenderer->bg[1].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
if (softwareRenderer->bg[2].enabled > 0 && softwareRenderer->bg[2].enabled < 4) {
|
||||
++softwareRenderer->bg[2].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
if (softwareRenderer->bg[3].enabled > 0 && softwareRenderer->bg[3].enabled < 4) {
|
||||
++softwareRenderer->bg[3].enabled;
|
||||
DIRTY_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
|
||||
GBAVideoSoftwareRendererPostprocessBuffer(softwareRenderer);
|
||||
|
@ -718,7 +724,6 @@ static void GBAVideoSoftwareRendererDrawScanline(struct GBAVideoRenderer* render
|
|||
#else
|
||||
memcpy(row, softwareRenderer->row, softwareRenderer->masterEnd * sizeof(*row));
|
||||
#endif
|
||||
CLEAN_SCANLINE(softwareRenderer, y);
|
||||
}
|
||||
|
||||
static void GBAVideoSoftwareRendererFinishFrame(struct GBAVideoRenderer* renderer) {
|
||||
|
@ -765,9 +770,10 @@ static void GBAVideoSoftwareRendererPutPixels(struct GBAVideoRenderer* renderer,
|
|||
}
|
||||
|
||||
static void _enableBg(struct GBAVideoSoftwareRenderer* renderer, int bg, bool active) {
|
||||
int wasActive = renderer->bg[bg].enabled;
|
||||
if (!active) {
|
||||
renderer->bg[bg].enabled = 0;
|
||||
} else if (!renderer->bg[bg].enabled && active) {
|
||||
} else if (!wasActive && active) {
|
||||
if (renderer->nextY == 0) {
|
||||
renderer->bg[bg].enabled = 4;
|
||||
} else {
|
||||
|
|
|
@ -88,7 +88,11 @@ void GBASavedataDeinit(struct GBASavedata* savedata) {
|
|||
|
||||
void GBASavedataMask(struct GBASavedata* savedata, struct VFile* vf, bool writeback) {
|
||||
enum SavedataType type = savedata->type;
|
||||
struct VFile* oldVf = savedata->vf;
|
||||
GBASavedataDeinit(savedata);
|
||||
if (oldVf && oldVf != savedata->realVf) {
|
||||
oldVf->close(oldVf);
|
||||
}
|
||||
savedata->vf = vf;
|
||||
savedata->mapMode = MAP_READ;
|
||||
savedata->maskWriteback = writeback;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <fcntl.h>
|
||||
|
||||
const uint32_t GBA_SAVESTATE_MAGIC = 0x01000000;
|
||||
const uint32_t GBA_SAVESTATE_VERSION = 0x00000002;
|
||||
const uint32_t GBA_SAVESTATE_VERSION = 0x00000003;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize");
|
||||
|
||||
|
@ -62,6 +62,10 @@ void GBASerialize(struct GBA* gba, struct GBASerializedState* state) {
|
|||
GBASerializedMiscFlags miscFlags = 0;
|
||||
miscFlags = GBASerializedMiscFlagsSetHalted(miscFlags, gba->cpu->halted);
|
||||
miscFlags = GBASerializedMiscFlagsSetPOSTFLG(miscFlags, gba->memory.io[REG_POSTFLG >> 1] & 1);
|
||||
if (mTimingIsScheduled(&gba->timing, &gba->irqEvent)) {
|
||||
miscFlags = GBASerializedMiscFlagsFillIrqPending(miscFlags);
|
||||
STORE_32(gba->irqEvent.when - mTimingCurrentTime(&gba->timing), 0, &state->nextIrq);
|
||||
}
|
||||
STORE_32(miscFlags, 0, &state->miscFlags);
|
||||
|
||||
GBAMemorySerialize(&gba->memory, state);
|
||||
|
@ -179,6 +183,11 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
LOAD_32(miscFlags, 0, &state->miscFlags);
|
||||
gba->cpu->halted = GBASerializedMiscFlagsGetHalted(miscFlags);
|
||||
gba->memory.io[REG_POSTFLG >> 1] = GBASerializedMiscFlagsGetPOSTFLG(miscFlags);
|
||||
if (GBASerializedMiscFlagsIsIrqPending(miscFlags)) {
|
||||
int32_t when;
|
||||
LOAD_32(when, 0, &state->nextIrq);
|
||||
mTimingSchedule(&gba->timing, &gba->irqEvent, when);
|
||||
}
|
||||
|
||||
GBAVideoDeserialize(&gba->video, state);
|
||||
GBAMemoryDeserialize(&gba->memory, state);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include <mgba/internal/gba/gba.h>
|
||||
#include <mgba/internal/gba/io.h>
|
||||
|
||||
#define TIMER_IRQ_DELAY 7
|
||||
#define TIMER_RELOAD_DELAY 0
|
||||
#define TIMER_STARTUP_DELAY 2
|
||||
|
||||
|
@ -16,49 +15,17 @@
|
|||
|
||||
static void GBATimerIrq(struct GBA* gba, int timerId) {
|
||||
struct GBATimer* timer = &gba->timers[timerId];
|
||||
if (GBATimerFlagsIsIrqPending(timer->flags)) {
|
||||
timer->flags = GBATimerFlagsClearIrqPending(timer->flags);
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
GBARaiseIRQ(gba, IRQ_TIMER0 + timerId);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerIrq0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 0);
|
||||
}
|
||||
|
||||
static void GBATimerIrq1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 1);
|
||||
}
|
||||
|
||||
static void GBATimerIrq2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 2);
|
||||
}
|
||||
|
||||
static void GBATimerIrq3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
GBATimerIrq(context, 3);
|
||||
}
|
||||
|
||||
void GBATimerUpdate(struct mTiming* timing, struct GBATimer* timer, uint16_t* io, uint32_t cyclesLate) {
|
||||
if (GBATimerFlagsIsCountUp(timer->flags)) {
|
||||
*io = timer->reload;
|
||||
} else {
|
||||
GBATimerUpdateRegisterInternal(timer, timing, io, TIMER_RELOAD_DELAY + cyclesLate);
|
||||
}
|
||||
|
||||
if (GBATimerFlagsIsDoIrq(timer->flags)) {
|
||||
timer->flags = GBATimerFlagsFillIrqPending(timer->flags);
|
||||
if (!mTimingIsScheduled(timing, &timer->irq)) {
|
||||
mTimingSchedule(timing, &timer->irq, TIMER_IRQ_DELAY - cyclesLate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLate) {
|
||||
|
@ -74,38 +41,50 @@ static void GBATimerUpdateAudio(struct GBA* gba, int timerId, uint32_t cyclesLat
|
|||
}
|
||||
}
|
||||
|
||||
void GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate) {
|
||||
bool GBATimerUpdateCountUp(struct mTiming* timing, struct GBATimer* nextTimer, uint16_t* io, uint32_t cyclesLate) {
|
||||
if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
|
||||
++*io;
|
||||
if (!*io && GBATimerFlagsIsEnable(nextTimer->flags)) {
|
||||
GBATimerUpdate(timing, nextTimer, io, cyclesLate);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void GBATimerUpdate0(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
GBATimerUpdateAudio(gba, 0, cyclesLate);
|
||||
GBATimerUpdate(timing, &gba->timers[0], &gba->memory.io[REG_TM0CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerIrq(gba, 0);
|
||||
if (GBATimerUpdateCountUp(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate)) {
|
||||
GBATimerIrq(gba, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerUpdate1(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
GBATimerUpdateAudio(gba, 1, cyclesLate);
|
||||
GBATimerUpdate(timing, &gba->timers[1], &gba->memory.io[REG_TM1CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerIrq(gba, 1);
|
||||
if (GBATimerUpdateCountUp(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate)) {
|
||||
GBATimerIrq(gba, 2);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerUpdate2(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
GBATimerUpdate(timing, &gba->timers[2], &gba->memory.io[REG_TM2CNT_LO >> 1], cyclesLate);
|
||||
GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
GBATimerIrq(gba, 2);
|
||||
if (GBATimerUpdateCountUp(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate)) {
|
||||
GBATimerIrq(gba, 3);
|
||||
}
|
||||
}
|
||||
|
||||
static void GBATimerUpdate3(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
struct GBA* gba = context;
|
||||
GBATimerUpdate(timing, &gba->timers[3], &gba->memory.io[REG_TM3CNT_LO >> 1], cyclesLate);
|
||||
GBATimerIrq(gba, 3);
|
||||
}
|
||||
|
||||
void GBATimerInit(struct GBA* gba) {
|
||||
|
@ -126,22 +105,6 @@ void GBATimerInit(struct GBA* gba) {
|
|||
gba->timers[3].event.callback = GBATimerUpdate3;
|
||||
gba->timers[3].event.context = gba;
|
||||
gba->timers[3].event.priority = 0x23;
|
||||
gba->timers[0].irq.name = "GBA Timer 0 IRQ";
|
||||
gba->timers[0].irq.callback = GBATimerIrq0;
|
||||
gba->timers[0].irq.context = gba;
|
||||
gba->timers[0].irq.priority = 0x28;
|
||||
gba->timers[1].irq.name = "GBA Timer 1 IRQ";
|
||||
gba->timers[1].irq.callback = GBATimerIrq1;
|
||||
gba->timers[1].irq.context = gba;
|
||||
gba->timers[1].irq.priority = 0x29;
|
||||
gba->timers[2].irq.name = "GBA Timer 2 IRQ";
|
||||
gba->timers[2].irq.callback = GBATimerIrq2;
|
||||
gba->timers[2].irq.context = gba;
|
||||
gba->timers[2].irq.priority = 0x2A;
|
||||
gba->timers[3].irq.name = "GBA Timer 3 IRQ";
|
||||
gba->timers[3].irq.callback = GBATimerIrq3;
|
||||
gba->timers[3].irq.context = gba;
|
||||
gba->timers[3].irq.priority = 0x2B;
|
||||
}
|
||||
|
||||
void GBATimerUpdateRegister(struct GBA* gba, int timer, int32_t cyclesLate) {
|
||||
|
|
|
@ -113,6 +113,8 @@ if(BUILD_PERF)
|
|||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cia.rsf.in ${CMAKE_CURRENT_BINARY_DIR}/cia.rsf)
|
||||
|
||||
install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg)
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.3dsx
|
||||
${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.smdh
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
#include <mgba/gba/interface.h>
|
||||
#include <mgba/internal/gba/gba.h>
|
||||
#endif
|
||||
#include <mgba-util/circle-buffer.h>
|
||||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
|
@ -55,8 +54,8 @@ static void* data;
|
|||
static size_t dataSize;
|
||||
static void* savedata;
|
||||
static struct mAVStream stream;
|
||||
static int rumbleLevel;
|
||||
static struct CircleBuffer rumbleHistory;
|
||||
static int rumbleUp;
|
||||
static int rumbleDown;
|
||||
static struct mRumble rumble;
|
||||
static struct GBALuminanceSource lux;
|
||||
static int luxLevel;
|
||||
|
@ -254,7 +253,6 @@ void retro_init(void) {
|
|||
struct retro_rumble_interface rumbleInterface;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE, &rumbleInterface)) {
|
||||
rumbleCallback = rumbleInterface.set_rumble_state;
|
||||
CircleBufferInit(&rumbleHistory, RUMBLE_PWM);
|
||||
rumble.setRumble = _setRumble;
|
||||
} else {
|
||||
rumbleCallback = 0;
|
||||
|
@ -348,6 +346,18 @@ void retro_run(void) {
|
|||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256);
|
||||
|
||||
if (rumbleCallback) {
|
||||
if (rumbleUp) {
|
||||
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));
|
||||
rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));
|
||||
} else {
|
||||
rumbleCallback(0, RETRO_RUMBLE_STRONG, 0);
|
||||
rumbleCallback(0, RETRO_RUMBLE_WEAK, 0);
|
||||
}
|
||||
rumbleUp = 0;
|
||||
rumbleDown = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void _setupMaps(struct mCore* core) {
|
||||
|
@ -438,9 +448,8 @@ void retro_reset(void) {
|
|||
core->reset(core);
|
||||
_setupMaps(core);
|
||||
|
||||
if (rumbleCallback) {
|
||||
CircleBufferClear(&rumbleHistory);
|
||||
}
|
||||
rumbleUp = 0;
|
||||
rumbleDown = 0;
|
||||
}
|
||||
|
||||
bool retro_load_game(const struct retro_game_info* game) {
|
||||
|
@ -558,7 +567,6 @@ void retro_unload_game(void) {
|
|||
data = 0;
|
||||
mappedMemoryFree(savedata, SIZE_CART_FLASH1M);
|
||||
savedata = 0;
|
||||
CircleBufferDeinit(&rumbleHistory);
|
||||
}
|
||||
|
||||
size_t retro_serialize_size(void) {
|
||||
|
@ -755,15 +763,11 @@ static void _setRumble(struct mRumble* rumble, int enable) {
|
|||
if (!rumbleCallback) {
|
||||
return;
|
||||
}
|
||||
rumbleLevel += enable;
|
||||
if (CircleBufferSize(&rumbleHistory) == RUMBLE_PWM) {
|
||||
int8_t oldLevel;
|
||||
CircleBufferRead8(&rumbleHistory, &oldLevel);
|
||||
rumbleLevel -= oldLevel;
|
||||
if (enable) {
|
||||
++rumbleUp;
|
||||
} else {
|
||||
++rumbleDown;
|
||||
}
|
||||
CircleBufferWrite8(&rumbleHistory, enable);
|
||||
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleLevel * 0xFFFF / RUMBLE_PWM);
|
||||
rumbleCallback(0, RETRO_RUMBLE_WEAK, rumbleLevel * 0xFFFF / RUMBLE_PWM);
|
||||
}
|
||||
|
||||
static void _updateLux(struct GBALuminanceSource* lux) {
|
||||
|
|
|
@ -61,4 +61,5 @@ vita_create_vpk(${BINARY_NAME}.vpk MGBA00001 ${BINARY_NAME}.self
|
|||
FILE ${CMAKE_CURRENT_SOURCE_DIR}/startup.png sce_sys/livearea/contents/startup.png
|
||||
FILE ${CMAKE_CURRENT_BINARY_DIR}/template.xml sce_sys/livearea/contents/template.xml)
|
||||
|
||||
install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.vpk DESTINATION . COMPONENT ${BINARY_NAME}-psp2)
|
||||
|
|
|
@ -349,6 +349,10 @@ elseif(WIN32)
|
|||
find_program(BASH bash)
|
||||
install(CODE "execute_process(COMMAND \"${BASH}\" \"${CMAKE_SOURCE_DIR}/tools/deploy-win.sh\" \"${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe\" \"\${CMAKE_INSTALL_PREFIX}\" \"\$ENV{PWD}\" WORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")" COMPONENT ${BINARY_NAME}-qt)
|
||||
endif()
|
||||
if(DISTBUILD)
|
||||
file(WRITE "${CMAKE_BINARY_DIR}/portable.ini" "")
|
||||
install(FILES "${CMAKE_BINARY_DIR}/portable.ini" DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT ${BINARY_NAME}-qt)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(DISTBUILD AND CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
|
|
|
@ -249,6 +249,10 @@ void CoreController::loadConfig(ConfigController* config) {
|
|||
m_autosave = config->getOption("autosave", false).toInt();
|
||||
m_autoload = config->getOption("autoload", true).toInt();
|
||||
m_autofireThreshold = config->getOption("autofireThreshold", m_autofireThreshold).toInt();
|
||||
m_fastForwardVolume = config->getOption("fastForwardVolume", -1).toInt();
|
||||
m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt();
|
||||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
|
||||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
|
||||
mCoreLoadForeignConfig(m_threadContext.core, config->config());
|
||||
if (hasStarted()) {
|
||||
updateFastForward();
|
||||
|
@ -782,15 +786,29 @@ void CoreController::finishFrame() {
|
|||
|
||||
void CoreController::updateFastForward() {
|
||||
if (m_fastForward || m_fastForwardForced) {
|
||||
if (m_fastForwardVolume >= 0) {
|
||||
m_threadContext.core->opts.volume = m_fastForwardVolume;
|
||||
}
|
||||
if (m_fastForwardMute >= 0) {
|
||||
m_threadContext.core->opts.mute = m_fastForwardMute;
|
||||
}
|
||||
if (m_fastForwardRatio > 0) {
|
||||
m_threadContext.impl->sync.fpsTarget = m_fpsTarget * m_fastForwardRatio;
|
||||
} else {
|
||||
setSync(false);
|
||||
}
|
||||
} else {
|
||||
if (!mCoreConfigGetIntValue(&m_threadContext.core->config, "volume", &m_threadContext.core->opts.volume)) {
|
||||
m_threadContext.core->opts.volume = 0x100;
|
||||
}
|
||||
int fakeBool = 0;
|
||||
mCoreConfigGetIntValue(&m_threadContext.core->config, "mute", &fakeBool);
|
||||
m_threadContext.core->opts.mute = fakeBool;
|
||||
m_threadContext.impl->sync.fpsTarget = m_fpsTarget;
|
||||
setSync(true);
|
||||
}
|
||||
// XXX: Have a way of just updating opts
|
||||
m_threadContext.core->loadConfig(m_threadContext.core, &m_threadContext.core->config);
|
||||
}
|
||||
|
||||
CoreController::Interrupter::Interrupter(CoreController* parent, bool fromThread)
|
||||
|
|
|
@ -200,6 +200,8 @@ private:
|
|||
|
||||
int m_fastForward = false;
|
||||
int m_fastForwardForced = false;
|
||||
int m_fastForwardVolume = -1;
|
||||
int m_fastForwardMute = -1;
|
||||
float m_fastForwardRatio = -1.f;
|
||||
float m_fpsTarget;
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ DebuggerConsole::DebuggerConsole(DebuggerConsoleController* controller, QWidget*
|
|||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.prompt->installEventFilter(this);
|
||||
|
||||
connect(m_ui.prompt, &QLineEdit::returnPressed, this, &DebuggerConsole::postLine);
|
||||
connect(controller, &DebuggerConsoleController::log, this, &DebuggerConsole::log);
|
||||
connect(m_ui.breakpoint, &QAbstractButton::clicked, controller, &DebuggerController::attach);
|
||||
|
@ -36,7 +38,47 @@ void DebuggerConsole::postLine() {
|
|||
if (line.isEmpty()) {
|
||||
m_consoleController->enterLine(QString("\n"));
|
||||
} else {
|
||||
m_history.append(line);
|
||||
m_historyOffset = 0;
|
||||
log(QString("> %1\n").arg(line));
|
||||
m_consoleController->enterLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
bool DebuggerConsole::eventFilter(QObject*, QEvent* event) {
|
||||
if (event->type() != QEvent::KeyPress) {
|
||||
return false;
|
||||
}
|
||||
if (m_history.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
switch (keyEvent->key()) {
|
||||
case Qt::Key_Down:
|
||||
if (m_historyOffset <= 0) {
|
||||
return false;
|
||||
}
|
||||
--m_historyOffset;
|
||||
break;
|
||||
case Qt::Key_Up:
|
||||
if (m_historyOffset >= m_history.size()) {
|
||||
return false;
|
||||
}
|
||||
++m_historyOffset;
|
||||
break;
|
||||
case Qt::Key_End:
|
||||
m_historyOffset = 0;
|
||||
break;
|
||||
case Qt::Key_Home:
|
||||
m_historyOffset = m_history.size();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
if (m_historyOffset == 0) {
|
||||
m_ui.prompt->clear();
|
||||
} else {
|
||||
m_ui.prompt->setText(m_history[m_history.size() - m_historyOffset]);
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -21,8 +21,13 @@ private slots:
|
|||
void log(const QString&);
|
||||
void postLine();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject*, QEvent*) override;
|
||||
|
||||
private:
|
||||
Ui::DebuggerConsole m_ui;
|
||||
QStringList m_history;
|
||||
int m_historyOffset;
|
||||
|
||||
DebuggerConsoleController* m_consoleController;
|
||||
};
|
||||
|
|
|
@ -40,6 +40,7 @@ DisplayGL::DisplayGL(const QGLFormat& format, QWidget* parent)
|
|||
}
|
||||
|
||||
DisplayGL::~DisplayGL() {
|
||||
stopDrawing();
|
||||
delete m_painter;
|
||||
}
|
||||
|
||||
|
@ -196,8 +197,15 @@ PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
|
|||
mGLES2Context* gl2Backend;
|
||||
#endif
|
||||
|
||||
m_gl->makeCurrent();
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
|
||||
QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
|
||||
|
||||
#if !defined(_WIN32) || defined(USE_EPOXY)
|
||||
if (majorVersion >= 2) {
|
||||
if (extensions.contains("GL_ARB_framebuffer_object") && majorVersion >= 2) {
|
||||
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
|
||||
mGLES2ContextCreate(gl2Backend);
|
||||
m_backend = &gl2Backend->d;
|
||||
|
@ -218,10 +226,6 @@ PainterGL::PainterGL(int majorVersion, QGLWidget* parent)
|
|||
painter->m_gl->swapBuffers();
|
||||
};
|
||||
|
||||
m_gl->makeCurrent();
|
||||
#if defined(_WIN32) && defined(USE_EPOXY)
|
||||
epoxy_handle_external_wglMakeCurrent();
|
||||
#endif
|
||||
m_backend->init(m_backend, reinterpret_cast<WHandle>(m_gl->winId()));
|
||||
#if !defined(_WIN32) || defined(USE_EPOXY)
|
||||
if (m_supportsShaders) {
|
||||
|
|
|
@ -27,8 +27,9 @@ LoadSaveState::LoadSaveState(std::shared_ptr<CoreController> controller, QWidget
|
|||
, m_mode(LoadSave::LOAD)
|
||||
, m_currentFocus(controller->stateSlot() - 1)
|
||||
{
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
m_ui.setupUi(this);
|
||||
m_ui.lsLabel->setFocusProxy(this);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
m_slots[0] = m_ui.state1;
|
||||
m_slots[1] = m_ui.state2;
|
||||
|
@ -56,6 +57,7 @@ LoadSaveState::LoadSaveState(std::shared_ptr<CoreController> controller, QWidget
|
|||
if (m_currentFocus < 0) {
|
||||
m_currentFocus = 0;
|
||||
}
|
||||
m_slots[m_currentFocus]->setFocus();
|
||||
|
||||
QAction* escape = new QAction(this);
|
||||
connect(escape, &QAction::triggered, this, &QWidget::close);
|
||||
|
@ -242,6 +244,10 @@ void LoadSaveState::showEvent(QShowEvent* event) {
|
|||
QWidget::showEvent(event);
|
||||
}
|
||||
|
||||
void LoadSaveState::focusInEvent(QFocusEvent*) {
|
||||
m_slots[m_currentFocus]->setFocus();
|
||||
}
|
||||
|
||||
void LoadSaveState::paintEvent(QPaintEvent*) {
|
||||
QPainter painter(this);
|
||||
QRect full(QPoint(), size());
|
||||
|
|
|
@ -41,6 +41,7 @@ protected:
|
|||
virtual void closeEvent(QCloseEvent*) override;
|
||||
virtual void showEvent(QShowEvent*) override;
|
||||
virtual void paintEvent(QPaintEvent*) override;
|
||||
virtual void focusInEvent(QFocusEvent*) override;
|
||||
|
||||
private:
|
||||
void loadState(int slot);
|
||||
|
|
|
@ -32,7 +32,7 @@ SensorView::SensorView(InputController* input, QWidget* parent)
|
|||
m_ui.time->setDateTime(QDateTime::currentDateTime());
|
||||
});
|
||||
|
||||
m_timer.setInterval(2);
|
||||
m_timer.setInterval(15);
|
||||
connect(&m_timer, &QTimer::timeout, this, &SensorView::updateSensors);
|
||||
if (!m_rotation || !m_rotation->readTiltX || !m_rotation->readTiltY) {
|
||||
m_ui.tilt->hide();
|
||||
|
|
|
@ -43,6 +43,18 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
|||
|
||||
reloadConfig();
|
||||
|
||||
connect(m_ui.volume, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), [this](int v) {
|
||||
if (v < m_ui.volumeFf->value()) {
|
||||
m_ui.volumeFf->setValue(v);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_ui.mute, &QAbstractButton::toggled, [this](bool e) {
|
||||
if (e) {
|
||||
m_ui.muteFf->setChecked(e);
|
||||
}
|
||||
});
|
||||
|
||||
if (m_ui.savegamePath->text().isEmpty()) {
|
||||
m_ui.savegameSameDir->setChecked(true);
|
||||
}
|
||||
|
@ -344,6 +356,8 @@ void SettingsView::updateConfig() {
|
|||
saveSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
|
||||
saveSetting("volume", m_ui.volume);
|
||||
saveSetting("mute", m_ui.mute);
|
||||
saveSetting("fastForwardVolume", m_ui.volumeFf);
|
||||
saveSetting("fastForwardMute", m_ui.muteFf);
|
||||
saveSetting("rewindEnable", m_ui.rewind);
|
||||
saveSetting("rewindBufferCapacity", m_ui.rewindCapacity);
|
||||
saveSetting("resampleVideo", m_ui.resampleVideo);
|
||||
|
@ -472,8 +486,10 @@ void SettingsView::reloadConfig() {
|
|||
loadSetting("autofireThreshold", m_ui.autofireThreshold);
|
||||
loadSetting("lockAspectRatio", m_ui.lockAspectRatio);
|
||||
loadSetting("lockIntegerScaling", m_ui.lockIntegerScaling);
|
||||
loadSetting("volume", m_ui.volume);
|
||||
loadSetting("mute", m_ui.mute);
|
||||
loadSetting("volume", m_ui.volume, 0x100);
|
||||
loadSetting("mute", m_ui.mute, false);
|
||||
loadSetting("fastForwardVolume", m_ui.volumeFf, m_ui.volume->value());
|
||||
loadSetting("fastForwardMute", m_ui.muteFf, m_ui.mute->isChecked());
|
||||
loadSetting("rewindEnable", m_ui.rewind);
|
||||
loadSetting("rewindBufferCapacity", m_ui.rewindCapacity);
|
||||
loadSetting("resampleVideo", m_ui.resampleVideo);
|
||||
|
@ -604,9 +620,9 @@ void SettingsView::loadSetting(const char* key, QLineEdit* field) {
|
|||
field->setText(option);
|
||||
}
|
||||
|
||||
void SettingsView::loadSetting(const char* key, QSlider* field) {
|
||||
void SettingsView::loadSetting(const char* key, QSlider* field, int defaultVal) {
|
||||
QString option = loadSetting(key);
|
||||
field->setValue(option.toInt());
|
||||
field->setValue(option.isNull() ? defaultVal : option.toInt());
|
||||
}
|
||||
|
||||
void SettingsView::loadSetting(const char* key, QSpinBox* field) {
|
||||
|
|
|
@ -75,7 +75,7 @@ private:
|
|||
void loadSetting(const char* key, QComboBox*);
|
||||
void loadSetting(const char* key, QDoubleSpinBox*);
|
||||
void loadSetting(const char* key, QLineEdit*);
|
||||
void loadSetting(const char* key, QSlider*);
|
||||
void loadSetting(const char* key, QSlider*, int defaultVal = 0);
|
||||
void loadSetting(const char* key, QSpinBox*);
|
||||
QString loadSetting(const char* key);
|
||||
};
|
||||
|
|
|
@ -263,21 +263,61 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_34">
|
||||
<property name="text">
|
||||
<string>Fast forward volume:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17">
|
||||
<item>
|
||||
<widget class="QSlider" name="volumeFf">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>128</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>256</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>256</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="muteFf">
|
||||
<property name="text">
|
||||
<string>Mute</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Display driver:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="displayDriver">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
|
@ -287,14 +327,14 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Frameskip:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_16">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_12">
|
||||
|
@ -315,14 +355,14 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="8" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>FPS target:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="fpsTarget">
|
||||
|
@ -349,21 +389,21 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Sync:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<item row="10" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="videoSync">
|
||||
|
@ -381,7 +421,7 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="lockAspectRatio">
|
||||
<property name="text">
|
||||
<string>Lock aspect ratio</string>
|
||||
|
@ -389,19 +429,19 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<widget class="QCheckBox" name="resampleVideo">
|
||||
<property name="text">
|
||||
<string>Bilinear filtering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="lockIntegerScaling">
|
||||
<property name="text">
|
||||
<string>Force integer scaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="1">
|
||||
<widget class="QCheckBox" name="resampleVideo">
|
||||
<property name="text">
|
||||
<string>Bilinear filtering</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="interface_2">
|
||||
|
|
|
@ -59,8 +59,12 @@ void TilePainter::setTileCount(int tiles) {
|
|||
int w = width() / m_size;
|
||||
int h = (tiles + w - 1) * m_size / w;
|
||||
setMinimumSize(m_size, h - (h % m_size));
|
||||
resizeEvent(nullptr);
|
||||
} else {
|
||||
int w = minimumSize().width() / m_size;
|
||||
int h = (tiles + w - 1) * m_size / w;
|
||||
setMinimumSize(minimumSize().width(), h - (h % m_size));
|
||||
}
|
||||
resizeEvent(nullptr);
|
||||
}
|
||||
|
||||
void TilePainter::setTileMagnification(int mag) {
|
||||
|
|
|
@ -65,6 +65,21 @@ TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
|
||||
updateTiles(true);
|
||||
});
|
||||
|
||||
connect(m_ui.tilesPerRow, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this](int count) {
|
||||
m_ui.tiles->setMinimumSize(m_ui.magnification->value() * 8 * count, m_ui.tiles->minimumSize().height());
|
||||
updateTiles(true);
|
||||
});
|
||||
|
||||
connect(m_ui.tileFit, &QAbstractButton::toggled, [this](bool selected) {
|
||||
if (!selected) {
|
||||
m_ui.tiles->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_ui.tiles->setMinimumSize(m_ui.magnification->value() * 8 * m_ui.tilesPerRow->value(), m_ui.tiles->minimumSize().height());
|
||||
} else {
|
||||
m_ui.tiles->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
|
||||
}
|
||||
updateTiles(true);
|
||||
});
|
||||
}
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
|
|
|
@ -6,48 +6,15 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>501</width>
|
||||
<height>335</height>
|
||||
<width>693</width>
|
||||
<height>467</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Tiles</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="0">
|
||||
<widget class="QGBA::AssetTile" name="tile"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="magnification">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Magnification</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="5">
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
|
||||
<item row="0" column="1" rowspan="4">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
|
@ -66,7 +33,7 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>256</width>
|
||||
<width>405</width>
|
||||
<height>768</height>
|
||||
</rect>
|
||||
</property>
|
||||
|
@ -112,7 +79,7 @@
|
|||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
@ -125,22 +92,84 @@
|
|||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QGBA::AssetTile" name="tile"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QSpinBox" name="paletteId">
|
||||
<property name="maximum">
|
||||
<number>15</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="palette256">
|
||||
<property name="text">
|
||||
<string>256 colors</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QSpinBox" name="magnification">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>4</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Magnification</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QSpinBox" name="tilesPerRow">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>64</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>32</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Tiles per row</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="tileFit">
|
||||
<property name="text">
|
||||
<string>Fit to window</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -164,6 +193,22 @@
|
|||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>tileFit</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>tilesPerRow</receiver>
|
||||
<slot>setDisabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>162</x>
|
||||
<y>180</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>39</x>
|
||||
<y>133</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>magnification</sender>
|
||||
<signal>valueChanged(int)</signal>
|
||||
|
@ -171,12 +216,12 @@
|
|||
<slot>setTileMagnification(int)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>36</x>
|
||||
<y>83</y>
|
||||
<x>39</x>
|
||||
<y>81</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>339</x>
|
||||
<y>396</y>
|
||||
<x>462</x>
|
||||
<y>391</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
@ -187,11 +232,11 @@
|
|||
<slot>setDisabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>158</x>
|
||||
<y>29</y>
|
||||
<x>148</x>
|
||||
<y>24</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>44</x>
|
||||
<x>39</x>
|
||||
<y>29</y>
|
||||
</hint>
|
||||
</hints>
|
||||
|
|
|
@ -103,7 +103,6 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi
|
|||
setAcceptDrops(true);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
updateTitle();
|
||||
reloadDisplayDriver();
|
||||
|
||||
m_logo.setDevicePixelRatio(m_screenWidget->devicePixelRatio());
|
||||
m_logo = m_logo; // Free memory left over in old pixmap
|
||||
|
@ -160,12 +159,10 @@ Window::Window(CoreManager* manager, ConfigController* config, int playerId, QWi
|
|||
|
||||
connect(this, &Window::shutdown, m_logView, &QWidget::hide);
|
||||
connect(&m_fpsTimer, &QTimer::timeout, this, &Window::showFPS);
|
||||
connect(&m_frameTimer, &QTimer::timeout, this, &Window::delimitFrames);
|
||||
connect(&m_focusCheck, &QTimer::timeout, this, &Window::focusCheck);
|
||||
|
||||
m_log.setLevels(mLOG_WARN | mLOG_ERROR | mLOG_FATAL);
|
||||
m_fpsTimer.setInterval(FPS_TIMER_INTERVAL);
|
||||
m_frameTimer.setInterval(FRAME_LIST_INTERVAL);
|
||||
m_focusCheck.setInterval(200);
|
||||
|
||||
setupMenu(menuBar());
|
||||
|
@ -270,8 +267,10 @@ void Window::reloadConfig() {
|
|||
}
|
||||
m_display->resizeContext();
|
||||
}
|
||||
m_display->lockAspectRatio(opts->lockAspectRatio);
|
||||
m_display->filter(opts->resampleVideo);
|
||||
if (m_display) {
|
||||
m_display->lockAspectRatio(opts->lockAspectRatio);
|
||||
m_display->filter(opts->resampleVideo);
|
||||
}
|
||||
|
||||
m_inputController.setScreensaverSuspendable(opts->suspendScreensaver);
|
||||
}
|
||||
|
@ -410,6 +409,22 @@ void Window::selectSave(bool temporary) {
|
|||
}
|
||||
}
|
||||
|
||||
void Window::selectState(bool load) {
|
||||
QStringList formats{"*.ss0", "*.ss1", "*.ss2", "*.ss3", "*.ss4", "*.ss5", "*.ss6", "*.ss7", "*.ss8", "*.ss9"};
|
||||
QString filter = tr("mGBA savestate files (%1)").arg(formats.join(QChar(' ')));
|
||||
if (load) {
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select savestate"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->loadState(filename);
|
||||
}
|
||||
} else {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select savestate"), filter);
|
||||
if (!filename.isEmpty()) {
|
||||
m_controller->saveState(filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::multiplayerChanged() {
|
||||
if (!m_controller) {
|
||||
return;
|
||||
|
@ -600,9 +615,7 @@ void Window::showEvent(QShowEvent* event) {
|
|||
enterFullScreen();
|
||||
m_fullscreenOnStart = false;
|
||||
}
|
||||
if (m_display) {
|
||||
reloadDisplayDriver();
|
||||
}
|
||||
reloadDisplayDriver();
|
||||
}
|
||||
|
||||
void Window::closeEvent(QCloseEvent* event) {
|
||||
|
@ -614,6 +627,7 @@ void Window::closeEvent(QCloseEvent* event) {
|
|||
m_config->setOption("width", VIDEO_HORIZONTAL_PIXELS * m_savedScale);
|
||||
}
|
||||
saveConfig();
|
||||
m_display.reset();
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
|
@ -745,6 +759,9 @@ void Window::gameStarted() {
|
|||
if (m_savedScale > 0) {
|
||||
resizeFrame(size * m_savedScale);
|
||||
}
|
||||
if (!m_display) {
|
||||
reloadDisplayDriver();
|
||||
}
|
||||
attachWidget(m_display.get());
|
||||
setMouseTracking(true);
|
||||
m_display->setMinimumSize(size);
|
||||
|
@ -830,7 +847,6 @@ void Window::gameStopped() {
|
|||
m_audioChannels->clear();
|
||||
|
||||
m_fpsTimer.stop();
|
||||
m_frameTimer.stop();
|
||||
m_focusCheck.stop();
|
||||
|
||||
emit paused(false);
|
||||
|
@ -960,19 +976,8 @@ void Window::mustRestart() {
|
|||
}
|
||||
|
||||
void Window::recordFrame() {
|
||||
if (m_frameList.isEmpty()) {
|
||||
m_frameList.append(1);
|
||||
} else {
|
||||
++m_frameList.back();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::delimitFrames() {
|
||||
if (m_frameList.size() >= FRAME_LIST_SIZE) {
|
||||
m_frameCounter -= m_frameList.takeAt(0);
|
||||
}
|
||||
m_frameCounter += m_frameList.back();
|
||||
m_frameList.append(0);
|
||||
m_frameList.append(m_frameTimer.nsecsElapsed());
|
||||
m_frameTimer.restart();
|
||||
}
|
||||
|
||||
void Window::showFPS() {
|
||||
|
@ -980,7 +985,12 @@ void Window::showFPS() {
|
|||
updateTitle();
|
||||
return;
|
||||
}
|
||||
float fps = m_frameCounter * 10000.f / (FRAME_LIST_INTERVAL * (m_frameList.size() - 1));
|
||||
qint64 total = 0;
|
||||
for (qint64 t : m_frameList) {
|
||||
total += t;
|
||||
}
|
||||
double fps = (m_frameList.size() * 1e10) / total;
|
||||
m_frameList.clear();
|
||||
fps = round(fps) / 10.f;
|
||||
updateTitle(fps);
|
||||
}
|
||||
|
@ -1113,6 +1123,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_platformActions.append(qMakePair(loadState, SUPPORT_GB | SUPPORT_GBA));
|
||||
addControlledAction(fileMenu, loadState, "loadState");
|
||||
|
||||
QAction* loadStateFile = new QAction(tr("Load state file..."), fileMenu);
|
||||
connect(loadStateFile, &QAction::triggered, [this]() { this->selectState(true); });
|
||||
m_gameActions.append(loadStateFile);
|
||||
m_nonMpActions.append(loadStateFile);
|
||||
addControlledAction(fileMenu, loadStateFile, "loadStateFile");
|
||||
|
||||
QAction* saveState = new QAction(tr("&Save state"), fileMenu);
|
||||
saveState->setShortcut(tr("Shift+F10"));
|
||||
connect(saveState, &QAction::triggered, [this]() { this->openStateWindow(LoadSave::SAVE); });
|
||||
|
@ -1121,6 +1137,12 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_platformActions.append(qMakePair(saveState, SUPPORT_GB | SUPPORT_GBA));
|
||||
addControlledAction(fileMenu, saveState, "saveState");
|
||||
|
||||
QAction* saveStateFile = new QAction(tr("Save state file..."), fileMenu);
|
||||
connect(saveStateFile, &QAction::triggered, [this]() { this->selectState(false); });
|
||||
m_gameActions.append(saveStateFile);
|
||||
m_nonMpActions.append(saveStateFile);
|
||||
addControlledAction(fileMenu, saveStateFile, "saveStateFile");
|
||||
|
||||
QMenu* quickLoadMenu = fileMenu->addMenu(tr("Quick load"));
|
||||
QMenu* quickSaveMenu = fileMenu->addMenu(tr("Quick save"));
|
||||
|
||||
|
@ -1424,7 +1446,9 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
ConfigOption* lockAspectRatio = m_config->addOption("lockAspectRatio");
|
||||
lockAspectRatio->addBoolean(tr("Lock aspect ratio"), avMenu);
|
||||
lockAspectRatio->connect([this](const QVariant& value) {
|
||||
m_display->lockAspectRatio(value.toBool());
|
||||
if (m_display) {
|
||||
m_display->lockAspectRatio(value.toBool());
|
||||
}
|
||||
if (m_controller) {
|
||||
m_screenWidget->setLockAspectRatio(value.toBool());
|
||||
}
|
||||
|
@ -1434,7 +1458,9 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
ConfigOption* lockIntegerScaling = m_config->addOption("lockIntegerScaling");
|
||||
lockIntegerScaling->addBoolean(tr("Force integer scaling"), avMenu);
|
||||
lockIntegerScaling->connect([this](const QVariant& value) {
|
||||
m_display->lockIntegerScaling(value.toBool());
|
||||
if (m_display) {
|
||||
m_display->lockIntegerScaling(value.toBool());
|
||||
}
|
||||
if (m_controller) {
|
||||
m_screenWidget->setLockIntegerScaling(value.toBool());
|
||||
}
|
||||
|
@ -1444,7 +1470,9 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
ConfigOption* resampleVideo = m_config->addOption("resampleVideo");
|
||||
resampleVideo->addBoolean(tr("Bilinear filtering"), avMenu);
|
||||
resampleVideo->connect([this](const QVariant& value) {
|
||||
m_display->filter(value.toBool());
|
||||
if (m_display) {
|
||||
m_display->filter(value.toBool());
|
||||
}
|
||||
}, this);
|
||||
m_config->updateOption("resampleVideo");
|
||||
|
||||
|
@ -1671,6 +1699,16 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
reloadConfig();
|
||||
}, this);
|
||||
|
||||
ConfigOption* volumeFf = m_config->addOption("fastForwardVolume");
|
||||
volumeFf->connect([this](const QVariant& value) {
|
||||
reloadConfig();
|
||||
}, this);
|
||||
|
||||
ConfigOption* muteFf = m_config->addOption("fastForwardMute");
|
||||
muteFf->connect([this](const QVariant& value) {
|
||||
reloadConfig();
|
||||
}, this);
|
||||
|
||||
ConfigOption* rewindEnable = m_config->addOption("rewindEnable");
|
||||
rewindEnable->connect([this](const QVariant& value) {
|
||||
reloadConfig();
|
||||
|
@ -1706,7 +1744,6 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
showFps->connect([this](const QVariant& value) {
|
||||
if (!value.toInt()) {
|
||||
m_fpsTimer.stop();
|
||||
m_frameTimer.stop();
|
||||
updateTitle();
|
||||
} else if (m_controller) {
|
||||
m_fpsTimer.start();
|
||||
|
@ -1810,7 +1847,7 @@ void Window::focusCheck() {
|
|||
void Window::updateFrame() {
|
||||
QSize size = m_controller->screenDimensions();
|
||||
QImage currentImage(reinterpret_cast<const uchar*>(m_controller->drawContext()), size.width(), size.height(),
|
||||
256 * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
|
||||
size.width() * BYTES_PER_PIXEL, QImage::Format_RGBX8888);
|
||||
QPixmap pixmap;
|
||||
pixmap.convertFromImage(currentImage);
|
||||
m_screenWidget->setPixmap(pixmap);
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <QAction>
|
||||
#include <QDateTime>
|
||||
#include <QElapsedTimer>
|
||||
#include <QList>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
@ -68,6 +69,7 @@ public slots:
|
|||
void addDirToLibrary();
|
||||
#endif
|
||||
void selectSave(bool temporary);
|
||||
void selectState(bool load);
|
||||
void selectPatch();
|
||||
void enterFullScreen();
|
||||
void exitFullScreen();
|
||||
|
@ -131,7 +133,6 @@ private slots:
|
|||
void mustRestart();
|
||||
|
||||
void recordFrame();
|
||||
void delimitFrames();
|
||||
void showFPS();
|
||||
void focusCheck();
|
||||
|
||||
|
@ -139,8 +140,6 @@ private slots:
|
|||
|
||||
private:
|
||||
static const int FPS_TIMER_INTERVAL = 2000;
|
||||
static const int FRAME_LIST_INTERVAL = 100;
|
||||
static const int FRAME_LIST_SIZE = 40;
|
||||
|
||||
void setupMenu(QMenuBar*);
|
||||
void openStateWindow(LoadSave);
|
||||
|
@ -186,10 +185,9 @@ private:
|
|||
QPixmap m_logo{":/res/medusa-bg.jpg"};
|
||||
ConfigController* m_config;
|
||||
InputController m_inputController;
|
||||
QList<int> m_frameList;
|
||||
int m_frameCounter = 0;
|
||||
QList<qint64> m_frameList;
|
||||
QElapsedTimer m_frameTimer;
|
||||
QTimer m_fpsTimer;
|
||||
QTimer m_frameTimer;
|
||||
QList<QString> m_mruFiles;
|
||||
QMenu* m_mruMenu = nullptr;
|
||||
QMenu* m_videoLayers;
|
||||
|
|
|
@ -947,13 +947,13 @@ void InputController::rebindKey(const QString& key) {
|
|||
}
|
||||
|
||||
void InputController::loadCamImage(const QString& path) {
|
||||
QMutexLocker locker(&m_image.mutex);
|
||||
m_image.image.load(path);
|
||||
m_image.resizedImage = QImage();
|
||||
m_image.outOfDate = true;
|
||||
setCamImage(QImage(path));
|
||||
}
|
||||
|
||||
void InputController::setCamImage(const QImage& image) {
|
||||
if (image.isNull()) {
|
||||
return;
|
||||
}
|
||||
QMutexLocker locker(&m_image.mutex);
|
||||
m_image.image = image;
|
||||
m_image.resizedImage = QImage();
|
||||
|
|
|
@ -50,4 +50,5 @@ add_custom_target(${BINARY_NAME}.nro ALL
|
|||
${ELF2NRO} ${BINARY_NAME}.elf ${BINARY_NAME}.nro --romfs=romfs.bin --nacp=control.nacp --icon="${CMAKE_CURRENT_SOURCE_DIR}/icon.jpg"
|
||||
DEPENDS ${BINARY_NAME}.elf control.nacp ${CMAKE_CURRENT_SOURCE_DIR}/icon.jpg romfs.bin)
|
||||
|
||||
install(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.nro DESTINATION . COMPONENT ${BINARY_NAME}-switch)
|
||||
|
|
|
@ -75,6 +75,13 @@ static GLuint tex;
|
|||
|
||||
static color_t* frameBuffer;
|
||||
static struct mAVStream stream;
|
||||
static struct mSwitchRumble {
|
||||
struct mRumble d;
|
||||
int up;
|
||||
int down;
|
||||
HidVibrationValue value;
|
||||
} rumble;
|
||||
static struct mRotationSource rotation = {0};
|
||||
static int audioBufferActive;
|
||||
static struct GBAStereoSample audioBuffer[N_BUFFERS][SAMPLES] __attribute__((__aligned__(0x1000)));
|
||||
static AudioOutBuffer audoutBuffer[N_BUFFERS];
|
||||
|
@ -82,6 +89,8 @@ static int enqueuedBuffers;
|
|||
static bool frameLimiter = true;
|
||||
static unsigned framecount = 0;
|
||||
static unsigned framecap = 10;
|
||||
static u32 vibrationDeviceHandles[4];
|
||||
static HidVibrationValue vibrationStop = { .freq_low = 160.f, .freq_high = 320.f };
|
||||
|
||||
static bool initEgl() {
|
||||
s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
|
@ -226,6 +235,8 @@ static void _setup(struct mGUIRunner* runner) {
|
|||
_mapKey(&runner->core->inputMap, AUTO_INPUT, KEY_R, GBA_KEY_R);
|
||||
|
||||
runner->core->setVideoBuffer(runner->core, frameBuffer, 256);
|
||||
runner->core->setPeripheral(runner->core, mPERIPH_RUMBLE, &rumble.d);
|
||||
runner->core->setPeripheral(runner->core, mPERIPH_ROTATION, &rotation);
|
||||
runner->core->setAVStream(runner->core, &stream);
|
||||
}
|
||||
|
||||
|
@ -237,6 +248,18 @@ static void _gameLoaded(struct mGUIRunner* runner) {
|
|||
blip_set_rates(runner->core->getAudioChannel(runner->core, 1), runner->core->frequency(runner->core), samplerate * ratio);
|
||||
|
||||
mCoreConfigGetUIntValue(&runner->config, "fastForwardCap", &framecap);
|
||||
|
||||
rumble.up = 0;
|
||||
rumble.down = 0;
|
||||
}
|
||||
|
||||
static void _gameUnloaded(struct mGUIRunner* runner) {
|
||||
HidVibrationValue values[4];
|
||||
memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
|
||||
memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
|
||||
memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
|
||||
memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
|
||||
hidSendVibrationValues(vibrationDeviceHandles, values, 4);
|
||||
}
|
||||
|
||||
static void _drawTex(struct mGUIRunner* runner, unsigned width, unsigned height, bool faded) {
|
||||
|
@ -299,6 +322,24 @@ static void _drawFrame(struct mGUIRunner* runner, bool faded) {
|
|||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
_drawTex(runner, width, height, faded);
|
||||
|
||||
HidVibrationValue values[4];
|
||||
if (rumble.up) {
|
||||
rumble.value.amp_low = rumble.up / (float) (rumble.up + rumble.down);
|
||||
rumble.value.amp_high = rumble.up / (float) (rumble.up + rumble.down);
|
||||
memcpy(&values[0], &rumble.value, sizeof(rumble.value));
|
||||
memcpy(&values[1], &rumble.value, sizeof(rumble.value));
|
||||
memcpy(&values[2], &rumble.value, sizeof(rumble.value));
|
||||
memcpy(&values[3], &rumble.value, sizeof(rumble.value));
|
||||
} else {
|
||||
memcpy(&values[0], &vibrationStop, sizeof(rumble.value));
|
||||
memcpy(&values[1], &vibrationStop, sizeof(rumble.value));
|
||||
memcpy(&values[2], &vibrationStop, sizeof(rumble.value));
|
||||
memcpy(&values[3], &vibrationStop, sizeof(rumble.value));
|
||||
}
|
||||
hidSendVibrationValues(vibrationDeviceHandles, values, 4);
|
||||
rumble.up = 0;
|
||||
rumble.down = 0;
|
||||
}
|
||||
|
||||
static void _drawScreenshot(struct mGUIRunner* runner, const color_t* pixels, unsigned width, unsigned height, bool faded) {
|
||||
|
@ -355,6 +396,36 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig
|
|||
++enqueuedBuffers;
|
||||
}
|
||||
|
||||
void _setRumble(struct mRumble* rumble, int enable) {
|
||||
struct mSwitchRumble* sr = (struct mSwitchRumble*) rumble;
|
||||
if (enable) {
|
||||
++sr->up;
|
||||
} else {
|
||||
++sr->down;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t _readTiltX(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
SixAxisSensorValues sixaxis;
|
||||
hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
|
||||
return sixaxis.accelerometer.x * 3e8f;
|
||||
}
|
||||
|
||||
int32_t _readTiltY(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
SixAxisSensorValues sixaxis;
|
||||
hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
|
||||
return sixaxis.accelerometer.y * -3e8f;
|
||||
}
|
||||
|
||||
int32_t _readGyroZ(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
SixAxisSensorValues sixaxis;
|
||||
hidSixAxisSensorValuesRead(&sixaxis, CONTROLLER_P1_AUTO, 1);
|
||||
return sixaxis.gyroscope.z * 1.1e9f;
|
||||
}
|
||||
|
||||
static int _batteryState(void) {
|
||||
u32 charge;
|
||||
int state = 0;
|
||||
|
@ -455,6 +526,24 @@ int main(int argc, char* argv[]) {
|
|||
glEnableVertexAttribArray(offsetLocation);
|
||||
glBindVertexArray(0);
|
||||
|
||||
rumble.d.setRumble = _setRumble;
|
||||
rumble.value.freq_low = 120.0;
|
||||
rumble.value.freq_high = 180.0;
|
||||
hidInitializeVibrationDevices(&vibrationDeviceHandles[0], 2, CONTROLLER_HANDHELD, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
|
||||
hidInitializeVibrationDevices(&vibrationDeviceHandles[2], 2, CONTROLLER_PLAYER_1, TYPE_HANDHELD | TYPE_JOYCON_PAIR);
|
||||
|
||||
u32 handles[4];
|
||||
hidGetSixAxisSensorHandles(&handles[0], 2, CONTROLLER_PLAYER_1, TYPE_JOYCON_PAIR);
|
||||
hidGetSixAxisSensorHandles(&handles[2], 1, CONTROLLER_PLAYER_1, TYPE_PROCONTROLLER);
|
||||
hidGetSixAxisSensorHandles(&handles[3], 1, CONTROLLER_HANDHELD, TYPE_HANDHELD);
|
||||
hidStartSixAxisSensor(handles[0]);
|
||||
hidStartSixAxisSensor(handles[1]);
|
||||
hidStartSixAxisSensor(handles[2]);
|
||||
hidStartSixAxisSensor(handles[3]);
|
||||
rotation.readTiltX = _readTiltX;
|
||||
rotation.readTiltY = _readTiltY;
|
||||
rotation.readGyroZ = _readGyroZ;
|
||||
|
||||
stream.videoDimensionsChanged = NULL;
|
||||
stream.postVideoFrame = NULL;
|
||||
stream.postAudioFrame = NULL;
|
||||
|
@ -528,6 +617,23 @@ int main(int argc, char* argv[]) {
|
|||
"10", "11", "12", "13", "14", "15",
|
||||
"20", "30"
|
||||
},
|
||||
.stateMappings = (const struct GUIVariant[]) {
|
||||
GUI_V_U(3),
|
||||
GUI_V_U(4),
|
||||
GUI_V_U(5),
|
||||
GUI_V_U(6),
|
||||
GUI_V_U(7),
|
||||
GUI_V_U(8),
|
||||
GUI_V_U(9),
|
||||
GUI_V_U(10),
|
||||
GUI_V_U(11),
|
||||
GUI_V_U(12),
|
||||
GUI_V_U(13),
|
||||
GUI_V_U(14),
|
||||
GUI_V_U(15),
|
||||
GUI_V_U(20),
|
||||
GUI_V_U(30),
|
||||
},
|
||||
.nStates = 15
|
||||
},
|
||||
},
|
||||
|
@ -535,11 +641,11 @@ int main(int argc, char* argv[]) {
|
|||
.setup = _setup,
|
||||
.teardown = NULL,
|
||||
.gameLoaded = _gameLoaded,
|
||||
.gameUnloaded = NULL,
|
||||
.gameUnloaded = _gameUnloaded,
|
||||
.prepareForFrame = _prepareForFrame,
|
||||
.drawFrame = _drawFrame,
|
||||
.drawScreenshot = _drawScreenshot,
|
||||
.paused = NULL,
|
||||
.paused = _gameUnloaded,
|
||||
.unpaused = _gameLoaded,
|
||||
.incrementScreenMode = NULL,
|
||||
.setFrameLimiter = _setFrameLimiter,
|
||||
|
@ -577,6 +683,11 @@ int main(int argc, char* argv[]) {
|
|||
glDeleteProgram(program);
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
|
||||
hidStopSixAxisSensor(handles[0]);
|
||||
hidStopSixAxisSensor(handles[1]);
|
||||
hidStopSixAxisSensor(handles[2]);
|
||||
hidStopSixAxisSensor(handles[3]);
|
||||
|
||||
psmExit();
|
||||
audoutExit();
|
||||
deinitEgl();
|
||||
|
|
|
@ -40,5 +40,7 @@ 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(TARGETS ${BINARY_NAME}.elf DESTINATION . COMPONENT ${BINARY_NAME}-dbg)
|
||||
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)
|
||||
|
|
|
@ -195,18 +195,18 @@ static void reconfigureScreen(struct mGUIRunner* runner) {
|
|||
break;
|
||||
}
|
||||
|
||||
free(framebuffer[0]);
|
||||
free(framebuffer[1]);
|
||||
|
||||
VIDEO_SetBlack(true);
|
||||
VIDEO_Configure(vmode);
|
||||
|
||||
free(framebuffer[0]);
|
||||
free(framebuffer[1]);
|
||||
|
||||
framebuffer[0] = SYS_AllocateFramebuffer(vmode);
|
||||
framebuffer[1] = SYS_AllocateFramebuffer(vmode);
|
||||
VIDEO_ClearFrameBuffer(vmode, framebuffer[0], COLOR_BLACK);
|
||||
VIDEO_ClearFrameBuffer(vmode, framebuffer[1], COLOR_BLACK);
|
||||
VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[0]), COLOR_BLACK);
|
||||
VIDEO_ClearFrameBuffer(vmode, MEM_K0_TO_K1(framebuffer[1]), COLOR_BLACK);
|
||||
|
||||
VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
|
||||
VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
|
||||
VIDEO_Flush();
|
||||
VIDEO_WaitVSync();
|
||||
if (vmode->viTVMode & VI_NON_INTERLACE) {
|
||||
|
@ -469,9 +469,49 @@ int main(int argc, char* argv[]) {
|
|||
"Bilinear (pixelated)",
|
||||
},
|
||||
.nStates = 3
|
||||
}
|
||||
},
|
||||
{
|
||||
.title = "Horizontal stretch",
|
||||
.data = "stretchWidth",
|
||||
.submenu = 0,
|
||||
.state = 7,
|
||||
.validStates = (const char*[]) {
|
||||
"1/2x", "0.6x", "1/3x", "0.7x", "1/4x", "0.8x", "0.9x", "1.0x"
|
||||
},
|
||||
.stateMappings = (const struct GUIVariant[]) {
|
||||
GUI_V_F(0.5f),
|
||||
GUI_V_F(0.6f),
|
||||
GUI_V_F(1.f / 3.f),
|
||||
GUI_V_F(0.7f),
|
||||
GUI_V_F(0.75f),
|
||||
GUI_V_F(0.8f),
|
||||
GUI_V_F(0.9f),
|
||||
GUI_V_F(1.0f),
|
||||
},
|
||||
.nStates = 8
|
||||
},
|
||||
{
|
||||
.title = "Vertical stretch",
|
||||
.data = "stretchHeight",
|
||||
.submenu = 0,
|
||||
.state = 6,
|
||||
.validStates = (const char*[]) {
|
||||
"1/2x", "0.6x", "1/3x", "0.7x", "1/4x", "0.8x", "0.9x", "1.0x"
|
||||
},
|
||||
.stateMappings = (const struct GUIVariant[]) {
|
||||
GUI_V_F(0.5f),
|
||||
GUI_V_F(0.6f),
|
||||
GUI_V_F(1.f / 3.f),
|
||||
GUI_V_F(0.7f),
|
||||
GUI_V_F(0.75f),
|
||||
GUI_V_F(0.8f),
|
||||
GUI_V_F(0.9f),
|
||||
GUI_V_F(1.0f),
|
||||
},
|
||||
.nStates = 8
|
||||
},
|
||||
},
|
||||
.nConfigExtra = 3,
|
||||
.nConfigExtra = 5,
|
||||
.setup = _setup,
|
||||
.teardown = 0,
|
||||
.gameLoaded = _gameLoaded,
|
||||
|
@ -517,6 +557,15 @@ int main(int argc, char* argv[]) {
|
|||
_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_LEFT, GUI_INPUT_LEFT);
|
||||
_mapKey(&runner.params.keyMap, CLASSIC_INPUT, WPAD_CLASSIC_BUTTON_RIGHT, GUI_INPUT_RIGHT);
|
||||
|
||||
|
||||
float stretch = 0;
|
||||
if (mCoreConfigGetFloatValue(&runner.config, "stretchWidth", &stretch)) {
|
||||
wStretch = fminf(1.0f, fmaxf(0.5f, stretch));
|
||||
}
|
||||
if (mCoreConfigGetFloatValue(&runner.config, "stretchHeight", &stretch)) {
|
||||
hStretch = fminf(1.0f, fmaxf(0.5f, stretch));
|
||||
}
|
||||
|
||||
if (argc > 1) {
|
||||
size_t i;
|
||||
for (i = 0; runner.keySources[i].id; ++i) {
|
||||
|
@ -595,7 +644,7 @@ static void _drawStart(void) {
|
|||
static void _drawEnd(void) {
|
||||
GX_CopyDisp(framebuffer[whichFb], GX_TRUE);
|
||||
GX_DrawDone();
|
||||
VIDEO_SetNextFramebuffer(framebuffer[whichFb]);
|
||||
VIDEO_SetNextFramebuffer(MEM_K0_TO_K1(framebuffer[whichFb]));
|
||||
VIDEO_Flush();
|
||||
whichFb = !whichFb;
|
||||
|
||||
|
@ -862,8 +911,8 @@ void _drawFrame(struct mGUIRunner* runner, bool faded) {
|
|||
GX_LoadTexObj(&rescaleTex, GX_TEXMAP0);
|
||||
}
|
||||
|
||||
int hfactor = vmode->fbWidth / (corew * wAdjust);
|
||||
int vfactor = vmode->efbHeight / (coreh * hAdjust);
|
||||
int hfactor = (vmode->fbWidth * wStretch) / (corew * wAdjust);
|
||||
int vfactor = (vmode->efbHeight * hStretch) / (coreh * hAdjust);
|
||||
if (hfactor > vfactor) {
|
||||
scaleFactor = vfactor;
|
||||
} else {
|
||||
|
|
After Width: | Height: | Size: 43 KiB |
|
@ -0,0 +1,140 @@
|
|||
#define AppName "${PROJECT_NAME}"
|
||||
#define AppName2 "${BINARY_NAME}"
|
||||
#define VerMajor ${LIB_VERSION_MAJOR}
|
||||
#define VerMinor ${LIB_VERSION_MINOR}
|
||||
#define VerRev ${LIB_VERSION_PATCH}
|
||||
#define VerBuild ${GIT_REV}
|
||||
#define Release ${IS_RELEASE}
|
||||
#define WinBits "${WIN_BITS}"
|
||||
#define VersionString "${VERSION_STRING}"
|
||||
#define CleanVersionString "${CLEAN_VERSION_STRING}"
|
||||
#define SetupDir "${SETUP_DIR}"
|
||||
#define BinDir "${BIN_DIR}"
|
||||
#define ResDir "${RES_DIR}"
|
||||
|
||||
#define FullVersion ParseVersion('{#AppName}.exe', VerMajor, VerMinor, VerRev, VerBuild)
|
||||
#define AppVer Str(VerMajor) + "." + Str(VerMinor) + "." + Str(VerRev)
|
||||
|
||||
[Setup]
|
||||
SourceDir={#BinDir}
|
||||
SetupIconFile={#SetupDir}\setup.ico
|
||||
WizardImageFile={#SetupDir}\wizard-image.bmp
|
||||
|
||||
AppName={#AppName}
|
||||
AppVersion={#AppVer}
|
||||
AppPublisher=Jeffrey Pfau
|
||||
AppPublisherURL=https://mgba.io
|
||||
AppSupportURL=https://mgba.io
|
||||
AppUpdatesURL=https://mgba.io
|
||||
AppReadmeFile={#BinDir}\README.html
|
||||
OutputDir=.\
|
||||
DefaultDirName={pf}\{#AppName}
|
||||
DefaultGroupName={#AppName}
|
||||
AllowNoIcons=yes
|
||||
DirExistsWarning=no
|
||||
ChangesAssociations=True
|
||||
AppendDefaultDirName=False
|
||||
UninstallDisplayIcon={app}\{#AppName}.exe
|
||||
MinVersion=0,6.0
|
||||
AlwaysShowDirOnReadyPage=True
|
||||
UsePreviousSetupType=True
|
||||
UsePreviousTasks=True
|
||||
AlwaysShowGroupOnReadyPage=True
|
||||
LicenseFile={#BinDir}\LICENSE.txt
|
||||
#if Release
|
||||
#define IsRelease = 'yes'
|
||||
AppVerName={#AppName} {#AppVer}
|
||||
#else
|
||||
#define IsRelease = 'no'
|
||||
AppVerName={#AppName} {#VersionString} (Development build)
|
||||
#endif
|
||||
#if '{#WinBits}' == '64'
|
||||
ArchitecturesInstallIn64BitMode=x64
|
||||
ArchitecturesAllows=x64
|
||||
#endif
|
||||
OutputBaseFilename={#AppName}-setup-{#CleanVersionString}-win{#WinBits}
|
||||
UsePreviousLanguage=False
|
||||
DisableWelcomePage=False
|
||||
VersionInfoDescription={#AppName} is an open-source Game Boy Advance emulator
|
||||
VersionInfoCopyright=© 2013–2018 Jeffrey Pfau
|
||||
VersionInfoProductName={#AppName}
|
||||
VersionInfoVersion={#AppVer}
|
||||
Compression=lzma2/ultra64
|
||||
SolidCompression=True
|
||||
VersionInfoTextVersion={#AppVer}
|
||||
VersionInfoProductVersion={#AppVer}
|
||||
VersionInfoProductTextVersion={#AppVer}
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||
Name: "german"; MessagesFile: "compiler:Languages\German.isl"
|
||||
Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl"
|
||||
Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
Name: "gbfileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Game Boy}"; GroupDescription: "{cm:FileAssoc}"
|
||||
Name: "gbcfileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Game Boy Color}"; GroupDescription: "{cm:FileAssoc}"
|
||||
Name: "sgbfileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Super Game Boy}"; GroupDescription: "{cm:FileAssoc}"
|
||||
Name: "gbafileassoc"; Description: "{cm:AssocFileExtension,{#AppName},Game Boy Advance}"; GroupDescription: "{cm:FileAssoc}"
|
||||
|
||||
[Files]
|
||||
Source: "{#BinDir}\qt\{#AppName}.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#BinDir}\sdl\{#AppName2}-sdl.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#BinDir}\CHANGES.txt"; DestDir: "{app}\"; Flags: ignoreversion isreadme
|
||||
Source: "{#BinDir}\LICENSE.txt"; DestDir: "{app}\"; Flags: ignoreversion
|
||||
Source: "{#ResDir}\nointro.dat"; DestDir: "{app}\"; Flags: ignoreversion
|
||||
Source: "{#BinDir}\README.html"; DestDir: "{app}\"; Flags: ignoreversion isreadme; Languages: english italian spanish
|
||||
Source: "{#BinDir}\README_DE.html"; DestDir: "{app}\"; DestName: "LIESMICH.html"; Flags: ignoreversion isreadme; Languages: german
|
||||
Source: "{#ResDir}\shaders\*"; DestDir: "{app}\shaders\"; Flags: ignoreversion recursesubdirs
|
||||
Source: "{#ResDir}\licenses\*"; DestDir: "{app}\licenses\"; Flags: ignoreversion recursesubdirs
|
||||
|
||||
[Icons]
|
||||
Name: "{commonstartmenu}\{#AppName}"; Filename: "{app}\{#AppName}.exe"
|
||||
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppName}.exe"; Tasks: desktopicon
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#AppName}.exe"; Description: "{cm:LaunchProgram,{#AppName}}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
[Dirs]
|
||||
Name: "{app}"
|
||||
|
||||
[CustomMessages]
|
||||
english.FileAssoc=Register file associations
|
||||
french.FileAssoc=Register file associations
|
||||
italian.FileAssoc=Register file associations
|
||||
spanish.FileAssoc=Register file associations
|
||||
german.FileAssoc=Dateierweiterungen registrieren
|
||||
|
||||
[Registry]
|
||||
Root: HKCR; Subkey: ".gb"; ValueType: string; ValueName: ""; ValueData: "Game Boy ROM"; Flags: uninsdeletevalue; Tasks: gbfileassoc
|
||||
Root: HKCR; Subkey: ".gb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: gbfileassoc
|
||||
Root: HKCR; Subkey: ".gb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: gbfileassoc
|
||||
Root: HKCR; Subkey: ".gbc"; ValueType: string; ValueName: ""; ValueData: "Game Boy Color ROM"; Flags: uninsdeletevalue; Tasks: gbcfileassoc
|
||||
Root: HKCR; Subkey: ".gbc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: gbcfileassoc
|
||||
Root: HKCR; Subkey: ".gbc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: gbcfileassoc
|
||||
Root: HKCR; Subkey: ".sgb"; ValueType: string; ValueName: ""; ValueData: "Super Game Boy ROM"; Flags: uninsdeletevalue; Tasks: sgbfileassoc
|
||||
Root: HKCR; Subkey: ".sgb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: sgbfileassoc
|
||||
Root: HKCR; Subkey: ".sgb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: sgbfileassoc
|
||||
Root: HKCR; Subkey: ".gba"; ValueType: string; ValueName: ""; ValueData: "Game Boy Advance ROM"; Flags: uninsdeletevalue; Tasks: gbafileassoc
|
||||
Root: HKCR; Subkey: ".gba\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#AppName}.exe,0"; Tasks: gbafileassoc
|
||||
Root: HKCR; Subkey: ".gba\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#AppName}.exe"" ""%1"""; Tasks: gbafileassoc
|
||||
|
||||
[Code]
|
||||
var
|
||||
noReleaseWarning: String;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
if ExpandConstant('{#IsRelease}') = 'no' then
|
||||
begin
|
||||
if ExpandConstant('{language}') = 'english' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.';
|
||||
if ExpandConstant('{language}') = 'french' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.';
|
||||
if ExpandConstant('{language}') = 'italian' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.';
|
||||
if ExpandConstant('{language}') = 'spanish' then noReleaseWarning := 'You are about to install a development build of {#AppName}.' + #13#10#13#10 + 'Development builds may contain bugs that are not yet discovered. Please report any issues you can find to the GitHub project page.';
|
||||
if ExpandConstant('{language}') = 'german' then noReleaseWarning := 'Sie möchten eine Entwicklerversion von {#AppName} installieren.' + #13#10#13#10 + 'Entwicklerversionen können bislang noch nicht endeckte Fehler beinhalten. Bitte melden Sie alle Fehler, die Sie finden können, auf der GitHub-Projektseite.';
|
||||
MsgBox(noReleaseWarning, mbInformation, MB_OK);
|
||||
end;
|
||||
end;
|
||||
end.
|
After Width: | Height: | Size: 151 KiB |