From 4943d3e5f439061ffc99c6d193997f69e9202e84 Mon Sep 17 00:00:00 2001 From: Gabriel Florio Date: Tue, 28 Jul 2026 14:40:49 -0300 Subject: [PATCH 1/2] Unset LD_LIBRARY_PATH when calling system tools from AppImage --- src/core/HostShell.lua | 23 +++++++++++++++++++++++ src/import/RomImporter.lua | 3 ++- src/update/check_worker.lua | 11 ++++++----- 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 src/core/HostShell.lua diff --git a/src/core/HostShell.lua b/src/core/HostShell.lua new file mode 100644 index 00000000..2ce06b9b --- /dev/null +++ b/src/core/HostShell.lua @@ -0,0 +1,23 @@ +-- Helpers for calling host tools (curl, zenity/kdialog, ...). + +local HostShell = {} + +-- Our AppRun exports LD_LIBRARY_PATH="$APPDIR/lib:..." so every subprocess we +-- spawn tries to link against the libraries we're shipping instead of the +-- system ones. We want to unset the var so that any system tools can find +-- their proper libraries. Only needed when running in an AppImage. +function HostShell.envPrefix() + if os.getenv("APPIMAGE") then + return "env -u LD_LIBRARY_PATH " + end + return "" +end + +-- Wraps io.popen with the AppImage env fix applied and lua errors swallowed +function HostShell.popen(command, mode) + local ok, pipe = pcall(io.popen, HostShell.envPrefix() .. command, mode or "r") + if not ok or not pipe then return nil end + return pipe +end + +return HostShell diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index 5eb9318f..c604f1cf 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -1,5 +1,6 @@ local GameVersion = require("src.core.GameVersion") local Strings = require("src.core.Strings") +local HostShell = require("src.core.HostShell") local RomImporter = {} RomImporter.__index = RomImporter @@ -285,7 +286,7 @@ end local function commandOutput(command) releasePointerGrab() - local pipe = io.popen(command, "r") + local pipe = HostShell.popen(command) if not pipe then return nil end local result = pipe:read("*a") pipe:close() diff --git a/src/update/check_worker.lua b/src/update/check_worker.lua index 258428a0..24ed03a7 100644 --- a/src/update/check_worker.lua +++ b/src/update/check_worker.lua @@ -35,6 +35,7 @@ local Json = loadModule("src/link/Json.lua") local Check = loadModule("src/update/Check.lua") local Version = loadModule("src/core/Version.lua") local Semver = loadModule("src/update/Semver.lua") +local HostShell = loadModule("src/core/HostShell.lua") -- Boot's top-level require("src.update.Semver") cannot resolve in this thread -- (no src.* searcher), which would leave Boot nil and the minShell gate -- permanently permissive. Seed the loaded table first so it resolves. @@ -76,8 +77,8 @@ local function curlCapture(url) .. "-H " .. shq("User-Agent: gen1recomp-updater") .. " " .. "-H " .. shq("Accept: application/vnd.github+json") .. " " .. shq(url) - local ok, pipe = pcall(io.popen, cmd) - if not ok or not pipe then return nil end + local pipe = HostShell.popen(cmd) + if not pipe then return nil end local out = pipe:read("*a") pipe:close() if not out or out == "" then return nil end @@ -85,8 +86,8 @@ local function curlCapture(url) end local function haveCurl() - local ok, pipe = pcall(io.popen, "curl --version") - if not ok or not pipe then return false end + local pipe = HostShell.popen("curl --version") + if not pipe then return false end local out = pipe:read("*a") pipe:close() return out ~= nil and out:find("curl", 1, true) ~= nil @@ -239,7 +240,7 @@ local function launchDownload(url, partAbs, doneAbs) os.execute('start "" /b ' .. shq(saveDir .. "/" .. batRel)) else -- ( ... ) & backgrounds the whole group so os.execute returns at once - os.execute("( curl -fsSL --connect-timeout 15 --max-time 900 -o " + os.execute("( " .. HostShell.envPrefix() .. "curl -fsSL --connect-timeout 15 --max-time 900 -o " .. shq(partAbs) .. " " .. shq(url) .. " ; touch " .. shq(doneAbs) .. " ) >/dev/null 2>&1 &") end From 6a9e77cde03ada47a668470fd6b3b94b61a53840 Mon Sep 17 00:00:00 2001 From: Gabriel Florio Date: Tue, 28 Jul 2026 16:02:20 -0300 Subject: [PATCH 2/2] Fix app crashing on self-restart in the AppImage release --- src/core/Game.lua | 4 +--- src/core/HostShell.lua | 28 ++++++++++++++++++++++++++++ src/import/RomImporter.lua | 2 +- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/core/Game.lua b/src/core/Game.lua index 1bad3b9f..ca4811e7 100644 --- a/src/core/Game.lua +++ b/src/core/Game.lua @@ -391,9 +391,7 @@ end -- LÖVE process ensures scripts, registries, and assets are all rebuilt from -- the newly selected mod state. function Game:restartWithMods() - if love.event and love.event.quit then - love.event.quit("restart") - end + require("src.core.HostShell").restart() end function Game:keyreleased(key) diff --git a/src/core/HostShell.lua b/src/core/HostShell.lua index 2ce06b9b..587a22f4 100644 --- a/src/core/HostShell.lua +++ b/src/core/HostShell.lua @@ -20,4 +20,32 @@ function HostShell.popen(command, mode) return pipe end +-- Restart the whole app. The obvious love.event.quit("restart") re-runs LÖVE's +-- boot in-process, which calls love.filesystem.init a second time -- and inside +-- an AppImage physfs is already initialized, so that second init throws +-- ("Failed to initialize filesystem: already initialized") and the relaunch +-- crashes. So on an AppImage we relaunch the executable; the fresh process's +-- Boot step mounts any downloaded update exactly as a manual relaunch would. +-- On every other platform the in-process restart works, so keep it. +function HostShell.restart() + if not (love and love.event and love.event.quit) then return end + local appimage = os.getenv("APPIMAGE") + if not appimage then + love.event.quit("restart") + return + end + + -- We have to restart the process with this cursed execv call to prevent the + -- PID from changing, which might cause SteamOS and other Linux launchers to + -- think the app has crashed. + local ffi = require("ffi") + pcall(ffi.cdef, [[ + int execv(const char *path, char *const argv[]); + int unsetenv(const char *name); + ]]) + ffi.C.unsetenv("LD_LIBRARY_PATH") + local argv = ffi.new("const char *[2]", appimage, nil) + ffi.C.execv(appimage, ffi.cast("char *const *", argv)) +end + return HostShell diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index c604f1cf..99618938 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -1860,7 +1860,7 @@ function RomImporter:mousepressed(x, y, button) if action == "download" and self.Check then pcall(self.Check.download) elseif action == "restart" then - love.event.quit("restart") + HostShell.restart() elseif action == "openurl" and self.Check then love.system.openURL(self.Check.releaseUrl()) end