mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-23 05:58:26 +02:00
e094b73536
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.
140 lines
5.9 KiB
Lua
140 lines
5.9 KiB
Lua
-- Gen 2's engine text, the counterpart to Gen 1's data/generated/text.lua.
|
|
--
|
|
-- 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. These
|
|
-- cover the three halves of closing that: the manifest names the labels and
|
|
-- resolves every one, the decoder emits the runtime name slots rather than
|
|
-- dropping them, and RomText fills those slots.
|
|
--
|
|
-- GOLD_CACHE="..." luajit tests/gen2_rom_text_test.lua
|
|
--
|
|
-- ROM-free apart from the last section, which reads an imported cache's
|
|
-- rom_text.lua and skips cleanly when there is none.
|
|
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
|
|
local S = require("tests.harness").suite("gen2 rom text")
|
|
local check, eq = S.check, S.eq
|
|
|
|
love = require("tests.love_stub")
|
|
|
|
local Json = require("src.link.Json")
|
|
local romText = require("src.core.RomText")
|
|
|
|
local function manifest(path)
|
|
local file = assert(io.open(path, "r"))
|
|
local data = assert(Json.decode(file:read("*a")))
|
|
file:close()
|
|
return data
|
|
end
|
|
|
|
-- ---- the label list, and that every label resolves ------------------------
|
|
-- A label the list names but the symbol table cannot place would fail the
|
|
-- import at the Dialogue stage rather than at generation time, so the pairing
|
|
-- is asserted here instead.
|
|
for _, edition in ipairs({ "gold", "silver" }) do
|
|
local data = manifest("tools/rom_manifest_" .. edition .. ".json")
|
|
local labels = (data.text or {}).labels or {}
|
|
check((data.text or {}).labels ~= nil,
|
|
edition .. " carries a text section")
|
|
check(#labels > 800,
|
|
("%s names %d text labels"):format(edition, #labels))
|
|
|
|
local unresolved = {}
|
|
for _, label in ipairs(labels) do
|
|
if not data.symbols[label] then unresolved[#unresolved + 1] = label end
|
|
end
|
|
eq(#unresolved, 0,
|
|
("every %s text label resolves to a symbol (%s)")
|
|
:format(edition, table.concat(unresolved, ", "):sub(1, 60)))
|
|
|
|
local named = {}
|
|
for _, label in ipairs(labels) do named[label] = true end
|
|
|
|
-- data/text/ also holds keyboard layouts and kana tables. Decoded as text
|
|
-- they come out as keyboard rows, so make_gold_manifest.TEXT_SOURCES leaves
|
|
-- their files out. `BattleText::` is excluded for a different reason: it
|
|
-- is a bank anchor sharing an address with the first real label under it,
|
|
-- and its own comment in the disassembly says so.
|
|
for _, excluded in ipairs({ "NameInputLower", "MailEntry_Uppercase",
|
|
"Dakutens", "Gen1TrainerClassNames", "BattleText" }) do
|
|
check(not named[excluded],
|
|
edition .. " leaves " .. excluded .. " out of the text list")
|
|
end
|
|
end
|
|
|
|
-- Both editions describe the same strings; only the addresses move.
|
|
do
|
|
local gold = (manifest("tools/rom_manifest_gold.json").text or {}).labels or {}
|
|
local silver =
|
|
(manifest("tools/rom_manifest_silver.json").text or {}).labels or {}
|
|
eq(#gold, #silver, "Gold and Silver name the same number of labels")
|
|
local mismatch
|
|
for index, label in ipairs(gold) do
|
|
if silver[index] ~= label then mismatch = label; break end
|
|
end
|
|
eq(mismatch, nil, "and the same labels in the same order")
|
|
end
|
|
|
|
-- ---- the slots RomText fills ----------------------------------------------
|
|
-- decodeGen2Text emits {USER}, {TARGET} and {ENEMY} for the three names
|
|
-- PlaceMoveUsersName / PlaceMoveTargetsName / PlaceEnemysName write at
|
|
-- runtime (home/text.asm:302, :307, :327). Dropped, the line printed with a
|
|
-- hole where the name belongs.
|
|
do
|
|
local data = { text = {
|
|
SubTookDamageText = "The SUBSTITUTE\ntook damage for\v{TARGET}!",
|
|
WantsToBattleText = "{ENEMY}\nwants to battle!",
|
|
ConfusedNoMoreText = "{USER}'s\nconfused no more!",
|
|
SuperEffectiveText = "It's super-\neffective!",
|
|
} }
|
|
|
|
eq(romText(data, "SubTookDamageText", "fallback", "GEODUDE"),
|
|
"The SUBSTITUTE\ntook damage for\vGEODUDE!",
|
|
"a {TARGET} slot takes the name the caller passes")
|
|
eq(romText(data, "WantsToBattleText", "fallback", "FALKNER"),
|
|
"FALKNER\nwants to battle!", "and so does {ENEMY}")
|
|
eq(romText(data, "ConfusedNoMoreText", "fallback", "CYNDAQUIL"),
|
|
"CYNDAQUIL's\nconfused no more!", "and {USER}")
|
|
eq(romText(data, "SuperEffectiveText", "It's super effective!"),
|
|
"It's super-\neffective!",
|
|
"a line with no slot comes back as the cart wrote it")
|
|
eq(romText(data, "NoSuchLabel", "the engine's own wording"),
|
|
"the engine's own wording",
|
|
"and a label the cache does not carry falls back")
|
|
end
|
|
|
|
-- ---- against a real imported cache ----------------------------------------
|
|
do
|
|
local cache = os.getenv("GOLD_CACHE")
|
|
if not cache then
|
|
local home = os.getenv("HOME") or ""
|
|
cache = home .. "/Library/Application Support/LOVE/gold-dev/gold"
|
|
end
|
|
local path = cache .. "/data/generated/rom_text.lua"
|
|
local file = io.open(path, "r")
|
|
if not file then
|
|
print(" (skipped: no rom_text.lua at " .. path .. ")")
|
|
else
|
|
file:close()
|
|
local texts = assert(loadfile(path))()
|
|
check(next(texts) ~= nil, "the imported cache carries strings")
|
|
-- Wording taken from pokegold's data/text/battle.asm, with \n for `line`
|
|
-- and \v for `cont`, which is what RomExtractorGen2 decodes those to.
|
|
eq(texts.SuperEffectiveText, "It's super-\neffective!",
|
|
"SuperEffectiveText comes off the cart hyphenated and broken")
|
|
eq(texts.NotVeryEffectiveText, "It's not very\neffective…",
|
|
"NotVeryEffectiveText ends on the ellipsis glyph")
|
|
eq(texts.StartPerishText, "Both POKéMON will\nfaint in 3 turns!",
|
|
"StartPerishText names both sides")
|
|
eq(texts.ButItFailedText, "But it failed!", "and a one-row line is one row")
|
|
eq(texts.SubTookDamageText, "The SUBSTITUTE\ntook damage for\v{TARGET}!",
|
|
"SpikesText's neighbour keeps its cont row and its target slot")
|
|
eq(texts.PlayerHitTimesText, "Hit {NUM} times!",
|
|
"a text_decimal reads back as {NUM}")
|
|
end
|
|
end
|
|
|
|
S.finish()
|