From 7c1cdea2ebef60ce6aa4f01718be21ce199e24e4 Mon Sep 17 00:00:00 2001 From: Shane McGovern Date: Mon, 3 Aug 2026 01:04:59 +0100 Subject: [PATCH] Sanitize picker file paths (spaces / non-ANSI) and shell prompts Issue #665: the Windows ROM and save pickers returned the raw chosen path. io.open on Windows needs ANSI bytes, so a path with accented characters (Pokemon -> Pok\x82mon, or a folder like 'Pokemon Gen1') could never be opened -- the same bug #325 already fixed for mod zips by copying to a plain-ASCII temp name. Apply that fix to the ROM and .sav pickers: each copies its pick to an ASCII temp name (pokeport_rom_pick.gb / pokeport_sav_pick.sav) before answering, exactly like chooseZip does. Also sanitize the prompt strings interpolated into the picker shell commands: '%' would be eaten as a string.format directive, and quotes would break the AppleScript/zenity argument or the surrounding shell string. Fixes #665 --- src/import/RomImporter.lua | 39 +++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index 80f37416..49e57149 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -337,6 +337,15 @@ local function commandOutput(command) return result ~= "" and result or nil end +-- Sanitize a string before it is interpolated into a picker shell command: +-- * "%" would be eaten as a string.format directive (#665); +-- * '"' would break the AppleScript / zenity double-quoted argument and +-- "'" the surrounding single-quoted shell string. +local function shellSafe(s) + s = tostring(s):gsub("%%", "%%%%") + return s:gsub('"', '\\"'):gsub("'", "''") +end + -- LOVE 11.5 on Android has no native file picker (love.window.showFileDialog -- is a LOVE 12 nightly-only addition) and never fires love.filedropped, so -- neither desktop path below works there. conf.lua points the Android save @@ -443,7 +452,7 @@ end local function chooseRom(promptName) promptName = promptName or "Pokemon" - local prompt = "Choose your " .. promptName .. " ROM" + local prompt = shellSafe("Choose your " .. promptName .. " ROM") local platform = love.system.getOS() if platform == "OS X" then return commandOutput( @@ -455,10 +464,16 @@ local function chooseRom(promptName) "$d=New-Object System.Windows.Forms.OpenFileDialog;", "$d.Title='" .. prompt .. "';", "$d.Filter='Game Boy ROM (*.gb;*.gbc)|*.gb;*.gbc|All files (*.*)|*.*';", - -- write the pick as UTF-8: the console's OEM codepage would mangle - -- non-ASCII names (Pokémon -> Pok\x82mon) and crash any text draw - -- that shows them (#325) - "if($d.ShowDialog() -eq 'OK'){[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Write($d.FileName)}", + -- copy the pick to a plain-ASCII temp name and answer with that: + -- the console's OEM codepage would mangle a non-ASCII path + -- (Pokémon -> Pok\x82mon) and io.open on Windows needs ANSI bytes, + -- so returning the original name both crashed the notice draw and + -- could never have opened the file (#325, #665) + "if($d.ShowDialog() -eq 'OK'){", + "$t=Join-Path $env:TEMP 'pokeport_rom_pick.gb';", + "Copy-Item -LiteralPath $d.FileName -Destination $t -Force;", + "[Console]::OutputEncoding=[Text.Encoding]::UTF8;", + "[Console]::Write($t)}", }) return commandOutput( 'powershell -NoProfile -STA -Command "' .. script .. '"') @@ -477,7 +492,7 @@ end -- Returns the chosen absolute path or nil. Android uses love.system.pickFile -- ("mod") instead -- see RomImporter:chooseMod. local function chooseZip() - local prompt = Strings("Choose a mod .zip") + local prompt = shellSafe(Strings("Choose a mod .zip")) local platform = love.system.getOS() if platform == "OS X" then return commandOutput( @@ -517,7 +532,7 @@ end -- dialogs). Returns the chosen absolute path or nil. Android uses -- love.system.pickFile("sav") instead -- see RomImporter:chooseSaveImport. local function chooseSav() - local prompt = Strings("Choose a .sav save file") + local prompt = shellSafe(Strings("Choose a .sav save file")) local platform = love.system.getOS() if platform == "OS X" then return commandOutput( @@ -529,8 +544,14 @@ local function chooseSav() "$d=New-Object System.Windows.Forms.OpenFileDialog;", "$d.Title='" .. prompt .. "';", "$d.Filter='Game Boy save (*.sav)|*.sav|All files (*.*)|*.*';", - -- UTF-8, like the ROM and mod pickers (#325) - "if($d.ShowDialog() -eq 'OK'){[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Write($d.FileName)}", + -- copy the pick to a plain-ASCII temp name: io.open on Windows + -- needs ANSI bytes, so a non-ASCII path (Pokémon -> Pok\x82mon) + -- could never have been opened (#325, #665) + "if($d.ShowDialog() -eq 'OK'){", + "$t=Join-Path $env:TEMP 'pokeport_sav_pick.sav';", + "Copy-Item -LiteralPath $d.FileName -Destination $t -Force;", + "[Console]::OutputEncoding=[Text.Encoding]::UTF8;", + "[Console]::Write($t)}", }) return commandOutput( 'powershell -NoProfile -STA -Command "' .. script .. '"')