From 41d78db96f5ee8002956cb8f9fd0d571fbc644ef Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 12:50:50 -0400 Subject: [PATCH 1/8] linux: force portal file dialogs (NFD_PORTAL) --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c104f86..a52b053 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,8 @@ endif() if (CMAKE_SYSTEM_NAME MATCHES "Linux") option(RECOMP_FLATPAK "Configure the build for Flatpak compatibility." OFF) + # Use xdg-desktop-portal for file dialogs to avoid GTK runtime mismatches in AppImages. + set(NFD_PORTAL ON CACHE BOOL "Use xdg-desktop-portal for native file dialogs on Linux" FORCE) endif() # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: From 6951aca1e30f8acfc4cb2861f7b50a4c7031da37 Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 12:50:53 -0400 Subject: [PATCH 2/8] appimage: drop gtk plugin packaging path --- .github/linux/appimage.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/linux/appimage.sh b/.github/linux/appimage.sh index bde546e..9d2b6df 100755 --- a/.github/linux/appimage.sh +++ b/.github/linux/appimage.sh @@ -13,9 +13,8 @@ else fi curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$LINUX_DEPLOY_ARCH.AppImage" -curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/raw/master/linuxdeploy-plugin-gtk.sh" -chmod a+x linuxdeploy* +chmod a+x "linuxdeploy-$LINUX_DEPLOY_ARCH.AppImage" mkdir -p AppDir/usr/bin cp BM64Recompiled AppDir/usr/bin/ @@ -26,7 +25,7 @@ cp .github/linux/BM64Recompiled.desktop AppDir/ "./linuxdeploy-$LINUX_DEPLOY_ARCH.AppImage" --appimage-extract mv squashfs-root/ deploy -./deploy/AppRun --appdir=AppDir/ -d AppDir/BM64Recompiled.desktop -i AppDir/BM64Recompiled.png -e AppDir/usr/bin/BM64Recompiled --plugin gtk +./deploy/AppRun --appdir=AppDir/ -d AppDir/BM64Recompiled.desktop -i AppDir/BM64Recompiled.png -e AppDir/usr/bin/BM64Recompiled sed -i 's/exec/#exec/g' AppDir/AppRun echo 'if [ -f "portable.txt" ]; then' >> AppDir/AppRun echo ' APP_FOLDER_PATH=$PWD' >> AppDir/AppRun @@ -37,9 +36,4 @@ echo ' cd "$this_dir"/usr/bin/' >> AppDir/AppRun echo ' ./BM64Recompiled' >> AppDir/AppRun echo 'fi' >> AppDir/AppRun -# Remove conflicting libraries -rm -rf AppDir/usr/lib/libgmodule* -rm -rf AppDir/usr/lib/gio/modules/*.so -rm -rf AppDir/usr/lib/libwayland* - ./deploy/usr/bin/linuxdeploy-plugin-appimage --appdir=AppDir From c3c46522b97acc77b6e302cd4266010afcf9a8a3 Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 12:50:57 -0400 Subject: [PATCH 3/8] ci/docs: use dbus dev package instead of gtk dev on Linux --- .github/workflows/validate.yml | 4 ++-- BUILDING.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index e870bd3..2f31c53 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -37,7 +37,7 @@ jobs: - name: Install Linux Dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libsdl2-dev libgtk-3-dev lld llvm clang-15 libfuse2 + sudo apt-get install -y ninja-build libsdl2-dev libdbus-1-dev lld llvm clang-15 libfuse2 # Install SDL2 echo ::group::install SDL2 @@ -131,7 +131,7 @@ jobs: - name: Install Linux Dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libsdl2-dev libgtk-3-dev lld llvm clang-15 libfuse2 + sudo apt-get install -y ninja-build libsdl2-dev libdbus-1-dev lld llvm clang-15 libfuse2 # Install SDL2 echo ::group::install SDL2 diff --git a/BUILDING.md b/BUILDING.md index e1dfea4..03db6df 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -23,7 +23,7 @@ For Linux the instructions for Ubuntu are provided, but you can find the equival ```bash # For Ubuntu, simply run: -sudo apt-get install cmake ninja libsdl2-dev libgtk-3-dev lld llvm clang-15 +sudo apt-get install cmake ninja libsdl2-dev libdbus-1-dev lld llvm clang-15 ``` ### Windows From 14d01caea2abfbff9b921e75abf1cb6d9edf078b Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 16:52:32 -0400 Subject: [PATCH 4/8] Fix Linux resource path resolution independent of CWD --- src/game/config.cpp | 9 ++++++++- src/main/support.cpp | 20 +++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/game/config.cpp b/src/game/config.cpp index ee76ca5..896f83e 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -134,7 +134,14 @@ namespace recomp { } std::filesystem::path zelda64::get_app_folder_path() { - // directly check for portable.txt (windows and native linux binary) + // Prefer portable.txt next to the executable so portable mode works even + // when launched from a different working directory. + const auto program_path = zelda64::get_program_path(); + if (!program_path.empty() && std::filesystem::exists(program_path / "portable.txt")) { + return program_path; + } + + // Keep compatibility with legacy launchers that rely on the current dir. if (std::filesystem::exists("portable.txt")) { return std::filesystem::current_path(); } diff --git a/src/main/support.cpp b/src/main/support.cpp index 6bdbb74..15d5e99 100644 --- a/src/main/support.cpp +++ b/src/main/support.cpp @@ -48,10 +48,24 @@ namespace zelda64 { std::filesystem::path get_program_path() { #if defined(__APPLE__) return get_bundle_resource_directory(); -#elif defined(__linux__) && defined(RECOMP_FLATPAK) - return "/app/bin"; #else - return ""; + // Resolve resources relative to the executable location instead of the launch + // working directory so packaged builds (e.g. AppImage) work from any CWD. + static const std::filesystem::path program_path = []() -> std::filesystem::path { + char* base_path = SDL_GetBasePath(); + if (base_path != nullptr) { + std::filesystem::path ret{ base_path }; + SDL_free(base_path); + return ret; + } +#if defined(__linux__) && defined(RECOMP_FLATPAK) + return "/app/bin"; +#else + std::error_code ec; + return std::filesystem::current_path(ec); +#endif + }(); + return program_path; #endif } From 6801645499b846acfe6fedfb130f904c15b781f6 Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 17:47:16 -0400 Subject: [PATCH 5/8] fix: patch NFD portal dbus crash on Linux shutdown NFD's portal backend uses dbus_bus_get() which returns a shared connection. Calling dbus_connection_unref() on it drops the last reference and triggers a libdbus abort. This patch switches to dbus_bus_get_private() so NFD_Quit() can safely close and unref the connection. Applied as a build-time patch to avoid forking the submodule chain. --- CMakeLists.txt | 22 ++++++++++++++++++++++ nfd-portal-dbus-fix.patch | 26 ++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 nfd-portal-dbus-fix.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index a52b053..cd20173 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,28 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") option(RECOMP_FLATPAK "Configure the build for Flatpak compatibility." OFF) # Use xdg-desktop-portal for file dialogs to avoid GTK runtime mismatches in AppImages. set(NFD_PORTAL ON CACHE BOOL "Use xdg-desktop-portal for native file dialogs on Linux" FORCE) + + # Fix NFD portal dbus crash: use private connection so NFD_Quit can safely close it. + # (Upstream nativefiledialog-extended uses dbus_bus_get which returns a shared connection + # that aborts on unref. This patch switches to dbus_bus_get_private.) + set(NFD_PORTAL_SRC "${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/nfd_portal.cpp") + set(NFD_PORTAL_PATCH "${CMAKE_SOURCE_DIR}/nfd-portal-dbus-fix.patch") + execute_process( + COMMAND git apply --check --reverse ${NFD_PORTAL_PATCH} + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended" + RESULT_VARIABLE PATCH_ALREADY_APPLIED + OUTPUT_QUIET ERROR_QUIET + ) + if (NOT PATCH_ALREADY_APPLIED EQUAL 0) + execute_process( + COMMAND git apply ${NFD_PORTAL_PATCH} + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended" + RESULT_VARIABLE PATCH_RESULT + ) + if (NOT PATCH_RESULT EQUAL 0) + message(WARNING "Failed to apply nfd-portal-dbus-fix.patch") + endif() + endif() endif() # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: diff --git a/nfd-portal-dbus-fix.patch b/nfd-portal-dbus-fix.patch new file mode 100644 index 0000000..389f284 --- /dev/null +++ b/nfd-portal-dbus-fix.patch @@ -0,0 +1,26 @@ +--- a/src/nfd_portal.cpp ++++ b/src/nfd_portal.cpp +@@ -1346,8 +1346,8 @@ void NFD_ClearError(void) { + nfdresult_t NFD_Init(void) { + // Initialize dbus_err + dbus_error_init(&dbus_err); +- // Get DBus connection +- dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err); ++ // Get a private DBus connection so we can safely close and unref it in NFD_Quit ++ dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &dbus_err); + if (!dbus_conn) { + NFDi_SetError(dbus_err.message); + return NFD_ERROR; +@@ -1360,7 +1360,11 @@ nfdresult_t NFD_Init(void) { + return NFD_OKAY; + } + void NFD_Quit(void) { +- dbus_connection_unref(dbus_conn); ++ if (dbus_conn) { ++ dbus_connection_close(dbus_conn); ++ dbus_connection_unref(dbus_conn); ++ dbus_conn = nullptr; ++ } + // Note: We do not free dbus_error since NFD_Init might set it. + // To avoid leaking memory, the caller should explicitly call NFD_ClearError after reading the + // error. From a0f011ef0a83e82d8d6f330c978d389b8e584265 Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 17:48:49 -0400 Subject: [PATCH 6/8] fix: skip NFD_Quit on Linux to avoid dbus shared connection crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The NFD portal backend's dbus_bus_get() returns a shared connection. Calling dbus_connection_unref() on shutdown drops the last reference and triggers a libdbus abort. Since the process is exiting, skipping cleanup is safe — the OS reclaims the connection. Replaces the previous cmake patch approach with a simpler ifdef guard. --- CMakeLists.txt | 22 ---------------------- nfd-portal-dbus-fix.patch | 26 -------------------------- src/main/main.cpp | 4 ++++ 3 files changed, 4 insertions(+), 48 deletions(-) delete mode 100644 nfd-portal-dbus-fix.patch diff --git a/CMakeLists.txt b/CMakeLists.txt index cd20173..a52b053 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,28 +29,6 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") option(RECOMP_FLATPAK "Configure the build for Flatpak compatibility." OFF) # Use xdg-desktop-portal for file dialogs to avoid GTK runtime mismatches in AppImages. set(NFD_PORTAL ON CACHE BOOL "Use xdg-desktop-portal for native file dialogs on Linux" FORCE) - - # Fix NFD portal dbus crash: use private connection so NFD_Quit can safely close it. - # (Upstream nativefiledialog-extended uses dbus_bus_get which returns a shared connection - # that aborts on unref. This patch switches to dbus_bus_get_private.) - set(NFD_PORTAL_SRC "${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/nfd_portal.cpp") - set(NFD_PORTAL_PATCH "${CMAKE_SOURCE_DIR}/nfd-portal-dbus-fix.patch") - execute_process( - COMMAND git apply --check --reverse ${NFD_PORTAL_PATCH} - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended" - RESULT_VARIABLE PATCH_ALREADY_APPLIED - OUTPUT_QUIET ERROR_QUIET - ) - if (NOT PATCH_ALREADY_APPLIED EQUAL 0) - execute_process( - COMMAND git apply ${NFD_PORTAL_PATCH} - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended" - RESULT_VARIABLE PATCH_RESULT - ) - if (NOT PATCH_RESULT EQUAL 0) - message(WARNING "Failed to apply nfd-portal-dbus-fix.patch") - endif() - endif() endif() # Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24: diff --git a/nfd-portal-dbus-fix.patch b/nfd-portal-dbus-fix.patch deleted file mode 100644 index 389f284..0000000 --- a/nfd-portal-dbus-fix.patch +++ /dev/null @@ -1,26 +0,0 @@ ---- a/src/nfd_portal.cpp -+++ b/src/nfd_portal.cpp -@@ -1346,8 +1346,8 @@ void NFD_ClearError(void) { - nfdresult_t NFD_Init(void) { - // Initialize dbus_err - dbus_error_init(&dbus_err); -- // Get DBus connection -- dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &dbus_err); -+ // Get a private DBus connection so we can safely close and unref it in NFD_Quit -+ dbus_conn = dbus_bus_get_private(DBUS_BUS_SESSION, &dbus_err); - if (!dbus_conn) { - NFDi_SetError(dbus_err.message); - return NFD_ERROR; -@@ -1360,7 +1360,11 @@ nfdresult_t NFD_Init(void) { - return NFD_OKAY; - } - void NFD_Quit(void) { -- dbus_connection_unref(dbus_conn); -+ if (dbus_conn) { -+ dbus_connection_close(dbus_conn); -+ dbus_connection_unref(dbus_conn); -+ dbus_conn = nullptr; -+ } - // Note: We do not free dbus_error since NFD_Init might set it. - // To avoid leaking memory, the caller should explicitly call NFD_ClearError after reading the - // error. diff --git a/src/main/main.cpp b/src/main/main.cpp index 85b7514..e64666d 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -700,7 +700,11 @@ int main(int argc, char** argv) { recomp::start(cfg); + // Skip NFD_Quit on Linux: the portal backend's dbus_bus_get() returns a shared + // connection that aborts on unref. Process exit cleans up the connection safely. +#ifndef __linux__ NFD_Quit(); +#endif if (preloaded) { release_preload(preload_context); From c0e05f5c011c80d7d50a557b25b686f326c7a1f6 Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 18:00:53 -0400 Subject: [PATCH 7/8] fix: replace binary AppRun with shell script in AppImage linuxdeploy without --plugin gtk generates a binary AppRun. The previous sed/echo commands to add portable mode support were silently corrupting the binary. Replace it entirely with a shell script that sets LD_LIBRARY_PATH and handles portable mode. --- .github/linux/appimage.sh | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/.github/linux/appimage.sh b/.github/linux/appimage.sh index 9d2b6df..7ed740d 100755 --- a/.github/linux/appimage.sh +++ b/.github/linux/appimage.sh @@ -26,14 +26,20 @@ cp .github/linux/BM64Recompiled.desktop AppDir/ "./linuxdeploy-$LINUX_DEPLOY_ARCH.AppImage" --appimage-extract mv squashfs-root/ deploy ./deploy/AppRun --appdir=AppDir/ -d AppDir/BM64Recompiled.desktop -i AppDir/BM64Recompiled.png -e AppDir/usr/bin/BM64Recompiled -sed -i 's/exec/#exec/g' AppDir/AppRun -echo 'if [ -f "portable.txt" ]; then' >> AppDir/AppRun -echo ' APP_FOLDER_PATH=$PWD' >> AppDir/AppRun -echo ' cd "$this_dir"/usr/bin/' >> AppDir/AppRun -echo ' APP_FOLDER_PATH=$APP_FOLDER_PATH ./BM64Recompiled' >> AppDir/AppRun -echo 'else' >> AppDir/AppRun -echo ' cd "$this_dir"/usr/bin/' >> AppDir/AppRun -echo ' ./BM64Recompiled' >> AppDir/AppRun -echo 'fi' >> AppDir/AppRun + +# linuxdeploy generates a binary AppRun; replace it with a shell script +# so we can handle portable mode and set the correct working directory. +cat > AppDir/AppRun << 'APPRUN' +#!/bin/bash +this_dir="$(dirname "$(readlink -f "$0")")" +export LD_LIBRARY_PATH="$this_dir/usr/lib:$LD_LIBRARY_PATH" +if [ -f "portable.txt" ]; then + APP_FOLDER_PATH="$PWD" exec "$this_dir/usr/bin/BM64Recompiled" "$@" +else + cd "$this_dir/usr/bin/" + exec ./BM64Recompiled "$@" +fi +APPRUN +chmod +x AppDir/AppRun ./deploy/usr/bin/linuxdeploy-plugin-appimage --appdir=AppDir From b040574259024aed52cc6007d503abda0dd5d693 Mon Sep 17 00:00:00 2001 From: Nick Benthem Date: Mon, 23 Mar 2026 18:07:18 -0400 Subject: [PATCH 8/8] fix: remove AppRun symlink before writing shell script linuxdeploy creates AppRun as a symlink to usr/bin/BM64Recompiled. Writing to it via cat follows the symlink and overwrites the actual binary with the shell script. Remove the symlink first. --- .github/linux/appimage.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/linux/appimage.sh b/.github/linux/appimage.sh index 7ed740d..9b0d2d7 100755 --- a/.github/linux/appimage.sh +++ b/.github/linux/appimage.sh @@ -27,8 +27,9 @@ cp .github/linux/BM64Recompiled.desktop AppDir/ mv squashfs-root/ deploy ./deploy/AppRun --appdir=AppDir/ -d AppDir/BM64Recompiled.desktop -i AppDir/BM64Recompiled.png -e AppDir/usr/bin/BM64Recompiled -# linuxdeploy generates a binary AppRun; replace it with a shell script -# so we can handle portable mode and set the correct working directory. +# linuxdeploy may create AppRun as a symlink to the binary; remove it first +# so we don't overwrite the actual executable through the symlink. +rm -f AppDir/AppRun cat > AppDir/AppRun << 'APPRUN' #!/bin/bash this_dir="$(dirname "$(readlink -f "$0")")"