mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-26 13:33:27 -04:00
6be2de2844
- Save Editor: Implement full Gen 3 FireRed save editing including PID/personality, IVs/EVs, natures, ability slots, gender, shiny toggles, held items, Pokédex flags, and event/trainer toggles. - Scripting & Specials: Add Pokédex rating evaluations, Magikarp/Heracross size records, Trainer Fan Club tracking, and FireRed event flag mappings. - Overworld & Rendering: Interleave metatile and actor draw calls by row bucket with OAM elevation priority sorting for bridges and overhead layers. - ROM Extractor: Parallelize Gen 3 extraction across 4 concurrent love.thread workers with love.filesystem searchers for cross-platform support (Switch/Xbox/Mobile/Desktop). - LZ77 Decompressor: Accelerate GBA LZ77 with preallocated FFI buffers and 0-based displacement indexing ensuring 100% byte-for-byte asset parity. - Testing: Add unit test suites for LZ77, size records, Pokédex rating, Fan Club, elevation priority, Seafoam puzzles, and Gen 3 save editor operations.
187 lines
5.2 KiB
Lua
187 lines
5.2 KiB
Lua
local Catalog = {}
|
|
|
|
local function sortedKeys(t)
|
|
local keys = {}
|
|
for k in pairs(t) do
|
|
table.insert(keys, k)
|
|
end
|
|
table.sort(keys)
|
|
return keys
|
|
end
|
|
|
|
-- Gen 2's generated tables carry provenance scalars (generation, source)
|
|
-- beside the id-keyed records, so a wheel built from every key would offer
|
|
-- them as pickable entries. #1466
|
|
local function sortedRecordKeys(t)
|
|
local keys = {}
|
|
for k, v in pairs(t or {}) do
|
|
if type(k) == "string" and type(v) == "table" then
|
|
table.insert(keys, k)
|
|
end
|
|
end
|
|
table.sort(keys)
|
|
return keys
|
|
end
|
|
|
|
function Catalog.build(data)
|
|
return {
|
|
species = sortedRecordKeys(data.pokemon),
|
|
items = sortedRecordKeys(data.items),
|
|
moves = sortedRecordKeys(data.moves),
|
|
}
|
|
end
|
|
|
|
-- Directory listing / file reading go through love.filesystem when it is
|
|
-- available, and only fall back to shelling out. That is what makes the
|
|
-- Events tab work in a packaged build: data/scripts is inside the .love
|
|
-- archive, data/generated is mounted from the save directory, and mods live
|
|
-- under the save directory too -- none of which io.open can reach by relative
|
|
-- path. The io.popen path stays for headless runs (tests/, plain lua) where
|
|
-- love.filesystem does not exist.
|
|
-- nil means "love.filesystem cannot see this directory", which is the signal
|
|
-- to fall through to the shell -- headless runs mount a stub filesystem that
|
|
-- knows nothing about the checkout, but their io.* can still read it.
|
|
local function loveListLua(dir)
|
|
local fs = love and love.filesystem
|
|
if not (fs and fs.getDirectoryItems and fs.getInfo) then return nil end
|
|
if not fs.getInfo(dir) then return nil end
|
|
local out = {}
|
|
for _, name in ipairs(fs.getDirectoryItems(dir)) do
|
|
if name:sub(-4) == ".lua" then out[#out + 1] = dir .. "/" .. name end
|
|
end
|
|
return out
|
|
end
|
|
|
|
local function shellListLua(dir)
|
|
local out = {}
|
|
if not (io and io.popen) then return out end
|
|
if package.config:sub(1, 1) == "\\" then
|
|
-- cmd has no ls; dir /b prints bare names, so re-attach the directory
|
|
local ok, p = pcall(io.popen, string.format('dir /b "%s\\*.lua" 2>nul', dir))
|
|
if ok and p then
|
|
for line in p:lines() do
|
|
if line ~= "" then table.insert(out, dir .. "/" .. line) end
|
|
end
|
|
p:close()
|
|
end
|
|
return out
|
|
end
|
|
local ok, p = pcall(io.popen, string.format('ls "%s"/*.lua 2>/dev/null', dir))
|
|
if ok and p then
|
|
for line in p:lines() do
|
|
table.insert(out, line)
|
|
end
|
|
p:close()
|
|
end
|
|
return out
|
|
end
|
|
|
|
local function readText(path)
|
|
local fs = love and love.filesystem
|
|
if fs and fs.read and fs.getInfo and fs.getInfo(path) then
|
|
local ok, body = pcall(fs.read, path)
|
|
if ok and body then return body end
|
|
end
|
|
local f = io.open(path, "r")
|
|
if not f then return nil end
|
|
local body = f:read("*a")
|
|
f:close()
|
|
return body
|
|
end
|
|
|
|
-- extraDirs: loaded mods' roots, so MOD_-prefixed flags defined in mod
|
|
-- scripts show up beside the vanilla EVENT_ ones
|
|
function Catalog.scrapeEvents(scriptDir, headerPath, listFiles, extraDirs)
|
|
listFiles = listFiles or function(dir)
|
|
return loveListLua(dir) or shellListLua(dir) or {}
|
|
end
|
|
|
|
local found = {}
|
|
local function eat(text)
|
|
for name in text:gmatch("EVENT_[A-Z0-9_]+") do
|
|
found[name] = true
|
|
end
|
|
for name in text:gmatch("MOD_[A-Z0-9_]+") do
|
|
found[name] = true
|
|
end
|
|
end
|
|
|
|
local dirs = {}
|
|
if scriptDir then dirs[#dirs + 1] = scriptDir end
|
|
for _, dir in ipairs(extraDirs or {}) do
|
|
dirs[#dirs + 1] = dir
|
|
end
|
|
for _, dir in ipairs(dirs) do
|
|
local files = listFiles(dir) or {}
|
|
for _, path in ipairs(files) do
|
|
local body = readText(path)
|
|
if body then eat(body) end
|
|
end
|
|
end
|
|
|
|
if headerPath then
|
|
local body = readText(headerPath)
|
|
if body then eat(body) end
|
|
end
|
|
|
|
return sortedKeys(found)
|
|
end
|
|
|
|
function Catalog.gen2EventList(engine, extraDirs)
|
|
local names = require("Gen2Flags").names(engine)
|
|
local modFlags = Catalog.scrapeEvents(nil, nil, nil, extraDirs)
|
|
local seen = {}
|
|
for _, name in ipairs(names) do seen[name] = true end
|
|
for _, name in ipairs(modFlags) do
|
|
if not seen[name] then
|
|
names[#names + 1] = name
|
|
seen[name] = true
|
|
end
|
|
end
|
|
return names
|
|
end
|
|
|
|
function Catalog.goldEventList(extraDirs)
|
|
return Catalog.gen2EventList("gs", extraDirs)
|
|
end
|
|
|
|
function Catalog.game3EventList(extraDirs)
|
|
local okF, FlagsTable = pcall(require, "src.core.game3.scripting.flags_table")
|
|
local names = {}
|
|
local seen = {}
|
|
if okF and FlagsTable and FlagsTable.FLAGS then
|
|
for name, id in pairs(FlagsTable.FLAGS) do
|
|
if type(name) == "string" and not seen[name] then
|
|
names[#names + 1] = name
|
|
seen[name] = true
|
|
end
|
|
end
|
|
end
|
|
local modFlags = Catalog.scrapeEvents(nil, nil, nil, extraDirs)
|
|
for _, name in ipairs(modFlags) do
|
|
if not seen[name] then
|
|
names[#names + 1] = name
|
|
seen[name] = true
|
|
end
|
|
end
|
|
table.sort(names)
|
|
return names
|
|
end
|
|
|
|
function Catalog.game3Categories(extraDirs)
|
|
local ok, Gen3Flags = pcall(require, "Gen3Flags")
|
|
if ok and Gen3Flags and Gen3Flags.categories then
|
|
return Gen3Flags.categories(extraDirs)
|
|
end
|
|
return {
|
|
story = Catalog.game3EventList(extraDirs),
|
|
trainers = {},
|
|
items = {},
|
|
toggles = {},
|
|
system = {},
|
|
vars = {},
|
|
}
|
|
end
|
|
|
|
return Catalog
|