Merge remote-tracking branch 'upstream/master' into interpreter-fixes
This commit is contained in:
commit
f0bd2b9051
|
@ -20,8 +20,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
|
sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev \
|
sudo apt install --allow-downgrades cmake ninja-build extra-cmake-modules libpcap0.8-dev libsdl2-dev libenet-dev \
|
||||||
qt6-{base,base-private,multimedia}-dev libarchive-dev libzstd-dev libfuse2
|
qt6-{base,base-private,multimedia}-dev libqt6svg6-dev libarchive-dev libzstd-dev libfuse2
|
||||||
- name: Configure
|
- name: Configure
|
||||||
run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr
|
run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr
|
||||||
- name: Build
|
- name: Build
|
||||||
|
@ -63,7 +63,7 @@ jobs:
|
||||||
apt update
|
apt update
|
||||||
apt -y full-upgrade
|
apt -y full-upgrade
|
||||||
apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \
|
apt -y install git {gcc-12,g++-12}-aarch64-linux-gnu cmake ninja-build extra-cmake-modules \
|
||||||
{libsdl2,qt6-{base,base-private,multimedia},libarchive,libzstd}-dev:arm64 \
|
{libsdl2,qt6-{base,base-private,multimedia},libqt6svg6,libarchive,libzstd,libenet}-dev:arm64 \
|
||||||
pkg-config dpkg-dev
|
pkg-config dpkg-dev
|
||||||
- name: Check out source
|
- name: Check out source
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
16
README.md
16
README.md
|
@ -35,9 +35,9 @@ As for the rest, the interface should be pretty straightforward. If you have a q
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
1. Install dependencies:
|
1. Install dependencies:
|
||||||
* Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libarchive-dev libzstd-dev`
|
* Ubuntu 22.04: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qtbase5-dev qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev`
|
||||||
* Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libarchive-dev libzstd-dev`
|
* Older Ubuntu: `sudo apt install cmake extra-cmake-modules libcurl4-gnutls-dev libpcap0.8-dev libsdl2-dev qt5-default qtbase5-private-dev qtmultimedia5-dev libqt5svg5-dev libarchive-dev libenet-dev libzstd-dev`
|
||||||
* Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia libarchive zstd`
|
* Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia qt5-svg libarchive enet zstd`
|
||||||
3. Download the melonDS repository and prepare:
|
3. Download the melonDS repository and prepare:
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/melonDS-emu/melonDS
|
git clone https://github.com/melonDS-emu/melonDS
|
||||||
|
@ -64,7 +64,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q
|
||||||
cd melonDS
|
cd melonDS
|
||||||
```
|
```
|
||||||
#### Dynamic builds (with DLLs)
|
#### Dynamic builds (with DLLs)
|
||||||
5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,qt5-tools,libarchive,zstd}`
|
5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-base,qt5-svg,qt5-multimedia,qt5-svg,qt5-tools,libarchive,enet,zstd}`
|
||||||
6. Compile:
|
6. Compile:
|
||||||
```bash
|
```bash
|
||||||
cmake -B build
|
cmake -B build
|
||||||
|
@ -75,7 +75,7 @@ As for the rest, the interface should be pretty straightforward. If you have a q
|
||||||
If everything went well, melonDS and the libraries it needs should now be in the `dist` folder.
|
If everything went well, melonDS and the libraries it needs should now be in the `dist` folder.
|
||||||
|
|
||||||
#### Static builds (without DLLs, standalone executable)
|
#### Static builds (without DLLs, standalone executable)
|
||||||
5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libarchive,zstd}`
|
5. Install dependencies: `pacman -S mingw-w64-x86_64-{cmake,SDL2,toolchain,qt5-static,libarchive,enet,zstd}`
|
||||||
6. Compile:
|
6. Compile:
|
||||||
```bash
|
```bash
|
||||||
cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static
|
cmake -B build -DBUILD_STATIC=ON -DCMAKE_PREFIX_PATH=/mingw64/qt5-static
|
||||||
|
@ -85,7 +85,7 @@ If everything went well, melonDS should now be in the `build` folder.
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
1. Install the [Homebrew Package Manager](https://brew.sh)
|
1. Install the [Homebrew Package Manager](https://brew.sh)
|
||||||
2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive zstd`
|
2. Install dependencies: `brew install git pkg-config cmake sdl2 qt@6 libarchive enet zstd`
|
||||||
3. Download the melonDS repository and prepare:
|
3. Download the melonDS repository and prepare:
|
||||||
```zsh
|
```zsh
|
||||||
git clone https://github.com/melonDS-emu/melonDS
|
git clone https://github.com/melonDS-emu/melonDS
|
||||||
|
@ -93,14 +93,14 @@ If everything went well, melonDS should now be in the `build` folder.
|
||||||
```
|
```
|
||||||
4. Compile:
|
4. Compile:
|
||||||
```zsh
|
```zsh
|
||||||
cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)" -DUSE_QT6=ON
|
cmake -B build -DCMAKE_PREFIX_PATH="$(brew --prefix qt@6);$(brew --prefix libarchive)"
|
||||||
cmake --build build -j$(sysctl -n hw.logicalcpu)
|
cmake --build build -j$(sysctl -n hw.logicalcpu)
|
||||||
```
|
```
|
||||||
If everything went well, melonDS.app should now be in the `build` directory.
|
If everything went well, melonDS.app should now be in the `build` directory.
|
||||||
|
|
||||||
#### Self-contained app bundle
|
#### Self-contained app bundle
|
||||||
If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run `
|
If you want an app bundle that can be distributed to other computers without needing to install dependencies through Homebrew, you can additionally run `
|
||||||
../tools/mac-bundle.rb melonDS.app` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command.
|
../tools/mac-libs.rb .` after the build is completed, or add `-DMACOS_BUNDLE_LIBS=ON` to the first CMake command.
|
||||||
|
|
||||||
## TODO LIST
|
## TODO LIST
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
# - Try to find enet
|
||||||
|
# Once done this will define
|
||||||
|
#
|
||||||
|
# ENET_FOUND - system has enet
|
||||||
|
# ENET_INCLUDE_DIRS - the enet include directory
|
||||||
|
# ENET_LIBRARIES - the libraries needed to use enet
|
||||||
|
#
|
||||||
|
# $ENETDIR is an environment variable used for finding enet.
|
||||||
|
#
|
||||||
|
# Borrowed from The Mana World
|
||||||
|
# http://themanaworld.org/
|
||||||
|
#
|
||||||
|
# Several changes and additions by Fabian 'x3n' Landau
|
||||||
|
# Lots of simplifications by Adrian Friedli
|
||||||
|
# > www.orxonox.net <
|
||||||
|
|
||||||
|
FIND_PATH(ENET_INCLUDE_DIRS enet/enet.h
|
||||||
|
PATHS
|
||||||
|
$ENV{ENETDIR}
|
||||||
|
/usr/local
|
||||||
|
/usr
|
||||||
|
PATH_SUFFIXES include
|
||||||
|
)
|
||||||
|
|
||||||
|
FIND_LIBRARY(ENET_LIBRARY
|
||||||
|
NAMES enet
|
||||||
|
PATHS
|
||||||
|
$ENV{ENETDIR}
|
||||||
|
/usr/local
|
||||||
|
/usr
|
||||||
|
PATH_SUFFIXES lib
|
||||||
|
)
|
||||||
|
|
||||||
|
# handle the QUIETLY and REQUIRED arguments and set ENET_FOUND to TRUE if
|
||||||
|
# all listed variables are TRUE
|
||||||
|
INCLUDE(FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS(ENet DEFAULT_MSG ENET_LIBRARY ENET_INCLUDE_DIRS)
|
||||||
|
|
||||||
|
IF (ENET_FOUND)
|
||||||
|
IF(WIN32)
|
||||||
|
SET(WINDOWS_ENET_DEPENDENCIES "ws2_32;winmm")
|
||||||
|
SET(ENET_LIBRARIES ${ENET_LIBRARY} ${WINDOWS_ENET_DEPENDENCIES})
|
||||||
|
ELSE(WIN32)
|
||||||
|
SET(ENET_LIBRARIES ${ENET_LIBRARY})
|
||||||
|
ENDIF(WIN32)
|
||||||
|
ENDIF (ENET_FOUND)
|
||||||
|
|
||||||
|
MARK_AS_ADVANCED(ENET_LIBRARY ENET_LIBRARIES ENET_INCLUDE_DIRS)
|
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710146030,
|
||||||
|
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1723175592,
|
||||||
|
"narHash": "sha256-M0xJ3FbDUc4fRZ84dPGx5VvgFsOzds77KiBMW/mMTnI=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "5e0ca22929f3342b19569b21b2f3462f053e497b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
description = "Nintendo DS emulator";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
inherit (pkgs.lib) cmakeBool optionals makeLibraryPath;
|
||||||
|
inherit (pkgs.stdenv) isLinux isDarwin;
|
||||||
|
|
||||||
|
versionSuffix = with self; if sourceInfo?dirtyShortRev
|
||||||
|
then sourceInfo.dirtyShortRev
|
||||||
|
else sourceInfo.shortRev;
|
||||||
|
|
||||||
|
melonDS = pkgs.stdenv.mkDerivation {
|
||||||
|
pname = "melonDS";
|
||||||
|
version = "0.9.5-${versionSuffix}";
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
cmake
|
||||||
|
ninja
|
||||||
|
pkg-config
|
||||||
|
kdePackages.wrapQtAppsHook
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = (with pkgs; [
|
||||||
|
kdePackages.qtbase
|
||||||
|
kdePackages.qtmultimedia
|
||||||
|
extra-cmake-modules
|
||||||
|
SDL2
|
||||||
|
zstd
|
||||||
|
libarchive
|
||||||
|
libGL
|
||||||
|
libslirp
|
||||||
|
enet
|
||||||
|
]) ++ optionals isLinux [
|
||||||
|
pkgs.wayland
|
||||||
|
pkgs.kdePackages.qtwayland
|
||||||
|
];
|
||||||
|
|
||||||
|
cmakeFlags = [
|
||||||
|
(cmakeBool "USE_QT6" true)
|
||||||
|
(cmakeBool "USE_SYSTEM_LIBSLIRP" true)
|
||||||
|
];
|
||||||
|
|
||||||
|
qtWrapperArgs = optionals isLinux [
|
||||||
|
"--prefix LD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap pkgs.wayland ]}"
|
||||||
|
] ++ optionals isDarwin [
|
||||||
|
"--prefix DYLD_LIBRARY_PATH : ${makeLibraryPath [ pkgs.libpcap ]}"
|
||||||
|
];
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
exePath = if isDarwin then
|
||||||
|
"/Applications/melonDS.app/Contents/MacOS/melonDS"
|
||||||
|
else "/bin/melonDS";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
packages.default = melonDS;
|
||||||
|
apps.default = flake-utils.lib.mkApp {
|
||||||
|
drv = self.packages.${system}.default;
|
||||||
|
};
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ self.packages.${system}.default ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
|
@ -632,6 +632,7 @@ void ARMv5::Execute()
|
||||||
|
|
||||||
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
while (NDS.ARM9Timestamp < NDS.ARM9Target)
|
||||||
{
|
{
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
if constexpr (mode == CPUExecuteMode::JIT)
|
if constexpr (mode == CPUExecuteMode::JIT)
|
||||||
{
|
{
|
||||||
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
||||||
|
@ -670,6 +671,7 @@ void ARMv5::Execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
if (CPSR & 0x20) // THUMB
|
if (CPSR & 0x20) // THUMB
|
||||||
{
|
{
|
||||||
|
@ -784,6 +786,7 @@ void ARMv4::Execute()
|
||||||
|
|
||||||
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
while (NDS.ARM7Timestamp < NDS.ARM7Target)
|
||||||
{
|
{
|
||||||
|
#ifdef JIT_ENABLED
|
||||||
if constexpr (mode == CPUExecuteMode::JIT)
|
if constexpr (mode == CPUExecuteMode::JIT)
|
||||||
{
|
{
|
||||||
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
u32 instrAddr = R[15] - ((CPSR&0x20)?2:4);
|
||||||
|
@ -821,6 +824,7 @@ void ARMv4::Execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
if (CPSR & 0x20) // THUMB
|
if (CPSR & 0x20) // THUMB
|
||||||
{
|
{
|
||||||
|
|
|
@ -833,7 +833,7 @@ void GBACartSlot::SetCart(std::unique_ptr<CartCommon>&& cart) noexcept
|
||||||
|
|
||||||
if (!Cart)
|
if (!Cart)
|
||||||
{
|
{
|
||||||
Log(LogLevel::Info, "Ejected GBA cart");
|
Log(LogLevel::Info, "Ejected GBA cart\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -270,6 +270,9 @@ bool Mutex_TryLock(Mutex* mutex);
|
||||||
|
|
||||||
void Sleep(u64 usecs);
|
void Sleep(u64 usecs);
|
||||||
|
|
||||||
|
u64 GetMSCount();
|
||||||
|
u64 GetUSCount();
|
||||||
|
|
||||||
|
|
||||||
// functions called when the NDS or GBA save files need to be written back to storage
|
// functions called when the NDS or GBA save files need to be written back to storage
|
||||||
// savedata and savelen are always the entire save memory buffer and its full length
|
// savedata and savelen are always the entire save memory buffer and its full length
|
||||||
|
|
16
src/Wifi.cpp
16
src/Wifi.cpp
|
@ -1645,9 +1645,21 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames
|
||||||
*(u16*)&RXBuffer[6] = txrate;
|
*(u16*)&RXBuffer[6] = txrate;
|
||||||
*(u16*)&RXBuffer[8] = framelen;
|
*(u16*)&RXBuffer[8] = framelen;
|
||||||
|
|
||||||
|
u16 frametype = (framectl & 0x00FF);
|
||||||
bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0));
|
bool macgood = (RXBuffer[12 + 4] & 0x01) || MACEqual(&RXBuffer[12 + 4], (u8*)&IOPORT(W_MACAddr0));
|
||||||
|
|
||||||
if (((framectl & 0x00FF) == 0x0010) && timestamp && macgood)
|
// HACK: when receiving auth/assoc frames, extend the post-beacon interval
|
||||||
|
// during MP comm, the host will periodically wake up to send a beacon, and stay awake during the
|
||||||
|
// post-beacon interval to see if any clients are trying to connect
|
||||||
|
// the auth/assoc procedure would normally fit during that window, but when we are emulating wifi
|
||||||
|
// and not yet synced, these frames may lag behind, preventing a successful connection
|
||||||
|
if ((frametype == 0x00B0 || frametype == 0x0010 || frametype == 0x0000) && timestamp && macgood)
|
||||||
|
{
|
||||||
|
if (IOPORT(W_BeaconCount2))
|
||||||
|
IOPORT(W_BeaconCount2) += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((frametype == 0x0010) && timestamp && macgood)
|
||||||
{
|
{
|
||||||
// if receiving an association response: get the sync value from the host
|
// if receiving an association response: get the sync value from the host
|
||||||
|
|
||||||
|
@ -1666,7 +1678,7 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames
|
||||||
RXTimestamp = 0;
|
RXTimestamp = 0;
|
||||||
StartRX();
|
StartRX();
|
||||||
}
|
}
|
||||||
else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient)
|
else if ((frametype == 0x00C0) && timestamp && macgood && IsMPClient)
|
||||||
{
|
{
|
||||||
IsMP = false;
|
IsMP = false;
|
||||||
IsMPClient = false;
|
IsMPClient = false;
|
||||||
|
|
|
@ -51,6 +51,9 @@ set(SOURCES_QT_SDL
|
||||||
|
|
||||||
CLI.h
|
CLI.h
|
||||||
CLI.cpp
|
CLI.cpp
|
||||||
|
|
||||||
|
LANDialog.cpp
|
||||||
|
NetplayDialog.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
|
@ -60,10 +63,10 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_QT6)
|
if (USE_QT6)
|
||||||
find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
|
find_package(Qt6 COMPONENTS Core Gui Widgets Network Multimedia OpenGL OpenGLWidgets Svg REQUIRED)
|
||||||
set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets)
|
set(QT_LINK_LIBS Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Network Qt6::Multimedia Qt6::OpenGL Qt6::OpenGLWidgets)
|
||||||
else()
|
else()
|
||||||
find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia REQUIRED)
|
find_package(Qt5 COMPONENTS Core Gui Widgets Network Multimedia Svg REQUIRED)
|
||||||
set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia)
|
set(QT_LINK_LIBS Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Network Qt5::Multimedia)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -164,9 +167,6 @@ target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||||
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..")
|
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../..")
|
||||||
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../net")
|
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../net")
|
||||||
target_include_directories(melonDS PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../../net/libslirp/src")
|
|
||||||
get_target_property(SLIRP_BINARY_DIR slirp BINARY_DIR)
|
|
||||||
target_include_directories(melonDS PUBLIC "${SLIRP_BINARY_DIR}") # for libslirp-version.h
|
|
||||||
if (USE_QT6)
|
if (USE_QT6)
|
||||||
target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
target_include_directories(melonDS PUBLIC ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||||
else()
|
else()
|
||||||
|
@ -243,6 +243,8 @@ if (UNIX AND NOT APPLE)
|
||||||
INTERPROCEDURAL_OPTIMIZATION OFF
|
INTERPROCEDURAL_OPTIMIZATION OFF
|
||||||
INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF)
|
INTERPROCEDURAL_OPTIMIZATION_RELEASE OFF)
|
||||||
endif()
|
endif()
|
||||||
|
elseif(APPLE)
|
||||||
|
install(TARGETS melonDS BUNDLE DESTINATION "${CMAKE_INSTALL_PREFIX}/Applications")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (ENABLE_OGLRENDERER)
|
if (ENABLE_OGLRENDERER)
|
||||||
|
|
|
@ -71,7 +71,8 @@ DefaultList<int> DefaultInts =
|
||||||
#ifdef GDBSTUB_ENABLED
|
#ifdef GDBSTUB_ENABLED
|
||||||
{"Instance*.Gdb.ARM7.Port", 3334},
|
{"Instance*.Gdb.ARM7.Port", 3334},
|
||||||
{"Instance*.Gdb.ARM9.Port", 3333},
|
{"Instance*.Gdb.ARM9.Port", 3333},
|
||||||
#endif
|
#endif,
|
||||||
|
{"LAN.HostNumPlayers", 16},
|
||||||
};
|
};
|
||||||
|
|
||||||
RangeList IntRanges =
|
RangeList IntRanges =
|
||||||
|
@ -90,6 +91,7 @@ RangeList IntRanges =
|
||||||
{"Instance*.Window*.ScreenAspectTop", {0, AspectRatiosNum-1}},
|
{"Instance*.Window*.ScreenAspectTop", {0, AspectRatiosNum-1}},
|
||||||
{"Instance*.Window*.ScreenAspectBot", {0, AspectRatiosNum-1}},
|
{"Instance*.Window*.ScreenAspectBot", {0, AspectRatiosNum-1}},
|
||||||
{"MP.AudioMode", {0, 2}},
|
{"MP.AudioMode", {0, 2}},
|
||||||
|
{"LAN.HostNumPlayers", {2, 16}},
|
||||||
};
|
};
|
||||||
|
|
||||||
DefaultList<bool> DefaultBools =
|
DefaultList<bool> DefaultBools =
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
#include "LocalMP.h"
|
#include "MPInterface.h"
|
||||||
|
|
||||||
#include "NDS.h"
|
#include "NDS.h"
|
||||||
#include "DSi.h"
|
#include "DSi.h"
|
||||||
|
@ -62,11 +62,11 @@ using namespace melonDS::Platform;
|
||||||
MainWindow* topWindow = nullptr;
|
MainWindow* topWindow = nullptr;
|
||||||
|
|
||||||
const string kWifiSettingsPath = "wfcsettings.bin";
|
const string kWifiSettingsPath = "wfcsettings.bin";
|
||||||
extern LocalMP localMp;
|
|
||||||
extern Net net;
|
extern Net net;
|
||||||
|
|
||||||
|
|
||||||
EmuInstance::EmuInstance(int inst) : instanceID(inst),
|
EmuInstance::EmuInstance(int inst) : deleting(false),
|
||||||
|
instanceID(inst),
|
||||||
globalCfg(Config::GetGlobalTable()),
|
globalCfg(Config::GetGlobalTable()),
|
||||||
localCfg(Config::GetLocalTable(inst))
|
localCfg(Config::GetLocalTable(inst))
|
||||||
{
|
{
|
||||||
|
@ -117,8 +117,10 @@ EmuInstance::EmuInstance(int inst) : instanceID(inst),
|
||||||
|
|
||||||
EmuInstance::~EmuInstance()
|
EmuInstance::~EmuInstance()
|
||||||
{
|
{
|
||||||
// TODO window cleanup and shit?
|
deleting = true;
|
||||||
localMp.End(instanceID);
|
deleteAllWindows();
|
||||||
|
|
||||||
|
MPInterface::Get().End(instanceID);
|
||||||
|
|
||||||
emuThread->emuExit();
|
emuThread->emuExit();
|
||||||
emuThread->wait();
|
emuThread->wait();
|
||||||
|
@ -168,6 +170,44 @@ void EmuInstance::createWindow()
|
||||||
emuThread->attachWindow(win);
|
emuThread->attachWindow(win);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EmuInstance::deleteWindow(int id, bool close)
|
||||||
|
{
|
||||||
|
if (id >= kMaxWindows) return;
|
||||||
|
|
||||||
|
MainWindow* win = windowList[id];
|
||||||
|
if (!win) return;
|
||||||
|
|
||||||
|
if (win->hasOpenGL() && win == mainWindow)
|
||||||
|
{
|
||||||
|
// we intentionally don't unpause here
|
||||||
|
emuThread->emuPause();
|
||||||
|
emuThread->deinitContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
emuThread->detachWindow(win);
|
||||||
|
|
||||||
|
windowList[id] = nullptr;
|
||||||
|
numWindows--;
|
||||||
|
|
||||||
|
if (topWindow == win) topWindow = nullptr;
|
||||||
|
if (mainWindow == win) mainWindow = nullptr;
|
||||||
|
|
||||||
|
if (close)
|
||||||
|
win->close();
|
||||||
|
|
||||||
|
if ((!mainWindow) && (!deleting))
|
||||||
|
{
|
||||||
|
// if we closed this instance's main window, delete the instance
|
||||||
|
deleteEmuInstance(instanceID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmuInstance::deleteAllWindows()
|
||||||
|
{
|
||||||
|
for (int i = kMaxWindows-1; i >= 0; i--)
|
||||||
|
deleteWindow(i, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...)
|
void EmuInstance::osdAddMessage(unsigned int color, const char* fmt, ...)
|
||||||
{
|
{
|
||||||
|
|
|
@ -91,6 +91,8 @@ public:
|
||||||
std::string instanceFileSuffix();
|
std::string instanceFileSuffix();
|
||||||
|
|
||||||
void createWindow();
|
void createWindow();
|
||||||
|
void deleteWindow(int id, bool close);
|
||||||
|
void deleteAllWindows();
|
||||||
|
|
||||||
void osdAddMessage(unsigned int color, const char* fmt, ...);
|
void osdAddMessage(unsigned int color, const char* fmt, ...);
|
||||||
|
|
||||||
|
@ -217,6 +219,8 @@ private:
|
||||||
bool hotkeyPressed(int id) { return hotkeyPress & (1<<id); }
|
bool hotkeyPressed(int id) { return hotkeyPress & (1<<id); }
|
||||||
bool hotkeyReleased(int id) { return hotkeyRelease & (1<<id); }
|
bool hotkeyReleased(int id) { return hotkeyRelease & (1<<id); }
|
||||||
|
|
||||||
|
bool deleting;
|
||||||
|
|
||||||
int instanceID;
|
int instanceID;
|
||||||
|
|
||||||
EmuThread* emuThread;
|
EmuThread* emuThread;
|
||||||
|
|
|
@ -151,6 +151,7 @@ void EmuThread::run()
|
||||||
|
|
||||||
while (emuStatus != emuStatus_Exit)
|
while (emuStatus != emuStatus_Exit)
|
||||||
{
|
{
|
||||||
|
MPInterface::Get().Process();
|
||||||
emuInstance->inputProcess();
|
emuInstance->inputProcess();
|
||||||
|
|
||||||
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
|
if (emuInstance->hotkeyPressed(HK_FastForwardToggle)) emit windowLimitFPSChange();
|
||||||
|
|
|
@ -0,0 +1,418 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <queue>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "LANDialog.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "LAN.h"
|
||||||
|
|
||||||
|
#include "ui_LANStartHostDialog.h"
|
||||||
|
#include "ui_LANStartClientDialog.h"
|
||||||
|
#include "ui_LANDialog.h"
|
||||||
|
|
||||||
|
using namespace melonDS;
|
||||||
|
|
||||||
|
|
||||||
|
LANStartClientDialog* lanClientDlg = nullptr;
|
||||||
|
LANDialog* lanDlg = nullptr;
|
||||||
|
|
||||||
|
#define lan() ((LAN&)MPInterface::Get())
|
||||||
|
|
||||||
|
|
||||||
|
LANStartHostDialog::LANStartHostDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LANStartHostDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
setMPInterface(MPInterface_LAN);
|
||||||
|
|
||||||
|
auto cfg = Config::GetGlobalTable();
|
||||||
|
ui->txtPlayerName->setText(cfg.GetQString("LAN.PlayerName"));
|
||||||
|
|
||||||
|
ui->sbNumPlayers->setRange(2, 16);
|
||||||
|
ui->sbNumPlayers->setValue(cfg.GetInt("LAN.HostNumPlayers"));
|
||||||
|
}
|
||||||
|
|
||||||
|
LANStartHostDialog::~LANStartHostDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartHostDialog::done(int r)
|
||||||
|
{
|
||||||
|
if (r == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
if (ui->txtPlayerName->text().trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, "melonDS", "Please enter a player name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string player = ui->txtPlayerName->text().toStdString();
|
||||||
|
int numplayers = ui->sbNumPlayers->value();
|
||||||
|
|
||||||
|
if (!lan().StartHost(player.c_str(), numplayers))
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, "melonDS", "Failed to start LAN game.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lanDlg = LANDialog::openDlg(parentWidget());
|
||||||
|
|
||||||
|
auto cfg = Config::GetGlobalTable();
|
||||||
|
cfg.SetString("LAN.PlayerName", player);
|
||||||
|
cfg.SetInt("LAN.HostNumPlayers", numplayers);
|
||||||
|
Config::Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setMPInterface(MPInterface_Local);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::done(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LANStartClientDialog::LANStartClientDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LANStartClientDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
setMPInterface(MPInterface_LAN);
|
||||||
|
|
||||||
|
auto cfg = Config::GetGlobalTable();
|
||||||
|
ui->txtPlayerName->setText(cfg.GetQString("LAN.PlayerName"));
|
||||||
|
|
||||||
|
QStandardItemModel* model = new QStandardItemModel();
|
||||||
|
ui->tvAvailableGames->setModel(model);
|
||||||
|
const QStringList listheader = {"Name", "Players", "Status", "Host IP"};
|
||||||
|
model->setHorizontalHeaderLabels(listheader);
|
||||||
|
|
||||||
|
connect(ui->tvAvailableGames->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
|
||||||
|
this, SLOT(onGameSelectionChanged(const QItemSelection&, const QItemSelection&)));
|
||||||
|
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Connect");
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
|
|
||||||
|
QPushButton* btn = ui->buttonBox->addButton("Direct connect...", QDialogButtonBox::ActionRole);
|
||||||
|
connect(btn, SIGNAL(clicked()), this, SLOT(onDirectConnect()));
|
||||||
|
|
||||||
|
lanClientDlg = this;
|
||||||
|
lan().StartDiscovery();
|
||||||
|
|
||||||
|
timerID = startTimer(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
LANStartClientDialog::~LANStartClientDialog()
|
||||||
|
{
|
||||||
|
killTimer(timerID);
|
||||||
|
|
||||||
|
lanClientDlg = nullptr;
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartClientDialog::onGameSelectionChanged(const QItemSelection& cur, const QItemSelection& prev)
|
||||||
|
{
|
||||||
|
QModelIndexList indlist = cur.indexes();
|
||||||
|
if (indlist.count() == 0)
|
||||||
|
{
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartClientDialog::on_tvAvailableGames_doubleClicked(QModelIndex index)
|
||||||
|
{
|
||||||
|
done(QDialog::Accepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartClientDialog::onDirectConnect()
|
||||||
|
{
|
||||||
|
if (ui->txtPlayerName->text().trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, "melonDS", "Please enter a player name before connecting.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString host = QInputDialog::getText(this, "Direct connect", "Host address:");
|
||||||
|
if (host.isEmpty()) return;
|
||||||
|
|
||||||
|
std::string hostname = host.toStdString();
|
||||||
|
std::string player = ui->txtPlayerName->text().toStdString();
|
||||||
|
|
||||||
|
setEnabled(false);
|
||||||
|
lan().EndDiscovery();
|
||||||
|
if (!lan().StartClient(player.c_str(), hostname.c_str()))
|
||||||
|
{
|
||||||
|
QString msg = QString("Failed to connect to the host %0.").arg(QString::fromStdString(hostname));
|
||||||
|
QMessageBox::warning(this, "melonDS", msg);
|
||||||
|
setEnabled(true);
|
||||||
|
lan().StartDiscovery();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
lanDlg = LANDialog::openDlg(parentWidget());
|
||||||
|
QDialog::done(QDialog::Accepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartClientDialog::done(int r)
|
||||||
|
{
|
||||||
|
if (r == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
if (ui->txtPlayerName->text().trimmed().isEmpty())
|
||||||
|
{
|
||||||
|
QMessageBox::warning(this, "melonDS", "Please enter a player name before connecting.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndexList indlist = ui->tvAvailableGames->selectionModel()->selectedRows();
|
||||||
|
if (indlist.count() == 0) return;
|
||||||
|
|
||||||
|
QStandardItemModel* model = (QStandardItemModel*)ui->tvAvailableGames->model();
|
||||||
|
QStandardItem* item = model->item(indlist[0].row());
|
||||||
|
u32 addr = item->data().toUInt();
|
||||||
|
char hostname[16];
|
||||||
|
snprintf(hostname, 16, "%d.%d.%d.%d", (addr>>24), ((addr>>16)&0xFF), ((addr>>8)&0xFF), (addr&0xFF));
|
||||||
|
|
||||||
|
std::string player = ui->txtPlayerName->text().toStdString();
|
||||||
|
|
||||||
|
setEnabled(false);
|
||||||
|
lan().EndDiscovery();
|
||||||
|
if (!lan().StartClient(player.c_str(), hostname))
|
||||||
|
{
|
||||||
|
QString msg = QString("Failed to connect to the host %0.").arg(QString(hostname));
|
||||||
|
QMessageBox::warning(this, "melonDS", msg);
|
||||||
|
setEnabled(true);
|
||||||
|
lan().StartDiscovery();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
lanDlg = LANDialog::openDlg(parentWidget());
|
||||||
|
|
||||||
|
auto cfg = Config::GetGlobalTable();
|
||||||
|
cfg.SetString("LAN.PlayerName", player);
|
||||||
|
Config::Save();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lan().EndDiscovery();
|
||||||
|
setMPInterface(MPInterface_Local);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::done(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartClientDialog::timerEvent(QTimerEvent *event)
|
||||||
|
{
|
||||||
|
doUpdateDiscoveryList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANStartClientDialog::doUpdateDiscoveryList()
|
||||||
|
{
|
||||||
|
auto disclist = lan().GetDiscoveryList();
|
||||||
|
|
||||||
|
QStandardItemModel* model = (QStandardItemModel*)ui->tvAvailableGames->model();
|
||||||
|
int curcount = model->rowCount();
|
||||||
|
int newcount = disclist.size();
|
||||||
|
if (curcount > newcount)
|
||||||
|
{
|
||||||
|
model->removeRows(newcount, curcount-newcount);
|
||||||
|
}
|
||||||
|
else if (curcount < newcount)
|
||||||
|
{
|
||||||
|
for (int i = curcount; i < newcount; i++)
|
||||||
|
{
|
||||||
|
QList<QStandardItem*> row;
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
model->appendRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (const auto& [key, data] : disclist)
|
||||||
|
{
|
||||||
|
model->item(i, 0)->setText(data.SessionName);
|
||||||
|
model->item(i, 0)->setData(QVariant(key));
|
||||||
|
|
||||||
|
QString plcount = QString("%0/%1").arg(data.NumPlayers).arg(data.MaxPlayers);
|
||||||
|
model->item(i, 1)->setText(plcount);
|
||||||
|
|
||||||
|
QString status;
|
||||||
|
switch (data.Status)
|
||||||
|
{
|
||||||
|
case 0: status = "Idle"; break;
|
||||||
|
case 1: status = "Playing"; break;
|
||||||
|
}
|
||||||
|
model->item(i, 2)->setText(status);
|
||||||
|
|
||||||
|
QString ip = QString("%0.%1.%2.%3").arg(key>>24).arg((key>>16)&0xFF).arg((key>>8)&0xFF).arg(key&0xFF);
|
||||||
|
model->item(i, 3)->setText(ip);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LANDialog::LANDialog(QWidget* parent) : QDialog(parent), ui(new Ui::LANDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
QStandardItemModel* model = new QStandardItemModel();
|
||||||
|
ui->tvPlayerList->setModel(model);
|
||||||
|
const QStringList header = {"#", "Player", "Status", "Ping", "IP"};
|
||||||
|
model->setHorizontalHeaderLabels(header);
|
||||||
|
|
||||||
|
timerID = startTimer(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
LANDialog::~LANDialog()
|
||||||
|
{
|
||||||
|
killTimer(timerID);
|
||||||
|
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANDialog::on_btnLeaveGame_clicked()
|
||||||
|
{
|
||||||
|
done(QDialog::Accepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANDialog::done(int r)
|
||||||
|
{
|
||||||
|
bool showwarning = true;
|
||||||
|
if (lan().GetNumPlayers() < 2)
|
||||||
|
showwarning = false;
|
||||||
|
|
||||||
|
if (showwarning)
|
||||||
|
{
|
||||||
|
if (QMessageBox::warning(this, "melonDS", "Really leave this LAN game?",
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lan().EndSession();
|
||||||
|
setMPInterface(MPInterface_Local);
|
||||||
|
|
||||||
|
QDialog::done(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANDialog::timerEvent(QTimerEvent *event)
|
||||||
|
{
|
||||||
|
doUpdatePlayerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LANDialog::doUpdatePlayerList()
|
||||||
|
{
|
||||||
|
auto playerlist = lan().GetPlayerList();
|
||||||
|
auto maxplayers = lan().GetMaxPlayers();
|
||||||
|
|
||||||
|
QStandardItemModel* model = (QStandardItemModel*)ui->tvPlayerList->model();
|
||||||
|
int curcount = model->rowCount();
|
||||||
|
int newcount = playerlist.size();
|
||||||
|
if (curcount > newcount)
|
||||||
|
{
|
||||||
|
model->removeRows(newcount, curcount-newcount);
|
||||||
|
}
|
||||||
|
else if (curcount < newcount)
|
||||||
|
{
|
||||||
|
for (int i = curcount; i < newcount; i++)
|
||||||
|
{
|
||||||
|
QList<QStandardItem*> row;
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
row.append(new QStandardItem());
|
||||||
|
model->appendRow(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (const auto& player : playerlist)
|
||||||
|
{
|
||||||
|
QString id = QString("%0/%1").arg(player.ID+1).arg(maxplayers);
|
||||||
|
model->item(i, 0)->setText(id);
|
||||||
|
|
||||||
|
QString name = player.Name;
|
||||||
|
model->item(i, 1)->setText(name);
|
||||||
|
|
||||||
|
QString status = "???";
|
||||||
|
switch (player.Status)
|
||||||
|
{
|
||||||
|
case LAN::Player_Client:
|
||||||
|
status = "Connected";
|
||||||
|
break;
|
||||||
|
case LAN::Player_Host:
|
||||||
|
status = "Game host";
|
||||||
|
break;
|
||||||
|
case LAN::Player_Connecting:
|
||||||
|
status = "Connecting";
|
||||||
|
break;
|
||||||
|
case LAN::Player_Disconnected:
|
||||||
|
status = "Connection lost";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
model->item(i, 2)->setText(status);
|
||||||
|
|
||||||
|
if (player.IsLocalPlayer)
|
||||||
|
{
|
||||||
|
model->item(i, 3)->setText("-");
|
||||||
|
model->item(i, 4)->setText("(local)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (player.Status == LAN::Player_Client ||
|
||||||
|
player.Status == LAN::Player_Host)
|
||||||
|
{
|
||||||
|
QString ping = QString("%0 ms").arg(player.Ping);
|
||||||
|
model->item(i, 3)->setText(ping);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
model->item(i, 3)->setText("-");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
u32 ip = player.Address;
|
||||||
|
|
||||||
|
QString ips = QString("%0.%1.%2.%3").arg(ip&0xFF).arg((ip>>8)&0xFF).arg((ip>>16)&0xFF).arg(ip>>24);
|
||||||
|
model->item(i, 4)->setText(ips);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LANDIALOG_H
|
||||||
|
#define LANDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QItemSelection>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class LANStartHostDialog;
|
||||||
|
class LANStartClientDialog;
|
||||||
|
class LANDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LANStartHostDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LANStartHostDialog(QWidget* parent);
|
||||||
|
~LANStartHostDialog();
|
||||||
|
|
||||||
|
static LANStartHostDialog* openDlg(QWidget* parent)
|
||||||
|
{
|
||||||
|
LANStartHostDialog* dlg = new LANStartHostDialog(parent);
|
||||||
|
dlg->open();
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void done(int r);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::LANStartHostDialog* ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LANStartClientDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LANStartClientDialog(QWidget* parent);
|
||||||
|
~LANStartClientDialog();
|
||||||
|
|
||||||
|
static LANStartClientDialog* openDlg(QWidget* parent)
|
||||||
|
{
|
||||||
|
LANStartClientDialog* dlg = new LANStartClientDialog(parent);
|
||||||
|
dlg->open();
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void timerEvent(QTimerEvent* event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onGameSelectionChanged(const QItemSelection& cur, const QItemSelection& prev);
|
||||||
|
void on_tvAvailableGames_doubleClicked(QModelIndex index);
|
||||||
|
void onDirectConnect();
|
||||||
|
void done(int r);
|
||||||
|
|
||||||
|
void doUpdateDiscoveryList();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::LANStartClientDialog* ui;
|
||||||
|
int timerID;
|
||||||
|
};
|
||||||
|
|
||||||
|
class LANDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit LANDialog(QWidget* parent);
|
||||||
|
~LANDialog();
|
||||||
|
|
||||||
|
static LANDialog* openDlg(QWidget* parent)
|
||||||
|
{
|
||||||
|
LANDialog* dlg = new LANDialog(parent);
|
||||||
|
dlg->show();
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void timerEvent(QTimerEvent* event) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void on_btnLeaveGame_clicked();
|
||||||
|
void done(int r);
|
||||||
|
|
||||||
|
void doUpdatePlayerList();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::LANDialog* ui;
|
||||||
|
int timerID;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LANDIALOG_H
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>LANDialog</class>
|
||||||
|
<widget class="QDialog" name="LANDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>522</width>
|
||||||
|
<height>391</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>LAN game - melonDS</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btnLeaveGame">
|
||||||
|
<property name="text">
|
||||||
|
<string>Leave game</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeView" name="tvPlayerList"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>LANStartClientDialog</class>
|
||||||
|
<widget class="QDialog" name="LANStartClientDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>547</width>
|
||||||
|
<height>409</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Join LAN game - melonDS</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Player name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLineEdit" name="txtPlayerName">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>150</width>
|
||||||
|
<height>0</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeView" name="tvAvailableGames">
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>LANStartClientDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>LANStartClientDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>LANStartHostDialog</class>
|
||||||
|
<widget class="QDialog" name="LANStartHostDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>389</width>
|
||||||
|
<height>228</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Host LAN game - melonDS</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Player name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="txtPlayerName"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Number of players:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QSpinBox" name="sbNumPlayers"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>LANStartHostDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>LANStartHostDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
|
@ -0,0 +1,181 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include <enet/enet.h>
|
||||||
|
|
||||||
|
#include <QStandardItemModel>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
#include "NDS.h"
|
||||||
|
#include "NDSCart.h"
|
||||||
|
#include "main.h"
|
||||||
|
//#include "IPC.h"
|
||||||
|
#include "NetplayDialog.h"
|
||||||
|
//#include "Input.h"
|
||||||
|
//#include "ROMManager.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Savestate.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
|
#include "ui_NetplayStartHostDialog.h"
|
||||||
|
#include "ui_NetplayStartClientDialog.h"
|
||||||
|
#include "ui_NetplayDialog.h"
|
||||||
|
|
||||||
|
using namespace melonDS;
|
||||||
|
|
||||||
|
|
||||||
|
extern EmuThread* emuThread;
|
||||||
|
NetplayDialog* netplayDlg;
|
||||||
|
|
||||||
|
|
||||||
|
NetplayStartHostDialog::NetplayStartHostDialog(QWidget* parent) : QDialog(parent), ui(new Ui::NetplayStartHostDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
ui->txtPort->setText("8064");
|
||||||
|
}
|
||||||
|
|
||||||
|
NetplayStartHostDialog::~NetplayStartHostDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayStartHostDialog::done(int r)
|
||||||
|
{
|
||||||
|
if (r == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
std::string player = ui->txtPlayerName->text().toStdString();
|
||||||
|
int port = ui->txtPort->text().toInt();
|
||||||
|
|
||||||
|
// TODO validate input!!
|
||||||
|
|
||||||
|
netplayDlg = NetplayDialog::openDlg(parentWidget());
|
||||||
|
|
||||||
|
Netplay::StartHost(player.c_str(), port);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::done(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NetplayStartClientDialog::NetplayStartClientDialog(QWidget* parent) : QDialog(parent), ui(new Ui::NetplayStartClientDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
ui->txtPort->setText("8064");
|
||||||
|
}
|
||||||
|
|
||||||
|
NetplayStartClientDialog::~NetplayStartClientDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayStartClientDialog::done(int r)
|
||||||
|
{
|
||||||
|
if (r == QDialog::Accepted)
|
||||||
|
{
|
||||||
|
std::string player = ui->txtPlayerName->text().toStdString();
|
||||||
|
std::string host = ui->txtIPAddress->text().toStdString();
|
||||||
|
int port = ui->txtPort->text().toInt();
|
||||||
|
|
||||||
|
// TODO validate input!!
|
||||||
|
|
||||||
|
netplayDlg = NetplayDialog::openDlg(parentWidget());
|
||||||
|
|
||||||
|
Netplay::StartClient(player.c_str(), host.c_str(), port);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDialog::done(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NetplayDialog::NetplayDialog(QWidget* parent) : QDialog(parent), ui(new Ui::NetplayDialog)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
|
QStandardItemModel* model = new QStandardItemModel();
|
||||||
|
ui->tvPlayerList->setModel(model);
|
||||||
|
|
||||||
|
connect(this, &NetplayDialog::sgUpdatePlayerList, this, &NetplayDialog::doUpdatePlayerList);
|
||||||
|
}
|
||||||
|
|
||||||
|
NetplayDialog::~NetplayDialog()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayDialog::done(int r)
|
||||||
|
{
|
||||||
|
// ???
|
||||||
|
|
||||||
|
QDialog::done(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayDialog::updatePlayerList(Netplay::Player* players, int num)
|
||||||
|
{
|
||||||
|
emit sgUpdatePlayerList(players, num);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetplayDialog::doUpdatePlayerList(Netplay::Player* players, int num)
|
||||||
|
{
|
||||||
|
QStandardItemModel* model = (QStandardItemModel*)ui->tvPlayerList->model();
|
||||||
|
|
||||||
|
model->clear();
|
||||||
|
model->setRowCount(num);
|
||||||
|
|
||||||
|
// TODO: remove IP column in final product
|
||||||
|
|
||||||
|
const QStringList header = {"#", "Player", "Status", "Ping", "IP"};
|
||||||
|
model->setHorizontalHeaderLabels(header);
|
||||||
|
|
||||||
|
for (int i = 0; i < num; i++)
|
||||||
|
{
|
||||||
|
Netplay::Player* player = &players[i];
|
||||||
|
|
||||||
|
QString id = QString("%0").arg(player->ID+1);
|
||||||
|
model->setItem(i, 0, new QStandardItem(id));
|
||||||
|
|
||||||
|
QString name = player->Name;
|
||||||
|
model->setItem(i, 1, new QStandardItem(name));
|
||||||
|
|
||||||
|
QString status;
|
||||||
|
switch (player->Status)
|
||||||
|
{
|
||||||
|
case 1: status = ""; break;
|
||||||
|
case 2: status = "Host"; break;
|
||||||
|
default: status = "ded"; break;
|
||||||
|
}
|
||||||
|
model->setItem(i, 2, new QStandardItem(status));
|
||||||
|
|
||||||
|
// TODO: ping
|
||||||
|
model->setItem(i, 3, new QStandardItem("x"));
|
||||||
|
|
||||||
|
char ip[32];
|
||||||
|
u32 addr = player->Address;
|
||||||
|
sprintf(ip, "%d.%d.%d.%d", addr&0xFF, (addr>>8)&0xFF, (addr>>16)&0xFF, addr>>24);
|
||||||
|
model->setItem(i, 4, new QStandardItem(ip));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NETPLAYDIALOG_H
|
||||||
|
#define NETPLAYDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "Netplay.h"
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class NetplayStartHostDialog;
|
||||||
|
class NetplayStartClientDialog;
|
||||||
|
class NetplayDialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
class NetplayStartHostDialog;
|
||||||
|
class NetplayStartClientDialog;
|
||||||
|
class NetplayDialog;
|
||||||
|
|
||||||
|
class NetplayStartHostDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NetplayStartHostDialog(QWidget* parent);
|
||||||
|
~NetplayStartHostDialog();
|
||||||
|
|
||||||
|
static NetplayStartHostDialog* openDlg(QWidget* parent)
|
||||||
|
{
|
||||||
|
NetplayStartHostDialog* dlg = new NetplayStartHostDialog(parent);
|
||||||
|
dlg->open();
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void done(int r);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::NetplayStartHostDialog* ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetplayStartClientDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NetplayStartClientDialog(QWidget* parent);
|
||||||
|
~NetplayStartClientDialog();
|
||||||
|
|
||||||
|
static NetplayStartClientDialog* openDlg(QWidget* parent)
|
||||||
|
{
|
||||||
|
NetplayStartClientDialog* dlg = new NetplayStartClientDialog(parent);
|
||||||
|
dlg->open();
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void done(int r);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::NetplayStartClientDialog* ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetplayDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit NetplayDialog(QWidget* parent);
|
||||||
|
~NetplayDialog();
|
||||||
|
|
||||||
|
static NetplayDialog* openDlg(QWidget* parent)
|
||||||
|
{
|
||||||
|
NetplayDialog* dlg = new NetplayDialog(parent);
|
||||||
|
dlg->show();
|
||||||
|
return dlg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePlayerList(Netplay::Player* players, int num);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void sgUpdatePlayerList(Netplay::Player* players, int num);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void done(int r);
|
||||||
|
|
||||||
|
void doUpdatePlayerList(Netplay::Player* players, int num);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::NetplayDialog* ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NETPLAYDIALOG_H
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>NetplayDialog</class>
|
||||||
|
<widget class="QDialog" name="NetplayDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>522</width>
|
||||||
|
<height>391</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>NETPLAY SHITO</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblStatus">
|
||||||
|
<property name="text">
|
||||||
|
<string>STATUS PLACEHOLDER</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTreeView" name="tvPlayerList"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>NetplayStartClientDialog</class>
|
||||||
|
<widget class="QDialog" name="NetplayStartClientDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>229</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>NETPLAY CLIENT</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Player name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Host port:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="txtPlayerName"/>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLineEdit" name="txtPort"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Host address:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="txtIPAddress"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>NetplayStartClientDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>NetplayStartClientDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
|
@ -0,0 +1,97 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>NetplayStartHostDialog</class>
|
||||||
|
<widget class="QDialog" name="NetplayStartHostDialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>229</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>NETPLAY HOST</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<property name="sizeConstraint">
|
||||||
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Player name:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Port:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLineEdit" name="txtPlayerName"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLineEdit" name="txtPort"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>NetplayStartHostDialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>NetplayStartHostDialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
|
@ -38,7 +38,7 @@
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "CameraManager.h"
|
#include "CameraManager.h"
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
#include "LocalMP.h"
|
#include "MPInterface.h"
|
||||||
#include "SPI_Firmware.h"
|
#include "SPI_Firmware.h"
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
|
@ -47,7 +47,7 @@
|
||||||
#endif // __WIN32__
|
#endif // __WIN32__
|
||||||
|
|
||||||
extern CameraManager* camManager[2];
|
extern CameraManager* camManager[2];
|
||||||
extern melonDS::LocalMP localMp;
|
|
||||||
extern melonDS::Net net;
|
extern melonDS::Net net;
|
||||||
|
|
||||||
namespace melonDS::Platform
|
namespace melonDS::Platform
|
||||||
|
@ -395,6 +395,16 @@ void Sleep(u64 usecs)
|
||||||
QThread::usleep(usecs);
|
QThread::usleep(usecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 GetMSCount()
|
||||||
|
{
|
||||||
|
return sysTimer.elapsed();
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 GetUSCount()
|
||||||
|
{
|
||||||
|
return sysTimer.nsecsElapsed() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata)
|
void WriteNDSSave(const u8* savedata, u32 savelen, u32 writeoffset, u32 writelen, void* userdata)
|
||||||
{
|
{
|
||||||
|
@ -458,55 +468,55 @@ void WriteDateTime(int year, int month, int day, int hour, int minute, int secon
|
||||||
void MP_Begin(void* userdata)
|
void MP_Begin(void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
localMp.Begin(inst);
|
MPInterface::Get().Begin(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MP_End(void* userdata)
|
void MP_End(void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
localMp.End(inst);
|
MPInterface::Get().End(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata)
|
int MP_SendPacket(u8* data, int len, u64 timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.SendPacket(inst, data, len, timestamp);
|
return MPInterface::Get().SendPacket(inst, data, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_RecvPacket(u8* data, u64* timestamp, void* userdata)
|
int MP_RecvPacket(u8* data, u64* timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.RecvPacket(inst, data, timestamp);
|
return MPInterface::Get().RecvPacket(inst, data, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata)
|
int MP_SendCmd(u8* data, int len, u64 timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.SendCmd(inst, data, len, timestamp);
|
return MPInterface::Get().SendCmd(inst, data, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata)
|
int MP_SendReply(u8* data, int len, u64 timestamp, u16 aid, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.SendReply(inst, data, len, timestamp, aid);
|
return MPInterface::Get().SendReply(inst, data, len, timestamp, aid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata)
|
int MP_SendAck(u8* data, int len, u64 timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.SendAck(inst, data, len, timestamp);
|
return MPInterface::Get().SendAck(inst, data, len, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata)
|
int MP_RecvHostPacket(u8* data, u64* timestamp, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.RecvHostPacket(inst, data, timestamp);
|
return MPInterface::Get().RecvHostPacket(inst, data, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata)
|
u16 MP_RecvReplies(u8* data, u64 timestamp, u16 aidmask, void* userdata)
|
||||||
{
|
{
|
||||||
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
int inst = ((EmuInstance*)userdata)->getInstanceID();
|
||||||
return localMp.RecvReplies(inst, data, timestamp, aidmask);
|
return MPInterface::Get().RecvReplies(inst, data, timestamp, aidmask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QDesktopServices>
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QSocketNotifier>
|
#include <QSocketNotifier>
|
||||||
|
@ -72,7 +73,8 @@
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "Savestate.h"
|
#include "Savestate.h"
|
||||||
#include "LocalMP.h"
|
#include "MPInterface.h"
|
||||||
|
#include "LANDialog.h"
|
||||||
|
|
||||||
//#include "main_shaders.h"
|
//#include "main_shaders.h"
|
||||||
|
|
||||||
|
@ -87,7 +89,6 @@ using namespace melonDS;
|
||||||
|
|
||||||
extern CameraManager* camManager[2];
|
extern CameraManager* camManager[2];
|
||||||
extern bool camStarted[2];
|
extern bool camStarted[2];
|
||||||
extern LocalMP localMp;
|
|
||||||
|
|
||||||
|
|
||||||
QString NdsRomMimeType = "application/x-nintendo-ds-rom";
|
QString NdsRomMimeType = "application/x-nintendo-ds-rom";
|
||||||
|
@ -366,6 +367,12 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
|
||||||
actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12));
|
actUndoStateLoad->setShortcut(QKeySequence(Qt::Key_F12));
|
||||||
connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad);
|
connect(actUndoStateLoad, &QAction::triggered, this, &MainWindow::onUndoStateLoad);
|
||||||
|
|
||||||
|
menu->addSeparator();
|
||||||
|
actOpenConfig = menu->addAction("Open melonDS directory");
|
||||||
|
connect(actOpenConfig, &QAction::triggered, this, [&]() {
|
||||||
|
QDesktopServices::openUrl(QUrl::fromLocalFile(emuDirectory));
|
||||||
|
});
|
||||||
|
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
|
||||||
actQuit = menu->addAction("Quit");
|
actQuit = menu->addAction("Quit");
|
||||||
|
@ -425,6 +432,25 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
|
||||||
|
|
||||||
actMPNewInstance = submenu->addAction("Launch new instance");
|
actMPNewInstance = submenu->addAction("Launch new instance");
|
||||||
connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance);
|
connect(actMPNewInstance, &QAction::triggered, this, &MainWindow::onMPNewInstance);
|
||||||
|
|
||||||
|
submenu->addSeparator();
|
||||||
|
|
||||||
|
actLANStartHost = submenu->addAction("Host LAN game");
|
||||||
|
connect(actLANStartHost, &QAction::triggered, this, &MainWindow::onLANStartHost);
|
||||||
|
|
||||||
|
actLANStartClient = submenu->addAction("Join LAN game");
|
||||||
|
connect(actLANStartClient, &QAction::triggered, this, &MainWindow::onLANStartClient);
|
||||||
|
|
||||||
|
/*submenu->addSeparator();
|
||||||
|
|
||||||
|
actNPStartHost = submenu->addAction("NETPLAY HOST");
|
||||||
|
connect(actNPStartHost, &QAction::triggered, this, &MainWindow::onNPStartHost);
|
||||||
|
|
||||||
|
actNPStartClient = submenu->addAction("NETPLAY CLIENT");
|
||||||
|
connect(actNPStartClient, &QAction::triggered, this, &MainWindow::onNPStartClient);
|
||||||
|
|
||||||
|
actNPTest = submenu->addAction("NETPLAY GO");
|
||||||
|
connect(actNPTest, &QAction::triggered, this, &MainWindow::onNPTest);*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -730,6 +756,8 @@ MainWindow::MainWindow(int id, EmuInstance* inst, QWidget* parent) :
|
||||||
|
|
||||||
QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged);
|
QObject::connect(qApp, &QApplication::applicationStateChanged, this, &MainWindow::onAppStateChanged);
|
||||||
onUpdateInterfaceSettings();
|
onUpdateInterfaceSettings();
|
||||||
|
|
||||||
|
updateMPInterface(MPInterface::GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
@ -749,24 +777,9 @@ void MainWindow::closeEvent(QCloseEvent* event)
|
||||||
QByteArray geom = saveGeometry();
|
QByteArray geom = saveGeometry();
|
||||||
QByteArray enc = geom.toBase64(QByteArray::Base64Encoding);
|
QByteArray enc = geom.toBase64(QByteArray::Base64Encoding);
|
||||||
windowCfg.SetString("Geometry", enc.toStdString());
|
windowCfg.SetString("Geometry", enc.toStdString());
|
||||||
|
|
||||||
Config::Save();
|
Config::Save();
|
||||||
|
|
||||||
if (hasOGL && (windowID == 0))
|
emuInstance->deleteWindow(windowID, false);
|
||||||
{
|
|
||||||
// we intentionally don't unpause here
|
|
||||||
emuThread->emuPause();
|
|
||||||
emuThread->deinitContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
emuThread->detachWindow(this);
|
|
||||||
|
|
||||||
if (windowID == 0)
|
|
||||||
{
|
|
||||||
int inst = emuInstance->instanceID;
|
|
||||||
deleteEmuInstance(inst);
|
|
||||||
}
|
|
||||||
|
|
||||||
QMainWindow::closeEvent(event);
|
QMainWindow::closeEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1678,6 +1691,67 @@ void MainWindow::onMPNewInstance()
|
||||||
createEmuInstance();
|
createEmuInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onLANStartHost()
|
||||||
|
{
|
||||||
|
if (!lanWarning(true)) return;
|
||||||
|
LANStartHostDialog::openDlg(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onLANStartClient()
|
||||||
|
{
|
||||||
|
if (!lanWarning(false)) return;
|
||||||
|
LANStartClientDialog::openDlg(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onNPStartHost()
|
||||||
|
{
|
||||||
|
//Netplay::StartHost();
|
||||||
|
//NetplayStartHostDialog::openDlg(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onNPStartClient()
|
||||||
|
{
|
||||||
|
//Netplay::StartClient();
|
||||||
|
//NetplayStartClientDialog::openDlg(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onNPTest()
|
||||||
|
{
|
||||||
|
// HAX
|
||||||
|
//Netplay::StartGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::updateMPInterface(MPInterfaceType type)
|
||||||
|
{
|
||||||
|
// MP interface was changed, reflect it in the UI
|
||||||
|
|
||||||
|
bool enable = (type == MPInterface_Local);
|
||||||
|
actMPNewInstance->setEnabled(enable);
|
||||||
|
actLANStartHost->setEnabled(enable);
|
||||||
|
actLANStartClient->setEnabled(enable);
|
||||||
|
/*actNPStartHost->setEnabled(enable);
|
||||||
|
actNPStartClient->setEnabled(enable);
|
||||||
|
actNPTest->setEnabled(enable);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::lanWarning(bool host)
|
||||||
|
{
|
||||||
|
if (numEmuInstances() < 2)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
QString verb = host ? "host" : "join";
|
||||||
|
QString msg = "Multiple emulator instances are currently open.\n"
|
||||||
|
"If you "+verb+" a LAN game now, all secondary instances will be closed.\n\n"
|
||||||
|
"Do you wish to continue?";
|
||||||
|
|
||||||
|
auto res = QMessageBox::warning(this, "melonDS", msg, QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
|
||||||
|
if (res == QMessageBox::No)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
deleteAllEmuInstances(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onOpenEmuSettings()
|
void MainWindow::onOpenEmuSettings()
|
||||||
{
|
{
|
||||||
emuThread->emuPause();
|
emuThread->emuPause();
|
||||||
|
@ -1833,7 +1907,7 @@ void MainWindow::onMPSettingsFinished(int res)
|
||||||
{
|
{
|
||||||
emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode");
|
emuInstance->mpAudioMode = globalCfg.GetInt("MP.AudioMode");
|
||||||
emuInstance->audioMute();
|
emuInstance->audioMute();
|
||||||
localMp.SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout"));
|
MPInterface::Get().SetRecvTimeout(globalCfg.GetInt("MP.RecvTimeout"));
|
||||||
|
|
||||||
emuThread->emuUnpause();
|
emuThread->emuUnpause();
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "Screen.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "MPInterface.h"
|
||||||
|
|
||||||
|
|
||||||
class EmuInstance;
|
class EmuInstance;
|
||||||
|
@ -125,6 +126,9 @@ public:
|
||||||
|
|
||||||
void osdAddMessage(unsigned int color, const char* msg);
|
void osdAddMessage(unsigned int color, const char* msg);
|
||||||
|
|
||||||
|
// called when the MP interface is changed
|
||||||
|
void updateMPInterface(melonDS::MPInterfaceType type);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void keyPressEvent(QKeyEvent* event) override;
|
void keyPressEvent(QKeyEvent* event) override;
|
||||||
void keyReleaseEvent(QKeyEvent* event) override;
|
void keyReleaseEvent(QKeyEvent* event) override;
|
||||||
|
@ -167,6 +171,11 @@ private slots:
|
||||||
void onRAMInfo();
|
void onRAMInfo();
|
||||||
void onOpenTitleManager();
|
void onOpenTitleManager();
|
||||||
void onMPNewInstance();
|
void onMPNewInstance();
|
||||||
|
void onLANStartHost();
|
||||||
|
void onLANStartClient();
|
||||||
|
void onNPStartHost();
|
||||||
|
void onNPStartClient();
|
||||||
|
void onNPTest();
|
||||||
|
|
||||||
void onOpenEmuSettings();
|
void onOpenEmuSettings();
|
||||||
void onEmuSettingsDialogFinished(int res);
|
void onEmuSettingsDialogFinished(int res);
|
||||||
|
@ -232,6 +241,8 @@ private:
|
||||||
|
|
||||||
void createScreenPanel();
|
void createScreenPanel();
|
||||||
|
|
||||||
|
bool lanWarning(bool host);
|
||||||
|
|
||||||
bool showOSD;
|
bool showOSD;
|
||||||
|
|
||||||
bool hasOGL;
|
bool hasOGL;
|
||||||
|
@ -264,6 +275,7 @@ public:
|
||||||
QAction* actSaveState[9];
|
QAction* actSaveState[9];
|
||||||
QAction* actLoadState[9];
|
QAction* actLoadState[9];
|
||||||
QAction* actUndoStateLoad;
|
QAction* actUndoStateLoad;
|
||||||
|
QAction* actOpenConfig;
|
||||||
QAction* actQuit;
|
QAction* actQuit;
|
||||||
|
|
||||||
QAction* actPause;
|
QAction* actPause;
|
||||||
|
@ -278,6 +290,11 @@ public:
|
||||||
QAction* actRAMInfo;
|
QAction* actRAMInfo;
|
||||||
QAction* actTitleManager;
|
QAction* actTitleManager;
|
||||||
QAction* actMPNewInstance;
|
QAction* actMPNewInstance;
|
||||||
|
QAction* actLANStartHost;
|
||||||
|
QAction* actLANStartClient;
|
||||||
|
QAction* actNPStartHost;
|
||||||
|
QAction* actNPStartClient;
|
||||||
|
QAction* actNPTest;
|
||||||
|
|
||||||
QAction* actEmuSettings;
|
QAction* actEmuSettings;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
|
|
@ -22,18 +22,14 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QStyle>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QMenuBar>
|
#include <QMenuBar>
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QPaintEvent>
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
@ -57,24 +53,14 @@
|
||||||
#include "duckstation/gl/context.h"
|
#include "duckstation/gl/context.h"
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "CheatsDialog.h"
|
|
||||||
#include "DateTimeDialog.h"
|
|
||||||
#include "EmuSettingsDialog.h"
|
|
||||||
#include "InputConfig/InputConfigDialog.h"
|
|
||||||
#include "VideoSettingsDialog.h"
|
|
||||||
#include "ROMInfoDialog.h"
|
|
||||||
#include "RAMInfoDialog.h"
|
|
||||||
#include "PowerManagement/PowerManagementDialog.h"
|
|
||||||
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "DSi.h"
|
|
||||||
|
|
||||||
#include "EmuInstance.h"
|
#include "EmuInstance.h"
|
||||||
#include "ArchiveUtil.h"
|
#include "ArchiveUtil.h"
|
||||||
#include "CameraManager.h"
|
#include "CameraManager.h"
|
||||||
#include "LocalMP.h"
|
#include "MPInterface.h"
|
||||||
#include "Net.h"
|
#include "Net.h"
|
||||||
|
|
||||||
#include "CLI.h"
|
#include "CLI.h"
|
||||||
|
@ -87,7 +73,6 @@ using namespace melonDS;
|
||||||
QString* systemThemeName;
|
QString* systemThemeName;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
QString emuDirectory;
|
QString emuDirectory;
|
||||||
|
|
||||||
const int kMaxEmuInstances = 16;
|
const int kMaxEmuInstances = 16;
|
||||||
|
@ -95,10 +80,14 @@ EmuInstance* emuInstances[kMaxEmuInstances];
|
||||||
|
|
||||||
CameraManager* camManager[2];
|
CameraManager* camManager[2];
|
||||||
bool camStarted[2];
|
bool camStarted[2];
|
||||||
LocalMP localMp;
|
|
||||||
std::optional<LibPCap> pcap;
|
std::optional<LibPCap> pcap;
|
||||||
Net net;
|
Net net;
|
||||||
|
|
||||||
|
|
||||||
|
QElapsedTimer sysTimer;
|
||||||
|
|
||||||
|
|
||||||
void NetInit()
|
void NetInit()
|
||||||
{
|
{
|
||||||
Config::Table cfg = Config::GetGlobalTable();
|
Config::Table cfg = Config::GetGlobalTable();
|
||||||
|
@ -159,12 +148,25 @@ void deleteEmuInstance(int id)
|
||||||
emuInstances[id] = nullptr;
|
emuInstances[id] = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteAllEmuInstances()
|
void deleteAllEmuInstances(int first)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < kMaxEmuInstances; i++)
|
for (int i = first; i < kMaxEmuInstances; i++)
|
||||||
deleteEmuInstance(i);
|
deleteEmuInstance(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int numEmuInstances()
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < kMaxEmuInstances; i++)
|
||||||
|
{
|
||||||
|
if (emuInstances[i])
|
||||||
|
ret++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void pathInit()
|
void pathInit()
|
||||||
{
|
{
|
||||||
|
@ -203,6 +205,28 @@ void pathInit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void setMPInterface(MPInterfaceType type)
|
||||||
|
{
|
||||||
|
// switch to the requested MP interface
|
||||||
|
MPInterface::Set(type);
|
||||||
|
|
||||||
|
// set receive timeout
|
||||||
|
// TODO: different settings per interface?
|
||||||
|
MPInterface::Get().SetRecvTimeout(Config::GetGlobalTable().GetInt("MP.RecvTimeout"));
|
||||||
|
|
||||||
|
// update UI appropriately
|
||||||
|
// TODO: decide how to deal with multi-window when it becomes a thing
|
||||||
|
for (int i = 0; i < kMaxEmuInstances; i++)
|
||||||
|
{
|
||||||
|
EmuInstance* inst = emuInstances[i];
|
||||||
|
if (!inst) continue;
|
||||||
|
|
||||||
|
MainWindow* win = inst->getMainWindow();
|
||||||
|
if (win) win->updateMPInterface(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
MelonApplication::MelonApplication(int& argc, char** argv)
|
MelonApplication::MelonApplication(int& argc, char** argv)
|
||||||
: QApplication(argc, argv)
|
: QApplication(argc, argv)
|
||||||
|
@ -237,6 +261,7 @@ bool MelonApplication::event(QEvent *event)
|
||||||
|
|
||||||
int main(int argc, char** argv)
|
int main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
|
sysTimer.start();
|
||||||
srand(time(nullptr));
|
srand(time(nullptr));
|
||||||
|
|
||||||
for (int i = 0; i < kMaxEmuInstances; i++)
|
for (int i = 0; i < kMaxEmuInstances; i++)
|
||||||
|
@ -308,7 +333,10 @@ int main(int argc, char** argv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// localMp is initialized at this point
|
// default MP interface type is local MP
|
||||||
|
// this will be changed if a LAN or netplay session is initiated
|
||||||
|
setMPInterface(MPInterface_Local);
|
||||||
|
|
||||||
NetInit();
|
NetInit();
|
||||||
|
|
||||||
createEmuInstance();
|
createEmuInstance();
|
||||||
|
|
|
@ -22,19 +22,14 @@
|
||||||
#include "glad/glad.h"
|
#include "glad/glad.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QWidget>
|
#include <QEvent>
|
||||||
#include <QWindow>
|
#include <QElapsedTimer>
|
||||||
#include <QMainWindow>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QActionGroup>
|
|
||||||
#include <QTimer>
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QCloseEvent>
|
|
||||||
|
|
||||||
#include "EmuInstance.h"
|
#include "EmuInstance.h"
|
||||||
#include "Window.h"
|
#include "Window.h"
|
||||||
#include "EmuThread.h"
|
#include "EmuThread.h"
|
||||||
#include "ScreenLayout.h"
|
#include "ScreenLayout.h"
|
||||||
|
#include "MPInterface.h"
|
||||||
|
|
||||||
class MelonApplication : public QApplication
|
class MelonApplication : public QApplication
|
||||||
{
|
{
|
||||||
|
@ -48,8 +43,13 @@ public:
|
||||||
extern QString* systemThemeName;
|
extern QString* systemThemeName;
|
||||||
extern QString emuDirectory;
|
extern QString emuDirectory;
|
||||||
|
|
||||||
|
extern QElapsedTimer sysTimer;
|
||||||
|
|
||||||
bool createEmuInstance();
|
bool createEmuInstance();
|
||||||
void deleteEmuInstance(int id);
|
void deleteEmuInstance(int id);
|
||||||
void deleteAllEmuInstances();
|
void deleteAllEmuInstances(int first = 0);
|
||||||
|
int numEmuInstances();
|
||||||
|
|
||||||
|
void setMPInterface(melonDS::MPInterfaceType type);
|
||||||
|
|
||||||
#endif // MAIN_H
|
#endif // MAIN_H
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
include(FixInterfaceIncludes)
|
||||||
|
|
||||||
add_library(net-utils STATIC
|
add_library(net-utils STATIC
|
||||||
Net.cpp
|
Net.cpp
|
||||||
Net_PCap.cpp
|
Net_PCap.cpp
|
||||||
Net_Slirp.cpp
|
Net_Slirp.cpp
|
||||||
PacketDispatcher.cpp
|
PacketDispatcher.cpp
|
||||||
LocalMP.cpp
|
LocalMP.cpp
|
||||||
|
LAN.cpp
|
||||||
|
Netplay.cpp
|
||||||
|
MPInterface.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(net-utils PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
target_include_directories(net-utils PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
|
@ -13,9 +18,18 @@ target_include_directories(net-utils PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/..")
|
||||||
option(USE_SYSTEM_LIBSLIRP "Use system libslirp instead of the bundled version" OFF)
|
option(USE_SYSTEM_LIBSLIRP "Use system libslirp instead of the bundled version" OFF)
|
||||||
if (USE_SYSTEM_LIBSLIRP)
|
if (USE_SYSTEM_LIBSLIRP)
|
||||||
pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp)
|
pkg_check_modules(Slirp REQUIRED IMPORTED_TARGET slirp)
|
||||||
target_link_libraries(net-utils PRIVATE PkgConfig::Slirp)
|
fix_interface_includes(PkgConfig::Slirp)
|
||||||
|
target_link_libraries(net-utils PUBLIC PkgConfig::Slirp)
|
||||||
else()
|
else()
|
||||||
add_subdirectory(libslirp EXCLUDE_FROM_ALL)
|
add_subdirectory(libslirp EXCLUDE_FROM_ALL)
|
||||||
target_include_directories(net-utils SYSTEM PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/libslirp/glib")
|
target_link_libraries(net-utils PUBLIC slirp)
|
||||||
target_link_libraries(net-utils PRIVATE slirp)
|
endif()
|
||||||
|
|
||||||
|
if (USE_VCPKG)
|
||||||
|
find_package(unofficial-enet CONFIG REQUIRED)
|
||||||
|
target_link_libraries(net-utils PRIVATE unofficial::enet::enet)
|
||||||
|
else()
|
||||||
|
pkg_check_modules(ENet REQUIRED IMPORTED_TARGET libenet)
|
||||||
|
fix_interface_includes(PkgConfig::ENet)
|
||||||
|
target_link_libraries(net-utils PUBLIC PkgConfig::ENet)
|
||||||
endif()
|
endif()
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,156 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LAN_H
|
||||||
|
#define LAN_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include <enet/enet.h>
|
||||||
|
|
||||||
|
#ifndef socket_t
|
||||||
|
#ifdef __WIN32__
|
||||||
|
#include <winsock2.h>
|
||||||
|
#define socket_t SOCKET
|
||||||
|
#else
|
||||||
|
#define socket_t int
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
#include "MPInterface.h"
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
|
||||||
|
class LAN : public MPInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LAN() noexcept;
|
||||||
|
LAN(const LAN&) = delete;
|
||||||
|
LAN& operator=(const LAN&) = delete;
|
||||||
|
LAN(LAN&& other) = delete;
|
||||||
|
LAN& operator=(LAN&& other) = delete;
|
||||||
|
~LAN() noexcept;
|
||||||
|
|
||||||
|
enum PlayerStatus
|
||||||
|
{
|
||||||
|
Player_None = 0, // no player in this entry
|
||||||
|
Player_Client, // game client
|
||||||
|
Player_Host, // game host
|
||||||
|
Player_Connecting, // player still connecting
|
||||||
|
Player_Disconnected, // player disconnected
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Player
|
||||||
|
{
|
||||||
|
int ID;
|
||||||
|
char Name[32];
|
||||||
|
PlayerStatus Status;
|
||||||
|
u32 Address;
|
||||||
|
|
||||||
|
bool IsLocalPlayer;
|
||||||
|
u32 Ping;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DiscoveryData
|
||||||
|
{
|
||||||
|
u32 Magic;
|
||||||
|
u32 Version;
|
||||||
|
u32 Tick;
|
||||||
|
char SessionName[64];
|
||||||
|
u8 NumPlayers;
|
||||||
|
u8 MaxPlayers;
|
||||||
|
u8 Status; // 0=idle 1=playing
|
||||||
|
};
|
||||||
|
|
||||||
|
bool StartDiscovery();
|
||||||
|
void EndDiscovery();
|
||||||
|
bool StartHost(const char* player, int numplayers);
|
||||||
|
bool StartClient(const char* player, const char* host);
|
||||||
|
void EndSession();
|
||||||
|
|
||||||
|
std::map<u32, DiscoveryData> GetDiscoveryList();
|
||||||
|
std::vector<Player> GetPlayerList();
|
||||||
|
int GetNumPlayers() { return NumPlayers; }
|
||||||
|
int GetMaxPlayers() { return MaxPlayers; }
|
||||||
|
|
||||||
|
void Process() override;
|
||||||
|
|
||||||
|
void Begin(int inst) override;
|
||||||
|
void End(int inst) override;
|
||||||
|
|
||||||
|
int SendPacket(int inst, u8* data, int len, u64 timestamp) override;
|
||||||
|
int RecvPacket(int inst, u8* data, u64* timestamp) override;
|
||||||
|
int SendCmd(int inst, u8* data, int len, u64 timestamp) override;
|
||||||
|
int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) override;
|
||||||
|
int SendAck(int inst, u8* data, int len, u64 timestamp) override;
|
||||||
|
int RecvHostPacket(int inst, u8* data, u64* timestamp) override;
|
||||||
|
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool Inited;
|
||||||
|
bool Active;
|
||||||
|
bool IsHost;
|
||||||
|
|
||||||
|
ENetHost* Host;
|
||||||
|
ENetPeer* RemotePeers[16];
|
||||||
|
|
||||||
|
socket_t DiscoverySocket;
|
||||||
|
u32 DiscoveryLastTick;
|
||||||
|
std::map<u32, DiscoveryData> DiscoveryList;
|
||||||
|
Platform::Mutex* DiscoveryMutex;
|
||||||
|
|
||||||
|
Player Players[16];
|
||||||
|
int NumPlayers;
|
||||||
|
int MaxPlayers;
|
||||||
|
Platform::Mutex* PlayersMutex;
|
||||||
|
|
||||||
|
Player MyPlayer;
|
||||||
|
u32 HostAddress;
|
||||||
|
|
||||||
|
u16 ConnectedBitmask;
|
||||||
|
|
||||||
|
int MPRecvTimeout;
|
||||||
|
int LastHostID;
|
||||||
|
ENetPeer* LastHostPeer;
|
||||||
|
std::queue<ENetPacket*> RXQueue;
|
||||||
|
|
||||||
|
u32 FrameCount;
|
||||||
|
|
||||||
|
void ProcessDiscovery();
|
||||||
|
|
||||||
|
void HostUpdatePlayerList();
|
||||||
|
void ClientUpdatePlayerList();
|
||||||
|
|
||||||
|
void ProcessHostEvent(ENetEvent& event);
|
||||||
|
void ProcessClientEvent(ENetEvent& event);
|
||||||
|
void ProcessEvent(ENetEvent& event);
|
||||||
|
void ProcessLAN(int type);
|
||||||
|
|
||||||
|
int SendPacketGeneric(u32 type, u8* packet, int len, u64 timestamp);
|
||||||
|
int RecvPacketGeneric(u8* packet, bool block, u64* timestamp);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LAN_H
|
|
@ -19,8 +19,6 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "LocalMP.h"
|
#include "LocalMP.h"
|
||||||
#include "Platform.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
using namespace melonDS;
|
using namespace melonDS;
|
||||||
using namespace melonDS::Platform;
|
using namespace melonDS::Platform;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "Platform.h"
|
#include "Platform.h"
|
||||||
|
#include "MPInterface.h"
|
||||||
|
|
||||||
namespace melonDS
|
namespace melonDS
|
||||||
{
|
{
|
||||||
|
@ -33,20 +34,11 @@ struct MPStatusData
|
||||||
u16 MPReplyBitmask; // bitmask of which clients replied in time
|
u16 MPReplyBitmask; // bitmask of which clients replied in time
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MPPacketHeader
|
|
||||||
{
|
|
||||||
u32 Magic;
|
|
||||||
u32 SenderID;
|
|
||||||
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
|
|
||||||
u32 Length;
|
|
||||||
u64 Timestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
constexpr u32 kPacketQueueSize = 0x10000;
|
constexpr u32 kPacketQueueSize = 0x10000;
|
||||||
constexpr u32 kReplyQueueSize = 0x10000;
|
constexpr u32 kReplyQueueSize = 0x10000;
|
||||||
constexpr u32 kMaxFrameSize = 0x948;
|
constexpr u32 kMaxFrameSize = 0x948;
|
||||||
|
|
||||||
class LocalMP
|
class LocalMP : public MPInterface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LocalMP() noexcept;
|
LocalMP() noexcept;
|
||||||
|
@ -56,8 +48,7 @@ public:
|
||||||
LocalMP& operator=(LocalMP&& other) = delete;
|
LocalMP& operator=(LocalMP&& other) = delete;
|
||||||
~LocalMP() noexcept;
|
~LocalMP() noexcept;
|
||||||
|
|
||||||
[[nodiscard]] int GetRecvTimeout() const noexcept { return RecvTimeout; }
|
void Process() {}
|
||||||
void SetRecvTimeout(int timeout) noexcept { RecvTimeout = timeout; }
|
|
||||||
|
|
||||||
void Begin(int inst);
|
void Begin(int inst);
|
||||||
void End(int inst);
|
void End(int inst);
|
||||||
|
@ -69,11 +60,13 @@ public:
|
||||||
int SendAck(int inst, u8* data, int len, u64 timestamp);
|
int SendAck(int inst, u8* data, int len, u64 timestamp);
|
||||||
int RecvHostPacket(int inst, u8* data, u64* timestamp);
|
int RecvHostPacket(int inst, u8* data, u64* timestamp);
|
||||||
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask);
|
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void FIFORead(int inst, int fifo, void* buf, int len) noexcept;
|
void FIFORead(int inst, int fifo, void* buf, int len) noexcept;
|
||||||
void FIFOWrite(int inst, int fifo, void* buf, int len) noexcept;
|
void FIFOWrite(int inst, int fifo, void* buf, int len) noexcept;
|
||||||
int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) noexcept;
|
int SendPacketGeneric(int inst, u32 type, u8* packet, int len, u64 timestamp) noexcept;
|
||||||
int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp) noexcept;
|
int RecvPacketGeneric(int inst, u8* packet, bool block, u64* timestamp) noexcept;
|
||||||
|
|
||||||
Platform::Mutex* MPQueueLock;
|
Platform::Mutex* MPQueueLock;
|
||||||
MPStatusData MPStatus {};
|
MPStatusData MPStatus {};
|
||||||
u8 MPPacketQueue[kPacketQueueSize] {};
|
u8 MPPacketQueue[kPacketQueueSize] {};
|
||||||
|
@ -81,8 +74,6 @@ private:
|
||||||
u32 PacketReadOffset[16] {};
|
u32 PacketReadOffset[16] {};
|
||||||
u32 ReplyReadOffset[16] {};
|
u32 ReplyReadOffset[16] {};
|
||||||
|
|
||||||
int RecvTimeout = 25;
|
|
||||||
|
|
||||||
int LastHostID = -1;
|
int LastHostID = -1;
|
||||||
Platform::Semaphore* SemPool[32] {};
|
Platform::Semaphore* SemPool[32] {};
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "MPInterface.h"
|
||||||
|
#include "LocalMP.h"
|
||||||
|
#include "LAN.h"
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
|
||||||
|
class DummyMP : public MPInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void Process() override {}
|
||||||
|
|
||||||
|
void Begin(int inst) override {}
|
||||||
|
void End(int inst) override {}
|
||||||
|
|
||||||
|
int SendPacket(int inst, u8* data, int len, u64 timestamp) override { return 0; }
|
||||||
|
int RecvPacket(int inst, u8* data, u64* timestamp) override { return 0; }
|
||||||
|
int SendCmd(int inst, u8* data, int len, u64 timestamp) override { return 0; }
|
||||||
|
int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) override { return 0; }
|
||||||
|
int SendAck(int inst, u8* data, int len, u64 timestamp) override { return 0; }
|
||||||
|
int RecvHostPacket(int inst, u8* data, u64* timestamp) override { return 0; }
|
||||||
|
u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) override { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
std::unique_ptr<MPInterface> MPInterface::Current(std::make_unique<DummyMP>());
|
||||||
|
MPInterfaceType MPInterface::CurrentType = MPInterface_Dummy;
|
||||||
|
|
||||||
|
|
||||||
|
void MPInterface::Set(MPInterfaceType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MPInterface_Local:
|
||||||
|
Current = std::make_unique<LocalMP>();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MPInterface_LAN:
|
||||||
|
Current = std::make_unique<LAN>();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Current = std::make_unique<DummyMP>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentType = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MPINTERFACE_H
|
||||||
|
#define MPINTERFACE_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
|
||||||
|
// TODO: provision for excluding unwanted interfaces at compile time
|
||||||
|
enum MPInterfaceType
|
||||||
|
{
|
||||||
|
MPInterface_Dummy = -1,
|
||||||
|
MPInterface_Local,
|
||||||
|
MPInterface_LAN,
|
||||||
|
MPInterface_Netplay,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MPPacketHeader
|
||||||
|
{
|
||||||
|
u32 Magic;
|
||||||
|
u32 SenderID;
|
||||||
|
u32 Type; // 0=regular 1=CMD 2=reply 3=ack
|
||||||
|
u32 Length;
|
||||||
|
u64 Timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MPInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~MPInterface() = default;
|
||||||
|
|
||||||
|
static MPInterface& Get() { return *Current; }
|
||||||
|
static MPInterfaceType GetType() { return CurrentType; }
|
||||||
|
static void Set(MPInterfaceType type);
|
||||||
|
|
||||||
|
[[nodiscard]] int GetRecvTimeout() const noexcept { return RecvTimeout; }
|
||||||
|
void SetRecvTimeout(int timeout) noexcept { RecvTimeout = timeout; }
|
||||||
|
|
||||||
|
// function called every video frame
|
||||||
|
virtual void Process() = 0;
|
||||||
|
|
||||||
|
virtual void Begin(int inst) = 0;
|
||||||
|
virtual void End(int inst) = 0;
|
||||||
|
|
||||||
|
virtual int SendPacket(int inst, u8* data, int len, u64 timestamp) = 0;
|
||||||
|
virtual int RecvPacket(int inst, u8* data, u64* timestamp) = 0;
|
||||||
|
virtual int SendCmd(int inst, u8* data, int len, u64 timestamp) = 0;
|
||||||
|
virtual int SendReply(int inst, u8* data, int len, u64 timestamp, u16 aid) = 0;
|
||||||
|
virtual int SendAck(int inst, u8* data, int len, u64 timestamp) = 0;
|
||||||
|
virtual int RecvHostPacket(int inst, u8* data, u64* timestamp) = 0;
|
||||||
|
virtual u16 RecvReplies(int inst, u8* data, u64 timestamp, u16 aidmask) = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int RecvTimeout = 25;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static MPInterfaceType CurrentType;
|
||||||
|
static std::unique_ptr<MPInterface> Current;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MPINTERFACE_H
|
|
@ -25,11 +25,6 @@
|
||||||
|
|
||||||
#include <libslirp.h>
|
#include <libslirp.h>
|
||||||
|
|
||||||
// "register" is indirectly used by slirp.h but isn't allowed in C++17, this is a workaround
|
|
||||||
#define register
|
|
||||||
// Needed for Slirp's definition so we can adjust the opaque pointer in the move constructor
|
|
||||||
#include <slirp.h>
|
|
||||||
|
|
||||||
#ifdef __WIN32__
|
#ifdef __WIN32__
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
#else
|
#else
|
||||||
|
@ -163,63 +158,6 @@ Net_Slirp::Net_Slirp(const Platform::SendPacketCallback& callback) noexcept : Ca
|
||||||
Ctx = slirp_new(&cfg, &cb, this);
|
Ctx = slirp_new(&cfg, &cb, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Net_Slirp::Net_Slirp(Net_Slirp&& other) noexcept
|
|
||||||
{
|
|
||||||
RXBuffer = other.RXBuffer;
|
|
||||||
IPv4ID = other.IPv4ID;
|
|
||||||
Ctx = other.Ctx;
|
|
||||||
PollListSize = other.PollListSize;
|
|
||||||
Callback = std::move(other.Callback);
|
|
||||||
memcpy(PollList, other.PollList, sizeof(PollList));
|
|
||||||
|
|
||||||
other.RXBuffer = {};
|
|
||||||
other.IPv4ID = 0;
|
|
||||||
other.Ctx = nullptr;
|
|
||||||
other.PollListSize = 0;
|
|
||||||
other.Callback = nullptr;
|
|
||||||
memset(other.PollList, 0, sizeof(other.PollList));
|
|
||||||
|
|
||||||
if (Ctx)
|
|
||||||
{
|
|
||||||
Ctx->opaque = this;
|
|
||||||
// Gotta ensure that the context doesn't try to pass around a dead object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Net_Slirp& Net_Slirp::operator=(Net_Slirp&& other) noexcept
|
|
||||||
{
|
|
||||||
if (this != &other)
|
|
||||||
{
|
|
||||||
if (Ctx)
|
|
||||||
{
|
|
||||||
slirp_cleanup(Ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
RXBuffer = other.RXBuffer;
|
|
||||||
IPv4ID = other.IPv4ID;
|
|
||||||
Ctx = other.Ctx;
|
|
||||||
PollListSize = other.PollListSize;
|
|
||||||
Callback = std::move(other.Callback);
|
|
||||||
memcpy(PollList, other.PollList, sizeof(PollList));
|
|
||||||
|
|
||||||
other.RXBuffer = {};
|
|
||||||
other.IPv4ID = 0;
|
|
||||||
other.Ctx = nullptr;
|
|
||||||
other.PollListSize = 0;
|
|
||||||
other.Callback = nullptr;
|
|
||||||
memset(other.PollList, 0, sizeof(other.PollList));
|
|
||||||
|
|
||||||
if (Ctx)
|
|
||||||
{
|
|
||||||
Ctx->opaque = this;
|
|
||||||
// Gotta ensure that the context doesn't try to pass around a dead object
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Net_Slirp::~Net_Slirp() noexcept
|
Net_Slirp::~Net_Slirp() noexcept
|
||||||
{
|
{
|
||||||
if (Ctx)
|
if (Ctx)
|
||||||
|
@ -229,7 +167,6 @@ Net_Slirp::~Net_Slirp() noexcept
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void FinishUDPFrame(u8* data, int len)
|
void FinishUDPFrame(u8* data, int len)
|
||||||
{
|
{
|
||||||
u8* ipheader = &data[0xE];
|
u8* ipheader = &data[0xE];
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS is free software: you can redistribute it and/or modify it under
|
||||||
|
the terms of the GNU General Public License as published by the Free
|
||||||
|
Software Foundation, either version 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NETPLAY_H
|
||||||
|
#define NETPLAY_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Netplay
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Player
|
||||||
|
{
|
||||||
|
int ID;
|
||||||
|
char Name[32];
|
||||||
|
int Status; // 0=no player 1=normal 2=host 3=connecting
|
||||||
|
melonDS::u32 Address;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
extern bool Active;
|
||||||
|
|
||||||
|
bool Init();
|
||||||
|
void DeInit();
|
||||||
|
|
||||||
|
void StartHost(const char* player, int port);
|
||||||
|
void StartClient(const char* player, const char* host, int port);
|
||||||
|
void StartMirror(const Player* player);
|
||||||
|
|
||||||
|
melonDS::u32 PlayerAddress(int id);
|
||||||
|
|
||||||
|
void StartGame();
|
||||||
|
void StartLocal();
|
||||||
|
|
||||||
|
void StartGame();
|
||||||
|
|
||||||
|
void ProcessFrame();
|
||||||
|
void ProcessInput();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NETPLAY_H
|
|
@ -49,9 +49,9 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/libslirp-version.h.in" "${CMAKE_
|
||||||
add_library(slirp STATIC ${SOURCES})
|
add_library(slirp STATIC ${SOURCES})
|
||||||
target_compile_definitions(slirp PUBLIC LIBSLIRP_STATIC_BUILD)
|
target_compile_definitions(slirp PUBLIC LIBSLIRP_STATIC_BUILD)
|
||||||
|
|
||||||
target_include_directories(slirp PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/glib")
|
target_include_directories(slirp SYSTEM PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/glib")
|
||||||
target_include_directories(slirp PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
target_include_directories(slirp SYSTEM PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||||
target_include_directories(slirp PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
target_include_directories(slirp SYSTEM PUBLIC "${CMAKE_CURRENT_BINARY_DIR}")
|
||||||
|
|
||||||
target_compile_definitions(slirp PRIVATE BUILDING_LIBSLIRP)
|
target_compile_definitions(slirp PRIVATE BUILDING_LIBSLIRP)
|
||||||
target_compile_definitions(slirp PRIVATE "G_LOG_DOMAIN=\"Slirp\"")
|
target_compile_definitions(slirp PRIVATE "G_LOG_DOMAIN=\"Slirp\"")
|
||||||
|
|
|
@ -68,10 +68,10 @@
|
||||||
|
|
||||||
#define GLIB_SIZEOF_VOID_P 8
|
#define GLIB_SIZEOF_VOID_P 8
|
||||||
#ifndef MAX
|
#ifndef MAX
|
||||||
#define MAX(a, b) (a > b ? a : b)
|
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
#ifndef MIN
|
#ifndef MIN
|
||||||
#define MIN(a, b) (a < b ? a : b)
|
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef TRUE
|
#ifndef TRUE
|
||||||
|
|
|
@ -77,6 +77,19 @@ def expand_load_path(lib, path)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def detect_framework(lib)
|
||||||
|
framework = lib.match(/(.*).framework/)
|
||||||
|
framework = framework.to_s if framework
|
||||||
|
|
||||||
|
if framework
|
||||||
|
fwname = File.basename(framework)
|
||||||
|
fwlib = lib.sub(framework + "/", "")
|
||||||
|
return true, framework, fwname, fwlib
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def system_path?(path)
|
def system_path?(path)
|
||||||
path.match(/^\/usr\/lib|^\/System/) != nil
|
path.match(/^\/usr\/lib|^\/System/) != nil
|
||||||
end
|
end
|
||||||
|
@ -85,9 +98,10 @@ def system_lib?(lib)
|
||||||
system_path? File.dirname(lib)
|
system_path? File.dirname(lib)
|
||||||
end
|
end
|
||||||
|
|
||||||
def install_name_tool(exec, action, path1, path2 = nil)
|
def install_name_tool(exec, *options)
|
||||||
args = ["-#{action.to_s}", path1]
|
args = options.map do |it|
|
||||||
args << path2 if path2 != nil
|
if it.is_a? Symbol then "-#{it.to_s}" else it end
|
||||||
|
end
|
||||||
|
|
||||||
Open3.popen3("install_name_tool", *args, exec) do |stdin, stdout, stderr, thread|
|
Open3.popen3("install_name_tool", *args, exec) do |stdin, stdout, stderr, thread|
|
||||||
print stdout.read
|
print stdout.read
|
||||||
|
@ -99,58 +113,68 @@ def install_name_tool(exec, action, path1, path2 = nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def strip(lib)
|
def strip(lib)
|
||||||
out, _ = Open3.capture2("strip", "-no_code_signature_warning", "-Sx", lib)
|
out, _ = Open3.capture2("xcrun", "strip", "-no_code_signature_warning", "-Sx", lib)
|
||||||
print out
|
print out
|
||||||
end
|
end
|
||||||
|
|
||||||
def fixup_libs(prog, orig_path)
|
def fixup_libs(prog, orig_path)
|
||||||
throw "fixup_libs: #{prog} doesn't exist" unless File.exist? prog
|
throw "fixup_libs: #{prog} doesn't exist" unless File.exist? prog
|
||||||
|
|
||||||
libs = get_load_libs(prog).map { |it| expand_load_path(orig_path, it) }.select { |it| not system_lib? it[0] }
|
libs = get_load_libs(prog)
|
||||||
|
.map { |it| expand_load_path(orig_path, it) }
|
||||||
|
.select { |it| not system_lib? it[0] }
|
||||||
|
|
||||||
FileUtils.chmod("u+w", prog)
|
FileUtils.chmod("u+w", prog)
|
||||||
strip prog
|
strip prog
|
||||||
|
|
||||||
|
changes = []
|
||||||
|
|
||||||
|
isfw, _, fwname, fwlib = detect_framework(prog)
|
||||||
|
if isfw then
|
||||||
|
changes += [:id, File.join("@rpath", fwname, fwlib)]
|
||||||
|
else
|
||||||
|
changes += [:id, File.join("@rpath", File.basename(prog))]
|
||||||
|
end
|
||||||
|
|
||||||
libs.each do |lib|
|
libs.each do |lib|
|
||||||
libpath, libtype = lib
|
libpath, libtype = lib
|
||||||
if File.basename(libpath) == File.basename(prog)
|
if File.basename(libpath) == File.basename(prog)
|
||||||
if libtype == :absolute
|
if libtype == :absolute
|
||||||
install_name_tool prog, :change, libpath, File.join("@rpath", File.basename(libpath))
|
changes += [:change, libpath, File.join("@rpath", File.basename(libpath))]
|
||||||
end
|
end
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
|
|
||||||
framework = libpath.match(/(.*).framework/)
|
is_framework, fwpath, fwname, fwlib = detect_framework(libpath)
|
||||||
framework = framework.to_s if framework
|
|
||||||
|
|
||||||
if framework
|
|
||||||
fwlib = libpath.sub(framework + "/", "")
|
|
||||||
fwname = File.basename(framework)
|
|
||||||
|
|
||||||
|
if is_framework
|
||||||
unless libtype == :rpath
|
unless libtype == :rpath
|
||||||
install_name_tool prog, :change, libpath, File.join("@rpath", fwname, fwlib)
|
changes += [:change, libpath, File.join("@rpath", fwname, fwlib)]
|
||||||
end
|
end
|
||||||
|
|
||||||
next if File.exist? File.join(frameworks_dir, fwname)
|
next if File.exist? File.join(frameworks_dir, fwname)
|
||||||
expath, _ = expand_load_path(orig_path, framework)
|
expath, _ = expand_load_path(orig_path, fwpath)
|
||||||
FileUtils.cp_r(expath, frameworks_dir, preserve: true)
|
FileUtils.cp_r(expath, frameworks_dir, preserve: true)
|
||||||
FileUtils.chmod_R("u+w", File.join(frameworks_dir, fwname))
|
FileUtils.chmod_R("u+w", File.join(frameworks_dir, fwname))
|
||||||
fixup_libs File.join(frameworks_dir, fwname, fwlib), libpath
|
fixup_libs File.join(frameworks_dir, fwname, fwlib), libpath
|
||||||
else
|
else
|
||||||
libname = File.basename(libpath)
|
reallibpath = File.realpath(libpath)
|
||||||
|
libname = File.basename(reallibpath)
|
||||||
dest = File.join(frameworks_dir, libname)
|
dest = File.join(frameworks_dir, libname)
|
||||||
|
|
||||||
if libtype == :absolute
|
if libtype == :absolute
|
||||||
install_name_tool prog, :change, libpath, File.join("@rpath", libname)
|
changes += [:change, libpath, File.join("@rpath", libname)]
|
||||||
end
|
end
|
||||||
|
|
||||||
next if File.exist? dest
|
next if File.exist? dest
|
||||||
expath, _ = expand_load_path(orig_path, libpath)
|
expath, _ = expand_load_path(orig_path, reallibpath)
|
||||||
FileUtils.copy expath, frameworks_dir
|
FileUtils.copy expath, frameworks_dir
|
||||||
FileUtils.chmod("u+w", dest)
|
FileUtils.chmod("u+w", dest)
|
||||||
fixup_libs dest, libpath
|
fixup_libs dest, reallibpath
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
install_name_tool(prog, *changes)
|
||||||
end
|
end
|
||||||
|
|
||||||
if ARGV[0] == "--dmg"
|
if ARGV[0] == "--dmg"
|
||||||
|
@ -176,14 +200,6 @@ unless File.exist? $bundle and File.exist? File.join($build_dir, "CMakeCache.txt
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
File.read(File.join($build_dir, "CMakeCache.txt"))
|
|
||||||
.split("\n")
|
|
||||||
.find { |it| it.match /Qt(.)_DIR:PATH=(.*)/ }
|
|
||||||
|
|
||||||
qt_major = $1
|
|
||||||
qt_dir = $2
|
|
||||||
qt_dir = File.absolute_path("#{qt_dir}/../../..")
|
|
||||||
|
|
||||||
for lib in get_load_libs(executable) do
|
for lib in get_load_libs(executable) do
|
||||||
next if system_lib? lib
|
next if system_lib? lib
|
||||||
|
|
||||||
|
@ -196,19 +212,38 @@ for lib in get_load_libs(executable) do
|
||||||
$fallback_rpaths << path unless $fallback_rpaths.include? path
|
$fallback_rpaths << path unless $fallback_rpaths.include? path
|
||||||
end
|
end
|
||||||
|
|
||||||
$fallback_rpaths << File.join(qt_dir, "lib")
|
$qt_major = nil
|
||||||
|
|
||||||
plugin_paths = [
|
qt_dirs = File.read(File.join($build_dir, "CMakeCache.txt"))
|
||||||
File.join(qt_dir, "libexec", "qt#{qt_major}", "plugins"),
|
.split("\n")
|
||||||
File.join(qt_dir, "plugins"),
|
.select { |it| it.match /^Qt([\w]+)_DIR:PATH=.*/ }
|
||||||
File.join(qt_dir, "share", "qt", "plugins")
|
.map { |dir|
|
||||||
]
|
dir.match /^Qt(5|6).*\=(.*)/
|
||||||
|
throw "Inconsistent Qt versions found." if $qt_major != nil && $qt_major != $1
|
||||||
|
$qt_major = $1
|
||||||
|
File.absolute_path("#{$2}/../../..")
|
||||||
|
}.uniq
|
||||||
|
|
||||||
qt_plugins = plugin_paths.find { |file| File.exist? file }
|
|
||||||
|
|
||||||
if qt_plugins == nil
|
def locate_plugin(dirs, plugin)
|
||||||
puts "Couldn't find Qt plugins, tried looking for:"
|
plugin_paths = [
|
||||||
plugin_paths.each { |path| puts " - #{path}" }
|
File.join("plugins", plugin),
|
||||||
|
File.join("lib", "qt-#{$qt_major}", "plugins", plugin),
|
||||||
|
File.join("libexec", "qt-#{$qt_major}", "plugins", plugin),
|
||||||
|
File.join("share", "qt", "plugins", plugin)
|
||||||
|
]
|
||||||
|
|
||||||
|
dirs.each do |dir|
|
||||||
|
plugin_paths.each do |plug|
|
||||||
|
path = File.join(dir, plug)
|
||||||
|
return path if File.exists? path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
puts "Couldn't find the required Qt plugin: #{plugin}"
|
||||||
|
puts "Tried the following prefixes: "
|
||||||
|
puts dirs.map { |dir| "- #{dir}"}.join("\n")
|
||||||
|
puts "With the following plugin paths:"
|
||||||
|
puts plugin_paths.map { |path| "- #{path}"}.join("\n")
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -217,12 +252,19 @@ fixup_libs(executable, executable)
|
||||||
|
|
||||||
bundle_plugins = File.join($bundle, "Contents", "PlugIns")
|
bundle_plugins = File.join($bundle, "Contents", "PlugIns")
|
||||||
|
|
||||||
want_plugins = ["styles/libqmacstyle.dylib", "platforms/libqcocoa.dylib", "imageformats/libqsvg.dylib"]
|
want_plugins = [
|
||||||
|
"styles/libqmacstyle.dylib",
|
||||||
|
"platforms/libqcocoa.dylib",
|
||||||
|
"imageformats/libqsvg.dylib"
|
||||||
|
]
|
||||||
|
|
||||||
want_plugins.each do |plug|
|
want_plugins.each do |plug|
|
||||||
|
pluginpath = locate_plugin(qt_dirs, plug)
|
||||||
|
|
||||||
destdir = File.join(bundle_plugins, File.dirname(plug))
|
destdir = File.join(bundle_plugins, File.dirname(plug))
|
||||||
FileUtils.mkdir_p(destdir)
|
FileUtils.mkdir_p(destdir)
|
||||||
FileUtils.copy(File.join(qt_plugins, plug), destdir)
|
FileUtils.copy(pluginpath, destdir)
|
||||||
fixup_libs File.join(bundle_plugins, plug), File.join(qt_plugins, plug)
|
fixup_libs File.join(bundle_plugins, plug), pluginpath
|
||||||
end
|
end
|
||||||
|
|
||||||
want_rpath = "@executable_path/../Frameworks"
|
want_rpath = "@executable_path/../Frameworks"
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"sdl2",
|
"sdl2",
|
||||||
"libarchive",
|
"libarchive",
|
||||||
"zstd"
|
"zstd",
|
||||||
|
"enet"
|
||||||
],
|
],
|
||||||
"features": {
|
"features": {
|
||||||
"qt6": {
|
"qt6": {
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
{
|
{
|
||||||
"name": "qtbase",
|
"name": "qtbase",
|
||||||
"default-features": false,
|
"default-features": false,
|
||||||
"features": ["gui", "png", "thread", "widgets", "opengl", "zstd"]
|
"features": ["gui", "png", "thread", "widgets", "opengl", "zstd", "harfbuzz"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "qtbase",
|
"name": "qtbase",
|
||||||
|
|
Loading…
Reference in New Issue