Give Gen 2 the ROM text table Gen 1 has had all along

Gold and Silver had no label-keyed string table at all.  The manifests
carried no text section, RomExtractorGen2 had no extractText, and
game.data.text was never assigned, so every call through
src/core/RomText.lua fell back to the literal written beside it.  The only
Gen 2 text the cache held was the script text in data/generated/text.lua,
keyed by bank:address for the overworld VM, which nothing can look a battle
line up in.

make_gold_manifest.py now walks data/text/'s five dialogue files for their
labels, the way make_rom_manifest.text_metadata walks pokered's, and embeds
each one's symbol.  889 labels, all of them resolving in both editions.
make_silver_manifest.py inherits the list unchanged and re-resolves the
addresses from pokesilver.sym.

RomExtractorGen2:extractText decodes them into data/generated/rom_text.lua.
The mechanism is the one extractOakSpeech already used for _OakText1-7:
resolve the label, decode from the cart, key by name.  What is new is that
the list comes from the manifest rather than being written out in Lua, so
all of data/text/ arrives instead of seven strings.

decodeGen2Text also emits the three runtime name slots it used to drop.
PlaceMoveUsersName, PlaceMoveTargetsName and PlaceEnemysName (home/text.asm)
substitute a battler's name as the line prints, so <USER>, <TARGET> and
<ENEMY> are markers, not glyphs.  Skipped as control glyphs, SubTookDamageText
decoded as "The SUBSTITUTE / took damage for" with nothing after it.

Game2:load assigns the table to self.data.text, which is what makes the
existing shared RomText helper work on Gold and Silver at all.

The new cache file is listed in the Gold override rather than bumping
CACHE_FORMAT, so caches built before this stage re-import themselves and Red,
Blue and Yellow are left alone.
This commit is contained in:
Colson Rice
2026-08-21 23:08:26 -04:00
parent 51bc4c7437
commit e094b73536
10 changed files with 8361 additions and 7 deletions
+47 -1
View File
@@ -27,7 +27,7 @@ RomExtractorGen2.__index = RomExtractorGen2
-- scripts, pokemon, moves, items, marts, encounters, trainers, pokedex,
-- landmarks, intro movie, menu gfx, title, credits, diploma, trade animation,
-- audio, stubs
local STAGE_COUNT = 26
local STAGE_COUNT = 27
local Opcodes = require("src.script.gen2.Opcodes")
-- BG palette slots inside one loaded 8-palette set (constants/tileset_constants.asm
@@ -136,6 +136,18 @@ local TEXT_NO_GLYPH = {
[0x13] = true, [0x15] = true,
}
-- The three runtime name slots. PlaceMoveUsersName, PlaceMoveTargetsName and
-- PlaceEnemysName (home/text.asm:302, :307, :327) swap these for a battler's
-- own name as the line prints, so they are markers rather than glyphs. They
-- decode to the same shape Gen 1 uses, which src/core/RomText.lua already
-- fills in argument order. Dropped, SubTookDamageText read "took damage
-- for" with nothing after it.
local NAME_SLOT = {
["<USER>"] = "{USER}",
["<TARGET>"] = "{TARGET}",
["<ENEMY>"] = "{ENEMY}",
}
local ROOF_TILES = 9
local SPRITEDATA_LENGTH = 6
@@ -2596,6 +2608,8 @@ function RomExtractorGen2:decodeGen2Text(bank, address, charmap, buffers)
out[#out + 1] = ch
elseif ch == "<……>" or b == 0x56 then
out[#out + 1] = "……"
elseif NAME_SLOT[ch] then
out[#out + 1] = NAME_SLOT[ch]
elseif not ch then
out[#out + 1] = ("{BYTE:%02X}"):format(b)
end
@@ -3698,6 +3712,37 @@ function RomExtractorGen2:splashGfx()
}
end
-- The engine's own strings, keyed by the label the disassembly gives them.
--
-- This is what extractOakSpeech has always done for _OakText1-7: resolve the
-- label, decode from the cart, key by name. What is new is that the list of
-- labels comes from the manifest instead of being written out here, so all of
-- data/text/ arrives rather than seven strings. Gen 1 has had the same table
-- since RomExtractor:extractText; this is the Gen 2 side of it, and it is
-- what lets src/core/RomText.lua work on Gold and Silver at all.
--
-- Written as `rom_text` rather than `text`: data/generated/text.lua is
-- already the script text, keyed by bank:address for the overworld VM, and
-- these are a different table with different keys.
function RomExtractorGen2:extractText()
self:beginStage("Dialogue")
local charmap = self.manifest.charmap or {}
local labels = (self.manifest.text or {}).labels or {}
local texts = {}
for index, label in ipairs(labels) do
local location = self.symbols[label]
-- A label the manifest names but the symbol table does not carry would
-- be a generator bug, not a cart difference: make_gold_manifest.py
-- resolves every one of these before it writes the list.
if location then
texts[label] = self:decodeGen2Text(location[1], location[2], charmap)
end
self:tick("Dialogue", index, #labels)
end
self:write("rom_text", texts)
return texts
end
-- OakSpeech (engine/menus/intro_menu.asm): named _OakText* strings plus the
-- POKEMON_PROF / CAL trainer pics shown before NamePlayer. Also pulls
-- Shrink1/2 pics and the GameFreak splash sheets for the boot cinema.
@@ -6283,6 +6328,7 @@ function RomExtractorGen2:run()
results.sprites = self:extractSprites()
results.stdScripts = self:extractStdScripts()
results.scripts = self:extractScriptsAndText(results.maps, results.stdScripts)
results.text = self:extractText()
results.pokemon = self:extractPokemon()
results.moves = self:extractMoves()
results.items = self:extractItems()
+6
View File
@@ -104,6 +104,12 @@ local VERSION_REQUIRED_FILES_OVERRIDE = {
"data/generated/sprites.lua", -- OW sheets (Chris + NPCs)
"data/generated/scripts.lua", -- disassembled map scripts
"data/generated/text.lua", -- decoded Gen 2 dialogue strings
-- The engine's own strings, keyed by label rather than by address. A
-- cache built before RomExtractorGen2:extractText has none, and every
-- line that reads through src/core/RomText.lua would silently keep
-- printing its Lua fallback, so this re-imports those caches rather than
-- bumping CACHE_FORMAT and dragging Red, Blue and Yellow through it too.
"data/generated/rom_text.lua",
"data/generated/pokemon.lua",
"data/generated/tilesets.lua",
"data/generated/audio.lua",