Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2021-06-11 19:22:10 -07:00
commit a012157896
131 changed files with 26727 additions and 14885 deletions

View File

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

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

View File

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

View File

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

View File

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

254
README_ES.md Normal file
View File

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

254
README_ZH_CN.md Normal file
View File

@ -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。
#### 工具链构建
如果您拥有 devkitARM3DS、devkitPPCWii、devkitA64Switch或 vitasdkPS 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 5GUI 前端的所需依赖项。音频需要 Qt Multimedia 或 SDL。
- SDL更基本的前端以及在 Qt 前端中支持游戏手柄的所需依赖项。推荐使用 SDL 2、但也支持 1.2。
- zlib 和 libpng截图与 PNG 即时存档支持的所需依赖项
- libedit命令行调试器的所需依赖项
- ffmpeg 或 libav录制视频、GIF、WebP 和 APNG 的所需依赖项
- libzip 或 zlib载入储存在 ZIP 文件中的 ROM 的所需依赖项。
- SQLite3游戏数据库的所需依赖项
- libelfELF 载入的所需依赖项
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) 获取更多信息。

View File

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

View File

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

View File

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

View File

@ -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 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];
};
DECLARE_VECTOR(GBACheatPatchList, struct GBACheatPatch);
struct GBACheatSet {
struct mCheatSet d;
struct GBACheatHook* hook;
struct GBACheatPatchList romPatches;
size_t incompleteCheat;
struct GBACheatPatch* incompletePatch;

View File

@ -116,6 +116,7 @@ struct GBA {
int32_t cachedRegisters[16];
bool taintedRegisters[16];
bool vbaBugCompat;
bool hardCrash;
bool allowOpposingDirections;

View File

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

View File

@ -20,6 +20,7 @@ struct GBACartridgeOverride {
int hardware;
uint32_t idleLoop;
bool mirroring;
bool vbaBugCompat;
};
struct Configuration;

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -151,13 +151,15 @@ static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uin
return false;
}
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);
}

View File

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

View File

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

View File

@ -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];
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;
MultiByteToWideChar(CP_UTF8, 0, projectName, -1, wprojectName, MAX_PATH);
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) {

View File

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

View File

@ -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) {
if (debugger->traceVf) {
debugger->traceVf->close(debugger->traceVf);
debugger->traceVf = NULL;
}
return false;
}
return true;

View File

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

View File

@ -152,7 +152,6 @@ static void _GBCoreDeinit(struct mCore* core) {
if (gbcore->cheatDevice) {
mCheatDeviceDestroy(gbcore->cheatDevice);
}
free(gbcore->cheatDevice);
mCoreConfigFreeOpts(&core->opts);
free(core);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -95,8 +95,12 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uin
}
switch (address) {
case GPIO_REG_DATA:
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);

View File

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

View File

@ -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;
if (glRenderer->paletteDirty) {
for (i = 0; i < 512; ++i) {
glRenderer->shadowPalette[i] = glRenderer->d.palette[i];
int oldPalette = glRenderer->nextPalette;
glRenderer->nextPalette = y + 1;
if (glRenderer->nextPalette >= GBA_VIDEO_VERTICAL_PIXELS) {
glRenderer->nextPalette = 0;
}
if (glRenderer->paletteDirty) {
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);
}
}
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 {

View File

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

View File

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

View File

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

View File

@ -7,18 +7,17 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
set(${FEATURE_NAME} OFF PARENT_SCOPE)
return()
endif()
foreach(REQUIRE ${FEATURE_REQUIRES})
foreach(NAMES ${FEATURE_REQUIRES})
string(REPLACE "|" ";" NAMELIST "${NAMES}")
set(FOUND OFF)
foreach(REQUIRE ${NAMELIST})
if(NOT ${REQUIRE}_FOUND)
find_package(${REQUIRE} QUIET)
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()
endif()
endif()
endif()
if(${REQUIRE}_FOUND)
string(TOUPPER ${REQUIRE} UREQUIRE)
set(${UREQUIRE}_CFLAGS_OTHER ${${REQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
set(${UREQUIRE}_FOUND ${${REQUIRE}_FOUND} PARENT_SCOPE)
@ -43,5 +42,15 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
endif()
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()
endforeach()
endfunction()

View File

@ -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,25 +412,27 @@ void retro_run(void) {
keys |= (!!inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L)) << 9;
core->setKeys(core, keys);
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)) {
++luxLevel;
if (luxLevel > 10) {
luxLevel = 10;
++luxLevelIndex;
if (luxLevelIndex > 10) {
luxLevelIndex = 10;
}
wasAdjustingLux = true;
} else if (inputCallback(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_L3)) {
--luxLevel;
if (luxLevel < 0) {
luxLevel = 0;
--luxLevelIndex;
if (luxLevelIndex < 0) {
luxLevelIndex = 0;
}
wasAdjustingLux = true;
}
}
}
core->runFrame(core);
unsigned width, height;
@ -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 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) {
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) {
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;
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 {
luxLevel = newLuxLevel;
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;
}

View File

@ -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) */
@ -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. */

View File

@ -54,6 +54,7 @@ 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.",
{
{ "sensor", "Use device sensor if available" },
{ "0", NULL },
{ "1", NULL },
{ "2", NULL },

View File

@ -208,6 +208,7 @@ 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.",
{
{ "sensor", "Sensörü" },
{ "0", NULL },
{ "1", NULL },
{ "2", NULL },

View File

@ -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 = []

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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();
}
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));
}
}
void DisplayGL::lockIntegerScaling(bool lock) {
Display::lockIntegerScaling(lock);
if (m_drawThread) {
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));
}
}
void DisplayGL::showOSDMessages(bool enable) {
Display::showOSDMessages(enable);
if (m_drawThread) {
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));
}
}
void DisplayGL::framePosted() {
if (m_drawThread) {
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);
}
}
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");
}
}
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) {
} 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);

View File

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

View File

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

View File

@ -25,7 +25,7 @@
</sizepolicy>
</property>
<property name="suffix">
<string>×</string>
<string notr="true">×</string>
</property>
<property name="minimum">
<number>1</number>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -25,7 +25,6 @@
#include <QAction>
#include <QButtonGroup>
#include <QClipboard>
#include <QFontDatabase>
#include <QMouseEvent>
#include <QRadioButton>
#include <QTimer>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -174,21 +174,6 @@
</property>
</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">
@ -197,19 +182,11 @@
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
<widget class="QCheckBox" name="vbaBugCompat">
<property name="text">
<string>VBA bug compatibility mode</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>

View File

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

View File

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

View File

@ -164,7 +164,7 @@
</sizepolicy>
</property>
<property name="suffix">
<string>×</string>
<string notr="true">×</string>
</property>
<property name="minimum">
<number>1</number>

View File

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

View File

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

View File

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

View File

@ -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>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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 &lt;a href=&quot;https://mgba.io/i/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#2980b9;&quot;&gt;mgba.io/i&lt;/span&gt;&lt;/a&gt; to file the bug report on GitHub. Make sure to attach the report you generated!&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</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>

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,6 @@
#include <QAction>
#include <QClipboard>
#include <QFontDatabase>
#include <QTimer>
#ifdef M_CORE_GB

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,6 @@ public:
signals:
void dataAvailable();
void eventPosted(int);
public slots:
void processData();

View File

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