diff --git a/src/import/RomImporter.lua b/src/import/RomImporter.lua index 37fa0868..712790df 100644 --- a/src/import/RomImporter.lua +++ b/src/import/RomImporter.lua @@ -988,8 +988,18 @@ local function pickerHasKind(kind) return false end -local function findPendingRequiredImport() +local function findPendingRequiredImport(self) local names = { "picked_required_import.bin", "picked_stadium.z64" } + -- Builds released before required_import was added to the Android JNI bridge + -- only understand the long-standing "rom" picker kind. While a required + -- import request is in flight, it is safe to treat its staging name as a + -- dependency file: the pending IDs below select the same validation/copy + -- path as a current bridge. Never scan picked_rom.gb otherwise, since that + -- remains reserved for an ordinary game-ROM import. + if self and self.requiredImportLegacyRomPick + and self.pickerPendingKind == "required_import" then + names[#names + 1] = "picked_rom.gb" + end for _, name in ipairs(names) do if love.filesystem.getInfo(name, "file") then return name end end @@ -1471,12 +1481,16 @@ function RomImporter:focus(f) local text = "Could not read the picked file. Reopen the picker and choose " .. "it with the Files (Documents) app, or copy it into: " .. love.filesystem.getSaveDirectory() + local legacyRequiredPick = self.requiredImportLegacyRomPick + and self.pickerPendingKind == "required_import" if pickError:find("picked_required_import", 1, true) - or pickError:find("picked_stadium", 1, true) then + or pickError:find("picked_stadium", 1, true) + or (legacyRequiredPick and pickError:find("picked_rom", 1, true)) then self.modNotice = { ok = false, text = text } self.pickerPendingKind = nil self.pickerPendingModId = nil self.pickerPendingImportId = nil + self.requiredImportLegacyRomPick = nil elseif pickError:find("picked_mod", 1, true) then self.modNotice = { ok = false, text = text } elseif pickError:find("picked_save", 1, true) then @@ -1488,11 +1502,12 @@ function RomImporter:focus(f) end return end - local requiredName = findPendingRequiredImport() + local requiredName = findPendingRequiredImport(self) if requiredName then local modId, importId = self.pickerPendingModId, self.pickerPendingImportId self.pickerPendingKind = nil self.pickerPendingModId, self.pickerPendingImportId = nil, nil + self.requiredImportLegacyRomPick = nil local imported = modId and importId and self:_importRequiredSource(modId, importId, requiredName) consumePick(self, requiredName, requiredName, imported) @@ -2024,7 +2039,17 @@ function RomImporter:chooseRequiredImport(modId, importId) return end if self.nativePicker then - if self.mobileFileBridge and not pickerHasKind("required_import") then + -- Android 13+ uses the Storage Access Framework for both paths. Some + -- Android 15 installs carry the newer Lua launcher with an older native + -- bridge, however, so they do not advertise required_import yet. Fall + -- back to that bridge's known "rom" picker and quarantine its result by + -- the pending required-import IDs. iOS has a different asynchronous + -- bridge and deliberately keeps the explicit capability requirement. + local legacyAndroidPicker = self.mobileFileBridge + and love.system.getOS() == "Android" + and not pickerHasKind("required_import") + if self.mobileFileBridge and not pickerHasKind("required_import") + and not legacyAndroidPicker then requiredImportNotice(self, modId, importId, "This app build cannot pick required mod files yet. Update the app and try again.") self.modNotice = nil @@ -2033,10 +2058,12 @@ function RomImporter:chooseRequiredImport(modId, importId) self.pickerPendingKind = "required_import" self.pickerPendingModId = modId self.pickerPendingImportId = importId - if not pickFile("required_import") then + self.requiredImportLegacyRomPick = legacyAndroidPicker or nil + if not pickFile(legacyAndroidPicker and "rom" or "required_import") then self.pickerPendingKind = nil self.pickerPendingModId = nil self.pickerPendingImportId = nil + self.requiredImportLegacyRomPick = nil requiredImportNotice(self, modId, importId, "Could not open the file picker.") self.modNotice = nil elseif self.android then diff --git a/tests/rom_importer_android_mod_pick_test.lua b/tests/rom_importer_android_mod_pick_test.lua index b958e75b..bc88882f 100644 --- a/tests/rom_importer_android_mod_pick_test.lua +++ b/tests/rom_importer_android_mod_pick_test.lua @@ -166,6 +166,47 @@ eq(ri._requiredImported.importId, "source", "focus routes to the pending declara check(love.filesystem.getInfo("picked_required_import.bin") == nil, "focus removes the staged required-file pick") +-- Android releases with the updated launcher but the older native bridge do +-- not advertise required_import. They still support the established ROM SAF +-- picker, whose result must be quarantined to the pending dependency request. +love.system.pickFileKinds = function() return "rom,mod,sav" end +pickCalls = {} +ri = freshImporter({ red = true, blue = true }) +ri.nativePicker = true +ri.mobileFileBridge = true +ri.mods = { { + id = "needs_source", + manifest = { id = "needs_source", name = "Needs Source", + required_imports = { { id = "source", name = "Source", file = "source.bin", + format = "raw", md5 = { "00000000000000000000000000000000" } } } }, +} } +ri:chooseRequiredImport("needs_source", "source") +eq(pickCalls[1], "rom", "legacy Android bridge falls back to its ROM SAF picker") +check(ri.requiredImportLegacyRomPick, + "legacy Android ROM picker result is marked as a required import") +ri._importRequiredSource = function(self, modId, importId, source) + self._requiredImported = { modId = modId, importId = importId, source = source } + return true +end +love.filesystem.write("picked_rom.gb", "source bytes") +ri:focus(true) +eq(ri._requiredImported.source, "picked_rom.gb", + "legacy Android ROM staging name is routed to the required import") +eq(ri._requiredImported.modId, "needs_source", + "legacy Android picker preserves the requested mod") +check(love.filesystem.getInfo("picked_rom.gb") == nil, + "legacy Android dependency pick is removed after import") + +-- A legacy bridge reports a failed copy using that same staging basename; it +-- must stay on the dependency page rather than becoming a game-ROM error. +ri:chooseRequiredImport("needs_source", "source") +love.filesystem.write("pick_error.flag", "picked_rom.gb") +ri:focus(true) +check(ri.modNotice ~= nil and ri.modNotice.ok == false, + "legacy Android picker errors are shown as dependency import errors") +check(ri.pickerPendingKind == nil, + "legacy Android picker error clears the pending dependency request") + love.system.getOS = saved.getOS love.system.pickFile = saved.pickFile love.system.pickFileKinds = saved.pickFileKinds @@ -174,5 +215,6 @@ love.filesystem.remove("usb_mod.zip") love.filesystem.remove("picked_mod.zip") love.filesystem.remove("picked_save.sav") love.filesystem.remove("picked_required_import.bin") +love.filesystem.remove("picked_rom.gb") S.finish()