mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-09 10:01:50 -04:00
6774889505
Ruby and Sapphire 1.0/1.1/1.2 import, play, save, convert and link. Gen 3 shaders, braille, weather and reflection parity, link room players, RS custom carts and launcher support, Gen 3 link fixes for Emerald and FireRed/LeafGreen.
283 lines
9.2 KiB
Lua
283 lines
9.2 KiB
Lua
-- Extract FRLG item table from ROM (gItems) into data/generated/gba/items/pack.lua.
|
|
-- Pure Lua ROM reader: 0 external pret / python dependencies.
|
|
|
|
local Versions = require("src.import.gba.versions")
|
|
local TextIR = require("src.core.game3.scripting.text_ir")
|
|
local Layouts = require("src.import.gba.layouts.registry")
|
|
|
|
local ItemsExtract = {}
|
|
|
|
ItemsExtract.CACHE_SUB = "items"
|
|
ItemsExtract.FORMAT_VERSION = 1
|
|
ItemsExtract.REQUIRED = { "items/pack.lua" }
|
|
|
|
local function default_cache_root()
|
|
local ok, Extract = pcall(require, "src.import.gba.extract_island1")
|
|
if ok and Extract and Extract.CACHE_ROOT then
|
|
return Extract.CACHE_ROOT
|
|
end
|
|
return "data/generated/gba"
|
|
end
|
|
|
|
local function get_byte(rom, off)
|
|
if rom.get then
|
|
return rom:get(off)
|
|
elseif rom.data then
|
|
return rom.data:byte(off + 1)
|
|
end
|
|
return 0
|
|
end
|
|
|
|
local function get_u16(rom, off)
|
|
if rom.u16 then
|
|
return rom:u16(off)
|
|
end
|
|
return get_byte(rom, off) + get_byte(rom, off + 1) * 256
|
|
end
|
|
|
|
local function get_u32(rom, off)
|
|
if rom.u32 then
|
|
return rom:u32(off)
|
|
end
|
|
return get_byte(rom, off)
|
|
+ get_byte(rom, off + 1) * 256
|
|
+ get_byte(rom, off + 2) * 65536
|
|
+ get_byte(rom, off + 3) * 16777216
|
|
end
|
|
|
|
local function decode_name(rom, off, maxLen)
|
|
maxLen = maxLen or 14
|
|
local chars = {}
|
|
local extra = TextIR.dialect().CHARMAP_EXTRA
|
|
for i = 0, maxLen - 1 do
|
|
local b = get_byte(rom, off + i)
|
|
if b == 0xFF then break end
|
|
if TextIR.CHARMAP[b] then
|
|
chars[#chars + 1] = TextIR.CHARMAP[b]
|
|
elseif extra and extra[b] then
|
|
-- pokeemerald/charmap.txt:45
|
|
chars[#chars + 1] = extra[b]
|
|
elseif b >= 0xBB and b <= 0xD4 then
|
|
chars[#chars + 1] = string.char(string.byte("A") + (b - 0xBB))
|
|
elseif b >= 0xD5 and b <= 0xEE then
|
|
chars[#chars + 1] = string.char(string.byte("a") + (b - 0xD5))
|
|
end
|
|
end
|
|
return table.concat(chars)
|
|
end
|
|
|
|
local function decode_text(rom, gbaPtr, maxLen)
|
|
if not gbaPtr or gbaPtr < 0x08000000 or gbaPtr >= 0x0A000000 then
|
|
return ""
|
|
end
|
|
local off = gbaPtr - 0x08000000
|
|
maxLen = maxLen or 256
|
|
local chars = {}
|
|
local extra = TextIR.dialect().CHARMAP_EXTRA
|
|
for i = 0, maxLen - 1 do
|
|
local b = get_byte(rom, off + i)
|
|
if b == 0xFF then break end
|
|
if b == 0xFE or b == 0xFA or b == 0xFB then
|
|
chars[#chars + 1] = "\n"
|
|
elseif TextIR.CHARMAP[b] then
|
|
chars[#chars + 1] = TextIR.CHARMAP[b]
|
|
elseif extra and extra[b] then
|
|
-- pokeemerald/charmap.txt:45
|
|
chars[#chars + 1] = extra[b]
|
|
elseif b >= 0xBB and b <= 0xD4 then
|
|
chars[#chars + 1] = string.char(string.byte("A") + (b - 0xBB))
|
|
elseif b >= 0xD5 and b <= 0xEE then
|
|
chars[#chars + 1] = string.char(string.byte("a") + (b - 0xD5))
|
|
end
|
|
end
|
|
return table.concat(chars)
|
|
end
|
|
|
|
local function escape_lua(s)
|
|
return (tostring(s or ""):gsub("\\", "\\\\"):gsub("\"", "\\\""):gsub("\n", "\\n"))
|
|
end
|
|
|
|
local function popcount(v)
|
|
local n = 0
|
|
while v > 0 do
|
|
n = n + v % 2
|
|
v = math.floor(v / 2)
|
|
end
|
|
return n
|
|
end
|
|
|
|
-- src/data/pokemon/item_effects.h:338, src/pokemon.c:4202
|
|
local function read_effect(rom, id)
|
|
if id < Versions.ITEM_EFFECT_FIRST or id > Versions.ITEM_EFFECT_LAST then return nil end
|
|
local ptr = get_u32(rom, Versions.ITEM_EFFECT_TABLE + (id - Versions.ITEM_EFFECT_FIRST) * 4)
|
|
if ptr < 0x08000000 or ptr >= 0x0A000000 then return nil end
|
|
local off = ptr - 0x08000000
|
|
local e4, e5 = get_byte(rom, off + 4), get_byte(rom, off + 5)
|
|
local len = 6 + popcount(e4 % 8) + ((math.floor(e4 / 8) % 4 ~= 0) and 1 or 0)
|
|
+ popcount(e5 % 16) + popcount(math.floor(e5 / 32))
|
|
local out = {}
|
|
for i = 1, len do out[i] = get_byte(rom, off + i - 1) end
|
|
return out
|
|
end
|
|
|
|
-- src/item_use.c:409
|
|
local function medicine_kind(effect)
|
|
if not effect then return "heal" end
|
|
local e3, e4, e5 = effect[4], effect[5], effect[6]
|
|
if math.floor(e4 / 64) % 2 == 1 then return "revive" end
|
|
if e4 % 4 ~= 0 or e5 % 16 ~= 0 then return "vitamin" end
|
|
if math.floor(e4 / 4) % 2 == 1 then return "heal" end
|
|
if e3 % 64 ~= 0 or effect[1] >= 0x80 then return "status" end
|
|
return "heal"
|
|
end
|
|
|
|
local function determine_field_use(pocket, battleUsage, fieldUseFunc, effect)
|
|
if pocket == "KEY_ITEMS" then return "key" end
|
|
if pocket == "TM_CASE" or pocket == "TM_HM" then return "tm" end
|
|
if pocket == "POKE_BALLS" then return "battle" end
|
|
local F = Versions.FIELD_USE_FUNCS
|
|
local fn = fieldUseFunc - 0x08000001
|
|
if fn == F.medicine or (F.reduce_ev and fn == F.reduce_ev) then return medicine_kind(effect) end
|
|
if fn == F.ether or fn == F.pp_up then return "pp" end
|
|
if fn == F.rare_candy then return "level" end
|
|
if fn == F.evo_item then return "evo" end
|
|
if fn == F.sacred_ash then return "revive" end
|
|
if fn == F.repel then return "repel" end
|
|
if fn == F.escape_rope then return "escape" end
|
|
if fn == F.black_white_flute then return "black_white_flute" end
|
|
if (battleUsage or 0) > 0 then return "battle" end
|
|
return "none"
|
|
end
|
|
|
|
function ItemsExtract.ready(cache, cacheRoot)
|
|
local root = (cacheRoot or default_cache_root()) .. "/" .. ItemsExtract.CACHE_SUB
|
|
local need = root .. "/pack.lua"
|
|
if cache and cache.exists and cache:exists(need) then
|
|
return true
|
|
end
|
|
local okC, CacheFs = pcall(require, "src.import.CacheFs")
|
|
if okC and CacheFs and CacheFs.exists and CacheFs.exists(need) then
|
|
return true
|
|
end
|
|
if love and love.filesystem and love.filesystem.getInfo and love.filesystem.getInfo(need) then
|
|
return true
|
|
end
|
|
local f = io.open(need, "rb") or io.open("data/generated/gba/" .. ItemsExtract.CACHE_SUB .. "/pack.lua", "rb")
|
|
if f then
|
|
f:close()
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function ItemsExtract.run(rom, cache, opts)
|
|
opts = opts or {}
|
|
local cacheRoot = opts.cacheRoot or default_cache_root()
|
|
local outRel = cacheRoot .. "/" .. ItemsExtract.CACHE_SUB .. "/pack.lua"
|
|
|
|
if not opts.force and ItemsExtract.ready(cache, cacheRoot) then
|
|
return { ok = true, count = Versions.ITEMS_COUNT, path = outRel, skipped = true }
|
|
end
|
|
|
|
local itemBase = assert(Versions.ITEMS, "items_extract: no ITEMS key")
|
|
local itemCount = assert(Versions.ITEMS_COUNT, "items_extract: no ITEMS_COUNT key")
|
|
local itemStride = assert(Versions.ITEM_STRIDE, "items_extract: no ITEM_STRIDE key")
|
|
local layout = Layouts.active()
|
|
local pocketNames = Layouts.pockets(layout)
|
|
local syms = Versions.SYMS
|
|
|
|
local lines = {
|
|
"-- Auto-generated from GBA ROM gItems table. DO NOT EDIT DIRECTLY.",
|
|
"return {",
|
|
" version = 1,",
|
|
string.format(" count = %d,", itemCount),
|
|
" items = {",
|
|
}
|
|
|
|
for id = 0, itemCount - 1 do
|
|
local off = itemBase + id * itemStride
|
|
local name = decode_name(rom, off, 14)
|
|
local itemId = get_u16(rom, off + 14)
|
|
local price = get_u16(rom, off + 16)
|
|
local holdEffect = get_byte(rom, off + 18)
|
|
local holdEffectParam = get_byte(rom, off + 19)
|
|
local descPtr = get_u32(rom, off + 20)
|
|
local importance = get_byte(rom, off + 24)
|
|
local registrability = get_byte(rom, off + 25)
|
|
local pocketId = get_byte(rom, off + 26)
|
|
local itemType = get_byte(rom, off + 27)
|
|
local fieldUseFunc = get_u32(rom, off + 28)
|
|
local battleUsage = get_byte(rom, off + 32)
|
|
local battleUseFunc = get_u32(rom, off + 36)
|
|
local secondaryId = get_byte(rom, off + 40)
|
|
|
|
local desc = decode_text(rom, descPtr, 256)
|
|
local pocket = pocketNames[pocketId] or "ITEMS"
|
|
local effect = read_effect(rom, id)
|
|
local fieldUse = determine_field_use(pocket, battleUsage, fieldUseFunc, effect)
|
|
|
|
lines[#lines + 1] = string.format(
|
|
' [%d] = { name="%s", pocket="%s", fieldUse="%s", price=%d, ' ..
|
|
'holdEffect=%d, holdEffectParam=%d, importance=%d, registrability=%d, ' ..
|
|
'battleUsage=%d, secondaryId=%d, itemId=%d, itemType=%d, ' ..
|
|
'fieldUseFunc=%d, battleUseFunc=%d, effect=%s, description="%s" },',
|
|
id,
|
|
escape_lua(name ~= "" and name or "????????"),
|
|
pocket,
|
|
fieldUse,
|
|
price,
|
|
holdEffect,
|
|
holdEffectParam,
|
|
importance,
|
|
registrability,
|
|
battleUsage,
|
|
secondaryId,
|
|
itemId,
|
|
itemType,
|
|
fieldUseFunc,
|
|
battleUseFunc,
|
|
effect and ("{" .. table.concat(effect, ",") .. "}") or "nil",
|
|
escape_lua(desc)
|
|
)
|
|
if syms then
|
|
local fieldName = fieldUseFunc ~= 0 and syms.funcAt(fieldUseFunc) or nil
|
|
local battleName = battleUseFunc ~= 0 and syms.funcAt(battleUseFunc) or nil
|
|
lines[#lines] = lines[#lines]:sub(1, -4) .. string.format(
|
|
', pocketId=%d, fieldUseName=%s, battleUseName=%s },',
|
|
pocketId,
|
|
fieldName and ('"' .. fieldName .. '"') or "nil",
|
|
battleName and ('"' .. battleName .. '"') or "nil")
|
|
end
|
|
end
|
|
|
|
lines[#lines + 1] = " },"
|
|
lines[#lines + 1] = "}"
|
|
lines[#lines + 1] = ""
|
|
|
|
local outputText = table.concat(lines, "\n")
|
|
|
|
local wrote = false
|
|
if cache and cache.write then
|
|
cache:write(outRel, outputText)
|
|
wrote = true
|
|
end
|
|
if not wrote then
|
|
local okC, CacheFs = pcall(require, "src.import.CacheFs")
|
|
if okC and CacheFs and CacheFs.write then
|
|
local ok = pcall(CacheFs.write, outRel, outputText)
|
|
if ok then wrote = true end
|
|
end
|
|
end
|
|
if not wrote and love and love.filesystem and love.filesystem.write then
|
|
pcall(love.filesystem.write, outRel, outputText)
|
|
end
|
|
|
|
return {
|
|
ok = true,
|
|
count = itemCount,
|
|
path = outRel,
|
|
}
|
|
end
|
|
|
|
return ItemsExtract
|