mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
a012157896
|
@ -16,9 +16,10 @@ env:
|
|||
- DOCKER_TAG=windows:w32
|
||||
- DOCKER_TAG=windows:w64
|
||||
|
||||
matrix:
|
||||
jobs:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode12.2
|
||||
compiler: clang
|
||||
env: DOCKER_TAG=
|
||||
|
||||
|
|
14
CHANGES
14
CHANGES
|
@ -40,8 +40,10 @@ Features:
|
|||
- Separate overrides for GBC games that can also run on SGB or regular GB
|
||||
- Game Boy Player features can be enabled by default for all compatible games
|
||||
- Frame viewer support for Game Boy
|
||||
- Bug report tool for gathering information helpful for reporting bugs
|
||||
- Mute option in homebrew ports
|
||||
- Status indicators for fast-forward and mute in homebrew ports
|
||||
- VBA bug compatibility mode for ROM hacks that don't work on real hardware
|
||||
- Read-only support for MBC6 flash memory
|
||||
- New unlicensed GB mappers: Pokémon Jade/Diamond, BBD, and Hitek
|
||||
- Stack tracing tools in ARM debugger (by ahigerd)
|
||||
|
@ -54,6 +56,7 @@ Emulation fixes:
|
|||
- ARM: Fix ALU reading PC after shifting
|
||||
- ARM: Fix STR storing PC after address calculation
|
||||
- ARM: Fix Addressing mode 1 shifter on rs == pc (fixes mgba.io/i/1926)
|
||||
- ARM: Fix long multiply-and-accumulate register write order (fixes mgba.io/1/1956)
|
||||
- GB: Partially fix timing for skipped BIOS
|
||||
- GB: Downgrade DMG-only ROMs from CGB mode even without boot ROM
|
||||
- GB Audio: Fix serializing sweep time
|
||||
|
@ -76,6 +79,7 @@ Emulation fixes:
|
|||
- GBA Memory: Improve robustness of Matrix memory support
|
||||
- GBA Memory: Mark Famicom Mini games 22 through 28 as non-mirroring
|
||||
- GBA Memory: Return correct byte for odd ROM open bus addresses
|
||||
- GBA Serialize: Fix alignment check when loading states
|
||||
- GBA SIO: Fix copying Normal mode transfer values
|
||||
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
|
||||
- GBA SIO: Fix deseralizing SIO registers
|
||||
|
@ -110,6 +114,10 @@ Other fixes:
|
|||
- Qt: Fix cancelling pausing before the frame ends
|
||||
- Qt: Fix gamepad event dispatching (fixes mgba.io/i/1922)
|
||||
- Qt: Pre-attach GDB stub when launching with -g (fixes mgba.io/i/1950)
|
||||
- Qt: Fix crash when editing shortcuts with none selected (fixes mgba.io/i/1964)
|
||||
- Qt: Fix crashing when no OpenGL context can be obtained
|
||||
- Qt: Fix issues with I/O viewer not properly synchronizing state
|
||||
- Qt: Fix loading a new game crashing on Wayland (fixes mgba.io/i/1992)
|
||||
- SM83: Simplify register pair access on big endian
|
||||
- SM83: Disassemble STOP as one byte
|
||||
- Wii: Fix crash on unloading irregularly sized GBA ROMs
|
||||
|
@ -124,12 +132,16 @@ Misc:
|
|||
- GB I/O: Implement preliminary support for PCM12/PCM34 (closes mgba.io/i/1468)
|
||||
- GBA: Allow pausing event loop while CPU is blocked
|
||||
- GBA BIOS: Division by zero should emit a FATAL error
|
||||
- GBA Cheats: Allow unlimited ROM patch-type codes per set
|
||||
- GBA Video: Convert OpenGL VRAM texture to integer
|
||||
- GBA Video: Skip attempting to render offscreen sprites in OpenGL
|
||||
- GBA Video: New GL palette approach, no more batch splitting on palette edits
|
||||
- GBA Video: Avoid integer division using reciprocal tricks
|
||||
- Debugger: Keep track of global cycle count
|
||||
- FFmpeg: Add looping option for GIF/APNG
|
||||
- mGUI: Show battery percentage
|
||||
- mGUI: Skip second scan loop when possible
|
||||
- mGUI: Improve loading speed (fixes mgba.io/i/1957)
|
||||
- Qt: Renderer can be changed while a game is running
|
||||
- Qt: Add hex index to palette view
|
||||
- Qt: Add transformation matrix info to sprite view
|
||||
|
@ -138,6 +150,8 @@ Misc:
|
|||
- Qt: Window title updates can be disabled (closes mgba.io/i/1912)
|
||||
- Qt: Redo OpenGL context thread handling (fixes mgba.io/i/1724)
|
||||
- Qt: Discard additional frame draws if waiting fails
|
||||
- Qt: Unify monospace font usage
|
||||
- SDL: Fall back to sw blit if OpenGL init fails
|
||||
- Util: Reset vector size on deinit
|
||||
- VFS: Change semantics of VFile.sync on mapped files (fixes mgba.io/i/1730)
|
||||
|
||||
|
|
|
@ -497,11 +497,11 @@ set(USE_CMOCKA ${BUILD_SUITE})
|
|||
if(DEFINED VCPKG_TARGET_TRIPLET)
|
||||
find_feature(USE_FFMPEG "FFMPEG")
|
||||
if(FFMPEG_FOUND)
|
||||
set(USE_LIBAVRESAMPLE OFF)
|
||||
set(USE_LIBSWRESAMPLE ON)
|
||||
set(LIBAVRESAMPLE_FOUND OFF)
|
||||
set(LIBSWRESAMPLE_FOUND ON)
|
||||
endif()
|
||||
else()
|
||||
find_feature(USE_FFMPEG "libavcodec;libavfilter;libavformat;libavutil;libswscale")
|
||||
find_feature(USE_FFMPEG "libavcodec;libavfilter;libavformat;libavutil;libswscale;libswresample|libavresample")
|
||||
endif()
|
||||
find_feature(USE_ZLIB "ZLIB")
|
||||
find_feature(USE_MINIZIP "minizip")
|
||||
|
@ -509,17 +509,10 @@ find_feature(USE_PNG "PNG")
|
|||
find_feature(USE_LIBZIP "libzip")
|
||||
find_feature(USE_EPOXY "epoxy")
|
||||
find_feature(USE_CMOCKA "cmocka")
|
||||
find_feature(USE_SQLITE3 "sqlite3")
|
||||
find_feature(USE_SQLITE3 "SQLite3|sqlite3")
|
||||
find_feature(USE_ELF "libelf")
|
||||
find_feature(ENABLE_PYTHON "PythonLibs")
|
||||
|
||||
if(USE_FFMPEG AND NOT DEFINED VCPKG_TARGET_TRIPLET)
|
||||
set(USE_LIBAVRESAMPLE ON)
|
||||
set(USE_LIBSWRESAMPLE ON)
|
||||
find_feature(USE_LIBAVRESAMPLE "libavresample")
|
||||
find_feature(USE_LIBSWRESAMPLE "libswresample")
|
||||
endif()
|
||||
|
||||
# Features
|
||||
add_subdirectory(src/debugger)
|
||||
add_subdirectory(src/feature)
|
||||
|
@ -558,7 +551,7 @@ source_group("Debugger" FILES ${DEBUGGER_SRC})
|
|||
|
||||
if(USE_FFMPEG)
|
||||
list(APPEND FEATURES FFMPEG)
|
||||
if(USE_LIBSWRESAMPLE)
|
||||
if(LIBSWRESAMPLE_FOUND)
|
||||
list(APPEND FEATURES LIBSWRESAMPLE)
|
||||
else()
|
||||
list(APPEND FEATURES LIBAVRESAMPLE)
|
||||
|
@ -580,7 +573,7 @@ if(USE_FFMPEG)
|
|||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavcodec${LIBAVCODEC_VERSION_MAJOR}|libavcodec-extra-${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg${LIBAVCODEC_VERSION_MAJOR}|libavcodec-ffmpeg-extra${LIBAVCODEC_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavfilter${LIBAVFILTER_VERSION_MAJOR}|libavfilter-ffmpeg${LIBAVFILTER_VERSION_MAJOR}")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libavformat${LIBAVFORMAT_VERSION_MAJOR}|libavformat-ffmpeg${LIBAVFORMAT_VERSION_MAJOR}")
|
||||
if(USE_LIBSWRESAMPLE)
|
||||
if(LIBSWRESAMPLE_FOUND)
|
||||
string(REGEX MATCH "^[0-9]+" LIBSWRESAMPLE_VERSION_MAJOR ${libswresample_VERSION})
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libswresample${LIBSWRESAMPLE_VERSION_MAJOR}|libswresample-ffmpeg${LIBSWRESAMPLE_VERSION_MAJOR}")
|
||||
else()
|
||||
|
@ -698,11 +691,13 @@ elseif(USE_MINIZIP)
|
|||
elseif(USE_ZLIB)
|
||||
list(APPEND VFS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/util/vfs/vfs-zip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/ioapi.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/unzip.c)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/unzip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/zip.c)
|
||||
if(NOT MSVC)
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/ioapi.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/unzip.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/zlib/contrib/minizip/zip.c
|
||||
PROPERTIES COMPILE_FLAGS "-Wno-unused-parameter -Wno-implicit-function-declaration")
|
||||
endif()
|
||||
endif()
|
||||
|
@ -750,7 +745,7 @@ elseif(BUILD_GLES2)
|
|||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libgles2")
|
||||
endif()
|
||||
|
||||
if(WIN32 AND NOT SKIP_LIBRARY AND NOT USE_EPOXY)
|
||||
if(WIN32 AND NOT (LIBMGBA_ONLY OR SKIP_LIBRARY OR USE_EPOXY))
|
||||
message(FATAL_ERROR "Windows requires epoxy module!")
|
||||
endif()
|
||||
|
||||
|
@ -1018,26 +1013,6 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/mgba DESTINATION ${CMAKE_I
|
|||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/mgba-util DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT ${BINARY_NAME}-dev FILES_MATCHING PATTERN "*.h")
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/include/mgba/flags.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mgba COMPONENT ${BINARY_NAME}-dev)
|
||||
|
||||
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(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/licenses/blip_buf.txt DESTINATION ${CMAKE_INSTALL_DOCDIR}/licenses COMPONENT ${BINARY_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/res/licenses/inih.txt DESTINATION ${CMAKE_INSTALL_DOCDIR}/licenses COMPONENT ${BINARY_NAME})
|
||||
|
@ -1069,6 +1044,25 @@ else()
|
|||
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)
|
||||
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" setup.iss)
|
||||
set_source_files_properties(setup.iss PROPERTIES GENERATED ON)
|
||||
|
||||
if(INSTALLER_NAME)
|
||||
set(INSTALLER_TARGET "${INSTALLER_NAME}.exe")
|
||||
set(ISCC_FLAGS "/F${INSTALLER_NAME}")
|
||||
|
@ -1078,15 +1072,14 @@ else()
|
|||
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)
|
||||
DEPENDS ${BINARY_NAME}-qt ${BINARY_NAME}-sdl setup.iss 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)
|
||||
DEPENDS ${BINARY_NAME}-qt ${BINARY_NAME}-sdl setup.iss CHANGES LICENSE)
|
||||
endif()
|
||||
if(ISCC)
|
||||
add_custom_target(installer ALL DEPENDS ${INSTALLER_TARGET})
|
||||
|
|
15
README.md
15
README.md
|
@ -181,13 +181,7 @@ Note that you should not do a `make install` on macOS, as it will not work prope
|
|||
|
||||
To build on Windows for development, using MSYS2 is recommended. Follow the installation steps found on their [website](https://msys2.github.io). Make sure you're running the 32-bit version ("MSYS2 MinGW 32-bit") (or the 64-bit version "MSYS2 MinGW 64-bit" if you want to build for x86_64) and run this additional command (including the braces) to install the needed dependencies (please note that this involves downloading over 1100MiB of packages, so it will take a long time):
|
||||
|
||||
For x86 (32 bit) builds:
|
||||
|
||||
pacman -Sy --needed base-devel git mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
For x86_64 (64 bit) builds:
|
||||
|
||||
pacman -Sy --needed base-devel git mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkgconf,qt5,SDL2,ntldd-git}
|
||||
|
||||
Check out the source code by running this command:
|
||||
|
||||
|
@ -195,11 +189,10 @@ Check out the source code by running this command:
|
|||
|
||||
Then finally build it by running these commands:
|
||||
|
||||
cd medusa
|
||||
mkdir build
|
||||
cd build
|
||||
mkdir -p medusa/build
|
||||
cd medusa/build
|
||||
cmake .. -G "MSYS Makefiles"
|
||||
make
|
||||
make -j$(nproc --ignore=1)
|
||||
|
||||
Please note that this build of medusa for Windows is not suitable for distribution, due to the scattering of DLLs it needs to run, but is perfect for development. However, if distributing such a build is desired (e.g. for testing on machines that don't have the MSYS2 environment installed), running `cpack -G ZIP` will prepare a zip file with all of the necessary DLLs.
|
||||
|
||||
|
|
15
README_DE.md
15
README_DE.md
|
@ -163,13 +163,7 @@ Bitte beachte, dass Du unter macOS nicht `make install` verwenden solltest, da d
|
|||
|
||||
Um mGBA auf Windows zu kompilieren, wird MSYS2 empfohlen. Befolge die Installationsschritte auf der [MSYS2-Website](https://msys2.github.io). Stelle sicher, dass Du die 32-Bit-Version ("MSYS2 MinGW 32-bit") (oder die 64-Bit-Version "MSYS2 MinGW 64-bit", wenn Du mGBA für x86_64 kompilieren willst) verwendest und führe folgendes Kommando (einschließlich der Klammern) aus, um alle benötigten Abhängigkeiten zu installieren. Bitte beachte, dass dafür über 1100MiB an Paketen heruntergeladen werden, was eine Weile dauern kann:
|
||||
|
||||
Für x86 (32 Bit):
|
||||
|
||||
pacman -Sy --needed base-devel git mingw-w64-i686-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
|
||||
Für x86_64 (64 Bit):
|
||||
|
||||
pacman -Sy --needed base-devel git mingw-w64-x86_64-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkg-config,qt5,SDL2,ntldd-git}
|
||||
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkgconf,qt5,SDL2,ntldd-git}
|
||||
|
||||
Lade den aktuellen mGBA-Quellcode mithilfe des folgenden Kommandos herunter:
|
||||
|
||||
|
@ -177,11 +171,10 @@ Lade den aktuellen mGBA-Quellcode mithilfe des folgenden Kommandos herunter:
|
|||
|
||||
Abschließend wird mGBA über folgende Kommandos kompiliert:
|
||||
|
||||
cd mgba
|
||||
mkdir build
|
||||
cd build
|
||||
mkdir -p mgba/build
|
||||
cd mgba/build
|
||||
cmake .. -G "MSYS Makefiles"
|
||||
make
|
||||
make -j$(nproc --ignore=1)
|
||||
|
||||
Bitte beachte, dass mGBA für Windows aufgrund der Vielzahl an benötigten DLLs nicht für die weitere Verteilung geeignet ist, wenn es auf diese Weise gebaut wurde. Es ist jedoch perfekt für Entwickler geeignet. Soll mGBA dennoch weiter verteilt werden (beispielsweise zu Testzwecken auf Systemen, auf denen keine MSYS2-Umgebung installiert ist), kann mithilfe des Befehls `cpack -G ZIP` ein ZIP-Archiv mit allen benötigten DLLs erstellt werden.
|
||||
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
mGBA
|
||||
====
|
||||
|
||||
mGBA es un emulador para juegos de Game Boy Advance. Su objetivo es ser más rápido y más preciso que muchos emuladores de Game Boy Advance existentes, además de añadir funciones que otros emuladores no tienen. También es compatible con juegos de Game Boy y Game Boy Color.
|
||||
|
||||
Las noticias actualizadas y las descargas se encuentran en [mgba.io](https://mgba.io/).
|
||||
|
||||
[![Estado de compilación](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)](https://travis-ci.org/mgba-emu/mgba)
|
||||
|
||||
Características
|
||||
--------
|
||||
|
||||
- Soporte de hardware Game Boy Advance altamente preciso[<sup>[1]</sup>](#missing).
|
||||
- Soporte de hardware Game Boy/Game Boy Color.
|
||||
- Emulación rápida. Corre a velocidad completa en hardware de gama baja, como los netbooks.
|
||||
- Interfaz gráfica en SDL y Qt.
|
||||
- Soporte para cable de enlace (link cable) local (en la misma computadora).
|
||||
- Detección de tipos de guardado, incluso para tamaños de memoria flash[<sup>[2]</sup>](#flashdetect).
|
||||
- Soporte para cartuchos con sensores de movimiento y vibración (solo usable con mandos).
|
||||
- Soporte para reloj en tiempo real, incluso sin configuración.
|
||||
- Soporte para sensor solar, para juegos Boktai.
|
||||
- Soporta la Cámara y la Impresora Game Boy.
|
||||
- Implementación interna de BIOS, y opción para usar una BIOS externa.
|
||||
- Modo turbo/avance rápido al mantener Tab presionado.
|
||||
- Retroceder al presionar "`".
|
||||
- Salto de cuadros de hasta 10 cuadros por vez.
|
||||
- Captura de pantalla (pantallazo).
|
||||
- Soporta códigos de truco.
|
||||
- 9 espacios para estados de guardado. Estos tambien pueden ser vistos como pantallazos.
|
||||
- Grabación de video, GIF, WebP, y APNG.
|
||||
- Soporte para e-Reader.
|
||||
- Controles modificables para teclado y mandos.
|
||||
- Cargar desde archivos ZIP y 7z.
|
||||
- Soporta parches IPS, UPS y BPS.
|
||||
- Depuración de juegos a través de una interfaz de línea de comandos y soporte remoto GDB, compatible con IDA Pro.
|
||||
- Retroceso configurable.
|
||||
- Soporte para cargar y exportar instantáneas de GameShark y Action Replay.
|
||||
- Núcleos disponibles para RetroArch/Libretro y OpenEmu.
|
||||
- Otras cosas más pequeñas.
|
||||
|
||||
#### Mappers (controladores de memoria) soportados
|
||||
|
||||
Estos mappers tienen soporte completo:
|
||||
|
||||
- MBC1
|
||||
- MBC1M
|
||||
- MBC2
|
||||
- MBC3
|
||||
- MBC3+RTC
|
||||
- MBC5
|
||||
- MBC5+Rumble
|
||||
- MBC7
|
||||
- Wisdom Tree (sin licencia)
|
||||
- Pokémon Jade/Diamond (sin licencia)
|
||||
- BBD (sin licencia, similar a MBC5)
|
||||
- Hitek (sin licencia, similar a MBC5)
|
||||
|
||||
Estos mappers tienen soporte parcial:
|
||||
|
||||
- MBC6 (sin soporte para escribir a la memoria flash)
|
||||
- MMM01
|
||||
- Pocket Cam
|
||||
- TAMA5 (sin soporte para RTC)
|
||||
- HuC-1 (sin soporte para IR)
|
||||
- HuC-3 (sin soporte para RTC e IR)
|
||||
|
||||
### Características planeadas
|
||||
|
||||
- Soporte para cable de enlace por red.
|
||||
- Soporte para cable de enlace por Joybus para Dolphin.
|
||||
- Mezcla de audio MP2k, para mayor calidad de sonido.
|
||||
- Soporte de regrabación para speedruns asistidos por herramientas (TAS).
|
||||
- Soporte de Lua para prog.
|
||||
- Un completo paquete de depuración.
|
||||
- Compatibilidad con adaptadores inalámbricos.
|
||||
|
||||
Plataformas soportadas
|
||||
-------------------
|
||||
|
||||
- Windows Vista o más reciente
|
||||
- OS X 10.8 (Mountain Lion)[<sup>[3]</sup>](#osxver) o más reciente
|
||||
- Linux
|
||||
- FreeBSD
|
||||
- Nintendo 3DS
|
||||
- Nintendo Switch
|
||||
- Wii
|
||||
- PlayStation Vita
|
||||
|
||||
Otras plataformas Unix-like, como OpenBSD, funcionan también, pero no han sido probadas.
|
||||
|
||||
### Requisitos de sistema
|
||||
|
||||
Los requisitos son mínimos. Cualquier computadora que pueda ejecutar Windows Vista o más reciente debería ser capaz de emular. También se requiere soporte para OpenGL 1.1 o más reciente, con OpenGL 3.2 o más reciente para los shaders y las funciones avanzadas.
|
||||
|
||||
Descargas
|
||||
---------
|
||||
|
||||
Las descargas se pueden encontrar en la página web oficial, en la sección [Descargas][downloads]. El código fuente se puede encontrar en [GitHub][source].
|
||||
|
||||
Controles
|
||||
--------
|
||||
|
||||
Los controles son configurables en el menú de configuración. Many game controllers should be automatically mapped by default. The default keyboard controls are as follows:
|
||||
|
||||
- **A**: X
|
||||
- **B**: Z
|
||||
- **L**: A
|
||||
- **R**: S
|
||||
- **Start**: Entrar
|
||||
- **Select**: Retroceso
|
||||
|
||||
Compilar
|
||||
---------
|
||||
|
||||
La compilación requiere el uso de CMake 3.1 o más reciente. GCC y Clang funcionan para compilar mGBA, pero Visual Studio 2013 y posteriores no funcionan. El soporte para Visual Studio 2015 y más recientes llegará pronto.
|
||||
|
||||
#### Compilación por Docker
|
||||
|
||||
Recomendamos usar Docker para compilar en la mayoría de las plataformas. Proporcionamos varias imágenes Docker que contienen la cadena de herramientas y las dependencias necesarias para compilar mGBA a través de varias plataformas.
|
||||
|
||||
Para usar una imagen Docker para compilar mGBA, ejecuta este comando mientras estés en el directorio donde hayas desplegado (checkout) el código fuente de mGBA:
|
||||
|
||||
docker run --rm -t -v $PWD:/home/mgba/src mgba/windows:w32
|
||||
|
||||
Esto producirá un directorio `build-win32` con los ejecutables compilados. Reemplaza `mgba/windows:w32` con otro nombre de imagen Docker para otras plataformas, lo cual creará un directorio correspondiente. Las siguientes imágenes están disponibles en Docker Hub:
|
||||
|
||||
- mgba/3ds
|
||||
- mgba/switch
|
||||
- mgba/ubuntu:xenial
|
||||
- mgba/ubuntu:bionic
|
||||
- mgba/ubuntu:focal
|
||||
- mgba/ubuntu:groovy
|
||||
- mgba/vita
|
||||
- mgba/wii
|
||||
- mgba/windows:w32
|
||||
- mgba/windows:w64
|
||||
|
||||
#### Compilación en *nix
|
||||
|
||||
Si quieres usar CMake para compilar mGBA en un sistema Unix-like, recomendamos los siguientes comandos:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr ..
|
||||
make
|
||||
sudo make install
|
||||
|
||||
Esto compilará e instalará mGBA en `/usr/bin` y `/usr/lib`. Las dependencias que estén instaladas serán detectadas automáticamente, y las características que estén desactivadas si las dependencias no se encuentran serán mostradas después de que el comando `cmake` muestre las advertencias.
|
||||
|
||||
Si estás en macOS, los pasos son un poco diferentes. Asumiendo que usas el gestor de paquetes Homebrew, los comandos recomendados para obtener las dependencias y compilar mGBA son:
|
||||
|
||||
brew install cmake ffmpeg libzip qt5 sdl2 libedit pkg-config
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
|
||||
make
|
||||
|
||||
Toma nota de que no debes usar `make install` en macOS, ya que no funcionará correctamente.
|
||||
|
||||
#### Compilación en Windows para desarrolladores
|
||||
|
||||
##### MSYS2
|
||||
|
||||
Para desarrollar en Windows, recomendamos MSYS2. Sigue las instrucciones en su [sitio web](https://msys2.github.io). Asegúrate de que estés ejecutando la versión de 32 bits ("MSYS2 MinGW 32-bit") (o la versión de 64 bits "MSYS2 MinGW 64-bit" si quieres compilar para x86_64) y ejecuta estos comandos adicionales para instalar las dependencias necesarias (toma nota de que esto descargará más de 1100 MB en paquetes, así que puede demorarse un poco):
|
||||
|
||||
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkgconf,qt5,SDL2,ntldd-git}
|
||||
|
||||
Despliega (haz check out en) el código fuente ejecutando este comando:
|
||||
|
||||
git clone https://github.com/mgba-emu/mgba.git
|
||||
|
||||
Luego, compílalo usando estos comandos:
|
||||
|
||||
mkdir -p mgba/build
|
||||
cd mgba/build
|
||||
cmake .. -G "MSYS Makefiles"
|
||||
make -j$(nproc --ignore=1)
|
||||
|
||||
Ten en cuenta de que esta versión de mGBA para Windows no es adecuada para distribuirse, debido a la dispersión de las DLL que necesita para funcionar, pero es perfecta para el desarrollo. Sin embargo, si quieres distribuir tal compilación (por ejemplo, para pruebas en máquinas que no tienen el entorno MSYS2 instalado), al ejecutar `cpack -G ZIP` se preparará un archivo zip con todas las DLLs necesarias.
|
||||
|
||||
##### Visual Studio
|
||||
|
||||
Construir usando Visual Studio requiere una configuración igualmente complicada. Para empezar, necesitarás instalar [vcpkg](https://github.com/Microsoft/vcpkg). Después de instalar vcpkg necesitarás instalar varios paquetes adicionales:
|
||||
|
||||
vcpkg install ffmpeg[vpx,x264] libepoxy libpng libzip sdl2 sqlite3
|
||||
|
||||
Toma nota de que esta instalación no soportará la codificación de video acelerada por hardware en Nvidia. Si te preocupa esto, necesitarás instalar CUDA, y luego sustituir `ffmpeg[vpx,x264,nvcodec]` en el comando anterior.
|
||||
|
||||
También necesitarás instalar Qt. Desafortunadamente, debido a que Qt pertenece y es administrado por una empresa en problemas en lugar de una organización razonable, ya no existe un instalador de la edición de código abierto sin conexión para la última versión, por lo que deberás recurrir a un [instalador de una versión anterior](https://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-windows-x86-5.12.9.exe) (que quiere que crees una cuenta que de otro modo sería inútil, pero puedes omitir esto al configurar temporalmente un proxy inválido o deshabilitar la red), usa el instalador en línea (que requiere una cuenta de todos modos) o usa vcpkg para construirlo (lentamente). Ninguna de estas son buenas opciones. Si usas el instalador, querrás instalar las versiones de MSVC correspondientes. Ten en cuenta que los instaladores sin conexión no son compatibles con MSVC 2019. Para vcpkg, querrás instalarlo así, lo que llevará bastante tiempo, especialmente en computadoras de cuatro núcleos o menos:
|
||||
|
||||
vcpkg install qt5-base qt5-multimedia
|
||||
|
||||
Luego, abre Visual Studio, selecciona Clonar repositorio, e ingresa `https://github.com/mgba-emu/mgba.git`. Cuando Visual Studio termine de clonar, ve a Archivo > CMake y abre el archivo CMakeLists.txt en la raíz del repositorio desplegado. Desde allí, puedes trabajar en MGBA en Visual Studio de manera similar a otros proyectos CMake de Visual Studio.
|
||||
|
||||
#### Compilación con cadenas de herramientas (toolchain)
|
||||
|
||||
Si tienes devkitARM (para 3DS), devkitPPC (para Wii), devkitA64 (para Switch), o vitasdk (para PS Vita), puedes usar los siguientes comandos para compilar:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../src/platform/3ds/CMakeToolchain.txt ..
|
||||
make
|
||||
|
||||
Reemplaza el parámetro `-DCMAKE_TOOLCHAIN_FILE` para las plataformas:
|
||||
|
||||
- 3DS: `../src/platform/3ds/CMakeToolchain.txt`
|
||||
- Switch: `../src/platform/switch/CMakeToolchain.txt`
|
||||
- Vita: `../src/platform/psp2/CMakeToolchain.vitasdk`
|
||||
- Wii: `../src/platform/wii/CMakeToolchain.txt`
|
||||
|
||||
### Dependencies
|
||||
|
||||
mGBA no tiene dependencias duras, sin embargo, se requieren las siguientes dependencias opcionales para características específicas. Las características se desactivarán si no se pueden encontrar las dependencias.
|
||||
|
||||
- Qt 5: para la interfaz gráfica. Qt Multimedia o SDL se requieren para el audio.
|
||||
- SDL: para un frontend más básico y soporte de gamepad en el frontend de Qt. Se recomienda SDL 2, pero se admite 1.2.
|
||||
- zlib y libpng: para soporte de capturas de pantalla y soporte de estados de guardado embebidos en PNG.
|
||||
- libedit: para soporte del depurador de línea de comandos.
|
||||
- ffmpeg o libav: para grabación de video, GIF, WebP y APNG.
|
||||
- libzip o zlib: para cargar ROMs almacenadas en archivos zip.
|
||||
- SQLite3: para la bases de datos de juegos.
|
||||
- libelf: para cargar ELF.
|
||||
|
||||
SQLite3, libpng y zlib están incluidos en el emulador, por lo que no necesitan ser compilados externamente primero.
|
||||
|
||||
Notas a pie
|
||||
---------
|
||||
|
||||
<a name="missing">[1]</a> Las características faltantes actualmente son
|
||||
|
||||
- OBJ window para los modos 3, 4 y 5 ([Bug #5](http://mgba.io/b/5))
|
||||
|
||||
<a name="flashdetect">[2]</a> La detección del tamaño de la memoria flash no funciona en algunos casos. Se pueden configurar en tiempo de ejecución, pero se recomienda ingresar un bug si se encuentra un caso así.
|
||||
|
||||
<a name="osxver">[3]</a> 10.8 sólo se necesita para la versión con Qt. Puede ser posible compilar o hacer funcionar la versión Qt en 10.7 o versiones más antigas, pero esto no está oficialmente soportado. La versión SDL funciona en 10.5, y puede funcionar en versiones anteriores.
|
||||
|
||||
[downloads]: http://mgba.io/downloads.html
|
||||
[source]: https://github.com/mgba-emu/mgba/
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
mGBA es Copyright © 2013 – 2020 Jeffrey Pfau. Es distribuído bajo la [licencia pública de Mozilla (Mozilla Public License) version 2.0](https://www.mozilla.org/MPL/2.0/). Una copia de la licencia está disponible en el archivo LICENSE.
|
||||
|
||||
mGBA contiene las siguientes bibliotecas de terceros:
|
||||
|
||||
- [inih](https://github.com/benhoyt/inih), que es copyright © 2009 - 2020 Ben Hoyt y se utiliza bajo licencia de la cláusula 3 de BSD.
|
||||
- [blip-buf](https://code.google.com/archive/p/blip-buf), que es copyright © 2003 - 2009 Shay Green y se usa bajo LGPL.
|
||||
- [LZMA SDK](http://www.7-zip.org/sdk.html), la cual está en el dominio público.
|
||||
- [MurmurHash3](https://github.com/aappleby/smhasher), implementación por Austin Appleby, la cual está en el dominio público.
|
||||
- [getopt for MSVC](https://github.com/skandhurkat/Getopt-for-Visual-Studio/), la cual está en el dominio público.
|
||||
- [SQLite3](https://www.sqlite.org), la cual está en el dominio público.
|
||||
|
||||
Si usted es un editor de juegos y desea obtener una licencia de mGBA para uso comercial, por favor envíe un correo electrónico a [licensing@mgba.io](mailto:licensing@mgba.io) para obtener más información.
|
|
@ -0,0 +1,254 @@
|
|||
mGBA
|
||||
====
|
||||
|
||||
mGBA 是一个运行 Game Boy Advance 游戏的模拟器。mGBA 的目标是比众多现有的 Game Boy Advance 模拟器更快、更准确,并增加其他模拟器所缺少的功能。mGBA 还支持 Game Boy 和 Game Boy Color 游戏。
|
||||
|
||||
可在以下网址找到最新新闻和下载:[mgba.io](https://mgba.io/)。
|
||||
|
||||
[![Build status](https://travis-ci.org/mgba-emu/mgba.svg?branch=master)](https://travis-ci.org/mgba-emu/mgba)
|
||||
|
||||
功能
|
||||
--------
|
||||
|
||||
- 支持高精确的 Game Boy Advance 硬件[<sup>[1]</sup>](#missing)。
|
||||
- 支持 Game Boy/Game Boy Color 硬件。
|
||||
- 快速模拟:已知即使在低端硬件(例如上网本)上也能够全速运行。
|
||||
- 用于重型和轻型前端的 Qt 和 SDL 端口。
|
||||
- 支持本地(同一台计算机)链接电缆。
|
||||
- 存档类型检测,即使是闪存大小也可检测[<sup>[2]</sup>](#flashdetect)。
|
||||
- 支持附带有运动传感器和振动机制的卡带(仅适用于游戏控制器)。
|
||||
- 支持实时时钟(RTC),甚至无需配置。
|
||||
- 支持《我们的太阳》系列游戏的太阳能传感器。
|
||||
- 支持 Game Boy 相机和 Game Boy 打印机。
|
||||
- 内置 BIOS 执行,并具有加载外部 BIOS 文件的功能。
|
||||
- 支持 Turbo/快进功能(按住 Tab 键)。
|
||||
- 支持倒带(按住反引号键)。
|
||||
- 支持跳帧,最多可配置 10 级。
|
||||
- 支持截图。
|
||||
- 支持作弊码。
|
||||
- 支持 9 个即时存档插槽。还能够以屏幕截图的形式查看即时存档。
|
||||
- 支持视频、GIF、WebP 和 APNG 录制。
|
||||
- 支持 e-Reader。
|
||||
- 可重新映射键盘和游戏手柄的控制键。
|
||||
- 支持从 ZIP 和 7z 文件中加载。
|
||||
- 支持 IPS、UPS 和 BPS 补丁。
|
||||
- 支持通过命令行界面和 GDB 远程支持进行游戏调试,兼容 IDA Pro。
|
||||
- 支持可配置的模拟倒带。
|
||||
- 支持载入和导出 GameShark 和 Action Replay 快照。
|
||||
- 适用于 RetroArch/Libretro 和 OpenEmu 的内核。
|
||||
- 许许多多的小玩意。
|
||||
|
||||
#### Game Boy 映射器(mapper)
|
||||
|
||||
完美支持以下 mapper:
|
||||
|
||||
- MBC1
|
||||
- MBC1M
|
||||
- MBC2
|
||||
- MBC3
|
||||
- MBC3+RTC
|
||||
- MBC5
|
||||
- MBC5+振动
|
||||
- MBC7
|
||||
- Wisdom Tree(未授权)
|
||||
- Pokémon Jade/Diamond(未授权)
|
||||
- BBD(未授权、类 MBC5)
|
||||
- Hitek(未授权、类 MBC5)
|
||||
|
||||
部分支持以下 mapper:
|
||||
|
||||
- MBC6(缺少闪存写入支持)
|
||||
- MMM01
|
||||
- Pocket Cam
|
||||
- TAMA5(缺少 RTC 支持)
|
||||
- HuC-1(缺少 IR 支持)
|
||||
- HuC-3(缺少 IR 和 RTC 支持)
|
||||
|
||||
### 计划加入的功能
|
||||
|
||||
- 支持联网多人链接电缆。
|
||||
- 支持 Dolphin/JOY 总线链接电缆。
|
||||
- MP2k 音频混合,获得比硬件更高质量的声音。
|
||||
- 支持针对工具辅助竞速(Tool-Assisted Speedrun)的重录功能。
|
||||
- 支持 Lua 脚本。
|
||||
- 全方位的调试套件。
|
||||
- 支持无线适配器。
|
||||
|
||||
支持平台
|
||||
-------------------
|
||||
|
||||
- Windows Vista 或更新
|
||||
- OS X 10.8(山狮 / Mountain Lion)[<sup>[3]</sup>](#osxver) 或更新
|
||||
- Linux
|
||||
- FreeBSD
|
||||
- Nintendo 3DS
|
||||
- Nintendo Switch
|
||||
- Wii
|
||||
- PlayStation Vita
|
||||
|
||||
已知其他类 Unix 平台(如 OpenBSD)也可以使用,但未经测试且不完全受支持。
|
||||
|
||||
### 系统需求
|
||||
|
||||
系统需求很低。任何可以运行 Windows Vista 或更高版本的计算机都应该能够处理模拟机制,还需要支持 OpenGL 1.1 或更高版本。而对于着色器和高级功能,则需要支持 OpenGL 3.2 或更高版本。
|
||||
|
||||
下载
|
||||
---------
|
||||
|
||||
可在官方网站的[下载(Downloads)][downloads]区域找到下载地址。可在 [GitHub][source] 找到源代码。
|
||||
|
||||
控制键位
|
||||
--------
|
||||
|
||||
可在设置菜单中进行控制键位的配置。许多游戏控制器应该会在默认情况下自动映射。键盘的默认控制键位如下:
|
||||
|
||||
- **A**:X
|
||||
- **B**:Z
|
||||
- **L**:A
|
||||
- **R**:S
|
||||
- **Start**:回车键
|
||||
- **Select**:退格键
|
||||
|
||||
编译
|
||||
---------
|
||||
|
||||
编译需要使用 CMake 3.1 或更新版本。已知 GCC 和 Clang 都可以编译 mGBA,而 Visual Studio 2013 和更旧的版本则无法编译。我们即将实现对 Visual Studio 2015 或更新版本的支持。
|
||||
|
||||
#### Docker 构建
|
||||
|
||||
对于大多数平台来说,建议使用 Docker 进行构建。我们提供了多个 Docker 映像,其中包含在多个平台上构建 mGBA 所需的工具链和依赖项。
|
||||
|
||||
要使用 Docker 映像构建 mGBA,只需在 mGBA 的签出(checkout)根目录中运行以下命令:
|
||||
|
||||
docker run --rm -t -v $PWD:/home/mgba/src mgba/windows:w32
|
||||
|
||||
此命令将生成 `build-win32` 目录。将 `mgba/windows:w32` 替换为其他平台上的 Docker 映像,会生成相应的其他目录。Docker Hub 上提供了以下 Docker 映像:
|
||||
|
||||
- mgba/3ds
|
||||
- mgba/switch
|
||||
- mgba/ubuntu:xenial
|
||||
- mgba/ubuntu:bionic
|
||||
- mgba/ubuntu:focal
|
||||
- mgba/ubuntu:groovy
|
||||
- mgba/vita
|
||||
- mgba/wii
|
||||
- mgba/windows:w32
|
||||
- mgba/windows:w64
|
||||
|
||||
#### *nix 构建
|
||||
|
||||
要在基于 Unix 的系统上使用 CMake 进行构建,推荐执行以下命令:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr ..
|
||||
make
|
||||
sudo make install
|
||||
|
||||
这些命令将构建 mGBA 并将其安装到 `/usr/bin` 和 `/usr/lib` 中。系统会自动检测已安装的依赖项,如果未找到依赖项,则会在提示找不到依赖项的情况下运行 `cmake` 命令,并显示已被禁用的功能。
|
||||
|
||||
如果您使用的是 MacOS,则步骤略有不同。假设您使用的是自制软件包管理器,建议使用以下命令来获取依赖项并进行构建:
|
||||
|
||||
brew install cmake ffmpeg libzip qt5 sdl2 libedit pkg-config
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_PREFIX_PATH=`brew --prefix qt5` ..
|
||||
make
|
||||
|
||||
请注意,您不能在 MacOS 上执行 `make install`,因为此命令不能正常工作。
|
||||
|
||||
#### Windows 开发者构建
|
||||
|
||||
##### MSYS2
|
||||
|
||||
如果要在 Windows 上进行构建,建议使用 MSYS2。请按照 MSYS2 [网站](https://msys2.github.io)上的安装步骤操作。请确保您运行的是 32 位版本的 MSYS2(“MSYS2 MinGW 32-bit”)。如果想要构建 x86_64 版本,则运行 64 位版本的 MSYS2(“MSYS2 MinGW 64-bit”) ,并执行以下额外命令(包括花括号)来安装所需的依赖项(请注意,此命令涉及下载超过 1100MiB 的包,因此会需要很长一段时间):
|
||||
|
||||
pacman -Sy --needed base-devel git ${MINGW_PACKAGE_PREFIX}-{cmake,ffmpeg,gcc,gdb,libelf,libepoxy,libzip,pkgconf,qt5,SDL2,ntldd-git}
|
||||
|
||||
运行以下命令检查源代码:
|
||||
|
||||
git clone https://github.com/mgba-emu/mgba.git
|
||||
|
||||
最后运行以下命令进行构建:
|
||||
|
||||
mkdir -p mgba/build
|
||||
cd mgba/build
|
||||
cmake .. -G "MSYS Makefiles"
|
||||
make -j$(nproc --ignore=1)
|
||||
|
||||
请注意,此版本的 mGBA for Windows 不适合分发,因为运行此版本所需的 DLL 非常分散,但非常适合开发。但是,如果需要分发此类版本(例如用于在未安装 MSYS2 环境的计算机上进行测试),请运行 `cpack-G ZIP`,准备一个包含所有必要 DLL 的压缩文件。
|
||||
|
||||
##### Visual Studio
|
||||
|
||||
使用 Visual Studio 进行构建需要同样复杂的设置。首先需要安装 [vcpkg](https://github.com/Microsoft/vcpkg)。安装 vcpkg 后,还需要安装数个额外的软件包:
|
||||
|
||||
vcpkg install ffmpeg[vpx,x264] libepoxy libpng libzip sdl2 sqlite3
|
||||
|
||||
请注意,此安装将不支持 Nvidia 硬件上的硬件加速视频编码。如果对此非常在意,则需要预先安装 CUDA,然后用 `ffmpeg[vpx,x264,nvcodec]` 替换前面命令中的 `ffmpeg[vpx,x264]`。
|
||||
|
||||
您还需要安装 Qt。但不幸的是,由于 Qt 已被一家境况不佳的公司而不是合理的组织所拥有并运营,所以不再存在针对最新版本的离线开源版本安装程序,需要退回到[旧版本的安装程序](https://download.qt.io/official_releases/qt/5.12/5.12.9/qt-opensource-windows-x86-5.12.9.exe) (会要求创建一个原本已无用的帐号,但可以通过临时设置无效代理或以其他方式禁用网络来绕过这一机制。)、使用在线安装程序(无论如何都需要一个帐号),或使用 vcpkg 进行构建(速度很慢)。这些都不是很好的选择。需要针对安装程序安装适用的 MSVC 版本。请注意,离线安装程序不支持 MSVC 2019。若使用 vcpkg,您需要花费相当一段时间将其安装,尤其是在四核或更少内核的计算机上花费时间更久:
|
||||
|
||||
vcpkg install qt5-base qt5-multimedia
|
||||
|
||||
下一步打开 Visual Studio,选择“克隆仓库”, 输入 `https://github.com/mgba-emu/mgba.git`。在 Visual Studio 完成克隆后,转到“文件”>“CMake”,然后打开已签出(checked out)仓库的 CMakeLists.txt 文件。在此基础上便可像其他 Visual Studio CMake 项目一样在 Visual Studio 中开发 mGBA。
|
||||
|
||||
#### 工具链构建
|
||||
|
||||
如果您拥有 devkitARM(3DS)、devkitPPC(Wii)、devkitA64(Switch)或 vitasdk(PS Vita),您可以使用以下命令进行构建:
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_TOOLCHAIN_FILE=../src/platform/3ds/CMakeToolchain.txt ..
|
||||
make
|
||||
|
||||
将 `-DCMAKE_TOOLCHAIN_FILE` 参数替换为以下不同平台的参数:
|
||||
|
||||
- 3DS:`../src/platform/3ds/CMakeToolchain.txt`
|
||||
- Switch:`../src/platform/switch/CMakeToolchain.txt`
|
||||
- Vita:`../src/platform/psp2/CMakeToolchain.vitasdk`
|
||||
- Wii:`../src/platform/wii/CMakeToolchain.txt`
|
||||
|
||||
### 依赖项
|
||||
|
||||
mGBA 没有硬性的依赖项,但是特定功能需要以下可选的依赖项。如果找不到依赖项,则这些可选功能将会被禁用。
|
||||
|
||||
- Qt 5:GUI 前端的所需依赖项。音频需要 Qt Multimedia 或 SDL。
|
||||
- SDL:更基本的前端以及在 Qt 前端中支持游戏手柄的所需依赖项。推荐使用 SDL 2、但也支持 1.2。
|
||||
- zlib 和 libpng:截图与 PNG 即时存档支持的所需依赖项
|
||||
- libedit:命令行调试器的所需依赖项
|
||||
- ffmpeg 或 libav:录制视频、GIF、WebP 和 APNG 的所需依赖项
|
||||
- libzip 或 zlib:载入储存在 ZIP 文件中的 ROM 的所需依赖项。
|
||||
- SQLite3:游戏数据库的所需依赖项
|
||||
- libelf:ELF 载入的所需依赖项
|
||||
|
||||
SQLite3、libpng 以及 zlib 已包含在模拟器中,因此不需要先对这些依赖项进行外部编译。
|
||||
|
||||
Footnotes
|
||||
---------
|
||||
|
||||
<a name="missing">[1]</a> 目前缺失的功能有
|
||||
|
||||
- 模式 3、4 和 5 的 OBJ 窗口 ([Bug #5](http://mgba.io/b/5))
|
||||
|
||||
<a name="flashdetect">[2]</a> 闪存大小检测在某些情况下不起作用。 这些可以在运行时中进行配置,但如果遇到此类情况,建议提交错误。
|
||||
|
||||
<a name="osxver">[3]</a> 仅 Qt 端口需要 10.8。应该可以在 10.7 或更早版本上构建或运行 Qt 端口,但这类操作不受官方支持。已知 SDL 端口可以在 10.5 上运行,并且可能能够在旧版本上运行。
|
||||
|
||||
[downloads]: http://mgba.io/downloads.html
|
||||
[source]: https://github.com/mgba-emu/mgba/
|
||||
|
||||
版权
|
||||
---------
|
||||
|
||||
mGBA 版权 © 2013 – 2020 Jeffrey Pfau。基于 [Mozilla 公共许可证版本 2.0](https://www.mozilla.org/MPL/2.0/) 许可证分发。分发的 LICENSE 文件中提供了许可证的副本。
|
||||
|
||||
mGBA 包含以下第三方库:
|
||||
|
||||
- [inih](https://github.com/benhoyt/inih):版权 © 2009 – 2020 Ben Hoyt,基于 BSD 3-clause 许可证使用。
|
||||
- [blip-buf](https://code.google.com/archive/p/blip-buf):版权 © 2003 – 2009 Shay Green,基于 Lesser GNU 公共许可证使用。
|
||||
- [LZMA SDK](http://www.7-zip.org/sdk.html):属公有领域使用。
|
||||
- [MurmurHash3](https://github.com/aappleby/smhasher):由 Austin Appleby 实施,属公有领域使用。
|
||||
- [getopt for MSVC](https://github.com/skandhurkat/Getopt-for-Visual-Studio/):属公有领域使用。
|
||||
- [SQLite3](https://www.sqlite.org):属公有领域使用。
|
||||
|
||||
如果您是游戏发行商,并希望获得 mGBA 用于商业用途的许可,请发送电子邮件到 [licensing@mgba.io](mailto:licensing@mgba.io) 获取更多信息。
|
|
@ -1,16 +0,0 @@
|
|||
/* Copyright (c) 2013-2015 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#ifndef ARM_ALGO_H
|
||||
#define ARM_ALGO_H
|
||||
|
||||
#ifdef __arm__
|
||||
#if defined(__ARM_NEON)
|
||||
void _neon2x(void* dest, void* src, int width, int height);
|
||||
void _neon4x(void* dest, void* src, int width, int height);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -36,6 +36,7 @@ bool ConfigurationRead(struct Configuration*, const char* path);
|
|||
bool ConfigurationReadVFile(struct Configuration*, struct VFile* vf);
|
||||
bool ConfigurationWrite(const struct Configuration*, const char* path);
|
||||
bool ConfigurationWriteSection(const struct Configuration*, const char* path, const char* section);
|
||||
bool ConfigurationWriteVFile(const struct Configuration*, struct VFile* vf);
|
||||
|
||||
void ConfigurationEnumerateSections(const struct Configuration* configuration, void (*handler)(const char* sectionName, void* user), void* user);
|
||||
void ConfigurationEnumerate(const struct Configuration* configuration, const char* section, void (*handler)(const char* key, const char* value, void* user), void* user);
|
||||
|
|
|
@ -68,9 +68,13 @@ bool mCoreConfigLoad(struct mCoreConfig*);
|
|||
bool mCoreConfigSave(const struct mCoreConfig*);
|
||||
bool mCoreConfigLoadPath(struct mCoreConfig*, const char* path);
|
||||
bool mCoreConfigSavePath(const struct mCoreConfig*, const char* path);
|
||||
bool mCoreConfigLoadVFile(struct mCoreConfig*, struct VFile* vf);
|
||||
bool mCoreConfigSaveVFile(const struct mCoreConfig*, struct VFile* vf);
|
||||
|
||||
void mCoreConfigMakePortable(const struct mCoreConfig*);
|
||||
void mCoreConfigDirectory(char* out, size_t outLength);
|
||||
void mCoreConfigPortablePath(char* out, size_t outLength);
|
||||
bool mCoreConfigIsPortable(void);
|
||||
#endif
|
||||
|
||||
const char* mCoreConfigGetValue(const struct mCoreConfig*, const char* key);
|
||||
|
|
|
@ -12,8 +12,8 @@ CXX_GUARD_START
|
|||
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
#include <mgba/core/cheats.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
#define MAX_ROM_PATCHES 10
|
||||
#define COMPLETE ((size_t) -1)
|
||||
|
||||
enum GBACheatType {
|
||||
|
@ -134,17 +134,20 @@ struct GBACheatHook {
|
|||
size_t reentries;
|
||||
};
|
||||
|
||||
struct GBACheatPatch {
|
||||
uint32_t address;
|
||||
int16_t newValue;
|
||||
int16_t oldValue;
|
||||
bool applied;
|
||||
};
|
||||
|
||||
DECLARE_VECTOR(GBACheatPatchList, struct GBACheatPatch);
|
||||
|
||||
struct GBACheatSet {
|
||||
struct mCheatSet d;
|
||||
struct GBACheatHook* hook;
|
||||
|
||||
struct GBACheatPatch {
|
||||
uint32_t address;
|
||||
int16_t newValue;
|
||||
int16_t oldValue;
|
||||
bool applied;
|
||||
bool exists;
|
||||
} romPatches[MAX_ROM_PATCHES];
|
||||
struct GBACheatPatchList romPatches;
|
||||
|
||||
size_t incompleteCheat;
|
||||
struct GBACheatPatch* incompletePatch;
|
||||
|
|
|
@ -116,6 +116,7 @@ struct GBA {
|
|||
int32_t cachedRegisters[16];
|
||||
bool taintedRegisters[16];
|
||||
|
||||
bool vbaBugCompat;
|
||||
bool hardCrash;
|
||||
bool allowOpposingDirections;
|
||||
|
||||
|
|
|
@ -84,8 +84,6 @@ DECL_BITFIELD(RTCStatus2, uint8_t);
|
|||
DECL_BITS(RTCStatus2, INT1, 0, 4);
|
||||
DECL_BIT(RTCStatus2, INT2, 6);
|
||||
|
||||
#ifndef PYCPARSE
|
||||
#pragma pack(push, 1)
|
||||
struct GBARTC {
|
||||
int32_t bytesRemaining;
|
||||
int32_t transferStep;
|
||||
|
@ -100,10 +98,6 @@ struct GBARTC {
|
|||
uint8_t alarm2[3];
|
||||
uint8_t time[7];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#else
|
||||
struct GBATRC;
|
||||
#endif
|
||||
|
||||
struct GBAGBPKeyCallback {
|
||||
struct mKeyCallback d;
|
||||
|
|
|
@ -20,6 +20,7 @@ struct GBACartridgeOverride {
|
|||
int hardware;
|
||||
uint32_t idleLoop;
|
||||
bool mirroring;
|
||||
bool vbaBugCompat;
|
||||
};
|
||||
|
||||
struct Configuration;
|
||||
|
|
|
@ -79,8 +79,7 @@ enum {
|
|||
GBA_GL_TEX_OBJ_COLOR = 0,
|
||||
GBA_GL_TEX_OBJ_FLAGS,
|
||||
GBA_GL_TEX_OBJ_DEPTH,
|
||||
GBA_GL_TEX_BACKDROP_COLOR,
|
||||
GBA_GL_TEX_BACKDROP_FLAGS,
|
||||
GBA_GL_TEX_BACKDROP,
|
||||
GBA_GL_TEX_WINDOW,
|
||||
GBA_GL_TEX_MAX
|
||||
};
|
||||
|
@ -121,8 +120,8 @@ enum {
|
|||
GBA_GL_FINALIZE_LAYERS,
|
||||
GBA_GL_FINALIZE_FLAGS,
|
||||
GBA_GL_FINALIZE_WINDOW,
|
||||
GBA_GL_FINALIZE_PALETTE,
|
||||
GBA_GL_FINALIZE_BACKDROP,
|
||||
GBA_GL_FINALIZE_BACKDROPFLAGS,
|
||||
|
||||
GBA_GL_UNIFORM_MAX = 14
|
||||
};
|
||||
|
@ -150,7 +149,10 @@ struct GBAVideoGLRenderer {
|
|||
|
||||
GLuint outputTex;
|
||||
|
||||
GLint shadowPalette[512];
|
||||
GLuint paletteTex;
|
||||
uint16_t shadowPalette[GBA_VIDEO_VERTICAL_PIXELS][512];
|
||||
int nextPalette;
|
||||
int paletteDirtyScanlines;
|
||||
bool paletteDirty;
|
||||
|
||||
GLuint vramTex;
|
||||
|
|
|
@ -316,7 +316,14 @@ struct GBASerializedState {
|
|||
struct {
|
||||
uint16_t pinState;
|
||||
uint16_t pinDirection;
|
||||
struct GBARTC rtc;
|
||||
int32_t rtcBytesRemaining;
|
||||
int32_t rtcTransferStep;
|
||||
int32_t rtcBitsRead;
|
||||
int32_t rtcBits;
|
||||
int32_t rtcCommandActive;
|
||||
RTCCommandData rtcCommand;
|
||||
RTCControl rtcControl;
|
||||
uint8_t time[7];
|
||||
uint8_t devices;
|
||||
uint16_t gyroSample;
|
||||
uint16_t tiltSampleX;
|
||||
|
|
|
@ -17,10 +17,10 @@ Vulcan2
|
|||
Vulcan3
|
||||
Spreader
|
||||
HeatShot
|
||||
HeatV
|
||||
Heat-V
|
||||
HeatSide
|
||||
Bubbler
|
||||
BubbleV
|
||||
Bubble-V
|
||||
BubbleSide
|
||||
ElementFlare
|
||||
ElementIce
|
||||
|
@ -50,13 +50,13 @@ WideSword
|
|||
LongSword
|
||||
WideBlade
|
||||
LongBlade
|
||||
WindRacket
|
||||
WindRack
|
||||
CustomSword
|
||||
VariableSword
|
||||
Slasher
|
||||
ThunderBall1
|
||||
ThunderBall2
|
||||
ThunderBall3
|
||||
Thunder1
|
||||
Thunder2
|
||||
Thunder3
|
||||
Counter1
|
||||
Counter2
|
||||
Counter3
|
||||
|
@ -80,9 +80,9 @@ SideBamboo2
|
|||
SideBamboo3
|
||||
Lance
|
||||
Hole
|
||||
Boy'sBomb1
|
||||
Boy'sBomb2
|
||||
Boy'sBomb3
|
||||
BoyBomb1
|
||||
BoyBomb2
|
||||
BoyBomb3
|
||||
Guard1
|
||||
Guard2
|
||||
Guard3
|
||||
|
@ -171,7 +171,7 @@ BlackWing
|
|||
GodHammer
|
||||
DarkLine
|
||||
NeoVariable
|
||||
ZSaber
|
||||
Z-Saber
|
||||
GunDelSolEX
|
||||
SuperVulcan
|
||||
Roll
|
||||
|
@ -236,7 +236,7 @@ VideoManSP
|
|||
VideoManDS
|
||||
Marking
|
||||
CannonMode
|
||||
CannonballMode
|
||||
BallMode
|
||||
SwordMode
|
||||
FirePlus
|
||||
ThunderPlus
|
||||
|
@ -292,24 +292,24 @@ BugFix
|
|||
Sanctuary
|
||||
SignalRed
|
||||
BlackBarrier
|
||||
MegaManNavi
|
||||
RollNavi
|
||||
GutsManNavi
|
||||
WindManNavi
|
||||
SearchManNavi
|
||||
FireManNavi
|
||||
ThunderManNavi
|
||||
ProtoManNavi
|
||||
NumberManNavi
|
||||
MetalManNavi
|
||||
JunkManNavi
|
||||
AquaManNavi
|
||||
WoodManNavi
|
||||
StarManNavi
|
||||
IceManNavi
|
||||
ShadowManNavi
|
||||
ElecManNavi
|
||||
KnightManNavi
|
||||
PlantManNavi
|
||||
NapalmManNavi
|
||||
BassNavi
|
||||
MegaMan Navi
|
||||
Roll Navi
|
||||
GutsMan Navi
|
||||
WindMan Navi
|
||||
SearchMan Navi
|
||||
FireMan Navi
|
||||
ThunderMan Navi
|
||||
ProtoMan Navi
|
||||
NumberMan Navi
|
||||
MetalMan Navi
|
||||
JunkMan Navi
|
||||
AquaMan Navi
|
||||
WoodMan Navi
|
||||
StarMan Navi
|
||||
IceMan Navi
|
||||
ShadowMan Navi
|
||||
ElecMan Navi
|
||||
KnightMan Navi
|
||||
PlantMan Navi
|
||||
NapalmMan Navi
|
||||
Bass Navi
|
||||
|
|
|
@ -16,7 +16,7 @@ Vulcan1
|
|||
Vulcan2
|
||||
Vulcan3
|
||||
Spreader
|
||||
ThunderBall
|
||||
Thunder
|
||||
IceSeed
|
||||
Pulsar1
|
||||
Pulsar2
|
||||
|
@ -79,12 +79,12 @@ Voltz1
|
|||
Voltz2
|
||||
Voltz3
|
||||
Lance
|
||||
Yo-Yo
|
||||
YoYo
|
||||
Wind
|
||||
Fan
|
||||
Boy'sBomb1
|
||||
Boy'sBomb2
|
||||
Boy'sBomb3
|
||||
BoyBomb1
|
||||
BoyBomb2
|
||||
BoyBomb3
|
||||
Guard1
|
||||
Guard2
|
||||
Guard3
|
||||
|
@ -175,9 +175,9 @@ MudWave
|
|||
CactusBall1
|
||||
CactusBall2
|
||||
CactusBall3
|
||||
WoodyNose1
|
||||
WoodyNose2
|
||||
WoodyNose3
|
||||
WoodNose1
|
||||
WoodNose2
|
||||
WoodNose3
|
||||
|
||||
|
||||
|
||||
|
@ -215,7 +215,7 @@ BlackWing
|
|||
Otenko
|
||||
JusticeOne
|
||||
NeoVariable
|
||||
ZSaber
|
||||
Z-Saber
|
||||
GunDelSolEX
|
||||
SuperVulcan
|
||||
Roll
|
||||
|
@ -279,9 +279,9 @@ Django
|
|||
DjangoSP
|
||||
DjangoDS
|
||||
CannonMode
|
||||
CannonBall
|
||||
BallMode
|
||||
SwordMode
|
||||
Yo-YoMode
|
||||
YoYoMode
|
||||
DrillMode
|
||||
LCurseShield
|
||||
LStepSword
|
||||
|
@ -327,25 +327,25 @@ BugCharge
|
|||
|
||||
|
||||
|
||||
MegaManNavi
|
||||
MegaMan Navi
|
||||
|
||||
|
||||
|
||||
SearchManNavi
|
||||
SearchMan Navi
|
||||
|
||||
|
||||
ProtoManNavi
|
||||
NumberManNavi
|
||||
ProtoMan Navi
|
||||
NumberMan Navi
|
||||
|
||||
|
||||
|
||||
|
||||
ShadowManNavi
|
||||
NapalmManNavi
|
||||
KnightManNavi
|
||||
ToadManNavi
|
||||
MagnetManNavi
|
||||
GyroManNavi
|
||||
ColonelNavi
|
||||
MeddyNavi
|
||||
TomahawkManNavi
|
||||
ShadowMan Navi
|
||||
NapalmMan Navi
|
||||
KnightMan Navi
|
||||
ToadMan Navi
|
||||
MagnetMan Navi
|
||||
GyroMan Navi
|
||||
Colonel Navi
|
||||
Meddy Navi
|
||||
TomahawkMan Navi
|
||||
|
|
|
@ -16,7 +16,7 @@ GunDelSol1
|
|||
GunDelSol2
|
||||
GunDelSol3
|
||||
GunDelSolEX
|
||||
Yo-Yo
|
||||
YoYo
|
||||
FireBurner1
|
||||
FireBurner2
|
||||
FireBurner3
|
||||
|
@ -77,7 +77,7 @@ FireSword
|
|||
AquaSword
|
||||
ElecSword
|
||||
BambooSword
|
||||
WindRacket
|
||||
WindRack
|
||||
StepSword
|
||||
VariableSword
|
||||
NeoVariable
|
||||
|
@ -95,9 +95,9 @@ WaveArm3
|
|||
AuraHead1
|
||||
AuraHead2
|
||||
AuraHead3
|
||||
LittleBoiler1
|
||||
LittleBoiler2
|
||||
LittleBoiler3
|
||||
LilBoiler1
|
||||
LilBoiler2
|
||||
LilBoiler3
|
||||
SandWorm1
|
||||
SandWorm2
|
||||
SandWorm3
|
||||
|
@ -280,7 +280,7 @@ Django2
|
|||
Django3
|
||||
PunchArm
|
||||
NeedleArm
|
||||
PuzzleArm
|
||||
PulseArm
|
||||
BoomerArm
|
||||
SynchroTrigger
|
||||
DarkSword
|
||||
|
@ -497,4 +497,4 @@ ProtoManV11
|
|||
ProtoManV12
|
||||
ProtoManV13
|
||||
ProtoManV14
|
||||
ProtoManSP
|
||||
ProtoManSP
|
||||
|
|
6939
res/nointro.dat
6939
res/nointro.dat
File diff suppressed because it is too large
Load Diff
|
@ -151,13 +151,15 @@ static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uin
|
|||
return false;
|
||||
}
|
||||
|
||||
if (isCall) {
|
||||
int instructionLength = isWideInstruction ? WORD_SIZE_ARM : WORD_SIZE_THUMB;
|
||||
frame = mStackTracePush(stack, pc, destAddress + instructionLength, cpu->gprs[ARM_SP], &cpu->regs);
|
||||
if (interrupt || isCall) {
|
||||
if (isCall) {
|
||||
int instructionLength = isWideInstruction ? WORD_SIZE_ARM : WORD_SIZE_THUMB;
|
||||
frame = mStackTracePush(stack, pc, destAddress + instructionLength, cpu->gprs[ARM_SP], &cpu->regs);
|
||||
}
|
||||
if (!(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_CALL)) {
|
||||
return false;
|
||||
}
|
||||
} else if (!interrupt) {
|
||||
} else {
|
||||
if (frame && currentStack == ARMSelectBank(FRAME_PRIV(frame))) {
|
||||
mStackTracePop(stack);
|
||||
}
|
||||
|
|
|
@ -594,12 +594,11 @@ DEFINE_MULTIPLY_INSTRUCTION_2_ARM(MLA, cpu->gprs[rdHi] = cpu->gprs[rm] * cpu->gp
|
|||
DEFINE_MULTIPLY_INSTRUCTION_ARM(MUL, cpu->gprs[rd] = cpu->gprs[rm] * cpu->gprs[rs], ARM_NEUTRAL_S(cpu->gprs[rm], cpu->gprs[rs], cpu->gprs[rd]))
|
||||
|
||||
DEFINE_MULTIPLY_INSTRUCTION_2_ARM(SMLAL,
|
||||
int64_t d = ((int64_t) cpu->gprs[rm]) * ((int64_t) cpu->gprs[rs]);
|
||||
int32_t dm = cpu->gprs[rd];
|
||||
int32_t dn = d;
|
||||
cpu->gprs[rd] = dm + dn;
|
||||
cpu->gprs[rdHi] = cpu->gprs[rdHi] + (d >> 32) + ARM_CARRY_FROM(dm, dn, cpu->gprs[rd]);,
|
||||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi]), 3)
|
||||
int64_t d = ((int64_t) cpu->gprs[rm]) * ((int64_t) cpu->gprs[rs]) + ((uint32_t) cpu->gprs[rd]);
|
||||
int32_t dHi = cpu->gprs[rdHi] + (d >> 32);
|
||||
cpu->gprs[rd] = d;
|
||||
cpu->gprs[rdHi] = dHi;,
|
||||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], dHi), 3)
|
||||
|
||||
DEFINE_MULTIPLY_INSTRUCTION_XY_ARM(SMLA,
|
||||
int32_t dn = cpu->gprs[rn]; \
|
||||
|
@ -624,12 +623,11 @@ DEFINE_MULTIPLY_INSTRUCTION_2_ARM(SMULL,
|
|||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi]), 2)
|
||||
|
||||
DEFINE_MULTIPLY_INSTRUCTION_2_ARM(UMLAL,
|
||||
uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]);
|
||||
int32_t dm = cpu->gprs[rd];
|
||||
int32_t dn = d;
|
||||
cpu->gprs[rd] = dm + dn;
|
||||
cpu->gprs[rdHi] = cpu->gprs[rdHi] + (d >> 32) + ARM_CARRY_FROM(dm, dn, cpu->gprs[rd]);,
|
||||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], cpu->gprs[rdHi]), 3)
|
||||
uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]) + ((uint32_t) cpu->gprs[rd]);
|
||||
uint32_t dHi = ((uint32_t) cpu->gprs[rdHi]) + (d >> 32);
|
||||
cpu->gprs[rd] = d;
|
||||
cpu->gprs[rdHi] = dHi;,
|
||||
ARM_NEUTRAL_HI_S(cpu->gprs[rd], dHi), 3)
|
||||
|
||||
DEFINE_MULTIPLY_INSTRUCTION_2_ARM(UMULL,
|
||||
uint64_t d = ARM_UXT_64(cpu->gprs[rm]) * ARM_UXT_64(cpu->gprs[rs]);
|
||||
|
|
|
@ -60,6 +60,7 @@ void mCheatDeviceCreate(struct mCheatDevice* device) {
|
|||
void mCheatDeviceDestroy(struct mCheatDevice* device) {
|
||||
mCheatDeviceClear(device);
|
||||
mCheatSetsDeinit(&device->cheats);
|
||||
free(device);
|
||||
}
|
||||
|
||||
void mCheatDeviceClear(struct mCheatDevice* device) {
|
||||
|
|
|
@ -170,27 +170,23 @@ bool mCoreConfigSavePath(const struct mCoreConfig* config, const char* path) {
|
|||
return ConfigurationWrite(&config->configTable, path);
|
||||
}
|
||||
|
||||
bool mCoreConfigLoadVFile(struct mCoreConfig* config, struct VFile* vf) {
|
||||
return ConfigurationReadVFile(&config->configTable, vf);
|
||||
}
|
||||
|
||||
bool mCoreConfigSaveVFile(const struct mCoreConfig* config, struct VFile* vf) {
|
||||
return ConfigurationWriteVFile(&config->configTable, vf);
|
||||
}
|
||||
|
||||
void mCoreConfigMakePortable(const struct mCoreConfig* config) {
|
||||
struct VFile* portable = 0;
|
||||
#ifdef _WIN32
|
||||
char out[MAX_PATH];
|
||||
wchar_t wpath[MAX_PATH];
|
||||
wchar_t wprojectName[MAX_PATH];
|
||||
MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH);
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
GetModuleFileNameW(hModule, wpath, MAX_PATH);
|
||||
PathRemoveFileSpecW(wpath);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, MAX_PATH, 0, 0);
|
||||
StringCchCatA(out, MAX_PATH, "\\portable.ini");
|
||||
portable = VFileOpen(out, O_WRONLY | O_CREAT);
|
||||
#elif defined(PSP2) || defined(_3DS) || defined(__SWITCH__) || defined(GEKKO)
|
||||
// Already portable
|
||||
#else
|
||||
struct VFile* portable = NULL;
|
||||
char out[PATH_MAX];
|
||||
getcwd(out, PATH_MAX);
|
||||
strncat(out, PATH_SEP "portable.ini", PATH_MAX - strlen(out));
|
||||
mCoreConfigPortablePath(out, sizeof(out));
|
||||
if (!out[0]) {
|
||||
// Cannot be made portable
|
||||
return;
|
||||
}
|
||||
portable = VFileOpen(out, O_WRONLY | O_CREAT);
|
||||
#endif
|
||||
if (portable) {
|
||||
portable->close(portable);
|
||||
mCoreConfigSave(config);
|
||||
|
@ -199,62 +195,47 @@ void mCoreConfigMakePortable(const struct mCoreConfig* config) {
|
|||
|
||||
void mCoreConfigDirectory(char* out, size_t outLength) {
|
||||
struct VFile* portable;
|
||||
char portableDir[PATH_MAX];
|
||||
mCoreConfigPortablePath(portableDir, sizeof(portableDir));
|
||||
if (portableDir[0]) {
|
||||
portable = VFileOpen(portableDir, O_RDONLY);
|
||||
if (portable) {
|
||||
portable->close(portable);
|
||||
if (outLength < PATH_MAX) {
|
||||
char outTmp[PATH_MAX];
|
||||
separatePath(portableDir, outTmp, NULL, NULL);
|
||||
strlcpy(out, outTmp, outLength);
|
||||
} else {
|
||||
separatePath(portableDir, out, NULL, NULL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
wchar_t wpath[MAX_PATH];
|
||||
wchar_t wprojectName[MAX_PATH];
|
||||
wchar_t* home;
|
||||
MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH);
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
GetModuleFileNameW(hModule, wpath, MAX_PATH);
|
||||
PathRemoveFileSpecW(wpath);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
|
||||
StringCchCatA(out, outLength, "\\portable.ini");
|
||||
portable = VFileOpen(out, O_RDONLY);
|
||||
if (portable) {
|
||||
portable->close(portable);
|
||||
} else {
|
||||
wchar_t* home;
|
||||
SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &home);
|
||||
StringCchPrintfW(wpath, MAX_PATH, L"%ws\\%ws", home, wprojectName);
|
||||
CoTaskMemFree(home);
|
||||
CreateDirectoryW(wpath, NULL);
|
||||
}
|
||||
SHGetKnownFolderPath(&FOLDERID_RoamingAppData, 0, NULL, &home);
|
||||
StringCchPrintfW(wpath, MAX_PATH, L"%ws\\%ws", home, wprojectName);
|
||||
CoTaskMemFree(home);
|
||||
CreateDirectoryW(wpath, NULL);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
|
||||
#elif defined(PSP2)
|
||||
UNUSED(portable);
|
||||
snprintf(out, outLength, "ux0:data/%s", projectName);
|
||||
sceIoMkdir(out, 0777);
|
||||
#elif defined(GEKKO) || defined(__SWITCH__)
|
||||
UNUSED(portable);
|
||||
snprintf(out, outLength, "/%s", projectName);
|
||||
mkdir(out, 0777);
|
||||
#elif defined(_3DS)
|
||||
UNUSED(portable);
|
||||
snprintf(out, outLength, "/%s", projectName);
|
||||
FSUSER_CreateDirectory(sdmcArchive, fsMakePath(PATH_ASCII, out), 0);
|
||||
#elif defined(__HAIKU__)
|
||||
getcwd(out, outLength);
|
||||
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
|
||||
portable = VFileOpen(out, O_RDONLY);
|
||||
if (portable) {
|
||||
getcwd(out, outLength);
|
||||
portable->close(portable);
|
||||
return;
|
||||
}
|
||||
|
||||
char path[B_PATH_NAME_LENGTH];
|
||||
find_directory(B_USER_SETTINGS_DIRECTORY, 0, false, path, B_PATH_NAME_LENGTH);
|
||||
snprintf(out, outLength, "%s/%s", path, binaryName);
|
||||
mkdir(out, 0755);
|
||||
#else
|
||||
getcwd(out, outLength);
|
||||
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
|
||||
portable = VFileOpen(out, O_RDONLY);
|
||||
if (portable) {
|
||||
getcwd(out, outLength);
|
||||
portable->close(portable);
|
||||
return;
|
||||
}
|
||||
|
||||
char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
|
||||
if (xdgConfigHome && xdgConfigHome[0] == '/') {
|
||||
snprintf(out, outLength, "%s/%s", xdgConfigHome, binaryName);
|
||||
|
@ -268,6 +249,37 @@ void mCoreConfigDirectory(char* out, size_t outLength) {
|
|||
mkdir(out, 0755);
|
||||
#endif
|
||||
}
|
||||
|
||||
void mCoreConfigPortablePath(char* out, size_t outLength) {
|
||||
#ifdef _WIN32
|
||||
wchar_t wpath[MAX_PATH];
|
||||
HMODULE hModule = GetModuleHandleW(NULL);
|
||||
GetModuleFileNameW(hModule, wpath, MAX_PATH);
|
||||
PathRemoveFileSpecW(wpath);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wpath, -1, out, outLength, 0, 0);
|
||||
StringCchCatA(out, outLength, "\\portable.ini");
|
||||
#elif defined(PSP2) || defined(GEKKO) || defined(__SWITCH__) || defined(_3DS)
|
||||
out[0] = '\0';
|
||||
#else
|
||||
getcwd(out, outLength);
|
||||
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mCoreConfigIsPortable(void) {
|
||||
struct VFile* portable;
|
||||
char portableDir[PATH_MAX];
|
||||
mCoreConfigPortablePath(portableDir, sizeof(portableDir));
|
||||
if (portableDir[0]) {
|
||||
portable = VFileOpen(portableDir, O_RDONLY);
|
||||
if (portable) {
|
||||
portable->close(portable);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const char* mCoreConfigGetValue(const struct mCoreConfig* config, const char* key) {
|
||||
|
|
|
@ -160,12 +160,23 @@ bool mCorePreloadVFCB(struct mCore* core, struct VFile* vf, void (cb)(size_t, si
|
|||
vfm = VFileMemChunk(NULL, size);
|
||||
#endif
|
||||
|
||||
uint8_t buffer[2048];
|
||||
size_t chunkSize;
|
||||
#ifdef FIXED_ROM_BUFFER
|
||||
uint8_t* buffer = (uint8_t*) romBuffer;
|
||||
chunkSize = 0x10000;
|
||||
#else
|
||||
uint8_t buffer[0x4000];
|
||||
chunkSize = sizeof(buffer);
|
||||
#endif
|
||||
ssize_t read;
|
||||
size_t total = 0;
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) {
|
||||
while ((read = vf->read(vf, buffer, chunkSize)) > 0) {
|
||||
#ifdef FIXED_ROM_BUFFER
|
||||
buffer += read;
|
||||
#else
|
||||
vfm->write(vfm, buffer, read);
|
||||
#endif
|
||||
total += read;
|
||||
if (cb) {
|
||||
cb(total, size, context);
|
||||
|
|
|
@ -126,6 +126,7 @@ static struct CLIDebuggerCommandAlias _debuggerCommandAliases[] = {
|
|||
{ "d", "delete" },
|
||||
{ "dis", "disassemble" },
|
||||
{ "disasm", "disassemble" },
|
||||
{ "fin", "finish" },
|
||||
{ "h", "help" },
|
||||
{ "i", "status" },
|
||||
{ "info", "status" },
|
||||
|
@ -753,8 +754,10 @@ static bool _doTrace(struct CLIDebugger* debugger) {
|
|||
--debugger->traceRemaining;
|
||||
}
|
||||
if (!debugger->traceRemaining) {
|
||||
debugger->traceVf->close(debugger->traceVf);
|
||||
debugger->traceVf = NULL;
|
||||
if (debugger->traceVf) {
|
||||
debugger->traceVf->close(debugger->traceVf);
|
||||
debugger->traceVf = NULL;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -296,10 +296,6 @@ static void _log(struct mLogger* logger, int category, enum mLogLevel level, con
|
|||
|
||||
static void _updateLoading(size_t read, size_t size, void* context) {
|
||||
struct mGUIRunner* runner = context;
|
||||
if (read & 0x3FFFF) {
|
||||
return;
|
||||
}
|
||||
|
||||
runner->params.drawStart();
|
||||
if (runner->params.guiPrepare) {
|
||||
runner->params.guiPrepare();
|
||||
|
@ -388,7 +384,13 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
|
|||
mInputMapInit(&runner->core->inputMap, &GBAInputInfo);
|
||||
|
||||
struct VFile* rom = mDirectorySetOpenPath(&runner->core->dirs, path, runner->core->isROM);
|
||||
if (runner->setFrameLimiter) {
|
||||
runner->setFrameLimiter(runner, false);
|
||||
}
|
||||
found = mCorePreloadVFCB(runner->core, rom, _updateLoading, runner);
|
||||
if (runner->setFrameLimiter) {
|
||||
runner->setFrameLimiter(runner, true);
|
||||
}
|
||||
|
||||
#ifdef FIXED_ROM_BUFFER
|
||||
extern size_t romBufferSize;
|
||||
|
|
|
@ -152,7 +152,6 @@ static void _GBCoreDeinit(struct mCore* core) {
|
|||
if (gbcore->cheatDevice) {
|
||||
mCheatDeviceDestroy(gbcore->cheatDevice);
|
||||
}
|
||||
free(gbcore->cheatDevice);
|
||||
mCoreConfigFreeOpts(&core->opts);
|
||||
free(core);
|
||||
}
|
||||
|
|
|
@ -160,6 +160,10 @@ bool GBLoadSave(struct GB* gb, struct VFile* vf) {
|
|||
if (gb->sramSize) {
|
||||
GBResizeSram(gb, gb->sramSize);
|
||||
GBMBCSwitchSramBank(gb, gb->memory.sramCurrentBank);
|
||||
|
||||
if (gb->memory.mbcType == GB_MBC3_RTC) {
|
||||
GBMBCRTCRead(gb);
|
||||
}
|
||||
}
|
||||
return vf;
|
||||
}
|
||||
|
|
|
@ -570,7 +570,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
|
|||
maps += GB_SIZE_MAP;
|
||||
}
|
||||
if (softwareRenderer->d.disableBG) {
|
||||
memset(&softwareRenderer->row[startX], 0, endX - startX);
|
||||
memset(&softwareRenderer->row[startX], 0, (endX - startX) * sizeof(softwareRenderer->row[0]));
|
||||
}
|
||||
if (GBRegisterLCDCIsBgEnable(softwareRenderer->lcdc) || softwareRenderer->model >= GB_MODEL_CGB) {
|
||||
int wy = softwareRenderer->wy + softwareRenderer->currentWy;
|
||||
|
@ -592,7 +592,7 @@ static void GBVideoSoftwareRendererDrawRange(struct GBVideoRenderer* renderer, i
|
|||
GBVideoSoftwareRendererDrawBackground(softwareRenderer, maps, startX, endX, softwareRenderer->scx - softwareRenderer->offsetScx, softwareRenderer->scy + y - softwareRenderer->offsetScy, renderer->highlightBG);
|
||||
}
|
||||
} else if (!softwareRenderer->d.disableBG) {
|
||||
memset(&softwareRenderer->row[startX], 0, endX - startX);
|
||||
memset(&softwareRenderer->row[startX], 0, (endX - startX) * sizeof(softwareRenderer->row[0]));
|
||||
}
|
||||
|
||||
if (GBRegisterLCDCIsObjEnable(softwareRenderer->lcdc) && !softwareRenderer->d.disableOBJ) {
|
||||
|
|
|
@ -664,6 +664,13 @@ static void _unLz77(struct GBA* gba, int width) {
|
|||
while (bytes--) {
|
||||
if (remaining) {
|
||||
--remaining;
|
||||
} else {
|
||||
mLOG(GBA_BIOS, GAME_ERROR, "Improperly compressed LZ77 data at %08X. "
|
||||
"This will lead to a buffer overrun at %08X and may crash on hardware.",
|
||||
cpu->gprs[0], cpu->gprs[1]);
|
||||
if (gba->vbaBugCompat) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (width == 2) {
|
||||
byte = (int16_t) cpu->memory.load16(cpu, disp & ~1, 0);
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
|
||||
#define MAX_LINE_LENGTH 128
|
||||
|
||||
DEFINE_VECTOR(GBACheatPatchList, struct GBACheatPatch);
|
||||
|
||||
static void _addBreakpoint(struct mCheatDevice* device, struct GBACheatSet* cheats) {
|
||||
if (!device->p || !cheats->hook) {
|
||||
return;
|
||||
|
@ -39,13 +41,14 @@ static void _patchROM(struct mCheatDevice* device, struct GBACheatSet* cheats) {
|
|||
if (!device->p) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < MAX_ROM_PATCHES; ++i) {
|
||||
if (!cheats->romPatches[i].exists || cheats->romPatches[i].applied) {
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatPatchListSize(&cheats->romPatches); ++i) {
|
||||
struct GBACheatPatch* patch = GBACheatPatchListGetPointer(&cheats->romPatches, i);
|
||||
if (patch->applied) {
|
||||
continue;
|
||||
}
|
||||
GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].newValue, &cheats->romPatches[i].oldValue);
|
||||
cheats->romPatches[i].applied = true;
|
||||
GBAPatch16(device->p->cpu, patch->address, patch->newValue, &patch->oldValue);
|
||||
patch->applied = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,13 +56,14 @@ static void _unpatchROM(struct mCheatDevice* device, struct GBACheatSet* cheats)
|
|||
if (!device->p) {
|
||||
return;
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < MAX_ROM_PATCHES; ++i) {
|
||||
if (!cheats->romPatches[i].exists || !cheats->romPatches[i].applied) {
|
||||
size_t i;
|
||||
for (i = 0; i < GBACheatPatchListSize(&cheats->romPatches); ++i) {
|
||||
struct GBACheatPatch* patch = GBACheatPatchListGetPointer(&cheats->romPatches, i);
|
||||
if (!patch->applied) {
|
||||
continue;
|
||||
}
|
||||
GBAPatch16(device->p->cpu, cheats->romPatches[i].address, cheats->romPatches[i].oldValue, 0);
|
||||
cheats->romPatches[i].applied = false;
|
||||
GBAPatch16(device->p->cpu, patch->address, patch->oldValue, NULL);
|
||||
patch->applied = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,10 +101,7 @@ static struct mCheatSet* GBACheatSetCreate(struct mCheatDevice* device, const ch
|
|||
|
||||
set->d.refresh = GBACheatRefresh;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < MAX_ROM_PATCHES; ++i) {
|
||||
set->romPatches[i].exists = false;
|
||||
}
|
||||
GBACheatPatchListInit(&set->romPatches, 4);
|
||||
return &set->d;
|
||||
}
|
||||
|
||||
|
@ -119,6 +120,7 @@ static void GBACheatSetDeinit(struct mCheatSet* set) {
|
|||
free(gbaset->hook);
|
||||
}
|
||||
}
|
||||
GBACheatPatchListDeinit(&gbaset->romPatches);
|
||||
}
|
||||
|
||||
static void GBACheatAddSet(struct mCheatSet* cheats, struct mCheatDevice* device) {
|
||||
|
|
|
@ -93,7 +93,7 @@ void GBACheatSetGameSharkVersion(struct GBACheatSet* cheats, enum GBACheatGameSh
|
|||
bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t op2) {
|
||||
enum GBAGameSharkType type = op1 >> 28;
|
||||
struct mCheat* cheat = 0;
|
||||
int romPatch = 0;
|
||||
struct GBACheatPatch* romPatch;
|
||||
|
||||
if (cheats->incompleteCheat != COMPLETE) {
|
||||
struct mCheat* incompleteCheat = mCheatListGetPointer(&cheats->d.list, cheats->incompleteCheat);
|
||||
|
@ -149,16 +149,10 @@ bool GBACheatAddGameSharkRaw(struct GBACheatSet* cheats, uint32_t op1, uint32_t
|
|||
cheats->incompleteCheat = mCheatListIndex(&cheats->d.list, cheat);
|
||||
break;
|
||||
case GSA_PATCH:
|
||||
while (cheats->romPatches[romPatch].exists) {
|
||||
++romPatch;
|
||||
if (romPatch >= MAX_ROM_PATCHES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cheats->romPatches[romPatch].address = BASE_CART0 | ((op1 & 0xFFFFFF) << 1);
|
||||
cheats->romPatches[romPatch].newValue = op2;
|
||||
cheats->romPatches[romPatch].applied = false;
|
||||
cheats->romPatches[romPatch].exists = true;
|
||||
romPatch = GBACheatPatchListAppend(&cheats->romPatches);
|
||||
romPatch->address = BASE_CART0 | ((op1 & 0xFFFFFF) << 1);
|
||||
romPatch->newValue = op2;
|
||||
romPatch->applied = false;
|
||||
return true;
|
||||
case GSA_BUTTON:
|
||||
switch (op1 & 0x00F00000) {
|
||||
|
|
|
@ -230,16 +230,10 @@ static bool _addPAR3Special(struct GBACheatSet* cheats, uint32_t op2) {
|
|||
break;
|
||||
}
|
||||
if (romPatch >= 0) {
|
||||
while (cheats->romPatches[romPatch].exists) {
|
||||
++romPatch;
|
||||
if (romPatch >= MAX_ROM_PATCHES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cheats->romPatches[romPatch].address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
|
||||
cheats->romPatches[romPatch].applied = false;
|
||||
cheats->romPatches[romPatch].exists = true;
|
||||
cheats->incompletePatch = &cheats->romPatches[romPatch];
|
||||
struct GBACheatPatch* patch = GBACheatPatchListAppend(&cheats->romPatches);
|
||||
patch->address = BASE_CART0 | ((op2 & 0xFFFFFF) << 1);
|
||||
patch->applied = false;
|
||||
cheats->incompletePatch = patch;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -242,7 +242,6 @@ static void _GBACoreDeinit(struct mCore* core) {
|
|||
if (gbacore->cheatDevice) {
|
||||
mCheatDeviceDestroy(gbacore->cheatDevice);
|
||||
}
|
||||
free(gbacore->cheatDevice);
|
||||
free(gbacore->audioMixer);
|
||||
mCoreConfigFreeOpts(&core->opts);
|
||||
free(core);
|
||||
|
|
|
@ -612,6 +612,9 @@ void _eReaderWriteControl0(struct GBACartridgeHardware* hw, uint8_t value) {
|
|||
hw->eReaderRegisterControl0 = control;
|
||||
if (!EReaderControl0IsScan(oldControl) && EReaderControl0IsScan(control)) {
|
||||
if (hw->eReaderX > 1000) {
|
||||
if (hw->eReaderDots) {
|
||||
memset(hw->eReaderDots, 0, EREADER_DOTCODE_SIZE);
|
||||
}
|
||||
int i;
|
||||
for (i = 0; i < EREADER_CARDS_MAX; ++i) {
|
||||
if (!hw->eReaderCards[i].data) {
|
||||
|
|
|
@ -347,10 +347,8 @@ void GBAVideoProxyRendererFinishFrame(struct GBAVideoRenderer* renderer) {
|
|||
static void GBAVideoProxyRendererGetPixels(struct GBAVideoRenderer* renderer, size_t* stride, const void** pixels) {
|
||||
struct GBAVideoProxyRenderer* proxyRenderer = (struct GBAVideoProxyRenderer*) renderer;
|
||||
if (proxyRenderer->logger->block && proxyRenderer->logger->wait) {
|
||||
// Insert an extra item into the queue to make sure it gets flushed
|
||||
mVideoLoggerRendererFlush(proxyRenderer->logger);
|
||||
proxyRenderer->logger->wait(proxyRenderer->logger);
|
||||
proxyRenderer->logger->postEvent(proxyRenderer->logger, LOGGER_EVENT_GET_PIXELS);
|
||||
mVideoLoggerRendererFlush(proxyRenderer->logger);
|
||||
*pixels = proxyRenderer->logger->pixelBuffer;
|
||||
*stride = proxyRenderer->logger->pixelStride;
|
||||
} else {
|
||||
|
|
|
@ -108,6 +108,7 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
|
|||
gba->idleOptimization = IDLE_LOOP_REMOVE;
|
||||
gba->idleLoop = IDLE_LOOP_NONE;
|
||||
|
||||
gba->vbaBugCompat = false;
|
||||
gba->hardCrash = true;
|
||||
gba->allowOpposingDirections = true;
|
||||
|
||||
|
|
|
@ -95,8 +95,12 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uin
|
|||
}
|
||||
switch (address) {
|
||||
case GPIO_REG_DATA:
|
||||
hw->pinState &= ~hw->direction;
|
||||
hw->pinState |= value & hw->direction;
|
||||
if (!hw->p->vbaBugCompat) {
|
||||
hw->pinState &= ~hw->direction;
|
||||
hw->pinState |= value & hw->direction;
|
||||
} else {
|
||||
hw->pinState = value;
|
||||
}
|
||||
_readPins(hw);
|
||||
break;
|
||||
case GPIO_REG_DIRECTION:
|
||||
|
@ -616,14 +620,14 @@ void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASeria
|
|||
STORE_16(hw->direction, 0, &state->hw.pinDirection);
|
||||
state->hw.devices = hw->devices;
|
||||
|
||||
STORE_32(hw->rtc.bytesRemaining, 0, &state->hw.rtc.bytesRemaining);
|
||||
STORE_32(hw->rtc.transferStep, 0, &state->hw.rtc.transferStep);
|
||||
STORE_32(hw->rtc.bitsRead, 0, &state->hw.rtc.bitsRead);
|
||||
STORE_32(hw->rtc.bits, 0, &state->hw.rtc.bits);
|
||||
state->hw.rtc.commandActive = hw->rtc.commandActive;
|
||||
state->hw.rtc.command = hw->rtc.command;
|
||||
state->hw.rtc.control = hw->rtc.control;
|
||||
memcpy(state->hw.rtc.time, hw->rtc.time, sizeof(state->hw.rtc.time));
|
||||
STORE_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining);
|
||||
STORE_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep);
|
||||
STORE_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead);
|
||||
STORE_32(hw->rtc.bits, 0, &state->hw.rtcBits);
|
||||
STORE_32(hw->rtc.commandActive, 0, &state->hw.rtcCommandActive);
|
||||
STORE_32(hw->rtc.command, 0, &state->hw.rtcCommand);
|
||||
STORE_32(hw->rtc.control, 0, &state->hw.rtcControl);
|
||||
memcpy(state->hw.time, hw->rtc.time, sizeof(state->hw.time));
|
||||
|
||||
STORE_16(hw->gyroSample, 0, &state->hw.gyroSample);
|
||||
flags1 = GBASerializedHWFlags1SetGyroEdge(flags1, hw->gyroEdge);
|
||||
|
@ -648,14 +652,14 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
|||
LOAD_16(hw->direction, 0, &state->hw.pinDirection);
|
||||
hw->devices = state->hw.devices;
|
||||
|
||||
LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtc.bytesRemaining);
|
||||
LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtc.transferStep);
|
||||
LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtc.bitsRead);
|
||||
LOAD_32(hw->rtc.bits, 0, &state->hw.rtc.bits);
|
||||
hw->rtc.commandActive = state->hw.rtc.commandActive;
|
||||
hw->rtc.command = state->hw.rtc.command;
|
||||
hw->rtc.control = state->hw.rtc.control;
|
||||
memcpy(hw->rtc.time, state->hw.rtc.time, sizeof(hw->rtc.time));
|
||||
LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining);
|
||||
LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep);
|
||||
LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead);
|
||||
LOAD_32(hw->rtc.bits, 0, &state->hw.rtcBits);
|
||||
LOAD_32(hw->rtc.commandActive, 0, &state->hw.rtcCommandActive);
|
||||
LOAD_32(hw->rtc.command, 0, &state->hw.rtcCommand);
|
||||
LOAD_32(hw->rtc.control, 0, &state->hw.rtcControl);
|
||||
memcpy(hw->rtc.time, state->hw.time, sizeof(hw->rtc.time));
|
||||
|
||||
LOAD_16(hw->gyroSample, 0, &state->hw.gyroSample);
|
||||
hw->gyroEdge = GBASerializedHWFlags1GetGyroEdge(flags1);
|
||||
|
|
|
@ -207,6 +207,7 @@ bool GBAOverrideFind(const struct Configuration* config, struct GBACartridgeOver
|
|||
override->hardware = HW_NONE;
|
||||
override->idleLoop = IDLE_LOOP_NONE;
|
||||
override->mirroring = false;
|
||||
override->vbaBugCompat = false;
|
||||
bool found = false;
|
||||
|
||||
int i;
|
||||
|
@ -320,6 +321,8 @@ void GBAOverrideApply(struct GBA* gba, const struct GBACartridgeOverride* overri
|
|||
GBASavedataForceType(&gba->memory.savedata, override->savetype);
|
||||
}
|
||||
|
||||
gba->vbaBugCompat = override->vbaBugCompat;
|
||||
|
||||
if (override->hardware != HW_NO_OVERRIDE) {
|
||||
GBAHardwareClear(&gba->memory.hw);
|
||||
|
||||
|
@ -376,6 +379,7 @@ void GBAOverrideApplyDefaults(struct GBA* gba, const struct Configuration* overr
|
|||
// Enable FLASH1M and RTC on Pokémon FireRed ROM hacks
|
||||
override.savetype = SAVEDATA_FLASH1M;
|
||||
override.hardware = HW_RTC;
|
||||
override.vbaBugCompat = true;
|
||||
GBAOverrideApply(gba, &override);
|
||||
} else if (GBAOverrideFind(overrides, &override)) {
|
||||
GBAOverrideApply(gba, &override);
|
||||
|
|
|
@ -53,6 +53,9 @@ struct GBAVideoGLUniform {
|
|||
};
|
||||
|
||||
#define PALETTE_ENTRY "#define PALETTE_ENTRY(x) (vec3((ivec3(0x1F, 0x3E0, 0x7C00) & (x)) >> ivec3(0, 5, 10)) / 31.)\n"
|
||||
#define MOSAIC \
|
||||
"#define MOSAIC(LHS, RHS) (((int(LHS) * mosaicTable[RHS]) >> 12) * RHS)\n" \
|
||||
"const int mosaicTable[17] = int[17](0, 4096, 2048, 1366, 1024, 820, 683, 586, 512, 456, 410, 373, 342, 316, 293, 274, 256);\n"
|
||||
|
||||
static const GLchar* const _gles3Header =
|
||||
"#version 300 es\n"
|
||||
|
@ -82,29 +85,25 @@ static const char* const _vertexShader =
|
|||
"}";
|
||||
|
||||
static const char* const _renderTile16 =
|
||||
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
|
||||
" int address = charBase + tile * 16 + (localCoord.x >> 2) + (localCoord.y << 1);\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n"
|
||||
" int entry = (halfrow >> (4 * (localCoord.x & 3))) & 15;\n"
|
||||
" if (entry == 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" int paletteEntry = palette[paletteId * 16 + entry];\n"
|
||||
" vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
|
||||
" return color;\n"
|
||||
" return paletteId * 16 + entry;\n"
|
||||
"}";
|
||||
|
||||
static const char* const _renderTile256 =
|
||||
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
|
||||
" int address = charBase + tile * 32 + (localCoord.x >> 1) + (localCoord.y << 2);\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n"
|
||||
" int entry = (halfrow >> (8 * (localCoord.x & 1))) & 255;\n"
|
||||
" if (entry == 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" int paletteEntry = palette[entry];\n"
|
||||
" vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
|
||||
" return color;\n"
|
||||
" return entry;\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsMode0[] = {
|
||||
|
@ -121,9 +120,10 @@ static const struct GBAVideoGLUniform _uniformsMode0[] = {
|
|||
};
|
||||
|
||||
static const char* const _renderMode0 =
|
||||
MOSAIC
|
||||
"in vec2 texCoord;\n"
|
||||
"uniform isampler2D vram;\n"
|
||||
"uniform int palette[256];\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform int screenBase;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform int size;\n"
|
||||
|
@ -131,15 +131,15 @@ static const char* const _renderMode0 =
|
|||
"uniform ivec2 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\n"
|
||||
|
||||
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||
|
||||
"void main() {\n"
|
||||
" ivec2 coord = ivec2(texCoord);\n"
|
||||
" if (mosaic.x > 1) {\n"
|
||||
" coord.x -= coord.x % mosaic.x;\n"
|
||||
" coord.x = MOSAIC(coord.x, mosaic.x);\n"
|
||||
" }\n"
|
||||
" if (mosaic.y > 1) {\n"
|
||||
" coord.y -= coord.y % mosaic.y;\n"
|
||||
" coord.y = MOSAIC(coord.y, mosaic.y);\n"
|
||||
" }\n"
|
||||
" coord += (ivec2(0x1FF, 0x1FF000) & offset[int(texCoord.y)]) >> ivec2(0, 12);\n"
|
||||
" ivec2 wrap = ivec2(255, 255);\n"
|
||||
|
@ -165,18 +165,19 @@ static const char* const _renderMode0 =
|
|||
" coord.y ^= 7;\n"
|
||||
" }\n"
|
||||
" int tile = map & 1023;\n"
|
||||
" color = renderTile(tile, map >> 12, coord & 7);\n"
|
||||
" int paletteEntry = renderTile(tile, map >> 12, coord & 7);\n"
|
||||
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
|
||||
"}";
|
||||
|
||||
static const char* const _fetchTileOverflow =
|
||||
"vec4 fetchTile(ivec2 coord) {\n"
|
||||
"int fetchTile(ivec2 coord) {\n"
|
||||
" int sizeAdjusted = (0x8000 << size) - 1;\n"
|
||||
" coord &= sizeAdjusted;\n"
|
||||
" return renderTile(coord);\n"
|
||||
"}";
|
||||
|
||||
static const char* const _fetchTileNoOverflow =
|
||||
"vec4 fetchTile(ivec2 coord) {\n"
|
||||
"int fetchTile(ivec2 coord) {\n"
|
||||
" int sizeAdjusted = (0x8000 << size) - 1;\n"
|
||||
" ivec2 outerCoord = coord & ~sizeAdjusted;\n"
|
||||
" if ((outerCoord.x | outerCoord.y) != 0) {\n"
|
||||
|
@ -222,9 +223,10 @@ static const char* const _interpolate =
|
|||
"}\n";
|
||||
|
||||
static const char* const _renderMode2 =
|
||||
MOSAIC
|
||||
"in vec2 texCoord;\n"
|
||||
"uniform isampler2D vram;\n"
|
||||
"uniform int palette[256];\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform int screenBase;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform int size;\n"
|
||||
|
@ -233,11 +235,11 @@ static const char* const _renderMode2 =
|
|||
"uniform ivec2 mosaic;\n"
|
||||
"OUT(0) out vec4 color;\n"
|
||||
|
||||
"vec4 fetchTile(ivec2 coord);\n"
|
||||
"int fetchTile(ivec2 coord);\n"
|
||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
||||
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
|
||||
|
||||
"vec4 renderTile(ivec2 coord) {\n"
|
||||
"int renderTile(ivec2 coord) {\n"
|
||||
" int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
|
||||
" int mapAddress = screenBase + (map >> 1);\n"
|
||||
" int twomaps = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0).r;\n"
|
||||
|
@ -248,9 +250,7 @@ static const char* const _renderMode2 =
|
|||
" if (entry == 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" int paletteEntry = palette[entry];\n"
|
||||
" vec4 color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
|
||||
" return color;\n"
|
||||
" return entry;\n"
|
||||
"}\n"
|
||||
|
||||
"void main() {\n"
|
||||
|
@ -258,10 +258,10 @@ static const char* const _renderMode2 =
|
|||
" ivec2 offset[4];\n"
|
||||
" vec2 incoord = texCoord;\n"
|
||||
" if (mosaic.x > 1) {\n"
|
||||
" incoord.x = floor(incoord.x - float(int(incoord.x) % mosaic.x));\n"
|
||||
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
|
||||
" }\n"
|
||||
" if (mosaic.y > 1) {\n"
|
||||
" incoord.y = floor(incoord.y - float(int(incoord.y) % mosaic.y));\n"
|
||||
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
|
||||
" }\n"
|
||||
" loadAffine(int(incoord.y), mat, offset);\n"
|
||||
" float y = fract(incoord.y);\n"
|
||||
|
@ -273,7 +273,8 @@ static const char* const _renderMode2 =
|
|||
" float lin = start + y * 0.25;\n"
|
||||
" vec2 mixedTransform = interpolate(mat, lin);\n"
|
||||
" vec2 mixedOffset = interpolate(offset, lin);\n"
|
||||
" color = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
|
||||
" int paletteEntry = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
|
||||
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsMode35[] = {
|
||||
|
@ -290,6 +291,7 @@ static const struct GBAVideoGLUniform _uniformsMode35[] = {
|
|||
};
|
||||
|
||||
static const char* const _renderMode35 =
|
||||
MOSAIC
|
||||
"in vec2 texCoord;\n"
|
||||
"uniform isampler2D vram;\n"
|
||||
"uniform int charBase;\n"
|
||||
|
@ -307,10 +309,10 @@ static const char* const _renderMode35 =
|
|||
" ivec2 offset[4];\n"
|
||||
" vec2 incoord = texCoord;\n"
|
||||
" if (mosaic.x > 1) {\n"
|
||||
" incoord.x = floor(incoord.x - float(int(incoord.x) % mosaic.x));\n"
|
||||
" incoord.x = floor(MOSAIC(incoord.x, mosaic.x));\n"
|
||||
" }\n"
|
||||
" if (mosaic.y > 1) {\n"
|
||||
" incoord.y = floor(incoord.y - float(int(incoord.y) % mosaic.y));\n"
|
||||
" incoord.y = floor(MOSAIC(incoord.y, mosaic.y));\n"
|
||||
" }\n"
|
||||
" loadAffine(int(incoord.y), mat, offset);\n"
|
||||
" float y = fract(incoord.y);\n"
|
||||
|
@ -349,9 +351,10 @@ static const struct GBAVideoGLUniform _uniformsMode4[] = {
|
|||
};
|
||||
|
||||
static const char* const _renderMode4 =
|
||||
MOSAIC
|
||||
"in vec2 texCoord;\n"
|
||||
"uniform isampler2D vram;\n"
|
||||
"uniform int palette[256];\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform ivec2 size;\n"
|
||||
"uniform ivec4 transform[160];\n"
|
||||
|
@ -367,10 +370,10 @@ static const char* const _renderMode4 =
|
|||
" ivec2 offset[4];\n"
|
||||
" vec2 incoord = texCoord;\n"
|
||||
" if (mosaic.x > 1) {\n"
|
||||
" incoord.x = floor(incoord.x - float(int(incoord.x) % mosaic.x));\n"
|
||||
" incoord.x = floor(MOSAIC(incoord.x, mosaic.x));\n"
|
||||
" }\n"
|
||||
" if (mosaic.y > 1) {\n"
|
||||
" incoord.y = floor(incoord.y - float(int(incoord.y) % mosaic.y));\n"
|
||||
" incoord.y = floor(MOSAIC(incoord.y, mosaic.y));\n"
|
||||
" }\n"
|
||||
" loadAffine(int(incoord.y), mat, offset);\n"
|
||||
" float y = fract(incoord.y);\n"
|
||||
|
@ -395,8 +398,7 @@ static const char* const _renderMode4 =
|
|||
" if (entry == 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" int paletteEntry = palette[entry];\n"
|
||||
" color = vec4(PALETTE_ENTRY(paletteEntry), 1.);\n"
|
||||
" color = texelFetch(palette, ivec2(entry, int(texCoord.y)), 0);\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsObj[] = {
|
||||
|
@ -417,9 +419,10 @@ static const struct GBAVideoGLUniform _uniformsObj[] = {
|
|||
};
|
||||
|
||||
static const char* const _renderObj =
|
||||
MOSAIC
|
||||
"in vec2 texCoord;\n"
|
||||
"uniform isampler2D vram;\n"
|
||||
"uniform int palette[256];\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform int stride;\n"
|
||||
"uniform int localPalette;\n"
|
||||
|
@ -433,30 +436,33 @@ static const char* const _renderObj =
|
|||
"OUT(1) out ivec4 flags;\n"
|
||||
"OUT(2) out ivec4 window;\n"
|
||||
|
||||
"vec4 renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec2 incoord = texCoord;\n"
|
||||
" if (mosaic.x > 1) {\n"
|
||||
" int x = int(incoord.x);\n"
|
||||
" incoord.x = float(clamp(x - (mosaic.z + x) % mosaic.x, 0, dims.z - 1));\n"
|
||||
" x = MOSAIC(mosaic.z + x, mosaic.x) - mosaic.z;\n"
|
||||
" incoord.x = float(clamp(x, 0, dims.z - 1));\n"
|
||||
" } else if (mosaic.x < -1) {\n"
|
||||
" int x = dims.z - int(incoord.x) - 1;\n"
|
||||
" incoord.x = float(clamp(dims.z - x + (mosaic.z + x) % -mosaic.x - 1, 0, dims.z - 1));\n"
|
||||
" x = dims.z - MOSAIC(mosaic.z + x, -mosaic.x) + mosaic.z - 1;\n"
|
||||
" incoord.x = float(clamp(x, 0, dims.z - 1));\n"
|
||||
" }\n"
|
||||
" if (cyclesRemaining[int(incoord.y) + mosaic.w] <= 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" if (mosaic.y > 1) {\n"
|
||||
" int y = int(incoord.y);\n"
|
||||
" incoord.y = float(clamp(y - (mosaic.w + y) % mosaic.y, 0, dims.w - 1));\n"
|
||||
" y = MOSAIC(mosaic.w + y, mosaic.y) - mosaic.w;"
|
||||
" incoord.y = float(clamp(y, 0, dims.w - 1));\n"
|
||||
" }\n"
|
||||
" ivec2 coord = ivec2(transform * (incoord - vec2(dims.zw) / 2.) + vec2(dims.xy) / 2.);\n"
|
||||
" if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" vec4 pix = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"
|
||||
" color = pix;\n"
|
||||
" int paletteEntry = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"
|
||||
" color = texelFetch(palette, ivec2(paletteEntry + 256, coord.y), 0);\n"
|
||||
" flags = inflags;\n"
|
||||
" gl_FragDepth = float(flags.x) / 16.;\n"
|
||||
" window = ivec4(objwin, 0);\n"
|
||||
|
@ -551,8 +557,8 @@ static const struct GBAVideoGLUniform _uniformsFinalize[] = {
|
|||
{ "layers", GBA_GL_FINALIZE_LAYERS, },
|
||||
{ "objFlags", GBA_GL_FINALIZE_FLAGS, },
|
||||
{ "window", GBA_GL_FINALIZE_WINDOW, },
|
||||
{ "backdrop", GBA_GL_FINALIZE_BACKDROP, },
|
||||
{ "backdropFlags", GBA_GL_FINALIZE_BACKDROPFLAGS, },
|
||||
{ "palette", GBA_GL_FINALIZE_PALETTE, },
|
||||
{ "backdropFlags", GBA_GL_FINALIZE_BACKDROP, },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
@ -562,7 +568,7 @@ static const char* const _finalize =
|
|||
"uniform sampler2D layers[5];\n"
|
||||
"uniform isampler2D objFlags;\n"
|
||||
"uniform isampler2D window;\n"
|
||||
"uniform sampler2D backdrop;\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform isampler2D backdropFlags;\n"
|
||||
"out vec4 color;\n"
|
||||
|
||||
|
@ -582,7 +588,7 @@ static const char* const _finalize =
|
|||
"}\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec4 topPixel = texelFetch(backdrop, ivec2(0, texCoord.y), 0);\n"
|
||||
" vec4 topPixel = texelFetch(palette, ivec2(0, texCoord.y), 0);\n"
|
||||
" vec4 bottomPixel = topPixel;\n"
|
||||
" ivec4 topFlags = ivec4(texelFetch(backdropFlags, ivec2(0, texCoord.y), 0));\n"
|
||||
" ivec4 bottomFlags = topFlags;\n"
|
||||
|
@ -745,8 +751,7 @@ static void _initFramebuffers(struct GBAVideoGLRenderer* glRenderer) {
|
|||
_initFramebufferTextureEx(glRenderer->layers[GBA_GL_TEX_OBJ_DEPTH], GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, GL_DEPTH_STENCIL_ATTACHMENT, glRenderer->scale);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
|
||||
_initFramebufferTexture(glRenderer->layers[GBA_GL_TEX_BACKDROP_COLOR], GL_RGB, GL_COLOR_ATTACHMENT0, 0);
|
||||
_initFramebufferTextureEx(glRenderer->layers[GBA_GL_TEX_BACKDROP_FLAGS], GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, GL_COLOR_ATTACHMENT1, 0);
|
||||
_initFramebufferTextureEx(glRenderer->layers[GBA_GL_TEX_BACKDROP], GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE, GL_COLOR_ATTACHMENT0, 0);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_WINDOW]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, glRenderer->layers[GBA_GL_TEX_WINDOW], 0);
|
||||
|
@ -776,6 +781,12 @@ void GBAVideoGLRendererInit(struct GBAVideoRenderer* renderer) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_R16UI, 256, 192, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, 0);
|
||||
|
||||
glGenTextures(1, &glRenderer->paletteTex);
|
||||
glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
|
||||
glGenBuffers(1, &glRenderer->vbo);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, glRenderer->vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(_vertices), _vertices, GL_STATIC_DRAW);
|
||||
|
@ -889,6 +900,7 @@ void GBAVideoGLRendererDeinit(struct GBAVideoRenderer* renderer) {
|
|||
glDeleteFramebuffers(GBA_GL_FBO_MAX, glRenderer->fbo);
|
||||
glDeleteTextures(GBA_GL_TEX_MAX, glRenderer->layers);
|
||||
glDeleteTextures(1, &glRenderer->vramTex);
|
||||
glDeleteTextures(1, &glRenderer->paletteTex);
|
||||
glDeleteBuffers(1, &glRenderer->vbo);
|
||||
|
||||
_deleteShader(&glRenderer->bgShader[0]);
|
||||
|
@ -918,8 +930,19 @@ void GBAVideoGLRendererReset(struct GBAVideoRenderer* renderer) {
|
|||
glRenderer->firstY = -1;
|
||||
glRenderer->dispcnt = 0x0080;
|
||||
glRenderer->mosaic = 0;
|
||||
glRenderer->nextPalette = 0;
|
||||
glRenderer->paletteDirtyScanlines = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
memset(glRenderer->shadowRegs, 0, sizeof(glRenderer->shadowRegs));
|
||||
glRenderer->regsDirty = 0xFFFFFFFFFFFEULL;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 512; ++i) {
|
||||
int r = M_R5(glRenderer->d.palette[i]);
|
||||
int g = M_G5(glRenderer->d.palette[i]) << 1;
|
||||
g |= g >> 5;
|
||||
int b = M_B5(glRenderer->d.palette[i]);
|
||||
glRenderer->shadowPalette[0][i] = (r << 11) | (g << 5) | b;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAVideoGLRendererWriteVRAM(struct GBAVideoRenderer* renderer, uint32_t address) {
|
||||
|
@ -938,6 +961,12 @@ void GBAVideoGLRendererWritePalette(struct GBAVideoRenderer* renderer, uint32_t
|
|||
UNUSED(address);
|
||||
UNUSED(value);
|
||||
glRenderer->paletteDirty = true;
|
||||
int r = M_R5(value);
|
||||
int g = M_G5(value) << 1;
|
||||
g |= g >> 5;
|
||||
int b = M_B5(value);
|
||||
glRenderer->paletteDirtyScanlines = GBA_VIDEO_VERTICAL_PIXELS;
|
||||
glRenderer->shadowPalette[glRenderer->nextPalette][address >> 1] = (r << 11) | (g << 5) | b;
|
||||
}
|
||||
|
||||
uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer, uint32_t address, uint16_t value) {
|
||||
|
@ -1291,7 +1320,7 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
|||
glRenderer->firstAffine = -1;
|
||||
}
|
||||
|
||||
if (glRenderer->paletteDirty || _needsVramUpload(glRenderer, y) || glRenderer->oamDirty || glRenderer->regsDirty) {
|
||||
if (_needsVramUpload(glRenderer, y) || glRenderer->oamDirty || glRenderer->regsDirty) {
|
||||
if (glRenderer->firstY >= 0) {
|
||||
_drawScanlines(glRenderer, y - 1);
|
||||
glBindVertexArray(0);
|
||||
|
@ -1336,11 +1365,22 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
|||
glRenderer->bg[3].scanlineAffine[y * 4 + 2] = glRenderer->bg[3].affine.sx;
|
||||
glRenderer->bg[3].scanlineAffine[y * 4 + 3] = glRenderer->bg[3].affine.sy;
|
||||
|
||||
int oldPalette = glRenderer->nextPalette;
|
||||
glRenderer->nextPalette = y + 1;
|
||||
if (glRenderer->nextPalette >= GBA_VIDEO_VERTICAL_PIXELS) {
|
||||
glRenderer->nextPalette = 0;
|
||||
}
|
||||
if (glRenderer->paletteDirty) {
|
||||
for (i = 0; i < 512; ++i) {
|
||||
glRenderer->shadowPalette[i] = glRenderer->d.palette[i];
|
||||
memcpy(glRenderer->shadowPalette[glRenderer->nextPalette], glRenderer->shadowPalette[oldPalette], sizeof(glRenderer->shadowPalette[0]));
|
||||
if (glRenderer->paletteDirtyScanlines > 0) {
|
||||
--glRenderer->paletteDirtyScanlines;
|
||||
}
|
||||
if (!glRenderer->paletteDirtyScanlines) {
|
||||
glRenderer->paletteDirty = false;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glRenderer->shadowPalette);
|
||||
}
|
||||
glRenderer->paletteDirty = false;
|
||||
}
|
||||
|
||||
if (_needsVramUpload(glRenderer, y)) {
|
||||
|
@ -1379,6 +1419,8 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
|||
glClearDepth(1);
|
||||
#endif
|
||||
glClearStencil(0);
|
||||
glDepthMask(GL_TRUE);
|
||||
glStencilMask(1);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_OBJ]);
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
|
@ -1406,24 +1448,24 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
|||
void _drawScanlines(struct GBAVideoGLRenderer* glRenderer, int y) {
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
|
||||
uint32_t backdrop = M_RGB5_TO_RGB8(glRenderer->shadowPalette[0]);
|
||||
glViewport(0, 0, 1, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
glScissor(0, glRenderer->firstY, 1, y - glRenderer->firstY + 1);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, glRenderer->fbo[GBA_GL_FBO_BACKDROP]);
|
||||
glDrawBuffers(2, (GLenum[]) { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 });
|
||||
glClearBufferfv(GL_COLOR, 0, (GLfloat[]) { ((backdrop >> 16) & 0xF8) / 248., ((backdrop >> 8) & 0xF8) / 248., (backdrop & 0xF8) / 248., 1.f });
|
||||
glClearBufferiv(GL_COLOR, 1, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 });
|
||||
|
||||
glDrawBuffers(2, (GLenum[]) { GL_NONE, GL_COLOR_ATTACHMENT1 });
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
glClearBufferiv(GL_COLOR, 0, (GLint[]) { 32, glRenderer->target1Bd | (glRenderer->target2Bd * 2) | (glRenderer->blendEffect * 4), glRenderer->blda, 0 });
|
||||
int i;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
glScissor(i + 1, glRenderer->firstY, 1, y - glRenderer->firstY + 1);
|
||||
glClearBufferiv(GL_COLOR, 1, (GLint[]) { glRenderer->bg[i].priority,
|
||||
glClearBufferiv(GL_COLOR, 0, (GLint[]) { glRenderer->bg[i].priority,
|
||||
glRenderer->bg[i].target1 | (glRenderer->bg[i].target2 << 1) | (glRenderer->blendEffect << 2),
|
||||
glRenderer->blda, 0 });
|
||||
}
|
||||
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
if (glRenderer->paletteDirty) {
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, glRenderer->paletteTex);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, GBA_VIDEO_VERTICAL_PIXELS, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, glRenderer->shadowPalette);
|
||||
}
|
||||
|
||||
GBAVideoGLRendererDrawWindow(glRenderer, y);
|
||||
if (GBARegisterDISPCNTIsObjEnable(glRenderer->dispcnt) && !glRenderer->d.disableOBJ) {
|
||||
|
@ -1632,9 +1674,9 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) {
|
|||
glActiveTexture(GL_TEXTURE0 + 6);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->bg[3].tex);
|
||||
glActiveTexture(GL_TEXTURE0 + 7);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_COLOR]);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->paletteTex);
|
||||
glActiveTexture(GL_TEXTURE0 + 8);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP_FLAGS]);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->layers[GBA_GL_TEX_BACKDROP]);
|
||||
|
||||
glUniform2i(uniforms[GBA_GL_VS_LOC], GBA_VIDEO_VERTICAL_PIXELS, 0);
|
||||
glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
|
@ -1642,9 +1684,8 @@ void _finalizeLayers(struct GBAVideoGLRenderer* renderer) {
|
|||
glUniform1iv(uniforms[GBA_GL_FINALIZE_LAYERS], 5, (GLint[]) { 3, 4, 5, 6, 1 });
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_FLAGS], 2);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_WINDOW], 0);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 7);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROPFLAGS], 8);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_PALETTE], 7);
|
||||
glUniform1i(uniforms[GBA_GL_FINALIZE_BACKDROP], 8);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
@ -1689,10 +1730,12 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
glBindVertexArray(shader->vao);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->vramTex);
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->paletteTex);
|
||||
glUniform2i(uniforms[GBA_GL_VS_LOC], totalHeight, 0);
|
||||
glUniform2i(uniforms[GBA_GL_VS_MAXPOS], totalWidth, totalHeight);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0);
|
||||
glUniform1iv(uniforms[GBA_GL_OBJ_PALETTE], 256, &renderer->shadowPalette[256]);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_CHARBASE], charBase);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c));
|
||||
|
@ -1775,9 +1818,11 @@ void _prepareBackground(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBa
|
|||
glViewport(0, 0, GBA_VIDEO_HORIZONTAL_PIXELS * renderer->scale, GBA_VIDEO_VERTICAL_PIXELS * renderer->scale);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->vramTex);
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, renderer->paletteTex);
|
||||
glUniform2i(uniforms[GBA_GL_VS_MAXPOS], GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||
glUniform1i(uniforms[GBA_GL_BG_VRAM], 0);
|
||||
glUniform1iv(uniforms[GBA_GL_OBJ_PALETTE], 256, renderer->shadowPalette);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1);
|
||||
if (background->mosaic) {
|
||||
glUniform2i(uniforms[GBA_GL_BG_MOSAIC], GBAMosaicControlGetBgV(renderer->mosaic) + 1, GBAMosaicControlGetBgH(renderer->mosaic) + 1);
|
||||
} else {
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
#include <mgba/internal/gba/io.h>
|
||||
#include <mgba/internal/gba/renderers/cache-set.h>
|
||||
|
||||
#include <mgba-util/arm-algo.h>
|
||||
#include <mgba-util/memory.h>
|
||||
|
||||
#define DIRTY_SCANLINE(R, Y) R->scanlineDirty[Y >> 5] |= (1U << (Y & 0x1F))
|
||||
|
|
|
@ -154,7 +154,7 @@ bool GBADeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
LOAD_32(gba->cpu->bankedSPSRs[i], i * sizeof(gba->cpu->bankedSPSRs[0]), state->cpu.bankedSPSRs);
|
||||
}
|
||||
gba->cpu->privilegeMode = gba->cpu->cpsr.priv;
|
||||
uint32_t pcMask = (gba->cpu->executionMode == MODE_THUMB ? WORD_SIZE_THUMB : WORD_SIZE_ARM) - 1;
|
||||
uint32_t pcMask = (gba->cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM) - 1;
|
||||
if (gba->cpu->gprs[ARM_PC] & pcMask) {
|
||||
mLOG(GBA_STATE, WARN, "Savestate has unaligned PC and is probably corrupted");
|
||||
gba->cpu->gprs[ARM_PC] &= ~pcMask;
|
||||
|
|
|
@ -149,6 +149,13 @@ void GBAVideoAssociateRenderer(struct GBAVideo* video, struct GBAVideoRenderer*
|
|||
renderer->vramOBJ[3] = _zeroes;
|
||||
renderer->oam = &video->oam;
|
||||
video->renderer->init(video->renderer);
|
||||
video->renderer->reset(video->renderer);
|
||||
renderer->writeVideoRegister(renderer, REG_DISPCNT, video->p->memory.io[REG_DISPCNT >> 1]);
|
||||
renderer->writeVideoRegister(renderer, REG_GREENSWP, video->p->memory.io[REG_GREENSWP >> 1]);
|
||||
int address;
|
||||
for (address = REG_BG0CNT; address < REG_SOUND1CNT_LO; address += 2) {
|
||||
renderer->writeVideoRegister(renderer, address, video->p->memory.io[address >> 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void _midHblank(struct mTiming* timing, void* context, uint32_t cyclesLate) {
|
||||
|
|
|
@ -7,41 +7,50 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
|||
set(${FEATURE_NAME} OFF PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
foreach(REQUIRE ${FEATURE_REQUIRES})
|
||||
if(NOT ${REQUIRE}_FOUND)
|
||||
find_package(${REQUIRE} QUIET)
|
||||
foreach(NAMES ${FEATURE_REQUIRES})
|
||||
string(REPLACE "|" ";" NAMELIST "${NAMES}")
|
||||
set(FOUND OFF)
|
||||
foreach(REQUIRE ${NAMELIST})
|
||||
if(NOT ${REQUIRE}_FOUND)
|
||||
pkg_search_module(${REQUIRE} ${REQUIRE})
|
||||
if (NOT ${REQUIRE}_FOUND)
|
||||
message(WARNING "Requested module ${REQUIRE} missing for feature ${FEATURE_NAME}. Feature disabled.")
|
||||
set(${FEATURE_NAME} OFF PARENT_SCOPE)
|
||||
return()
|
||||
find_package(${REQUIRE} QUIET)
|
||||
if(NOT ${REQUIRE}_FOUND)
|
||||
pkg_search_module(${REQUIRE} ${REQUIRE})
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
string(TOUPPER ${REQUIRE} UREQUIRE)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${REQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_FOUND ${${REQUIRE}_FOUND} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${REQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${REQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
if (APPLE)
|
||||
set(IS_FRAMEWORK OFF)
|
||||
set(LIBS)
|
||||
foreach(LIB IN LISTS ${REQUIRE}_LIBRARIES)
|
||||
if(LIB STREQUAL "-framework")
|
||||
set(IS_FRAMEWORK ON)
|
||||
elseif(IS_FRAMEWORK)
|
||||
list(APPEND LIBS "-framework ${LIB}")
|
||||
if(${REQUIRE}_FOUND)
|
||||
string(TOUPPER ${REQUIRE} UREQUIRE)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${REQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_FOUND ${${REQUIRE}_FOUND} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${REQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${REQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
if (APPLE)
|
||||
set(IS_FRAMEWORK OFF)
|
||||
set(LIBS)
|
||||
foreach(LIB IN LISTS ${REQUIRE}_LIBRARIES)
|
||||
if(LIB STREQUAL "-framework")
|
||||
set(IS_FRAMEWORK ON)
|
||||
elseif(IS_FRAMEWORK)
|
||||
list(APPEND LIBS "-framework ${LIB}")
|
||||
set(IS_FRAMEWORK OFF)
|
||||
else()
|
||||
list(APPEND LIBS ${LIB})
|
||||
endif()
|
||||
endforeach()
|
||||
set(${UREQUIRE}_LIBRARIES ${LIBS} PARENT_SCOPE)
|
||||
else()
|
||||
list(APPEND LIBS ${LIB})
|
||||
set(${UREQUIRE}_LIBRARIES ${${REQUIRE}_LIBRARIES} PARENT_SCOPE)
|
||||
endif()
|
||||
endforeach()
|
||||
set(${UREQUIRE}_LIBRARIES ${LIBS} PARENT_SCOPE)
|
||||
else()
|
||||
set(${UREQUIRE}_LIBRARIES ${${REQUIRE}_LIBRARIES} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_LIBRARY_DIRS ${${REQUIRE}_LIBRARY_DIRS} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_LDFLAGS_OTHER ${${REQUIRE}_LDFLAGS_OTHER} PARENT_SCOPE)
|
||||
set(FOUND ON)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if (NOT FOUND)
|
||||
message(WARNING "Requested module ${NAMES} missing for feature ${FEATURE_NAME}. Feature disabled.")
|
||||
set(${FEATURE_NAME} OFF PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
set(${UREQUIRE}_LIBRARY_DIRS ${${REQUIRE}_LIBRARY_DIRS} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_LDFLAGS_OTHER ${${REQUIRE}_LDFLAGS_OTHER} PARENT_SCOPE)
|
||||
endforeach()
|
||||
endfunction()
|
||||
endfunction()
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#define SAMPLES 1024
|
||||
#define RUMBLE_PWM 35
|
||||
#define EVENT_RATE 60
|
||||
|
||||
static retro_environment_t environCallback;
|
||||
static retro_video_refresh_t videoCallback;
|
||||
|
@ -38,6 +39,8 @@ static retro_input_poll_t inputPollCallback;
|
|||
static retro_input_state_t inputCallback;
|
||||
static retro_log_printf_t logCallback;
|
||||
static retro_set_rumble_state_t rumbleCallback;
|
||||
static retro_sensor_get_input_t sensorGetCallback;
|
||||
static retro_set_sensor_state_t sensorStateCallback;
|
||||
|
||||
static void GBARetroLog(struct mLogger* logger, int category, enum mLogLevel level, const char* format, va_list args);
|
||||
|
||||
|
@ -49,6 +52,10 @@ static void _updateCamera(const uint32_t* buffer, unsigned width, unsigned heigh
|
|||
static void _startImage(struct mImageSource*, unsigned w, unsigned h, int colorFormats);
|
||||
static void _stopImage(struct mImageSource*);
|
||||
static void _requestImage(struct mImageSource*, const void** buffer, size_t* stride, enum mColorFormat* colorFormat);
|
||||
static void _updateRotation(struct mRotationSource* source);
|
||||
static int32_t _readTiltX(struct mRotationSource* source);
|
||||
static int32_t _readTiltY(struct mRotationSource* source);
|
||||
static int32_t _readGyroZ(struct mRotationSource* source);
|
||||
|
||||
static struct mCore* core;
|
||||
static color_t* outputBuffer = NULL;
|
||||
|
@ -56,11 +63,17 @@ static void* data;
|
|||
static size_t dataSize;
|
||||
static void* savedata;
|
||||
static struct mAVStream stream;
|
||||
static bool sensorsInitDone;
|
||||
static int rumbleUp;
|
||||
static int rumbleDown;
|
||||
static struct mRumble rumble;
|
||||
static struct GBALuminanceSource lux;
|
||||
static int luxLevel;
|
||||
static struct mRotationSource rotation;
|
||||
static bool rotationEnabled;
|
||||
static int luxLevelIndex;
|
||||
static uint8_t luxLevel;
|
||||
static bool luxSensorEnabled;
|
||||
static bool luxSensorUsed;
|
||||
static struct mLogger logger;
|
||||
static struct retro_camera_callback cam;
|
||||
static struct mImageSource imageSource;
|
||||
|
@ -70,6 +83,36 @@ static unsigned camHeight;
|
|||
static unsigned imcapWidth;
|
||||
static unsigned imcapHeight;
|
||||
static size_t camStride;
|
||||
static bool deferredSetup = false;
|
||||
static bool envVarsUpdated;
|
||||
static int32_t tiltX = 0;
|
||||
static int32_t tiltY = 0;
|
||||
static int32_t gyroZ = 0;
|
||||
|
||||
static void _initSensors(void) {
|
||||
if (sensorsInitDone) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct retro_sensor_interface sensorInterface;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_SENSOR_INTERFACE, &sensorInterface)) {
|
||||
sensorGetCallback = sensorInterface.get_sensor_input;
|
||||
sensorStateCallback = sensorInterface.set_sensor_state;
|
||||
|
||||
if (sensorStateCallback && sensorGetCallback) {
|
||||
if (sensorStateCallback(0, RETRO_SENSOR_ACCELEROMETER_ENABLE, EVENT_RATE)
|
||||
&& sensorStateCallback(0, RETRO_SENSOR_GYROSCOPE_ENABLE, EVENT_RATE)) {
|
||||
rotationEnabled = true;
|
||||
}
|
||||
|
||||
if (sensorStateCallback(0, RETRO_SENSOR_ILLUMINANCE_ENABLE, EVENT_RATE)) {
|
||||
luxSensorEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sensorsInitDone = true;
|
||||
}
|
||||
|
||||
static void _reloadSettings(void) {
|
||||
struct mCoreOptions opts = {
|
||||
|
@ -154,6 +197,18 @@ static void _reloadSettings(void) {
|
|||
mCoreLoadConfig(core);
|
||||
}
|
||||
|
||||
static void _doDeferredSetup(void) {
|
||||
// Libretro API doesn't let you know when it's done copying data into the save buffers.
|
||||
// On the off-hand chance that a core actually expects its buffers to be populated when
|
||||
// you actually first get them, you're out of luck without workarounds. Yup, seriously.
|
||||
// Here's that workaround, but really the API needs to be thrown out and rewritten.
|
||||
struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||
if (!core->loadSave(core, save)) {
|
||||
save->close(save);
|
||||
}
|
||||
deferredSetup = false;
|
||||
}
|
||||
|
||||
unsigned retro_api_version(void) {
|
||||
return RETRO_API_VERSION;
|
||||
}
|
||||
|
@ -259,6 +314,20 @@ void retro_init(void) {
|
|||
rumbleCallback = 0;
|
||||
}
|
||||
|
||||
sensorsInitDone = false;
|
||||
sensorGetCallback = 0;
|
||||
sensorStateCallback = 0;
|
||||
|
||||
rotationEnabled = false;
|
||||
rotation.sample = _updateRotation;
|
||||
rotation.readTiltX = _readTiltX;
|
||||
rotation.readTiltY = _readTiltY;
|
||||
rotation.readGyroZ = _readGyroZ;
|
||||
|
||||
envVarsUpdated = true;
|
||||
luxSensorUsed = false;
|
||||
luxSensorEnabled = false;
|
||||
luxLevelIndex = 0;
|
||||
luxLevel = 0;
|
||||
lux.readLuminance = _readLux;
|
||||
lux.sample = _updateLux;
|
||||
|
@ -286,14 +355,33 @@ void retro_init(void) {
|
|||
|
||||
void retro_deinit(void) {
|
||||
free(outputBuffer);
|
||||
|
||||
if (sensorStateCallback) {
|
||||
sensorStateCallback(0, RETRO_SENSOR_ACCELEROMETER_DISABLE, EVENT_RATE);
|
||||
sensorStateCallback(0, RETRO_SENSOR_GYROSCOPE_DISABLE, EVENT_RATE);
|
||||
sensorStateCallback(0, RETRO_SENSOR_ILLUMINANCE_DISABLE, EVENT_RATE);
|
||||
sensorGetCallback = NULL;
|
||||
sensorStateCallback = NULL;
|
||||
}
|
||||
|
||||
rotationEnabled = false;
|
||||
luxSensorEnabled = false;
|
||||
sensorsInitDone = false;
|
||||
}
|
||||
|
||||
void retro_run(void) {
|
||||
if (deferredSetup) {
|
||||
_doDeferredSetup();
|
||||
}
|
||||
uint16_t keys;
|
||||
|
||||
_initSensors();
|
||||
inputPollCallback();
|
||||
|
||||
bool updated = false;
|
||||
if (environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) && updated) {
|
||||
envVarsUpdated = true;
|
||||
|
||||
struct retro_variable var = {
|
||||
.key = "mgba_allow_opposing_directions",
|
||||
.value = 0
|
||||
|
@ -324,23 +412,25 @@ void retro_run(void) {
|
|||
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
|
||||
core->setKeys(core, keys);
|
||||
|
||||
static bool wasAdjustingLux = false;
|
||||
if (wasAdjustingLux) {
|
||||
wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
|
||||
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
|
||||
} else {
|
||||
if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
|
||||
++luxLevel;
|
||||
if (luxLevel > 10) {
|
||||
luxLevel = 10;
|
||||
if (!luxSensorUsed) {
|
||||
static bool wasAdjustingLux = false;
|
||||
if (wasAdjustingLux) {
|
||||
wasAdjustingLux = inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3) ||
|
||||
inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3);
|
||||
} else {
|
||||
if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_R3)) {
|
||||
++luxLevelIndex;
|
||||
if (luxLevelIndex > 10) {
|
||||
luxLevelIndex = 10;
|
||||
}
|
||||
wasAdjustingLux = true;
|
||||
} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
|
||||
--luxLevelIndex;
|
||||
if (luxLevelIndex < 0) {
|
||||
luxLevelIndex = 0;
|
||||
}
|
||||
wasAdjustingLux = true;
|
||||
}
|
||||
wasAdjustingLux = true;
|
||||
} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
|
||||
--luxLevel;
|
||||
if (luxLevel < 0) {
|
||||
luxLevel = 0;
|
||||
}
|
||||
wasAdjustingLux = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -589,11 +679,10 @@ bool retro_load_game(const struct retro_game_info* game) {
|
|||
|
||||
savedata = anonymousMemoryMap(SIZE_CART_FLASH1M);
|
||||
memset(savedata, 0xFF, SIZE_CART_FLASH1M);
|
||||
struct VFile* save = VFileFromMemory(savedata, SIZE_CART_FLASH1M);
|
||||
|
||||
_reloadSettings();
|
||||
core->loadROM(core, rom);
|
||||
core->loadSave(core, save);
|
||||
deferredSetup = true;
|
||||
|
||||
const char* sysDir = 0;
|
||||
const char* biosName = 0;
|
||||
|
@ -671,6 +760,9 @@ void retro_unload_game(void) {
|
|||
}
|
||||
|
||||
size_t retro_serialize_size(void) {
|
||||
if (deferredSetup) {
|
||||
_doDeferredSetup();
|
||||
}
|
||||
struct VFile* vfm = VFileMemChunk(NULL, 0);
|
||||
mCoreSaveStateNamed(core, vfm, SAVESTATE_SAVEDATA | SAVESTATE_RTC);
|
||||
size_t size = vfm->size(vfm);
|
||||
|
@ -679,6 +771,9 @@ size_t retro_serialize_size(void) {
|
|||
}
|
||||
|
||||
bool retro_serialize(void* data, size_t size) {
|
||||
if (deferredSetup) {
|
||||
_doDeferredSetup();
|
||||
}
|
||||
struct VFile* vfm = VFileMemChunk(NULL, 0);
|
||||
mCoreSaveStateNamed(core, vfm, SAVESTATE_SAVEDATA | SAVESTATE_RTC);
|
||||
if ((ssize_t) size > vfm->size(vfm)) {
|
||||
|
@ -694,6 +789,9 @@ bool retro_serialize(void* data, size_t size) {
|
|||
}
|
||||
|
||||
bool retro_unserialize(const void* data, size_t size) {
|
||||
if (deferredSetup) {
|
||||
_doDeferredSetup();
|
||||
}
|
||||
struct VFile* vfm = VFileFromConstMemory(data, size);
|
||||
bool success = mCoreLoadStateNamed(core, vfm, SAVESTATE_RTC);
|
||||
vfm->close(vfm);
|
||||
|
@ -778,31 +876,68 @@ bool retro_load_game_special(unsigned game_type, const struct retro_game_info* i
|
|||
}
|
||||
|
||||
void* retro_get_memory_data(unsigned id) {
|
||||
if (id != RETRO_MEMORY_SAVE_RAM) {
|
||||
return 0;
|
||||
switch (id) {
|
||||
case RETRO_MEMORY_SAVE_RAM:
|
||||
return savedata;
|
||||
case RETRO_MEMORY_RTC:
|
||||
switch (core->platform(core)) {
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
switch (((struct GB*) core->board)->memory.mbcType) {
|
||||
case GB_MBC3_RTC:
|
||||
return &((uint8_t*) savedata)[((struct GB*) core->board)->sramSize];
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return savedata;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t retro_get_memory_size(unsigned id) {
|
||||
if (id != RETRO_MEMORY_SAVE_RAM) {
|
||||
return 0;
|
||||
}
|
||||
switch (id) {
|
||||
case RETRO_MEMORY_SAVE_RAM:
|
||||
switch (core->platform(core)) {
|
||||
#ifdef M_CORE_GBA
|
||||
if (core->platform(core) == PLATFORM_GBA) {
|
||||
switch (((struct GBA*) core->board)->memory.savedata.type) {
|
||||
case SAVEDATA_AUTODETECT:
|
||||
return SIZE_CART_FLASH1M;
|
||||
default:
|
||||
return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata);
|
||||
}
|
||||
}
|
||||
case PLATFORM_GBA:
|
||||
switch (((struct GBA*) core->board)->memory.savedata.type) {
|
||||
case SAVEDATA_AUTODETECT:
|
||||
return SIZE_CART_FLASH1M;
|
||||
default:
|
||||
return GBASavedataSize(&((struct GBA*) core->board)->memory.savedata);
|
||||
}
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
if (core->platform(core) == PLATFORM_GB) {
|
||||
return ((struct GB*) core->board)->sramSize;
|
||||
}
|
||||
case PLATFORM_GB:
|
||||
return ((struct GB*) core->board)->sramSize;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case RETRO_MEMORY_RTC:
|
||||
switch (core->platform(core)) {
|
||||
#ifdef M_CORE_GB
|
||||
case PLATFORM_GB:
|
||||
switch (((struct GB*) core->board)->memory.mbcType) {
|
||||
case GB_MBC3_RTC:
|
||||
return sizeof(struct GBMBCRTCSaveBuffer);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -878,35 +1013,47 @@ static void _updateLux(struct GBALuminanceSource* lux) {
|
|||
.key = "mgba_solar_sensor_level",
|
||||
.value = 0
|
||||
};
|
||||
bool luxVarUpdated = envVarsUpdated;
|
||||
|
||||
bool updated = false;
|
||||
if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE, &updated) || !updated) {
|
||||
return;
|
||||
}
|
||||
if (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value) {
|
||||
return;
|
||||
if (luxVarUpdated && (!environCallback(RETRO_ENVIRONMENT_GET_VARIABLE, &var) || !var.value)) {
|
||||
luxVarUpdated = false;
|
||||
}
|
||||
|
||||
char* end;
|
||||
int newLuxLevel = strtol(var.value, &end, 10);
|
||||
if (!*end) {
|
||||
if (newLuxLevel > 10) {
|
||||
luxLevel = 10;
|
||||
} else if (newLuxLevel < 0) {
|
||||
luxLevel = 0;
|
||||
} else {
|
||||
luxLevel = newLuxLevel;
|
||||
if (luxVarUpdated) {
|
||||
luxSensorUsed = strcmp(var.value, "sensor") == 0;
|
||||
}
|
||||
|
||||
if (luxSensorUsed) {
|
||||
float fLux = luxSensorEnabled ? sensorGetCallback(0, RETRO_SENSOR_ILLUMINANCE) : 0.0f;
|
||||
luxLevel = cbrtf(fLux) * 8;
|
||||
} else {
|
||||
if (luxVarUpdated) {
|
||||
char* end;
|
||||
int newLuxLevelIndex = strtol(var.value, &end, 10);
|
||||
|
||||
if (!*end) {
|
||||
if (newLuxLevelIndex > 10) {
|
||||
luxLevelIndex = 10;
|
||||
} else if (newLuxLevelIndex < 0) {
|
||||
luxLevelIndex = 0;
|
||||
} else {
|
||||
luxLevelIndex = newLuxLevelIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
luxLevel = 0x16;
|
||||
if (luxLevelIndex > 0) {
|
||||
luxLevel += GBA_LUX_LEVELS[luxLevelIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
envVarsUpdated = false;
|
||||
}
|
||||
|
||||
static uint8_t _readLux(struct GBALuminanceSource* lux) {
|
||||
UNUSED(lux);
|
||||
int value = 0x16;
|
||||
if (luxLevel > 0) {
|
||||
value += GBA_LUX_LEVELS[luxLevel - 1];
|
||||
}
|
||||
return 0xFF - value;
|
||||
return 0xFF - luxLevel;
|
||||
}
|
||||
|
||||
static void _updateCamera(const uint32_t* buffer, unsigned width, unsigned height, size_t pitch) {
|
||||
|
@ -972,3 +1119,30 @@ static void _requestImage(struct mImageSource* image, const void** buffer, size_
|
|||
*stride = camStride;
|
||||
*colorFormat = mCOLOR_XRGB8;
|
||||
}
|
||||
|
||||
static void _updateRotation(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
tiltX = 0;
|
||||
tiltY = 0;
|
||||
gyroZ = 0;
|
||||
if (rotationEnabled) {
|
||||
tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * 3e8f;
|
||||
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * -3e8f;
|
||||
gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -1.1e9f;
|
||||
}
|
||||
}
|
||||
|
||||
static int32_t _readTiltX(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
return tiltX;
|
||||
}
|
||||
|
||||
static int32_t _readTiltY(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
return tiltY;
|
||||
}
|
||||
|
||||
static int32_t _readGyroZ(struct mRotationSource* source) {
|
||||
UNUSED(source);
|
||||
return gyroZ;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2010-2018 The RetroArch team
|
||||
/* Copyright (C) 2010-2020 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this libretro API header (libretro.h).
|
||||
|
@ -278,6 +278,10 @@ enum retro_language
|
|||
RETRO_LANGUAGE_ARABIC = 16,
|
||||
RETRO_LANGUAGE_GREEK = 17,
|
||||
RETRO_LANGUAGE_TURKISH = 18,
|
||||
RETRO_LANGUAGE_SLOVAK = 19,
|
||||
RETRO_LANGUAGE_PERSIAN = 20,
|
||||
RETRO_LANGUAGE_HEBREW = 21,
|
||||
RETRO_LANGUAGE_ASTURIAN = 22,
|
||||
RETRO_LANGUAGE_LAST,
|
||||
|
||||
/* Ensure sizeof(enum) == sizeof(int) */
|
||||
|
@ -1087,10 +1091,10 @@ enum retro_mod
|
|||
|
||||
#define RETRO_ENVIRONMENT_GET_TARGET_REFRESH_RATE (50 | RETRO_ENVIRONMENT_EXPERIMENTAL)
|
||||
/* float * --
|
||||
* Float value that lets us know what target refresh rate
|
||||
* Float value that lets us know what target refresh rate
|
||||
* is curently in use by the frontend.
|
||||
*
|
||||
* The core can use the returned value to set an ideal
|
||||
* The core can use the returned value to set an ideal
|
||||
* refresh rate/framerate.
|
||||
*/
|
||||
|
||||
|
@ -1098,7 +1102,7 @@ enum retro_mod
|
|||
/* bool * --
|
||||
* Boolean value that indicates whether or not the frontend supports
|
||||
* input bitmasks being returned by retro_input_state_t. The advantage
|
||||
* of this is that retro_input_state_t has to be only called once to
|
||||
* of this is that retro_input_state_t has to be only called once to
|
||||
* grab all button states instead of multiple times.
|
||||
*
|
||||
* If it returns true, you can pass RETRO_DEVICE_ID_JOYPAD_MASK as 'id'
|
||||
|
@ -1246,6 +1250,130 @@ enum retro_mod
|
|||
* default when calling SET_VARIABLES/SET_CORE_OPTIONS.
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_GET_PREFERRED_HW_RENDER 56
|
||||
/* unsigned * --
|
||||
*
|
||||
* Allows an implementation to ask frontend preferred hardware
|
||||
* context to use. Core should use this information to deal
|
||||
* with what specific context to request with SET_HW_RENDER.
|
||||
*
|
||||
* 'data' points to an unsigned variable
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_GET_DISK_CONTROL_INTERFACE_VERSION 57
|
||||
/* unsigned * --
|
||||
* Unsigned value is the API version number of the disk control
|
||||
* interface supported by the frontend. If callback return false,
|
||||
* API version is assumed to be 0.
|
||||
*
|
||||
* In legacy code, the disk control interface is defined by passing
|
||||
* a struct of type retro_disk_control_callback to
|
||||
* RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE.
|
||||
* This may be still be done regardless of the disk control
|
||||
* interface version.
|
||||
*
|
||||
* If version is >= 1 however, the disk control interface may
|
||||
* instead be defined by passing a struct of type
|
||||
* retro_disk_control_ext_callback to
|
||||
* RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE.
|
||||
* This allows the core to provide additional information about
|
||||
* disk images to the frontend and/or enables extra
|
||||
* disk control functionality by the frontend.
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE 58
|
||||
/* const struct retro_disk_control_ext_callback * --
|
||||
* Sets an interface which frontend can use to eject and insert
|
||||
* disk images, and also obtain information about individual
|
||||
* disk image files registered by the core.
|
||||
* This is used for games which consist of multiple images and
|
||||
* must be manually swapped out by the user (e.g. PSX, floppy disk
|
||||
* based systems).
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_GET_MESSAGE_INTERFACE_VERSION 59
|
||||
/* unsigned * --
|
||||
* Unsigned value is the API version number of the message
|
||||
* interface supported by the frontend. If callback returns
|
||||
* false, API version is assumed to be 0.
|
||||
*
|
||||
* In legacy code, messages may be displayed in an
|
||||
* implementation-specific manner by passing a struct
|
||||
* of type retro_message to RETRO_ENVIRONMENT_SET_MESSAGE.
|
||||
* This may be still be done regardless of the message
|
||||
* interface version.
|
||||
*
|
||||
* If version is >= 1 however, messages may instead be
|
||||
* displayed by passing a struct of type retro_message_ext
|
||||
* to RETRO_ENVIRONMENT_SET_MESSAGE_EXT. This allows the
|
||||
* core to specify message logging level, priority and
|
||||
* destination (OSD, logging interface or both).
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_SET_MESSAGE_EXT 60
|
||||
/* const struct retro_message_ext * --
|
||||
* Sets a message to be displayed in an implementation-specific
|
||||
* manner for a certain amount of 'frames'. Additionally allows
|
||||
* the core to specify message logging level, priority and
|
||||
* destination (OSD, logging interface or both).
|
||||
* Should not be used for trivial messages, which should simply be
|
||||
* logged via RETRO_ENVIRONMENT_GET_LOG_INTERFACE (or as a
|
||||
* fallback, stderr).
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_GET_INPUT_MAX_USERS 61
|
||||
/* unsigned * --
|
||||
* Unsigned value is the number of active input devices
|
||||
* provided by the frontend. This may change between
|
||||
* frames, but will remain constant for the duration
|
||||
* of each frame.
|
||||
* If callback returns true, a core need not poll any
|
||||
* input device with an index greater than or equal to
|
||||
* the number of active devices.
|
||||
* If callback returns false, the number of active input
|
||||
* devices is unknown. In this case, all input devices
|
||||
* should be considered active.
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK 62
|
||||
/* const struct retro_audio_buffer_status_callback * --
|
||||
* Lets the core know the occupancy level of the frontend
|
||||
* audio buffer. Can be used by a core to attempt frame
|
||||
* skipping in order to avoid buffer under-runs.
|
||||
* A core may pass NULL to disable buffer status reporting
|
||||
* in the frontend.
|
||||
*/
|
||||
|
||||
#define RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY 63
|
||||
/* const unsigned * --
|
||||
* Sets minimum frontend audio latency in milliseconds.
|
||||
* Resultant audio latency may be larger than set value,
|
||||
* or smaller if a hardware limit is encountered. A frontend
|
||||
* is expected to honour requests up to 512 ms.
|
||||
*
|
||||
* - If value is less than current frontend
|
||||
* audio latency, callback has no effect
|
||||
* - If value is zero, default frontend audio
|
||||
* latency is set
|
||||
*
|
||||
* May be used by a core to increase audio latency and
|
||||
* therefore decrease the probability of buffer under-runs
|
||||
* (crackling) when performing 'intensive' operations.
|
||||
* A core utilising RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK
|
||||
* to implement audio-buffer-based frame skipping may achieve
|
||||
* optimal results by setting the audio latency to a 'high'
|
||||
* (typically 6x or 8x) integer multiple of the expected
|
||||
* frame time.
|
||||
*
|
||||
* WARNING: This can only be called from within retro_run().
|
||||
* Calling this can require a full reinitialization of audio
|
||||
* drivers in the frontend, so it is important to call it very
|
||||
* sparingly, and usually only with the users explicit consent.
|
||||
* An eventual driver reinitialize will happen so that audio
|
||||
* callbacks happening after this call within the same retro_run()
|
||||
* call will target the newly initialized driver.
|
||||
*/
|
||||
|
||||
/* VFS functionality */
|
||||
|
||||
/* File paths:
|
||||
|
@ -1922,6 +2050,10 @@ enum retro_sensor_action
|
|||
{
|
||||
RETRO_SENSOR_ACCELEROMETER_ENABLE = 0,
|
||||
RETRO_SENSOR_ACCELEROMETER_DISABLE,
|
||||
RETRO_SENSOR_GYROSCOPE_ENABLE,
|
||||
RETRO_SENSOR_GYROSCOPE_DISABLE,
|
||||
RETRO_SENSOR_ILLUMINANCE_ENABLE,
|
||||
RETRO_SENSOR_ILLUMINANCE_DISABLE,
|
||||
|
||||
RETRO_SENSOR_DUMMY = INT_MAX
|
||||
};
|
||||
|
@ -1930,6 +2062,10 @@ enum retro_sensor_action
|
|||
#define RETRO_SENSOR_ACCELEROMETER_X 0
|
||||
#define RETRO_SENSOR_ACCELEROMETER_Y 1
|
||||
#define RETRO_SENSOR_ACCELEROMETER_Z 2
|
||||
#define RETRO_SENSOR_GYROSCOPE_X 3
|
||||
#define RETRO_SENSOR_GYROSCOPE_Y 4
|
||||
#define RETRO_SENSOR_GYROSCOPE_Z 5
|
||||
#define RETRO_SENSOR_ILLUMINANCE 6
|
||||
|
||||
typedef bool (RETRO_CALLCONV *retro_set_sensor_state_t)(unsigned port,
|
||||
enum retro_sensor_action action, unsigned rate);
|
||||
|
@ -2127,6 +2263,30 @@ struct retro_frame_time_callback
|
|||
retro_usec_t reference;
|
||||
};
|
||||
|
||||
/* Notifies a libretro core of the current occupancy
|
||||
* level of the frontend audio buffer.
|
||||
*
|
||||
* - active: 'true' if audio buffer is currently
|
||||
* in use. Will be 'false' if audio is
|
||||
* disabled in the frontend
|
||||
*
|
||||
* - occupancy: Given as a value in the range [0,100],
|
||||
* corresponding to the occupancy percentage
|
||||
* of the audio buffer
|
||||
*
|
||||
* - underrun_likely: 'true' if the frontend expects an
|
||||
* audio buffer underrun during the
|
||||
* next frame (indicates that a core
|
||||
* should attempt frame skipping)
|
||||
*
|
||||
* It will be called right before retro_run() every frame. */
|
||||
typedef void (RETRO_CALLCONV *retro_audio_buffer_status_callback_t)(
|
||||
bool active, unsigned occupancy, bool underrun_likely);
|
||||
struct retro_audio_buffer_status_callback
|
||||
{
|
||||
retro_audio_buffer_status_callback_t callback;
|
||||
};
|
||||
|
||||
/* Pass this to retro_video_refresh_t if rendering to hardware.
|
||||
* Passing NULL to retro_video_refresh_t is still a frame dupe as normal.
|
||||
* */
|
||||
|
@ -2287,7 +2447,8 @@ struct retro_keyboard_callback
|
|||
retro_keyboard_event_t callback;
|
||||
};
|
||||
|
||||
/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE.
|
||||
/* Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE &
|
||||
* RETRO_ENVIRONMENT_SET_DISK_CONTROL_EXT_INTERFACE.
|
||||
* Should be set for implementations which can swap out multiple disk
|
||||
* images in runtime.
|
||||
*
|
||||
|
@ -2345,6 +2506,53 @@ typedef bool (RETRO_CALLCONV *retro_replace_image_index_t)(unsigned index,
|
|||
* with replace_image_index. */
|
||||
typedef bool (RETRO_CALLCONV *retro_add_image_index_t)(void);
|
||||
|
||||
/* Sets initial image to insert in drive when calling
|
||||
* core_load_game().
|
||||
* Since we cannot pass the initial index when loading
|
||||
* content (this would require a major API change), this
|
||||
* is set by the frontend *before* calling the core's
|
||||
* retro_load_game()/retro_load_game_special() implementation.
|
||||
* A core should therefore cache the index/path values and handle
|
||||
* them inside retro_load_game()/retro_load_game_special().
|
||||
* - If 'index' is invalid (index >= get_num_images()), the
|
||||
* core should ignore the set value and instead use 0
|
||||
* - 'path' is used purely for error checking - i.e. when
|
||||
* content is loaded, the core should verify that the
|
||||
* disk specified by 'index' has the specified file path.
|
||||
* This is to guard against auto selecting the wrong image
|
||||
* if (for example) the user should modify an existing M3U
|
||||
* playlist. We have to let the core handle this because
|
||||
* set_initial_image() must be called before loading content,
|
||||
* i.e. the frontend cannot access image paths in advance
|
||||
* and thus cannot perform the error check itself.
|
||||
* If set path and content path do not match, the core should
|
||||
* ignore the set 'index' value and instead use 0
|
||||
* Returns 'false' if index or 'path' are invalid, or core
|
||||
* does not support this functionality
|
||||
*/
|
||||
typedef bool (RETRO_CALLCONV *retro_set_initial_image_t)(unsigned index, const char *path);
|
||||
|
||||
/* Fetches the path of the specified disk image file.
|
||||
* Returns 'false' if index is invalid (index >= get_num_images())
|
||||
* or path is otherwise unavailable.
|
||||
*/
|
||||
typedef bool (RETRO_CALLCONV *retro_get_image_path_t)(unsigned index, char *path, size_t len);
|
||||
|
||||
/* Fetches a core-provided 'label' for the specified disk
|
||||
* image file. In the simplest case this may be a file name
|
||||
* (without extension), but for cores with more complex
|
||||
* content requirements information may be provided to
|
||||
* facilitate user disk swapping - for example, a core
|
||||
* running floppy-disk-based content may uniquely label
|
||||
* save disks, data disks, level disks, etc. with names
|
||||
* corresponding to in-game disk change prompts (so the
|
||||
* frontend can provide better user guidance than a 'dumb'
|
||||
* disk index value).
|
||||
* Returns 'false' if index is invalid (index >= get_num_images())
|
||||
* or label is otherwise unavailable.
|
||||
*/
|
||||
typedef bool (RETRO_CALLCONV *retro_get_image_label_t)(unsigned index, char *label, size_t len);
|
||||
|
||||
struct retro_disk_control_callback
|
||||
{
|
||||
retro_set_eject_state_t set_eject_state;
|
||||
|
@ -2358,6 +2566,27 @@ struct retro_disk_control_callback
|
|||
retro_add_image_index_t add_image_index;
|
||||
};
|
||||
|
||||
struct retro_disk_control_ext_callback
|
||||
{
|
||||
retro_set_eject_state_t set_eject_state;
|
||||
retro_get_eject_state_t get_eject_state;
|
||||
|
||||
retro_get_image_index_t get_image_index;
|
||||
retro_set_image_index_t set_image_index;
|
||||
retro_get_num_images_t get_num_images;
|
||||
|
||||
retro_replace_image_index_t replace_image_index;
|
||||
retro_add_image_index_t add_image_index;
|
||||
|
||||
/* NOTE: Frontend will only attempt to record/restore
|
||||
* last used disk index if both set_initial_image()
|
||||
* and get_image_path() are implemented */
|
||||
retro_set_initial_image_t set_initial_image; /* Optional - may be NULL */
|
||||
|
||||
retro_get_image_path_t get_image_path; /* Optional - may be NULL */
|
||||
retro_get_image_label_t get_image_label; /* Optional - may be NULL */
|
||||
};
|
||||
|
||||
enum retro_pixel_format
|
||||
{
|
||||
/* 0RGB1555, native endian.
|
||||
|
@ -2388,6 +2617,104 @@ struct retro_message
|
|||
unsigned frames; /* Duration in frames of message. */
|
||||
};
|
||||
|
||||
enum retro_message_target
|
||||
{
|
||||
RETRO_MESSAGE_TARGET_ALL = 0,
|
||||
RETRO_MESSAGE_TARGET_OSD,
|
||||
RETRO_MESSAGE_TARGET_LOG
|
||||
};
|
||||
|
||||
enum retro_message_type
|
||||
{
|
||||
RETRO_MESSAGE_TYPE_NOTIFICATION = 0,
|
||||
RETRO_MESSAGE_TYPE_NOTIFICATION_ALT,
|
||||
RETRO_MESSAGE_TYPE_STATUS,
|
||||
RETRO_MESSAGE_TYPE_PROGRESS
|
||||
};
|
||||
|
||||
struct retro_message_ext
|
||||
{
|
||||
/* Message string to be displayed/logged */
|
||||
const char *msg;
|
||||
/* Duration (in ms) of message when targeting the OSD */
|
||||
unsigned duration;
|
||||
/* Message priority when targeting the OSD
|
||||
* > When multiple concurrent messages are sent to
|
||||
* the frontend and the frontend does not have the
|
||||
* capacity to display them all, messages with the
|
||||
* *highest* priority value should be shown
|
||||
* > There is no upper limit to a message priority
|
||||
* value (within the bounds of the unsigned data type)
|
||||
* > In the reference frontend (RetroArch), the same
|
||||
* priority values are used for frontend-generated
|
||||
* notifications, which are typically assigned values
|
||||
* between 0 and 3 depending upon importance */
|
||||
unsigned priority;
|
||||
/* Message logging level (info, warn, error, etc.) */
|
||||
enum retro_log_level level;
|
||||
/* Message destination: OSD, logging interface or both */
|
||||
enum retro_message_target target;
|
||||
/* Message 'type' when targeting the OSD
|
||||
* > RETRO_MESSAGE_TYPE_NOTIFICATION: Specifies that a
|
||||
* message should be handled in identical fashion to
|
||||
* a standard frontend-generated notification
|
||||
* > RETRO_MESSAGE_TYPE_NOTIFICATION_ALT: Specifies that
|
||||
* message is a notification that requires user attention
|
||||
* or action, but that it should be displayed in a manner
|
||||
* that differs from standard frontend-generated notifications.
|
||||
* This would typically correspond to messages that should be
|
||||
* displayed immediately (independently from any internal
|
||||
* frontend message queue), and/or which should be visually
|
||||
* distinguishable from frontend-generated notifications.
|
||||
* For example, a core may wish to inform the user of
|
||||
* information related to a disk-change event. It is
|
||||
* expected that the frontend itself may provide a
|
||||
* notification in this case; if the core sends a
|
||||
* message of type RETRO_MESSAGE_TYPE_NOTIFICATION, an
|
||||
* uncomfortable 'double-notification' may occur. A message
|
||||
* of RETRO_MESSAGE_TYPE_NOTIFICATION_ALT should therefore
|
||||
* be presented such that visual conflict with regular
|
||||
* notifications does not occur
|
||||
* > RETRO_MESSAGE_TYPE_STATUS: Indicates that message
|
||||
* is not a standard notification. This typically
|
||||
* corresponds to 'status' indicators, such as a core's
|
||||
* internal FPS, which are intended to be displayed
|
||||
* either permanently while a core is running, or in
|
||||
* a manner that does not suggest user attention or action
|
||||
* is required. 'Status' type messages should therefore be
|
||||
* displayed in a different on-screen location and in a manner
|
||||
* easily distinguishable from both standard frontend-generated
|
||||
* notifications and messages of type RETRO_MESSAGE_TYPE_NOTIFICATION_ALT
|
||||
* > RETRO_MESSAGE_TYPE_PROGRESS: Indicates that message reports
|
||||
* the progress of an internal core task. For example, in cases
|
||||
* where a core itself handles the loading of content from a file,
|
||||
* this may correspond to the percentage of the file that has been
|
||||
* read. Alternatively, an audio/video playback core may use a
|
||||
* message of type RETRO_MESSAGE_TYPE_PROGRESS to display the current
|
||||
* playback position as a percentage of the runtime. 'Progress' type
|
||||
* messages should therefore be displayed as a literal progress bar,
|
||||
* where:
|
||||
* - 'retro_message_ext.msg' is the progress bar title/label
|
||||
* - 'retro_message_ext.progress' determines the length of
|
||||
* the progress bar
|
||||
* NOTE: Message type is a *hint*, and may be ignored
|
||||
* by the frontend. If a frontend lacks support for
|
||||
* displaying messages via alternate means than standard
|
||||
* frontend-generated notifications, it will treat *all*
|
||||
* messages as having the type RETRO_MESSAGE_TYPE_NOTIFICATION */
|
||||
enum retro_message_type type;
|
||||
/* Task progress when targeting the OSD and message is
|
||||
* of type RETRO_MESSAGE_TYPE_PROGRESS
|
||||
* > -1: Unmetered/indeterminate
|
||||
* > 0-100: Current progress percentage
|
||||
* NOTE: Since message type is a hint, a frontend may ignore
|
||||
* progress values. Where relevant, a core should therefore
|
||||
* include progress percentage within the message string,
|
||||
* such that the message intent remains clear when displayed
|
||||
* as a standard frontend-generated notification */
|
||||
int8_t progress;
|
||||
};
|
||||
|
||||
/* Describes how the libretro implementation maps a libretro input bind
|
||||
* to its internal input system through a human readable string.
|
||||
* This string can be used to better let a user configure input. */
|
||||
|
@ -2408,7 +2735,7 @@ struct retro_input_descriptor
|
|||
struct retro_system_info
|
||||
{
|
||||
/* All pointers are owned by libretro implementation, and pointers must
|
||||
* remain valid until retro_deinit() is called. */
|
||||
* remain valid until it is unloaded. */
|
||||
|
||||
const char *library_name; /* Descriptive name of library. Should not
|
||||
* contain any version numbers, etc. */
|
||||
|
|
|
@ -54,18 +54,19 @@ struct retro_core_option_definition option_defs_us[] = {
|
|||
"Solar Sensor Level",
|
||||
"Sets ambient sunlight intensity. Can be used by games that included a solar sensor in their cartridges, e.g: the Boktai series.",
|
||||
{
|
||||
{ "0", NULL },
|
||||
{ "1", NULL },
|
||||
{ "2", NULL },
|
||||
{ "3", NULL },
|
||||
{ "4", NULL },
|
||||
{ "5", NULL },
|
||||
{ "6", NULL },
|
||||
{ "7", NULL },
|
||||
{ "8", NULL },
|
||||
{ "9", NULL },
|
||||
{ "10", NULL },
|
||||
{ NULL, NULL },
|
||||
{ "sensor", "Use device sensor if available" },
|
||||
{ "0", NULL },
|
||||
{ "1", NULL },
|
||||
{ "2", NULL },
|
||||
{ "3", NULL },
|
||||
{ "4", NULL },
|
||||
{ "5", NULL },
|
||||
{ "6", NULL },
|
||||
{ "7", NULL },
|
||||
{ "8", NULL },
|
||||
{ "9", NULL },
|
||||
{ "10", NULL },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"0"
|
||||
},
|
||||
|
|
|
@ -208,18 +208,19 @@ struct retro_core_option_definition option_defs_tr[] = {
|
|||
"Güneş Sensörü Seviyesi",
|
||||
"Ortam güneş ışığının yoğunluğunu ayarlar. Boktai serisi, kartuşlarına güneş sensörü içeren oyunlar tarafından kullanılabilir.",
|
||||
{
|
||||
{ "0", NULL },
|
||||
{ "1", NULL },
|
||||
{ "2", NULL },
|
||||
{ "3", NULL },
|
||||
{ "4", NULL },
|
||||
{ "5", NULL },
|
||||
{ "6", NULL },
|
||||
{ "7", NULL },
|
||||
{ "8", NULL },
|
||||
{ "9", NULL },
|
||||
{ "10", NULL },
|
||||
{ NULL, NULL },
|
||||
{ "sensor", "Sensörü" },
|
||||
{ "0", NULL },
|
||||
{ "1", NULL },
|
||||
{ "2", NULL },
|
||||
{ "3", NULL },
|
||||
{ "4", NULL },
|
||||
{ "5", NULL },
|
||||
{ "6", NULL },
|
||||
{ "7", NULL },
|
||||
{ "8", NULL },
|
||||
{ "9", NULL },
|
||||
{ "10", NULL },
|
||||
{ NULL, NULL },
|
||||
},
|
||||
"0"
|
||||
},
|
||||
|
|
|
@ -68,22 +68,6 @@ for line in preprocessed.splitlines():
|
|||
lines.append(line)
|
||||
ffi.cdef('\n'.join(lines))
|
||||
|
||||
ffi.cdef("""
|
||||
struct GBARTC {
|
||||
int32_t bytesRemaining;
|
||||
int32_t transferStep;
|
||||
int32_t bitsRead;
|
||||
int32_t bits;
|
||||
uint8_t commandActive;
|
||||
uint8_t alarm1[3];
|
||||
RTCCommandData command;
|
||||
RTCStatus2 status2;
|
||||
uint8_t freeReg;
|
||||
RTCControl control;
|
||||
uint8_t alarm2[3];
|
||||
uint8_t time[7];
|
||||
};""", packed=True)
|
||||
|
||||
preprocessed = subprocess.check_output(cpp + ["-fno-inline", "-P"] + cppflags + [os.path.join(pydir, "lib.h")], universal_newlines=True)
|
||||
|
||||
lines = []
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "AssetInfo.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
using namespace QGBA;
|
||||
|
@ -19,7 +20,7 @@ void AssetInfo::addCustomProperty(const QString& id, const QString& visibleName)
|
|||
QHBoxLayout* newLayout = new QHBoxLayout;
|
||||
newLayout->addWidget(new QLabel(visibleName));
|
||||
QLabel* value = new QLabel;
|
||||
value->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
|
||||
value->setFont(GBAApp::monospaceFont());
|
||||
value->setAlignment(Qt::AlignRight);
|
||||
newLayout->addWidget(value);
|
||||
m_customProperties[id] = value;
|
||||
|
@ -37,4 +38,4 @@ void AssetInfo::setCustomProperty(const QString& id, const QVariant& value) {
|
|||
|
||||
int AssetInfo::customLocation(const QString&) {
|
||||
return layout()->count();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
#include "CoreController.h"
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include <mgba/core/interface.h>
|
||||
|
@ -32,7 +31,7 @@ AssetTile::AssetTile(QWidget* parent)
|
|||
|
||||
connect(m_ui.preview, &Swatch::indexPressed, this, &AssetTile::selectColor);
|
||||
|
||||
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
|
||||
m_ui.tileId->setFont(font);
|
||||
m_ui.paletteId->setFont(font);
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="tileId">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -69,7 +69,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="paletteId">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -90,7 +90,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="address">
|
||||
<property name="text">
|
||||
<string>0x06000000</string>
|
||||
<string notr="true">0x06000000</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -160,21 +160,21 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="r">
|
||||
<property name="text">
|
||||
<string>0x00 (00)</string>
|
||||
<string notr="true">0x00 (00)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="g">
|
||||
<property name="text">
|
||||
<string>0x00 (00)</string>
|
||||
<string notr="true">0x00 (00)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="b">
|
||||
<property name="text">
|
||||
<string>0x00 (00)</string>
|
||||
<string notr="true">0x00 (00)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -185,18 +185,18 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetInfo</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetInfo.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::Swatch</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>Swatch.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetInfo</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetInfo.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -51,7 +51,7 @@ bool BattleChipModel::removeRows(int row, int count, const QModelIndex& parent)
|
|||
return false;
|
||||
}
|
||||
beginRemoveRows(QModelIndex(), row, row + count - 1);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
m_deck.removeAt(row);
|
||||
}
|
||||
endRemoveRows();
|
||||
|
|
|
@ -98,6 +98,7 @@ set(SOURCE_FILES
|
|||
PaletteView.cpp
|
||||
PlacementControl.cpp
|
||||
RegisterView.cpp
|
||||
ReportView.cpp
|
||||
ROMInfo.cpp
|
||||
RotatedHeaderView.cpp
|
||||
SavestateButton.cpp
|
||||
|
@ -142,6 +143,7 @@ set(UI_FILES
|
|||
PaletteView.ui
|
||||
PlacementControl.ui
|
||||
PrinterView.ui
|
||||
ReportView.ui
|
||||
ROMInfo.ui
|
||||
SensorView.ui
|
||||
SettingsView.ui
|
||||
|
@ -260,7 +262,9 @@ if(Qt5LinguistTools_FOUND)
|
|||
file(GLOB TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ts/${BINARY_NAME}-*.ts")
|
||||
if(UPDATE_TRANSLATIONS)
|
||||
qt5_create_translation(TRANSLATION_FILES ${SOURCE_FILES} ${UI_FILES} ${TS_FILES} OPTIONS -locations absolute -no-obsolete)
|
||||
list(REMOVE_ITEM TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ts/${BINARY_NAME}-template.ts")
|
||||
else()
|
||||
list(REMOVE_ITEM TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ts/${BINARY_NAME}-template.ts")
|
||||
qt5_add_translation(TRANSLATION_FILES ${TS_FILES})
|
||||
endif()
|
||||
set(QT_QM_FILES)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "CheatsModel.h"
|
||||
|
||||
#include "GBAApp.h"
|
||||
#include "LogController.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
|
@ -19,8 +20,7 @@ CheatsModel::CheatsModel(mCheatDevice* device, QObject* parent)
|
|||
: QAbstractItemModel(parent)
|
||||
, m_device(device)
|
||||
{
|
||||
m_font.setFamily("Source Code Pro");
|
||||
m_font.setStyleHint(QFont::Monospace);
|
||||
m_font = GBAApp::monospaceFont();
|
||||
}
|
||||
|
||||
QVariant CheatsModel::data(const QModelIndex& index, int role) const {
|
||||
|
|
|
@ -297,6 +297,10 @@ void ConfigController::makePortable() {
|
|||
m_settings = settings2;
|
||||
}
|
||||
|
||||
bool ConfigController::isPortable() {
|
||||
return mCoreConfigIsPortable();
|
||||
}
|
||||
|
||||
const QString& ConfigController::configDir() {
|
||||
if (s_configDir.isNull()) {
|
||||
char path[PATH_MAX];
|
||||
|
|
|
@ -91,6 +91,7 @@ public:
|
|||
mCoreConfig* config() { return &m_config; }
|
||||
|
||||
static const QString& configDir();
|
||||
static bool isPortable();
|
||||
|
||||
public slots:
|
||||
void setOption(const char* key, bool value);
|
||||
|
|
|
@ -504,7 +504,7 @@ void CoreController::loadState(int slot) {
|
|||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
if (!controller->m_backupLoadState.isOpen()) {
|
||||
controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
|
||||
controller->m_backupLoadState = VFileDevice::openMemory();
|
||||
}
|
||||
mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
|
||||
if (mCoreLoadState(context->core, controller->m_stateSlot, controller->m_loadStateFlags)) {
|
||||
|
@ -514,8 +514,12 @@ void CoreController::loadState(int slot) {
|
|||
});
|
||||
}
|
||||
|
||||
void CoreController::loadState(const QString& path) {
|
||||
void CoreController::loadState(const QString& path, int flags) {
|
||||
m_statePath = path;
|
||||
int savedFlags = m_loadStateFlags;
|
||||
if (flags != -1) {
|
||||
m_loadStateFlags = flags;
|
||||
}
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
|
||||
|
@ -523,7 +527,7 @@ void CoreController::loadState(const QString& path) {
|
|||
return;
|
||||
}
|
||||
if (!controller->m_backupLoadState.isOpen()) {
|
||||
controller->m_backupLoadState = VFileMemChunk(nullptr, 0);
|
||||
controller->m_backupLoadState = VFileDevice::openMemory();
|
||||
}
|
||||
mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
|
||||
if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
|
||||
|
@ -532,6 +536,35 @@ void CoreController::loadState(const QString& path) {
|
|||
}
|
||||
vf->close(vf);
|
||||
});
|
||||
m_loadStateFlags = savedFlags;
|
||||
}
|
||||
|
||||
void CoreController::loadState(QIODevice* iodev, int flags) {
|
||||
m_stateVf = VFileDevice::wrap(iodev, QIODevice::ReadOnly);
|
||||
if (!m_stateVf) {
|
||||
return;
|
||||
}
|
||||
int savedFlags = m_loadStateFlags;
|
||||
if (flags != -1) {
|
||||
m_loadStateFlags = flags;
|
||||
}
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
VFile* vf = controller->m_stateVf;
|
||||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
if (!controller->m_backupLoadState.isOpen()) {
|
||||
controller->m_backupLoadState = VFileDevice::openMemory();
|
||||
}
|
||||
mCoreSaveStateNamed(context->core, controller->m_backupLoadState, controller->m_saveStateFlags);
|
||||
if (mCoreLoadStateNamed(context->core, vf, controller->m_loadStateFlags)) {
|
||||
emit controller->frameAvailable();
|
||||
emit controller->stateLoaded();
|
||||
}
|
||||
vf->close(vf);
|
||||
});
|
||||
m_loadStateFlags = savedFlags;
|
||||
}
|
||||
|
||||
void CoreController::saveState(int slot) {
|
||||
|
@ -550,8 +583,12 @@ void CoreController::saveState(int slot) {
|
|||
});
|
||||
}
|
||||
|
||||
void CoreController::saveState(const QString& path) {
|
||||
void CoreController::saveState(const QString& path, int flags) {
|
||||
m_statePath = path;
|
||||
int savedFlags = m_saveStateFlags;
|
||||
if (flags != -1) {
|
||||
m_saveStateFlags = flags;
|
||||
}
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
VFile* vf = VFileDevice::open(controller->m_statePath, O_RDONLY);
|
||||
|
@ -567,6 +604,28 @@ void CoreController::saveState(const QString& path) {
|
|||
mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
|
||||
vf->close(vf);
|
||||
});
|
||||
m_saveStateFlags = savedFlags;
|
||||
}
|
||||
|
||||
void CoreController::saveState(QIODevice* iodev, int flags) {
|
||||
m_stateVf = VFileDevice::wrap(iodev, QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
if (!m_stateVf) {
|
||||
return;
|
||||
}
|
||||
int savedFlags = m_saveStateFlags;
|
||||
if (flags != -1) {
|
||||
m_saveStateFlags = flags;
|
||||
}
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
VFile* vf = controller->m_stateVf;
|
||||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
mCoreSaveStateNamed(context->core, vf, controller->m_saveStateFlags);
|
||||
vf->close(vf);
|
||||
});
|
||||
m_saveStateFlags = savedFlags;
|
||||
}
|
||||
|
||||
void CoreController::loadBackupState() {
|
||||
|
@ -659,6 +718,9 @@ void CoreController::yankPak() {
|
|||
GBYankROM(static_cast<GB*>(m_threadContext.core->board));
|
||||
break;
|
||||
#endif
|
||||
case PLATFORM_NONE:
|
||||
LOG(QT, ERROR) << tr("Can't yank pack in unexpected platform!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -853,7 +915,7 @@ void CoreController::startVideoLog(const QString& path, bool compression) {
|
|||
if (!vf) {
|
||||
return;
|
||||
}
|
||||
startVideoLog(vf);
|
||||
startVideoLog(vf, compression);
|
||||
}
|
||||
|
||||
void CoreController::startVideoLog(VFile* vf, bool compression) {
|
||||
|
|
|
@ -121,9 +121,11 @@ public slots:
|
|||
void forceFastForward(bool);
|
||||
|
||||
void loadState(int slot = 0);
|
||||
void loadState(const QString& path);
|
||||
void loadState(const QString& path, int flags = -1);
|
||||
void loadState(QIODevice* iodev, int flags = -1);
|
||||
void saveState(int slot = 0);
|
||||
void saveState(const QString& path);
|
||||
void saveState(const QString& path, int flags = -1);
|
||||
void saveState(QIODevice* iodev, int flags = -1);
|
||||
void loadBackupState();
|
||||
void saveBackupState();
|
||||
|
||||
|
@ -221,6 +223,7 @@ private:
|
|||
QByteArray m_backupSaveState{nullptr};
|
||||
int m_stateSlot = 1;
|
||||
QString m_statePath;
|
||||
VFile* m_stateVf;
|
||||
int m_loadStateFlags;
|
||||
int m_saveStateFlags;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "DisplayGL.h"
|
||||
#include "DisplayQt.h"
|
||||
#include "LogController.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
|
@ -27,16 +28,31 @@ Display* Display::create(QWidget* parent) {
|
|||
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY)
|
||||
case Driver::OPENGL:
|
||||
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
|
||||
format.setVersion(3, 0);
|
||||
format.setVersion(2, 0);
|
||||
} else {
|
||||
format.setVersion(3, 2);
|
||||
}
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
if (!DisplayGL::supportsFormat(format)) {
|
||||
#ifdef BUILD_GL
|
||||
LOG(QT, WARN) << ("Failed to create an OpenGL Core context, trying old-style...");
|
||||
format.setVersion(1, 4);
|
||||
format.setOption(QSurfaceFormat::DeprecatedFunctions);
|
||||
if (!DisplayGL::supportsFormat(format)) {
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
return new DisplayGL(format, parent);
|
||||
#endif
|
||||
#ifdef BUILD_GL
|
||||
case Driver::OPENGL1:
|
||||
format.setVersion(1, 4);
|
||||
if (!DisplayGL::supportsFormat(format)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new DisplayGL(format, parent);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -27,12 +27,8 @@ Q_OBJECT
|
|||
public:
|
||||
enum class Driver {
|
||||
QT = 0,
|
||||
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(USE_EPOXY)
|
||||
OPENGL = 1,
|
||||
#endif
|
||||
#ifdef BUILD_GL
|
||||
OPENGL1 = 2,
|
||||
#endif
|
||||
};
|
||||
|
||||
Display(QWidget* parent = nullptr);
|
||||
|
|
|
@ -10,9 +10,10 @@
|
|||
#include "CoreController.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMutexLocker>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLPaintDevice>
|
||||
#include <QMutexLocker>
|
||||
#include <QResizeEvent>
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
|
@ -34,6 +35,15 @@
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
|
||||
|
||||
uint qHash(const QSurfaceFormat& format, uint seed) {
|
||||
QByteArray representation;
|
||||
QDataStream stream(&representation, QIODevice::WriteOnly);
|
||||
stream << format.version() << format.renderableType() << format.profile();
|
||||
return qHash(representation, seed);
|
||||
}
|
||||
|
||||
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||
: Display(parent)
|
||||
{
|
||||
|
@ -41,10 +51,23 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
|||
windowHandle()->create();
|
||||
|
||||
m_painter = std::make_unique<PainterGL>(windowHandle(), format);
|
||||
m_drawThread.setObjectName("Painter Thread");
|
||||
m_painter->moveToThread(&m_drawThread);
|
||||
|
||||
connect(&m_drawThread, &QThread::started, m_painter.get(), &PainterGL::create);
|
||||
connect(m_painter.get(), &PainterGL::started, this, [this] {
|
||||
m_hasStarted = true;
|
||||
resizePainter();
|
||||
emit drawingStarted();
|
||||
});
|
||||
m_drawThread.start();
|
||||
}
|
||||
|
||||
DisplayGL::~DisplayGL() {
|
||||
stopDrawing();
|
||||
QMetaObject::invokeMethod(m_painter.get(), "destroy", Qt::BlockingQueuedConnection);
|
||||
m_drawThread.exit();
|
||||
m_drawThread.wait();
|
||||
}
|
||||
|
||||
bool DisplayGL::supportsShaders() const {
|
||||
|
@ -53,16 +76,12 @@ bool DisplayGL::supportsShaders() const {
|
|||
|
||||
VideoShader* DisplayGL::shaders() {
|
||||
VideoShader* shaders = nullptr;
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
|
||||
} else {
|
||||
shaders = m_painter->shaders();
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "shaders", Qt::BlockingQueuedConnection, Q_RETURN_ARG(VideoShader*, shaders));
|
||||
return shaders;
|
||||
}
|
||||
|
||||
void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
||||
if (m_drawThread) {
|
||||
if (m_isDrawing) {
|
||||
return;
|
||||
}
|
||||
QSize dims = controller->screenDimensions();
|
||||
|
@ -72,18 +91,9 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
m_painter->setContext(controller);
|
||||
m_painter->setMessagePainter(messagePainter());
|
||||
m_context = controller;
|
||||
m_drawThread = new QThread(this);
|
||||
m_drawThread->setObjectName("Painter Thread");
|
||||
m_painter->moveToThread(m_drawThread);
|
||||
if (videoProxy()) {
|
||||
videoProxy()->moveToThread(m_drawThread);
|
||||
videoProxy()->moveToThread(&m_drawThread);
|
||||
}
|
||||
connect(m_drawThread, &QThread::started, m_painter.get(), &PainterGL::start);
|
||||
connect(m_painter.get(), &PainterGL::started, this, [this] {
|
||||
m_hasStarted = true;
|
||||
resizePainter();
|
||||
emit drawingStarted();
|
||||
});
|
||||
|
||||
lockAspectRatio(isAspectRatioLocked());
|
||||
lockIntegerScaling(isIntegerScalingLocked());
|
||||
|
@ -91,32 +101,66 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
|||
showOSDMessages(isShowOSD());
|
||||
filter(isFiltered());
|
||||
|
||||
m_drawThread->start();
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
|
||||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatioF());
|
||||
#else
|
||||
messagePainter()->resize(size(), isAspectRatioLocked(), devicePixelRatio());
|
||||
#endif
|
||||
QMetaObject::invokeMethod(m_painter.get(), "start");
|
||||
setUpdatesEnabled(false);
|
||||
}
|
||||
|
||||
bool DisplayGL::supportsFormat(const QSurfaceFormat& format) {
|
||||
if (!s_supports.contains(format)) {
|
||||
QOpenGLContext context;
|
||||
context.setFormat(format);
|
||||
if (!context.create()) {
|
||||
s_supports[format] = false;
|
||||
return false;
|
||||
}
|
||||
auto foundVersion = context.format().version();
|
||||
if (foundVersion == format.version()) {
|
||||
// Match!
|
||||
s_supports[format] = true;
|
||||
} else if (format.version() >= qMakePair(3, 2) && foundVersion > format.version()) {
|
||||
// At least as good
|
||||
s_supports[format] = true;
|
||||
} else if (format.majorVersion() == 1 && (foundVersion < qMakePair(3, 0) ||
|
||||
context.format().profile() == QSurfaceFormat::CompatibilityProfile ||
|
||||
context.format().testOption(QSurfaceFormat::DeprecatedFunctions))) {
|
||||
// Supports the old stuff
|
||||
s_supports[format] = true;
|
||||
} else if (!context.isOpenGLES() && format.version() >= qMakePair(2, 1) && foundVersion < qMakePair(3, 0) && foundVersion >= qMakePair(2, 1)) {
|
||||
// Weird edge case we support if ARB_framebuffer_object is present
|
||||
QOffscreenSurface surface;
|
||||
surface.create();
|
||||
if (!context.makeCurrent(&surface)) {
|
||||
s_supports[format] = false;
|
||||
return false;
|
||||
}
|
||||
s_supports[format] = context.hasExtension("GL_ARB_framebuffer_object");
|
||||
context.doneCurrent();
|
||||
} else {
|
||||
// No match
|
||||
s_supports[format] = false;
|
||||
}
|
||||
}
|
||||
return s_supports[format];
|
||||
}
|
||||
|
||||
void DisplayGL::stopDrawing() {
|
||||
if (m_drawThread) {
|
||||
if (m_hasStarted || m_isDrawing) {
|
||||
m_isDrawing = false;
|
||||
m_hasStarted = false;
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter.get(), "stop", Qt::BlockingQueuedConnection);
|
||||
m_drawThread->exit();
|
||||
m_drawThread->wait();
|
||||
m_drawThread = nullptr;
|
||||
setUpdatesEnabled(true);
|
||||
}
|
||||
m_context.reset();
|
||||
}
|
||||
|
||||
void DisplayGL::pauseDrawing() {
|
||||
if (m_drawThread) {
|
||||
if (m_hasStarted) {
|
||||
m_isDrawing = false;
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter.get(), "pause", Qt::BlockingQueuedConnection);
|
||||
|
@ -127,7 +171,7 @@ void DisplayGL::pauseDrawing() {
|
|||
}
|
||||
|
||||
void DisplayGL::unpauseDrawing() {
|
||||
if (m_drawThread) {
|
||||
if (m_hasStarted) {
|
||||
m_isDrawing = true;
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter.get(), "unpause", Qt::BlockingQueuedConnection);
|
||||
|
@ -138,78 +182,59 @@ void DisplayGL::unpauseDrawing() {
|
|||
}
|
||||
|
||||
void DisplayGL::forceDraw() {
|
||||
if (m_drawThread) {
|
||||
if (m_hasStarted) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "forceDraw");
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::lockAspectRatio(bool lock) {
|
||||
Display::lockAspectRatio(lock);
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "lockAspectRatio", Q_ARG(bool, lock));
|
||||
}
|
||||
|
||||
void DisplayGL::lockIntegerScaling(bool lock) {
|
||||
Display::lockIntegerScaling(lock);
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "lockIntegerScaling", Q_ARG(bool, lock));
|
||||
}
|
||||
|
||||
void DisplayGL::interframeBlending(bool enable) {
|
||||
Display::interframeBlending(enable);
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "interframeBlending", Q_ARG(bool, enable));
|
||||
}
|
||||
|
||||
void DisplayGL::showOSDMessages(bool enable) {
|
||||
Display::showOSDMessages(enable);
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
|
||||
}
|
||||
|
||||
void DisplayGL::filter(bool filter) {
|
||||
Display::filter(filter);
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
|
||||
}
|
||||
|
||||
void DisplayGL::framePosted() {
|
||||
if (m_drawThread) {
|
||||
m_painter->enqueue(m_context->drawContext());
|
||||
QMetaObject::invokeMethod(m_painter.get(), "draw");
|
||||
}
|
||||
m_painter->enqueue(m_context->drawContext());
|
||||
QMetaObject::invokeMethod(m_painter.get(), "draw");
|
||||
}
|
||||
|
||||
void DisplayGL::setShaders(struct VDir* shaders) {
|
||||
if (m_drawThread) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
|
||||
} else {
|
||||
m_painter->setShaders(shaders);
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "setShaders", Qt::BlockingQueuedConnection, Q_ARG(struct VDir*, shaders));
|
||||
}
|
||||
|
||||
void DisplayGL::clearShaders() {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "clearShaders");
|
||||
QMetaObject::invokeMethod(m_painter.get(), "clearShaders", Qt::BlockingQueuedConnection);
|
||||
}
|
||||
|
||||
void DisplayGL::resizeContext() {
|
||||
if (m_drawThread) {
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
|
||||
}
|
||||
|
||||
void DisplayGL::setVideoScale(int scale) {
|
||||
if (m_drawThread) {
|
||||
if (m_context) {
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
mCoreConfigSetIntValue(&m_context->thread()->core->config, "videoScale", scale);
|
||||
QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
|
||||
}
|
||||
QMetaObject::invokeMethod(m_painter.get(), "resizeContext");
|
||||
}
|
||||
|
||||
void DisplayGL::resizeEvent(QResizeEvent* event) {
|
||||
|
@ -218,15 +243,15 @@ void DisplayGL::resizeEvent(QResizeEvent* event) {
|
|||
}
|
||||
|
||||
void DisplayGL::resizePainter() {
|
||||
if (m_drawThread && m_hasStarted) {
|
||||
if (m_hasStarted) {
|
||||
QMetaObject::invokeMethod(m_painter.get(), "resize", Qt::BlockingQueuedConnection, Q_ARG(QSize, size()));
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayGL::setVideoProxy(std::shared_ptr<VideoProxy> proxy) {
|
||||
Display::setVideoProxy(proxy);
|
||||
if (m_drawThread && proxy) {
|
||||
proxy->moveToThread(m_drawThread);
|
||||
if (proxy) {
|
||||
proxy->moveToThread(&m_drawThread);
|
||||
}
|
||||
m_painter->setVideoProxy(proxy);
|
||||
}
|
||||
|
@ -239,6 +264,7 @@ PainterGL::PainterGL(QWindow* surface, const QSurfaceFormat& format)
|
|||
: m_surface(surface)
|
||||
, m_format(format)
|
||||
{
|
||||
m_supportsShaders = m_format.version() >= qMakePair(2, 0);
|
||||
for (auto& buf : m_buffers) {
|
||||
m_free.append(&buf.front());
|
||||
}
|
||||
|
@ -263,24 +289,6 @@ void PainterGL::create() {
|
|||
m_gl->create();
|
||||
makeCurrent();
|
||||
|
||||
auto version = m_gl->format().version();
|
||||
QStringList extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
|
||||
|
||||
int forceVersion = 0;
|
||||
if (m_format.majorVersion() < 2) {
|
||||
forceVersion = 1;
|
||||
}
|
||||
|
||||
if ((version == qMakePair(2, 1) && !extensions.contains("GL_ARB_framebuffer_object")) || version == qMakePair(2, 0)) {
|
||||
QSurfaceFormat newFormat(m_format);
|
||||
newFormat.setVersion(1, 4);
|
||||
forceVersion = 1;
|
||||
m_gl->doneCurrent();
|
||||
m_gl->setFormat(newFormat);
|
||||
m_gl->create();
|
||||
makeCurrent();
|
||||
}
|
||||
|
||||
#ifdef BUILD_GL
|
||||
mGLContext* glBackend;
|
||||
#endif
|
||||
|
@ -291,13 +299,11 @@ void PainterGL::create() {
|
|||
m_window = std::make_unique<QOpenGLPaintDevice>();
|
||||
|
||||
#ifdef BUILD_GLES2
|
||||
version = m_gl->format().version();
|
||||
extensions = QString(reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS))).split(' ');
|
||||
if (forceVersion != 1 && ((version == qMakePair(2, 1) && extensions.contains("GL_ARB_framebuffer_object")) || version.first > 2)) {
|
||||
auto version = m_format.version();
|
||||
if (version >= qMakePair(2, 0)) {
|
||||
gl2Backend = static_cast<mGLES2Context*>(malloc(sizeof(mGLES2Context)));
|
||||
mGLES2ContextCreate(gl2Backend);
|
||||
m_backend = &gl2Backend->d;
|
||||
m_supportsShaders = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -306,7 +312,6 @@ void PainterGL::create() {
|
|||
glBackend = static_cast<mGLContext*>(malloc(sizeof(mGLContext)));
|
||||
mGLContextCreate(glBackend);
|
||||
m_backend = &glBackend->d;
|
||||
m_supportsShaders = false;
|
||||
}
|
||||
#endif
|
||||
m_backend->swap = [](VideoBackend* v) {
|
||||
|
@ -336,15 +341,6 @@ void PainterGL::destroy() {
|
|||
return;
|
||||
}
|
||||
makeCurrent();
|
||||
if (m_context) {
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->detach(m_context.get());
|
||||
}
|
||||
m_context->setFramebufferHandle(-1);
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->processData();
|
||||
}
|
||||
}
|
||||
#ifdef BUILD_GLES2
|
||||
if (m_shader.passes) {
|
||||
mGLES2ShaderFree(&m_shader);
|
||||
|
@ -369,6 +365,7 @@ void PainterGL::resizeContext() {
|
|||
}
|
||||
|
||||
if (m_started) {
|
||||
CoreController::Interrupter interrupter(m_context);
|
||||
mCore* core = m_context->thread()->core;
|
||||
core->reloadConfigOption(core, "videoScale", NULL);
|
||||
}
|
||||
|
@ -415,9 +412,6 @@ void PainterGL::filter(bool filter) {
|
|||
}
|
||||
|
||||
void PainterGL::start() {
|
||||
if (!m_gl) {
|
||||
create();
|
||||
}
|
||||
makeCurrent();
|
||||
|
||||
#ifdef BUILD_GLES2
|
||||
|
@ -444,7 +438,7 @@ void PainterGL::draw() {
|
|||
if (!mCoreSyncWaitFrameStart(sync)) {
|
||||
mCoreSyncWaitFrameEnd(sync);
|
||||
++m_lagging;
|
||||
if (m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
|
||||
if ((sync->audioWait || sync->videoFrameWait) && m_delayTimer.elapsed() < 1000 / m_surface->screen()->refreshRate()) {
|
||||
QTimer::singleShot(1, this, &PainterGL::draw);
|
||||
}
|
||||
return;
|
||||
|
@ -456,16 +450,18 @@ void PainterGL::draw() {
|
|||
}
|
||||
if (!m_delayTimer.isValid()) {
|
||||
m_delayTimer.start();
|
||||
} else if (sync->audioWait || sync->videoFrameWait) {
|
||||
while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
|
||||
QThread::usleep(500);
|
||||
} else {
|
||||
if (sync->audioWait || sync->videoFrameWait) {
|
||||
while (m_delayTimer.nsecsElapsed() + 2000000 < 1000000000 / sync->fpsTarget) {
|
||||
QThread::usleep(500);
|
||||
}
|
||||
}
|
||||
m_delayTimer.restart();
|
||||
}
|
||||
mCoreSyncWaitFrameEnd(sync);
|
||||
|
||||
performDraw();
|
||||
m_backend->swap(m_backend);
|
||||
m_delayTimer.restart();
|
||||
}
|
||||
|
||||
void PainterGL::forceDraw() {
|
||||
|
@ -483,16 +479,23 @@ void PainterGL::stop() {
|
|||
m_active = false;
|
||||
m_started = false;
|
||||
dequeueAll();
|
||||
m_backend->clear(m_backend);
|
||||
m_backend->swap(m_backend);
|
||||
if (m_context) {
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->detach(m_context.get());
|
||||
}
|
||||
m_context->setFramebufferHandle(-1);
|
||||
m_context.reset();
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->processData();
|
||||
}
|
||||
}
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->reset();
|
||||
}
|
||||
destroy();
|
||||
moveToThread(m_surface->thread());
|
||||
if (m_videoProxy) {
|
||||
m_videoProxy->moveToThread(m_surface->thread());
|
||||
m_videoProxy.reset();
|
||||
}
|
||||
m_backend->clear(m_backend);
|
||||
m_backend->swap(m_backend);
|
||||
}
|
||||
|
||||
void PainterGL::pause() {
|
||||
|
@ -549,7 +552,6 @@ void PainterGL::dequeue() {
|
|||
m_buffer = nullptr;
|
||||
}
|
||||
m_buffer = buffer;
|
||||
return;
|
||||
}
|
||||
|
||||
void PainterGL::dequeueAll() {
|
||||
|
@ -580,9 +582,6 @@ void PainterGL::setShaders(struct VDir* dir) {
|
|||
return;
|
||||
}
|
||||
#ifdef BUILD_GLES2
|
||||
if (!m_started) {
|
||||
return; // TODO
|
||||
}
|
||||
if (m_shader.passes) {
|
||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||
mGLES2ShaderFree(&m_shader);
|
||||
|
@ -597,9 +596,6 @@ void PainterGL::clearShaders() {
|
|||
return;
|
||||
}
|
||||
#ifdef BUILD_GLES2
|
||||
if (!m_started) {
|
||||
return; // TODO
|
||||
}
|
||||
if (m_shader.passes) {
|
||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||
mGLES2ShaderFree(&m_shader);
|
||||
|
|
|
@ -18,9 +18,10 @@
|
|||
|
||||
#include <QAtomicInt>
|
||||
#include <QElapsedTimer>
|
||||
#include <QOpenGLContext>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
#include <QOpenGLContext>
|
||||
#include <QPainter>
|
||||
#include <QQueue>
|
||||
#include <QThread>
|
||||
|
@ -34,6 +35,8 @@
|
|||
|
||||
class QOpenGLPaintDevice;
|
||||
|
||||
uint qHash(const QSurfaceFormat&, uint seed = 0);
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class PainterGL;
|
||||
|
@ -51,6 +54,8 @@ public:
|
|||
void setVideoProxy(std::shared_ptr<VideoProxy>) override;
|
||||
int framebufferHandle() override;
|
||||
|
||||
static bool supportsFormat(const QSurfaceFormat&);
|
||||
|
||||
public slots:
|
||||
void stopDrawing() override;
|
||||
void pauseDrawing() override;
|
||||
|
@ -74,10 +79,12 @@ protected:
|
|||
private:
|
||||
void resizePainter();
|
||||
|
||||
static QHash<QSurfaceFormat, bool> s_supports;
|
||||
|
||||
bool m_isDrawing = false;
|
||||
bool m_hasStarted = false;
|
||||
std::unique_ptr<PainterGL> m_painter;
|
||||
QThread* m_drawThread = nullptr;
|
||||
QThread m_drawThread;
|
||||
std::shared_ptr<CoreController> m_context;
|
||||
};
|
||||
|
||||
|
@ -97,6 +104,9 @@ public:
|
|||
void setVideoProxy(std::shared_ptr<VideoProxy>);
|
||||
|
||||
public slots:
|
||||
void create();
|
||||
void destroy();
|
||||
|
||||
void forceDraw();
|
||||
void draw();
|
||||
void start();
|
||||
|
@ -125,8 +135,6 @@ private:
|
|||
void performDraw();
|
||||
void dequeue();
|
||||
void dequeueAll();
|
||||
void create();
|
||||
void destroy();
|
||||
|
||||
std::array<std::array<uint32_t, 0x100000>, 3> m_buffers;
|
||||
QList<uint32_t*> m_free;
|
||||
|
|
|
@ -302,6 +302,10 @@ void FrameView::injectGBA() {
|
|||
case LayerId::WINDOW:
|
||||
m_vl->enableVideoLayer(m_vl, GBA_LAYER_WIN0 + layer.id.index, layer.enabled);
|
||||
break;
|
||||
case LayerId::BACKDROP:
|
||||
case LayerId::FRAME:
|
||||
case LayerId::NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_overrideBackdrop.isValid()) {
|
||||
|
@ -408,6 +412,10 @@ void FrameView::injectGB() {
|
|||
gb->video.renderer->highlightWIN = true;
|
||||
}
|
||||
break;
|
||||
case LayerId::FRAME: // TODO for SGB
|
||||
case LayerId::BACKDROP:
|
||||
case LayerId::NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -433,6 +441,8 @@ void FrameView::invalidateQueue(const QSize& dims) {
|
|||
injectGB();
|
||||
break;
|
||||
#endif
|
||||
case PLATFORM_NONE:
|
||||
break;
|
||||
}
|
||||
if (m_ui.disableScanline->checkState() == Qt::Checked) {
|
||||
mVideoLoggerIgnoreAfterInjection(logger, (1 << DIRTY_PALETTE) | (1 << DIRTY_OAM) | (1 << DIRTY_REGISTER));
|
||||
|
@ -508,7 +518,7 @@ bool FrameView::eventFilter(QObject*, QEvent* event) {
|
|||
void FrameView::refreshVl() {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_currentFrame = m_nextFrame;
|
||||
m_nextFrame = VFileMemChunk(nullptr, 0);
|
||||
m_nextFrame = VFileDevice::openMemory();
|
||||
if (m_currentFrame) {
|
||||
m_controller->endVideoLog(false);
|
||||
QMetaObject::invokeMethod(this, "newVl");
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include <QFileInfo>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QFontDatabase>
|
||||
#include <QIcon>
|
||||
|
||||
#include <mgba-util/socket.h>
|
||||
|
@ -35,11 +36,14 @@ static GBAApp* g_app = nullptr;
|
|||
|
||||
mLOG_DEFINE_CATEGORY(QT, "Qt", "platform.qt");
|
||||
|
||||
QFont GBAApp::s_monospace;
|
||||
|
||||
GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
|
||||
: QApplication(argc, argv)
|
||||
, m_configController(config)
|
||||
{
|
||||
g_app = this;
|
||||
s_monospace = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
|
||||
#ifdef BUILD_SDL
|
||||
SDL_Init(SDL_INIT_NOPARACHUTE);
|
||||
|
@ -184,12 +188,12 @@ QString GBAApp::getSaveFileName(QWidget* owner, const QString& title, const QStr
|
|||
return filename;
|
||||
}
|
||||
|
||||
QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title) {
|
||||
QString GBAApp::getOpenDirectoryName(QWidget* owner, const QString& title, const QString& path) {
|
||||
QList<Window*> paused;
|
||||
pauseAll(&paused);
|
||||
QString filename = QFileDialog::getExistingDirectory(owner, title, m_configController->getOption("lastDirectory"));
|
||||
QString filename = QFileDialog::getExistingDirectory(owner, title, !path.isNull() ? path : m_configController->getOption("lastDirectory"));
|
||||
continueAll(paused);
|
||||
if (!filename.isEmpty()) {
|
||||
if (path.isNull() && !filename.isEmpty()) {
|
||||
m_configController->setOption("lastDirectory", QFileInfo(filename).dir().canonicalPath());
|
||||
}
|
||||
return filename;
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QFont>
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QMultiMap>
|
||||
|
@ -56,12 +57,15 @@ public:
|
|||
|
||||
static QString dataDir();
|
||||
|
||||
static QFont monospaceFont() { return s_monospace; }
|
||||
|
||||
QList<Window*> windows() { return m_windows; }
|
||||
Window* newWindow();
|
||||
|
||||
QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = QString());
|
||||
QString getOpenDirectoryName(QWidget* owner, const QString& title);
|
||||
QString getOpenFileName(QWidget* owner, const QString& title, const QString& filter = {});
|
||||
QStringList getOpenFileNames(QWidget* owner, const QString& title, const QString& filter = {});
|
||||
QString getSaveFileName(QWidget* owner, const QString& title, const QString& filter = {});
|
||||
QString getOpenDirectoryName(QWidget* owner, const QString& title, const QString& path = {});
|
||||
|
||||
const NoIntroDB* gameDB() const { return m_db; }
|
||||
bool reloadGameDB();
|
||||
|
@ -110,6 +114,8 @@ private:
|
|||
QThreadPool m_workerThreads;
|
||||
qint64 m_nextJob = 1;
|
||||
|
||||
static QFont s_monospace;
|
||||
|
||||
NoIntroDB* m_db = nullptr;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
#include "IOViewer.h"
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QFontDatabase>
|
||||
#include <QGridLayout>
|
||||
#include <QSpinBox>
|
||||
|
||||
|
@ -1037,7 +1037,7 @@ IOViewer::IOViewer(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_ui.regSelect->addItem("0x0400" + QString("%1: %2").arg(i << 1, 4, 16, QChar('0')).toUpper().arg(reg), i << 1);
|
||||
}
|
||||
|
||||
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
m_ui.regValue->setFont(font);
|
||||
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::clicked, this, &IOViewer::buttonPressed);
|
||||
|
@ -1072,17 +1072,16 @@ IOViewer::IOViewer(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
}
|
||||
|
||||
void IOViewer::updateRegister() {
|
||||
m_value = 0;
|
||||
uint16_t value = 0;
|
||||
{
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
value = GBAView16(static_cast<ARMCore*>(m_controller->thread()->core->cpu), BASE_IO | m_register);
|
||||
m_value = GBAView16(static_cast<ARMCore*>(m_controller->thread()->core->cpu), BASE_IO | m_register);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
m_b[i]->setChecked(value & (1 << i));
|
||||
QSignalBlocker blocker(m_b[i]);
|
||||
m_b[i]->setChecked(m_value & (1 << i));
|
||||
}
|
||||
m_value = value;
|
||||
m_ui.regValue->setText("0x" + QString("%1").arg(m_value, 4, 16, QChar('0')).toUpper());
|
||||
emit valueChanged();
|
||||
}
|
||||
|
||||
|
@ -1103,7 +1102,7 @@ void IOViewer::writeback() {
|
|||
updateRegister();
|
||||
}
|
||||
|
||||
void IOViewer::selectRegister(unsigned address) {
|
||||
void IOViewer::selectRegister(int address) {
|
||||
m_register = address;
|
||||
QGridLayout* box = static_cast<QGridLayout*>(m_ui.regDescription->layout());
|
||||
if (box) {
|
||||
|
@ -1130,7 +1129,10 @@ void IOViewer::selectRegister(unsigned address) {
|
|||
check->setEnabled(!ri.readonly);
|
||||
box->addWidget(check, i, 1, Qt::AlignRight);
|
||||
connect(check, &QAbstractButton::toggled, m_b[ri.start], &QAbstractButton::setChecked);
|
||||
connect(m_b[ri.start], &QAbstractButton::toggled, check, &QAbstractButton::setChecked);
|
||||
connect(this, &IOViewer::valueChanged, check, [check, this, &ri] {
|
||||
QSignalBlocker blocker(check);
|
||||
check->setChecked(bool(m_value & (1 << ri.start)));
|
||||
});
|
||||
} else if (ri.items.empty()) {
|
||||
QSpinBox* sbox = new QSpinBox;
|
||||
sbox->setEnabled(!ri.readonly);
|
||||
|
@ -1140,20 +1142,16 @@ void IOViewer::selectRegister(unsigned address) {
|
|||
|
||||
connect(sbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [sbox, this, &ri](int v) {
|
||||
for (int o = 0; o < ri.size; ++o) {
|
||||
bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
|
||||
QSignalBlocker blocker(m_b[o + ri.start]);
|
||||
m_b[o + ri.start]->setChecked(v & (1 << o));
|
||||
m_b[o + ri.start]->blockSignals(signalsBlocked);
|
||||
}
|
||||
bitFlipped();
|
||||
});
|
||||
|
||||
auto connection = connect(this, &IOViewer::valueChanged, [sbox, &ri, this]() {
|
||||
connect(this, &IOViewer::valueChanged, sbox, [sbox, this, &ri]() {
|
||||
QSignalBlocker blocker(sbox);
|
||||
int v = (m_value >> ri.start) & ((1 << ri.size) - 1);
|
||||
bool signalsBlocked = sbox->blockSignals(true);
|
||||
sbox->setValue(v);
|
||||
sbox->blockSignals(signalsBlocked);
|
||||
});
|
||||
connect(sbox, &QObject::destroyed, [connection, this]() {
|
||||
this->disconnect(connection);
|
||||
});
|
||||
} else {
|
||||
QComboBox* cbox = new QComboBox;
|
||||
|
@ -1170,24 +1168,22 @@ void IOViewer::selectRegister(unsigned address) {
|
|||
connect(cbox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [cbox, this, &ri](int index) {
|
||||
unsigned v = cbox->itemData(index).toUInt();
|
||||
for (int o = 0; o < ri.size; ++o) {
|
||||
bool signalsBlocked = m_b[o + ri.start]->blockSignals(true);
|
||||
QSignalBlocker blocker(m_b[o + ri.start]);
|
||||
m_b[o + ri.start]->setChecked(v & (1 << o));
|
||||
m_b[o + ri.start]->blockSignals(signalsBlocked);
|
||||
}
|
||||
bitFlipped();
|
||||
});
|
||||
|
||||
auto connection = connect(this, &IOViewer::valueChanged, [cbox, this, &ri]() {
|
||||
connect(this, &IOViewer::valueChanged, cbox, [cbox, this, &ri]() {
|
||||
QSignalBlocker blocker(cbox);
|
||||
unsigned v = (m_value >> ri.start) & ((1 << ri.size) - 1);
|
||||
for (int i = 0; i < 1 << ri.size; ++i) {
|
||||
if (cbox->itemData(i) == v) {
|
||||
cbox->setCurrentIndex(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(cbox, &QObject::destroyed, [connection, this]() {
|
||||
this->disconnect(connection);
|
||||
});
|
||||
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
|
|
@ -21,19 +21,19 @@ Q_OBJECT
|
|||
|
||||
public:
|
||||
struct RegisterItem {
|
||||
RegisterItem(const QString& description, uint start, uint size = 1, bool readonly = false)
|
||||
RegisterItem(const QString& description, uint start, int size = 1, bool readonly = false)
|
||||
: start(start)
|
||||
, size(size)
|
||||
, readonly(readonly)
|
||||
, description(description) {}
|
||||
RegisterItem(const QString& description, uint start, uint size, QStringList items, bool readonly = false)
|
||||
RegisterItem(const QString& description, uint start, int size, QStringList items, bool readonly = false)
|
||||
: start(start)
|
||||
, size(size)
|
||||
, readonly(readonly)
|
||||
, description(description)
|
||||
, items(items) {}
|
||||
uint start;
|
||||
uint size;
|
||||
int size;
|
||||
bool readonly;
|
||||
QString description;
|
||||
QStringList items;
|
||||
|
@ -49,7 +49,7 @@ signals:
|
|||
|
||||
public slots:
|
||||
void updateRegister();
|
||||
void selectRegister(unsigned address);
|
||||
void selectRegister(int address);
|
||||
|
||||
private slots:
|
||||
void buttonPressed(QAbstractButton* button);
|
||||
|
@ -61,7 +61,7 @@ private:
|
|||
static QList<RegisterDescription> s_registers;
|
||||
Ui::IOViewer m_ui;
|
||||
|
||||
unsigned m_register;
|
||||
int m_register;
|
||||
uint16_t m_value;
|
||||
|
||||
QCheckBox* m_b[16];
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -79,7 +79,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>5</string>
|
||||
<string notr="true">5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -97,7 +97,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>4</string>
|
||||
<string notr="true">4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -115,7 +115,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>7</string>
|
||||
<string notr="true">7</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -133,7 +133,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -154,7 +154,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>9</string>
|
||||
<string notr="true">9</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -175,7 +175,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -193,7 +193,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>3</string>
|
||||
<string notr="true">3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -214,7 +214,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
<string notr="true">8</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -238,7 +238,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>C</string>
|
||||
<string notr="true">C</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -256,7 +256,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>E</string>
|
||||
<string notr="true">E</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -280,7 +280,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>6</string>
|
||||
<string notr="true">6</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -307,7 +307,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>D</string>
|
||||
<string notr="true">D</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -325,7 +325,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>F</string>
|
||||
<string notr="true">F</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -343,7 +343,7 @@
|
|||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>A</string>
|
||||
<string notr="true">A</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -56,7 +56,12 @@ bool LogConfigModel::setData(const QModelIndex& index, const QVariant& value, in
|
|||
if (levels < 0) {
|
||||
levels = m_levels;
|
||||
}
|
||||
levels ^= 1 << (index.column() - 1);
|
||||
int bit = 1 << (index.column() - 1);
|
||||
if (value.value<Qt::CheckState>() == Qt::Unchecked) {
|
||||
levels &= ~bit;
|
||||
} else {
|
||||
levels |= bit;
|
||||
}
|
||||
}
|
||||
if (index.row() == 0) {
|
||||
beginResetModel();
|
||||
|
@ -102,18 +107,27 @@ QVariant LogConfigModel::headerData(int section, Qt::Orientation orientation, in
|
|||
}
|
||||
|
||||
QModelIndex LogConfigModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return createIndex(row, column, nullptr);
|
||||
}
|
||||
|
||||
QModelIndex LogConfigModel::parent(const QModelIndex& index) const {
|
||||
QModelIndex LogConfigModel::parent(const QModelIndex&) const {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
int LogConfigModel::columnCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
|
||||
int LogConfigModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return m_cache.size() + 1;
|
||||
}
|
||||
|
||||
|
@ -146,4 +160,4 @@ void LogConfigModel::save(ConfigController* config) {
|
|||
}
|
||||
m_controller->setLevels(m_levels);
|
||||
m_controller->save(config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include <QAction>
|
||||
#include <QButtonGroup>
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QMouseEvent>
|
||||
#include <QRadioButton>
|
||||
#include <QTimer>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
@ -143,18 +143,18 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetTile.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetInfo</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetInfo.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetTile.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>:</string>
|
||||
<string notr="true">:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -55,7 +55,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>0x</string>
|
||||
<string notr="true">0x</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>268435455</number>
|
||||
|
@ -86,7 +86,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>0x</string>
|
||||
<string notr="true">0x</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
|
|
@ -27,8 +27,7 @@ using namespace QGBA;
|
|||
MemoryModel::MemoryModel(QWidget* parent)
|
||||
: QAbstractScrollArea(parent)
|
||||
{
|
||||
m_font.setFamily("Source Code Pro");
|
||||
m_font.setStyleHint(QFont::Monospace);
|
||||
m_font = GBAApp::monospaceFont();
|
||||
#ifdef Q_OS_MAC
|
||||
m_font.setPointSize(12);
|
||||
#else
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="segmentColon">
|
||||
<property name="text">
|
||||
<string>:</string>
|
||||
<string notr="true">:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -65,7 +65,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="prefix">
|
||||
<string>0x</string>
|
||||
<string notr="true">0x</string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>268435455</number>
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "MessagePainter.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include "GBAApp.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
|
||||
#include <mgba/gba/interface.h>
|
||||
|
||||
|
@ -16,8 +16,7 @@ using namespace QGBA;
|
|||
MessagePainter::MessagePainter(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
m_messageFont.setFamily("Source Code Pro");
|
||||
m_messageFont.setStyleHint(QFont::Monospace);
|
||||
m_messageFont = GBAApp::monospaceFont();
|
||||
m_messageFont.setPixelSize(13);
|
||||
connect(&m_messageTimer, &QTimer::timeout, this, &MessagePainter::clearMessage);
|
||||
m_messageTimer.setSingleShot(true);
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include <QAction>
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QListWidgetItem>
|
||||
#include <QTimer>
|
||||
|
||||
|
@ -34,7 +33,7 @@ ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_ui.setupUi(this);
|
||||
m_ui.tile->setController(controller);
|
||||
|
||||
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
|
||||
m_ui.x->setFont(font);
|
||||
m_ui.y->setFont(font);
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="address">
|
||||
<property name="text">
|
||||
<string>0x07000000</string>
|
||||
<string notr="true">0x07000000</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -106,7 +106,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
@ -168,7 +168,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -194,7 +194,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -241,7 +241,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
<string notr="true">8</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -267,7 +267,7 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>8</string>
|
||||
<string notr="true">8</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -281,14 +281,14 @@
|
|||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="xformPC">
|
||||
<property name="text">
|
||||
<string>+0.00</string>
|
||||
<string notr="true">+0.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="xformPA">
|
||||
<property name="text">
|
||||
<string>+1.00</string>
|
||||
<string notr="true">+1.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -302,7 +302,7 @@
|
|||
<item row="1" column="3">
|
||||
<widget class="QLabel" name="xformPD">
|
||||
<property name="text">
|
||||
<string>+1.00</string>
|
||||
<string notr="true">+1.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -322,7 +322,7 @@
|
|||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="xformPB">
|
||||
<property name="text">
|
||||
<string>+0.00</string>
|
||||
<string notr="true">+0.00</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -434,7 +434,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="palette">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -515,7 +515,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>H</string>
|
||||
<string extracomment="Short for horizontal">H</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Return, Ctrl+R</string>
|
||||
|
@ -528,7 +528,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>V</string>
|
||||
<string extracomment="Short for vertical">V</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Return, Ctrl+R</string>
|
||||
|
@ -664,7 +664,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="priority">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
<string notr="true">0</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -686,6 +686,12 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetTile.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::TilePainter</class>
|
||||
<extends>QWidget</extends>
|
||||
|
@ -695,12 +701,6 @@
|
|||
<slot>setTileMagnification(int)</slot>
|
||||
</slots>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetTile.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
|
|
@ -142,6 +142,7 @@ void OverrideView::updateOverrides() {
|
|||
gba->override.hardware = HW_NO_OVERRIDE;
|
||||
gba->override.idleLoop = IDLE_LOOP_NONE;
|
||||
gba->override.mirroring = false;
|
||||
gba->override.vbaBugCompat = false;
|
||||
|
||||
if (!m_ui.hwAutodetect->isChecked()) {
|
||||
gba->override.hardware = HW_NONE;
|
||||
|
@ -164,6 +165,9 @@ void OverrideView::updateOverrides() {
|
|||
if (m_ui.hwGBPlayer->isChecked()) {
|
||||
gba->override.hardware |= HW_GB_PLAYER_DETECTION;
|
||||
}
|
||||
if (m_ui.vbaBugCompat->isChecked()) {
|
||||
gba->override.vbaBugCompat = true;
|
||||
}
|
||||
|
||||
bool ok;
|
||||
uint32_t parsedIdleLoop = m_ui.idleLoop->text().toInt(&ok, 16);
|
||||
|
@ -219,6 +223,7 @@ void OverrideView::gameStarted() {
|
|||
m_ui.hwTilt->setChecked(gba->memory.hw.devices & HW_TILT);
|
||||
m_ui.hwRumble->setChecked(gba->memory.hw.devices & HW_RUMBLE);
|
||||
m_ui.hwGBPlayer->setChecked(gba->memory.hw.devices & HW_GB_PLAYER_DETECTION);
|
||||
m_ui.vbaBugCompat->setChecked(gba->vbaBugCompat);
|
||||
|
||||
if (gba->idleLoop != IDLE_LOOP_NONE) {
|
||||
m_ui.idleLoop->setText(QString::number(gba->idleLoop, 16));
|
||||
|
|
|
@ -175,41 +175,18 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="hwGBPlayer">
|
||||
<property name="text">
|
||||
<string>Game Boy Player features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<widget class="QCheckBox" name="hwGBPlayer">
|
||||
<property name="text">
|
||||
<string>Game Boy Player features</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="vbaBugCompat">
|
||||
<property name="text">
|
||||
<string>VBA bug compatibility mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#include "VFileDevice.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFontDatabase>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
#ifdef M_CORE_GBA
|
||||
|
@ -48,7 +47,7 @@ PaletteView::PaletteView(std::shared_ptr<CoreController> controller, QWidget* pa
|
|||
m_ui.selected->setDimensions(QSize(1, 1));
|
||||
updatePalette();
|
||||
|
||||
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
|
||||
m_ui.hexcode->setFont(font);
|
||||
m_ui.value->setFont(font);
|
||||
|
|
|
@ -209,21 +209,21 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="r">
|
||||
<property name="text">
|
||||
<string>0x00 (00)</string>
|
||||
<string notr="true">0x00 (00)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="g">
|
||||
<property name="text">
|
||||
<string>0x00 (00)</string>
|
||||
<string notr="true">0x00 (00)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="b">
|
||||
<property name="text">
|
||||
<string>0x00 (00)</string>
|
||||
<string notr="true">0x00 (00)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -269,7 +269,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="value">
|
||||
<property name="text">
|
||||
<string>0x0000</string>
|
||||
<string notr="true">0x0000</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -279,7 +279,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="hexcode">
|
||||
<property name="text">
|
||||
<string>#000000</string>
|
||||
<string notr="true">#000000</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
@ -289,7 +289,7 @@
|
|||
<item>
|
||||
<widget class="QLabel" name="index">
|
||||
<property name="text">
|
||||
<string>0x000 (000)</string>
|
||||
<string notr="true">0x000 (000)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
|
|
|
@ -164,7 +164,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "RegisterView.h"
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "GBAApp.h"
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/arm/arm.h>
|
||||
|
@ -14,7 +15,6 @@
|
|||
#include <mgba/internal/sm83/sm83.h>
|
||||
#endif
|
||||
|
||||
#include <QFontDatabase>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
|
||||
|
@ -74,7 +74,7 @@ RegisterView::RegisterView(std::shared_ptr<CoreController> controller, QWidget*
|
|||
|
||||
void RegisterView::addRegisters(const QStringList& names) {
|
||||
QFormLayout* form = static_cast<QFormLayout*>(layout());
|
||||
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
||||
const QFont font = GBAApp::monospaceFont();
|
||||
for (const auto& reg : names) {
|
||||
QLabel* value = new QLabel;
|
||||
value->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
|
|
|
@ -0,0 +1,445 @@
|
|||
/* Copyright (c) 2013-2020 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ReportView.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDesktopServices>
|
||||
#include <QOffscreenSurface>
|
||||
#include <QScreen>
|
||||
#include <QSysInfo>
|
||||
#include <QWindow>
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#include "CoreController.h"
|
||||
#include "GBAApp.h"
|
||||
#include "Window.h"
|
||||
|
||||
#include "ui_ReportView.h"
|
||||
|
||||
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
||||
#define USE_CPUID
|
||||
#include <cpuid.h>
|
||||
#endif
|
||||
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
||||
#define USE_CPUID
|
||||
#endif
|
||||
|
||||
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3) || defined(USE_EPOXY)
|
||||
#define DISPLAY_GL_INFO
|
||||
|
||||
#include "DisplayGL.h"
|
||||
|
||||
#include <QOpenGLFunctions>
|
||||
#endif
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
#include "feature/sqlite3/no-intro.h"
|
||||
#endif
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
static const QLatin1String yesNo[2] = {
|
||||
QLatin1String("No"),
|
||||
QLatin1String("Yes")
|
||||
};
|
||||
|
||||
#ifdef USE_CPUID
|
||||
unsigned ReportView::s_cpuidMax = 0xFFFFFFFF;
|
||||
unsigned ReportView::s_cpuidExtMax = 0xFFFFFFFF;
|
||||
#endif
|
||||
|
||||
ReportView::ReportView(QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
QString description = m_ui.description->text();
|
||||
description.replace("{projectName}", QLatin1String(projectName));
|
||||
m_ui.description->setText(description);
|
||||
|
||||
connect(m_ui.fileList, &QListWidget::currentTextChanged, this, &ReportView::setShownReport);
|
||||
}
|
||||
|
||||
void ReportView::generateReport() {
|
||||
m_displayOrder.clear();
|
||||
m_reports.clear();
|
||||
|
||||
QDir configDir(ConfigController::configDir());
|
||||
|
||||
QStringList swReport;
|
||||
swReport << QString("Name: %1").arg(QLatin1String(projectName));
|
||||
swReport << QString("Executable location: %1").arg(redact(QCoreApplication::applicationFilePath()));
|
||||
swReport << QString("Portable: %1").arg(yesNo[ConfigController::isPortable()]);
|
||||
swReport << QString("Configuration directory: %1").arg(redact(configDir.path()));
|
||||
swReport << QString("Version: %1").arg(QLatin1String(projectVersion));
|
||||
swReport << QString("Git branch: %1").arg(QLatin1String(gitBranch));
|
||||
swReport << QString("Git commit: %1").arg(QLatin1String(gitCommit));
|
||||
swReport << QString("Git revision: %1").arg(gitRevision);
|
||||
swReport << QString("OS: %1").arg(QSysInfo::prettyProductName());
|
||||
swReport << QString("Build architecture: %1").arg(QSysInfo::buildCpuArchitecture());
|
||||
swReport << QString("Run architecture: %1").arg(QSysInfo::currentCpuArchitecture());
|
||||
swReport << QString("Qt version: %1").arg(QLatin1String(qVersion()));
|
||||
addReport(QString("System info"), swReport.join('\n'));
|
||||
|
||||
QStringList hwReport;
|
||||
addCpuInfo(hwReport);
|
||||
addGLInfo(hwReport);
|
||||
addReport(QString("Hardware info"), hwReport.join('\n'));
|
||||
|
||||
QList<QScreen*> screens = QGuiApplication::screens();
|
||||
std::sort(screens.begin(), screens.end(), [](const QScreen* a, const QScreen* b) {
|
||||
if (a->geometry().y() < b->geometry().y()) {
|
||||
return true;
|
||||
}
|
||||
if (a->geometry().x() < b->geometry().x()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
int screenId = 0;
|
||||
for (const QScreen* screen : screens) {
|
||||
++screenId;
|
||||
QStringList screenReport;
|
||||
addScreenInfo(screenReport, screen);
|
||||
addReport(QString("Screen %1").arg(screenId), screenReport.join('\n'));
|
||||
}
|
||||
|
||||
QList<QPair<QString, QByteArray>> deferredBinaries;
|
||||
QList<ConfigController*> configs;
|
||||
int winId = 0;
|
||||
for (auto window : GBAApp::app()->windows()) {
|
||||
++winId;
|
||||
QStringList windowReport;
|
||||
auto controller = window->controller();
|
||||
ConfigController* config = window->config();
|
||||
if (configs.indexOf(config) < 0) {
|
||||
configs.append(config);
|
||||
}
|
||||
|
||||
windowReport << QString("Window size: %1x%2").arg(window->width()).arg(window->height());
|
||||
windowReport << QString("Window location: %1, %2").arg(window->x()).arg(window->y());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
QScreen* screen = window->screen();
|
||||
#else
|
||||
QScreen* screen = NULL;
|
||||
if (window->windowHandle()) {
|
||||
screen = window->windowHandle()->screen();
|
||||
}
|
||||
#endif
|
||||
if (screen && screens.contains(screen)) {
|
||||
windowReport << QString("Screen: %1").arg(screens.contains(screen) + 1);
|
||||
} else {
|
||||
windowReport << QString("Screen: Unknown");
|
||||
}
|
||||
if (controller) {
|
||||
windowReport << QString("ROM open: Yes");
|
||||
|
||||
{
|
||||
CoreController::Interrupter interrupter(controller);
|
||||
addROMInfo(windowReport, controller.get());
|
||||
|
||||
if (m_ui.includeSave->isChecked() && !m_ui.includeState->isChecked()) {
|
||||
// Only do the save separately if savestates aren't enabled, to guarantee consistency
|
||||
mCore* core = controller->thread()->core;
|
||||
void* sram = NULL;
|
||||
size_t size = core->savedataClone(core, &sram);
|
||||
if (sram) {
|
||||
QByteArray save(static_cast<const char*>(sram), size);
|
||||
free(sram);
|
||||
deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_ui.includeState->isChecked()) {
|
||||
QBuffer state;
|
||||
int flags = SAVESTATE_SCREENSHOT | SAVESTATE_CHEATS | SAVESTATE_RTC | SAVESTATE_METADATA;
|
||||
if (m_ui.includeSave->isChecked()) {
|
||||
flags |= SAVESTATE_SAVEDATA;
|
||||
}
|
||||
controller->saveState(&state, flags);
|
||||
deferredBinaries.append(qMakePair(QString("State %1").arg(winId), state.buffer()));
|
||||
if (m_ui.includeSave->isChecked()) {
|
||||
VFile* vf = VFileDevice::wrap(&state, QIODevice::ReadOnly);
|
||||
mStateExtdata extdata;
|
||||
mStateExtdataItem savedata;
|
||||
mStateExtdataInit(&extdata);
|
||||
if (mCoreExtractExtdata(controller->thread()->core, vf, &extdata) && mStateExtdataGet(&extdata, EXTDATA_SAVEDATA, &savedata)) {
|
||||
QByteArray save(static_cast<const char*>(savedata.data), savedata.size);
|
||||
deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save));
|
||||
}
|
||||
mStateExtdataDeinit(&extdata);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
windowReport << QString("ROM open: No");
|
||||
}
|
||||
windowReport << QString("Configuration: %1").arg(configs.indexOf(config) + 1);
|
||||
addReport(QString("Window %1").arg(winId), windowReport.join('\n'));
|
||||
}
|
||||
for (ConfigController* config : configs) {
|
||||
VFile* vf = VFileDevice::openMemory();
|
||||
mCoreConfigSaveVFile(config->config(), vf);
|
||||
void* contents = vf->map(vf, vf->size(vf), MAP_READ);
|
||||
if (contents) {
|
||||
QString report(QString::fromUtf8(static_cast<const char*>(contents), vf->size(vf)));
|
||||
addReport(QString("Configuration %1").arg(configs.indexOf(config) + 1), redact(report));
|
||||
vf->unmap(vf, contents, vf->size(vf));
|
||||
}
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
QFile qtIni(configDir.filePath("qt.ini"));
|
||||
if (qtIni.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
addReport(QString("Qt Configuration"), redact(QString::fromUtf8(qtIni.readAll())));
|
||||
qtIni.close();
|
||||
}
|
||||
|
||||
std::sort(deferredBinaries.begin(), deferredBinaries.end());
|
||||
for (auto& pair : deferredBinaries) {
|
||||
addBinary(pair.first, pair.second);
|
||||
}
|
||||
|
||||
rebuildModel();
|
||||
}
|
||||
|
||||
void ReportView::save() {
|
||||
QString filename = GBAApp::app()->getSaveFileName(this, tr("Bug report archive"), tr("ZIP archive (*.zip)"));
|
||||
if (filename.isNull()) {
|
||||
return;
|
||||
}
|
||||
VDir* zip = VDirOpenZip(filename.toLocal8Bit().constData(), O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (!zip) {
|
||||
return;
|
||||
}
|
||||
for (const auto& filename : m_displayOrder) {
|
||||
VFileDevice vf(zip->openFile(zip, filename.toLocal8Bit().constData(), O_WRONLY));
|
||||
if (m_reports.contains(filename)) {
|
||||
vf.setTextModeEnabled(true);
|
||||
vf.write(m_reports[filename].toUtf8());
|
||||
} else if (m_binaries.contains(filename)) {
|
||||
vf.write(m_binaries[filename]);
|
||||
}
|
||||
vf.close();
|
||||
}
|
||||
zip->close(zip);
|
||||
}
|
||||
|
||||
void ReportView::setShownReport(const QString& filename) {
|
||||
m_ui.fileView->setPlainText(m_reports[filename]);
|
||||
}
|
||||
|
||||
void ReportView::rebuildModel() {
|
||||
m_ui.fileList->clear();
|
||||
for (const auto& filename : m_displayOrder) {
|
||||
QListWidgetItem* item = new QListWidgetItem(filename);
|
||||
if (m_binaries.contains(filename)) {
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
|
||||
}
|
||||
m_ui.fileList->addItem(item);
|
||||
}
|
||||
m_ui.save->setEnabled(true);
|
||||
m_ui.fileList->setEnabled(true);
|
||||
m_ui.fileView->setEnabled(true);
|
||||
m_ui.openList->setEnabled(true);
|
||||
m_ui.fileList->setCurrentRow(0);
|
||||
m_ui.fileView->installEventFilter(this);
|
||||
}
|
||||
|
||||
void ReportView::openBugReportPage() {
|
||||
QDesktopServices::openUrl(QUrl("https://mgba.io/i/"));
|
||||
}
|
||||
|
||||
void ReportView::addCpuInfo(QStringList& report) {
|
||||
#ifdef USE_CPUID
|
||||
std::array<unsigned, 4> regs;
|
||||
if (!cpuid(0, regs.data())) {
|
||||
return;
|
||||
}
|
||||
unsigned vendor[4] = { regs[1], regs[3], regs[2], 0 };
|
||||
std::array<unsigned, 13> cpu{};
|
||||
cpuid(0x80000002, &cpu[0]);
|
||||
cpuid(0x80000003, &cpu[4]);
|
||||
cpuid(0x80000004, &cpu[8]);
|
||||
|
||||
auto testBit = [](unsigned bit, unsigned reg) {
|
||||
return yesNo[bool(reg & (1 << bit))];
|
||||
};
|
||||
QStringList features;
|
||||
report << QString("CPU: %1").arg(QLatin1String(reinterpret_cast<char*>(cpu.data())));
|
||||
report << QString("CPU manufacturer: %1").arg(QLatin1String(reinterpret_cast<char*>(vendor)));
|
||||
cpuid(1, regs.data());
|
||||
unsigned family = ((regs[0] >> 8) & 0xF) | ((regs[0] >> 16) & 0xFF0);
|
||||
unsigned model = ((regs[0] >> 4) & 0xF) | ((regs[0] >> 12) & 0xF0);
|
||||
report << QString("CPU family ID: %1h").arg(family, 2, 16, QChar('0'));
|
||||
report << QString("CPU model ID: %1h").arg(model, 2, 16, QChar('0'));
|
||||
features << QString("Supports SSE: %1").arg(testBit(25, regs[3]));
|
||||
features << QString("Supports SSE2: %1").arg(testBit(26, regs[3]));
|
||||
features << QString("Supports SSE3: %1").arg(testBit(0, regs[2]));
|
||||
features << QString("Supports SSSE3: %1").arg(testBit(9, regs[2]));
|
||||
features << QString("Supports SSE4.1: %1").arg(testBit(19, regs[2]));
|
||||
features << QString("Supports SSE4.2: %1").arg(testBit(20, regs[2]));
|
||||
features << QString("Supports MOVBE: %1").arg(testBit(22, regs[2]));
|
||||
features << QString("Supports POPCNT: %1").arg(testBit(23, regs[2]));
|
||||
features << QString("Supports RDRAND: %1").arg(testBit(30, regs[2]));
|
||||
features << QString("Supports AVX: %1").arg(testBit(28, regs[2]));
|
||||
features << QString("Supports CMPXCHG8: %1").arg(testBit(8, regs[3]));
|
||||
features << QString("Supports CMPXCHG16: %1").arg(testBit(13, regs[2]));
|
||||
cpuid(7, 0, regs.data());
|
||||
features << QString("Supports AVX2: %1").arg(testBit(5, regs[1]));
|
||||
features << QString("Supports BMI1: %1").arg(testBit(3, regs[1]));
|
||||
features << QString("Supports BMI2: %1").arg(testBit(8, regs[1]));
|
||||
cpuid(0x80000001, regs.data());
|
||||
features << QString("Supports ABM: %1").arg(testBit(5, regs[2]));
|
||||
features << QString("Supports SSE4a: %1").arg(testBit(6, regs[2]));
|
||||
features.sort();
|
||||
report << features;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ReportView::addGLInfo(QStringList& report) {
|
||||
#ifdef DISPLAY_GL_INFO
|
||||
QSurfaceFormat format;
|
||||
|
||||
report << QString("OpenGL type: %1").arg(QLatin1String(QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? "OpenGL" : "OpenGL|ES"));
|
||||
|
||||
format.setVersion(1, 4);
|
||||
report << QString("OpenGL supports legacy (1.x) contexts: %1").arg(yesNo[DisplayGL::supportsFormat(format)]);
|
||||
|
||||
if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) {
|
||||
format.setVersion(2, 0);
|
||||
} else {
|
||||
format.setVersion(3, 2);
|
||||
}
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
report << QString("OpenGL supports core contexts: %1").arg(yesNo[DisplayGL::supportsFormat(format)]);
|
||||
|
||||
QOpenGLContext context;
|
||||
if (context.create()) {
|
||||
QOffscreenSurface surface;
|
||||
surface.create();
|
||||
context.makeCurrent(&surface);
|
||||
report << QString("OpenGL renderer: %1").arg(QLatin1String(reinterpret_cast<const char*>(context.functions()->glGetString(GL_RENDERER))));
|
||||
report << QString("OpenGL vendor: %1").arg(QLatin1String(reinterpret_cast<const char*>(context.functions()->glGetString(GL_VENDOR))));
|
||||
report << QString("OpenGL version string: %1").arg(QLatin1String(reinterpret_cast<const char*>(context.functions()->glGetString(GL_VERSION))));
|
||||
}
|
||||
#else
|
||||
report << QString("OpenGL support disabled at compilation time");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ReportView::addROMInfo(QStringList& report, CoreController* controller) {
|
||||
report << QString("Currently paused: %1").arg(yesNo[controller->isPaused()]);
|
||||
|
||||
mCore* core = controller->thread()->core;
|
||||
char title[17] = {};
|
||||
core->getGameTitle(core, title);
|
||||
report << QString("Internal title: %1").arg(QLatin1String(title));
|
||||
|
||||
title[8] = '\0';
|
||||
core->getGameCode(core, title);
|
||||
if (title[0]) {
|
||||
report << QString("Game code: %1").arg(QLatin1String(title));
|
||||
} else {
|
||||
report << QString("Invalid game code");
|
||||
}
|
||||
|
||||
uint32_t crc32 = 0;
|
||||
core->checksum(core, &crc32, CHECKSUM_CRC32);
|
||||
report << QString("CRC32: %1").arg(crc32, 8, 16, QChar('0'));
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
const NoIntroDB* db = GBAApp::app()->gameDB();
|
||||
if (db && crc32) {
|
||||
NoIntroGame game{};
|
||||
if (NoIntroDBLookupGameByCRC(db, crc32, &game)) {
|
||||
report << QString("No-Intro name: %1").arg(game.name);
|
||||
} else {
|
||||
report << QString("Not present in No-Intro database").arg(game.name);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ReportView::addScreenInfo(QStringList& report, const QScreen* screen) {
|
||||
QRect geometry = screen->geometry();
|
||||
|
||||
report << QString("Size: %1x%2").arg(geometry.width()).arg(geometry.height());
|
||||
report << QString("Location: %1, %2").arg(geometry.x()).arg(geometry.y());
|
||||
report << QString("Refresh rate: %1 Hz").arg(screen->refreshRate());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0))
|
||||
report << QString("Pixel ratio: %1").arg(screen->devicePixelRatio());
|
||||
#endif
|
||||
report << QString("Logical DPI: %1x%2").arg(screen->logicalDotsPerInchX()).arg(screen->logicalDotsPerInchY());
|
||||
report << QString("Physical DPI: %1x%2").arg(screen->physicalDotsPerInchX()).arg(screen->physicalDotsPerInchY());
|
||||
}
|
||||
|
||||
void ReportView::addReport(const QString& filename, const QString& report) {
|
||||
m_reports[filename] = report;
|
||||
m_displayOrder.append(filename);
|
||||
}
|
||||
|
||||
void ReportView::addBinary(const QString& filename, const QByteArray& binary) {
|
||||
m_binaries[filename] = binary;
|
||||
m_displayOrder.append(filename);
|
||||
}
|
||||
|
||||
QString ReportView::redact(const QString& text) {
|
||||
static QRegularExpression home(R"((?:\b|^)[A-Z]:[\\/](?:Users|Documents and Settings)[\\/][^\\/]+|(?:/usr)?/home/[^/]+)",
|
||||
QRegularExpression::MultilineOption | QRegularExpression::CaseInsensitiveOption);
|
||||
QString redacted = text;
|
||||
redacted.replace(home, QString("[Home directory]"));
|
||||
return redacted;
|
||||
}
|
||||
|
||||
bool ReportView::eventFilter(QObject*, QEvent* event) {
|
||||
if (event->type() != QEvent::FocusOut) {
|
||||
QListWidgetItem* currentReport = m_ui.fileList->currentItem();
|
||||
if (currentReport && !currentReport->text().isNull()) {
|
||||
m_reports[currentReport->text()] = m_ui.fileView->toPlainText();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_CPUID
|
||||
bool ReportView::cpuid(unsigned id, unsigned* regs) {
|
||||
return cpuid(id, 0, regs);
|
||||
}
|
||||
|
||||
bool ReportView::cpuid(unsigned id, unsigned sub, unsigned* regs) {
|
||||
if (s_cpuidMax == 0xFFFFFFFF) {
|
||||
#ifdef _MSC_VER
|
||||
__cpuid(reinterpret_cast<int*>(regs), 0);
|
||||
s_cpuidMax = regs[0];
|
||||
__cpuid(reinterpret_cast<int*>(regs), 0x80000000);
|
||||
s_cpuidExtMax = regs[0];
|
||||
#else
|
||||
s_cpuidMax = __get_cpuid_max(0, nullptr);
|
||||
s_cpuidExtMax = __get_cpuid_max(0x80000000, nullptr);
|
||||
#endif
|
||||
}
|
||||
regs[0] = 0;
|
||||
regs[1] = 0;
|
||||
regs[2] = 0;
|
||||
regs[3] = 0;
|
||||
if (!(id & 0x80000000) && id > s_cpuidMax) {
|
||||
return false;
|
||||
}
|
||||
if ((id & 0x80000000) && id > s_cpuidExtMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
__cpuidex(reinterpret_cast<int*>(regs), id, sub);
|
||||
#else
|
||||
__cpuid_count(id, sub, regs[0], regs[1], regs[2], regs[3]);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,68 @@
|
|||
/* Copyright (c) 2013-2020 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QHash>
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "ConfigController.h"
|
||||
|
||||
#include "ui_ReportView.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
class CoreController;
|
||||
|
||||
class ReportView : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ReportView(QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void generateReport();
|
||||
void save();
|
||||
|
||||
private slots:
|
||||
void setShownReport(const QString&);
|
||||
void rebuildModel();
|
||||
void openBugReportPage();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
||||
private:
|
||||
void addCpuInfo(QStringList&);
|
||||
void addGLInfo(QStringList&);
|
||||
void addROMInfo(QStringList&, CoreController*);
|
||||
void addScreenInfo(QStringList&, const QScreen*);
|
||||
|
||||
void addReport(const QString& filename, const QString& report);
|
||||
void addBinary(const QString& filename, const QByteArray& report);
|
||||
QString redact(const QString& text);
|
||||
|
||||
#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || (defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)))
|
||||
static bool cpuid(unsigned id, unsigned* regs);
|
||||
static bool cpuid(unsigned id, unsigned sub, unsigned* regs);
|
||||
|
||||
static unsigned s_cpuidMax;
|
||||
static unsigned s_cpuidExtMax;
|
||||
#endif
|
||||
|
||||
ConfigController* m_config;
|
||||
|
||||
QStringList m_displayOrder;
|
||||
QHash<QString, QString> m_reports;
|
||||
QHash<QString, QByteArray> m_binaries;
|
||||
|
||||
Ui::ReportView m_ui;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ReportView</class>
|
||||
<widget class="QDialog" name="ReportView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>855</width>
|
||||
<height>474</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Generate Bug Report</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="3,2,7,0">
|
||||
<item row="1" column="1" rowspan="3">
|
||||
<widget class="QListWidget" name="fileList">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="horizontalScrollMode">
|
||||
<enum>QAbstractItemView::ScrollPerPixel</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2" rowspan="3">
|
||||
<widget class="QPlainTextEdit" name="fileView">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Monospace</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextEditorInteraction</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="description">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string><html><head/><body><p>To file a bug report, please first generate a report file to attach to the bug report you're about to file. It is recommended that you include the save files, as these often help with debugging issues. This will collect some information about the version of {projectName} you're running, your configuration, your computer, and the game you currently have open (if any). Once this collection is completed you can review all of the information gathered below and save it to a zip file. The collection will automatically attempt to redact any personal information, such as your username if it's in any of the paths gathered, but just in case you can edit it afterwards. After you have generated and saved it, please click the button below or go to <a href="https://mgba.io/i/"><span style=" text-decoration: underline; color:#2980b9;">mgba.io/i</span></a> to file the bug report on GitHub. Make sure to attach the report you generated!</p></body></html></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1" rowspan="2" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="generate">
|
||||
<property name="text">
|
||||
<string>Generate report</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="view-refresh">
|
||||
<normaloff>../../../../../../</normaloff>../../../../../../</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-save">
|
||||
<normaloff>../../../../../../</normaloff>../../../../../../</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openList">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open issue list in browser</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="document-send">
|
||||
<normaloff>../../../../../../</normaloff>../../../../../../</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0" rowspan="4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeSave">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Include save file</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="includeState">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create and include savestate</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>openList</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ReportView</receiver>
|
||||
<slot>openBugReportPage()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>593</x>
|
||||
<y>442</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>357</x>
|
||||
<y>234</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>generate</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ReportView</receiver>
|
||||
<slot>generateReport()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>121</x>
|
||||
<y>432</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>357</x>
|
||||
<y>229</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>save</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>ReportView</receiver>
|
||||
<slot>save()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>357</x>
|
||||
<y>432</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>357</x>
|
||||
<y>229</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>generateReport()</slot>
|
||||
<slot>save()</slot>
|
||||
<slot>openBugReportPage()</slot>
|
||||
</slots>
|
||||
</ui>
|
|
@ -606,7 +606,7 @@ void SettingsView::reloadConfig() {
|
|||
loadSetting("logFile", m_ui.logFile);
|
||||
loadSetting("useDiscordPresence", m_ui.useDiscordPresence);
|
||||
loadSetting("gba.audioHle", m_ui.audioHle);
|
||||
loadSetting("dynamicTitle", m_ui.dynamicTitle);
|
||||
loadSetting("dynamicTitle", m_ui.dynamicTitle, true);
|
||||
loadSetting("gba.forceGbp", m_ui.forceGbp);
|
||||
|
||||
m_ui.libraryStyle->setCurrentIndex(loadSetting("libraryStyle").toInt());
|
||||
|
|
|
@ -706,7 +706,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.010000000000000</double>
|
||||
|
@ -749,7 +749,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.010000000000000</double>
|
||||
|
@ -988,7 +988,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="oglEnahance">
|
||||
<widget class="QGroupBox" name="oglEnhance">
|
||||
<property name="title">
|
||||
<string>OpenGL enhancements</string>
|
||||
</property>
|
||||
|
@ -1005,7 +1005,7 @@
|
|||
<item>
|
||||
<widget class="QSpinBox" name="videoScale">
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "VFileDevice.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDir>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
|
@ -60,14 +61,11 @@ void ShaderSelector::clear() {
|
|||
}
|
||||
|
||||
void ShaderSelector::selectShader() {
|
||||
QString path(GBAApp::dataDir());
|
||||
path += QLatin1String("/shaders");
|
||||
QFileDialog dialog(nullptr, tr("Load shader"), path);
|
||||
dialog.setFileMode(QFileDialog::Directory);
|
||||
dialog.exec();
|
||||
QStringList names = dialog.selectedFiles();
|
||||
if (names.count() == 1) {
|
||||
loadShader(names[0]);
|
||||
QDir path(GBAApp::dataDir());
|
||||
path.cd(QLatin1String("shaders"));
|
||||
QString name = GBAApp::app()->getOpenDirectoryName(this, tr("Load shader"), path.absolutePath());
|
||||
if (!name.isNull()) {
|
||||
loadShader(name);
|
||||
refreshShaders();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ void ShortcutView::clear() {
|
|||
QModelIndex index = m_ui.shortcutTable->selectionModel()->currentIndex();
|
||||
QString name = m_model->name(index);
|
||||
const Shortcut* item = m_controller->shortcut(name);
|
||||
if (!item->action()) {
|
||||
if (!item || !item->action()) {
|
||||
return;
|
||||
}
|
||||
if (m_ui.gamepadButton->isChecked()) {
|
||||
|
@ -106,7 +106,7 @@ void ShortcutView::updateButton(int button) {
|
|||
}
|
||||
QString name = m_model->name(m_ui.shortcutTable->selectionModel()->currentIndex());
|
||||
const Shortcut* item = m_controller->shortcut(name);
|
||||
if (!item->action()) {
|
||||
if (!item || !item->action()) {
|
||||
return;
|
||||
}
|
||||
if (m_ui.gamepadButton->isChecked()) {
|
||||
|
@ -122,7 +122,7 @@ void ShortcutView::updateAxis(int axis, int direction) {
|
|||
}
|
||||
QString name = m_model->name(m_ui.shortcutTable->selectionModel()->currentIndex());
|
||||
const Shortcut* item = m_controller->shortcut(name);
|
||||
if (!item->action()) {
|
||||
if (!item || !item->action()) {
|
||||
return;
|
||||
}
|
||||
m_controller->updateAxis(name, axis, static_cast<GamepadAxisEvent::Direction>(direction));
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include <QAction>
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
|
@ -203,6 +203,12 @@
|
|||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetTile.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::TilePainter</class>
|
||||
<extends>QWidget</extends>
|
||||
|
@ -212,12 +218,6 @@
|
|||
<slot>setTileMagnification(int)</slot>
|
||||
</slots>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QGBA::AssetTile</class>
|
||||
<extends>QGroupBox</extends>
|
||||
<header>AssetTile.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
|
|
@ -5,10 +5,65 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class VFileAbstractWrapper : public VFile {
|
||||
public:
|
||||
VFileAbstractWrapper(QIODevice*);
|
||||
|
||||
protected:
|
||||
QIODevice* m_iodev;
|
||||
|
||||
private:
|
||||
static bool close(struct VFile* vf);
|
||||
static off_t seek(struct VFile* vf, off_t offset, int whence);
|
||||
static ssize_t read(struct VFile* vf, void* buffer, size_t size);
|
||||
static ssize_t readline(struct VFile* vf, char* buffer, size_t size);
|
||||
static ssize_t write(struct VFile* vf, const void* buffer, size_t size);
|
||||
static void* map(struct VFile* vf, size_t size, int flags);
|
||||
static void unmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void truncate(struct VFile* vf, size_t size);
|
||||
static ssize_t size(struct VFile* vf);
|
||||
static bool sync(struct VFile* vf, void* buffer, size_t size);
|
||||
|
||||
};
|
||||
|
||||
class VFileWrapper : public VFileAbstractWrapper {
|
||||
public:
|
||||
VFileWrapper(QFileDevice*);
|
||||
|
||||
protected:
|
||||
QFileDevice* iodev() { return static_cast<QFileDevice*>(m_iodev); }
|
||||
|
||||
private:
|
||||
static bool close(struct VFile* vf);
|
||||
static void* map(struct VFile* vf, size_t size, int flags);
|
||||
static void unmap(struct VFile* vf, void* memory, size_t size);
|
||||
static void truncate(struct VFile* vf, size_t size);
|
||||
static bool sync(struct VFile* vf, void* buffer, size_t size);
|
||||
};
|
||||
|
||||
class VFileBufferWrapper : public VFileAbstractWrapper {
|
||||
public:
|
||||
VFileBufferWrapper(QBuffer*);
|
||||
|
||||
protected:
|
||||
QBuffer* iodev() { return static_cast<QBuffer*>(m_iodev); }
|
||||
|
||||
private:
|
||||
static bool close(struct VFile* vf);
|
||||
static void* map(struct VFile* vf, size_t size, int flags);
|
||||
static void unmap(struct VFile* vf, void* memory, size_t size);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
VFileDevice::VFileDevice(VFile* vf, QObject* parent)
|
||||
: QIODevice(parent)
|
||||
, m_vf(vf)
|
||||
|
@ -74,13 +129,178 @@ qint64 VFileDevice::size() const {
|
|||
return m_vf->size(m_vf);
|
||||
}
|
||||
|
||||
VFile* VFileDevice::wrap(QIODevice* iodev, QIODevice::OpenMode mode) {
|
||||
if (!iodev->open(mode)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new VFileAbstractWrapper(iodev);
|
||||
}
|
||||
|
||||
VFile* VFileDevice::wrap(QFileDevice* iodev, QIODevice::OpenMode mode) {
|
||||
if (!iodev->open(mode)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new VFileWrapper(iodev);
|
||||
}
|
||||
|
||||
VFile* VFileDevice::wrap(QBuffer* iodev, QIODevice::OpenMode mode) {
|
||||
if (!iodev->open(mode)) {
|
||||
return nullptr;
|
||||
}
|
||||
return new VFileBufferWrapper(iodev);
|
||||
}
|
||||
|
||||
VFile* VFileDevice::open(const QString& path, int mode) {
|
||||
return VFileOpen(path.toUtf8().constData(), mode);
|
||||
}
|
||||
|
||||
VFile* VFileDevice::openMemory() {
|
||||
return VFileMemChunk(nullptr, 0);
|
||||
}
|
||||
|
||||
VDir* VFileDevice::openDir(const QString& path) {
|
||||
return VDirOpen(path.toUtf8().constData());
|
||||
}
|
||||
|
||||
VDir* VFileDevice::openArchive(const QString& path) {
|
||||
return VDirOpenArchive(path.toUtf8().constData());
|
||||
}
|
||||
|
||||
VFileAbstractWrapper::VFileAbstractWrapper(QIODevice* iodev)
|
||||
: m_iodev(iodev)
|
||||
{
|
||||
VFile::close = &VFileAbstractWrapper::close;
|
||||
VFile::seek = &VFileAbstractWrapper::seek;
|
||||
VFile::read = &VFileAbstractWrapper::read;
|
||||
VFile::readline = &VFileAbstractWrapper::readline;
|
||||
VFile::write = &VFileAbstractWrapper::write;
|
||||
VFile::map = &VFileAbstractWrapper::map;
|
||||
VFile::unmap = &VFileAbstractWrapper::unmap;
|
||||
VFile::truncate = &VFileAbstractWrapper::truncate;
|
||||
VFile::size = &VFileAbstractWrapper::size;
|
||||
VFile::sync = &VFileAbstractWrapper::sync;
|
||||
}
|
||||
|
||||
bool VFileAbstractWrapper::close(VFile* vf) {
|
||||
QIODevice* iodev = static_cast<VFileAbstractWrapper*>(vf)->m_iodev;
|
||||
iodev->close();
|
||||
delete static_cast<VFileAbstractWrapper*>(vf);
|
||||
return true;
|
||||
}
|
||||
|
||||
off_t VFileAbstractWrapper::seek(VFile* vf, off_t offset, int whence) {
|
||||
QIODevice* iodev = static_cast<VFileAbstractWrapper*>(vf)->m_iodev;
|
||||
switch (whence) {
|
||||
case SEEK_SET:
|
||||
if (!iodev->seek(offset)) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
if (!iodev->seek(iodev->pos() + offset)) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
case SEEK_END:
|
||||
if (!iodev->seek(iodev->size() + offset)) {
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return iodev->pos();
|
||||
}
|
||||
|
||||
ssize_t VFileAbstractWrapper::read(VFile* vf, void* buffer, size_t size) {
|
||||
QIODevice* iodev = static_cast<VFileAbstractWrapper*>(vf)->m_iodev;
|
||||
return iodev->read(static_cast<char*>(buffer), size);
|
||||
}
|
||||
|
||||
ssize_t VFileAbstractWrapper::readline(VFile* vf, char* buffer, size_t size) {
|
||||
QIODevice* iodev = static_cast<VFileAbstractWrapper*>(vf)->m_iodev;
|
||||
return iodev->readLine(static_cast<char*>(buffer), size);
|
||||
}
|
||||
|
||||
ssize_t VFileAbstractWrapper::write(VFile* vf, const void* buffer, size_t size) {
|
||||
QIODevice* iodev = static_cast<VFileAbstractWrapper*>(vf)->m_iodev;
|
||||
return iodev->write(static_cast<const char*>(buffer), size);
|
||||
}
|
||||
|
||||
void* VFileAbstractWrapper::map(VFile*, size_t, int) {
|
||||
// Doesn't work on QIODevice base class
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VFileAbstractWrapper::unmap(VFile*, void*, size_t) {
|
||||
// Doesn't work on QIODevice base class
|
||||
}
|
||||
|
||||
void VFileAbstractWrapper::truncate(VFile*, size_t) {
|
||||
// Doesn't work on QIODevice base class
|
||||
}
|
||||
|
||||
ssize_t VFileAbstractWrapper::size(VFile* vf) {
|
||||
QIODevice* iodev = static_cast<VFileAbstractWrapper*>(vf)->m_iodev;
|
||||
return iodev->size();
|
||||
}
|
||||
|
||||
bool VFileAbstractWrapper::sync(VFile*, void*, size_t) {
|
||||
// Doesn't work on QIODevice base class
|
||||
return false;
|
||||
}
|
||||
|
||||
VFileWrapper::VFileWrapper(QFileDevice* iodev)
|
||||
: VFileAbstractWrapper(iodev)
|
||||
{
|
||||
VFile::close = &VFileWrapper::close;
|
||||
VFile::map = &VFileWrapper::map;
|
||||
VFile::unmap = &VFileWrapper::unmap;
|
||||
VFile::truncate = &VFileWrapper::truncate;
|
||||
VFile::sync = &VFileWrapper::sync;
|
||||
}
|
||||
|
||||
bool VFileWrapper::close(VFile* vf) {
|
||||
QIODevice* iodev = static_cast<VFileWrapper*>(vf)->m_iodev;
|
||||
iodev->close();
|
||||
delete static_cast<VFileWrapper*>(vf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void* VFileWrapper::map(VFile* vf, size_t size, int mode) {
|
||||
QFileDevice* iodev = static_cast<VFileWrapper*>(vf)->iodev();
|
||||
return iodev->map(0, size, mode == MAP_READ ? QFileDevice::MapPrivateOption : QFileDevice::NoOptions);
|
||||
}
|
||||
|
||||
void VFileWrapper::unmap(VFile* vf, void* buffer, size_t) {
|
||||
QFileDevice* iodev = static_cast<VFileWrapper*>(vf)->iodev();
|
||||
iodev->unmap(static_cast<uchar*>(buffer));
|
||||
}
|
||||
|
||||
void VFileWrapper::truncate(VFile* vf, size_t size) {
|
||||
QFileDevice* iodev = static_cast<VFileWrapper*>(vf)->iodev();
|
||||
iodev->resize(size);
|
||||
}
|
||||
|
||||
bool VFileWrapper::sync(VFile* vf, void*, size_t) {
|
||||
QFileDevice* iodev = static_cast<VFileWrapper*>(vf)->iodev();
|
||||
return iodev->flush();
|
||||
}
|
||||
|
||||
VFileBufferWrapper::VFileBufferWrapper(QBuffer* iodev)
|
||||
: VFileAbstractWrapper(iodev)
|
||||
{
|
||||
VFile::close = &VFileBufferWrapper::close;
|
||||
VFile::map = &VFileBufferWrapper::map;
|
||||
}
|
||||
|
||||
bool VFileBufferWrapper::close(VFile* vf) {
|
||||
QIODevice* iodev = static_cast<VFileBufferWrapper*>(vf)->m_iodev;
|
||||
iodev->close();
|
||||
delete static_cast<VFileBufferWrapper*>(vf);
|
||||
return true;
|
||||
}
|
||||
|
||||
void* VFileBufferWrapper::map(VFile* vf, size_t, int) {
|
||||
QBuffer* iodev = static_cast<VFileBufferWrapper*>(vf)->iodev();
|
||||
QByteArray& buffer = iodev->buffer();
|
||||
return static_cast<void*>(buffer.data());
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
struct VDir;
|
||||
struct VFile;
|
||||
|
||||
class QBuffer;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class VFileDevice : public QIODevice {
|
||||
|
@ -28,7 +30,12 @@ public:
|
|||
VFileDevice& operator=(VFile*);
|
||||
operator VFile*() { return m_vf; }
|
||||
|
||||
static VFile* wrap(QIODevice*, QIODevice::OpenMode);
|
||||
static VFile* wrap(QFileDevice*, QIODevice::OpenMode);
|
||||
static VFile* wrap(QBuffer*, QIODevice::OpenMode);
|
||||
|
||||
static VFile* open(const QString& path, int mode);
|
||||
static VFile* openMemory();
|
||||
static VDir* openDir(const QString& path);
|
||||
static VDir* openArchive(const QString& path);
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ VideoProxy::VideoProxy() {
|
|||
m_logger.d.postEvent = &callback<void, enum mVideoLoggerEvent>::func<&VideoProxy::postEvent>;
|
||||
|
||||
connect(this, &VideoProxy::dataAvailable, this, &VideoProxy::processData);
|
||||
connect(this, &VideoProxy::eventPosted, this, &VideoProxy::handleEvent);
|
||||
}
|
||||
|
||||
void VideoProxy::attach(CoreController* controller) {
|
||||
|
@ -98,19 +97,15 @@ bool VideoProxy::readData(void* data, size_t length, bool block) {
|
|||
void VideoProxy::postEvent(enum mVideoLoggerEvent event) {
|
||||
if (QThread::currentThread() == thread()) {
|
||||
// We're on the main thread
|
||||
emit eventPosted(event);
|
||||
handleEvent(event);
|
||||
} else {
|
||||
m_mutex.lock();
|
||||
emit eventPosted(event);
|
||||
m_fromThreadCond.wait(&m_mutex, 1);
|
||||
m_mutex.unlock();
|
||||
QMetaObject::invokeMethod(this, "handleEvent", Qt::BlockingQueuedConnection, Q_ARG(int, event));
|
||||
}
|
||||
}
|
||||
|
||||
void VideoProxy::handleEvent(int event) {
|
||||
m_mutex.lock();
|
||||
m_logger.d.handleEvent(&m_logger.d, static_cast<enum mVideoLoggerEvent>(event));
|
||||
m_fromThreadCond.wakeAll();
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@ public:
|
|||
|
||||
signals:
|
||||
void dataAvailable();
|
||||
void eventPosted(int);
|
||||
|
||||
public slots:
|
||||
void processData();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>351</width>
|
||||
<height>510</height>
|
||||
<height>584</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -431,7 +431,7 @@
|
|||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>:</string>
|
||||
<string notr="true">:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
|
@ -441,7 +441,7 @@
|
|||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>×</string>
|
||||
<string notr="true">×</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue