Map the French, German, Italian and Spanish releases' addresses (#84)
Checks / web (push) Failing after 18s
Private ROM checks / private-rom (push) Failing after 25m51s

PRET builds none of these releases, so scripts/map-revision.mjs derives
where each keeps the profile's labels from the 1.1 build: a bank-by-bank
alignment, then the exact references RGBDS recorded while linking 1.1,
read at the matching place in the release, each result checked against
its bytes. All but 1-3 labels per release are found; the rest, in code the
release changed, are -1 so reading them refuses. check:reference verifies
the generated file when the ROMs are present.

CrystalAssets now points the profile at the cartridge's addresses, and
the releases' characters (from their own fonts) and two European text
codes decode. The introduction and Mom's scene play in each language and
every map sweeps as in English, but the releases stay refused until the
logic they localize (units, word order, layouts) is ported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
kawaandClaude Opus 5.5 committed 2026-10-17 17:45:34 +02:00
1 parent 3c480a28f0
commit 627f87d8b5
17 files changed
+1202 -190

No files matched your search

+4 -2
View File
@@ -1,4 +1,5 @@
import { PokemonData } from "../rom/pokemon";
import { NAME_LETTERS } from "../rom/charset";
import type { Mail } from "./mail";
/** A party Pokémon with the fields of the original party_struct. */
@@ -75,8 +76,9 @@ export function partyMonDetails(mon: PartyMon): string {
export const PK = "ᴾᴷ",
MN = "ᴹᴺ";
/** One character of a name: the naming screen's letters and symbols, plus
* the digits and apostrophes of default species names. */
const NAME_CHARACTER = `(?:[A-Za-z0-9 ()":;[\\]?!♂♀/.,×'é-]|${PK}|${MN})`;
* the digits and apostrophes of default species names and the European
* releases' letters. */
const NAME_CHARACTER = `(?:[A-Za-z0-9 ()":;[\\]?!♂♀/.,×'é${NAME_LETTERS}-]|${PK}|${MN})`;
/** Characters a nickname can hold (up to ten). */
export const NICKNAME_PATTERN = new RegExp(`^${NAME_CHARACTER}{1,10}$`, "u");
/** The same characters, for box names (up to eight). */
+3
View File
@@ -1,5 +1,6 @@
import { RomReader } from "./reader";
import { decompress } from "./lz";
import { knownRevision, useRevision } from "./revision";
import {
LAST_MAP_GROUP_SIZE,
MAP_GROUP_COUNT,
@@ -43,6 +44,8 @@ export class CrystalAssets {
private sceneOwners?: Map<string, string>;
constructor(bytes: Uint8Array) {
this.reader = new RomReader(bytes);
// Synthetic ROMs in tests carry no game code: they read as 1.1.
useRevision(knownRevision(this.reader));
this.pokemon = new PokemonData(this.reader);
this.pokedex = new PokedexData(this.reader);
}
+105
View File
@@ -0,0 +1,105 @@
/** PlaceNextChar's own codes in every European release: $1d places "-"
* and breaks the line (a word hyphenated at a line's end), $1e prints
* nothing (a word break, where the Town Map wraps a long name). */
const EUROPE: Record<number, string> = { 0x1d: "-\n", 0x1e: "" };
/** The characters the European releases add to the English charmap
* (constants/charmap.asm), identified from each release's own 1bpp font
* (Font, $80-$ff). French and German share one font, Italian and Spanish
* another; neither keeps English's apostrophe ligatures at $d0-$d6. */
const NORTH: Record<number, string> = {
...EUROPE,
0xba: "à",
0xbb: "è",
0xbc: "é",
0xbd: "ù",
0xbe: "ß",
0xbf: "ç",
0xc6: "ë",
0xc7: "ï",
0xc8: "â",
0xc9: "ô",
0xca: "û",
0xcb: "ê",
0xcc: "î",
0xcf: "←",
0xd4: "c'",
0xd5: "d'",
0xd6: "j'",
0xd7: "l'",
0xd8: "m'",
0xd9: "n'",
0xda: "p'",
0xdb: "s'",
0xdc: "'s",
0xdd: "t'",
0xde: "u'",
0xdf: "y'",
0xe4: "+",
};
const SOUTH: Record<number, string> = {
...EUROPE,
0xba: "à",
0xbb: "è",
0xbc: "é",
0xbd: "ù",
0xbe: "À",
0xbf: "Á",
0xc6: "È",
0xc7: "É",
0xc8: "Ì",
0xc9: "Í",
0xca: "Ñ",
0xcb: "Ò",
0xcc: "Ó",
0xcd: "Ù",
0xce: "Ú",
0xcf: "á",
0xd0: "ì",
0xd1: "í",
0xd2: "ñ",
0xd3: "ò",
0xd4: "ó",
0xd5: "ú",
0xd6: "º",
0xd7: "&",
0xd8: "'d",
0xd9: "'l",
0xda: "'m",
0xdb: "'r",
0xdc: "'s",
0xdd: "'t",
0xde: "'v",
0xe4: "¿",
0xe5: "¡",
};
export const LANGUAGE_CHARACTERS = { fr: NORTH, de: NORTH, it: SOUTH, es: SOUTH } as const;
/** Every letter a supported release can store in a name: English's
* umlauts ($c0-$c5) and the releases' own. */
export const NAME_LETTERS = [...new Set(
[..."ÄÖÜäöü", ...Object.values(NORTH), ...Object.values(SOUTH)].filter(
(value) => value.length === 1 && /\p{L}/u.test(value),
),
)].join("");
let active: Record<number, string> = {};
let codes = new Map<string, number>();
/** Use a release's characters (none for the English cartridges). */
export function useCharacters(language?: keyof typeof LANGUAGE_CHARACTERS) {
active = language ? LANGUAGE_CHARACTERS[language] : {};
codes = new Map();
for (const [code, value] of Object.entries(active))
if (value.length === 1 && !codes.has(value)) codes.set(value, Number(code));
}
/** The active release's character for a code (undefined when it keeps
* English's). French and German leave $d0-$d3 blank. */
export function releaseCharacter(code: number): string | null | undefined {
if (code in active) return active[code];
if (active === NORTH && code >= 0xd0 && code <= 0xd3) return null;
return undefined;
}
/** The active release's code for a character it adds. */
export const releaseCode = (character: string) => codes.get(character);
+8 -1
View File
@@ -1,5 +1,6 @@
import type { RomReader } from "./reader";
import { JOHTO } from "./profile";
import { releaseCode } from "./charset";
/** TEXTBOX_FRAME_TILES and NUM_FRAMES (constants/text_constants.asm). */
const TEXTBOX_FRAME_TILES = 6,
@@ -51,6 +52,12 @@ const CODES: Record<string, number> = {
";": 0x9d,
"[": 0x9e,
"]": 0x9f,
Ä: 0xc0,
Ö: 0xc1,
Ü: 0xc2,
ä: 0xc3,
ö: 0xc4,
ü: 0xc5,
"'": 0xe0,
"-": 0xe3,
"?": 0xe6,
@@ -81,7 +88,7 @@ export function textTiles(text: string): number[] {
if (character >= "A" && character <= "Z") return 0x80 + code - 65;
if (character >= "a" && character <= "z") return 0xa0 + code - 97;
if (character >= "0" && character <= "9") return 0xf6 + code - 48;
const mapped = CODES[character];
const mapped = CODES[character] ?? releaseCode(character);
if (mapped === undefined) throw new Error(`No tile for "${character}".`);
return mapped;
});
+11
View File
@@ -0,0 +1,11 @@
// Generated by scripts/map-revision.mjs from PRET's English 1.1 build and
// each release: where it keeps each profile entry (table.key) whose label
// moved from 1.1; 0 where it did not, -1 where the label was not found
// there, which the port must not read in that release. Do not edit.
export const REVISION_KEYS = ["BATTLE.AlreadyAsleepText","BATTLE.AlreadyConfusedText","BATTLE.AlreadyParalyzedText","BATTLE.AlreadyPoisonedText","BATTLE.AskForgetMoveText","BATTLE.AskGiveNicknameText","BATTLE.AsleepTreeMonsDay","BATTLE.AsleepTreeMonsMorn","BATTLE.AsleepTreeMonsNite","BATTLE.AttackMissed2Text","BATTLE.AttackMissedText","BATTLE.BadgeTypeBoosts","BATTLE.BadlyPoisonedText","BATTLE.BallAlmostHadItText","BATTLE.BallAppearedCaughtText","BATTLE.BallBlockedText","BATTLE.BallBoxFullText","BATTLE.BallBrokeFreeText","BATTLE.BallDontBeAThiefText","BATTLE.BallSentToPCText","BATTLE.BallSoCloseText","BATTLE.BattleCommand_Charge.BattleDugText","BATTLE.BattleCommand_Charge.BattleFlewText","BATTLE.BattleCommand_Charge.BattleGlowingText","BATTLE.BattleCommand_Charge.BattleLoweredHeadText","BATTLE.BattleCommand_Charge.BattleMadeWhirlwindText","BATTLE.BattleCommand_Charge.BattleTookSunlightText","BATTLE.BattleCommand_Charge.UsedText","BATTLE.BattleMonNicknameText","BATTLE.BattleText_AnEGGCantBattle","BATTLE.BattleText_CantEscape","BATTLE.BattleText_CantEscape2","BATTLE.BattleText_EnemyIsAboutToUseWillPlayerChangeMon","BATTLE.BattleText_EnemyMonFainted","BATTLE.BattleText_EnemySentOut","BATTLE.BattleText_EnemyWasDefeated","BATTLE.BattleText_GotAwaySafely","BATTLE.BattleText_ItemHealedConfusion","BATTLE.BattleText_ItemsCantBeUsedHere","BATTLE.BattleText_MonCantBeRecalled","BATTLE.BattleText_MonFainted","BATTLE.BattleText_MonHasNoMovesLeft","BATTLE.BattleText_MonIsAlreadyOut","BATTLE.BattleText_MonsLightScreenFell","BATTLE.BattleText_MonsReflectFaded","BATTLE.BattleText_RainContinuesToFall","BATTLE.BattleText_SafeguardFaded","BATTLE.BattleText_StringBuffer1GrewToLevel","BATTLE.BattleText_TargetRecoveredWithItem","BATTLE.BattleText_TargetWasHitByFutureSight","BATTLE.BattleText_TargetsEncoreEnded","BATTLE.BattleText_TheMoveIsDisabled","BATTLE.BattleText_TheRainStopped","BATTLE.BattleText_TheSandstormRages","BATTLE.BattleText_TheSandstormSubsided","BATTLE.BattleText_TheSunlightFaded","BATTLE.BattleText_TheSunlightIsStrong","BATTLE.BattleText_TheresNoEscapeFromTrainerBattle","BATTLE.BattleText_TheresNoPPLeftForThisMove","BATTLE.BattleText_TheresNoWillToBattle","BATTLE.BattleText_UseNextMon","BATTLE.BattleText_UserFledUsingAStringBuffer1","BATTLE.BattleText_UserHurtBySpikes","BATTLE.BattleText_UserRecoveredPPUsing","BATTLE.BattleText_UserWasReleasedFromStringBuffer1","BATTLE.BattleText_UsersHurtByStringBuffer1","BATTLE.BattleText_UsersStringBuffer1Activated","BATTLE.BattleText_WildFled","BATTLE.BeatUpAttackText","BATTLE.BecameConfusedText","BATTLE.BeganToNapText","BATTLE.BellChimedText","BATTLE.BellyDrumText","BATTLE.BitterBerryEffect","BATTLE.BlewSpikesText","BATTLE.BlownAwayText","BATTLE.BoostedExpPointsText","BATTLE.BracedItselfText","BATTLE.BugContest_SetCaughtContestMon.ContestCaughtMonText","BATTLE.ButItFailedText","BATTLE.CantEscapeNowText","BATTLE.CatchTutorial.Dude","BATTLE.ChooseAMonString","BATTLE.ClampedByText","BATTLE.CoinsScatteredText","BATTLE.ComeBackText","BATTLE.ConfusedNoMoreText","BATTLE.ConsumableEffects","BATTLE.ContestAskSwitchText","BATTLE.CopiedStatsText","BATTLE.CoveredByVeilText","BATTLE.CrashedText","BATTLE.CriticalHitText","BATTLE.DefrostedOpponentText","BATTLE.DestinyBondEffectText","BATTLE.DidNotLearnMoveText","BATTLE.DidntAffect1Text","BATTLE.DidntAffect2Text","BATTLE.DireHitEffect","BATTLE.DisabledMoveText","BATTLE.DisabledNoMoreText","BATTLE.DisplayAlreadyCaughtText.ContestAlreadyCaughtText","BATTLE.DoItMonText","BATTLE.DoesntAffectText","BATTLE.DownpourText","BATTLE.DraggedOutText","BATTLE.DreamEatenText","BATTLE.EliminatedStatsText","BATTLE.EnduredText","BATTLE.EnemyHitTimesText","BATTLE.EnemyText","BATTLE.EnergyRootEffect","BATTLE.EnergypowderEffect","BATTLE.EvadedText","BATTLE.ExpPointsText","BATTLE.FastAsleepText","BATTLE.FellAsleepText","BATTLE.FellInLoveText","BATTLE.FireSpinTrapText","BATTLE.FledFromBattleText","BATTLE.FledInFearText","BATTLE.FlinchedText","BATTLE.ForesawAttackText","BATTLE.FrozenSolidText","BATTLE.FullRestoreEffect","BATTLE.FullyParalyzedText","BATTLE.GettingPumpedText","BATTLE.GoForItMonText","BATTLE.GoMonText","BATTLE.GoodComeBackText","BATTLE.GotAnEncoreText","BATTLE.GotMoneyForWinningText","BATTLE.GuardSpecEffect","BATTLE.HPIsFullText","BATTLE.HappinessChanges","BATTLE.HasANightmareText","BATTLE.HasNoPPLeftText","BATTLE.HasSubstituteText","BATTLE.HealPowderEffect","BATTLE.HealingHPAmounts","BATTLE.HeavyBallMultiplier.WeightsTable","BATTLE.HeavyBall_GetDexEntryBank.PokedexEntryBanks","BATTLE.HeldStatusHealingEffects","BATTLE.HookedPokemonAttackedText","BATTLE.HungOnText","BATTLE.HurtByBurnText","BATTLE.HurtByCurseText","BATTLE.HurtByPoisonText","BATTLE.HurtItselfText","BATTLE.IdentifiedText","BATTLE.IgnoredOrders2Text","BATTLE.IgnoredOrdersText","BATTLE.IgnoredSleepingText","BATTLE.InLoveWithText","BATTLE.InfatuationText","BATTLE.IsConfusedText","BATTLE.IsHMMove.HMMoves","BATTLE.ItFailedText","BATTLE.ItemAttributes","BATTLE.ItemCantUseOnEggText","BATTLE.IteLine truncated
export const REVISION_ADDRESSES = {
fr: [528985,527975,530010,529047,26265,60826,256876,256882,256864,528585,528564,1031749,529018,60772,60767,63433,63453,60762,63438,60816,60777,224300,224295,224290,224285,224275,224280,224224,258802,527258,527410,527290,527163,526842,527214,526889,527359,527947,527492,527546,527068,527633,527521,526595,526633,526670,526565,527681,526477,526532,527660,527606,526760,526728,526811,526784,526700,527309,527573,527231,527082,527382,527429,526501,528020,527995,527473,526265,530720,527928,528323,528953,530605,61711,530538,529248,258351,530380,59122,529804,530108,321009,0,528119,529612,258910,527907,160233,835777,530653,530422,528606,528666,529136,528888,26260,529842,529862,62557,528278,527834,835791,258779,528646,530559,529910,529100,529662,528201,529285,4711,61749,61745,529565,258356,527763,528973,530405,528084,529217,529229,527801,530696,527788,61645,529982,529359,258786,258772,258901,528803,526868,62544,529882,29243,526373,528460,529421,61266,62292,60521,60449,253510,526176,528179,526340,526395,526318,527871,530286,530765,528381,528399,528229,528251,527856,13533,529823,26587,63413,59153,63408,63458,63428,26245,0,529725,528306,529403,1031277,227769,530477,226393,529531,530058,529306,26250,26292,160255,1072770,1072683,1072705,527819,60821,528427,528723,529780,258896,68338,528682,63198,529944,526441,529264,62516,279404,526201,530739,227596,329073,330662,529330,530032,530215,530234,530177,528775,529377,527449,529753,528539,530494,528501,61739,62820,63183,61518,61549,530444,530346,526410,527003,526971,526918,528819,530516,528867,530246,528925,530302,530134,222443,61462,61297,530080,26255,528142,529505,529470,529071,530586,528700,26270,26287,60782,258334,258891,529436,528858,528753,529698,529634,528361,528625,528159,328948,528045,1072652,1072659,1072577,1072674,60811,526246,529126,530162,529584,529151,528999,529549,1031653,1031640,528489,328961,528070,526223,526148,258813,63839,527772,529190,528344,529164,528098,62503,62570,62633,258793,95168,95260,1838551,1838589,1838560,1838562,75027,559274,592741,592735,592730,298334,0,560058,24813,24824,71596,592022,591995,1838283,1842383,1842495,1842550,1842664,1851420,1819420,1819423,1819452,1819455,1819500,1819412,1819390,1819444,1819313,1819475,924544,0,0,89755,89185,89730,89725,89750,89740,89745,89735,0,1942651,0,304732,29711,79408,79427,0,79803,0,920855,921647,920796,920823,920839,922259,920724,919938,29126,0,317577,317917,317912,0,1073943,317940,923196,12768,150408,80458,79669,79651,79692,79674,79715,79697,80489,29749,91984,91900,92508,92601,92616,92621,92522,92606,92611,95310,95341,95443,95433,95448,95438,95453,90668,0,0,568937,0,1950436,0,324090,923158,29095,1383442,0,79379,29471,29827,29809,29818,89690,89720,89700,89695,89715,89710,89705,568796,924208,922612,924976,95375,95380,0,1031319,1031341,569048,304773,0,181589,181732,181762,181752,181737,181757,181747,181742,181727,0,0,0,29739,568816,925216,0,89790,89770,89765,89760,89785,89780,89775,93684,93765,93750,93760,93745,0,919636,0,323792,92368,317922,923818,923286,923323,602939,602957,602975,0,29590,88830,88847,324325,324256,324286,324281,324261,324276,324251,324266,324271,80352,0,603233,603829,604437,602993,602457,602184,602170,29584,29578,150608,0,150621,150615,76035,1157816,1157710,1157748,1157854,0,0,171631,324116,1069249,256932,0,923360,923980,924044,569021,6834,58284,0,29744,1855399,1855487,1855377,1855419,1855430,1855355,1855448,1855479,550666,1823134,1823107,1854563,1854676,1854651,1854588,1854636,1854610,1854583,550187,29711,178723,1093143,0,1955881,1957679,551146,321750,560855,299449,-1,1955718,0,157167,0,0,0,760125,760205,760221,761237,761037,761301,761397,760941,760189,760181,761125,760469,760661,760277,760421,758535,760373,761005,760517,760229,760877,760325,760973,760141,760117,761365,761101,760149,761437,760845,760133,760173,1959834,760909,760197,761173,760157,68358,68328,68323,63217,60845,62772,62778,182482,182477,68353,63210,62783,62789,271486,182487,62452,60852,271491,271501,271804,0,76960,62798,63244,77012,61003,76997,62813,76388,63915,761487,62475,78690,66299,66262,0,0,0,66128,77334,77324,77319,77329,77344,77339,77297,0,150752,77605,63240,62794,63261,63188,63193,0,0,0,0,0,0,77007,76982,76992,76987,76977,77002,63178,61113,68348,62479,62498,63224,329751,63203,75508,61072,61008,271496,78224,62471,62802,182492,68333,60898,68343,90672,91868,91409,91404,91414,91424,91419,91429,91765,91785,91795,91760,91820,91745,91750,91755,91800,91790,91780,91805,91770,91810,91775,91815,91843,91851,91825,91691,91835,75056,75036,70628,79495,74497,934815,934811,934875,934868,0,0,935041,935049,935025,935017,935033,934939,934946,934723,934725,934727,159472,51586,53624,53641,79151,52907,51707,300206,79395,53660,51581,50956,621960,51167,51096,51249,158293,159726,159712,159631,159542,158611,52716,79268,79193,52213,52228,52218,51023,599199,754948,53307,51826,51866,597109,50901,53067,52854,12291,300285,172713,75170,75203,75190,598038,176422,176765,598399,178176,755092,754885,300507,300636,159457,9936,0,15536,0,300127,159467,15945Line truncated
de: [529315,528124,530410,529380,26272,60873,256874,256880,256862,528854,528828,1031749,529355,60819,60814,63475,63495,60809,63480,60863,60824,224296,224291,224286,224281,224271,224276,224220,258800,527293,527442,527321,527187,526817,527238,526864,527385,528085,527528,527593,527080,527708,527572,526610,526638,526664,526578,527765,526493,526550,527744,527680,526733,526715,526790,526762,526687,527340,527632,527269,527100,527405,527461,526519,528162,528146,527507,526259,531166,528068,528552,529280,531046,61753,530972,529589,258349,530813,59169,530224,530516,321001,328922,528313,530002,258908,528040,160233,835780,531101,530853,528880,528953,529458,529218,26267,530251,530282,62599,528511,527948,835794,258777,528930,530994,530349,529426,530077,528418,529635,4713,61791,61787,529956,258354,527841,529303,530835,528254,529557,529568,527898,531147,527877,61687,530390,529716,258784,258770,258899,529107,526845,62586,530311,29250,526401,528741,529784,61308,62334,60568,60496,253509,526168,528391,526338,526423,526317,527995,530725,531218,528621,528644,528452,528475,527978,13530,530241,26594,63455,59200,63450,63500,63470,26252,526366,530159,528537,529754,1031277,227765,530908,226389,529917,530457,529661,26257,26299,160255,1072761,1072674,1072696,527918,60868,528685,529023,530205,258894,68330,528967,63240,530370,526462,529609,62558,279404,0,531181,227592,329070,330663,529693,530431,530644,530668,530594,529083,529736,527482,530181,528812,530928,528784,61781,62862,63225,61560,61591,530875,530788,526441,526998,526958,526905,529128,530941,529176,530705,529253,530745,530539,222439,61504,61339,530489,26262,528353,529884,529848,529406,531021,528991,26277,26294,60829,258332,258889,529814,529161,529061,530121,530039,528600,528911,528371,328935,528189,1072643,1072650,1072568,1072665,60858,526240,529447,530574,529976,529476,529335,529936,1031653,1031640,528768,328946,528232,526221,526149,258811,63881,527858,529528,528579,529498,528276,62545,62612,62675,258791,95161,95253,1839014,1839055,1839023,1839025,75000,559270,592770,592760,592754,298334,557804,560054,24820,24831,71573,592033,592004,1838737,1843476,1843599,1843659,1843788,1851415,1819501,1819504,1819531,1819534,1819589,1819494,1819476,1819524,1819381,1819559,924163,150691,0,89750,89180,89725,89720,89745,89735,89740,89730,0,1943064,569910,304739,29721,79351,79370,0,79746,0,920856,921258,920797,920824,920840,921870,920725,919939,29133,0,317577,317917,317912,0,1073934,317940,922807,12765,150408,80401,79612,79594,79635,79617,79658,79640,80432,29759,91980,91896,92504,92597,92612,92617,92518,92602,92607,95303,95334,95436,95426,95441,95431,95446,90663,0,0,568933,569898,1950431,0,324092,922769,29102,569710,569794,79322,29478,29837,29819,29828,89685,89715,89695,89690,89710,89705,89700,568792,923827,922223,924595,95368,95373,0,1031319,1031341,569043,304780,0,181578,181721,181751,181741,181726,181746,181736,181731,181716,0,0,0,29749,568812,924835,569925,89785,89765,89760,89755,89780,89775,89770,93677,93758,93743,93753,93738,0,919637,0,323794,92364,317922,923429,922897,922934,602947,602965,602983,0,29600,88825,88842,324334,324265,324295,324290,324270,324285,324260,324275,324280,80295,0,603241,603837,604445,603001,602465,602192,602178,29594,29585,150612,150604,150625,150619,76014,1158014,1157891,1157954,1158128,569930,569935,171643,324122,1069240,256930,0,922971,923591,923655,569017,6843,58328,569940,29754,1857983,1858066,1857959,1858001,1858017,1857936,1858031,1858060,550657,1824151,1824128,1855878,1856008,1855977,1855904,1855963,1855928,1855896,550187,29721,178735,1093149,0,1955832,1957630,551137,321742,560851,299450,-1,1955677,0,157172,0,0,0,760137,760217,760233,761249,761049,761313,761409,760953,760201,760193,761137,760481,760673,760289,760433,758547,760385,761017,760529,760241,760889,760337,760985,760153,760129,761377,761113,760161,761449,760857,760145,760185,1959803,760921,760209,761185,760169,68350,68320,68315,63259,60892,62814,62820,182471,182466,68345,63252,62825,62831,271459,182476,62494,60899,271464,271474,271777,66693,76939,62840,63286,76985,61050,76970,62855,76367,63957,761499,62517,78661,66291,66254,66203,66225,66168,66126,77304,77294,77289,77299,77314,77309,77270,150706,150754,77575,63282,62836,63303,63230,63235,65858,328904,328349,328352,0,328228,76980,76955,76965,76960,76950,76975,63220,61155,68340,62521,62540,63266,329748,63245,75481,61114,61055,271469,78194,62513,62844,182481,68325,60945,68335,90667,91865,91404,91399,91409,91419,91414,91424,91760,91780,91790,91755,91815,91740,91745,91750,91795,91785,91775,91800,91765,91805,91770,91810,91840,91850,91820,91686,91832,75029,75009,70620,79438,74470,934786,934782,934847,934839,935147,935151,935026,935038,935002,934990,935014,934912,934919,934694,934696,934698,159472,0,0,0,79122,0,0,300207,79338,0,0,0,621960,0,0,0,158298,159726,159712,159631,159542,158616,0,0,79164,0,0,0,0,599207,754948,0,0,0,597116,0,0,0,12288,300286,172725,75143,75176,75163,598046,176434,176777,598407,178188,755092,754885,300508,300637,159457,9945,0,15561,0,30Line truncated
it: [529031,528005,530046,529086,26264,60871,256874,256880,256862,528623,528610,1031749,529065,60817,60812,63482,63502,60807,63487,60861,60822,224301,224296,224291,224286,224276,224281,224225,258800,527250,527386,527276,527149,526841,527196,526882,527342,527972,527475,527533,527053,527625,527512,526601,526640,526676,526576,527686,526494,526546,527654,527598,526758,526729,526811,526774,526697,527292,527565,527221,527069,527362,527402,526520,528045,528023,527452,526263,530732,527952,528369,528995,530629,61760,530548,529304,258349,530399,59167,529874,530145,320987,328935,528165,529672,258908,527930,160214,835781,530675,530436,528636,528704,529167,528930,26259,529899,529927,62606,528324,527843,835795,258777,528682,530569,529973,529129,529730,528254,529346,4709,61798,61794,529628,258354,527764,529010,530419,528115,529262,529283,527809,530712,527793,61694,530028,529414,258784,258770,258899,528821,526864,62593,529955,29242,526394,528514,529471,61315,62341,60566,60494,0,526175,528232,526332,526411,526304,527895,530314,530780,528429,528451,528266,528288,527881,13534,529888,26586,63462,59198,63457,63507,63477,26244,526361,529798,528347,529449,1031277,227770,530487,226394,529586,530092,529369,26249,26291,160236,1072764,1072677,1072699,527822,60866,528482,528756,529851,258894,68338,528719,63247,529993,526462,529323,62565,279404,526197,530751,227597,329090,330690,529391,530068,530252,530268,530213,528801,529429,527426,529829,528594,530506,528559,61788,62869,63232,61567,61598,530456,530369,526437,526994,526976,526930,528843,530525,528897,530293,528969,530331,530166,222444,61511,61346,530120,26254,528192,529563,529526,529107,530596,528738,26269,26286,60827,258332,258889,529491,528878,528779,529772,529698,528410,528667,528213,328951,528068,1072646,1072653,1072571,1072668,60856,526245,529152,530196,529649,529184,529048,529608,1031653,1031640,528542,328970,528096,526220,0,258811,63888,527774,529232,528390,529200,528134,62552,62619,62682,258791,95173,95265,1838500,1838534,1838507,1838509,75102,559278,592757,592752,592747,298334,557812,560062,24812,24823,71594,592034,592003,1838224,1842422,1842533,1842593,1842696,1851423,1819225,1819228,1819252,1819255,1819310,1819217,1819205,1819244,1819128,1819278,924146,0,0,89760,89190,89735,89730,89755,89745,89750,89740,0,1941607,569911,304739,29712,79478,79497,49995,79873,50049,920855,921257,920796,920823,920839,921869,920724,919938,29125,0,317573,317913,317908,50229,1073937,317936,922806,12769,150407,80528,79739,79721,79762,79744,79785,79767,80559,29750,91983,91899,92507,92600,92615,92620,92521,92605,92610,95315,95346,95448,95438,95453,95443,95458,90670,150307,150348,568941,569899,1950432,50297,323898,922768,29094,569711,569795,79449,29470,29828,29810,29819,89695,89725,89705,89700,89720,89715,89710,568800,923810,922222,924578,95380,95385,0,1031319,1031341,569048,304780,150401,181597,181740,181770,181760,181745,181765,181755,181750,181735,49850,0,0,29740,568820,924818,569926,89795,89775,89770,89765,89790,89785,89780,93689,93770,93755,93765,93750,150246,919636,50291,323600,92367,317918,923428,922896,922933,602935,602953,602971,50211,29591,88835,88852,324130,324061,324091,324086,324066,324081,324056,324071,324076,80422,50036,603229,603825,604433,602989,602453,602180,602166,29584,29577,150609,150602,150623,150615,76121,1157844,1157730,1157786,1157949,569931,569936,171613,323923,1069243,256930,50017,922970,923590,923654,569025,6837,58334,569941,29745,1855673,1855769,1855657,1855692,1855710,1855637,1855732,1855764,550666,1823011,1822993,1854702,1854824,1854797,1854730,1854786,1854759,1854724,550187,29712,178705,1093151,50336,1955866,1957664,551144,321727,560859,299452,-1,1955701,50349,157157,50238,50280,50362,760128,760208,760224,761240,761040,761304,761400,760944,760192,760184,761128,760472,760664,760280,760424,758538,760376,761008,760520,760232,760880,760328,760976,760144,760120,761368,761104,760152,761440,760848,760136,760176,1959808,760912,760200,761176,760160,68358,68328,68323,63266,60890,62821,62827,182490,182485,68353,63259,62832,62838,271474,182495,62501,60897,271479,271489,271792,66700,77046,62847,63293,77094,61048,77079,62862,76474,63964,761490,62524,78773,66297,66260,66208,66231,66171,66128,77414,77404,77399,77409,77424,77419,77379,0,150747,77685,63289,62843,63310,63237,63242,0,328917,0,328362,328223,0,77089,77064,77074,77069,77059,77084,63227,61162,68348,62528,62547,63273,329768,63252,75583,61121,61053,271484,78304,62520,62851,182500,68333,60943,68343,90674,91869,91411,91406,91416,91426,91421,91431,91767,91787,91797,91762,91822,91747,91752,91757,91802,91792,91782,91807,91772,91812,91777,91817,91843,91852,91827,91693,91836,75131,75111,70628,79565,74572,934806,934803,934865,934858,935149,935152,935034,935043,935016,935007,935025,934929,934936,934715,934717,934719,159453,51636,53674,53691,79234,52957,51757,300209,79465,53710,51631,51006,621958,51217,51146,51299,158281,159707,159693,159612,159523,158599,52766,79351,79276,52263,52278,52268,51073,599195,754948,53357,51876,51916,597104Line truncated
es: [529173,528047,530289,529245,26270,60823,256874,256880,256862,528742,528721,1031749,529214,60769,60764,63433,63453,60759,63438,60813,60774,224300,224295,224290,224285,224275,224280,224224,258800,527227,527378,527258,527134,526820,527189,526866,527326,528016,527470,527526,527022,527628,527503,526611,526650,526688,526576,527694,526493,526548,527668,527594,526754,526723,526790,526771,526706,527276,527557,527207,527043,527353,527396,526519,528090,528068,527447,526260,531030,527998,528434,529131,530932,61711,530855,529471,258349,530678,59119,530110,530399,320992,328934,528200,529901,258908,527977,160236,0,530970,530722,528763,528825,529329,529065,26265,530133,530150,62557,528385,527882,0,258777,528808,530872,530198,529289,529968,528298,529513,4710,61749,61745,529848,258354,527790,529158,530701,528158,529426,529448,527845,531008,527825,61645,530257,529604,258784,258770,258899,528953,526848,62544,530167,29248,0,528606,529670,61266,62292,60518,60446,0,526169,528274,0,526395,526312,527933,530582,531081,528510,528531,528320,528345,527915,13530,530124,26592,63413,59150,63408,63458,63428,26250,526341,530032,528416,529647,1031277,227769,530789,226393,529805,530343,529535,26255,26297,160258,1072761,1072674,1072696,527861,60818,528570,528883,530085,258894,68358,528845,63198,530218,526449,529491,62516,279404,526198,531049,227596,0,330685,529570,530313,530510,530531,530477,528921,529627,527417,530063,528698,530808,528656,61739,62820,63183,61518,61549,530749,530643,526421,526964,526940,526903,528989,530832,529039,530556,529098,530602,530421,222443,61462,61297,530372,26260,528225,529779,529734,529266,530907,528864,26275,26292,60779,258332,258889,529691,529027,528904,530004,529933,528484,528791,528254,328949,528117,1072643,1072650,1072568,1072665,60808,526242,529315,530455,529871,529351,529194,529829,1031653,1031640,528635,328966,528140,526222,526149,258811,63839,527808,529398,528460,529370,528176,62503,62570,62633,258791,95154,95246,1838736,1838776,1838745,1838747,75137,559287,592778,592774,592769,298334,557813,560071,24818,24829,71616,592043,592006,1838451,1842873,1842981,1843045,1843153,1851424,1819501,1819443,1819501,1819474,1819536,1819432,1819417,1819462,1819339,1819501,924543,150689,1510290,89752,89182,89727,89722,89747,89737,89742,89732,1509001,1942287,569921,304728,29717,79504,79523,49993,79899,50047,920856,921648,920797,920824,920840,922260,920725,919939,29131,1510357,317573,317913,317908,50227,1073934,317936,923197,12765,150407,80554,79765,79747,79788,79770,79811,79793,80585,29755,91975,91891,92499,92592,92607,92612,92513,92597,92602,95296,95327,95429,95419,95434,95424,95439,90670,150308,150349,568950,569909,1950435,50295,324089,923159,29100,569721,569805,79475,29476,29833,29815,29824,89687,89717,89697,89692,89712,89707,89702,568809,924207,922613,924975,95361,95366,1510214,1031319,1031341,569057,304769,150402,181585,181728,181758,181748,181733,181753,181743,181738,181723,49848,2078033,2078061,29745,568829,925215,569936,89787,89767,89762,89757,89782,89777,89772,93670,93751,93736,93746,93731,150247,919637,50289,323790,92359,317918,923819,923287,923324,602957,602975,602993,50209,29596,88827,88844,324321,324252,324282,324277,324257,324272,324247,324262,324267,80448,50034,603251,603823,604431,603011,602475,602202,602188,29589,29583,150609,0,150623,150615,76150,1158057,1157941,1157989,1158177,569941,569946,171629,324112,1069240,256930,50015,923361,923981,924045,569034,6833,58284,569951,29750,1857778,1857861,1857762,1857796,1857813,1857738,1857832,1857855,550664,1823861,1823842,1855709,1855852,1855817,1855739,1855798,1855768,1855727,550187,29717,178721,1093151,50334,1955868,1957666,551147,321732,560868,299448,-1,1955709,50347,0,50236,50278,50360,760135,760215,760231,761247,761047,761311,761407,760951,760199,760191,761135,760479,760671,760287,760431,758545,760383,761015,760527,760239,760887,760335,760983,760151,760127,761375,761111,760159,761447,760855,760143,760183,1959824,760919,760207,761183,760167,68378,68348,68343,63217,60842,62772,62778,182478,182473,68373,63210,62783,62789,271494,182483,62452,60849,271499,271509,271812,66717,77075,62798,63244,77123,61000,77108,62813,76503,63915,761497,62475,78811,66313,66274,66217,66242,66178,66132,77443,77433,77428,77438,77453,77448,77408,150704,150752,77714,63240,62794,63261,63188,63193,65861,328916,0,328361,328224,0,77118,77093,77103,77098,77088,77113,63178,61113,68368,62479,62498,63224,0,63203,75618,61072,61005,271504,78333,62471,62802,182488,68353,60895,68363,90674,91863,91411,91406,91416,91426,91421,91431,91767,91787,91797,91762,91822,91747,91752,91757,91802,91792,91782,91807,91772,91812,91777,91817,91842,91848,91827,91693,91836,75166,75146,70648,79591,74607,934808,934805,934869,934860,935167,935170,935048,935059,935026,935015,935037,934935,934943,934717,934719,0,159475,51585,53623,53640,79272,52906,51706,300205,79491,53659,51580,50955,621952,51166,51095,51248,158293,159729,159715,159634,159545,158611,52715,79391,79314,52212,52227,52217,51022,599217,754948,53306,51825,51Line truncated
} as const;
+64 -10
View File
@@ -1,17 +1,71 @@
import type { RomReader } from "./reader";
import * as PROFILE from "./profile";
import { useCharacters } from "./charset";
import { REVISION_ADDRESSES, REVISION_KEYS } from "./revision-addresses";
/** The cartridges the port reads, as PRET builds them (`make crystal`,
* `crystal11` and `crystal_au`): the English US/Europe revisions 1.0 and
* 1.1, and the Australian release, which is 1.1 with the Game Corners'
* gambling texts rewritten. All three keep every label the port reads at the
* same address; only the routines `romRevision` callers check differ. */
export type Revision = "1.0" | "1.1" | "au";
/** The cartridges the port reads: the English US/Europe revisions 1.0 and
* 1.1 and the Australian release (PRET's `crystal`, `crystal11` and
* `crystal_au`, which keep every label the port reads at the same address),
* and the French, German, Italian and Spanish releases, whose addresses
* scripts/map-revision.mjs derives from 1.1 (revision-addresses.ts). */
export type Revision = "1.0" | "1.1" | "au" | Language;
export type Language = keyof typeof REVISION_ADDRESSES;
const CODES: Record<string, Revision> = { BYTU: "au", BYTF: "fr", BYTD: "de", BYTI: "it", BYTS: "es" };
/** The revision from the cartridge header (rgbfix's -i and -n): game code
* BYTU for the Australian release, else BYTE with mask ROM version 0 or 1. */
* BYTE with mask ROM version 0 or 1, or a release's own code. */
export function romRevision(r: RomReader): Revision {
const code = String.fromCharCode(r.byte(0x13f), r.byte(0x140), r.byte(0x141), r.byte(0x142));
if (code === "BYTU") return "au";
if (code !== "BYTE") throw new Error("Unknown cartridge game code.");
return r.byte(0x14c) === 0 ? "1.0" : "1.1";
if (code === "BYTE") return r.byte(0x14c) === 0 ? "1.0" : "1.1";
const revision = CODES[code];
if (!revision) throw new Error("Unknown cartridge game code.");
return revision;
}
/** romRevision, or 1.1 for a ROM without a known game code. */
export function knownRevision(r: RomReader): Revision {
try {
return romRevision(r);
} catch {
return "1.1";
}
}
type Table = Record<string, number>;
const tables = PROFILE as unknown as Record<string, Table>;
/** A release's profile entries that differ from 1.1: table -> key -> address
* (-1 where the label was not found there). */
export function revisionAddresses(language: Language): Record<string, Table> {
const out: Record<string, Table> = {};
REVISION_ADDRESSES[language].forEach((address, i) => {
if (!address) return;
const entry = REVISION_KEYS[i],
dot = entry.indexOf(".");
(out[entry.slice(0, dot)] ??= {})[entry.slice(dot + 1)] = address;
});
return out;
}
/** 1.1's values of every entry a release overrides. */
const english = new Map<string, Table>();
for (const entry of REVISION_KEYS) {
const dot = entry.indexOf("."),
table = entry.slice(0, dot),
key = entry.slice(dot + 1);
const saved = english.get(table) ?? {};
saved[key] = tables[table][key];
english.set(table, saved);
}
let current: Revision = "1.1";
/** Point the profile at a cartridge's addresses and characters. The port
* reads one cartridge at a time: CrystalAssets calls this for its ROM. */
export function useRevision(revision: Revision) {
if (revision === current) return;
for (const [table, saved] of english) Object.assign(tables[table], saved);
const language = revision in REVISION_ADDRESSES ? (revision as Language) : undefined;
if (language)
for (const [table, entries] of Object.entries(revisionAddresses(language))) Object.assign(tables[table], entries);
useCharacters(language);
current = revision;
}
+9 -7
View File
@@ -520,7 +520,9 @@ interface MartTexts {
thanks: number;
soldOut?: number;
}
const MART_TEXTS = {
/** The marts' texts, read from the profile when used: its addresses follow
* the cartridge (game/rom/revision.ts). */
const martTexts = () => ({
standard: {
howMany: SYMBOLS.martHowManyText,
finalPrice: SYMBOLS.martFinalPriceText,
@@ -549,7 +551,7 @@ const MART_TEXTS = {
packFull: JOHTO.PharmacyPackFullText,
thanks: JOHTO.PharmacyThanksText,
},
} satisfies Record<string, MartTexts>;
}) satisfies Record<string, MartTexts>;
/** One row of a mart's buy or sell list. `owned` is set only when selling. */
/** The mart's lists and quantity box, which the PC's item storage reuses. */
@@ -2708,12 +2710,12 @@ export class ScriptRun {
break;
case MARTTYPE_BITTER:
yield* textbox(JOHTO.HerbShopLadyIntroText);
yield* this.martBuy(martStock(), pages, MART_TEXTS.herb);
yield* this.martBuy(martStock(), pages, martTexts().herb);
yield* textbox(JOHTO.HerbalLadyComeAgainText);
return;
case MARTTYPE_PHARMACY:
yield* textbox(JOHTO.PharmacyIntroText);
yield* this.martBuy(martStock(), pages, MART_TEXTS.pharmacy);
yield* this.martBuy(martStock(), pages, martTexts().pharmacy);
yield* textbox(JOHTO.PharmacyComeAgainText);
return;
case MARTTYPE_BARGAIN: {
@@ -2721,7 +2723,7 @@ export class ScriptRun {
const bought = yield* this.martBuy(
pricedStock(JOHTO.BargainShopData),
pages,
MART_TEXTS.bargain,
martTexts().bargain,
);
// wBargainShopFlags: any purchase closes the shop for the day.
if (bought) this.state.flags.add(OW.ENGINE_GOLDENROD_UNDERGROUND_MERCHANT_CLOSED);
@@ -2736,7 +2738,7 @@ export class ScriptRun {
),
pages,
// The rooftop sale shares the standard mart's texts.
MART_TEXTS.standard,
martTexts().standard,
);
yield* textbox(SYMBOLS.martComeAgainText);
return;
@@ -2754,7 +2756,7 @@ export class ScriptRun {
};
first = false;
const option = typeof choice === "number" ? choice : 2;
if (option === 0) yield* this.martBuy(martStock(), pages, MART_TEXTS.standard);
if (option === 0) yield* this.martBuy(martStock(), pages, martTexts().standard);
else if (option === 1) yield* this.martSell(pages);
else {
write(SYMBOLS.martComeAgainText);
+4
View File
@@ -1,5 +1,6 @@
import { RomReader } from "../rom/reader";
import { RAM } from "../rom/profile";
import { releaseCharacter } from "../rom/charset";
const CHARACTERS: Record<number, string> = {
0x1f: " ",
@@ -138,6 +139,9 @@ function battler(context: TextContext, enemy: boolean): string | undefined {
}
function character(code: number, context: TextContext): string | undefined {
// The European releases' own characters (charset.ts).
const release = releaseCharacter(code);
if (release !== undefined) return release ?? undefined;
// PlaceMoveTargetsName / PlaceMoveUsersName
if (code === 0x59) return battler(context, context.battle?.turn === 0);
if (code === 0x5a) return battler(context, context.battle?.turn === 1);
+3 -7
View File
@@ -26,12 +26,8 @@ const PUZZLE_BORDER = 0xee,
const PLACES = 6 * 6,
PIECES = 16;
/** UNOWNPUZZLE_* (constants/script_constants.asm), in .LZPointers order. */
const PUZZLE_GRAPHICS = [
JOHTO.KabutoPuzzleLZ,
JOHTO.OmanytePuzzleLZ,
JOHTO.AerodactylPuzzleLZ,
JOHTO.HoOhPuzzleLZ,
] as const;
const puzzleGraphics = () =>
[JOHTO.KabutoPuzzleLZ, JOHTO.OmanytePuzzleLZ, JOHTO.AerodactylPuzzleLZ, JOHTO.HoOhPuzzleLZ] as const;
/** .EnlargedTiles: each bit of a nybble doubled. */
const enlarge = (nybble: number) =>
(nybble & 8) * 0x18 + (nybble & 4) * 0x0c + (nybble & 2) * 0x06 + (nybble & 1) * 0x03;
@@ -42,7 +38,7 @@ const enlarge = (nybble: number) =>
* then the cursor and START/CANCEL graphics. */
export function unownPuzzleTiles(reader: RomReader, puzzle: number) {
const tiles = new Uint8Array(0x100 * 16);
const picture = decompress(reader, PUZZLE_GRAPHICS[puzzle & 3], 0x1000);
const picture = decompress(reader, puzzleGraphics()[puzzle & 3], 0x1000);
let out = 0;
for (let row = 0; row < 6; row++)
for (let half = 0; half < 2; half++)
+15
View File
@@ -21,6 +21,16 @@ export const CRYSTAL_ROM = {
size: 2 * 1024 * 1024,
} as const;
/** The French, German, Italian and Spanish releases: their addresses and
* characters are known (game/rom/revision.ts), but parts of their own
* logic (units, word order, layouts) are not ported yet (board #84). */
export const RELEASES_IN_PROGRESS = [
{ name: "Pokémon Crystal · French", sha1: "c055992b16b7399c687647725cdd1f4f13a2f75c" },
{ name: "Pokémon Crystal · German", sha1: "accb584293ba056152f1fd908439b019017ff2fe" },
{ name: "Pokémon Crystal · Italian", sha1: "6cee05e5b95beeae74b8365ad18ec4a07a8c4af8" },
{ name: "Pokémon Crystal · Spanish", sha1: "889a06fc0bb863666865aa69def0adf97945ac2a" },
] as const;
/** The supported cartridge with this SHA-1, if any. */
export const supportedRom = (sha1: unknown) =>
CRYSTAL_ROMS.find((rom) => rom.sha1 === sha1);
@@ -48,6 +58,11 @@ export async function validateRom(file: File): Promise<ValidatedRom> {
byte.toString(16).padStart(2, "0"),
).join("");
const rom = supportedRom(sha1);
const later = RELEASES_IN_PROGRESS.find((release) => release.sha1 === sha1);
if (later)
throw new Error(
`This is the ${later.name.replace("Pokémon Crystal · ", "")} release of Pokémon Crystal. It is recognized, but parts of its own logic are not ported yet; for now, use an English cartridge.`,
);
if (!rom) {
throw new Error(
"This ROM is not a supported English Pokémon Crystal: US/Europe revision 1.0 or 1.1, or the Australian release. Other languages and modified ROMs are not supported yet.",