Dump flavor text

This commit is contained in:
Eevee (Lexy Munroe) 2016-09-10 21:23:57 -07:00
parent 50344e6794
commit dd3d3a3f25
2 changed files with 227 additions and 494 deletions

View file

@ -24,7 +24,7 @@ from construct import *
from pokedex.extract.lib.gbz80 import find_code
import pokedex.schema as schema
# TODO set this up to colorcode and use {} formatting
# TODO set this up to colorcode, probably put that... elsewhere
log = logging.getLogger(__name__)
# Known official games, the languages they were released in, and hashes of
@ -495,427 +495,74 @@ def bank(addr):
return bank, addr
EN_TEXT_MAP = {
# Sort of faux movement macros
0x00: "", # "Start text"?
0x4E: "\n", # Move to next line
0x49: "\f", # Start a new Pokédex page
0x5F: ".", # End of Pokédex entry, adds a period
JA_CHARMAP = dict(enumerate([
# 0x
# 00 appears at the beginning of a lot of Pokédex entries?
"", "イ゛", "", "エ゛", "オ゛", "", "", "",
"", "", "", "", "", "", "", "",
# 1x
"", "", "", "", "ナ゛", "ニ゛", "ヌ゛", "ネ゛",
"ノ゛", "", "", "", "", "マ゛", "ミ゛", "ム゛",
# 2x
"ィ゛", "あ゛", "い゛", "", "え゛", "お゛", "", "",
"", "", "", "", "", "", "", "",
# 3x
"", "", "", "", "", "な゛", "に゛", "ぬ゛",
"ね゛", "の゛", "", "", "", "", "", "ま゛",
# 4x
# 4F is the Pokédex newline
# 4E is the dialogue newline (puts the cursor on the bottom line)
"", "", "", "", "", "", "", "",
"", "\f", "が ", "も゜", "<EFBFBD>", "<EFBFBD>", "\n", "\n",
# 5x
# 50 is the string terminator, represented by @ in pokered
# 51 prompts for a button press, then clears the screen
# 52 is the player's name
# 53 is the rival's name
# 55 prompts for a button press, then scrolls up one line
# 57 ends dialogue, invisibly
# 58 ends dialogue, visibly
# 59 is the inactive Pokémon in battle
# 5A is the active Pokémon in battle
# 5F marks the end of a Pokédex entry, which automaticaly adds a period
"@", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "ポケモン", "<EFBFBD>", "……", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "パソコン", "わざマシン", "トレーナー", "ロケットだん", "",
# 6x
"A", "B", "C", "D", "E", "F", "G", "H",
"I", "V", "S", "L", "M", "", "", "",
# 7x
# 79 - 7E are the TL, T/B, TR, L/R, BL, and BR of the text box
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", " ",
# 8x
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
# 9x
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
# Ax
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
# Bx
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
# Cx
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
# Dx
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", "",
# Ex
"", "", "", "", "", "", " ?", " !",
"", "", "", "", "", "", "", "",
# Fx
"", "×", ".", "/", "", "", "0", "1",
"2", "3", "4", "5", "6", "7", "8", "9",
]))
0x05: "",
0x06: "",
0x07: "",
0x08: "",
0x09: "",
0x0A: "",
0x0B: "",
0x0C: "",
0x0D: "",
0x0E: "",
0x0F: "",
0x10: "",
0x11: "",
0x12: "",
0x13: "",
0x19: "",
0x1A: "",
0x1B: "",
0x1C: "",
0x26: "",
0x27: "",
0x28: "",
0x29: "",
0x2A: "",
0x2B: "",
0x2C: "",
0x2D: "",
0x2E: "",
0x2F: "",
0x30: "",
0x31: "",
0x32: "",
0x33: "",
0x34: "",
0x3A: "",
0x3B: "",
0x3C: "",
0x3D: "",
0x3E: "",
0x40: "",
0x41: "",
0x42: "",
0x43: "",
0x44: "",
0x45: "",
0x46: "",
0x47: "",
0x48: "",
0x80: "",
0x81: "",
0x82: "",
0x83: "",
0x84: "",
0x85: "",
0x86: "",
0x87: "",
0x88: "",
0x89: "",
0x8A: "",
0x8B: "",
0x8C: "",
0x8D: "",
0x8E: "",
0x8F: "",
0x90: "",
0x91: "",
0x92: "",
0x93: "",
0x94: "",
0x95: "",
0x96: "",
0x97: "",
0x98: "",
0x99: "",
0x9A: "",
0x9B: "",
0x9C: "",
0x9D: "",
0x9E: "",
0x9F: "",
0xA0: "",
0xA1: "",
0xA2: "",
0xA3: "",
0xA4: "",
0xA5: "",
0xA6: "",
0xA7: "",
0xA8: "",
0xA9: "",
0xAA: "",
0xAB: "",
0xAC: "",
0xAD: "",
0xAE: "",
0xAF: "",
0xB0: "",
0xB1: "",
0xB2: "",
0xB3: "",
0xB4: "",
0xB5: "",
0xB6: "",
0xB7: "",
0xB8: "",
0xB9: "",
0xBA: "",
0xBB: "",
0xBC: "",
0xBD: "",
0xBE: "",
0xBF: "",
0xC0: "",
0xC1: "",
0xC2: "",
0xC3: "",
0xC4: "",
0xC5: "",
0xC6: "",
0xC7: "",
0xC8: "",
0xC9: "",
0xCA: "",
0xCB: "",
0xCC: "",
0xCD: "",
0xCE: "",
0xCF: "",
0xD0: "",
0xD1: "",
0xD2: "",
0xD3: "",
0xD4: "",
0xD5: "",
0xD6: "",
0xD7: "",
0xD8: "",
0xD9: "",
0xDA: "",
0xDB: "",
0xDC: "",
0xDD: "",
0xDE: "",
0xDF: "",
0xE0: "",
0xE1: "",
0xE2: "",
0xE3: "",
0x50: "@",
0x54: "#",
0x54: "POKé",
0x75: "",
0x79: "",
0x7A: "",
0x7B: "",
0x7C: "",
0x7D: "",
0x7E: "",
0x74: "",
0x7F: " ",
0x80: "A",
0x81: "B",
0x82: "C",
0x83: "D",
0x84: "E",
0x85: "F",
0x86: "G",
0x87: "H",
0x88: "I",
0x89: "J",
0x8A: "K",
0x8B: "L",
0x8C: "M",
0x8D: "N",
0x8E: "O",
0x8F: "P",
0x90: "Q",
0x91: "R",
0x92: "S",
0x93: "T",
0x94: "U",
0x95: "V",
0x96: "W",
0x97: "X",
0x98: "Y",
0x99: "Z",
0x9A: "(",
0x9B: ")",
0x9C: ":",
0x9D: ";",
0x9E: "[",
0x9F: "]",
0xA0: "a",
0xA1: "b",
0xA2: "c",
0xA3: "d",
0xA4: "e",
0xA5: "f",
0xA6: "g",
0xA7: "h",
0xA8: "i",
0xA9: "j",
0xAA: "k",
0xAB: "l",
0xAC: "m",
0xAD: "n",
0xAE: "o",
0xAF: "p",
0xB0: "q",
0xB1: "r",
0xB2: "s",
0xB3: "t",
0xB4: "u",
0xB5: "v",
0xB6: "w",
0xB7: "x",
0xB8: "y",
0xB9: "z",
0xBA: "é",
0xBB: "'d",
0xBC: "'l",
0xBD: "'s",
0xBE: "'t",
0xBF: "'v",
0xE0: "'",
0xE3: "-",
0xE4: "'r",
0xE5: "'m",
0xE6: "?",
0xE7: "!",
0xE8: ".",
0xED: "",
0xEF: "",
0xF0: "¥",
0xF1: "×",
0xF3: "/",
0xF4: ",",
0xF5: "",
0xF6: "0",
0xF7: "1",
0xF8: "2",
0xF9: "3",
0xFA: "4",
0xFB: "5",
0xFC: "6",
0xFD: "7",
0xFE: "8",
0xFF: "9",
}
JA_CHARMAP = {
**EN_TEXT_MAP,
0x05: "",
0x06: "",
0x07: "",
0x08: "",
0x09: "",
0x0A: "",
0x0B: "",
0x0C: "",
0x0D: "",
0x0E: "",
0x0F: "",
0x10: "",
0x11: "",
0x12: "",
0x13: "",
0x19: "",
0x1A: "",
0x1B: "",
0x1C: "",
0x26: "",
0x27: "",
0x28: "",
0x29: "",
0x2A: "",
0x2B: "",
0x2C: "",
0x2D: "",
0x2E: "",
0x2F: "",
0x30: "",
0x31: "",
0x32: "",
0x33: "",
0x34: "",
0x3A: "",
0x3B: "",
0x3C: "",
0x3D: "",
0x3E: "",
0x40: "",
0x41: "",
0x42: "",
0x43: "",
0x44: "",
0x45: "",
0x46: "",
0x47: "",
0x48: "",
0x80: "",
0x81: "",
0x82: "",
0x83: "",
0x84: "",
0x85: "",
0x86: "",
0x87: "",
0x88: "",
0x89: "",
0x8A: "",
0x8B: "",
0x8C: "",
0x8D: "",
0x8E: "",
0x8F: "",
0x90: "",
0x91: "",
0x92: "",
0x93: "",
0x94: "",
0x95: "",
0x96: "",
0x97: "",
0x98: "",
0x99: "",
0x9A: "",
0x9B: "",
0x9C: "",
0x9D: "",
0x9E: "",
0x9F: "",
0xA0: "",
0xA1: "",
0xA2: "",
0xA3: "",
0xA4: "",
0xA5: "",
0xA6: "",
0xA7: "",
0xA8: "",
0xA9: "",
0xAA: "",
0xAB: "",
0xAC: "",
0xAD: "",
0xAE: "",
0xAF: "",
0xB0: "",
0xB1: "",
0xB2: "",
0xB3: "",
0xB4: "",
0xB5: "",
0xB6: "",
0xB7: "",
0xB8: "",
0xB9: "",
0xBA: "",
0xBB: "",
0xBC: "",
0xBD: "",
0xBE: "",
0xBF: "",
0xC0: "",
0xC1: "",
0xC2: "",
0xC3: "",
0xC4: "",
0xC5: "",
0xC6: "",
0xC7: "",
0xC8: "",
0xC9: "",
0xCA: "",
0xCB: "",
0xCC: "",
0xCD: "",
0xCE: "",
0xCF: "",
0xD0: "",
0xD1: "",
0xD2: "",
0xD3: "",
0xD4: "",
0xD5: "",
0xD6: "",
0xD7: "",
0xD8: "",
0xD9: "",
0xDA: "",
0xDB: "",
0xDC: "",
0xDD: "",
0xDE: "",
0xDF: "",
0xE0: "",
0xE1: "",
0xE2: "",
0xE3: "",
0xE9: "",
}
for n in range(0x100):
if not n in JA_CHARMAP:
JA_CHARMAP[n] = '<EFBFBD>'
# ty, tachyon
DE_FR_TEXT_MAP = dict(enumerate([
EN_CHARMAP = dict(enumerate([
# 0x0X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x1X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
@ -926,12 +573,65 @@ DE_FR_TEXT_MAP = dict(enumerate([
# 0x3X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x4X
# 4x
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "\f", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "\n", "\n",
# 5x
"@", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "POKé", "<EFBFBD>", "……", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "PC", "TM", "TRAINER", "ROCKET", ".",
# 6x
"A", "B", "C", "D", "E", "F", "G", "H",
"I", "V", "S", "L", "M", ":", "", "",
# 7x
"", "", "", "", "", "", "", "",
"", "", "", "", "", "", "", " ",
# 8x
"A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P",
# 9x
"Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z", "(", ")", " :", " ;", "[", "]",
# Ax
"a", "b", "c", "d", "e", "f", "g", "h",
"i", "j", "k", "l", "m", "n", "o", "p",
# Bx
"q", "r", "s", "t", "u", "v", "w", "x",
"y", "z", "é", "ʼd", "ʼl", "ʼs", "ʼt", "ʼv",
# Cx
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x5X
# Dx
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# Ex
"'", "ᴾₖ", "ᴹₙ", "-", "ʼr", "ʼm", " ?", " !",
".", "", "", "", "", "", "", "",
# Fx
# F0 is the currency symbol; this is the ruble sign, close enough
"", "×", ".", "/", ",", "", "0", "1",
"2", "3", "4", "5", "6", "7", "8", "9",
]))
# ty, tachyon
DE_FR_CHARMAP = dict(enumerate([
# 0x0X
"", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x1X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x2X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x3X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 4x
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "\f", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "\n", "\n",
# 5x
"@", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "POKé", "<EFBFBD>", "……", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x6X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
@ -957,23 +657,16 @@ DE_FR_TEXT_MAP = dict(enumerate([
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "cʼ", "dʼ", "jʼ", "lʼ",
"mʼ", "nʼ", "pʼ", "sʼ", "ʼs", "tʼ", "uʼ", "yʼ",
# 0xEX
"'", "P\u200dk", "M\u200dn", "-", "¿", "¡", "?", "!",
"'", "ᴾₖ", "ᴹₙ", "-", "¿", "¡", "?", "!",
".", "", "", "", "", "", "", "",
# 0xFX
"$", "×", ".", "/", ",", "", "0", "1",
"2", "3", "4", "5", "6", "7", "8", "9",
]))
DE_FR_TEXT_MAP.update({
0x00: "", # "Start text"?
0x4E: "\n", # Move to next line
0x49: "\f", # Start a new Pokédex page
0x5F: ".", # End of Pokédex entry, adds a period
0x54: "POKé",
})
ES_IT_CHARMAP = dict(enumerate([
# 0x0X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x1X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
@ -984,12 +677,12 @@ ES_IT_CHARMAP = dict(enumerate([
# 0x3X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x4X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 0x5X
"@", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
# 4x
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "\f", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "\n", "\n",
# 5x
"@", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "POKé", "<EFBFBD>", "……", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", ".",
# 0x6X
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
"<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>", "<EFBFBD>",
@ -1015,19 +708,21 @@ ES_IT_CHARMAP = dict(enumerate([
"ì", "í", "ñ", "ò", "ó", "ú", "º", "&",
"ʼd", "ʼl", "ʼm", "ʼr", "ʼs", "ʼt", "ʼv", " ",
# 0xEX
"'", "P\u200dk", "M\u200dn", "-", "¿", "¡", "?", "!",
"'", "ᴾₖ", "ᴹₙ", "-", "¿", "¡", "?", "!",
".", "", "", "", "", "", "", "",
# 0xFX
"$", "×", ".", "/", ",", "", "0", "1",
"2", "3", "4", "5", "6", "7", "8", "9"
"2", "3", "4", "5", "6", "7", "8", "9",
]))
ES_IT_CHARMAP.update({
0x00: "", # "Start text"?
0x4E: "\n", # Move to next line
0x49: "\f", # Start a new Pokédex page
0x5F: ".", # End of Pokédex entry, adds a period
0x54: "POKé",
})
CHARACTER_MAPS = dict(
ja=JA_CHARMAP,
en=EN_CHARMAP,
es=ES_IT_CHARMAP,
it=ES_IT_CHARMAP,
de=DE_FR_CHARMAP,
fr=DE_FR_CHARMAP,
)
class PokemonString:
@ -1036,15 +731,9 @@ class PokemonString:
self.raw = raw
def decrypt(self, language):
if language == 'ja':
charmap = JA_CHARMAP
elif language == 'en':
charmap = EN_TEXT_MAP
elif language in ('es', 'it'):
charmap = ES_IT_CHARMAP
elif language in ('de', 'fr'):
charmap = DE_FR_TEXT_MAP
else:
try:
charmap = CHARACTER_MAPS[language]
except KeyError:
raise ValueError("Not a known language: {!r}".format(language))
return ''.join(
@ -1069,6 +758,36 @@ class PokemonCString(Adapter):
return PokemonString(obj)
class MacroPokemonCString(Construct):
"""Similar to the above, but for strings that may contain the 0x17 "far
load" macro, whose parameters may in turn contain the NUL byte 0x50 without
marking the end of the string. Yikes."""
def _parse(self, stream, context):
buf = bytearray()
while True:
byte, = stream.read(1)
if byte == 0x17:
# "Far load"
addr_lo, addr_hi, bank = stream.read(3)
offset = unbank(bank, addr_lo + addr_hi * 256)
pos = stream.tell()
try:
stream.seek(offset)
buf.extend(self._parse(stream, context).raw)
finally:
stream.seek(pos)
elif byte == 0x50:
break
elif byte == 0x5F:
# 5F ends a Pokédex entry
buf.append(byte)
break
else:
buf.append(byte)
return PokemonString(bytes(buf))
class NullTerminatedArray(Subconstruct):
_peeker = Peek(ULInt8('___'))
__slots__ = ()
@ -1217,34 +936,24 @@ evos_moves_pointer = Struct(
Pointer(lambda ctx: ctx.offset + (0xE - 1) * 0x4000, evos_moves_struct),
)
pokedex_flavor_struct = Struct(
'pokedex_flavor',
PokemonCString('species'),
# TODO HA HA FUCK ME, SOME GAMES USE METRIC SOME (OK JUST THE US) USE IMPERIAL
#ULInt8('height_feet'),
#ULInt8('height_inches'),
#ULInt16('weight_pounds'),
# There are actually two versions of this struct... an imperial one, used by
# the US, and a metric one, used by the ENTIRE REST OF THE WORLD. The imperial
# one has separate bytes for feet and inches, so it's a different size, making
# it completely incompatible.
pokedex_flavor_struct_metric = Struct(
'pokedex_flavor_metric',
PokemonCString('genus'),
ULInt8('height_decimeters'),
ULInt16('weight_hectograms'),
# This appears to technically be a string containing a single macro, for
# "load other string from this address", but it always takes this same form
# so there's no need to actually evaluate it.
Const(ULInt8('macro'), 0x17), # 0x17 is the "far" macro
ULInt16('address'),
ULInt8('bank'),
Const(ULInt8('nul'), 0x50), # faux nul marking the end of the string
Pointer(
lambda ctx: ctx.address + (ctx.bank - 1) * 0x4000,
PokemonCString('flavor_text'),
),
MacroPokemonCString('flavor_text'),
)
# TODO this works very awkwardly as a struct
pokedex_flavor_pointer = Struct(
'xxx',
ULInt16('offset'),
# TODO hardcoded 0x10, same bank
# TODO this has to be on-demand because missingno's struct is actually bogus!
OnDemandPointer(lambda ctx: ctx.offset + (0x10 - 1) * 0x4000, pokedex_flavor_struct),
pokedex_flavor_struct_imperial = Struct(
'pokedex_flavor_imperial',
PokemonCString('genus'),
ULInt8('height_feet'),
ULInt8('height_inches'),
ULInt16('weight_pounds'),
MacroPokemonCString('flavor_text'),
)
@ -1267,6 +976,7 @@ class RBYCart:
self.addrs = self.detect_addresses()
self.game, self.language = self.detect_game()
self.uses_metric = not self.language == 'en'
# And snag this before anything else happens; prevents some silly
# problems where a reified property seeks, then tries to read this, and
@ -1639,13 +1349,13 @@ class RBYCart:
# Warn about a potentially bad checksum
if not game_c or not language_c:
log.warn(
"Hmm. I don't recognize the checksum for {}, but I'll "
"Hmm. I don't recognize the checksum for %s, but I'll "
"continue anyway.",
self.path.name)
elif game_c != game_h or language_c != language_h:
log.warn(
"This is very surprising. The checksum indicates that this "
"game should be {}, {}, but I detected it as {}, {}. Probably "
"game should be %s, %s, but I detected it as %s, %s. Probably "
"my fault, not yours. Continuing anyway.",
game_c, language_c, game_h, language_h)
@ -1854,25 +1564,24 @@ class RBYCart:
def pokedex_entries(self):
"""List of pokedex_flavor_structs."""
ret = [None] * self.NUM_POKEMON
dex_bank, _ = bank(self.addrs['PokedexEntryPointers'])
if self.uses_metric:
pokedex_flavor_struct = pokedex_flavor_struct_metric
else:
pokedex_flavor_struct = pokedex_flavor_struct_imperial
self.stream.seek(self.addrs['PokedexEntryPointers'])
for index, pointer in enumerate(Array(self.max_pokemon_index, pokedex_flavor_pointer).parse_stream(self.stream), start=1):
# This address is just an array of pointers
for index, address in enumerate(Array(self.max_pokemon_index, ULInt16('pointer')).parse_stream(self.stream), start=1):
try:
id = self.pokedex_order[index]
except KeyError:
continue
ret[id] = pointer.pokedex_flavor.value
self.stream.seek(unbank(dex_bank, address))
ret[id] = pokedex_flavor_struct.parse_stream(self.stream)
record = pokemon_records_by_internal[index]
pokedex_flavor = pointer.pokedex_flavor.value
# TODO FUCKKKK IMPERIALLLLL
#record.height = pokedex_flavor.height_feet * 12 + pokedex_flavor.height_inches
#record.weight = pokedex_flavor.weight_pounds
record.height = pokedex_flavor.height_decimeters
record.weight = pokedex_flavor.weight_hectograms
record.species = pokedex_flavor.species.decrypt(language)
record.flavor_text = pokedex_flavor.flavor_text.decrypt(language)
return ret
@reify
def move_names(self):
@ -2013,6 +1722,24 @@ def main(base_root):
evolutions.append(evo)
writer.evolutions = evolutions
# Pokédex flavor
flavor_struct = cart.pokedex_entries[id]
# TODO LOLLLL
if 'genus' not in writer.__dict__:
writer.genus = {}
writer.genus[cart.language] = flavor_struct.genus.decrypt(language)
if 'flavor_text' not in writer.__dict__:
writer.flavor_text = {}
writer.flavor_text[cart.language] = flavor_struct.flavor_text.decrypt(language)
if cart.uses_metric:
writer.height = 1000 * flavor_struct.height_decimeters
writer.weight = 100000000 * flavor_struct.weight_hectograms
else:
writer.height = 254 * (
12 * flavor_struct.height_feet
+ flavor_struct.height_inches)
writer.weight = 453592370 * flavor_struct.weight_pounds
fn = root / 'pokemon.yaml'
print('Writing', fn)
with fn.open('w') as f:

View file

@ -174,12 +174,18 @@ class Pokémon(VersionedLocus):
# TODO family?
evolutions = _List(Evolution)
species = _Value(str)
genus = _Value(str)
flavor_text = _Value(str)
# TODO maybe want little wrapper types that can display as either imperial
# or metric
# TODO maybe also dump as metric rather than plain numbers
# Inches and pounds are both defined as exact numbers of centimeters and
# kilograms respectively, so this uses the largest units that can represent
# both metric and imperial values as integers with no loss of precision:
# myriameters (tenths of a millimeter) and micrograms.
# Divide by 100 for centimeters, or by 254 for inches
height = _Value(int)
# Divide by one billion for kilograms, or by 453592370 for pounds
weight = _Value(int)
# TODO this belongs to a place, not to a pokemon