new launcher and save converts and pipeline

This commit is contained in:
bryanthaboi
2026-07-25 08:34:49 -04:00
parent 6625391f76
commit 9307488dc1
58 changed files with 10971 additions and 348 deletions
+93
View File
@@ -0,0 +1,93 @@
"""Extract the Gen1 text byte <-> glyph/token charmap.
Source: pokered/constants/charmap.asm -- `charmap "TOKEN", $NN` lines (one
byte value per token; TOKEN is a printable glyph for ordinary characters,
or a bracketed control token like "<PLAYER>"/"@" for terminators and
runtime substitutions). This is the encoding fixed-length name fields
(sPlayerName, sRivalName, party/box OT names, nicknames) use -- NOT the
same thing as data/generated/text.lua, which holds already-decoded
dialogue strings extracted from asm source text and never needed a raw
byte<->glyph table of its own.
Several byte VALUES are deliberately reused across different on-screen
graphics contexts later in the file (font_extra.png bold letters, then
font_battle_extra.png, then misc one-off glyphs, THEN the real A-Z table
at $80-$99, which unused Japanese katakana entries further down redefine
again at the same range) -- legal for RGBDS charmap (it only needs
token->byte to be unambiguous for encoding source text; nothing in this
English-only source ever assembles the literal token "", so its
redefinition is inert for the actual ROM). For our purposes it means:
- byToken[token] = byte is unambiguous either way (last-definition-wins,
matching RGBDS's own semantics), used to ENCODE a name.
- byByte[byte] = token needs the FIRST definition of each byte, not the
last, to DECODE a byte back to the international glyph that's actually
in the shipped font at that position instead of a later vestigial
redefinition (confirmed against the file: Latin "A".."Z" at $80-$99 are
defined once, early, cleanly, before later `charmap "", $80` etc.
entries that reuse those same bytes for characters this ROM's font
never draws there).
Output: src/save_convert/data/charmap.lua (committed; independent of ROM
import, like data/palettes_gbc.lua)
byByte[byte] = token (first definition per byte -- see above)
byToken[token] = byte (last definition per token -- see above)
"""
import argparse
import os
import re
import sys
from . import util
def extract(pokered, out_path):
path = os.path.join(pokered, "constants/charmap.asm")
by_byte = {}
by_token = {}
for lineno, line in util.read_asm(path):
s = line.strip()
m = re.match(r'charmap\s+"((?:[^"\\]|\\.)*)"\s*,\s*(\S+)', s)
if not m:
continue
token = m.group(1)
value = util.parse_number(m.group(2))
if not (0 <= value <= 255):
util.die(f"{path}:{lineno}: byte value {value} out of range for {token!r}")
if value not in by_byte: # first definition wins for decoding
by_byte[value] = token
by_token[token] = value # last definition wins for encoding
if not by_byte:
util.die(f"{path}: parsed 0 charmap entries")
util.write_lua(
out_path,
{"source": "pokered constants/charmap.asm",
"byByte": by_byte,
"byToken": by_token},
header="Gen1 text byte <-> glyph/token charmap (fixed-length name\n"
"fields: player/rival/OT names, nicknames -- box/party mon\n"
"names are NUL-free, '@' ($50) terminated, space-padded).\n"
"byToken's key is the literal glyph for ordinary characters\n"
"(\"A\", \"\", ...) or a bracketed control token\n"
"(\"<PLAYER>\", \"@\") for terminators/substitutions -- only\n"
"the plain single-glyph entries are meaningful inside a name.")
return by_byte, by_token
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--pokered", required=True)
parser.add_argument("--out", default="src/save_convert/data/charmap.lua")
args = parser.parse_args(argv)
extract(args.pokered, args.out)
return 0
if __name__ == "__main__":
if __package__ is None:
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
__package__ = "extract"
from extract import util as util # noqa: F811
raise SystemExit(main())
+117
View File
@@ -0,0 +1,117 @@
"""Extract wEventFlags' bit-index -> EVENT_* name table.
Source: pokered/constants/event_constants.asm -- an RGBDS const block using
`const_def` / `const NAME` / `const_skip N` / `const_next N` (jump the
counter to an absolute bit position; N is usually `$hex` but occasionally
a small arithmetic expression like `$F0 - 2`). This is NOT the same shape
tools/extract/util.py's parse_const_block handles (that one only knows
const_def/const/const_skip), so this file gets its own small tracker
rather than stretching a shared helper to fit one caller.
wEventFlags (ram/wram.asm) is a flat NUM_EVENTS-bit array; each EVENT_*
constant IS its bit index. NUM_EVENTS is set by the file's own trailing
`const_next $A00` (2560 bits = 320 bytes), matched by `flag_array
NUM_EVENTS` at the wEventFlags declaration.
Output: src/save_convert/data/event_flags.lua (committed; independent of ROM
import, like data/palettes_gbc.lua)
byName[EVENT_NAME] = bit index (int)
byBit[bit index] = EVENT_NAME
count = total bit width of wEventFlags (NUM_EVENTS)
"""
import argparse
import os
import re
import sys
from . import util
def _eval_next(expr):
"""`$XX` or `$XX - N` / `$XX + N` -> int."""
m = re.match(r"^(\S+)\s*([+-])\s*(\S+)$", expr)
if m:
base = util.parse_number(m.group(1))
n = util.parse_number(m.group(3))
return base + n if m.group(2) == "+" else base - n
return util.parse_number(expr)
def extract(pokered, out_path):
path = os.path.join(pokered, "constants/event_constants.asm")
by_name = {}
by_bit = {}
value = None
count = None
for lineno, line in util.read_asm(path):
s = line.strip()
if not s:
continue
m = re.match(r"const_def(?:\s+(\S+))?$", s)
if m:
value = util.parse_number(m.group(1)) if m.group(1) else 0
continue
m = re.match(r"const\s+(\w+)", s)
if m:
if value is None:
util.die(f"{path}:{lineno}: const before const_def")
name = m.group(1)
if name in by_name:
util.die(f"{path}:{lineno}: duplicate flag {name}")
by_name[name] = value
by_bit[value] = name
value += 1
continue
m = re.match(r"const_skip(?:\s+(\S+))?$", s)
if m:
if value is None:
util.die(f"{path}:{lineno}: const_skip before const_def")
value += util.parse_number(m.group(1)) if m.group(1) else 1
continue
m = re.match(r"const_next\s+(.+)$", s)
if m:
value = _eval_next(m.group(1).strip())
continue
m = re.match(r"DEF\s+NUM_EVENTS\s+EQU\s+const_value\s*$", s)
if m:
if value is None:
util.die(f"{path}:{lineno}: NUM_EVENTS before any const_def")
count = value
continue
if count is None:
util.die(f"{path}: NUM_EVENTS EQU const_value not found")
if count % 8 != 0:
util.die(f"{path}: NUM_EVENTS={count} is not byte-aligned")
if not by_name:
util.die(f"{path}: parsed 0 EVENT_* flags")
util.write_lua(
out_path,
{"source": "pokered constants/event_constants.asm",
"count": count,
"byName": by_name,
"byBit": by_bit},
header="wEventFlags bit index <-> EVENT_* name (see ram/wram.asm\n"
"wEventFlags, a flat NUM_EVENTS-bit / (NUM_EVENTS/8)-byte\n"
"array). byBit only has entries for bits with a name --\n"
"reserved/padding bits are intentionally absent.")
return by_name, by_bit, count
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
parser.add_argument("--pokered", required=True)
parser.add_argument("--out", default="src/save_convert/data/event_flags.lua")
args = parser.parse_args(argv)
extract(args.pokered, args.out)
return 0
if __name__ == "__main__":
if __package__ is None:
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
__package__ = "extract"
from extract import util as util # noqa: F811
raise SystemExit(main())