diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml index fc0857e4..1bcf2f66 100644 --- a/.github/workflows/build-ubuntu.yml +++ b/.github/workflows/build-ubuntu.yml @@ -21,7 +21,7 @@ jobs: sudo rm -f /etc/apt/sources.list.d/dotnetdev.list /etc/apt/sources.list.d/microsoft-prod.list sudo apt update 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 run: cmake -B build -G Ninja -DUSE_QT6=ON -DCMAKE_INSTALL_PREFIX=/usr - name: Build @@ -63,7 +63,7 @@ jobs: apt update apt -y full-upgrade 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,libenet}-dev:arm64 \ + {libsdl2,qt6-{base,base-private,multimedia},libqt6svg6,libarchive,libzstd,libenet}-dev:arm64 \ pkg-config dpkg-dev - name: Check out source uses: actions/checkout@v4 diff --git a/README.md b/README.md index 3d7c2561..eb8b1358 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,9 @@ As for the rest, the interface should be pretty straightforward. If you have a q ### Linux 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` - * 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` - * Arch Linux: `sudo pacman -S base-devel cmake extra-cmake-modules git libpcap sdl2 qt5-base qt5-multimedia libarchive zstd` + * 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 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 qt5-svg libarchive enet zstd` 3. Download the melonDS repository and prepare: ```bash 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 ``` #### 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: ```bash 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. #### 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: ```bash 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 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: ```zsh 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: ```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) ``` If everything went well, melonDS.app should now be in the `build` directory. #### 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 ` -../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 diff --git a/src/Wifi.cpp b/src/Wifi.cpp index b097da1e..3eaa9fd3 100644 --- a/src/Wifi.cpp +++ b/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[8] = framelen; + u16 frametype = (framectl & 0x00FF); 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 @@ -1666,7 +1678,7 @@ bool Wifi::CheckRX(int type) // 0=regular 1=MP replies 2=MP host frames RXTimestamp = 0; StartRX(); } - else if (((framectl & 0x00FF) == 0x00C0) && timestamp && macgood && IsMPClient) + else if ((frametype == 0x00C0) && timestamp && macgood && IsMPClient) { IsMP = false; IsMPClient = false; diff --git a/src/frontend/qt_sdl/CMakeLists.txt b/src/frontend/qt_sdl/CMakeLists.txt index 656b6535..9cd784aa 100644 --- a/src/frontend/qt_sdl/CMakeLists.txt +++ b/src/frontend/qt_sdl/CMakeLists.txt @@ -63,10 +63,10 @@ else() endif() 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) 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) endif() diff --git a/src/frontend/qt_sdl/Config.cpp b/src/frontend/qt_sdl/Config.cpp index 998a03ec..5997d543 100644 --- a/src/frontend/qt_sdl/Config.cpp +++ b/src/frontend/qt_sdl/Config.cpp @@ -71,7 +71,8 @@ DefaultList DefaultInts = #ifdef GDBSTUB_ENABLED {"Instance*.Gdb.ARM7.Port", 3334}, {"Instance*.Gdb.ARM9.Port", 3333}, -#endif +#endif, + {"LAN.HostNumPlayers", 16}, }; RangeList IntRanges = @@ -90,6 +91,7 @@ RangeList IntRanges = {"Instance*.Window*.ScreenAspectTop", {0, AspectRatiosNum-1}}, {"Instance*.Window*.ScreenAspectBot", {0, AspectRatiosNum-1}}, {"MP.AudioMode", {0, 2}}, + {"LAN.HostNumPlayers", {2, 16}}, }; DefaultList DefaultBools = diff --git a/src/frontend/qt_sdl/LANDialog.cpp b/src/frontend/qt_sdl/LANDialog.cpp index 58baf908..32539e3f 100644 --- a/src/frontend/qt_sdl/LANDialog.cpp +++ b/src/frontend/qt_sdl/LANDialog.cpp @@ -51,10 +51,11 @@ LANStartHostDialog::LANStartHostDialog(QWidget* parent) : QDialog(parent), ui(ne setMPInterface(MPInterface_LAN); - // TODO: remember the last setting? so this doesn't suck massively - // we could also remember the player name (and auto-init it from the firmware name or whatever) + auto cfg = Config::GetGlobalTable(); + ui->txtPlayerName->setText(cfg.GetQString("LAN.PlayerName")); + ui->sbNumPlayers->setRange(2, 16); - ui->sbNumPlayers->setValue(16); + ui->sbNumPlayers->setValue(cfg.GetInt("LAN.HostNumPlayers")); } LANStartHostDialog::~LANStartHostDialog() @@ -82,6 +83,11 @@ void LANStartHostDialog::done(int r) } lanDlg = LANDialog::openDlg(parentWidget()); + + auto cfg = Config::GetGlobalTable(); + cfg.SetString("LAN.PlayerName", player); + cfg.SetInt("LAN.HostNumPlayers", numplayers); + Config::Save(); } else { @@ -99,6 +105,9 @@ LANStartClientDialog::LANStartClientDialog(QWidget* parent) : QDialog(parent), u 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"}; @@ -209,6 +218,10 @@ void LANStartClientDialog::done(int r) setEnabled(true); lanDlg = LANDialog::openDlg(parentWidget()); + + auto cfg = Config::GetGlobalTable(); + cfg.SetString("LAN.PlayerName", player); + Config::Save(); } else { diff --git a/tools/mac-libs.rb b/tools/mac-libs.rb index e5d4dd57..e609bd8e 100755 --- a/tools/mac-libs.rb +++ b/tools/mac-libs.rb @@ -77,6 +77,19 @@ def expand_load_path(lib, path) return nil 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) path.match(/^\/usr\/lib|^\/System/) != nil end @@ -85,9 +98,10 @@ def system_lib?(lib) system_path? File.dirname(lib) end -def install_name_tool(exec, action, path1, path2 = nil) - args = ["-#{action.to_s}", path1] - args << path2 if path2 != nil +def install_name_tool(exec, *options) + args = options.map do |it| + 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| print stdout.read @@ -99,58 +113,68 @@ def install_name_tool(exec, action, path1, path2 = nil) end 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 end def fixup_libs(prog, orig_path) 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) 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| libpath, libtype = lib if File.basename(libpath) == File.basename(prog) 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 next end - framework = libpath.match(/(.*).framework/) - framework = framework.to_s if framework - - if framework - fwlib = libpath.sub(framework + "/", "") - fwname = File.basename(framework) + is_framework, fwpath, fwname, fwlib = detect_framework(libpath) + if is_framework unless libtype == :rpath - install_name_tool prog, :change, libpath, File.join("@rpath", fwname, fwlib) + changes += [:change, libpath, File.join("@rpath", fwname, fwlib)] end 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.chmod_R("u+w", File.join(frameworks_dir, fwname)) fixup_libs File.join(frameworks_dir, fwname, fwlib), libpath else - libname = File.basename(libpath) + reallibpath = File.realpath(libpath) + libname = File.basename(reallibpath) dest = File.join(frameworks_dir, libname) if libtype == :absolute - install_name_tool prog, :change, libpath, File.join("@rpath", libname) + changes += [:change, libpath, File.join("@rpath", libname)] end 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.chmod("u+w", dest) - fixup_libs dest, libpath + fixup_libs dest, reallibpath end end + + install_name_tool(prog, *changes) end if ARGV[0] == "--dmg" @@ -176,14 +200,6 @@ unless File.exist? $bundle and File.exist? File.join($build_dir, "CMakeCache.txt exit 1 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 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 end -$fallback_rpaths << File.join(qt_dir, "lib") +$qt_major = nil -plugin_paths = [ - File.join(qt_dir, "libexec", "qt#{qt_major}", "plugins"), - File.join(qt_dir, "plugins"), - File.join(qt_dir, "share", "qt", "plugins") -] +qt_dirs = File.read(File.join($build_dir, "CMakeCache.txt")) + .split("\n") + .select { |it| it.match /^Qt([\w]+)_DIR:PATH=.*/ } + .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 - puts "Couldn't find Qt plugins, tried looking for:" - plugin_paths.each { |path| puts " - #{path}" } +def locate_plugin(dirs, plugin) + plugin_paths = [ + 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 end @@ -217,12 +252,19 @@ fixup_libs(executable, executable) 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| + pluginpath = locate_plugin(qt_dirs, plug) + destdir = File.join(bundle_plugins, File.dirname(plug)) FileUtils.mkdir_p(destdir) - FileUtils.copy(File.join(qt_plugins, plug), destdir) - fixup_libs File.join(bundle_plugins, plug), File.join(qt_plugins, plug) + FileUtils.copy(pluginpath, destdir) + fixup_libs File.join(bundle_plugins, plug), pluginpath end want_rpath = "@executable_path/../Frameworks" diff --git a/vcpkg.json b/vcpkg.json index 445f3139..eb8790c8 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -13,7 +13,7 @@ { "name": "qtbase", "default-features": false, - "features": ["gui", "png", "thread", "widgets", "opengl", "zstd"] + "features": ["gui", "png", "thread", "widgets", "opengl", "zstd", "harfbuzz"] }, { "name": "qtbase",