mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-25 06:55:29 +02:00
chris 1.1 hotfix
This commit is contained in:
@@ -82,7 +82,8 @@ game data:
|
||||
- Yellow: `cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1`
|
||||
- Gold: `d8b8a3600a465308c9953dfa04f0081c05bdcb94`
|
||||
- Silver: `49b163f7e57702bc939d642a18f591de55d92dae`
|
||||
- Crystal: `f4cd194bdee0d04ca4eac29e09b8e4e9d818c133`
|
||||
- Crystal (1.0): `f4cd194bdee0d04ca4eac29e09b8e4e9d818c133`
|
||||
- Crystal (1.1): `f2f52230b536214ef7c9924f483392993e226cfb`
|
||||
|
||||
The packaged app contains neither a ROM nor pre-extracted game data. Music,
|
||||
sound effects, and cries are synthesized while the game runs from compact
|
||||
|
||||
@@ -96,6 +96,10 @@ GameVersion.VERSIONS = {
|
||||
saveSuffix = "_crystal", -- save_crystal.lua / .bak / .tmp
|
||||
generation = 2,
|
||||
engine = "crystal",
|
||||
revisions = {
|
||||
{ sha1 = "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133", label = "1.0" },
|
||||
{ sha1 = "f2f52230b536214ef7c9924f483392993e226cfb", label = "1.1" },
|
||||
},
|
||||
fixes = {
|
||||
-- pokegold/docs/bugs_and_glitches.md:61
|
||||
luckyNumberBoxes = true,
|
||||
@@ -168,10 +172,29 @@ function GameVersion.cachePrefix(id)
|
||||
return GameVersion.info(id).cachePrefix
|
||||
end
|
||||
|
||||
function GameVersion.revisions(id)
|
||||
local info = GameVersion.info(id)
|
||||
return info.revisions or { { sha1 = info.sha1 } }
|
||||
end
|
||||
|
||||
function GameVersion.acceptsSha1(id, sha1)
|
||||
for _, revision in ipairs(GameVersion.revisions(id)) do
|
||||
if revision.sha1 == sha1 then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function GameVersion.revisionLabel(id, sha1)
|
||||
for _, revision in ipairs(GameVersion.revisions(id)) do
|
||||
if revision.sha1 == sha1 then return revision.label end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- The version a ROM belongs to, by its SHA-1, or nil for an unknown ROM.
|
||||
function GameVersion.forSha1(sha1)
|
||||
for id, info in pairs(GameVersion.VERSIONS) do
|
||||
if info.sha1 == sha1 then return id end
|
||||
for id in pairs(GameVersion.VERSIONS) do
|
||||
if GameVersion.acceptsSha1(id, sha1) then return id end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
@@ -136,8 +136,15 @@ function CacheContract.requiredFilesFor(version)
|
||||
return CacheContract.REQUIRED_FILES, false
|
||||
end
|
||||
|
||||
function CacheContract.markerFor(version)
|
||||
return CacheContract.FORMAT .. GameVersion.info(version).sha1
|
||||
function CacheContract.markerFor(version, sha1)
|
||||
return CacheContract.FORMAT .. (sha1 or GameVersion.info(version).sha1)
|
||||
end
|
||||
|
||||
function CacheContract.markerMatches(version, marker)
|
||||
for _, revision in ipairs(GameVersion.revisions(version)) do
|
||||
if marker == CacheContract.markerFor(version, revision.sha1) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Keep the process-global CacheFs prefix isolated even when a filesystem
|
||||
@@ -189,11 +196,11 @@ function CacheContract.isReady(version, fs)
|
||||
fs = fs or require("src.import.CacheFs")
|
||||
if CacheContract.sourceTreeHasData(version) then return true end
|
||||
local marker, readError = CacheContract.readMarker(version, fs)
|
||||
if readError or marker ~= CacheContract.markerFor(version) then return false end
|
||||
if readError or not CacheContract.markerMatches(version, marker) then return false end
|
||||
return CacheContract.allRequiredFilesExist(version, fs)
|
||||
end
|
||||
|
||||
function CacheContract.publish(version, fs)
|
||||
function CacheContract.publish(version, fs, sha1)
|
||||
fs = fs or require("src.import.CacheFs")
|
||||
local complete, missing = CacheContract.allRequiredFilesExist(version, fs)
|
||||
if not complete then
|
||||
@@ -212,7 +219,7 @@ function CacheContract.publish(version, fs)
|
||||
return false, "cache is incomplete; missing " .. tostring(missing)
|
||||
end
|
||||
local changed, ok, err = withVersionPrefix(version, fs, function()
|
||||
return fs.write(CacheContract.MARKER_PATH, CacheContract.markerFor(version))
|
||||
return fs.write(CacheContract.MARKER_PATH, CacheContract.markerFor(version, sha1))
|
||||
end)
|
||||
if not changed then return false, tostring(ok) end
|
||||
return ok, err
|
||||
|
||||
@@ -13,7 +13,7 @@ require("love.math")
|
||||
require("love.system")
|
||||
require("love.timer")
|
||||
|
||||
local version, prefix, romData, progressName, resultName = ...
|
||||
local version, prefix, romData, progressName, resultName, romSha1 = ...
|
||||
|
||||
local progressChannel = love.thread.getChannel(progressName)
|
||||
local resultChannel = love.thread.getChannel(resultName)
|
||||
@@ -44,7 +44,7 @@ local ok, err = pcall(function()
|
||||
current = current, stageTotal = stageTotal,
|
||||
})
|
||||
end
|
||||
end)
|
||||
end, romSha1)
|
||||
extractor:run()
|
||||
end)
|
||||
|
||||
|
||||
@@ -201,14 +201,23 @@ local function copy(value)
|
||||
return result
|
||||
end
|
||||
|
||||
function RomExtractorGen2.new(romData, manifest, progress)
|
||||
function RomExtractorGen2.new(romData, manifest, progress, romSha1)
|
||||
-- _GOLD / _SILVER: the labels are shared, the data behind a handful of
|
||||
-- them is not (gfx/misc.asm:9-20 vs :46-57).
|
||||
local edition = GameVersion.forSha1(manifest.romSha1) or "gold"
|
||||
local symbols = manifest.symbols
|
||||
local revision = romSha1 and manifest.symbolRevisions
|
||||
and manifest.symbolRevisions[romSha1]
|
||||
if revision then
|
||||
local merged = {}
|
||||
for name, location in pairs(manifest.symbols) do merged[name] = location end
|
||||
for name, location in pairs(revision) do merged[name] = location end
|
||||
symbols = merged
|
||||
end
|
||||
return setmetatable({
|
||||
rom = Rom.new(romData),
|
||||
manifest = manifest,
|
||||
symbols = manifest.symbols,
|
||||
symbols = symbols,
|
||||
progress = progress,
|
||||
stage = 0,
|
||||
edition = edition,
|
||||
|
||||
@@ -1432,7 +1432,7 @@ function RomImporter.new(onComplete, opts)
|
||||
-- "update required" (re-import) rather than a clean first-run choose
|
||||
local marker = CacheContract.readMarker(version, CacheFs)
|
||||
self.returning[version] =
|
||||
(not ready) and marker ~= nil and marker ~= CacheContract.markerFor(version)
|
||||
(not ready) and marker ~= nil and not CacheContract.markerMatches(version, marker)
|
||||
self.romName[version] = "pokemon_" .. info.id
|
||||
.. ((info.id == "yellow" or GameVersion.generation(version) == 2)
|
||||
and ".gbc" or ".gb")
|
||||
@@ -1721,6 +1721,7 @@ function RomImporter:startData(data, displayName)
|
||||
.. "(tagged [b] or [BF]) never verify."):format(actualHash, cartsProse()))
|
||||
return
|
||||
end
|
||||
self.romSha1 = actualHash
|
||||
local info = GameVersion.info(version)
|
||||
|
||||
-- Bring the launcher to this version's tab so its progress bar is on screen
|
||||
@@ -1775,7 +1776,7 @@ function RomImporter:_startExtractThread(version, prefix, data, displayName)
|
||||
love.thread.getChannel(progressName):clear()
|
||||
love.thread.getChannel(resultName):clear()
|
||||
local started = pcall(thread.start, thread, version, prefix, data,
|
||||
progressName, resultName)
|
||||
progressName, resultName, self.romSha1)
|
||||
if not started then return false end
|
||||
self._extract = {
|
||||
thread = thread, version = version, prefix = prefix,
|
||||
@@ -1804,7 +1805,7 @@ function RomImporter:_startExtractCoroutine(version, info, prefix, displayName)
|
||||
self.stageCurrent = current
|
||||
self.stageTotal = stageTotal
|
||||
coroutine.yield()
|
||||
end)
|
||||
end, self.romSha1)
|
||||
extractor:run()
|
||||
CacheFs.prefix = "" -- restore the default so later writes stay at the root
|
||||
self.romData = nil
|
||||
@@ -1822,7 +1823,7 @@ function RomImporter:_completeImport(version, prefix, displayName)
|
||||
-- appear once every required file is in place.
|
||||
local savedPrefix = CacheFs.prefix
|
||||
CacheFs.prefix = prefix
|
||||
local ok, writeError = CacheContract.publish(version, CacheFs)
|
||||
local ok, writeError = CacheContract.publish(version, CacheFs, self.romSha1)
|
||||
CacheFs.prefix = savedPrefix
|
||||
if not ok then
|
||||
error("could not finish the private cache: " .. tostring(writeError))
|
||||
|
||||
@@ -18,7 +18,8 @@ function RomManifest.decode(version)
|
||||
if not manifest then
|
||||
error("ROM import metadata is invalid: " .. tostring(decodeError))
|
||||
end
|
||||
assert(manifest.romSha1 == info.sha1, "ROM import metadata version mismatch")
|
||||
assert(GameVersion.acceptsSha1(version, manifest.romSha1),
|
||||
"ROM import metadata version mismatch")
|
||||
return manifest
|
||||
end
|
||||
|
||||
|
||||
@@ -37,6 +37,36 @@ eq(crystal.romSha1, GameVersion.VERSIONS.crystal.sha1,
|
||||
eq(crystal.generation, 2, "generation 2")
|
||||
eq(crystal.format, gold.format, "same manifest format as Gold")
|
||||
|
||||
-- ------- 1b. the 1.1 revision's symbol delta
|
||||
|
||||
local rev11
|
||||
for _, revision in ipairs(GameVersion.revisions("crystal")) do
|
||||
if revision.label == "1.1" then rev11 = revision end
|
||||
end
|
||||
check(rev11 ~= nil, "the crystal row names a 1.1 revision")
|
||||
|
||||
if rev11 then
|
||||
check(GameVersion.acceptsSha1("crystal", GameVersion.VERSIONS.crystal.sha1),
|
||||
"the crystal row accepts the canonical 1.0 sha1")
|
||||
check(GameVersion.acceptsSha1("crystal", rev11.sha1),
|
||||
"and the 1.1 sha1 too")
|
||||
|
||||
local overlay = (crystal.symbolRevisions or {})[rev11.sha1]
|
||||
check(type(overlay) == "table",
|
||||
"the manifest carries a symbolRevisions overlay for the 1.1 sha1")
|
||||
if overlay then
|
||||
eq(size(overlay), 1, "with exactly one symbol moved")
|
||||
local moved = overlay.Stadium2N64Attrmap
|
||||
check(type(moved) == "table", "and it names Stadium2N64Attrmap")
|
||||
local base = crystal.symbols.Stadium2N64Attrmap
|
||||
check(base ~= nil, "the base manifest still carries Stadium2N64Attrmap")
|
||||
if moved and base then
|
||||
check(not (moved[1] == base[1] and moved[2] == base[2]),
|
||||
"at a location different from the base symbols entry")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ------- 2. content counts
|
||||
|
||||
eq(size(crystal.maps), 388, "388 maps")
|
||||
|
||||
@@ -68,11 +68,56 @@ eq(GameVersion.ORDER[5], "silver", "silver keeps slot 5")
|
||||
-- ------- 4. sha1 routing
|
||||
|
||||
eq(GameVersion.forSha1("f4cd194bdee0d04ca4eac29e09b8e4e9d818c133"), "crystal",
|
||||
"the retail Crystal sha1 resolves to crystal")
|
||||
"the retail Crystal 1.0 sha1 resolves to crystal")
|
||||
eq(GameVersion.forSha1("f2f52230b536214ef7c9924f483392993e226cfb"), "crystal",
|
||||
"the retail Crystal 1.1 sha1 also resolves to crystal")
|
||||
eq(GameVersion.forSha1("d8b8a3600a465308c9953dfa04f0081c05bdcb94"), "gold",
|
||||
"Gold's sha1 still resolves to gold")
|
||||
eq(GameVersion.forSha1("deadbeef"), nil, "an unknown ROM resolves to nothing")
|
||||
|
||||
-- ------- 4b. revisions / acceptsSha1 / revisionLabel
|
||||
|
||||
local crystalRevisions = GameVersion.revisions("crystal")
|
||||
eq(#crystalRevisions, 2, "crystal lists both accepted revisions")
|
||||
eq(crystalRevisions[1].sha1, "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133",
|
||||
"revision 1 is the 1.0 sha1")
|
||||
eq(crystalRevisions[1].label, "1.0", "revision 1 is labeled 1.0")
|
||||
eq(crystalRevisions[2].sha1, "f2f52230b536214ef7c9924f483392993e226cfb",
|
||||
"revision 2 is the 1.1 sha1")
|
||||
eq(crystalRevisions[2].label, "1.1", "revision 2 is labeled 1.1")
|
||||
|
||||
check(GameVersion.acceptsSha1("crystal", "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133"),
|
||||
"crystal accepts the 1.0 sha1")
|
||||
check(GameVersion.acceptsSha1("crystal", "f2f52230b536214ef7c9924f483392993e226cfb"),
|
||||
"crystal accepts the 1.1 sha1")
|
||||
check(not GameVersion.acceptsSha1("crystal", "deadbeef"),
|
||||
"crystal rejects an unknown sha1")
|
||||
|
||||
eq(GameVersion.revisionLabel("crystal", "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133"),
|
||||
"1.0", "revisionLabel resolves the 1.0 hash")
|
||||
eq(GameVersion.revisionLabel("crystal", "f2f52230b536214ef7c9924f483392993e226cfb"),
|
||||
"1.1", "revisionLabel resolves the 1.1 hash")
|
||||
eq(GameVersion.revisionLabel("crystal", "deadbeef"), nil,
|
||||
"revisionLabel is nil for an unrecognized hash")
|
||||
|
||||
local goldRevisions = GameVersion.revisions("gold")
|
||||
eq(#goldRevisions, 1, "gold synthesizes a single revision entry")
|
||||
eq(goldRevisions[1].sha1, GameVersion.info("gold").sha1,
|
||||
"the synthesized entry carries gold's canonical sha1")
|
||||
check(GameVersion.acceptsSha1("gold", GameVersion.info("gold").sha1),
|
||||
"gold accepts its own canonical sha1")
|
||||
check(not GameVersion.acceptsSha1("gold", "deadbeef"),
|
||||
"gold rejects an unknown sha1")
|
||||
eq(GameVersion.revisionLabel("gold", GameVersion.info("gold").sha1), nil,
|
||||
"gold's synthesized entry carries no label")
|
||||
|
||||
local redRevisions = GameVersion.revisions("red")
|
||||
eq(#redRevisions, 1, "red synthesizes a single revision entry")
|
||||
check(GameVersion.acceptsSha1("red", GameVersion.info("red").sha1),
|
||||
"red accepts its own canonical sha1")
|
||||
eq(GameVersion.forSha1(GameVersion.info("red").sha1), "red",
|
||||
"and forSha1 still resolves red through the synthesized entry")
|
||||
|
||||
-- ------- 5. set / get round trip
|
||||
|
||||
local savedCurrent = GameVersion.get()
|
||||
|
||||
@@ -110,6 +110,56 @@ check(not silverSet["assets/generated/trade/game_boy.png"],
|
||||
check(CacheContract.VERSION_REQUIRED_FILES.yellow ~= nil,
|
||||
"Yellow has version-specific required outputs")
|
||||
|
||||
-- Revisioned cache markers: Crystal accepts either the 1.0 or the 1.1 cart.
|
||||
local CRYSTAL_1_0 = "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133"
|
||||
local CRYSTAL_1_1 = "f2f52230b536214ef7c9924f483392993e226cfb"
|
||||
|
||||
eq(CacheContract.markerFor("crystal", CRYSTAL_1_1),
|
||||
CacheContract.FORMAT .. CRYSTAL_1_1,
|
||||
"markerFor with an explicit sha1 uses that sha1, not the canonical one")
|
||||
eq(CacheContract.markerFor("crystal"), CacheContract.FORMAT .. CRYSTAL_1_0,
|
||||
"markerFor with no sha1 still defaults to the canonical (1.0) sha1")
|
||||
|
||||
check(CacheContract.markerMatches("crystal", CacheContract.FORMAT .. CRYSTAL_1_0),
|
||||
"a marker written from the 1.0 hash matches crystal")
|
||||
check(CacheContract.markerMatches("crystal", CacheContract.FORMAT .. CRYSTAL_1_1),
|
||||
"a marker written from the 1.1 hash also matches crystal")
|
||||
check(not CacheContract.markerMatches("crystal",
|
||||
CacheContract.FORMAT .. "ea9bcae617fdf159b045185467ae58b2e4a48b9a"),
|
||||
"a marker written from Red's hash does not match crystal")
|
||||
check(not CacheContract.markerMatches("red", CacheContract.FORMAT .. CRYSTAL_1_1),
|
||||
"a marker written from Crystal's 1.1 hash does not match red")
|
||||
|
||||
local crystalFs = { prefix = "crystal-marker-test/", files = {} }
|
||||
function crystalFs.exists(path) return crystalFs.files[crystalFs.prefix .. path] ~= nil end
|
||||
function crystalFs.read(path) return crystalFs.files[crystalFs.prefix .. path] end
|
||||
function crystalFs.write(path, value)
|
||||
crystalFs.files[crystalFs.prefix .. path] = value
|
||||
return true
|
||||
end
|
||||
function crystalFs.remove(path) crystalFs.files[crystalFs.prefix .. path] = nil end
|
||||
|
||||
local crystalRequired = CacheContract.requiredFilesFor("crystal")
|
||||
for _, path in ipairs(crystalRequired) do
|
||||
crystalFs.files["crystal/" .. path] = true
|
||||
end
|
||||
local published11 = CacheContract.publish("crystal", crystalFs, CRYSTAL_1_1)
|
||||
check(published11, "publishing with the 1.1 sha1 succeeds")
|
||||
eq(crystalFs.files["crystal/" .. CacheContract.MARKER_PATH],
|
||||
CacheContract.FORMAT .. CRYSTAL_1_1, "the marker records the 1.1 sha1")
|
||||
check(CacheContract.isReady("crystal", crystalFs),
|
||||
"a cache published with the 1.1 sha1 still reads ready for crystal")
|
||||
|
||||
crystalFs.files["crystal/" .. CacheContract.MARKER_PATH] =
|
||||
CacheContract.FORMAT .. "ea9bcae617fdf159b045185467ae58b2e4a48b9a"
|
||||
check(not CacheContract.isReady("crystal", crystalFs),
|
||||
"a marker from another version's hash does not read ready for crystal")
|
||||
|
||||
check(CacheContract.markerMatches("blue", CacheContract.markerFor("blue")),
|
||||
"blue's own marker still matches blue")
|
||||
check(not CacheContract.markerMatches("blue", CacheContract.markerFor("red")),
|
||||
"red's marker still does not match blue")
|
||||
|
||||
-- A throwing adapter must not strand the process in its temporary prefix.
|
||||
local throwingFs = { prefix = "before/" }
|
||||
function throwingFs.exists() error("probe failed") end
|
||||
|
||||
@@ -194,6 +194,21 @@ if not cache then
|
||||
cache = home .. "/Library/Application Support/LOVE/crystal-dev/crystal"
|
||||
end
|
||||
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local CacheContract = require("src.import.CacheContract")
|
||||
|
||||
local function crystalCacheRevision()
|
||||
local fs = { prefix = "", read = function(rel) return readFile(cache .. "/" .. rel) end }
|
||||
local marker = CacheContract.readMarker("crystal", fs)
|
||||
if not marker then return "1.0" end
|
||||
for _, revision in ipairs(GameVersion.revisions("crystal")) do
|
||||
if marker == CacheContract.markerFor("crystal", revision.sha1) then
|
||||
return revision.label or "1.0"
|
||||
end
|
||||
end
|
||||
return "1.0"
|
||||
end
|
||||
|
||||
local function loadCache(rel)
|
||||
local chunk = loadfile(cache .. "/data/generated/" .. rel .. ".lua")
|
||||
if not chunk then return nil end
|
||||
@@ -338,7 +353,7 @@ local MAPS = {
|
||||
{ "centerAttrmap", "mobile_center_attrmap", 360,
|
||||
"gfx/mobile/mobile_center.attrmap" },
|
||||
{ "stadium2N64Tilemap", "stadium2_n64_tilemap", 360,
|
||||
"gfx/mobile/stadium2_n64.tilemap" },
|
||||
"revision" },
|
||||
{ "stadium2N64Attrmap", "stadium2_n64_attrmap", 360,
|
||||
"gfx/mobile/stadium2_n64.attrmap" },
|
||||
{ "dialpadTilemap", "dialpad_tilemap", 360, "gfx/mobile/dialpad.tilemap" },
|
||||
@@ -408,6 +423,14 @@ else
|
||||
|
||||
for _, row in ipairs(MAPS) do
|
||||
local key, file, bytes, source = row[1], row[2], row[3], row[4]
|
||||
local revisionLabel
|
||||
if key == "stadium2N64Tilemap" then
|
||||
-- ../pokecrystal/mobile/mobile_5c.asm:869
|
||||
revisionLabel = crystalCacheRevision()
|
||||
source = revisionLabel == "1.1"
|
||||
and "gfx/mobile/stadium2_n64_corrupt.tilemap"
|
||||
or "gfx/mobile/stadium2_n64.tilemap"
|
||||
end
|
||||
local entry = maps[key]
|
||||
local rel = "assets/generated/mobile/" .. file .. ".bin"
|
||||
if type(entry) ~= "table" then
|
||||
@@ -425,7 +448,10 @@ else
|
||||
check(true, "no ../pokecrystal: " .. source .. " not diffed (SKIP)")
|
||||
else
|
||||
check(blob == want:sub(1, bytes),
|
||||
"it is ../pokecrystal/" .. source .. " byte for byte")
|
||||
"it is ../pokecrystal/" .. source .. " byte for byte"
|
||||
.. (revisionLabel
|
||||
and (" (cache built from Crystal " .. revisionLabel .. ")")
|
||||
or ""))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,10 +31,19 @@ where `charmap` and `fontCharmap` already live, so a consumer picks the map it
|
||||
wants by name and no reader of the main table can see these bytes. The `ascii`
|
||||
map is not emitted, because nothing outside the Mobile System reads it.
|
||||
|
||||
The manifest also carries `symbolRevisions`, a map of ROM sha1 -> symbol name
|
||||
-> [bank, address] for the retail revisions this port accepts besides the 1.0
|
||||
hash in `romSha1`. Crystal has one, v1.1, where `Stadium2N64Attrmap` sits 13
|
||||
bytes later than on 1.0 because the Stadium 2 tilemap in front of it is longer
|
||||
(../pokecrystal/mobile/mobile_5c.asm:869). `crystal11_symbol_revisions` diffs
|
||||
the v1.1 symbol table against the resolved 1.0 `symbols` and keeps only the
|
||||
names that appear in both and moved.
|
||||
|
||||
Usage: python3 tools/make_crystal_manifest.py
|
||||
Default paths: pokecrystal at ../pokecrystal (relative to the repo) or
|
||||
/Users/bryanbassett/Documents/development/pokecrystal; symbols at
|
||||
/Users/bryanbassett/Documents/development/pokecrystal-symbols/pokecrystal.sym.
|
||||
/Users/bryanbassett/Documents/development/pokecrystal-symbols/pokecrystal.sym
|
||||
and /Users/bryanbassett/Documents/development/pokecrystal-symbols/pokecrystal11.sym.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -49,7 +58,9 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import make_gold_manifest as gold # noqa: E402
|
||||
from crystal_symbol_deltas import crystal_required # noqa: E402
|
||||
from rom_data import CANONICAL_CRYSTAL_SHA1 # noqa: E402
|
||||
from rom_data import ( # noqa: E402
|
||||
CANONICAL_CRYSTAL_SHA1, CANONICAL_CRYSTAL11_SHA1, SymbolTable,
|
||||
)
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
DEV = "/Users/bryanbassett/Documents/development"
|
||||
@@ -58,6 +69,8 @@ DEFAULT_POKECRYSTAL_CANDIDATES = [
|
||||
os.path.join(DEV, "pokecrystal"),
|
||||
]
|
||||
DEFAULT_SYMBOLS = os.path.join(DEV, "pokecrystal-symbols/pokecrystal.sym")
|
||||
DEFAULT_SYMBOLS11 = os.path.join(
|
||||
DEV, "pokecrystal-symbols/pokecrystal11.sym")
|
||||
DEFAULT_OUT = os.path.join(
|
||||
os.path.dirname(__file__), "rom_manifest_crystal.json")
|
||||
|
||||
@@ -126,7 +139,31 @@ def unown_charmap(pokecrystal, defines=None):
|
||||
return out
|
||||
|
||||
|
||||
def generate(pokecrystal, symbols_path):
|
||||
def crystal11_symbol_revisions(symbols11_path, base_symbols):
|
||||
"""Diff the v1.1 symbol table against the manifest's resolved 1.0 symbols.
|
||||
|
||||
Returns only the entries whose [bank, address] differs, restricted to
|
||||
names the 1.0 manifest actually carries -- a v1.1-only symbol with no
|
||||
1.0 counterpart is nothing an extractor built against `symbols` could
|
||||
ever look up, so it is not this table's business to report.
|
||||
"""
|
||||
if not os.path.isfile(symbols11_path):
|
||||
raise SystemExit(
|
||||
f"Crystal v1.1 symbol file not found: {symbols11_path} "
|
||||
"(pass --symbols11 or install it at the default path)")
|
||||
symbols11 = SymbolTable(symbols11_path)
|
||||
revisions = {}
|
||||
for name, location in base_symbols.items():
|
||||
symbol11 = symbols11.by_name.get(name)
|
||||
if symbol11 is None:
|
||||
continue
|
||||
location11 = [symbol11.bank, symbol11.address]
|
||||
if location11 != location:
|
||||
revisions[name] = location11
|
||||
return revisions
|
||||
|
||||
|
||||
def generate(pokecrystal, symbols_path, symbols11_path=DEFAULT_SYMBOLS11):
|
||||
data = gold.generate(
|
||||
pokecrystal, symbols_path,
|
||||
defines=CRYSTAL_ASM_DEFINES,
|
||||
@@ -139,6 +176,10 @@ def generate(pokecrystal, symbols_path):
|
||||
os.path.join(pokecrystal, "constants", "engine_flags.asm"),
|
||||
defines=CRYSTAL_ASM_DEFINES)
|
||||
data["unownCharmap"] = unown_charmap(pokecrystal, CRYSTAL_ASM_DEFINES)
|
||||
data["symbolRevisions"] = {
|
||||
CANONICAL_CRYSTAL11_SHA1: crystal11_symbol_revisions(
|
||||
symbols11_path, data["symbols"]),
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
@@ -153,13 +194,16 @@ def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--pokecrystal", default=find_pokecrystal())
|
||||
parser.add_argument("--symbols", default=DEFAULT_SYMBOLS)
|
||||
parser.add_argument("--symbols11", default=DEFAULT_SYMBOLS11)
|
||||
parser.add_argument("--out", default=DEFAULT_OUT)
|
||||
args = parser.parse_args()
|
||||
|
||||
pokecrystal = os.path.abspath(args.pokecrystal)
|
||||
if not os.path.isfile(os.path.join(pokecrystal, "main.asm")):
|
||||
raise SystemExit(f"{pokecrystal} is not a pokecrystal checkout")
|
||||
data = generate(pokecrystal, os.path.abspath(args.symbols))
|
||||
data = generate(
|
||||
pokecrystal, os.path.abspath(args.symbols),
|
||||
os.path.abspath(args.symbols11))
|
||||
with open(args.out, "w", encoding="utf-8", newline="\n") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2, sort_keys=True)
|
||||
f.write("\n")
|
||||
|
||||
+11
-4
@@ -14,9 +14,9 @@ CANONICAL_YELLOW_SHA1 = "cc7d03262ebfaf2f06772c1a480c7d9d5f4a38e1"
|
||||
# Gold and Silver are Gen 2: 2 MiB carts, twice the size of the Gen 1 ROMs above.
|
||||
CANONICAL_GOLD_SHA1 = "d8b8a3600a465308c9953dfa04f0081c05bdcb94"
|
||||
CANONICAL_SILVER_SHA1 = "49b163f7e57702bc939d642a18f591de55d92dae"
|
||||
# Crystal is the retail international v1.0 build (pret/pokecrystal's default
|
||||
# `make` target), also 2 MiB.
|
||||
# Crystal is the retail international v1.0 cart, also 2 MiB.
|
||||
CANONICAL_CRYSTAL_SHA1 = "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133"
|
||||
CANONICAL_CRYSTAL11_SHA1 = "f2f52230b536214ef7c9924f483392993e226cfb"
|
||||
ROM_BANK_SIZE = 0x4000
|
||||
|
||||
|
||||
@@ -74,9 +74,16 @@ class RomImage:
|
||||
with open(path, "rb") as f:
|
||||
self.data = f.read()
|
||||
self.sha1 = hashlib.sha1(self.data).hexdigest()
|
||||
if expected_sha1 and self.sha1 != expected_sha1:
|
||||
if isinstance(expected_sha1, (tuple, set, frozenset, list)):
|
||||
allowed = expected_sha1
|
||||
elif expected_sha1:
|
||||
allowed = (expected_sha1,)
|
||||
else:
|
||||
allowed = None
|
||||
if allowed and self.sha1 not in allowed:
|
||||
raise ValueError(
|
||||
f"unsupported ROM SHA-1 {self.sha1}; expected {expected_sha1}")
|
||||
f"unsupported ROM SHA-1 {self.sha1}; "
|
||||
f"expected {' or '.join(allowed)}")
|
||||
|
||||
@staticmethod
|
||||
def offset(bank, address):
|
||||
|
||||
@@ -14279,6 +14279,14 @@
|
||||
}
|
||||
},
|
||||
"romSha1": "f4cd194bdee0d04ca4eac29e09b8e4e9d818c133",
|
||||
"symbolRevisions": {
|
||||
"f2f52230b536214ef7c9924f483392993e226cfb": {
|
||||
"Stadium2N64Attrmap": [
|
||||
92,
|
||||
29988
|
||||
]
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"AbraBackpic": [
|
||||
86,
|
||||
|
||||
Reference in New Issue
Block a user