mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-06 01:04:05 -04:00
52ad498b8b
- Align CacheContract firered overrides with pure ROM extraction - Fix door animation extraction tile sizes, palettes, and strides - Update storage chrome, pokedex, summary, and battle chrome extractors - Ensure 100% self-contained ROM extraction without external dependencies
288 lines
9.9 KiB
Lua
288 lines
9.9 KiB
Lua
-- Hydrate Game3 standalone data from the firered/ GBA extract cache.
|
|
-- Uses native mid layouts + warps; does not touch Sevii ferry host maps.
|
|
|
|
local Versions = require("src.import.gba.versions")
|
|
local Extract = require("src.import.gba.extract_island1")
|
|
local MapIds = require("src.core.game3.map_ids")
|
|
|
|
local Dataset = {}
|
|
|
|
local function diskFallback(rel)
|
|
local f = io.open(rel, "rb") or io.open("data/generated/gba/" .. rel, "rb")
|
|
if f then
|
|
local data = f:read("*a")
|
|
f:close()
|
|
if type(data) == "string" and #data > 0 then return data end
|
|
end
|
|
|
|
local okG, GameVersion = pcall(require, "src.core.GameVersion")
|
|
local prefix = (okG and GameVersion.cachePrefix and GameVersion.cachePrefix()) or "firered/"
|
|
local prefixes = { prefix }
|
|
-- Always also try firered/ for GBA extract paths (standalone Game3).
|
|
if prefix ~= "firered/" then
|
|
prefixes[#prefixes + 1] = "firered/"
|
|
end
|
|
local roots = {}
|
|
local home = os.getenv("HOME")
|
|
if home then
|
|
roots[#roots + 1] = home .. "/.local/share/love/pokemon-love2d"
|
|
end
|
|
if love and love.filesystem and love.filesystem.getSaveDirectory then
|
|
local sd = love.filesystem.getSaveDirectory()
|
|
if type(sd) == "string" and sd ~= "" then
|
|
roots[#roots + 1] = sd
|
|
local parent = sd:match("^(.*)/[^/]+$")
|
|
if parent then roots[#roots + 1] = parent .. "/pokemon-love2d" end
|
|
end
|
|
end
|
|
for _, root in ipairs(roots) do
|
|
for _, pfx in ipairs(prefixes) do
|
|
for _, path in ipairs({ root .. "/" .. pfx .. rel, root .. "/" .. rel }) do
|
|
local f = io.open(path, "rb")
|
|
if f then
|
|
local data = f:read("*a")
|
|
f:close()
|
|
if type(data) == "string" and #data > 0 then return data end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function loveCache()
|
|
return {
|
|
read = function(_, rel)
|
|
local ok, CacheFs = pcall(require, "src.import.CacheFs")
|
|
if ok and CacheFs and CacheFs.readActive then
|
|
local bytes = CacheFs.readActive(rel)
|
|
if type(bytes) == "string" then return bytes end
|
|
end
|
|
if love and love.filesystem then
|
|
local bytes = love.filesystem.read(rel)
|
|
if type(bytes) == "string" then return bytes end
|
|
end
|
|
return diskFallback(rel)
|
|
end,
|
|
write = function(_, rel, bytes)
|
|
local ok, CacheFs = pcall(require, "src.import.CacheFs")
|
|
if ok and CacheFs and CacheFs.write then
|
|
return CacheFs.write(rel, bytes)
|
|
end
|
|
if not love or not love.filesystem then return false end
|
|
return love.filesystem.write(rel, bytes)
|
|
end,
|
|
exists = function(_, rel)
|
|
local cache = loveCache()
|
|
return cache:read(rel) ~= nil
|
|
end,
|
|
}
|
|
end
|
|
|
|
--- Shared firered CacheFs-backed cache for standalone Game3 (mod.cache is nil).
|
|
function Dataset.cache()
|
|
return loveCache()
|
|
end
|
|
|
|
local function load_lua_rel(rel)
|
|
local cache = loveCache()
|
|
local src = cache:read(rel)
|
|
if not src then return nil end
|
|
local chunk = load(src, "@" .. rel, "t", {})
|
|
if not chunk then return nil end
|
|
local ok, val = pcall(chunk)
|
|
if ok then return val end
|
|
return nil
|
|
end
|
|
|
|
--- Build map defs for every FR_* (and any other) entry in native manifest / Versions.MAPS.
|
|
function Dataset.buildMaps(warps)
|
|
warps = warps
|
|
or load_lua_rel(Extract.CACHE_ROOT .. "/warps.lua")
|
|
or Versions.WARPS
|
|
or {}
|
|
local connections = load_lua_rel(Extract.CACHE_ROOT .. "/connections.lua") or {}
|
|
local manifest = load_lua_rel((Extract.NATIVE_ROOT or Extract.CACHE_ROOT .. "/native") .. "/manifest.lua") or {}
|
|
local maps = {}
|
|
|
|
local function add(mapId, info)
|
|
local spec = Versions.MAPS[mapId] or {}
|
|
local pair = (info and info.pair) or spec.pair
|
|
local tileset = pair and Versions.PAIR_TILESET and Versions.PAIR_TILESET[pair]
|
|
maps[mapId] = {
|
|
id = mapId,
|
|
name = mapId,
|
|
width = (info and info.width) or spec.width or 20,
|
|
height = (info and info.height) or spec.height or 18,
|
|
kind = spec.kind or "town",
|
|
environment = spec.environment or "TOWN",
|
|
pair = pair,
|
|
tileset = tileset,
|
|
warps = warps[mapId] or {},
|
|
connections = connections[mapId] or {},
|
|
native = true,
|
|
}
|
|
end
|
|
|
|
if manifest.layouts then
|
|
for mapId, info in pairs(manifest.layouts) do
|
|
-- Prefer Fire Red maps for standalone boot; keep SEVII_* available but unused.
|
|
add(mapId, info)
|
|
end
|
|
else
|
|
for mapId, spec in pairs(Versions.MAPS or {}) do
|
|
add(mapId, { width = spec.width, height = spec.height, pair = spec.pair })
|
|
end
|
|
end
|
|
|
|
-- Fallback corridor edges if connections.lua missing (pre-v82 caches).
|
|
if not next(connections) then
|
|
if maps.FR_PALLET_TOWN and maps.FR_ROUTE_1 then
|
|
maps.FR_PALLET_TOWN.connections = {
|
|
north = { map = "FR_ROUTE_1", offset = 0 },
|
|
}
|
|
maps.FR_ROUTE_1.connections = {
|
|
south = { map = "FR_PALLET_TOWN", offset = 0 },
|
|
north = { map = "FR_VIRIDIAN_CITY", offset = -12 },
|
|
}
|
|
end
|
|
if maps.FR_VIRIDIAN_CITY and maps.FR_ROUTE_1 then
|
|
maps.FR_VIRIDIAN_CITY.connections = maps.FR_VIRIDIAN_CITY.connections or {}
|
|
maps.FR_VIRIDIAN_CITY.connections.south = { map = "FR_ROUTE_1", offset = 12 }
|
|
end
|
|
if maps.FR_VIRIDIAN_CITY and maps.FR_ROUTE_2 then
|
|
maps.FR_VIRIDIAN_CITY.connections = maps.FR_VIRIDIAN_CITY.connections or {}
|
|
maps.FR_VIRIDIAN_CITY.connections.north = { map = "FR_ROUTE_2", offset = 12 }
|
|
maps.FR_ROUTE_2.connections = {
|
|
south = { map = "FR_VIRIDIAN_CITY", offset = -12 },
|
|
north = { map = "FR_PEWTER_CITY", offset = -12 },
|
|
}
|
|
end
|
|
if maps.FR_PEWTER_CITY and maps.FR_ROUTE_2 then
|
|
maps.FR_PEWTER_CITY.connections = {
|
|
south = { map = "FR_ROUTE_2", offset = 12 },
|
|
}
|
|
end
|
|
end
|
|
|
|
return maps
|
|
end
|
|
|
|
--- Point extract roots at the engine firered cache and install native tilesets.
|
|
function Dataset.mountExtractRoots()
|
|
Extract.CACHE_ROOT = "data/generated/gba"
|
|
Extract.NATIVE_ROOT = "data/generated/gba/native"
|
|
end
|
|
|
|
--- Bind LayoutNative handles onto map defs (FieldView needs midLayout).
|
|
function Dataset.attachMidLayouts(maps, cache)
|
|
if type(maps) ~= "table" then return 0 end
|
|
cache = cache or loveCache()
|
|
local LayoutNative = require("src.core.game3.layout_native")
|
|
local NativePack = require("src.import.gba.native_pack")
|
|
local nativeRoot = Extract.NATIVE_ROOT or (Extract.CACHE_ROOT .. "/native")
|
|
local manifest = load_lua_rel(nativeRoot .. "/manifest.lua") or {}
|
|
local layouts = manifest.layouts or {}
|
|
local attached = 0
|
|
for mapId, def in pairs(maps) do
|
|
if def and not def.midLayout then
|
|
local info = layouts[mapId]
|
|
local rel = nativeRoot .. "/"
|
|
.. ((info and info.file) or ("layouts/" .. mapId .. ".mid"))
|
|
local blob = cache:read(rel)
|
|
if blob then
|
|
local decoded = NativePack.decodeMidLayout(blob)
|
|
if decoded then
|
|
local pair = (info and info.pair) or def.pair
|
|
def.midLayout = LayoutNative.fromDecoded(decoded, mapId, pair)
|
|
if decoded.width and decoded.width > 0 then def.width = decoded.width end
|
|
if decoded.height and decoded.height > 0 then def.height = decoded.height end
|
|
if pair then def.pair = pair end
|
|
attached = attached + 1
|
|
end
|
|
end
|
|
elseif def and def.midLayout then
|
|
attached = attached + 1
|
|
end
|
|
end
|
|
return attached
|
|
end
|
|
|
|
--- Populate game.data for standalone Fire Red.
|
|
function Dataset.hydrate(game)
|
|
Dataset.mountExtractRoots()
|
|
local cache = loveCache()
|
|
game.data = game.data or {}
|
|
game.data.maps = Dataset.buildMaps()
|
|
game.data.tilesets = game.data.tilesets or {}
|
|
|
|
local nLayouts = Dataset.attachMidLayouts(game.data.maps, cache)
|
|
|
|
local okS, Space = pcall(require, "src.core.game3.scripting.space")
|
|
local nEvents = 0
|
|
if okS and Space then
|
|
Space.ensureBundle(nil)
|
|
nEvents = Space.attachEventsToMaps(game.data.maps, Space.bundle) or 0
|
|
end
|
|
|
|
local NativeTileset = require("src.core.game3.tileset_native")
|
|
if NativeTileset.install then
|
|
NativeTileset.install(cache, nil)
|
|
end
|
|
local okO, OwSprites = pcall(require, "src.core.game3.ow_sprites")
|
|
if okO and OwSprites and OwSprites.install then
|
|
OwSprites.install(cache)
|
|
end
|
|
local okFx, FieldEffects = pcall(require, "src.core.game3.field_effects")
|
|
if okFx and FieldEffects and FieldEffects.install then
|
|
FieldEffects.install(cache)
|
|
end
|
|
|
|
local okPk, Pokemon = pcall(require, "src.core.game3.pokemon")
|
|
if okPk and Pokemon and Pokemon.install then
|
|
Pokemon.install(cache)
|
|
end
|
|
local okPc, PartyChrome = pcall(require, "src.ui.game3.party_chrome")
|
|
if okPc and PartyChrome and PartyChrome.install then
|
|
PartyChrome.install(cache)
|
|
end
|
|
local okBc, BagChrome = pcall(require, "src.ui.game3.bag_chrome")
|
|
if okBc and BagChrome and BagChrome.install then
|
|
BagChrome.install(cache)
|
|
end
|
|
|
|
local Audio = require("src.core.game3.audio")
|
|
-- Explicit firered audio root — never inherit Sevii Extract.CACHE_ROOT default.
|
|
local audioRoot = "data/generated/gba/audio"
|
|
local okA, errA = Audio.install(cache, { root = audioRoot })
|
|
if not okA then
|
|
local songs = load_lua_rel(audioRoot .. "/songs.lua")
|
|
or load_lua_rel(Extract.CACHE_ROOT .. "/audio/songs.lua")
|
|
if songs then
|
|
Audio.loadMeta({ songs = songs })
|
|
end
|
|
print("[game3/dataset] audio install: " .. tostring(errA))
|
|
elseif Audio._pack and Audio._pack.index and Audio._pack.index.mapSongs and game.data and game.data.maps then
|
|
for mapId, songId in pairs(Audio._pack.index.mapSongs) do
|
|
local def = game.data.maps[mapId]
|
|
if def and def.music == nil then def.music = songId end
|
|
end
|
|
end
|
|
|
|
-- Encounter tables need the mounted firered cache (install may have run earlier).
|
|
local okE, Encounters = pcall(require, "src.core.game3.encounters")
|
|
if okE and Encounters and Encounters.loadFromMod then
|
|
Encounters.loadFromMod(nil)
|
|
end
|
|
|
|
local nMaps = 0
|
|
for _ in pairs(game.data.maps) do nMaps = nMaps + 1 end
|
|
print(string.format(
|
|
"[game3/dataset] hydrated %d maps midLayouts=%d eventMaps=%d (start=%s)",
|
|
nMaps, nLayouts, nEvents, tostring(MapIds.NEW_GAME_START.map)))
|
|
|
|
return true
|
|
end
|
|
|
|
return Dataset
|