From 9ec3ff26d8e27376295ea331f610d03f30066a1d Mon Sep 17 00:00:00 2001 From: johnjohto Date: Mon, 27 Jul 2026 22:08:57 -0400 Subject: [PATCH] Fix launcher crash on non-ASCII picked filenames (#325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows pickers shell out to PowerShell, which writes the chosen path in the console's OEM codepage (CP437 on en-US): a file named "Pokémon ...zip" came back as Pok\x82mon. The import then failed and the error notice carrying those bytes hard-crashed the mods panel's UTF-8-validating text draw. - All three Windows picker scripts (ROM, mod, save) now force [Console]::OutputEncoding to UTF-8, so returned paths and any notice built from them are valid. - The mod picker also copies the pick to a plain-ASCII temp name and returns that, so a non-ASCII filename actually imports instead of failing the io.open (Windows io.open needs ANSI bytes). --- src/import/RomImporter.lua | 19 ++++++++++++++++--- tests/engine/launcher_mods_tests.lua | 22 ++++++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index 4eb18f9f..147ff030 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -338,7 +338,10 @@ local function chooseRom(promptName) "$d=New-Object System.Windows.Forms.OpenFileDialog;", "$d.Title='" .. prompt .. "';", "$d.Filter='Game Boy ROM (*.gb)|*.gb|All files (*.*)|*.*';", - "if($d.ShowDialog() -eq 'OK'){[Console]::Write($d.FileName)}", + -- 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)}", }) return commandOutput( 'powershell -NoProfile -STA -Command "' .. script .. '"') @@ -369,7 +372,16 @@ local function chooseZip() "$d=New-Object System.Windows.Forms.OpenFileDialog;", "$d.Title='" .. prompt .. "';", "$d.Filter='Mod archive (*.zip)|*.zip|All files (*.*)|*.*';", - "if($d.ShowDialog() -eq 'OK'){[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) + "if($d.ShowDialog() -eq 'OK'){", + "$t=Join-Path $env:TEMP 'pokeport_mod_pick.zip';", + "Copy-Item -LiteralPath $d.FileName -Destination $t -Force;", + "[Console]::OutputEncoding=[Text.Encoding]::UTF8;", + "[Console]::Write($t)}", }) return commandOutput( 'powershell -NoProfile -STA -Command "' .. script .. '"') @@ -400,7 +412,8 @@ local function chooseSav() "$d=New-Object System.Windows.Forms.OpenFileDialog;", "$d.Title='" .. prompt .. "';", "$d.Filter='Game Boy save (*.sav)|*.sav|All files (*.*)|*.*';", - "if($d.ShowDialog() -eq 'OK'){[Console]::Write($d.FileName)}", + -- UTF-8, like the ROM and mod pickers (#325) + "if($d.ShowDialog() -eq 'OK'){[Console]::OutputEncoding=[Text.Encoding]::UTF8; [Console]::Write($d.FileName)}", }) return commandOutput( 'powershell -NoProfile -STA -Command "' .. script .. '"') diff --git a/tests/engine/launcher_mods_tests.lua b/tests/engine/launcher_mods_tests.lua index 9199b31d..9329944b 100644 --- a/tests/engine/launcher_mods_tests.lua +++ b/tests/engine/launcher_mods_tests.lua @@ -208,4 +208,26 @@ do check(err ~= nil, "missing-mod uninstall carries a reason") end +-- ------- issue #325: the Windows pickers must not hand back mangled paths + +do + -- PowerShell writes the pick in the console's OEM codepage by default + -- (Pokémon -> Pok\x82mon), which broke the open AND crashed the mods + -- panel's UTF-8-validating text draw. Every Windows picker script must + -- force UTF-8 output, and the mod picker must return an ASCII temp copy + -- since io.open on Windows needs ANSI bytes to open the file at all. + local f = assert(io.open("src/import/RomImporter.lua", "rb")) + local src = f:read("*a") + f:close() + local utf8, copies = 0, 0 + for _ in src:gmatch("OutputEncoding=%[Text%.Encoding%]::UTF8") do + utf8 = utf8 + 1 + end + check(utf8 >= 3, "all three Windows pickers force UTF-8 output") + check(src:find("pokeport_mod_pick.zip", 1, true) ~= nil, + "the mod picker copies the pick to an ASCII temp name") + check(src:find("Copy%-Item %-LiteralPath") ~= nil, + "the copy uses the literal picked path") +end + T.finish("launcher_mods")