Compare commits

...
37 Commits
Author SHA1 Message Date
Rosalie Wanders 0d1fc16a53 Package: v0.8.0 2025-09-10 14:34:13 +02:00
Rosalie Wanders 9032008a63 RMG: add more safety in netplay dialogs with the dispatcher 2025-09-10 14:22:34 +02:00
Rosalie Wanders 4788d643da 3rdParty: fix 64DD games crashing in mupen64plus-core 2025-09-10 14:22:16 +02:00
Rosalie Wanders e1db6c24b0 RMG: don't use NetplayCommon::ConnectToIPv4Server() in UI event handler
This causes a slight UI lock-up which is distracting, and with the
dispatcher server, we don't need it for the JSON server list anymore
2025-09-10 10:53:03 +02:00
Rosalie Wanders 81f98505ca RMG: implement dispatcher server for netplay 2025-09-10 10:53:03 +02:00
Rosalie Wanders 7270e0dfbe RMG: add "Dispatcher URL" setting to SettingsDialog 2025-09-10 10:37:19 +02:00
Rosalie Wanders bc3e3b3f9c RMG-Core: add Netplay_DispatcherUrl setting 2025-09-10 10:37:19 +02:00
Rosalie Wanders d7b17cd494 RMG-Core: add netplay dispatcher settings migration 2025-09-10 10:37:19 +02:00
RibandRosalie e5cd034b4f RMG: allow switching between WindowsVista and Windows11 styles
Qt defaults to the Windows11 style on Windows 11, which isn't truly native and looks inconsistent from other desktop apps. This sets the native style to WindowsVista which is the true native style, and adds a Windows11 option on Windows OSs.
2025-08-16 11:23:28 +02:00
orbeaandGitHub 1c6210f964 3rdParty: change minimum cmake version in discord-rpc's rapidjson (#382)
Gentoo-Issue: https://bugs.gentoo.org/959468
2025-07-04 20:47:58 +02:00
GhostlyDarkandGitHub 9ce1e45d64 README: Add missing libusb dependency (#379)
The mupen64plus-input-gca plugin can't run without libusb-1.0.dll being present, but is able to build fine without libusb being installed. If not installed, the dependency script silently fails to find/copy the DLL.
2025-06-23 17:58:21 +02:00
Rosalie Wanders be738f0cb7 ci: use windows-2022 2025-06-09 16:01:11 +02:00
Rosalie Wanders e882e0f22c RMG: add NETPLAY guard to QWebSocket in NetplayCommon.hpp 2025-06-09 15:59:54 +02:00
Rosalie Wanders f7a1d0f952 RMG: add IPv4 resolver to NetplayCommon and use it 2025-06-09 15:35:46 +02:00
Logan McNaughtonandGitHub 6b8990fa1a RMG: ensure that netplay only connects using IPv4 (#374) 2025-06-09 15:18:35 +02:00
Rosalie Wanders a74cff50f8 Package: add vulkan-headers to makedepends of PKGBUILD 2025-05-20 09:35:34 +02:00
Rosalie Wanders d4ac45a9c5 Package: v0.7.9 2025-05-19 23:59:01 +02:00
Rosalie Wanders 32aebfec1a RMG-Core: change default of RomBrowser_MaxItems to 2048 2025-05-19 23:51:06 +02:00
Rosalie Wanders aee7da8068 RMG: hide RomBrowserSearchWidget by default 2025-05-19 23:47:36 +02:00
Rosalie Wanders 078361e2e6 RMG: use const reference for slots 2025-05-19 23:41:28 +02:00
Rosalie Wanders e0cae9c1a9 RMG: disable action_View_Game{List,Grid} during emulation 2025-05-19 23:34:01 +02:00
Rosalie Wanders c8d46ee919 RMG: showSearchLineEdit -> showSearchWidget 2025-05-19 23:23:47 +02:00
Rosalie Wanders c5965b3940 RMG: introduce RomBrowserSearchWidget 2025-05-19 23:06:39 +02:00
Rosalie Wanders affb0ee0ee RMG: change placeholder text of searchLineEdit in RomBrowserWidget.cpp 2025-05-19 23:06:39 +02:00
Rosalie Wanders dd6ea4f22a RMG-Core: change casing of default KeyBinding_ViewSearch setting value 2025-05-19 23:06:39 +02:00
Rosalie Wanders 006a7c8eff RMG: re-add nullptr check in RomBrowserWidget::getCurrentData() 2025-05-18 19:32:59 +02:00
Rosalie Wanders 5f10f39ac0 3rdParty: update mupen64plus-rsp-hle 2025-05-18 19:19:12 +02:00
Rosalie Wanders 6e1df7fc79 3rdParty: update mupen64plus-video-GLideN64 2025-05-18 19:18:44 +02:00
Rosalie Wanders 5cab9282e7 3rdParty: allow cropping each side separately in mupen64plus-video-parallel 2025-05-18 19:17:12 +02:00
Rosalie Wanders 1c6d8a6f59 RMG: fix ROM browser index mapping 2025-05-18 19:02:29 +02:00
Rosalie Wanders c43f43e634 RMG-Core: Keybinding_ViewLog -> KeyBinding_ViewLog 2025-05-06 01:28:09 +02:00
Rosalie Wanders 409aac2caa RMG-Core: remove unrequired include 2025-05-06 01:28:09 +02:00
Rosalie Wanders 21b187dacc RMG: implement search for the ROM browser 2025-05-06 01:28:09 +02:00
Rosalie Wanders a110f48fad RMG-Core: add KeyBinding_ViewSearch setting 2025-05-06 00:48:47 +02:00
Rosalie Wanders 1f24d20054 RMG-Core: fix name having incorrect count in CoreGetCurrentRomHeader() 2025-05-03 16:38:56 +02:00
Rosalie Wanders 8e61b79ecf 3rdParty: update mupen64plus-core 2025-05-02 15:08:02 +02:00
Rosalie Wanders f459c456de RMG-Core: use const reference for local functions in Settings.cpp 2025-05-02 14:36:19 +02:00
77 changed files with 1271 additions and 328 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
path: Bin/*.AppImage
build-windows:
runs-on: windows-2019
runs-on: windows-2022
strategy:
matrix:
features: [ ON, OFF ]
+3 -3
View File
@@ -1,14 +1,14 @@
# Maintainer: Rosalie Wanders <rosalie@mailbox.org>
pkgname=rmg
pkgver=0.7.8
pkgrel=1
pkgver=0.8.0
pkgrel=2
pkgdesc="Rosalie's Mupen GUI"
arch=('x86_64' 'aarch64')
url="https://github.com/Rosalie241/$pkgname"
license=('GPL3')
depends=("hidapi" "libsamplerate" "speexdsp" "minizip" "sdl2" "sdl2_net" "zlib" "freetype2" "qt6-base" "qt6-svg" "qt6-websockets")
makedepends=("git" "nasm" "cmake")
makedepends=("git" "nasm" "cmake" "vulkan-headers")
source=("git+https://github.com/Rosalie241/$pkgname.git#tag=v$pkgver")
sha256sums=('SKIP')
+1 -1
View File
@@ -1,5 +1,5 @@
Name: RMG
Version: 0.7.8
Version: 0.8.0
Release: %autorelease
Summary: Rosalie's Mupen GUI
@@ -29,6 +29,30 @@
</screenshot>
</screenshots>
<releases>
<release version="v0.8.0" date="2025-09-10" type="stable">
<description>
<p>Changes:</p>
<ul>
<li>Fix 64DD games crashing in some cases since v0.7.8</li>
<li>Add Windows Vista theme to settings on Windows (thank you RibShark)</li>
<li>Add support for the netplay dispatch server</li>
</ul>
</description>
</release>
<release version="v0.7.9" date="2025-05-19" type="stable">
<description>
<p>Changes:</p>
<ul>
<li>Fix netplay allocating UDP packets during emulation</li>
<li>Fix 'Game List' and 'Game Grid' UI elements not being disabled during emulation</li>
<li>Fix compilation error with newer minizip-ng versions</li>
<li>Add search feature to the ROM browser</li>
<li>Add crop settings for each side in mupen64plus-video-parallel</li>
<li>Change default ROM search limit to 2048</li>
<li>Update mupen64plus-video-GLideN64 and mupen64plus-rsp-hle</li>
</ul>
</description>
</release>
<release version="v0.7.8" date="2025-03-23" type="stable">
<description>
<p>Changes:</p>
+2 -2
View File
@@ -73,9 +73,9 @@ cmake --install "$build_dir" --prefix="/usr"
```
#### Windows
* Download & Install [MSYS2](https://www.msys2.org/)
* Download & Install [MSYS2](https://www.msys2.org/) (UCRT64)
```bash
pacman -S --needed make mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-hidapi mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-libpng mingw-w64-ucrt-x86_64-qt6 mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_net mingw-w64-ucrt-x86_64-speexdsp mingw-w64-ucrt-x86_64-libsamplerate mingw-w64-ucrt-x86_64-nasm mingw-w64-ucrt-x86_64-minizip mingw-w64-ucrt-x86_64-vulkan-headers git
pacman -S --needed make mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-hidapi mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-libpng mingw-w64-ucrt-x86_64-qt6 mingw-w64-ucrt-x86_64-SDL2 mingw-w64-ucrt-x86_64-SDL2_net mingw-w64-ucrt-x86_64-speexdsp mingw-w64-ucrt-x86_64-libsamplerate mingw-w64-ucrt-x86_64-libusb mingw-w64-ucrt-x86_64-nasm mingw-w64-ucrt-x86_64-minizip mingw-w64-ucrt-x86_64-vulkan-headers git
./Source/Script/Build.sh Release
```
@@ -1,4 +1,4 @@
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
cmake_minimum_required(VERSION 3.10)
if(POLICY CMP0025)
# detect Apple's Clang
cmake_policy(SET CMP0025 NEW)
+25 -19
View File
@@ -26,26 +26,20 @@ jobs:
arch: x64
- cc: Clang
arch: x86
name: Linux / ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: ubuntu-22.04
name: Linux / ${{ matrix.cc }} ${{ matrix.arch }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get build dependencies and arrange the environment
run: |
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
export BUILD_DEPS="libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libsdl1.2-dev libsdl2-dev libvulkan-dev zlib1g-dev"
export BUILD_DEPS="libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libsdl2-dev libvulkan-dev zlib1g-dev"
./.github/workflows/scripts/ci_install_ubuntu_deps.sh ${{ matrix.arch }} ${{ matrix.cc }}
- name: Build and related stuff, backup binaries
run: |
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
export PATH="$(pwd)/.github/workflows/scripts:${PATH}"
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1 SDL_CONFIG="sdl-config""
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1"
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1 SDL_CONFIG="sdl2-config""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 SDL_CONFIG="sdl-config" POSTFIX="-old""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 SDL_CONFIG="sdl2-config" POSTFIX="-old""
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 POSTFIX="-old""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }} makepkg
- name: Upload artifact
if: matrix.cc == 'GCC'
@@ -63,12 +57,24 @@ jobs:
arch: x64
cross: x86_64
env: MINGW64
gbc: 0
- cc: GCC
arch: x86
cross: i686
env: MINGW32
name: Windows / MSYS2 ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: windows-2022
gbc: 0
- cc: GCC
arch: x64
cross: ucrt-x86_64
env: UCRT64
gbc: 1
- cc: Clang
arch: x64
cross: clang-x86_64
env: CLANG64
gbc: 1
name: Windows ${{ matrix.arch }} / MSYS2 ${{ matrix.env }} ${{ matrix.cc }}
runs-on: windows-2025
defaults:
run:
shell: msys2 {0}
@@ -82,7 +88,6 @@ jobs:
git
libtool
make
mingw-w64-${{ matrix.cross }}-gcc
mingw-w64-${{ matrix.cross }}-toolchain
mingw-w64-${{ matrix.cross }}-freetype
mingw-w64-${{ matrix.cross }}-libpng
@@ -93,10 +98,11 @@ jobs:
mingw-w64-${{ matrix.cross }}-zlib
- name: Build and related stuff, backup binaries
run: |
if [[ ${{ matrix.gbc }} -eq 1 ]]; then pacman --noconfirm -S --needed mingw-w64-${{ matrix.cross }}-opencv; fi
export PATH="$(pwd)/.github/workflows/scripts:${PATH}"
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1"
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1 OPENCV=${{ matrix.gbc }}"
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 POSTFIX="-old""
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 OPENCV=${{ matrix.gbc }} POSTFIX="-old""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
- name: Backup dependencies, etc...
run: |
@@ -114,11 +120,11 @@ jobs:
include:
- toolset: v143
arch: x64
vs: 2022
vs: 2025
- toolset: v141_xp
arch: x86
vs: 2019
name: Windows / MSVC with ${{ matrix.toolset }} / ${{ matrix.arch }}
name: Windows ${{ matrix.arch }} / MSVC with ${{ matrix.toolset }}
runs-on: windows-${{ matrix.vs }}
defaults:
run:
@@ -131,7 +137,7 @@ jobs:
set PATH=%CD%\.github\workflows\scripts;%PATH%
set TOOLSET=${{ matrix.toolset }}
call ci_msvc_build.cmd ${{ matrix.arch }}
ci_msvc_build.cmd ${{ matrix.arch }} newdyn
call ci_msvc_build.cmd ${{ matrix.arch }} newdyn
- name: Backup dependencies, etc...
run: |
.\.github\workflows\scripts\ci_backup_msvc_deps.cmd ${{ matrix.arch }} freetype.dll libpng16.dll SDL2.dll SDL2_net.dll zlib.dll
@@ -19,27 +19,21 @@ jobs:
arch: x64
- cc: Clang
arch: x86
name: Linux / ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: ubuntu-22.04
name: Linux / ${{ matrix.cc }} ${{ matrix.arch }}
runs-on: ubuntu-24.04
if: github.repository == 'mupen64plus/mupen64plus-core'
steps:
- uses: actions/checkout@v4
- name: Get build dependencies and arrange the environment
run: |
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
export BUILD_DEPS="libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libsdl1.2-dev libsdl2-dev libvulkan-dev zlib1g-dev"
export BUILD_DEPS="libfreetype6-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libsdl2-dev libvulkan-dev zlib1g-dev"
./.github/workflows/scripts/ci_install_ubuntu_deps.sh ${{ matrix.arch }} ${{ matrix.cc }}
- name: Build and related stuff, backup binaries
run: |
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
export PATH="$(pwd)/.github/workflows/scripts:${PATH}"
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1 SDL_CONFIG="sdl-config""
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1"
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1 SDL_CONFIG="sdl2-config""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 SDL_CONFIG="sdl-config" POSTFIX="-old""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 SDL_CONFIG="sdl2-config" POSTFIX="-old""
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 POSTFIX="-old""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }} makepkg
- name: Upload artifact
if: matrix.cc == 'GCC'
@@ -57,12 +51,24 @@ jobs:
arch: x64
cross: x86_64
env: MINGW64
gbc: 0
- cc: GCC
arch: x86
cross: i686
env: MINGW32
name: Windows / MSYS2 ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: windows-2022
gbc: 0
- cc: GCC
arch: x64
cross: ucrt-x86_64
env: UCRT64
gbc: 1
- cc: Clang
arch: x64
cross: clang-x86_64
env: CLANG64
gbc: 1
name: Windows ${{ matrix.arch }} / MSYS2 ${{ matrix.env }} ${{ matrix.cc }}
runs-on: windows-2025
if: github.repository == 'mupen64plus/mupen64plus-core'
defaults:
run:
@@ -77,7 +83,6 @@ jobs:
git
libtool
make
mingw-w64-${{ matrix.cross }}-gcc
mingw-w64-${{ matrix.cross }}-toolchain
mingw-w64-${{ matrix.cross }}-freetype
mingw-w64-${{ matrix.cross }}-libpng
@@ -88,10 +93,11 @@ jobs:
mingw-w64-${{ matrix.cross }}-zlib
- name: Build and related stuff, backup binaries
run: |
if [[ ${{ matrix.gbc }} -eq 1 ]]; then pacman --noconfirm -S --needed mingw-w64-${{ matrix.cross }}-opencv; fi
export PATH="$(pwd)/.github/workflows/scripts:${PATH}"
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1"
export CONFIG_OVERRIDE="ACCURATE_FPU=1 NEW_DYNAREC=1 OPENCV=${{ matrix.gbc }}"
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 POSTFIX="-old""
export CONFIG_OVERRIDE="ACCURATE_FPU=0 NEW_DYNAREC=0 OPENCV=${{ matrix.gbc }} POSTFIX="-old""
ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }}
- name: Backup dependencies, etc...
run: |
@@ -109,11 +115,11 @@ jobs:
include:
- toolset: v143
arch: x64
vs: 2022
vs: 2025
- toolset: v141_xp
arch: x86
vs: 2019
name: Windows / MSVC with ${{ matrix.toolset }} / ${{ matrix.arch }}
name: Windows ${{ matrix.arch }} / MSVC with ${{ matrix.toolset }}
runs-on: windows-${{ matrix.vs }}
if: github.repository == 'mupen64plus/mupen64plus-core'
defaults:
@@ -127,7 +133,7 @@ jobs:
set PATH=%CD%\.github\workflows\scripts;%PATH%
set TOOLSET=${{ matrix.toolset }}
call ci_msvc_build.cmd ${{ matrix.arch }}
ci_msvc_build.cmd ${{ matrix.arch }} newdyn
call ci_msvc_build.cmd ${{ matrix.arch }} newdyn
- name: Backup dependencies, etc...
run: |
.\.github\workflows\scripts\ci_backup_msvc_deps.cmd ${{ matrix.arch }} freetype.dll libpng16.dll SDL2.dll SDL2_net.dll zlib.dll
@@ -37,7 +37,7 @@ export REPO="${PWD##*/}"
if [[ "${REPO}" == "" ]]; then exit 6; fi
export BIN_OS="$(uname -s | tr [A-Z] [a-z])"
if [[ ! -z ${MSYSTEM} ]]; then export BIN_OS="msys2"; fi
if [[ ! -z ${MSYSTEM} ]]; then export BIN_OS="msys2-$(echo "${MSYSTEM}" | tr [A-Z] [a-z])"; fi
export ENV_NAME="$(uname -s)"
export LDD="ldd"
export PKG_PATH="usr/local/lib/mupen64plus/"
@@ -105,6 +105,7 @@ if [[ -z ${ARTIFACT} ]]; then
exit 5
else
cd pkg
if [[ -f "mupen64plus.desktop" ]]; then rm -f mupen64plus.desktop; fi
ls -gG "${PKG_PATH}${ARTIFACT}"
echo ""
${LDD} "${PKG_PATH}${ARTIFACT}" > ldd.log
@@ -1,12 +1,16 @@
#!/usr/bin/env bash
set -e +u
# NOTE: There is no native "libglew-dev:i386" in Ubuntu 22.04, we will use Debian ones...
# NOTE: There is no native "libglew-dev:i386" in Ubuntu 24.04, we will use Debian ones...
export GLEWVER="$(apt list libglew-dev | grep "amd64" | cut -d ' ' -f2)"
export PKGS="$(apt list libglew* | grep "${GLEWVER}" | cut -d '/' -f1)"
export DEBSOURCE="http://http.us.debian.org/debian/pool/main/g/glew/"
export PKGVER_LS="$(curl -sS ${DEBSOURCE} | grep "${GLEWVER}" | grep "amd64" | cut -d '_' -f2 | sort)"
export BUILDP1="$(echo "${GLEWVER}" | cut -d '-' -f1)"
export BUILDP2="$(echo "${GLEWVER}" | cut -d '-' -f2)"
export GLEWBUILD="${BUILDP1}"
if [[ "${GLEWBUILD}" != "${GLEWVER}" ]]; then export GLEWBUILD="${BUILDP1}-${BUILDP2:0:1}"; fi
export PKGVER_LS="$(curl -sS ${DEBSOURCE} | grep "${GLEWBUILD}" | grep "amd64" | cut -d '_' -f2 | sort)"
if [[ "${PKGVER_LS}" != "" ]]; then
for VER in ${PKGVER_LS}; do export PKGVER="${VER}"; done
cd /tmp
@@ -4,7 +4,7 @@ set -e +u
if [[ ${#} -lt 2 ]]; then exit 9; fi
unset ARCH_DEP CC_DEP
export BUILD_DEPS_I386="crossbuild-essential-i386 g++${C_GCC_SUFFIX}-i686-linux-gnu gcc${C_GCC_SUFFIX}-i686-linux-gnu libc6-i386"
export BUILD_DEPS_I386="crossbuild-essential-i386 g++${C_GCC_SUFFIX}-i686-linux-gnu gcc${C_GCC_SUFFIX}-i686-linux-gnu"
export HOTFIX_I386="libatomic1:i386 libgcc-s1:i386 libstdc++6:i386 ${HOTFIX_I386}"
export ENV_ARGS="$(echo "${*}" | tr [A-Z] [a-z])"
@@ -17,7 +17,7 @@ for ARG in ${ENV_ARGS}; do
export CC_DEP="g++${C_GCC_SUFFIX} gcc${C_GCC_SUFFIX}"
;;
multilib )
export BUILD_DEPS_I386="g++${C_GCC_SUFFIX}-multilib gcc${C_GCC_SUFFIX}-multilib libc6-dev-i386"
export BUILD_DEPS_I386="g++${C_GCC_SUFFIX}-multilib gcc${C_GCC_SUFFIX}-multilib"
;;
x64 )
export ARCH_DEP="x64"
@@ -31,9 +31,9 @@ done
if [[ -z ${ARCH_DEP} ]]; then exit 8; fi
if [[ -z ${CC_DEP} ]]; then exit 7; fi
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
if [[ "${ARCH_DEP}" == "x86" ]]; then sudo dpkg --add-architecture i386; fi
sudo apt-get update
sudo apt-mark hold grub-common grub-efi-amd64-bin python3 python3-apt shim-signed
sudo apt-get -y install build-essential git nasm pkg-config ${CC_DEP} ${BUILD_DEPS}
if [[ "${ARCH_DEP}" == "x86" ]]; then
if [[ "${BUILD_DEPS}" != "" ]]; then
+3 -3
View File
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:mupen64plus/mupen64plus-core.git
branch = master
commit = e170c409fb006aa38fd02031b5eefab6886ec125
parent = 37839b0178b42b57a38a040bbf4e1f410e6952a1
commit = c5b1e72be0d2b33c18be48c989e726ac176e7872
parent = c8048741165583eaced380e84945764e924df6bf
method = merge
cmdver = 0.4.6
cmdver = 0.4.9
+4 -4
View File
@@ -339,11 +339,11 @@ LDLIBS += $(LIBPNG_LDLIBS)
ifeq ($(OPENCV), 1)
# OpenCV lib
ifeq ($(origin OPENCV_CFLAGS) $(origin OPENCV_LDLIBS), undefined undefined)
ifeq ($(shell $(PKG_CONFIG) --modversion opencv 2>/dev/null),)
$(error No OpenCV development libraries found!)
ifeq ($(shell $(PKG_CONFIG) --modversion opencv4 2>/dev/null),)
$(error No OpenCV 4 development libraries found!)
endif
OPENCV_CFLAGS += $(shell $(PKG_CONFIG) --cflags opencv)
OPENCV_LDLIBS += $(shell $(PKG_CONFIG) --libs opencv)
OPENCV_CFLAGS += $(shell $(PKG_CONFIG) --cflags opencv4)
OPENCV_LDLIBS += $(shell $(PKG_CONFIG) --libs opencv4)
endif
CFLAGS += $(OPENCV_CFLAGS)
LDLIBS += $(OPENCV_LDLIBS)
@@ -281,7 +281,7 @@ static db_addr_t
db_disasm_insn ( struct r4k_dis_t * state,
int insn,
db_addr_t loc,
bool altfmt )
unsigned char altfmt)
{
char * rel;
InstFmt i;
@@ -24,11 +24,9 @@
#if defined(WIN32)
typedef unsigned int uint32_t;
typedef unsigned char bool;
#define false 0
#define true 1
#else
#include <stdbool.h>
#include <stdint.h>
#endif
@@ -65,7 +65,7 @@ void read_cart_rom(void* opaque, uint32_t address, uint32_t* value)
}
else
{
*value = *(uint32_t*)(cart_rom->rom + addr);
*value = cart_rom->rom_size == 0 ? 0 : *(uint32_t*)(cart_rom->rom + addr);
}
}
+1 -1
View File
@@ -200,7 +200,7 @@ void init_device(struct device* dev,
* use CART unless DD is plugged and the plugged CART is not a combo media (cart+disk),
* or rom_size is 0 meaning there's no CART loaded
*/
uint8_t media = *((uint8_t*)mem_base_u32(base, MM_CART_ROM) + (0x3b ^ S8));
uint8_t media = rom_size == 0 ? 0 : *((uint8_t*)mem_base_u32(base, MM_CART_ROM) + (0x3b ^ S8));
uint32_t rom_base = (rom_size == 0 || (dd_rom_size > 0 && media != 'C'))
? MM_DD_ROM
: MM_CART_ROM;
@@ -4274,7 +4274,7 @@ static void loop_preload(signed char pre[],signed char entry[])
// Generate address for load/store instruction
static void address_generation(int i,struct regstat *i_regs,signed char entry[])
{
if(i>=4069) return;
if(i>=MAXBLOCK) return;
if(itype[i]==LOAD||itype[i]==LOADLR||itype[i]==STORE||itype[i]==STORELR||itype[i]==C1LS) {
int ra=0;
int agr=AGEN1+(i&1);
@@ -4381,7 +4381,7 @@ static void address_generation(int i,struct regstat *i_regs,signed char entry[])
}
}
// Preload constants for next instruction
if(i+1>=4096) return;
if(i+1>=MAXBLOCK) return;
if(itype[i+1]==LOAD||itype[i+1]==LOADLR||itype[i+1]==STORE||itype[i+1]==STORELR||itype[i+1]==C1LS) {
int agr,ra;
#if (NEW_DYNAREC!=NEW_DYNAREC_X86) && (NEW_DYNAREC!=NEW_DYNAREC_X64)
@@ -153,7 +153,7 @@ void read_pi_regs(void* opaque, uint32_t address, uint32_t* value)
struct pi_controller* pi = (struct pi_controller*)opaque;
uint32_t reg = pi_reg(address);
if (reg < PI_REGS_COUNT)
if (reg < PI_REGS_COUNT)
*value = pi->regs[reg];
if (reg == PI_WR_LEN_REG || reg == PI_RD_LEN_REG)
@@ -104,7 +104,7 @@ void read_vi_regs(void* opaque, uint32_t address, uint32_t* value)
/* update current field */
vi->regs[VI_CURRENT_REG] = (vi->regs[VI_CURRENT_REG] & (~1)) | vi->field;
}
if (reg < VI_REGS_COUNT)
{
*value = vi->regs[reg];
-1
View File
@@ -21,7 +21,6 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <SDL.h>
#include <SDL_syswm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+1 -1
View File
@@ -233,7 +233,7 @@ static m64p_error plugin_connect_gfx(m64p_dynlib_handle plugin_handle)
static m64p_error plugin_start_gfx(void)
{
uint8_t media = *((uint8_t*)mem_base_u32(g_mem_base, MM_CART_ROM) + (0x3b ^ S8));
uint8_t media = g_rom_size == 0 ? 0 : *((uint8_t*)mem_base_u32(g_mem_base, MM_CART_ROM) + (0x3b ^ S8));
/* Here we feed 64DD IPL ROM header to GFX plugin if 64DD is present.
* We use g_media_loader.get_dd_rom to detect 64DD presence
@@ -26,18 +26,16 @@ jobs:
arch: x64
- cc: Clang
arch: x86
name: Linux / ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: ubuntu-22.04
name: Linux / ${{ matrix.cc }} ${{ matrix.arch }}
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Get build dependencies and arrange the environment
run: |
git clone --depth 1 https://github.com/mupen64plus/mupen64plus-core.git ../mupen64plus-core
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
./../mupen64plus-core/.github/workflows/scripts/ci_install_ubuntu_deps.sh ${{ matrix.arch }} ${{ matrix.cc }}
- name: Build and related stuff, backup binaries
run: |
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
./../mupen64plus-core/.github/workflows/scripts/ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }} makepkg
- name: Upload artifact
if: matrix.cc == 'GCC'
@@ -59,8 +57,16 @@ jobs:
arch: x86
cross: i686
env: MINGW32
name: Windows / MSYS2 ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: windows-2022
- cc: GCC
arch: x64
cross: ucrt-x86_64
env: UCRT64
- cc: Clang
arch: x64
cross: clang-x86_64
env: CLANG64
name: Windows ${{ matrix.arch }} / MSYS2 ${{ matrix.env }} ${{ matrix.cc }}
runs-on: windows-2025
defaults:
run:
shell: msys2 {0}
@@ -74,7 +80,6 @@ jobs:
git
libtool
make
mingw-w64-${{ matrix.cross }}-gcc
mingw-w64-${{ matrix.cross }}-toolchain
mingw-w64-${{ matrix.cross }}-ntldd
- name: Build and related stuff, backup binaries
@@ -97,11 +102,11 @@ jobs:
include:
- toolset: v143
arch: x64
vs: 2022
vs: 2025
- toolset: v141_xp
arch: x86
vs: 2019
name: Windows / MSVC with ${{ matrix.toolset }} / ${{ matrix.arch }}
name: Windows ${{ matrix.arch }} / MSVC with ${{ matrix.toolset }}
runs-on: windows-${{ matrix.vs }}
defaults:
run:
@@ -19,19 +19,17 @@ jobs:
arch: x64
- cc: Clang
arch: x86
name: Linux / ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: ubuntu-22.04
name: Linux / ${{ matrix.cc }} ${{ matrix.arch }}
runs-on: ubuntu-24.04
if: github.repository == 'mupen64plus/mupen64plus-rsp-hle'
steps:
- uses: actions/checkout@v4
- name: Get build dependencies and arrange the environment
run: |
git clone --depth 1 https://github.com/mupen64plus/mupen64plus-core.git ../mupen64plus-core
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
./../mupen64plus-core/.github/workflows/scripts/ci_install_ubuntu_deps.sh ${{ matrix.arch }} ${{ matrix.cc }}
- name: Build and related stuff, backup binaries
run: |
export C_CLANG_SUFFIX="-15" C_GCC_SUFFIX="-12"
./../mupen64plus-core/.github/workflows/scripts/ci_build.sh ${{ matrix.arch }} ${{ matrix.cc }} makepkg
- name: Upload artifact
if: matrix.cc == 'GCC'
@@ -53,8 +51,16 @@ jobs:
arch: x86
cross: i686
env: MINGW32
name: Windows / MSYS2 ${{ matrix.cc }} / ${{ matrix.arch }}
runs-on: windows-2022
- cc: GCC
arch: x64
cross: ucrt-x86_64
env: UCRT64
- cc: Clang
arch: x64
cross: clang-x86_64
env: CLANG64
name: Windows ${{ matrix.arch }} / MSYS2 ${{ matrix.env }} ${{ matrix.cc }}
runs-on: windows-2025
if: github.repository == 'mupen64plus/mupen64plus-rsp-hle'
defaults:
run:
@@ -69,7 +75,6 @@ jobs:
git
libtool
make
mingw-w64-${{ matrix.cross }}-gcc
mingw-w64-${{ matrix.cross }}-toolchain
mingw-w64-${{ matrix.cross }}-ntldd
- name: Build and related stuff, backup binaries
@@ -92,11 +97,11 @@ jobs:
include:
- toolset: v143
arch: x64
vs: 2022
vs: 2025
- toolset: v141_xp
arch: x86
vs: 2019
name: Windows / MSVC with ${{ matrix.toolset }} / ${{ matrix.arch }}
name: Windows ${{ matrix.arch }} / MSVC with ${{ matrix.toolset }}
runs-on: windows-${{ matrix.vs }}
if: github.repository == 'mupen64plus/mupen64plus-rsp-hle'
defaults:
@@ -1,2 +1,5 @@
/projects/unix/_obj*/
/projects/unix/mupen64plus-rsp-hle*.so
/projects/unix/mupen64plus-rsp-hle*.dll
/projects/msvc/Win32/
/projects/msvc/x64/
+2 -2
View File
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:/mupen64plus/mupen64plus-rsp-hle.git
branch = master
commit = 47f8e92e9a8bb9e49195ecfb03e9df0b63add242
parent = 23120150f9ddbef13a0690f004c364bb334addfc
commit = 80efbed3d737a32114956e149905c304944ebd61
parent = 6e1df7fc792cbdee30d31366af395e72a6cde813
method = merge
cmdver = 0.4.6
+1 -1
View File
@@ -475,7 +475,7 @@ static void ReorderSubBlock(int16_t *dst, const int16_t *src, const unsigned int
unsigned int i;
/* source and destination sublocks cannot overlap */
assert(labs(dst - src) > SUBBLOCK_SIZE);
assert(labs(dst - src) >= SUBBLOCK_SIZE);
for (i = 0; i < SUBBLOCK_SIZE; ++i)
dst[i] = src[table[i]];
+2 -2
View File
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:/gonetz/GLideN64.git
branch = master
commit = 9f8fc1e4c59c4cdb37112ef0c100158b25c2c1cb
parent = 3c615c84599a0ea8624c2deea4c8ba438ce988d6
commit = 55c436c706224eae6cd1395b88e083105b7d7834
parent = 5cab9282e7a7f1407cde18b6e5d1630f3c156042
method = merge
cmdver = 0.4.6
+1 -1
View File
@@ -204,7 +204,7 @@ void GBIInfo::_makeCurrent(MicrocodeInfo * _pCurrent)
RDP_Init();
G_TRI1 = G_TRI2 = G_TRIX = G_QUAD = -1; // For correct work of gSPFlushTriangles()
G_TRI1 = G_TRI2 = G_TRIX = G_QUAD = G_TRISTRIP = G_TRIFAN = -1; // For correct work of gSPFlushTriangles()
gSP.clipRatio = 1U;
switch (m_pCurrent->type) {
@@ -53,6 +53,7 @@ void _loadSettings(GlSettings & settings)
config.generalEmulation.enableLegacyBlending = settings.value("enableLegacyBlending", config.generalEmulation.enableLegacyBlending).toInt(); //ini only
config.generalEmulation.enableHybridFilter = settings.value("enableHybridFilter", config.generalEmulation.enableHybridFilter).toInt(); //ini only
config.generalEmulation.enableFragmentDepthWrite = settings.value("enableFragmentDepthWrite", config.generalEmulation.enableFragmentDepthWrite).toInt(); //ini only
config.generalEmulation.hacks |= settings.value("hacks", config.generalEmulation.hacks).toInt(); //ini only
config.generalEmulation.enableCustomSettings = settings.value("enableCustomSettings", config.generalEmulation.enableCustomSettings).toInt();
settings.endGroup();
@@ -58,6 +58,7 @@ void _loadSettings(QSettings & settings)
config.generalEmulation.enableLegacyBlending = settings.value("enableLegacyBlending", config.generalEmulation.enableLegacyBlending).toInt(); //ini only
config.generalEmulation.enableHybridFilter = settings.value("enableHybridFilter", config.generalEmulation.enableHybridFilter).toInt(); //ini only
config.generalEmulation.enableFragmentDepthWrite = settings.value("enableFragmentDepthWrite", config.generalEmulation.enableFragmentDepthWrite).toInt(); //ini only
config.generalEmulation.hacks |= settings.value("hacks", config.generalEmulation.hacks).toInt(); //ini only
config.generalEmulation.enableCustomSettings = settings.value("enableCustomSettings", config.generalEmulation.enableCustomSettings).toInt();
settings.endGroup();
@@ -193,7 +193,7 @@ void GLInfo::init() {
eglImage = (Utils::isEGLExtensionSupported("EGL_KHR_image_base") || Utils::isEGLExtensionSupported("EGL_KHR_image"));
ext_fetch_arm = Utils::isExtensionSupported(*this, "GL_ARM_shader_framebuffer_fetch") && !ext_fetch;
dual_source_blending = !isGLESX || (Utils::isExtensionSupported(*this, "GL_EXT_blend_func_extended") && !isAnyAdreno);
dual_source_blending = !isGLESX || ((!isGLES2) && (Utils::isExtensionSupported(*this, "GL_EXT_blend_func_extended") && !isAnyAdreno));
anisotropic_filtering = Utils::isExtensionSupported(*this, "GL_EXT_texture_filter_anisotropic");
#ifdef OS_ANDROID
@@ -43,6 +43,8 @@ void gSPFlushTriangles()
(RSP.nextCmd != G_TRI1) &&
(RSP.nextCmd != G_TRI2) &&
(RSP.nextCmd != G_TRIX) &&
(RSP.nextCmd != G_TRISTRIP) &&
(RSP.nextCmd != G_TRIFAN) &&
(RSP.nextCmd != G_QUAD)
) {
dwnd().getDrawer().drawTriangles();
@@ -396,6 +396,8 @@ void Config_LoadCustomConfig()
if (result == M64ERR_SUCCESS) config.generalEmulation.enableLegacyBlending = atoi(value);
result = ConfigExternalGetParameter(fileHandle, sectionName, "generalEmulation\\enableFragmentDepthWrite", value, sizeof(value));
if (result == M64ERR_SUCCESS) config.generalEmulation.enableFragmentDepthWrite = atoi(value);
result = ConfigExternalGetParameter(fileHandle, sectionName, "generalEmulation\\hacks", value, sizeof(value));
if (result == M64ERR_SUCCESS) config.generalEmulation.hacks |= atoi(value);
result = ConfigExternalGetParameter(fileHandle, sectionName, "graphics2D\\correctTexrectCoords", value, sizeof(value));
if (result == M64ERR_SUCCESS) config.graphics2D.correctTexrectCoords = atoi(value);
@@ -170,7 +170,9 @@ struct Vertices7
u8 v[7];
inline bool valid(u8 i) const
{ return v[i] < 64; }
{
return v[i] < 64;
}
};
static inline Vertices7 unpackVertices7(u32 w0, u32 w1)
@@ -186,8 +188,15 @@ static inline Vertices7 unpackVertices7(u32 w0, u32 w1)
return v;
}
struct DeferFlush
{
~DeferFlush()
{ gSPFlushTriangles(); }
};
static void F3DEX3_TriStrip(u32 w0, u32 w1)
{
DeferFlush flush;
Vertices7 vertices = unpackVertices7(w0, w1);
// *v1 - v2 - v3, v3 - v2 - v4, v3 - v4 - v5, v5 - v4 - v6, v5 - v6 - v7
if (!vertices.valid(0) || !vertices.valid(1) || !vertices.valid(2)) return;
@@ -208,6 +217,7 @@ static void F3DEX3_TriStrip(u32 w0, u32 w1)
static void F3DEX3_TriFan(u32 w0, u32 w1)
{
DeferFlush flush;
Vertices7 vertices = unpackVertices7(w0, w1);
// *v1 - v2 - v3, v1 - v3 - v4, v1 - v4 - v5, v1 - v5 - v6, v1 - v6 - v7
if (!vertices.valid(0) || !vertices.valid(1) || !vertices.valid(2)) return;
@@ -220,7 +230,7 @@ static void F3DEX3_TriFan(u32 w0, u32 w1)
gSPTriangle(vertices.v[0], vertices.v[3], vertices.v[4]);
if (!vertices.valid(5)) return;
gSPTriangle(vertices.v[0], vertices.v[3], vertices.v[5]);
gSPTriangle(vertices.v[0], vertices.v[4], vertices.v[5]);
if (!vertices.valid(6)) return;
gSPTriangle(vertices.v[0], vertices.v[5], vertices.v[6]);
+2 -2
View File
@@ -6,7 +6,7 @@
[subrepo]
remote = git@github.com:/Rosalie241/parallel-rdp-standalone.git
branch = RMG
commit = 74a9badf728fcb6a01ceb2ca9668aae5d1f1157c
parent = cb2948ec7404a7ee91e08fd5c3acb65c41629856
commit = 7d1281997ff5a03378c9dd51fefb164cdb4e7877
parent = fddf5eb4a7e99767c4095b34f542098670cdc78b
method = merge
cmdver = 0.4.6
@@ -61,17 +61,15 @@ MainDialog::MainDialog(QWidget* parent) : QDialog(parent)
}
this->resolutionUpscaleComboBox->setCurrentIndex(comboBoxIndex);
// set deinterlacer combobox
this->deinterlacerComboBox->setCurrentIndex(ConfigGetParamBool(configVideoParallel, KEY_DEINTERLACE) ? 1 : 0);
// set downscaling combobox
this->downscalerComboBox->setCurrentIndex(ConfigGetParamInt(configVideoParallel, KEY_DOWNSCALE));
// set cropOverscan combobox
this->cropOverscanSpinBox->setValue(ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROP));
// set verticalStretch combobox
this->verticalStretchSpinBox->setValue(ConfigGetParamInt(configVideoParallel, KEY_VERTICAL_STRETCH));
this->overscanGroupBox->setChecked(ConfigGetParamBool(configVideoParallel, KEY_OVERSCANCROPENABLE));
this->overscanLeftSpinBox->setValue(ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPLEFT));
this->overscanRightSpinBox->setValue(ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPRIGHT));
this->overscanTopSpinBox->setValue(ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPTOP));
this->overscanBottomSpinBox->setValue(ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPBOTTOM));
this->superSampledDitherCheckBox->setChecked(ConfigGetParamBool(configVideoParallel, KEY_SSDITHER));
this->viAaCheckBox->setChecked(ConfigGetParamBool(configVideoParallel, KEY_AA));
@@ -127,8 +125,16 @@ void MainDialog::on_buttonBox_clicked(QAbstractButton* button)
ConfigSetParameter(configVideoParallel, KEY_DOWNSCALE, M64TYPE_INT, &downscalingValue);
// cropOverscan
int cropOverscanValue = this->cropOverscanSpinBox->value();
ConfigSetParameter(configVideoParallel, KEY_OVERSCANCROP, M64TYPE_INT, &cropOverscanValue);
int overscanEnabled = this->overscanGroupBox->isChecked();
int overscanLeft = this->overscanLeftSpinBox->value();
int overscanRight = this->overscanRightSpinBox->value();
int overscanTop = this->overscanTopSpinBox->value();
int overscanBottom = this->overscanBottomSpinBox->value();
ConfigSetParameter(configVideoParallel, KEY_OVERSCANCROPENABLE, M64TYPE_BOOL, &overscanEnabled);
ConfigSetParameter(configVideoParallel, KEY_OVERSCANCROPLEFT, M64TYPE_INT, &overscanLeft);
ConfigSetParameter(configVideoParallel, KEY_OVERSCANCROPRIGHT, M64TYPE_INT, &overscanRight);
ConfigSetParameter(configVideoParallel, KEY_OVERSCANCROPTOP, M64TYPE_INT, &overscanTop);
ConfigSetParameter(configVideoParallel, KEY_OVERSCANCROPBOTTOM, M64TYPE_INT, &overscanBottom);
// verticalStretch
int verticalStretchValue = this->verticalStretchSpinBox->value();
@@ -167,8 +173,12 @@ void MainDialog::on_buttonBox_clicked(QAbstractButton* button)
this->resolutionUpscaleComboBox->setCurrentText("None");
this->deinterlacerComboBox->setCurrentIndex(0);
this->downscalerComboBox->setCurrentIndex(0);
this->cropOverscanSpinBox->setValue(0);
this->verticalStretchSpinBox->setValue(0);
this->overscanGroupBox->setChecked(false);
this->overscanLeftSpinBox->setValue(0);
this->overscanRightSpinBox->setValue(0);
this->overscanTopSpinBox->setValue(0);
this->overscanBottomSpinBox->setValue(0);
this->superSampledDitherCheckBox->setChecked(true);
this->viAaCheckBox->setChecked(true);
@@ -201,27 +201,6 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Crop overscan</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="cropOverscanSpinBox">
<property name="suffix">
<string> px</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_9">
<property name="bottomMargin">
@@ -358,6 +337,53 @@
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="overscanGroupBox">
<property name="title">
<string>Overscan</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QSpinBox" name="overscanTopSpinBox">
<property name="minimum">
<number>-99</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QSpinBox" name="overscanLeftSpinBox">
<property name="minimum">
<number>-99</number>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QSpinBox" name="overscanRightSpinBox">
<property name="minimum">
<number>-99</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="overscanBottomSpinBox">
<property name="minimum">
<number>-99</number>
</property>
</widget>
</item>
</layout>
<zorder>overscanBottomSpinBox</zorder>
<zorder>overscanRightSpinBox</zorder>
<zorder>overscanLeftSpinBox</zorder>
<zorder>overscanTopSpinBox</zorder>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
+10 -2
View File
@@ -181,7 +181,11 @@ EXPORT m64p_error CALL PluginStartup(m64p_dynlib_handle _CoreLibHandle, void *Co
ConfigSetDefaultBool(configVideoParallel, KEY_DEINTERLACE, 0, "Deinterlacing method. False=Bob, True=Weave");
ConfigSetDefaultBool(configVideoParallel, KEY_SSREADBACKS, 0, "Enable superscaling of readbacks when upsampling");
ConfigSetDefaultBool(configVideoParallel, KEY_SSDITHER, 1, "Enable superscaling of dithering when upsampling");
ConfigSetDefaultInt(configVideoParallel, KEY_OVERSCANCROP, 0, "Amount of overscan pixels to crop on all sides");
ConfigSetDefaultInt(configVideoParallel, KEY_OVERSCANCROPLEFT, 0, "Amount of overscan pixels to crop on the left side");
ConfigSetDefaultInt(configVideoParallel, KEY_OVERSCANCROPRIGHT, 0, "Amount of overscan pixels to crop on the right side");
ConfigSetDefaultInt(configVideoParallel, KEY_OVERSCANCROPTOP, 0, "Amount of overscan pixels to crop on the top side");
ConfigSetDefaultInt(configVideoParallel, KEY_OVERSCANCROPBOTTOM, 0, "Amount of overscan pixels to crop on the bottom side");
ConfigSetDefaultBool(configVideoParallel, KEY_OVERSCANCROPENABLE, 0, "Whether to enable overscan");
ConfigSetDefaultInt(configVideoParallel, KEY_VERTICAL_STRETCH, 0, "Amount of pixels to stretch by vertically. Can fix PAL ports that didn't fill the PAL resolution of 288p (use value of 24 in that case).");
ConfigSetDefaultBool(configVideoParallel, KEY_AA, 1, "VI anti-aliasing, smooths polygon edges.");
ConfigSetDefaultBool(configVideoParallel, KEY_DIVOT, 1, "Allow VI divot filter, cleans up stray black pixels.");
@@ -307,7 +311,11 @@ EXPORT int CALL RomOpen(void)
vk_native_tex_rect = ConfigGetParamBool(configVideoParallel, KEY_NATIVETEXTRECT);
vk_interlacing = ConfigGetParamBool(configVideoParallel, KEY_DEINTERLACE);
vk_downscaling_steps = ConfigGetParamInt(configVideoParallel, KEY_DOWNSCALE);
vk_overscan = ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROP);
vk_overscan_left = ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPLEFT);
vk_overscan_right = ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPRIGHT);
vk_overscan_top = ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPTOP);
vk_overscan_bottom = ConfigGetParamInt(configVideoParallel, KEY_OVERSCANCROPBOTTOM);
vk_overscan_enable = ConfigGetParamBool(configVideoParallel, KEY_OVERSCANCROPENABLE);
vk_vertical_stretch = ConfigGetParamInt(configVideoParallel, KEY_VERTICAL_STRETCH);
m64p_error netplay_init = ConfigReceiveNetplayConfig(NULL, 0);
+5 -1
View File
@@ -35,7 +35,11 @@ extern ptr_ConfigSetParameter ConfigSetParameter;
#define KEY_WIDESCREEN "WidescreenStretch"
#define KEY_SSDITHER "SuperscaledDither"
#define KEY_SSREADBACKS "SuperscaledReads"
#define KEY_OVERSCANCROP "CropOverscan"
#define KEY_OVERSCANCROPLEFT "CropOverscanLeft"
#define KEY_OVERSCANCROPRIGHT "CropOverscanRight"
#define KEY_OVERSCANCROPTOP "CropOverscanTop"
#define KEY_OVERSCANCROPBOTTOM "CropOverscanBottom"
#define KEY_OVERSCANCROPENABLE "CropOverscanEnable"
#define KEY_VERTICAL_STRETCH "VerticalStretch"
#define KEY_DIVOT "Divot"
#define KEY_GAMMADITHER "GammaDither"
+12 -4
View File
@@ -19,9 +19,13 @@ static std::unique_ptr<MupenWSIPlatform> wsi_platform;
int32_t vk_rescaling;
bool vk_ssreadbacks;
bool vk_ssdither;
unsigned vk_overscan;
unsigned vk_vertical_stretch;
unsigned vk_downscaling_steps;
uint32_t vk_overscan_left;
uint32_t vk_overscan_right;
uint32_t vk_overscan_top;
uint32_t vk_overscan_bottom;
bool vk_overscan_enable;
uint32_t vk_vertical_stretch;
uint32_t vk_downscaling_steps;
bool vk_native_texture_lod;
bool vk_native_tex_rect;
bool vk_divot_filter, vk_gamma_dither;
@@ -180,7 +184,11 @@ static void render_frame(Vulkan::Device &device)
opts.blend_previous_frame = vk_interlacing;
opts.upscale_deinterlacing = !vk_interlacing;
opts.downscale_steps = vk_downscaling_steps;
opts.crop_overscan_pixels = vk_overscan;
opts.crop_rect.left = vk_overscan_left;
opts.crop_rect.right = vk_overscan_right;
opts.crop_rect.top = vk_overscan_top;
opts.crop_rect.bottom = vk_overscan_bottom;
opts.crop_rect.enable = vk_overscan_enable;
if (vk_vertical_stretch) {
opts.crop_rect.top = vk_vertical_stretch;
opts.crop_rect.bottom = vk_vertical_stretch;
+7 -3
View File
@@ -13,9 +13,13 @@ extern "C"
extern bool vk_ssreadbacks;
extern bool vk_ssdither;
extern unsigned vk_overscan;
extern unsigned vk_vertical_stretch;
extern unsigned vk_downscaling_steps;
extern uint32_t vk_overscan_left;
extern uint32_t vk_overscan_right;
extern uint32_t vk_overscan_top;
extern uint32_t vk_overscan_bottom;
extern bool vk_overscan_enable;
extern uint32_t vk_vertical_stretch;
extern uint32_t vk_downscaling_steps;
extern bool vk_native_texture_lod;
extern bool vk_native_tex_rect;
extern bool vk_divot_filter, vk_gamma_dither;
+21 -1
View File
@@ -24,6 +24,26 @@
// Local Functions
//
static std::string get_name_from_headername(uint8_t name[20])
{
std::string safeName;
size_t count = 0;
// count characters from internal
// ROM name, the maximum is 20 so
// we can hardcode that safely
for (; count < 20; count++)
{
if (name[count] == '\0')
{
break;
}
}
safeName = std::string(reinterpret_cast<char*>(name), count);
return CoreConvertStringEncoding(safeName, CoreStringEncoding::Shift_JIS);
}
static std::string get_gameid_from_header(m64p_rom_header header)
{
std::string gameID;
@@ -171,7 +191,7 @@ CORE_EXPORT bool CoreGetCurrentRomHeader(CoreRomHeader& header)
header.CRC1 = ntohl(m64p_header.CRC1);
header.CRC2 = ntohl(m64p_header.CRC2);
header.CountryCode = m64p_header.Country_code;
header.Name = CoreConvertStringEncoding(std::string(reinterpret_cast<char*>(m64p_header.Name), 20), CoreStringEncoding::Shift_JIS);
header.Name = get_name_from_headername(m64p_header.Name);
header.GameID = get_gameid_from_header(m64p_header);
header.Region = get_region_from_countrycode(static_cast<char>(header.CountryCode));
header.SystemType = get_systemtype_from_countrycode(header.CountryCode);
+46 -12
View File
@@ -167,7 +167,10 @@ static l_Setting get_setting(SettingsID settingId)
setting = {SETTING_SECTION_NETPLAY, "Nickname", std::string("NetplayUser")};
break;
case SettingsID::Netplay_ServerJsonUrl:
setting = {SETTING_SECTION_NETPLAY, "ServerJsonUrl", std::string("https://m64p.s3.amazonaws.com/servers.json")};
setting = {SETTING_SECTION_NETPLAY, "ServerJsonUrl", std::string("")};
break;
case SettingsID::Netplay_DispatcherUrl:
setting = {SETTING_SECTION_NETPLAY, "DispatcherUrl", std::string("https://dispatch.gopher64.com")};
break;
case SettingsID::Netplay_SelectedServer:
setting = {SETTING_SECTION_NETPLAY, "SelectedServer", std::string("")};
@@ -497,9 +500,12 @@ static l_Setting get_setting(SettingsID settingId)
case SettingsID::KeyBinding_Fullscreen:
setting = {SETTING_SECTION_KEYBIND, "Fullscreen", std::string("Alt+Return")};
break;
case SettingsID::Keybinding_ViewLog:
case SettingsID::KeyBinding_ViewLog:
setting = {SETTING_SECTION_KEYBIND, "ViewLog", std::string("Ctrl+Alt+L")};
break;
case SettingsID::KeyBinding_ViewSearch:
setting = {SETTING_SECTION_KEYBIND, "ViewSearch", std::string("Ctrl+F")};
break;
case SettingsID::KeyBinding_GraphicsSettings:
setting = {SETTING_SECTION_KEYBIND, "GraphicsSettings", std::string("Ctrl+G")};
break;
@@ -538,7 +544,7 @@ static l_Setting get_setting(SettingsID settingId)
setting = {SETTING_SECTION_ROMBROWSER, "Recursive", true};
break;
case SettingsID::RomBrowser_MaxItems:
setting = {SETTING_SECTION_ROMBROWSER, "MaxItems", 1024};
setting = {SETTING_SECTION_ROMBROWSER, "MaxItems", 2048};
break;
case SettingsID::RomBrowser_ColumnVisibility:
setting = {SETTING_SECTION_ROMBROWSER, "ColumnVisibility", std::string("1;1;1;0;0;0;0;0;0;")};
@@ -1360,7 +1366,7 @@ static void config_listsections_callback(void*, const char* section)
l_sectionList.emplace_back(std::string(section));
}
static bool config_section_exists(std::string section)
static bool config_section_exists(const std::string& section)
{
std::string error;
m64p_error ret;
@@ -1384,7 +1390,7 @@ static bool config_section_exists(std::string section)
return std::find(l_sectionList.begin(), l_sectionList.end(), section) != l_sectionList.end();
}
static bool config_section_open(std::string section)
static bool config_section_open(const std::string& section)
{
std::string error;
m64p_error ret;
@@ -1417,7 +1423,7 @@ static void config_listkeys_callback(void*, const char* key, m64p_type)
l_keyList.push_back(std::string(key));
}
static bool config_key_exists(std::string section, std::string key)
static bool config_key_exists(const std::string& section, const std::string& key)
{
std::string error;
m64p_error ret;
@@ -1446,7 +1452,7 @@ static bool config_key_exists(std::string section, std::string key)
return std::find(l_keyList.begin(), l_keyList.end(), key) != l_keyList.end();
}
static bool config_option_set(std::string section, std::string key, m64p_type type, void *value)
static bool config_option_set(const std::string& section, const std::string& key, m64p_type type, void *value)
{
std::string error;
m64p_error ret;
@@ -1472,7 +1478,7 @@ static bool config_option_set(std::string section, std::string key, m64p_type ty
return ret == M64ERR_SUCCESS;
}
static bool config_option_get(std::string section, std::string key, m64p_type type, void *value, int size)
static bool config_option_get(const std::string& section, const std::string& key, m64p_type type, void *value, int size)
{
std::string error;
m64p_error ret;
@@ -1505,7 +1511,7 @@ static bool config_option_get(std::string section, std::string key, m64p_type ty
return ret == M64ERR_SUCCESS;
}
static bool config_option_default_set(std::string section, std::string key, m64p_type type, void *value, const char* description)
static bool config_option_default_set(const std::string& section, const std::string& key, m64p_type type, void *value, const char* description)
{
std::string error;
m64p_error ret;
@@ -1573,7 +1579,7 @@ static bool int_list_to_string(const std::vector<int>& intList, std::string& str
return true;
}
static bool string_to_int_list(std::string string, std::vector<int>& intList)
static bool string_to_int_list(const std::string& string, std::vector<int>& intList)
{
std::string error;
@@ -1628,7 +1634,7 @@ static bool string_list_to_string(const std::vector<std::string>& stringList, st
return true;
}
static bool string_to_string_list(std::string string, std::vector<std::string>& stringList)
static bool string_to_string_list(const std::string& string, std::vector<std::string>& stringList)
{
std::string error;
@@ -1675,6 +1681,10 @@ CORE_EXPORT bool CoreSettingsUpgrade(void)
std::string settingsString;
settingsVersion = CoreSettingsGetStringValue(SettingsID::GUI_Version);
if (settingsVersion.size() > 6)
{ // strip git prefix
settingsVersion = settingsVersion.substr(0, 6);
}
// we don't need to do anything
// when the core version and the settings version match
@@ -1685,7 +1695,6 @@ CORE_EXPORT bool CoreSettingsUpgrade(void)
if (settingsVersion.empty())
{ // settings version was introduced in >v0.1.5
#ifndef PORTABLE_INSTALL // only applies to non-portable installs (i.e flatpak)
// sadly v0.1.5 introduced an issue,
// in v0.1.4 the screenshot directory was set to 'Screenshots',
@@ -1716,6 +1725,31 @@ CORE_EXPORT bool CoreSettingsUpgrade(void)
}
}
if (settingsVersion == "v0.6.8" ||
settingsVersion == "v0.6.9" ||
settingsVersion == "v0.7.0" ||
settingsVersion == "v0.7.0" ||
settingsVersion == "v0.7.1" ||
settingsVersion == "v0.7.2" ||
settingsVersion == "v0.7.3" ||
settingsVersion == "v0.7.4" ||
settingsVersion == "v0.7.5" ||
settingsVersion == "v0.7.6" ||
settingsVersion == "v0.7.7" ||
settingsVersion == "v0.7.8" ||
settingsVersion == "v0.7.9")
{
// in v0.8.0 RMG switched to the dispatcher server for netplay,
// to keep support for the json file, we only empty that setting
// when it is the default value because the default json url
// no longer exists
settingsString = CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl);
if (settingsString == "https://m64p.s3.amazonaws.com/servers.json")
{
CoreSettingsSetValue(SettingsID::Netplay_ServerJsonUrl, std::string(""));
}
}
// save core version
CoreSettingsSetValue(SettingsID::GUI_Version, CoreGetVersion());
CoreSettingsSave();
+3 -1
View File
@@ -45,6 +45,7 @@ enum class SettingsID
// Netplay Settings
Netplay_Nickname,
Netplay_ServerJsonUrl,
Netplay_DispatcherUrl,
Netplay_SelectedServer,
// Core Plugin Settings
@@ -166,7 +167,8 @@ enum class SettingsID
KeyBinding_SaveStateSlot8,
KeyBinding_SaveStateSlot9,
KeyBinding_Fullscreen,
Keybinding_ViewLog,
KeyBinding_ViewLog,
KeyBinding_ViewSearch,
KeyBinding_GraphicsSettings,
KeyBinding_AudioSettings,
KeyBinding_RspSettings,
+1
View File
@@ -49,6 +49,7 @@ set(RMG_SOURCES
UserInterface/Widget/RomBrowser/RomBrowserLoadingWidget.cpp
UserInterface/Widget/RomBrowser/RomBrowserEmptyWidget.cpp
UserInterface/Widget/RomBrowser/RomBrowserEmptyWidget.ui
UserInterface/Widget/RomBrowser/RomBrowserSearchWidget.cpp
UserInterface/Widget/Render/DummyWidget.cpp
UserInterface/Widget/Render/OGLWidget.cpp
UserInterface/Widget/Render/VKWidget.cpp
@@ -338,7 +338,7 @@ void AddCheatDialog::accept(void)
QDialog::accept();
}
void AddCheatDialog::on_nameLineEdit_textChanged(QString text)
void AddCheatDialog::on_nameLineEdit_textChanged(void)
{
this->validateOkButton();
}
@@ -49,7 +49,7 @@ class AddCheatDialog : public QDialog, private Ui::AddCheatDialog
private slots:
void accept(void) Q_DECL_OVERRIDE;
void on_nameLineEdit_textChanged(QString text);
void on_nameLineEdit_textChanged(void);
void on_codeTextEdit_textChanged(void);
void on_optionsTextEdit_textChanged(void);
};
@@ -17,6 +17,8 @@
#include <QJsonDocument>
#include <QPushButton>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>
#include <QFileInfo>
#include <QFile>
@@ -39,6 +41,7 @@ CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget *parent, QWebSock
this->webSocket = webSocket;
connect(this->webSocket, &QWebSocket::textMessageReceived, this, &CreateNetplaySessionDialog::on_webSocket_textMessageReceived);
connect(this->webSocket, &QWebSocket::pong, this, &CreateNetplaySessionDialog::on_webSocket_pong);
connect(this->webSocket, &QWebSocket::connected, this, &CreateNetplaySessionDialog::on_webSocket_connected);
// prepare broadcast
broadcastSocket.bind(QHostAddress(QHostAddress::AnyIPv4), 0);
@@ -96,13 +99,26 @@ CreateNetplaySessionDialog::CreateNetplaySessionDialog(QWidget *parent, QWebSock
else if (QUrl(serverUrl).isValid())
{
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_networkAccessManager_Finished);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_jsonServerListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
networkAccessManager->get(QNetworkRequest(QUrl(serverUrl)));
}
}
this->pingTimerId = this->startTimer(2000);
QString dispatcherUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_DispatcherUrl));
if (!dispatcherUrl.isEmpty() && QUrl(dispatcherUrl).isValid())
{
this->dispatcherUrl = dispatcherUrl;
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_dispatcherRegionListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
QNetworkRequest networkRequest(QUrl(this->dispatcherUrl + "/getRegions"));
networkRequest.setRawHeader("netplay-id", "RMG");
networkAccessManager->get(networkRequest);
}
}
CreateNetplaySessionDialog::~CreateNetplaySessionDialog(void)
@@ -173,6 +189,41 @@ void CreateNetplaySessionDialog::validateCreateButton(void)
createButton->setEnabled(this->validate());
}
void CreateNetplaySessionDialog::createSession(void)
{
QList<QString> plugins = NetplayCommon::GetPluginNames(this->sessionMD5);
QJsonObject jsonFeatures;
jsonFeatures.insert("rsp_plugin", plugins[0]);
jsonFeatures.insert("gfx_plugin", plugins[1]);
QJsonObject json;
QJsonObject session;
session.insert("room_name", this->sessionNameLineEdit->text());
session.insert("password", this->passwordLineEdit->text());
session.insert("MD5", this->sessionMD5);
session.insert("game_name", this->getGameName(this->sessionGoodName, this->sessionFile));
session.insert("features", jsonFeatures);
json.insert("type", "request_create_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
}
void CreateNetplaySessionDialog::toggleUI(bool enable, bool enableCreateButton)
{
QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok);
createButton->setEnabled(enableCreateButton);
this->serverComboBox->setEnabled(enable);
this->sessionNameLineEdit->setReadOnly(!enable);
this->passwordLineEdit->setReadOnly(!enable);
this->sessionNameLineEdit->setReadOnly(!enable);
this->nickNameLineEdit->setReadOnly(!enable);
}
void CreateNetplaySessionDialog::timerEvent(QTimerEvent* event)
{
if (event->timerId() == this->pingTimerId)
@@ -199,14 +250,25 @@ void CreateNetplaySessionDialog::on_webSocket_textMessageReceived(QString messag
else
{
QtMessageBox::Error(this, "Server Error", json.value("message").toString());
this->validateCreateButton();
this->toggleUI(true, this->validate());
}
}
}
void CreateNetplaySessionDialog::on_webSocket_pong(quint64 elapsedTime, const QByteArray&)
{
this->pingLineEdit->setText(QString::number(elapsedTime) + " ms");
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->pingLineEdit->setText(QString::number(elapsedTime) + " ms");
}
}
void CreateNetplaySessionDialog::on_webSocket_connected()
{
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->createSession();
}
}
void CreateNetplaySessionDialog::on_broadcastSocket_readyRead()
@@ -215,20 +277,14 @@ void CreateNetplaySessionDialog::on_broadcastSocket_readyRead()
{
QNetworkDatagram datagram = this->broadcastSocket.receiveDatagram();
QByteArray incomingData = datagram.data();
QJsonDocument json_doc = QJsonDocument::fromJson(incomingData);
QJsonObject json = json_doc.object();
QStringList servers = json.keys();
for (int i = 0; i < servers.size(); i++)
{
this->serverComboBox->addItem(servers.at(i), json.value(servers.at(i)).toString());
}
NetplayCommon::AddServers(this->serverComboBox, QJsonDocument::fromJson(incomingData));
}
NetplayCommon::RestoreSelectedServer(this->serverComboBox);
}
void CreateNetplaySessionDialog::on_networkAccessManager_Finished(QNetworkReply* reply)
void CreateNetplaySessionDialog::on_jsonServerListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
@@ -243,6 +299,55 @@ void CreateNetplaySessionDialog::on_networkAccessManager_Finished(QNetworkReply*
reply->deleteLater();
}
void CreateNetplaySessionDialog::on_dispatcherRegionListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
QtMessageBox::Error(this, "Server Error", "Failed to retrieve region list: " + reply->errorString());
reply->deleteLater();
return;
}
NetplayCommon::AddServers(this->serverComboBox,
QJsonDocument::fromJson(reply->readAll()), true);
reply->deleteLater();
}
void CreateNetplaySessionDialog::on_dispatcherServerCreate_Finished(QNetworkReply* reply)
{
if (reply->error())
{
QtMessageBox::Error(this, "Server Error", "Failed to create server: " + reply->errorString());
reply->deleteLater();
this->toggleUI(true, this->validate());
return;
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonObject = jsonDocument.object();
if (jsonObject.empty() || jsonObject.keys().empty())
{
QtMessageBox::Error(this, "Server Error", "Failed to create server: " + jsonDocument.toJson());
reply->deleteLater();
this->toggleUI(true, this->validate());
return;
}
// first item should be an address
QString address = jsonObject[jsonObject.keys().at(0)].toString();
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
{
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
reply->deleteLater();
this->toggleUI(true, this->validate());
return;
}
reply->deleteLater();
}
void CreateNetplaySessionDialog::on_serverComboBox_currentIndexChanged(int index)
{
if (index == -1)
@@ -250,10 +355,23 @@ void CreateNetplaySessionDialog::on_serverComboBox_currentIndexChanged(int index
return;
}
this->pingLineEdit->setText("Calculating...");
bool dispatcher = NetplayCommon::IsServerDispatcher(this->serverComboBox, index);
if (this->pingTimerId != -1)
{
this->killTimer(this->pingTimerId);
this->pingTimerId = -1;
}
QString address = this->serverComboBox->itemData(index).toString();
this->webSocket->open(QUrl(address));
this->pingLineEdit->setText(dispatcher ? "N/A" : "Calculating...");
if (!dispatcher)
{
this->pingTimerId = this->startTimer(2000);
QString address = NetplayCommon::GetServerData(this->serverComboBox, index);
this->webSocket->open(QUrl(address));
}
}
void CreateNetplaySessionDialog::on_nickNameLineEdit_textChanged(void)
@@ -278,7 +396,7 @@ void CreateNetplaySessionDialog::on_romListWidget_OnRomChanged(bool valid)
void CreateNetplaySessionDialog::accept()
{
if (!this->webSocket->isValid())
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox) && !this->webSocket->isValid())
{
QtMessageBox::Error(this, "Server Error", "Connection Failed");
return;
@@ -290,29 +408,36 @@ void CreateNetplaySessionDialog::accept()
return;
}
// store these for use in createSession()
this->sessionFile = romData.File;
this->sessionMD5 = romData.MD5;
this->sessionGoodName = romData.GoodName;
// disable create button while we're processing the request
QPushButton* createButton = this->buttonBox->button(QDialogButtonBox::Ok);
createButton->setEnabled(false);
this->toggleUI(false, false);
this->sessionFile = romData.File;
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &CreateNetplaySessionDialog::on_dispatcherServerCreate_Finished);
networkAccessManager->setTransferTimeout(120000);
QList<QString> plugins = NetplayCommon::GetPluginNames(romData.MD5);
QUrl url(this->dispatcherUrl + "/createServer");
QJsonObject jsonFeatures;
jsonFeatures.insert("rsp_plugin", plugins[0]);
jsonFeatures.insert("gfx_plugin", plugins[1]);
QUrlQuery urlQuery;
urlQuery.addQueryItem("region", NetplayCommon::GetServerData(this->serverComboBox));
url.setQuery(urlQuery);
QJsonObject json;
QJsonObject session;
session.insert("room_name", this->sessionNameLineEdit->text());
session.insert("password", this->passwordLineEdit->text());
session.insert("MD5", romData.MD5);
session.insert("game_name", this->getGameName(romData.GoodName, romData.File));
session.insert("features", jsonFeatures);
json.insert("type", "request_create_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("netplay-id", "RMG");
networkAccessManager->get(networkRequest);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
// creating a server can take a while,
// so show a loading screen
this->romListWidget->ShowLoading();
}
else
{
this->createSession();
}
}
@@ -45,20 +45,32 @@ class CreateNetplaySessionDialog : public QDialog, private Ui::CreateNetplaySess
QJsonObject sessionJson;
QString sessionFile;
QString sessionMD5;
QString sessionGoodName;
QString dispatcherUrl;
QString getGameName(QString goodName, QString file);
bool validate(void);
void validateCreateButton(void);
void createSession(void);
void toggleUI(bool enable, bool enableCreateButton);
protected:
void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE;
private slots:
void on_webSocket_textMessageReceived(QString message);
void on_webSocket_pong(quint64 elapsedTime, const QByteArray&);
void on_webSocket_connected(void);
void on_broadcastSocket_readyRead(void);
void on_networkAccessManager_Finished(QNetworkReply* reply);
void on_jsonServerListDownload_Finished(QNetworkReply* reply);
void on_dispatcherRegionListDownload_Finished(QNetworkReply* reply);
void on_dispatcherServerCreate_Finished(QNetworkReply* reply);
void on_serverComboBox_currentIndexChanged(int index);
@@ -31,7 +31,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<widget class="QLabel" name="pingLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -11,8 +11,11 @@
#include <QCryptographicHash>
#include <QByteArray>
#include <QJsonArray>
#include <QFileInfo>
#include <QDateTime>
#include <QHostInfo>
#include <QUrl>
#include <RMG-Core/Settings.hpp>
#include <RMG-Core/Plugins.hpp>
@@ -20,6 +23,9 @@
using namespace NetplayCommon;
// register type with qt
Q_DECLARE_METATYPE(NetplayServerData);
void NetplayCommon::AddCommonJson(QJsonObject& json)
{
QCryptographicHash hash = QCryptographicHash(QCryptographicHash::Sha256);
@@ -81,14 +87,36 @@ QList<QString> NetplayCommon::GetPluginNames(QString md5QString)
return pluginNames;
}
void NetplayCommon::AddServers(QComboBox* comboBox, QJsonDocument document)
void NetplayCommon::AddServers(QComboBox* comboBox, QJsonDocument document, bool dispatcher)
{
QJsonObject jsonObject = document.object();
QStringList jsonServers = jsonObject.keys();
for (int i = 0; i < jsonServers.size(); i++)
if (dispatcher)
{
comboBox->addItem(jsonServers.at(i), jsonObject.value(jsonServers.at(i)).toString());
QJsonArray jsonServers = document.array();
for (int i = 0; i < jsonServers.size(); i++)
{
comboBox->addItem(jsonServers.at(i).toString(),
QVariant::fromValue<NetplayServerData>(
{
dispatcher,
jsonServers.at(i).toString()
}));
}
}
else
{
QJsonObject jsonObject = document.object();
QStringList jsonServers = jsonObject.keys();
for (int i = 0; i < jsonServers.size(); i++)
{
comboBox->addItem(jsonServers.at(i),
QVariant::fromValue<NetplayServerData>(
{
dispatcher,
jsonObject.value(jsonServers.at(i)).toString()
}));
}
}
NetplayCommon::RestoreSelectedServer(comboBox);
@@ -108,3 +136,43 @@ void NetplayCommon::RestoreSelectedServer(QComboBox* comboBox)
comboBox->setCurrentIndex(index);
}
}
bool NetplayCommon::IsServerDispatcher(QComboBox* comboBox, int index)
{
if (index == -1)
{
index = comboBox->currentIndex();
}
return comboBox->itemData(index).value<NetplayServerData>().Dispatcher;
}
QString NetplayCommon::GetServerData(QComboBox* comboBox, int index)
{
if (index == -1)
{
index = comboBox->currentIndex();
}
return comboBox->itemData(index).value<NetplayServerData>().Data;
}
bool NetplayCommon::ConnectToIPv4Server(QString address, QWebSocket* webSocket)
{
QUrl addressUrl(address);
QHostInfo hostInfo = QHostInfo::fromName(addressUrl.host());
for (const QHostAddress &resolvedAddr : hostInfo.addresses())
{
// mupen64plus-core only supports IPv4 (due to SDL2_net)
if (resolvedAddr.protocol() == QAbstractSocket::IPv4Protocol)
{
addressUrl.setHost(resolvedAddr.toString());
webSocket->open(addressUrl);
return true;
}
}
webSocket->close();
return false;
}
@@ -12,11 +12,20 @@
#include <QJsonDocument>
#include <QJsonObject>
#ifdef NETPLAY
#include <QWebSocket>
#endif // NETPLAY
#include <QComboBox>
#include <QString>
namespace NetplayCommon
{
struct NetplayServerData
{
bool Dispatcher;
QString Data;
};
#define NETPLAYCOMMON_SESSION_REGEX "[a-zA-Z0-9 ]+"
#define NETPLAYCOMMON_NICKNAME_REGEX "[a-zA-Z0-9]+"
#define NETPLAYCOMMON_PASSWORD_REGEX "[a-zA-Z0-9,.\\/<>?;:[\\]{}\\-=_+`~!@#$%^&*()]+"
@@ -28,10 +37,21 @@ namespace NetplayCommon
QList<QString> GetPluginNames(QString md5QString);
// Adds servers from json to combobox
void AddServers(QComboBox* comboBox, QJsonDocument document);
void AddServers(QComboBox* comboBox, QJsonDocument document, bool dispatcher = false);
// Restores previously selected server
void RestoreSelectedServer(QComboBox* comboBox);
// Returns whether server from comboBox is dispatcher
bool IsServerDispatcher(QComboBox* comboBox, int index = -1);
// Returns server data from comboBox
QString GetServerData(QComboBox* comboBox, int index = -1);
#ifdef NETPLAY
// Attempts to connect the web socket to the given address using IPv4
bool ConnectToIPv4Server(QString address, QWebSocket* webSocket);
#endif // #ifdef NETPLAY
}
#endif // NETPLAYCOMMON_HPP
@@ -21,6 +21,7 @@
#include <QFileDialog>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>
#include <QFile>
#include <RMG-Core/Settings.hpp>
@@ -70,6 +71,11 @@ NetplaySessionBrowserDialog::NetplaySessionBrowserDialog(QWidget *parent, QWebSo
this->nickNameLineEdit->setValidator(new QRegularExpressionValidator(re, this));
this->nickNameLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_Nickname)));
// configure dispatcher network access manager
this->dispatcherNetworkAccessManager = new QNetworkAccessManager(this);
this->dispatcherNetworkAccessManager->setTransferTimeout(15000);
connect(this->dispatcherNetworkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_dispatcherRetrieveServers_Finished);
// request server list
QString serverUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl));
if (!serverUrl.isEmpty())
@@ -90,15 +96,28 @@ NetplaySessionBrowserDialog::NetplaySessionBrowserDialog(QWidget *parent, QWebSo
else if (QUrl(serverUrl).isValid())
{
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_networkAccessManager_Finished);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_jsonServerListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
networkAccessManager->get(QNetworkRequest(QUrl(serverUrl)));
}
}
this->validateJoinButton();
QString dispatcherUrl = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_DispatcherUrl));
if (!dispatcherUrl.isEmpty() && QUrl(dispatcherUrl).isValid())
{
this->dispatcherUrl = dispatcherUrl;
this->pingTimerId = this->startTimer(2000);
QNetworkAccessManager* networkAccessManager = new QNetworkAccessManager(this);
connect(networkAccessManager, &QNetworkAccessManager::finished, this, &NetplaySessionBrowserDialog::on_dispatcherRegionListDownload_Finished);
networkAccessManager->setTransferTimeout(15000);
QNetworkRequest networkRequest(QUrl(this->dispatcherUrl + "/getRegions"));
networkRequest.setRawHeader("netplay-id", "RMG");
networkAccessManager->get(networkRequest);
}
this->validateJoinButton();
}
NetplaySessionBrowserDialog::~NetplaySessionBrowserDialog(void)
@@ -179,6 +198,79 @@ void NetplaySessionBrowserDialog::validateJoinButton(void)
joinButton->setEnabled(this->validate());
}
void NetplaySessionBrowserDialog::toggleUI(bool enable, bool enableJoinButton)
{
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(enableJoinButton);
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
refreshButton->setEnabled(enable);
this->serverComboBox->setEnabled(enable);
this->nickNameLineEdit->setReadOnly(!enable);
}
void NetplaySessionBrowserDialog::refreshSessions(void)
{
// disable refresh button while refreshing
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
refreshButton->setEnabled(false);
// disable join button while refreshing
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(false);
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->on_serverComboBox_currentIndexChanged(this->serverComboBox->currentIndex());
}
else
{
this->on_webSocket_connected();
}
}
void NetplaySessionBrowserDialog::joinSession(void)
{
QJsonObject json;
QJsonObject session;
session.insert("port", sessionData.Port);
session.insert("password", this->sessionPassword);
session.insert("MD5", this->sessionData.MD5);
json.insert("type", "request_join_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
}
void NetplaySessionBrowserDialog::resetDispatcherState(void)
{
if (this->dispatcherTimerId != -1)
{
this->killTimer(this->dispatcherTimerId);
this->dispatcherTimerId = -1;
}
if (this->dispatcherTimeoutTimerId != -1)
{
this->killTimer(this->dispatcherTimeoutTimerId);
this->dispatcherTimeoutTimerId = -1;
}
if (this->dispatcherNetworkReply != nullptr)
{
this->dispatcherNetworkReply->abort();
this->dispatcherNetworkReply = nullptr;
}
this->dispatcherMoveThroughList = false;
this->dispatcherJoinSession = false;
this->dispatcherAddressListIndex = false;
}
void NetplaySessionBrowserDialog::timerEvent(QTimerEvent *event)
{
if (event->timerId() == this->pingTimerId)
@@ -188,6 +280,49 @@ void NetplaySessionBrowserDialog::timerEvent(QTimerEvent *event)
this->webSocket->ping();
}
}
else if (event->timerId() == this->dispatcherTimerId)
{
if (this->dispatcherMoveThroughList)
{
this->dispatcherAddressListIndex++;
if (this->dispatcherAddressListIndex < this->dispatcherAddressList.size())
{
QString address = this->dispatcherAddressList.at(this->dispatcherAddressListIndex);
if (!NetplayCommon::ConnectToIPv4Server(address, this->webSocket))
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Failed to find IPv4 address of server");
}
this->dispatcherMoveThroughList = false;
// start socket timeout
if (this->dispatcherTimeoutTimerId != -1)
{
this->killTimer(this->dispatcherTimeoutTimerId);
this->dispatcherTimeoutTimerId = -1;
}
this->dispatcherTimeoutTimerId = this->startTimer(15000);
}
else
{
this->sessionBrowserWidget->RefreshDone();
this->resetDispatcherState();
}
}
}
else if (event->timerId() == this->dispatcherTimeoutTimerId)
{
this->webSocket->close();
this->dispatcherMoveThroughList = true;
this->killTimer(this->dispatcherTimeoutTimerId);
this->dispatcherTimeoutTimerId = -1;
}
}
void NetplaySessionBrowserDialog::on_webSocket_connected(void)
@@ -198,16 +333,20 @@ void NetplaySessionBrowserDialog::on_webSocket_connected(void)
return;
}
// disable refresh button while refreshing
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
refreshButton->setEnabled(false);
bool dispatcher = NetplayCommon::IsServerDispatcher(this->serverComboBox);
// disable join button while refreshing
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(false);
if (dispatcher && this->dispatcherJoinSession)
{
this->joinSession();
this->dispatcherJoinSession = false;
return;
}
// clear sessions
this->sessionBrowserWidget->StartRefresh();
// clear sessions when we're not using the dispatcher server
if (!dispatcher)
{
this->sessionBrowserWidget->StartRefresh();
}
// request session list from server
QJsonObject json;
@@ -241,11 +380,15 @@ void NetplaySessionBrowserDialog::on_webSocket_textMessageReceived(QString messa
session.value("port").toInt(),
session.value("features").toObject().value("cpu_emulator").toString(),
session.value("features").toObject().value("rsp_plugin").toString(),
session.value("features").toObject().value("gfx_plugin").toString());
session.value("features").toObject().value("gfx_plugin").toString(),
this->webSocket->requestUrl().toString());
}
// we're done refreshing the sessions
this->sessionBrowserWidget->RefreshDone();
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->sessionBrowserWidget->RefreshDone();
}
}
else
{
@@ -263,19 +406,27 @@ void NetplaySessionBrowserDialog::on_webSocket_textMessageReceived(QString messa
else
{
QtMessageBox::Error(this, "Server Error", json.value("message").toString());
this->validateJoinButton();
this->toggleUI(true, this->validate());
}
}
this->dispatcherMoveThroughList = true;
}
void NetplaySessionBrowserDialog::on_webSocket_pong(quint64 elapsedTime, const QByteArray&)
{
this->pingLineEdit->setText(QString::number(elapsedTime) + " ms");
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->pingLineEdit->setText(QString::number(elapsedTime) + " ms");
}
}
void NetplaySessionBrowserDialog::on_webSocket_disconnected()
{
this->sessionBrowserWidget->Reset();
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->sessionBrowserWidget->Reset();
}
}
void NetplaySessionBrowserDialog::on_broadcastSocket_readyRead(void)
@@ -284,20 +435,14 @@ void NetplaySessionBrowserDialog::on_broadcastSocket_readyRead(void)
{
QNetworkDatagram datagram = this->broadcastSocket.receiveDatagram();
QByteArray incomingData = datagram.data();
QJsonDocument json_doc = QJsonDocument::fromJson(incomingData);
QJsonObject json = json_doc.object();
QStringList servers = json.keys();
for (int i = 0; i < servers.size(); i++)
{
this->serverComboBox->addItem(servers.at(i), json.value(servers.at(i)).toString());
}
NetplayCommon::AddServers(this->serverComboBox, QJsonDocument::fromJson(incomingData));
}
NetplayCommon::RestoreSelectedServer(this->serverComboBox);
}
void NetplaySessionBrowserDialog::on_networkAccessManager_Finished(QNetworkReply* reply)
void NetplaySessionBrowserDialog::on_jsonServerListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
@@ -313,6 +458,61 @@ void NetplaySessionBrowserDialog::on_networkAccessManager_Finished(QNetworkReply
reply->deleteLater();
}
void NetplaySessionBrowserDialog::on_dispatcherRegionListDownload_Finished(QNetworkReply* reply)
{
if (reply->error())
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Server Error", "Failed to retrieve region list: " + reply->errorString());
reply->deleteLater();
return;
}
NetplayCommon::AddServers(this->serverComboBox,
QJsonDocument::fromJson(reply->readAll()), true);
reply->deleteLater();
}
void NetplaySessionBrowserDialog::on_dispatcherRetrieveServers_Finished(QNetworkReply* reply)
{
// don't show an error when the request has been aborted
if (reply->error() == QNetworkReply::OperationCanceledError)
{
reply->deleteLater();
return;
}
if (reply->error())
{
this->sessionBrowserWidget->Reset();
QtMessageBox::Error(this, "Server Error", "Failed to retrieve server list: " + reply->errorString());
reply->deleteLater();
return;
}
QJsonDocument jsonDocument = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsonObject = jsonDocument.object();
QStringList jsonKeys = jsonObject.keys();
this->sessionBrowserWidget->StartRefresh();
this->resetDispatcherState();
// we only care about the addresses
this->dispatcherAddressList.clear();
for (int i = 0; i < jsonKeys.size(); i++)
{
QString address = jsonObject[jsonKeys.at(i)].toString();
this->dispatcherAddressList.append(address);
}
this->dispatcherAddressListIndex = -1;
this->dispatcherMoveThroughList = true;
this->dispatcherTimerId = this->startTimer(250);
reply->deleteLater();
}
void NetplaySessionBrowserDialog::on_serverComboBox_currentIndexChanged(int index)
{
QPushButton* refreshButton = this->buttonBox->button(QDialogButtonBox::RestoreDefaults);
@@ -323,12 +523,48 @@ void NetplaySessionBrowserDialog::on_serverComboBox_currentIndexChanged(int inde
return;
}
this->pingLineEdit->setText("Calculating...");
bool dispatcher = NetplayCommon::IsServerDispatcher(this->serverComboBox, index);
if (this->pingTimerId != -1)
{
this->killTimer(this->pingTimerId);
this->pingTimerId = -1;
}
this->resetDispatcherState();
this->pingLineEdit->setText(dispatcher ? "N/A" : "Calculating...");
this->sessionBrowserWidget->StartRefresh();
QString address = this->serverComboBox->itemData(index).toString();
this->webSocket->open(QUrl(address));
if (dispatcher)
{
QString region = NetplayCommon::GetServerData(this->serverComboBox, index);
QUrl url(this->dispatcherUrl + "/getServers");
QUrlQuery urlQuery;
urlQuery.addQueryItem("region", region);
url.setQuery(urlQuery);
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("netplay-id", "RMG");
// sadly we have to force HTTP/1 here due to a Qt bug,
// we abort the connection which sadly in Qt6 causes an error and eventually segfault,
// so to workaround this, force HTTP/1 until it's fixed in Qt6
//
// thank you MapLibre devs for figuring out this issue:
// https://github.com/maplibre/maplibre-native/issues/3644
networkRequest.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
this->dispatcherNetworkReply = this->dispatcherNetworkAccessManager->get(networkRequest);
}
else
{
this->pingTimerId = this->startTimer(2000);
QString address = NetplayCommon::GetServerData(this->serverComboBox, index);
this->webSocket->open(QUrl(address));
}
}
void NetplaySessionBrowserDialog::on_sessionBrowserWidget_OnSessionChanged(bool valid)
@@ -353,13 +589,14 @@ void NetplaySessionBrowserDialog::on_buttonBox_clicked(QAbstractButton* button)
// refresh session list when refresh button has been pressed
if (button == this->buttonBox->button(QDialogButtonBox::RestoreDefaults))
{
this->on_webSocket_connected();
this->refreshSessions();
}
}
void NetplaySessionBrowserDialog::accept()
{
if (!this->webSocket->isValid())
if (!NetplayCommon::IsServerDispatcher(this->serverComboBox) &&
!this->webSocket->isValid())
{
QtMessageBox::Error(this, "Server Error", "Connection Failed");
return;
@@ -373,21 +610,19 @@ void NetplaySessionBrowserDialog::accept()
}
// disable join button while we're processing the request
QPushButton* joinButton = this->buttonBox->button(QDialogButtonBox::Ok);
joinButton->setEnabled(false);
this->toggleUI(false, false);
// request password when needed
QString password;
if (sessionData.PasswordProtected)
{
NetplaySessionPasswordDialog dialog(this);
dialog.exec();
password = dialog.GetPassword();
this->sessionPassword = dialog.GetPassword();
// do nothing if password is empty
if (password.isEmpty())
if (this->sessionPassword.isEmpty())
{
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
}
@@ -400,7 +635,7 @@ void NetplaySessionBrowserDialog::accept()
details = "Expected RSP Plugin: " + sessionData.RspPlugin + "\n";
details += "Current RSP Plugin: " + pluginNames[0];
QtMessageBox::Error(this, "RSP Plugin Mismatch", details);
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
if (sessionData.GfxPlugin != pluginNames[1])
@@ -408,7 +643,7 @@ void NetplaySessionBrowserDialog::accept()
details = "Expected GFX Plugin: " + sessionData.GfxPlugin + "\n";
details += "Current GFX Plugin: " + pluginNames[1];
QtMessageBox::Error(this, "GFX Plugin Mismatch", details);
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
@@ -430,21 +665,20 @@ void NetplaySessionBrowserDialog::accept()
this->sessionFile = this->showROMDialog(sessionData.GameName, sessionData.MD5);
if (this->sessionFile.isEmpty())
{
joinButton->setEnabled(true);
this->toggleUI(true, true);
return;
}
}
QJsonObject json;
QJsonObject session;
session.insert("port", sessionData.Port);
session.insert("password", password);
session.insert("MD5", QString::fromStdString(md5));
this->sessionData = sessionData;
json.insert("type", "request_join_room");
json.insert("player_name", this->nickNameLineEdit->text());
json.insert("room", session);
NetplayCommon::AddCommonJson(json);
this->webSocket->sendTextMessage(QJsonDocument(json).toJson());
if (NetplayCommon::IsServerDispatcher(this->serverComboBox))
{
this->dispatcherJoinSession = true;
this->webSocket->open(sessionData.Address);
}
else
{
this->joinSession();
}
}
@@ -43,15 +43,35 @@ class NetplaySessionBrowserDialog : public QDialog, private Ui::NetplaySessionBr
QUdpSocket broadcastSocket;
QJsonObject sessionJson;
QString sessionFile;
QString sessionPassword;
NetplaySessionData sessionData;
QMap<QString, CoreRomSettings> romData;
int pingTimerId = -1;
int dispatcherTimerId = -1;
int dispatcherTimeoutTimerId = -1;
bool dispatcherMoveThroughList = false;
bool dispatcherJoinSession = false;
QNetworkAccessManager* dispatcherNetworkAccessManager = nullptr;
QNetworkReply* dispatcherNetworkReply = nullptr;
QString dispatcherUrl;
QStringList dispatcherAddressList;
int dispatcherAddressListIndex = 0;
QString showROMDialog(QString name, QString md5);
bool validate(void);
void validateJoinButton(void);
void toggleUI(bool enable, bool enableJoinButton);
void refreshSessions(void);
void joinSession(void);
void resetDispatcherState(void);
protected:
void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE;
@@ -62,7 +82,10 @@ class NetplaySessionBrowserDialog : public QDialog, private Ui::NetplaySessionBr
void on_webSocket_disconnected(void);
void on_broadcastSocket_readyRead(void);
void on_networkAccessManager_Finished(QNetworkReply* reply);
void on_jsonServerListDownload_Finished(QNetworkReply* reply);
void on_dispatcherRegionListDownload_Finished(QNetworkReply* reply);
void on_dispatcherRetrieveServers_Finished(QNetworkReply* reply);
void on_serverComboBox_currentIndexChanged(int index);
void on_sessionBrowserWidget_OnSessionChanged(bool valid);
@@ -52,7 +52,7 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="pingLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -141,7 +141,7 @@ void NetplaySessionDialog::updateCheatsTreeWidget(void)
CheatsCommon::AddCheatsToTreeWidget(true, cheatsArray, this->sessionFile, cheats, this->cheatsTreeWidget, true);
}
void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message)
void NetplaySessionDialog::on_webSocket_textMessageReceived(const QString& message)
{
QJsonDocument jsonDocument = QJsonDocument::fromJson(message.toUtf8());
QJsonObject json = jsonDocument.object();
@@ -225,7 +225,7 @@ void NetplaySessionDialog::on_webSocket_textMessageReceived(QString message)
}
}
void NetplaySessionDialog::on_chatLineEdit_textChanged(QString text)
void NetplaySessionDialog::on_chatLineEdit_textChanged(const QString& text)
{
this->sendPushButton->setEnabled(!text.startsWith(' ') && !text.trimmed().isEmpty() && text.size() <= 256);
this->sendPushButton->setDefault(this->sendPushButton->isEnabled());
@@ -47,9 +47,9 @@ class NetplaySessionDialog : public QDialog, private Ui::NetplaySessionDialog
void updateCheatsTreeWidget(void);
private slots:
void on_webSocket_textMessageReceived(QString message);
void on_webSocket_textMessageReceived(const QString& message);
void on_chatLineEdit_textChanged(QString text);
void on_chatLineEdit_textChanged(const QString& text);
void on_sendPushButton_clicked(void);
void on_buttonBox_clicked(QAbstractButton* button);
@@ -489,6 +489,10 @@ void SettingsDialog::loadInterfaceGeneralSettings(void)
this->themeComboBox->addItem(fileInfo.fileName());
}
#ifdef _WIN32
this->themeComboBox->insertItem(1,"Windows Vista");
#endif
// select currently chosen theme in UI
this->themeComboBox->setCurrentText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::GUI_Theme)));
this->iconThemeComboBox->setCurrentText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::GUI_IconTheme)));
@@ -558,6 +562,7 @@ void SettingsDialog::loadInterfaceNetplaySettings(void)
this->netplayNicknameLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_Nickname)));
this->netplayServerUrlLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_ServerJsonUrl)));
this->netplayDispatcherUrlLineEdit->setText(QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Netplay_DispatcherUrl)));
}
void SettingsDialog::loadDefaultCoreSettings(void)
@@ -732,6 +737,7 @@ void SettingsDialog::loadDefaultInterfaceNetplaySettings(void)
{
this->netplayNicknameLineEdit->setText(QString::fromStdString(CoreSettingsGetDefaultStringValue(SettingsID::Netplay_Nickname)));
this->netplayServerUrlLineEdit->setText(QString::fromStdString(CoreSettingsGetDefaultStringValue(SettingsID::Netplay_ServerJsonUrl)));
this->netplayDispatcherUrlLineEdit->setText(QString::fromStdString(CoreSettingsGetDefaultStringValue(SettingsID::Netplay_DispatcherUrl)));
}
void SettingsDialog::saveSettings(void)
@@ -976,6 +982,7 @@ void SettingsDialog::saveInterfaceNetplaySettings(void)
{
CoreSettingsSetValue(SettingsID::Netplay_Nickname, this->netplayNicknameLineEdit->text().toStdString());
CoreSettingsSetValue(SettingsID::Netplay_ServerJsonUrl, this->netplayServerUrlLineEdit->text().toStdString());
CoreSettingsSetValue(SettingsID::Netplay_DispatcherUrl, this->netplayDispatcherUrlLineEdit->text().toStdString());
}
void SettingsDialog::commonHotkeySettings(SettingsDialogAction action)
@@ -1057,8 +1064,9 @@ void SettingsDialog::commonHotkeySettings(SettingsDialogAction action)
std::vector<keybinding> keybindings_View =
{
{ this->fullscreenKeyButton, SettingsID::KeyBinding_Fullscreen },
{ this->logKeyButton, SettingsID::Keybinding_ViewLog },
{ this->refreshRomListKeyButton, SettingsID::KeyBinding_RefreshROMList }
{ this->logKeyButton, SettingsID::KeyBinding_ViewLog },
{ this->refreshRomListKeyButton, SettingsID::KeyBinding_RefreshROMList },
{ this->searchKeyButton, SettingsID::KeyBinding_ViewSearch }
};
@@ -665,6 +665,27 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_21">
<item>
<widget class="QLabel" name="label_18">
<property name="text">
<string>Dispatcher URL</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="netplayDispatcherUrlLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_19">
<property name="orientation">
@@ -1681,6 +1702,24 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_122">
<item>
<widget class="QLabel" name="label_119">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
<item>
<widget class="KeybindButton" name="searchKeyButton">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_16">
<property name="orientation">
+18 -1
View File
@@ -330,6 +330,12 @@ void MainWindow::configureTheme(QApplication* app)
{
// do nothing
}
#ifdef _WIN32
else if (theme == "Windows Vista")
{
app->setStyle(QStyleFactory::create("WindowsVista"));
}
#endif
else if (theme == "Fusion")
{
app->setPalette(QApplication::style()->standardPalette());
@@ -837,6 +843,8 @@ void MainWindow::updateActions(bool inEmulation, bool isPaused)
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Settings));
this->action_Settings_Settings->setShortcut(QKeySequence(keyBinding));
this->action_View_GameList->setEnabled(!inEmulation);
this->action_View_GameGrid->setEnabled(!inEmulation);
this->action_View_UniformSize->setEnabled(!inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_Fullscreen));
this->action_View_Fullscreen->setEnabled(inEmulation);
@@ -844,9 +852,12 @@ void MainWindow::updateActions(bool inEmulation, bool isPaused)
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_RefreshROMList));
this->action_View_RefreshRoms->setEnabled(!inEmulation);
this->action_View_RefreshRoms->setShortcut(QKeySequence(keyBinding));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::Keybinding_ViewLog));
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_ViewLog));
this->action_View_Log->setShortcut(QKeySequence(keyBinding));
this->action_View_ClearRomCache->setEnabled(!inEmulation);
keyBinding = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::KeyBinding_ViewSearch));
this->action_View_Search->setShortcut(QKeySequence(keyBinding));
this->action_View_Search->setEnabled(!inEmulation);
#ifdef NETPLAY
this->action_Netplay_CreateSession->setEnabled(!inEmulation && this->netplaySessionDialog == nullptr);
@@ -1180,6 +1191,7 @@ void MainWindow::connectActionSignals(void)
connect(this->action_View_RefreshRoms, &QAction::triggered, this, &MainWindow::on_Action_View_RefreshRoms);
connect(this->action_View_ClearRomCache, &QAction::triggered, this, &MainWindow::on_Action_View_ClearRomCache);
connect(this->action_View_Log, &QAction::triggered, this, &MainWindow::on_Action_View_Log);
connect(this->action_View_Search, &QAction::triggered, this, &MainWindow::on_Action_View_Search);
connect(this->action_Netplay_CreateSession, &QAction::triggered, this, &MainWindow::on_Action_Netplay_CreateSession);
connect(this->action_Netplay_BrowseSessions, &QAction::triggered, this, &MainWindow::on_Action_Netplay_BrowseSessions);
@@ -1952,6 +1964,11 @@ void MainWindow::on_Action_View_Log(void)
this->logDialog.show();
}
void MainWindow::on_Action_View_Search(void)
{
this->ui_Widget_RomBrowser->SetToggleSearch();
}
void MainWindow::on_Action_Netplay_CreateSession(void)
{
#ifdef NETPLAY
+1
View File
@@ -210,6 +210,7 @@ class MainWindow : public QMainWindow, private Ui::MainWindow
void on_Action_View_RefreshRoms(void);
void on_Action_View_ClearRomCache(void);
void on_Action_View_Log(void);
void on_Action_View_Search(void);
void on_Action_Netplay_CreateSession(void);
void on_Action_Netplay_BrowseSessions(void);
+10
View File
@@ -137,6 +137,8 @@
<addaction name="action_View_ClearRomCache"/>
<addaction name="separator"/>
<addaction name="action_View_Log"/>
<addaction name="separator"/>
<addaction name="action_View_Search"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
@@ -633,6 +635,14 @@
<string>View Session</string>
</property>
</action>
<action name="action_View_Search">
<property name="icon">
<iconset theme="search-line"/>
</property>
<property name="text">
<string>Search</string>
</property>
</action>
</widget>
<resources>
<include location="UIResources.qrc"/>
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z"></path></svg>

After

Width:  |  Height:  |  Size: 489 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M18.031 16.6168L22.3137 20.8995L20.8995 22.3137L16.6168 18.031C15.0769 19.263 13.124 20 11 20C6.032 20 2 15.968 2 11C2 6.032 6.032 2 11 2C15.968 2 20 6.032 20 11C20 13.124 19.263 15.0769 18.031 16.6168ZM16.0247 15.8748C17.2475 14.6146 18 12.8956 18 11C18 7.1325 14.8675 4 11 4C7.1325 4 4 7.1325 4 11C4 14.8675 7.1325 18 11 18C12.8956 18 14.6146 17.2475 15.8748 16.0247L16.0247 15.8748Z" fill="#ffffff"></path></svg>

After

Width:  |  Height:  |  Size: 504 B

+2
View File
@@ -31,6 +31,7 @@
<file alias="icons/black/svg/shut-down-line.svg">Resource/icons/black/svg/shut-down-line.svg</file>
<file alias="icons/black/svg/speed-line.svg">Resource/icons/black/svg/speed-line.svg</file>
<file alias="icons/black/svg/tools-line.svg">Resource/icons/black/svg/tools-line.svg</file>
<file alias="icons/black/svg/search-line.svg">Resource/icons/black/svg/search-line.svg</file>
<file alias="icons/black/svg/server-line.svg">Resource/icons/black/svg/server-line.svg</file>
<file alias="icons/black/svg/settings-3-line.svg">Resource/icons/black/svg/settings-3-line.svg</file>
<file alias="icons/black/svg/volume-up-line.svg">Resource/icons/black/svg/volume-up-line.svg</file>
@@ -63,6 +64,7 @@
<file alias="icons/white/svg/shut-down-line.svg">Resource/icons/white/svg/shut-down-line.svg</file>
<file alias="icons/white/svg/speed-line.svg">Resource/icons/white/svg/speed-line.svg</file>
<file alias="icons/white/svg/tools-line.svg">Resource/icons/white/svg/tools-line.svg</file>
<file alias="icons/white/svg/search-line.svg">Resource/icons/white/svg/search-line.svg</file>
<file alias="icons/white/svg/server-line.svg">Resource/icons/white/svg/server-line.svg</file>
<file alias="icons/white/svg/settings-3-line.svg">Resource/icons/white/svg/settings-3-line.svg</file>
<file alias="icons/white/svg/volume-up-line.svg">Resource/icons/white/svg/volume-up-line.svg</file>
@@ -26,6 +26,11 @@ CreateNetplaySessionWidget::CreateNetplaySessionWidget(QWidget* parent) : QStack
this->emptyWidget = new Widget::CreateNetplaySessionEmptyWidget(this);
this->addWidget(this->emptyWidget);
// configure loading widget
this->loadingWidget = new Widget::NetplaySessionBrowserLoadingWidget(this, "Creating server");
this->loadingWidget->SetWidgetIndex(this->addWidget(this->loadingWidget));
connect(this, &QStackedWidget::currentChanged, this->loadingWidget, &NetplaySessionBrowserLoadingWidget::on_NetplaySessionBrowserWidget_currentChanged);
// configure list widget
this->listWidget = new QListWidget(this);
this->addWidget(this->listWidget);
@@ -66,6 +71,11 @@ void CreateNetplaySessionWidget::RefreshDone(void)
}
}
void CreateNetplaySessionWidget::ShowLoading(void)
{
this->setCurrentWidget(this->loadingWidget);
}
bool CreateNetplaySessionWidget::IsCurrentRomValid()
{
return this->currentWidget() == this->listWidget &&
@@ -10,6 +10,7 @@
#ifndef CREATENETPLAYSESSIONWIDGET_HPP
#define CREATENETPLAYSESSIONWIDGET_HPP
#include "NetplaySessionBrowserLoadingWidget.hpp"
#include "CreateNetplaySessionEmptyWidget.hpp"
#include <QStackedWidget>
@@ -36,14 +37,15 @@ class CreateNetplaySessionWidget : public QStackedWidget
~CreateNetplaySessionWidget(void);
void AddRomData(QString goodName, QString md5, QString file);
void RefreshDone(void);
void ShowLoading(void);
bool IsCurrentRomValid(void);
bool GetCurrentRom(NetplayRomData& data);
private:
Widget::CreateNetplaySessionEmptyWidget* emptyWidget = nullptr;
Widget::NetplaySessionBrowserLoadingWidget* loadingWidget = nullptr;
QListWidget* listWidget = nullptr;
@@ -15,12 +15,13 @@
using namespace UserInterface::Widget;
NetplaySessionBrowserLoadingWidget::NetplaySessionBrowserLoadingWidget(QWidget* parent) : QWidget(parent)
NetplaySessionBrowserLoadingWidget::NetplaySessionBrowserLoadingWidget(QWidget* parent, QString loadingText) : QWidget(parent)
{
QHBoxLayout* layout = new QHBoxLayout(this);
this->baseLoadingText = loadingText;
this->loadingLabel = new QLabel(this);
this->loadingLabel->setText("Loading");
this->loadingLabel->setText(this->baseLoadingText);
this->loadingLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
layout->addWidget(loadingLabel);
@@ -66,7 +67,7 @@ void NetplaySessionBrowserLoadingWidget::on_NetplaySessionBrowserWidget_currentC
void NetplaySessionBrowserLoadingWidget::updateLoadingText()
{
QString loadingText = "Loading";
QString loadingText = this->baseLoadingText;
if (dotCount <= 3)
{
@@ -22,7 +22,7 @@ class NetplaySessionBrowserLoadingWidget : public QWidget
{
Q_OBJECT
public:
NetplaySessionBrowserLoadingWidget(QWidget* parent);
NetplaySessionBrowserLoadingWidget(QWidget* parent, QString loadingText = "Loading");
~NetplaySessionBrowserLoadingWidget();
void SetWidgetIndex(int index);
@@ -31,6 +31,7 @@ public slots:
void on_NetplaySessionBrowserWidget_currentChanged(int index);
private:
QString baseLoadingText;
QLabel* loadingLabel;
int loadingLabelTimerId = -1;
int widgetIndex = -1;
@@ -87,7 +87,7 @@ void NetplaySessionBrowserWidget::StartRefresh(void)
}
void NetplaySessionBrowserWidget::AddSessionData(QString name, QString game, QString md5, bool password, int port,
QString cpuEmulator, QString rspPlugin, QString gfxPlugin)
QString cpuEmulator, QString rspPlugin, QString gfxPlugin, QUrl address)
{
const NetplaySessionData sessionData =
{
@@ -98,7 +98,8 @@ void NetplaySessionBrowserWidget::AddSessionData(QString name, QString game, QSt
port,
cpuEmulator,
rspPlugin,
gfxPlugin
gfxPlugin,
address
};
int row = this->tableWidget->rowCount();
@@ -16,6 +16,7 @@
#include <QTableWidgetItem>
#include <QStackedWidget>
#include <QTableWidget>
#include <QUrl>
// session data
struct NetplaySessionData
@@ -28,6 +29,7 @@ struct NetplaySessionData
QString CpuEmulator;
QString RspPlugin;
QString GfxPlugin;
QUrl Address;
};
namespace UserInterface
@@ -47,7 +49,7 @@ class NetplaySessionBrowserWidget : public QStackedWidget
void StartRefresh(void);
void AddSessionData(QString name, QString game, QString md5, bool password, int port,
QString cpuEmulator, QString rspPlugin, QString gfxPlugin);
QString cpuEmulator, QString rspPlugin, QString gfxPlugin, QUrl address);
void RefreshDone(void);
@@ -0,0 +1,57 @@
/*
* Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG
* Copyright (C) 2020-2025 Rosalie Wanders <rosalie@mailbox.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "RomBrowserSearchWidget.hpp"
using namespace UserInterface::Widget;
RomBrowserSearchWidget::RomBrowserSearchWidget(QWidget* parent) : QWidget(parent)
{
// configure search line edit
this->lineEdit = new QLineEdit(this);
this->lineEdit->setPlaceholderText("Search games...");
connect(this->lineEdit, &QLineEdit::textChanged, this, &RomBrowserSearchWidget::on_lineEdit_textChanged);
// configure close button
this->closeButton = new QPushButton(this);
this->closeButton->setText("Close");
connect(this->closeButton, &QPushButton::clicked, this, &RomBrowserSearchWidget::on_closeButton_clicked);
// configure layout
this->widgetLayout = new QHBoxLayout(this);
this->widgetLayout->addWidget(this->lineEdit);
this->widgetLayout->addWidget(this->closeButton);
this->setLayout(this->widgetLayout);
}
RomBrowserSearchWidget::~RomBrowserSearchWidget(void)
{
}
void RomBrowserSearchWidget::FocusSearchText(void)
{
this->lineEdit->setFocus();
}
void RomBrowserSearchWidget::ClearSearchTerm(void)
{
this->lineEdit->setText("");
}
void RomBrowserSearchWidget::on_lineEdit_textChanged(const QString& text)
{
emit this->SearchTextChanged(text);
}
void RomBrowserSearchWidget::on_closeButton_clicked(void)
{
this->hide();
this->ClearSearchTerm();
}
@@ -0,0 +1,48 @@
/*
* Rosalie's Mupen GUI - https://github.com/Rosalie241/RMG
* Copyright (C) 2020-2025 Rosalie Wanders <rosalie@mailbox.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef ROMBROWSERSEARCHWIDGET_HPP
#define ROMBROWSERSEARCHWIDGET_HPP
#include <QPushButton>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QWidget>
namespace UserInterface
{
namespace Widget
{
class RomBrowserSearchWidget : public QWidget
{
Q_OBJECT
public:
RomBrowserSearchWidget(QWidget* parent);
~RomBrowserSearchWidget(void);
void FocusSearchText(void);
void ClearSearchTerm(void);
signals:
void SearchTextChanged(const QString& text);
private:
QHBoxLayout* widgetLayout = nullptr;
QLineEdit* lineEdit = nullptr;
QPushButton* closeButton = nullptr;
private slots:
void on_lineEdit_textChanged(const QString& text);
void on_closeButton_clicked(void);
};
}
}
#endif // ROMBROWSERSEARCHWIDGET_HPP
@@ -56,11 +56,26 @@ Q_DECLARE_METATYPE(RomBrowserModelData);
// Exported Functions
//
RomBrowserWidget::RomBrowserWidget(QWidget *parent) : QStackedWidget(parent)
RomBrowserWidget::RomBrowserWidget(QWidget *parent) : QWidget(parent)
{
// configure signal types
qRegisterMetaType<CoreRomType>("CoreRomType");
// configure stacked widget
this->stackedWidget = new QStackedWidget(this);
// configure search widget
this->searchWidget = new Widget::RomBrowserSearchWidget(this);
this->searchWidget->setVisible(false);
connect(this->searchWidget, &RomBrowserSearchWidget::SearchTextChanged, this, &RomBrowserWidget::on_searchWidget_SearchTextChanged);
// configure layout
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addWidget(this->stackedWidget);
layout->addWidget(this->searchWidget);
layout->setContentsMargins(0, 0, 0, 0);
this->setLayout(layout);
// configure rom searcher thread
this->romSearcherThread = new Thread::RomSearcherThread(this);
connect(this->romSearcherThread, &Thread::RomSearcherThread::RomsFound, this, &RomBrowserWidget::on_RomBrowserThread_RomsFound);
@@ -68,21 +83,23 @@ RomBrowserWidget::RomBrowserWidget(QWidget *parent) : QStackedWidget(parent)
// configure empty widget
this->emptyWidget = new Widget::RomBrowserEmptyWidget(this);
this->addWidget(this->emptyWidget);
this->stackedWidget->addWidget(this->emptyWidget);
connect(this->emptyWidget, &RomBrowserEmptyWidget::SelectRomDirectory, this, &RomBrowserWidget::on_Action_ChangeRomDirectory);
connect(this->emptyWidget, &RomBrowserEmptyWidget::Refresh, this, &RomBrowserWidget::on_Action_RefreshRomList);
connect(this->emptyWidget, &RomBrowserEmptyWidget::FileDropped, this, &RomBrowserWidget::FileDropped);
// configure loading widget
this->loadingWidget = new Widget::RomBrowserLoadingWidget(this);
connect(this, &QStackedWidget::currentChanged, this->loadingWidget, &RomBrowserLoadingWidget::on_RomBrowserWidget_currentChanged);
connect(this->stackedWidget, &QStackedWidget::currentChanged, this->loadingWidget, &RomBrowserLoadingWidget::on_RomBrowserWidget_currentChanged);
connect(this->loadingWidget, &RomBrowserLoadingWidget::FileDropped, this, &RomBrowserWidget::FileDropped);
this->loadingWidget->SetWidgetIndex(this->addWidget(this->loadingWidget));
this->loadingWidget->SetWidgetIndex(this->stackedWidget->addWidget(this->loadingWidget));
// configure list view widget
this->listViewWidget = new Widget::RomBrowserListViewWidget(this);
this->listViewModel = new QStandardItemModel(this);
this->listViewWidget->setModel(this->listViewModel);
this->listViewProxyModel = new QSortFilterProxyModel(this);
this->listViewProxyModel->setSourceModel(this->listViewModel);
this->listViewWidget->setModel(this->listViewProxyModel);
this->listViewWidget->setFrameStyle(QFrame::NoFrame);
this->listViewWidget->setItemDelegate(new NoFocusDelegate(this));
this->listViewWidget->setWordWrap(false);
@@ -101,7 +118,7 @@ RomBrowserWidget::RomBrowserWidget(QWidget *parent) : QStackedWidget(parent)
this->listViewWidget->horizontalHeader()->setSortIndicatorShown(false);
this->listViewWidget->horizontalHeader()->setHighlightSections(false);
this->listViewWidget->horizontalHeader()->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
this->addWidget(this->listViewWidget);
this->stackedWidget->addWidget(this->listViewWidget);
connect(this->listViewWidget, &QTableView::doubleClicked, this, &RomBrowserWidget::on_DoubleClicked);
connect(this->listViewWidget->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this, &RomBrowserWidget::on_listViewWidget_sortIndicatorChanged);
connect(this->listViewWidget->horizontalHeader(), &QHeaderView::sectionResized, this, &RomBrowserWidget::on_listViewWidget_sectionResized);
@@ -139,7 +156,9 @@ RomBrowserWidget::RomBrowserWidget(QWidget *parent) : QStackedWidget(parent)
// configure grid view widget
this->gridViewWidget = new Widget::RomBrowserGridViewWidget(this);
this->gridViewModel = new QStandardItemModel(this);
this->gridViewWidget->setModel(this->gridViewModel);
this->gridViewProxyModel = new QSortFilterProxyModel(this);
this->gridViewProxyModel->setSourceModel(this->gridViewModel);
this->gridViewWidget->setModel(this->gridViewProxyModel);
this->gridViewWidget->setFlow(QListView::Flow::LeftToRight);
this->gridViewWidget->setResizeMode(QListView::Adjust);
#ifndef DRAG_DROP
@@ -157,7 +176,7 @@ RomBrowserWidget::RomBrowserWidget(QWidget *parent) : QStackedWidget(parent)
int iconWidth = CoreSettingsGetIntValue(SettingsID::RomBrowser_GridViewIconWidth);
int iconHeight = CoreSettingsGetIntValue(SettingsID::RomBrowser_GridViewIconHeight);
this->gridViewWidget->setIconSize(QSize(iconWidth, iconHeight));
this->addWidget(this->gridViewWidget);
this->stackedWidget->addWidget(this->gridViewWidget);
connect(this->gridViewWidget, &QListView::doubleClicked, this, &RomBrowserWidget::on_DoubleClicked);
connect(this->gridViewWidget, &QListView::iconSizeChanged, this, &RomBrowserWidget::on_gridViewWidget_iconSizeChanged);
connect(this->gridViewWidget, &Widget::RomBrowserGridViewWidget::ZoomIn, this, &RomBrowserWidget::on_ZoomIn);
@@ -279,11 +298,15 @@ void RomBrowserWidget::RefreshRomList(void)
QString directory = QString::fromStdString(CoreSettingsGetStringValue(SettingsID::RomBrowser_Directory));
if (directory.isEmpty())
{
this->setCurrentWidget(this->emptyWidget);
this->stackedWidget->setCurrentWidget(this->emptyWidget);
return;
}
this->setCurrentWidget(this->loadingWidget);
this->stackedWidget->setCurrentWidget(this->loadingWidget);
this->showSearchWidget = this->searchWidget->isVisible();
this->searchWidget->hide();
this->romSearcherTimer.start();
this->romSearcherThread->SetMaximumFiles(CoreSettingsGetIntValue(SettingsID::RomBrowser_MaxItems));
@@ -308,9 +331,9 @@ void RomBrowserWidget::ShowList(void)
// only change widget now when we're not refreshing
if (!this->IsRefreshingRomList() &&
this->currentWidget() != this->emptyWidget)
this->stackedWidget->currentWidget() != this->emptyWidget)
{
this->setCurrentWidget(this->listViewWidget);
this->stackedWidget->setCurrentWidget(this->listViewWidget);
}
}
@@ -320,9 +343,9 @@ void RomBrowserWidget::ShowGrid(void)
// only change widget now when we're not refreshing
if (!this->IsRefreshingRomList() &&
this->currentWidget() != this->emptyWidget)
this->stackedWidget->currentWidget() != this->emptyWidget)
{
this->setCurrentWidget(this->gridViewWidget);
this->stackedWidget->setCurrentWidget(this->gridViewWidget);
}
}
@@ -330,7 +353,7 @@ void RomBrowserWidget::SetGridViewUniformSizes(bool value)
{
// refresh ROM list when we're currently in the grid view
// and we're not refreshing already
if (this->currentWidget() == this->gridViewWidget &&
if (this->stackedWidget->currentWidget() == this->gridViewWidget &&
!this->IsRefreshingRomList())
{
if (this->gridViewWidget->uniformItemSizes() != value)
@@ -344,20 +367,46 @@ void RomBrowserWidget::SetGridViewUniformSizes(bool value)
this->gridViewWidget->setUniformItemSizes(value);
}
void RomBrowserWidget::SetToggleSearch(void)
{
const bool show = !this->searchWidget->isVisible();
// do nothing when not in a valid view
QWidget* currentWidget = this->stackedWidget->currentWidget();
if (currentWidget == this->loadingWidget ||
currentWidget == this->emptyWidget)
{
return;
}
this->searchWidget->setVisible(show);
if (show)
{
// set focus when it's being shown
this->searchWidget->FocusSearchText();
}
else
{
// reset search when hidden
this->searchWidget->ClearSearchTerm();
}
}
QMap<QString, CoreRomSettings> RomBrowserWidget::GetModelData(void)
{
QMap<QString, CoreRomSettings> data;
QStandardItemModel* model = this->getCurrentModel();
QPair<QStandardItemModel*, QSortFilterProxyModel*> model = this->getCurrentModel();
RomBrowserModelData modelData;
if (model == nullptr)
if (model.first == nullptr)
{
return data;
}
for (int i = 0; i < model->rowCount(); i++)
for (int i = 0; i < model.first->rowCount(); i++)
{
modelData = model->item(i)->data().value<RomBrowserModelData>();
modelData = model.first->item(i)->data().value<RomBrowserModelData>();
// only add cartridges, 64dd disks aren't supported
if (modelData.type == CoreRomType::Cartridge)
{
@@ -368,9 +417,9 @@ QMap<QString, CoreRomSettings> RomBrowserWidget::GetModelData(void)
return data;
}
QStandardItemModel* RomBrowserWidget::getCurrentModel(void)
QPair<QStandardItemModel*, QSortFilterProxyModel*> RomBrowserWidget::getCurrentModel(void)
{
QWidget* currentWidget = this->currentWidget();
QWidget* currentWidget = this->stackedWidget->currentWidget();
if (currentWidget == this->loadingWidget)
{
currentWidget = this->currentViewWidget;
@@ -378,19 +427,19 @@ QStandardItemModel* RomBrowserWidget::getCurrentModel(void)
if (currentWidget == this->listViewWidget)
{
return this->listViewModel;
return qMakePair(this->listViewModel, this->listViewProxyModel);
}
else if (currentWidget == this->gridViewWidget)
{
return this->gridViewModel;
return qMakePair(this->gridViewModel, this->gridViewProxyModel);
}
return nullptr;
return qMakePair(nullptr, nullptr);
}
QAbstractItemView* RomBrowserWidget::getCurrentModelView(void)
{
QWidget* currentWidget = this->currentWidget();
QWidget* currentWidget = this->stackedWidget->currentWidget();
if (currentWidget == this->loadingWidget)
{
currentWidget = this->currentViewWidget;
@@ -410,16 +459,16 @@ QAbstractItemView* RomBrowserWidget::getCurrentModelView(void)
bool RomBrowserWidget::getCurrentData(RomBrowserModelData& data)
{
QStandardItemModel* model = this->getCurrentModel();
QAbstractItemView* view = this->getCurrentModelView();
QPair<QStandardItemModel*, QSortFilterProxyModel*> model = this->getCurrentModel();
QAbstractItemView* view = this->getCurrentModelView();
if (model == nullptr || view == nullptr)
if (model.first == nullptr || model.second == nullptr || view == nullptr)
{
return false;
}
QModelIndex index = view->currentIndex();
QStandardItem* item = model->item(index.row(), 0);
QModelIndex index = model.second->mapToSource(view->currentIndex());
QStandardItem* item = model.first->item(index.row(), 0);
if (item == nullptr)
{
@@ -614,7 +663,8 @@ QIcon RomBrowserWidget::getCurrentCover(QString file, CoreRomHeader header, Core
void RomBrowserWidget::timerEvent(QTimerEvent* event)
{
this->killTimer(event->timerId());
this->setCurrentWidget(this->currentViewWidget);
this->stackedWidget->setCurrentWidget(this->currentViewWidget);
this->searchWidget->setVisible(this->showSearchWidget);
}
void RomBrowserWidget::on_DoubleClicked(const QModelIndex& index)
@@ -624,9 +674,8 @@ void RomBrowserWidget::on_DoubleClicked(const QModelIndex& index)
void RomBrowserWidget::customContextMenuRequested(QPoint position)
{
QStandardItemModel* model = this->getCurrentModel();
QAbstractItemView* view = this->getCurrentModelView();
if (view == nullptr || model == nullptr)
QAbstractItemView* view = this->getCurrentModelView();
if (view == nullptr)
{
return;
}
@@ -713,20 +762,20 @@ void RomBrowserWidget::generateColumnsMenu(void)
void RomBrowserWidget::generatePlayWithDiskMenu(void)
{
QAction* playGameWithAction;
QStandardItemModel* model = this->getCurrentModel();
QPair<QStandardItemModel*, QSortFilterProxyModel*> model = this->getCurrentModel();
RomBrowserModelData modelData;
int count = 0;
this->menu_PlayGameWithDisk->clear();
if (model == nullptr)
if (model.first == nullptr)
{
return;
}
for (int i = 0; i < model->rowCount(); i++)
for (int i = 0; i < model.first->rowCount(); i++)
{
modelData = model->item(i)->data().value<RomBrowserModelData>();
modelData = model.first->item(i)->data().value<RomBrowserModelData>();
if (modelData.type == CoreRomType::Disk)
{
if (count == 0)
@@ -736,8 +785,8 @@ void RomBrowserWidget::generatePlayWithDiskMenu(void)
}
playGameWithAction = new QAction(this);
playGameWithAction->setText(model->item(i)->text());
playGameWithAction->setData(model->item(i)->data());
playGameWithAction->setText(model.first->item(i)->text());
playGameWithAction->setData(model.first->item(i)->data());
this->menu_PlayGameWithDisk->addAction(playGameWithAction);
// only add 10 disks to menu,
@@ -794,6 +843,19 @@ void RomBrowserWidget::generateStateMenu(void)
this->menu_PlayGameWithSlot->setDisabled(this->menu_PlayGameWithSlot->isEmpty());
}
void RomBrowserWidget::on_searchWidget_SearchTextChanged(const QString& text)
{
// TODO: I should really make a custom data
// model at this point to save memory & CPU cycles...
this->listViewProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
this->listViewProxyModel->setFilterWildcard(text);
this->listViewProxyModel->setFilterKeyColumn(0);
this->gridViewProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
this->gridViewProxyModel->setFilterWildcard(text);
this->gridViewProxyModel->setFilterKeyColumn(0);
}
void RomBrowserWidget::on_listViewWidget_sortIndicatorChanged(int logicalIndex, Qt::SortOrder sortOrder)
{
CoreSettingsSetValue(SettingsID::RomBrowser_ListViewSortSection, logicalIndex);
@@ -910,8 +972,8 @@ void RomBrowserWidget::on_RomBrowserThread_RomsFound(QList<RomSearcherThreadData
void RomBrowserWidget::on_RomBrowserThread_Finished(bool canceled)
{
// sort data
this->listViewModel->sort(this->listViewSortSection, (Qt::SortOrder)this->listViewSortOrder);
this->gridViewModel->sort(0, Qt::SortOrder::AscendingOrder);
this->listViewProxyModel->sort(this->listViewSortSection, (Qt::SortOrder)this->listViewSortOrder);
this->gridViewProxyModel->sort(0, Qt::SortOrder::AscendingOrder);
// retrieve column settings
std::vector<int> columnSizes = CoreSettingsGetIntListValue(SettingsID::RomBrowser_ColumnSizes);
@@ -1007,7 +1069,7 @@ void RomBrowserWidget::on_RomBrowserThread_Finished(bool canceled)
if (this->listViewModel->rowCount() == 0)
{
this->setCurrentWidget(this->emptyWidget);
this->stackedWidget->setCurrentWidget(this->emptyWidget);
return;
}
@@ -1020,7 +1082,8 @@ void RomBrowserWidget::on_RomBrowserThread_Finished(bool canceled)
return;
}
this->setCurrentWidget(this->currentViewWidget);
this->stackedWidget->setCurrentWidget(this->currentViewWidget);
this->searchWidget->setVisible(this->showSearchWidget);
}
void RomBrowserWidget::on_Action_PlayGame(void)
@@ -1144,9 +1207,9 @@ void RomBrowserWidget::on_Action_SetCoverImage(void)
QFileInfo sourceFileInfo;
QString coverFile;
QStandardItemModel* model = this->getCurrentModel();
QAbstractItemView* view = this->getCurrentModelView();
if (model == nullptr || view == nullptr)
QPair<QStandardItemModel*, QSortFilterProxyModel*> model = this->getCurrentModel();
QAbstractItemView* view = this->getCurrentModelView();
if (model.first == nullptr || model.second == nullptr || view == nullptr)
{
return;
}
@@ -1165,9 +1228,9 @@ void RomBrowserWidget::on_Action_SetCoverImage(void)
// retrieve file info
sourceFileInfo = QFileInfo(sourceFile);
QModelIndex index = view->currentIndex();
QStandardItem* item = model->item(index.row(), index.column());
RomBrowserModelData data = model->itemData(index).last().value<RomBrowserModelData>();
QModelIndex index = model.second->mapToSource(view->currentIndex());
QStandardItem* item = model.first->item(index.row(), index.column());
RomBrowserModelData data = model.first->itemData(index).last().value<RomBrowserModelData>();
// construct new file name (for the cover)
QString newFileName = this->coversDirectory;
@@ -1203,16 +1266,16 @@ void RomBrowserWidget::on_Action_SetCoverImage(void)
void RomBrowserWidget::on_Action_RemoveCoverImage(void)
{
QStandardItemModel* model = this->getCurrentModel();
QPair<QStandardItemModel*, QSortFilterProxyModel*> model = this->getCurrentModel();
QAbstractItemView* view = this->getCurrentModelView();
if (model == nullptr || view == nullptr)
if (model.first == nullptr || model.second == nullptr || view == nullptr)
{
return;
}
QModelIndex index = view->currentIndex();
QStandardItem* item = model->item(index.row(), index.column());
RomBrowserModelData data = model->itemData(index).last().value<RomBrowserModelData>();
QModelIndex index = model.second->mapToSource(view->currentIndex());
QStandardItem* item = model.first->item(index.row(), index.column());
RomBrowserModelData data = model.first->itemData(index).last().value<RomBrowserModelData>();
QString coverFile;
if (!data.coverFile.isEmpty() && QFile::exists(data.coverFile))
@@ -16,18 +16,22 @@
#include "RomBrowserListViewWidget.hpp"
#include "RomBrowserGridViewWidget.hpp"
#include "RomBrowserLoadingWidget.hpp"
#include "RomBrowserSearchWidget.hpp"
#include "RomBrowserEmptyWidget.hpp"
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QStackedWidget>
#include <QGridLayout>
#include <QListWidget>
#include <QHeaderView>
#include <QTableView>
#include <QLineEdit>
#include <QAction>
#include <QString>
#include <QList>
#include <QMenu>
#include <QPair>
#include <QMap>
// forward declaration of internal struct
@@ -37,7 +41,7 @@ namespace UserInterface
{
namespace Widget
{
class RomBrowserWidget : public QStackedWidget
class RomBrowserWidget : public QWidget
{
Q_OBJECT
@@ -53,17 +57,24 @@ class RomBrowserWidget : public QStackedWidget
void ShowGrid(void);
void SetGridViewUniformSizes(bool value);
void SetToggleSearch(void);
QMap<QString, CoreRomSettings> GetModelData(void);
private:
QStackedWidget* stackedWidget = nullptr;
Widget::RomBrowserEmptyWidget* emptyWidget = nullptr;
Widget::RomBrowserLoadingWidget* loadingWidget = nullptr;
Widget::RomBrowserListViewWidget* listViewWidget = nullptr;
QStandardItemModel* listViewModel = nullptr;
QSortFilterProxyModel* listViewProxyModel = nullptr;
Widget::RomBrowserGridViewWidget* gridViewWidget = nullptr;
QStandardItemModel* gridViewModel = nullptr;
QSortFilterProxyModel* gridViewProxyModel = nullptr;
Widget::RomBrowserSearchWidget* searchWidget = nullptr;
bool showSearchWidget = false;
QWidget* currentViewWidget = nullptr;
@@ -96,7 +107,7 @@ class RomBrowserWidget : public QStackedWidget
QString coversDirectory;
QStandardItemModel* getCurrentModel(void);
QPair<QStandardItemModel*, QSortFilterProxyModel*> getCurrentModel(void);
QAbstractItemView* getCurrentModelView(void);
bool getCurrentData(RomBrowserModelData& data);
@@ -116,6 +127,8 @@ class RomBrowserWidget : public QStackedWidget
void generatePlayWithDiskMenu(void);
void generateStateMenu(void);
void on_searchWidget_SearchTextChanged(const QString& text);
void on_listViewWidget_sortIndicatorChanged(int logicalIndex, Qt::SortOrder sortOrder);
void on_listViewWidget_sectionResized(int logicalIndex, int oldWidth, int newWidth);
void on_listViewWidget_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
+1 -1
View File
@@ -1 +1 @@
v0.7.8
v0.8.0