Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2019-06-28 16:54:30 -07:00
commit 3d27d1d7fe
62 changed files with 1805 additions and 1082 deletions

View File

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

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

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

View File

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

View File

@ -117,6 +117,8 @@ struct GB {
bool earlyExit;
struct mTimingEvent eiPending;
unsigned doubleSpeed;
bool allowOpposingDirections;
};
struct GBCartridge {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -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=© 20132018 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB