mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-17 19:24:01 +02:00
CLOSES #339, CLOSES #354, CLOSES #360, CLOSES #372, CLOSES #373, CLOSES #374, CLOSES #375, CLOSES #378, CLOSES #379, CLOSES #383, CLOSES #384, CLOSES #385, CLOSES #391, CLOSES #392, CLOSES #393, CLOSES #394, CLOSES #395, CLOSES #396, CLOSES #397, CLOSES #398, CLOSES #413, CLOSES #420, CLOSES #423, CLOSES #424, CLOSES #425, CLOSES #426, CLOSES #427, CLOSES #429, CLOSES #430, CLOSES #431, CLOSES #433, CLOSES #435, CLOSES #436, CLOSES #438, CLOSES #439, CLOSES #441, CLOSES #442, CLOSES #444
This commit is contained in:
+42
-3
@@ -179,6 +179,38 @@ local function resolveUnmount()
|
||||
return physfsUnmountFn
|
||||
end
|
||||
|
||||
-- PHYSFS_getMountPoint, resolved the same way: a non-NULL return means `dir`
|
||||
-- is already somewhere in the search path. withMounted needs it because
|
||||
-- PHYSFS_mount reports success for an already-mounted directory without
|
||||
-- adding a second entry, so its unmount would drop a mount it did not make
|
||||
-- (#413).
|
||||
local physfsMountPointFn = nil
|
||||
local function resolveMountPoint()
|
||||
if physfsMountPointFn ~= nil then return physfsMountPointFn end
|
||||
physfsMountPointFn = false
|
||||
local ok, ffi = pcall(require, "ffi")
|
||||
if not ok then return physfsMountPointFn end
|
||||
pcall(ffi.cdef, "const char *PHYSFS_getMountPoint(const char *dir);")
|
||||
local libs = {
|
||||
function() return ffi.C end,
|
||||
function() return ffi.load("love") end,
|
||||
}
|
||||
for _, getlib in ipairs(libs) do
|
||||
local okl, lib = pcall(getlib)
|
||||
if okl and lib then
|
||||
local oks, fn = pcall(function() return lib.PHYSFS_getMountPoint end)
|
||||
if oks and fn then
|
||||
physfsMountPointFn = function(d)
|
||||
local okr, ret = pcall(fn, d)
|
||||
return okr and ret ~= nil and ret ~= ffi.NULL
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return physfsMountPointFn
|
||||
end
|
||||
|
||||
-- The portable game folder when the cache should live there, else nil.
|
||||
-- Resolved (and, for a fused build, mounted) once and cached. Requires a
|
||||
-- desktop portable install (SaveData) and a working windowless mkdir.
|
||||
@@ -398,13 +430,20 @@ end
|
||||
-- resolves -- which is what makes it safe to point at a folder whose contents
|
||||
-- nobody has validated.
|
||||
--
|
||||
-- Returns nil when the mount is unavailable (no ffi, no PHYSFS symbol, or the
|
||||
-- mount was refused), which callers must treat as "could not look", not as
|
||||
-- "nothing there". An error inside fn still unmounts before it propagates.
|
||||
-- Returns nil when the mount is unavailable (no ffi, no PHYSFS symbol, the
|
||||
-- mount was refused, or `dir` is already on the read path), which callers must
|
||||
-- treat as "could not look", not as "nothing there". An error inside fn still
|
||||
-- unmounts before it propagates.
|
||||
function CacheFs.withMounted(dir, mountPoint, fn)
|
||||
if not dir or dir == "" then return nil end
|
||||
local mount, unmount = resolveMount(), resolveUnmount()
|
||||
if not (mount and unmount) then return nil end
|
||||
-- A directory already in the search path cannot be borrowed: PHYSFS_mount
|
||||
-- returns success without adding an entry, and the unmount below would then
|
||||
-- remove the mount somebody else is relying on -- for the portable game
|
||||
-- folder, the one that makes its cache and mods readable at all (#413)
|
||||
local mountedAt = resolveMountPoint()
|
||||
if mountedAt and mountedAt(dir) then return nil end
|
||||
if not mount(dir, mountPoint, true) then return nil end
|
||||
local ok, res = pcall(fn)
|
||||
unmount(dir)
|
||||
|
||||
@@ -1307,6 +1307,16 @@ function RomExtractor:extractTrainers()
|
||||
}
|
||||
self:tick("Trainers", index, #order)
|
||||
end
|
||||
-- Yellow's Jessie & James fight as OPP_ROCKET but carry their own pic
|
||||
-- (home/trainers2.asm IsFightingJessieJames); only Yellow's manifest has
|
||||
-- the symbol, so Red and Blue skip this (#439).
|
||||
if self.symbols.JessieJamesPic then
|
||||
local relative = "battle/trainers/jessie_james.png"
|
||||
self:writeCompressedPic("JessieJamesPic", relative)
|
||||
if out.OPP_ROCKET then
|
||||
out.OPP_ROCKET.picJessieJames = "assets/generated/" .. relative
|
||||
end
|
||||
end
|
||||
self:write("trainers", out)
|
||||
return out
|
||||
end
|
||||
|
||||
+172
-48
@@ -35,6 +35,13 @@ local REQUIRED_FILES = {
|
||||
"assets/generated/audio/programs.bin",
|
||||
}
|
||||
|
||||
-- Files only one version's cache carries. A version that predates one of
|
||||
-- them re-imports on its own, without dragging the other versions through a
|
||||
-- CACHE_FORMAT bump.
|
||||
local VERSION_REQUIRED_FILES = {
|
||||
yellow = { "assets/generated/battle/trainers/jessie_james.png" }, -- #439
|
||||
}
|
||||
|
||||
-- "Split-screen ROM selector" first-run palette (matches FirstRun.dc.html from
|
||||
-- the Claude Design project): a dark neon arcade panel, one column per game.
|
||||
-- Red, Blue, and Yellow share the same importer flow once listed in
|
||||
@@ -99,6 +106,9 @@ local function allRequiredFilesExist(version)
|
||||
for _, path in ipairs(REQUIRED_FILES) do
|
||||
if not CacheFs.exists(path) then ok = false; break end
|
||||
end
|
||||
for _, path in ipairs(ok and VERSION_REQUIRED_FILES[version] or {}) do
|
||||
if not CacheFs.exists(path) then ok = false; break end
|
||||
end
|
||||
CacheFs.prefix = saved
|
||||
return ok
|
||||
end
|
||||
@@ -321,18 +331,46 @@ local function findPendingRom(ready)
|
||||
return nil
|
||||
end
|
||||
|
||||
-- GameActivity always writes the SAF pick to picked_rom.gb, so a leftover
|
||||
-- under that exact basename is the file the player just chose and
|
||||
-- findPendingRom silently refused: wrong size, or a hacked/overdumped cart
|
||||
-- whose SHA-1 matches no known version ([b]/[BF] dumps never will). Route it
|
||||
-- through startData so the launcher says which of the two it was instead of
|
||||
-- staying on "No ROM imported" with no message at all (issue #442), and drop
|
||||
-- the file so the next tap starts from a clean slate. A cart that is simply
|
||||
-- already imported is not an error -- #167 skips it on purpose -- so leave
|
||||
-- that one alone.
|
||||
local function consumePickedRomError(self)
|
||||
local preferred = "picked_rom.gb"
|
||||
if not love.filesystem.getInfo(preferred, "file") then return false end
|
||||
local data = love.filesystem.read(preferred)
|
||||
if type(data) == "string" and #data == 1024 * 1024 then
|
||||
local version = GameVersion.forSha1(sha1(data))
|
||||
if version and self.ready[version] then return false end
|
||||
end
|
||||
love.filesystem.remove(preferred)
|
||||
if type(data) ~= "string" then
|
||||
self:setError("The picked file could not be read. Reopen the picker and "
|
||||
.. "choose the ROM with the Files (Documents) app.")
|
||||
return true
|
||||
end
|
||||
self:startData(data, preferred)
|
||||
return true
|
||||
end
|
||||
|
||||
-- Android SAF writes mod picks to picked_mod.zip; USB copies may use any
|
||||
-- .zip basename at the save-dir root. preferAny=true also accepts those USB
|
||||
-- copies (Choose / Import); focus only consumes the SAF basename so a random
|
||||
-- leftover archive is never auto-installed on every refocus.
|
||||
local function findPendingMod(preferAny)
|
||||
local function findPendingMod(preferAny, skip)
|
||||
local preferred = "picked_mod.zip"
|
||||
if love.filesystem.getInfo(preferred, "file") then
|
||||
return preferred
|
||||
end
|
||||
if not preferAny then return nil end
|
||||
for _, name in ipairs(love.filesystem.getDirectoryItems("")) do
|
||||
if name:lower():match("%.zip$") and love.filesystem.getInfo(name, "file") then
|
||||
if name:lower():match("%.zip$") and not (skip and skip[name])
|
||||
and love.filesystem.getInfo(name, "file") then
|
||||
return name
|
||||
end
|
||||
end
|
||||
@@ -340,20 +378,36 @@ local function findPendingMod(preferAny)
|
||||
end
|
||||
|
||||
-- Same pattern as findPendingMod for battery saves (picked_save.sav / *.sav).
|
||||
local function findPendingSav(preferAny)
|
||||
local function findPendingSav(preferAny, skip)
|
||||
local preferred = "picked_save.sav"
|
||||
if love.filesystem.getInfo(preferred, "file") then
|
||||
return preferred
|
||||
end
|
||||
if not preferAny then return nil end
|
||||
for _, name in ipairs(love.filesystem.getDirectoryItems("")) do
|
||||
if name:lower():match("%.sav$") and love.filesystem.getInfo(name, "file") then
|
||||
if name:lower():match("%.sav$") and not (skip and skip[name])
|
||||
and love.filesystem.getInfo(name, "file") then
|
||||
return name
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Retire an Android pick once it has been through the installer / importer,
|
||||
-- whether or not it worked: a pick left on disk wins the scans above forever,
|
||||
-- so the next tap re-runs the same failing file and the picker never reopens
|
||||
-- (#420). The SAF basename is GameActivity's own copy of the pick and is
|
||||
-- always deleted; a USB copy is the player's file, so a failed one is only
|
||||
-- skipped for the rest of the session.
|
||||
local function consumePick(self, name, safName, ok)
|
||||
if ok or name == safName then
|
||||
love.filesystem.remove(name)
|
||||
return
|
||||
end
|
||||
self.pickSkip = self.pickSkip or {}
|
||||
self.pickSkip[name] = true
|
||||
end
|
||||
|
||||
local function chooseRom(promptName)
|
||||
promptName = promptName or "Pokemon"
|
||||
local prompt = "Choose your " .. promptName .. " ROM"
|
||||
@@ -568,7 +622,13 @@ function RomImporter.new(onComplete, opts)
|
||||
end
|
||||
if android and needRom then
|
||||
local name, data = findPendingRom(self.ready)
|
||||
if name then self:startData(data, name) end
|
||||
if name then
|
||||
self:startData(data, name)
|
||||
else
|
||||
-- The picker runs as its own activity and Android may kill us while it
|
||||
-- is up, so a rejected pick can outlive the focus handler (#442).
|
||||
consumePickedRomError(self)
|
||||
end
|
||||
end
|
||||
|
||||
-- Mouse-wheel scroll for the save-slot / mods lists. main.lua (off limits)
|
||||
@@ -629,28 +689,53 @@ function RomImporter:focus(f)
|
||||
if self.tab == "mods" then self.tab = version end
|
||||
return
|
||||
end
|
||||
local modName = findPendingMod(false)
|
||||
if modName then
|
||||
self:_installMod(modName)
|
||||
if self.modNotice and self.modNotice.ok then
|
||||
love.filesystem.remove(modName)
|
||||
-- The SAF pick failed inside GameActivity, which wrote pick_error.flag with
|
||||
-- the destination basename in it: some OEM shells (ColorOS) let a third-party
|
||||
-- archive manager win the ACTION_OPEN_DOCUMENT chooser and hand back a URI
|
||||
-- this app has no permission to read, and until #442 that returned to a
|
||||
-- launcher that said nothing at all.
|
||||
local pickError = love.filesystem.getInfo("pick_error.flag", "file")
|
||||
and love.filesystem.read("pick_error.flag")
|
||||
if pickError then
|
||||
love.filesystem.remove("pick_error.flag")
|
||||
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()
|
||||
if pickError:find("picked_mod", 1, true) then
|
||||
self.modNotice = { ok = false, text = text }
|
||||
elseif pickError:find("picked_save", 1, true) then
|
||||
local version = self.androidPendingVersion or self:_savedropTarget()
|
||||
self.androidPendingVersion = nil
|
||||
self.saveNotice[version] = { ok = false, text = text }
|
||||
else
|
||||
self:setError(text)
|
||||
end
|
||||
return
|
||||
end
|
||||
local savName = findPendingSav(false)
|
||||
local modName = findPendingMod(false, self.pickSkip)
|
||||
if modName then
|
||||
self:_installMod(modName)
|
||||
consumePick(self, modName, "picked_mod.zip",
|
||||
self.modNotice and self.modNotice.ok)
|
||||
return
|
||||
end
|
||||
local savName = findPendingSav(false, self.pickSkip)
|
||||
if savName then
|
||||
local version = self.androidPendingVersion or self:_savedropTarget()
|
||||
self.androidPendingVersion = nil
|
||||
self:_importSave(version, savName)
|
||||
if self.saveNotice[version] and self.saveNotice[version].ok then
|
||||
love.filesystem.remove(savName)
|
||||
end
|
||||
consumePick(self, savName, "picked_save.sav",
|
||||
self.saveNotice[version] and self.saveNotice[version].ok)
|
||||
return
|
||||
end
|
||||
for _, v in ipairs(GameVersion.ORDER) do
|
||||
if not self.ready[v] then
|
||||
local name, data = findPendingRom(self.ready)
|
||||
if name then self:startData(data, name) end
|
||||
if name then
|
||||
self:startData(data, name)
|
||||
else
|
||||
consumePickedRomError(self)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -698,8 +783,9 @@ function RomImporter:startData(data, displayName)
|
||||
local actualHash = sha1(data)
|
||||
local version = GameVersion.forSha1(actualHash)
|
||||
if not version then
|
||||
self:setError(("Unsupported ROM (SHA-1 %s). Use an unmodified US Pokemon "
|
||||
.. "Red, Blue, or Yellow ROM."):format(actualHash))
|
||||
self:setError(("Unsupported ROM (SHA-1 %s). This needs a clean US Pokemon "
|
||||
.. "Red, Blue, or Yellow dump; patched, trimmed or \"fixed\" dumps "
|
||||
.. "(tagged [b] or [BF]) never verify."):format(actualHash))
|
||||
return
|
||||
end
|
||||
local info = GameVersion.info(version)
|
||||
@@ -849,12 +935,11 @@ end
|
||||
function RomImporter:chooseMod()
|
||||
if self.workState == "working" then return end
|
||||
if self.android then
|
||||
local name = findPendingMod(true)
|
||||
local name = findPendingMod(true, self.pickSkip)
|
||||
if name then
|
||||
self:_installMod(name)
|
||||
if self.modNotice and self.modNotice.ok then
|
||||
love.filesystem.remove(name)
|
||||
end
|
||||
consumePick(self, name, "picked_mod.zip",
|
||||
self.modNotice and self.modNotice.ok)
|
||||
return
|
||||
end
|
||||
if not love.system.pickFile("mod") then
|
||||
@@ -908,13 +993,12 @@ end
|
||||
function RomImporter:chooseSaveImport(version)
|
||||
if self.workState == "working" then return end
|
||||
if self.android then
|
||||
local name = findPendingSav(true)
|
||||
local name = findPendingSav(true, self.pickSkip)
|
||||
if name then
|
||||
self.androidPendingVersion = version
|
||||
self:_importSave(version, name)
|
||||
if self.saveNotice[version] and self.saveNotice[version].ok then
|
||||
love.filesystem.remove(name)
|
||||
end
|
||||
consumePick(self, name, "picked_save.sav",
|
||||
self.saveNotice[version] and self.saveNotice[version].ok)
|
||||
return
|
||||
end
|
||||
self.androidPendingVersion = version
|
||||
@@ -999,6 +1083,8 @@ function RomImporter:choose(version)
|
||||
local name, data = findPendingRom(self.ready)
|
||||
if name then
|
||||
self:startData(data, name)
|
||||
elseif consumePickedRomError(self) then
|
||||
return -- a rejected pick explains itself instead of silently reopening
|
||||
elseif not love.system.pickFile() then
|
||||
-- Picker unavailable (API < 19, or no document-picker app installed):
|
||||
-- fall back to the USB folder-drop path as a friendly notice, not an
|
||||
@@ -1415,6 +1501,32 @@ function RomImporter.pageScrollFor(naturalH, viewportH, scroll)
|
||||
return maxPage > 0, clamp(scroll or 0, 0, maxPage), maxPage
|
||||
end
|
||||
|
||||
-- Every hit rect mousepressed dispatches on, cleared before any panel draws:
|
||||
-- each rect is rebuilt only by the panel that draws its control, so whatever a
|
||||
-- frame does not draw must not stay clickable. Missing the Delete rects here
|
||||
-- let a click on the mods tab land on the game tab's save Delete label (#433).
|
||||
function RomImporter:_resetFrameRects()
|
||||
self.romButtonRect = nil
|
||||
self.playButtonRect = nil
|
||||
self.tabRects = {}
|
||||
-- Rebuilt only by the active version's SAVE SLOT panel, so the mods tab (or a
|
||||
-- version with no panel drawn this frame) cannot inherit last frame's rows.
|
||||
self.slotRects = nil
|
||||
self.slotEditRects = nil
|
||||
self.slotDeleteRects = nil
|
||||
self.newSlotRect = nil
|
||||
-- Rebuilt only by the mods panel; nil elsewhere so a game tab cannot inherit
|
||||
-- last frame's mod toggles / Delete labels / import button.
|
||||
self.modRects = nil
|
||||
self.modDeleteRects = nil
|
||||
self.modImportRect = nil
|
||||
-- Rebuilt only by the active game panel's SAVE FILES card; nil elsewhere so
|
||||
-- the mods tab cannot inherit last frame's save Import/Export/open-folder hits.
|
||||
self.saveImportRect = nil
|
||||
self.saveExportRect = nil
|
||||
self.saveFolderRect = nil
|
||||
end
|
||||
|
||||
function RomImporter:draw()
|
||||
local width, height = love.graphics.getDimensions()
|
||||
local s = clamp(height / 768, 0.7, 1.6)
|
||||
@@ -1433,23 +1545,7 @@ function RomImporter:draw()
|
||||
end
|
||||
self._hoverEnabled = self._padCursorActive or not self.android
|
||||
self._anyHover = false
|
||||
self.romButtonRect = nil
|
||||
self.playButtonRect = nil
|
||||
self.tabRects = {}
|
||||
-- Rebuilt only by the active version's SAVE SLOT panel, so the mods tab (or a
|
||||
-- version with no panel drawn this frame) cannot inherit last frame's rows.
|
||||
self.slotRects = nil
|
||||
self.slotEditRects = nil
|
||||
self.newSlotRect = nil
|
||||
-- Rebuilt only by the mods panel; nil elsewhere so a game tab cannot inherit
|
||||
-- last frame's mod toggles / import button.
|
||||
self.modRects = nil
|
||||
self.modImportRect = nil
|
||||
-- Rebuilt only by the active game panel's SAVE FILES card; nil elsewhere so
|
||||
-- the mods tab cannot inherit last frame's save Import/Export/open-folder hits.
|
||||
self.saveImportRect = nil
|
||||
self.saveExportRect = nil
|
||||
self.saveFolderRect = nil
|
||||
self:_resetFrameRects()
|
||||
|
||||
-- Fonts + size-dependent scenery, rebuilt only when the window size changes.
|
||||
local fontKey = ("%dx%d"):format(width, height)
|
||||
@@ -1936,6 +2032,16 @@ local function inside(r, x, y)
|
||||
return true
|
||||
end
|
||||
|
||||
-- A Delete label is armed by one click and commits on a second one on the same
|
||||
-- target; it disarms on any other press and after this many seconds, because
|
||||
-- nothing in the launcher can undo a delete (#433).
|
||||
local DELETE_CONFIRM_SECONDS = 4
|
||||
|
||||
local function armedDelete(a, kind, id, version)
|
||||
return a ~= nil and a.kind == kind and a.id == id and a.version == version
|
||||
and (love.timer.getTime() - a.t) <= DELETE_CONFIRM_SECONDS
|
||||
end
|
||||
|
||||
function RomImporter:mousepressed(x, y, button)
|
||||
if self._rename then return end -- the rename modal swallows all clicks
|
||||
-- Whether a press can be ARMED and resolved on release, which needs a
|
||||
@@ -1955,6 +2061,10 @@ function RomImporter:mousepressed(x, y, button)
|
||||
return
|
||||
end
|
||||
if button ~= 1 then return end
|
||||
-- Any press that is not the second click on an armed Delete disarms it, so
|
||||
-- take the arm off self up front and let the Delete loops below re-arm.
|
||||
local armed = self._confirmDelete
|
||||
self._confirmDelete = nil
|
||||
if inside(self.bcgButton, x, y) or inside(self.linkUrlRect, x, y) then
|
||||
love.system.openURL(COMMUNITY_URL)
|
||||
return
|
||||
@@ -2019,7 +2129,12 @@ function RomImporter:mousepressed(x, y, button)
|
||||
-- targets, no scroll conflict).
|
||||
for _, r in ipairs(self.slotDeleteRects or {}) do
|
||||
if inside(r, x, y) then
|
||||
self:_deleteSlot(self.panelVersion, r.id)
|
||||
if armedDelete(armed, "slot", r.id, self.panelVersion) then
|
||||
self:_deleteSlot(self.panelVersion, r.id)
|
||||
else
|
||||
self._confirmDelete = { kind = "slot", id = r.id,
|
||||
version = self.panelVersion, t = love.timer.getTime() }
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -2053,7 +2168,11 @@ function RomImporter:mousepressed(x, y, button)
|
||||
end
|
||||
for _, r in ipairs(self.modDeleteRects or {}) do
|
||||
if inside(r, x, y) then
|
||||
self:_deleteMod(r.id)
|
||||
if armedDelete(armed, "mod", r.id, nil) then
|
||||
self:_deleteMod(r.id)
|
||||
else
|
||||
self._confirmDelete = { kind = "mod", id = r.id, t = love.timer.getTime() }
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -2792,8 +2911,11 @@ function RomImporter:_drawSaveSlotPanel(version, x, y, w, h, paged)
|
||||
local drect = { x = delX - 6 * s, y = delY - 4 * s,
|
||||
width = delW + 12 * s, height = delH + 8 * s, id = slot.id }
|
||||
local dhot = self:_hover(drect)
|
||||
col(dhot and PAL.red or PAL.warning)
|
||||
love.graphics.print(delText, delX, delY)
|
||||
-- Armed by a first click, the label asks before a second one commits;
|
||||
-- the box stays sized to "Delete" so the row never reflows (#433).
|
||||
local darmed = armedDelete(self._confirmDelete, "slot", slot.id, version)
|
||||
col((darmed or dhot) and PAL.red or PAL.warning)
|
||||
love.graphics.printf(darmed and "Sure?" or delText, delX, delY, delW, "center")
|
||||
local rightReserve = delW + 18 * s
|
||||
|
||||
-- Edit label, immediately left of Delete: opens the bundled save
|
||||
@@ -3150,8 +3272,10 @@ function RomImporter:_drawModsPanel(x, y, w, h, paged)
|
||||
width = L.delW + 12 * s, height = delH + 4 * s, id = m.id }
|
||||
local dhot = self:_hover(drect)
|
||||
love.graphics.setFont(self.hintFont)
|
||||
col(dhot and PAL.red or PAL.warning)
|
||||
love.graphics.print("Delete", delX, delY)
|
||||
-- Same two-click arm as a save slot's Delete (#433).
|
||||
local darmed = armedDelete(self._confirmDelete, "mod", m.id, nil)
|
||||
col((darmed or dhot) and PAL.red or PAL.warning)
|
||||
love.graphics.printf(darmed and "Sure?" or "Delete", delX, delY, L.delW, "center")
|
||||
|
||||
-- hit rects clipped to the visible list band
|
||||
local vy = math.max(trect.y, top)
|
||||
|
||||
@@ -75,7 +75,10 @@ function SaveFileIO.importToSlot(source, version)
|
||||
return false, ("A save file must be %d bytes (32 KB); this one is %d.")
|
||||
:format(SAVE_SIZE, #bytes)
|
||||
end
|
||||
local save, convertErr = SaveConvert.importSav(bytes, version)
|
||||
-- 3rd arg: the crosswalk has to come from THIS game's ROM cache. The
|
||||
-- launcher imports before the cache is mounted on the un-prefixed paths, so
|
||||
-- SaveConvert cannot find the generated tables by itself here (#420).
|
||||
local save, convertErr = SaveConvert.importSav(bytes, version, version)
|
||||
if not save then return false, convertErr end
|
||||
-- Tag the game version and normalize the meta stamp: SaveConvert leaves
|
||||
-- meta.format = "gen1_import", but SaveData.load's migration pass compares
|
||||
@@ -103,7 +106,7 @@ function SaveFileIO.exportActiveSlot(version)
|
||||
version = version or GameVersion.get()
|
||||
local save = SaveData.load(version)
|
||||
if not save then return false, "this game has no save to export yet" end
|
||||
local bytes, exportErr = SaveConvert.exportSav(save)
|
||||
local bytes, exportErr = SaveConvert.exportSav(save, version)
|
||||
if not bytes then return false, exportErr end
|
||||
local slotId = SaveData.activeSlot(version) or "save"
|
||||
local fs = love and love.filesystem
|
||||
|
||||
Reference in New Issue
Block a user