papermario/tools/build/msg/parse_compile.py
lshamis ae66312d8c
Add Python linter to github actions (#1100)
* Add Python linter to github actions

* wip

* Add back splat_ext

* Format files

* C++ -> C

* format 2 files

* split workflow into separate file, line length 120, fix excludes

* -l 120 in ci

* update black locally and apply formatting changes

* pyproject.toject

---------

Co-authored-by: Ethan Roseman <ethteck@gmail.com>
2023-07-30 02:03:17 +09:00

1299 lines
47 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from sys import argv
from collections import OrderedDict
import re
import msgpack # way faster than pickle
class Message:
def __init__(self, name, section, index):
self.name = name
self.section = section
self.index = index
self.bytes = [] # XXX: bytearray would be better
def try_convert_int(s):
try:
return int(s, base=0)
except:
return s
def parse_command(source):
if source[0] != "[":
return None, [], {}, source
source = source[1:] # "["
inside_brackets = ""
while source[0] != "]":
if source[0] == "\n":
return None, [], {}, source
inside_brackets += source[0]
source = source[1:]
source = source[1:] # "]"
command, *raw_args = inside_brackets.split(" ")
args = []
named_args = {}
for arg in raw_args:
if "=" in arg:
key, value = arg.split("=", 1)
if "," in value:
named_args[key.lower()] = [try_convert_int(v) for v in value.lower().split(",")]
else:
named_args[key.lower()] = try_convert_int(value.lower())
else:
if "," in arg:
args.append([try_convert_int(value) for value in arg.lower().split(",")])
else:
args.append(try_convert_int(arg.lower()))
# args = []
# for arg in raw_args:
# args.append(try_convert_int(arg.lower()))
return command.lower(), args, named_args, source
def color_to_code(color, style):
COLORS = {
"diary": {
"normal": 0x00,
"red": 0x07,
},
"inspect": {
"dark": 0x17,
},
"button": {
"blue": 0x10,
"green": 0x11,
"red": 0x12,
"yellow": 0x13,
"gray": 0x14,
"grey": 0x14,
},
"popup": {
"red": 0x28,
"pink": 0x29,
"purple": 0x2A,
"blue": 0x2B,
"teal": 0x2C,
"green": 0x2D,
"yellow": 0x2E,
"normal": 0x2F,
},
"sign": {
"normal": 0x18,
"red": 0x19,
"blue": 0x1A,
"green": 0x1B,
},
}
if type(color) is int:
return color
return COLORS.get(
style,
{
# [style:left], [style:right]
"normal": 0x0A,
"red": 0x20,
"pink": 0x21,
"purple": 0x22,
"blue": 0x23,
"cyan": 0x24,
"green": 0x25,
"yellow": 0x26,
},
).get(color)
CHARSET = {
# "𝅘𝅥𝅮": 0x00,
"!": 0x01,
'"': 0x02,
"#": 0x03,
"$": 0x04,
"%": 0x05,
"&": 0x06,
"'": 0x07,
"(": 0x08,
")": 0x09,
"*": 0x0A,
"+": 0x0B,
",": 0x0C,
"-": 0x0D,
".": 0x0E,
"/": 0x0F,
"0": 0x10,
"1": 0x11,
"2": 0x12,
"3": 0x13,
"4": 0x14,
"5": 0x15,
"6": 0x16,
"7": 0x17,
"8": 0x18,
"9": 0x19,
":": 0x1A,
";": 0x1B,
"<": 0x1C,
"=": 0x1D,
">": 0x1E,
"?": 0x1F,
"@": 0x20,
"A": 0x21,
"B": 0x22,
"C": 0x23,
"D": 0x24,
"E": 0x25,
"F": 0x26,
"G": 0x27,
"H": 0x28,
"I": 0x29,
"J": 0x2A,
"K": 0x2B,
"L": 0x2C,
"M": 0x2D,
"N": 0x2E,
"O": 0x2F,
"P": 0x30,
"Q": 0x31,
"R": 0x32,
"S": 0x33,
"T": 0x34,
"U": 0x35,
"V": 0x36,
"W": 0x37,
"X": 0x38,
"Y": 0x39,
"Z": 0x3A,
"[": 0x3B,
"¥": 0x3C,
"]": 0x3D,
"^": 0x3E,
"_": 0x3F,
"`": 0x40,
"a": 0x41,
"b": 0x42,
"c": 0x43,
"d": 0x44,
"e": 0x45,
"f": 0x46,
"g": 0x47,
"h": 0x48,
"i": 0x49,
"j": 0x4A,
"k": 0x4B,
"l": 0x4C,
"m": 0x4D,
"n": 0x4E,
"o": 0x4F,
"p": 0x50,
"q": 0x51,
"r": 0x52,
"s": 0x53,
"t": 0x54,
"u": 0x55,
"v": 0x56,
"w": 0x57,
"x": 0x58,
"y": 0x59,
"z": 0x5A,
"{": 0x5B,
"|": 0x5C,
"}": 0x5D,
"~": 0x5E,
"°": 0x5F,
"À": 0x60,
"Á": 0x61,
"Â": 0x62,
"Ä": 0x63,
"Ç": 0x64,
"È": 0x65,
"É": 0x66,
"Ê": 0x67,
"Ë": 0x68,
"Ì": 0x69,
"Í": 0x6A,
"Î": 0x6B,
"Ï": 0x6C,
"Ñ": 0x6D,
"Ò": 0x6E,
"Ó": 0x6F,
"Ô": 0x70,
"Ö": 0x71,
"Ù": 0x72,
"Ú": 0x73,
"Û": 0x74,
"Ü": 0x75,
"ß": 0x76,
"à": 0x77,
"á": 0x78,
"â": 0x79,
"ä": 0x7A,
"ç": 0x7B,
"è": 0x7C,
"é": 0x7D,
"ê": 0x7E,
"ë": 0x7F,
"ì": 0x80,
"í": 0x81,
"î": 0x82,
"ï": 0x83,
"ñ": 0x84,
"ò": 0x85,
"ó": 0x86,
"ô": 0x87,
"ö": 0x88,
"ù": 0x89,
"ú": 0x8A,
"û": 0x8B,
"ü": 0x8C,
"¡": 0x8D,
"¿": 0x8E,
"ª": 0x8F,
# "♥": 0x90,
# "★": 0x91,
# "↑": 0x92,
# "↓": 0x93,
# "←": 0x94,
# "→": 0x95,
# "●": 0x96,
# "✖": 0x97,
"": 0xA2,
"": 0xA3,
"": 0xA4,
"": 0xA5,
" ": 0xF7,
# "Ⓐ": [0xFF, 0x24, 0xFF, 0x05, 0x10, 0x98, 0xFF, 0x25],
# "Ⓑ": [0xFF, 0x24, 0xFF, 0x05, 0x11, 0x99, 0xFF, 0x25],
# "Ⓢ": [0xFF, 0x24, 0xFF, 0x05, 0x12, 0xA1, 0xFF, 0x25],
# "▲": [0xFF, 0x24, 0xFF, 0x05, 0x13, 0x9D, 0xFF, 0x25],
# "▼": [0xFF, 0x24, 0xFF, 0x05, 0x13, 0x9E, 0xFF, 0x25],
# "◀": [0xFF, 0x24, 0xFF, 0x05, 0x13, 0x9F, 0xFF, 0x25],
# "▶": [0xFF, 0x24, 0xFF, 0x05, 0x13, 0xA0, 0xFF, 0x25],
# "Ⓛ": [0xFF, 0x24, 0xFF, 0x05, 0x14, 0x9A, 0xFF, 0x25],
# "Ⓡ": [0xFF, 0x24, 0xFF, 0x05, 0x14, 0x9B, 0xFF, 0x25],
# "Ⓩ": [0xFF, 0x24, 0xFF, 0x05, 0x14, 0x9C, 0xFF, 0x25],
}
CHARSET_CREDITS = {
"A": 0x00,
"B": 0x01,
"C": 0x02,
"D": 0x03,
"E": 0x04,
"F": 0x05,
"G": 0x06,
"H": 0x07,
"I": 0x08,
"J": 0x09,
"K": 0x0A,
"L": 0x0B,
"M": 0x0C,
"N": 0x0D,
"O": 0x0E,
"P": 0x0F,
"Q": 0x10,
"R": 0x11,
"S": 0x12,
"T": 0x13,
"U": 0x14,
"V": 0x15,
"W": 0x16,
"X": 0x17,
"Y": 0x18,
"Z": 0x19,
"'": 0x1A,
".": 0x1B,
",": 0x1C,
"0": 0x1D,
"1": 0x1E,
"2": 0x1F,
"3": 0x20,
"4": 0x21,
"5": 0x22,
"6": 0x23,
"7": 0x24,
"8": 0x25,
"9": 0x26,
"©": 0x27,
"&": 0x28,
" ": 0xF7,
}
def strip_c_comments(text):
def replacer(match):
s = match.group(0)
if s.startswith("/"):
return " "
else:
return s
pattern = re.compile(
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
re.DOTALL | re.MULTILINE,
)
return re.sub(pattern, replacer, text)
if __name__ == "__main__":
if len(argv) < 3:
print("usage: parse_compile.py [in.msg] [out.msgpack] [--c]")
exit(1)
filename = argv[1]
outfile = argv[2]
is_output_format_c = "--c" in argv
messages = []
message = None
with open(filename, "r") as f:
source = strip_c_comments(f.read())
lineno = 1
directive = ""
indent_level = 0
charset = CHARSET
font_stack = [0]
sound_stack = [0]
color_stack = [0x0A]
fx_stack = []
style = None
explicit_end = False
choiceindex = -1
while len(source) > 0:
if source[0] == "\r" or source[0] == "\t":
source = source[1:]
continue
if source[0] == "\n":
lineno += 1
source = source[1:]
for i in range(indent_level):
if source[0] == "\t":
source = source[1:]
else:
break
continue
if message is None:
directive = ""
while source[0] != " ":
if source[0] == "\n":
lineno += 1
elif source[0] == "\r":
pass
else:
directive += source[0]
source = source[1:]
directive = directive.split(":")
if directive[0] != "#message":
print(f"{filename}:{lineno}: expected #message directive")
exit(1)
if is_output_format_c:
if len(directive) != 2:
print(f"{filename}:{lineno}: expected #message:NAME directive")
exit(1)
message = Message(directive[1], None, None)
else:
if len(directive) != 3:
print(f"{filename}:{lineno}: expected #message:SECTION:INDEX directive")
exit(1)
section = int(directive[1], 16)
if directive[2].startswith("(") and directive[2].endswith(")"):
name = directive[2][1:-1]
index = None
else:
name = None
index = int(directive[2], 16)
directive = ""
message = Message(name, section, index)
messages.append(message)
while source[0] != "{":
source = source[1:]
if source[0] == "\n":
lineno += 1
elif source[0] == "\r":
pass
elif source[0] == "{":
break
elif source[0] != " " and source[0] != "\t":
print(f"{filename}:{lineno}: expected opening brace ('{{')")
exit(1)
source = source[1:] # {
# count indent level
indent_level = 0
"""
while source[0] == " " or source[0] == "\t" or source[0] == "\n" or source[0] == "\r":
if source[0] == " " or source[0] == "\t":
indent_level += 1
source = source[1:]
"""
else:
command, args, named_args, source = parse_command(source)
if command:
if command == "end":
message.bytes += [0xFD]
explicit_end = True
elif command == "raw":
message.bytes += [*args]
elif command == "br":
message.bytes += [0xF0]
elif command == "wait":
message.bytes += [0xF1]
elif command == "pause":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xF2, args[0]]
elif command == "next":
message.bytes += [0xFB]
elif command == "yield":
message.bytes += [0xFF, 0x04]
elif command == "savecolor":
message.bytes += [0xFF, 0x24]
elif command == "restorecolor":
message.bytes += [0xFF, 0x25]
elif command == "color":
if len(args) != 1:
print(f"{filename}:{lineno}: color command requires 1 parameter")
exit(1)
color = color_to_code(args[0], style)
if color is None:
print(f"{filename}:{lineno}: unknown color")
exit(1)
message.bytes += [0xFF, 0x05, color]
# color_stack.append(color)
# elif command == "/color":
# color_stack.pop()
# message.bytes += [0xFF, 0x05, color_stack[0]]
elif command == "style":
message.bytes += [0xFC]
style = args[0]
args = args[1:]
if type(style) is int:
message.bytes += [style, *args]
else:
if style == "right":
message.bytes += [0x01]
elif style == "left":
message.bytes += [0x02]
elif style == "center":
message.bytes += [0x03]
elif style == "tattle":
message.bytes += [0x04]
elif style == "choice":
pos = named_args.get("pos")
if not isinstance(pos, list) or len(pos) != 2:
print(f"{filename}:{lineno}: 'choice' style requires pos=_,_")
exit(1)
size = named_args.get("size")
if not isinstance(size, list) or len(size) != 2:
print(f"{filename}:{lineno}: 'choice' style requires size=_,_")
exit(1)
message.bytes += [
0x05,
pos[0],
pos[1],
size[0],
size[1],
]
elif style == "inspect":
message.bytes += [0x06]
elif style == "sign":
message.bytes += [0x07]
elif style == "lamppost":
height = named_args.get("height")
if not isinstance(height, int):
print(f"{filename}:{lineno}: 'lamppost' style requires height=_")
exit(1)
message.bytes += [0x08, height]
elif style == "postcard":
index = named_args.get("index")
if not isinstance(index, int):
print(f"{filename}:{lineno}: 'postcard' style requires index=_")
exit(1)
message.bytes += [0x09, index]
elif style == "popup":
message.bytes += [0x0A]
elif style == "upgrade":
pos = named_args.get("pos")
if not isinstance(pos, list) or len(pos) != 2:
print(f"{filename}:{lineno}: 'upgrade' style requires pos=_,_")
exit(1)
size = named_args.get("size")
if not isinstance(size, list) or len(size) != 2:
print(f"{filename}:{lineno}: 'upgrade' style requires size=_,_")
exit(1)
message.bytes += [
0x0C,
pos[0],
pos[1],
size[0],
size[1],
]
elif style == "narrate":
message.bytes += [0x0D]
elif style == "epilogue":
message.bytes += [0x0E]
elif command == "font":
if len(args) != 1:
print(f"{filename}:{lineno}: font command requires 1 parameter")
exit(1)
font = args[0]
if font == "standard":
font = 0
elif font == "menu":
font = 1
elif font == "title":
font = 3
elif font == "subtitle":
font = 4
if type(font) is not int:
print(f"{filename}:{lineno}: unknown font '{font}'")
exit(1)
message.bytes += [0xFF, 0x00, font]
# font_stack.append(font)
if font == 3 or font == 4:
charset = CHARSET_CREDITS
else:
charset = CHARSET
# elif command == "/font":
# font_stack.pop()
# message.bytes += [0xFF, 0x00, font_stack[0]]
# if font == 3 or font == 4:
# charset = CHARSET_CREDITS
# else:
# charset = CHARSET
elif command == "inputoff":
message.bytes += [0xFF, 0x07]
elif command == "inputon":
message.bytes += [0xFF, 0x08]
elif command == "delayoff":
message.bytes += [0xFF, 0x09]
elif command == "delayon":
message.bytes += [0xFF, 0x0A]
elif command == "charwidth":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x0B, args[0]]
elif command == "scroll":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x0C, args[0]]
elif command == "size":
args = args[0]
if len(args) == 1:
args.append(args[0])
if len(args) != 2:
print(f"{filename}:{lineno}: {command} command requires 2 parameters")
exit(1)
message.bytes += [0xFF, 0x0D, args[0], args[1]]
elif command == "sizereset":
message.bytes += [0xFF, 0x0E]
elif command == "speed":
delay = named_args.get("delay")
if not isinstance(delay, int):
print(f"{filename}:{lineno}: {command} command requires delay=_")
exit(1)
chars = named_args.get("chars")
if not isinstance(delay, int):
print(f"{filename}:{lineno}: {command} command requires chars=_")
exit(1)
message.bytes += [0xFF, 0x0F, delay, chars]
# elif command == "pos":
# if "y" not in named_args:
# print(f"{filename}:{lineno}: pos command requires parameter: y (x is optional)")
# exit(1)
# if "x" in named_args:
# message.bytes += [0xFF, 0x10, named_args["x"], named_args["y"]]
# else:
# message.bytes += [0xFF, 0x11, named_args["y"]]
elif command == "setposx":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x10, args[0] >> 8, args[0] & 0xFF]
elif command == "setposy":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x11, *args]
elif command == "right":
if len(args) == 0:
message.bytes += [0x95]
else:
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x12, args[0]]
elif command == "down":
if len(args) == 0:
message.bytes += [0x93]
else:
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x13, args[0]]
elif command == "up":
if len(args) == 0:
message.bytes += [0x92]
else:
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x14, args[0]]
elif command == "inlineimage":
index = named_args.get("index")
if not isinstance(index, int):
print(f"{filename}:{lineno}: {command} command requires index=_")
exit(1)
message.bytes += [0xFF, 0x15, index]
elif command == "animsprite":
spriteid = named_args.get("spriteid")
raster = named_args.get("raster")
# TODO: named sprite id and raster
if not isinstance(spriteid, int):
print(f"{filename}:{lineno}: {command} command requires spriteid=_")
exit(1)
if not isinstance(raster, int):
print(f"{filename}:{lineno}: {command} command requires raster=_")
exit(1)
message.bytes += [
0xFF,
0x16,
spriteid >> 8,
spriteid & 0xFF,
raster,
]
elif command == "itemicon":
itemid = named_args.get("itemid")
# TODO: itemname
if not isinstance(itemid, int):
print(f"{filename}:{lineno}: {command} command requires itemid=_")
exit(1)
message.bytes += [0xFF, 0x17, itemid >> 8, itemid & 0xFF]
elif command == "image":
index = named_args.get("index")
pos = named_args.get("pos") # xx,y
hasborder = named_args.get("hasborder")
alpha = named_args.get("alpha")
fadeamount = named_args.get("fadeamount")
if not isinstance(index, int):
print(f"{filename}:{lineno}: {command} command requires index=_")
exit(1)
if not isinstance(pos, list) or len(pos) != 2:
print(f"{filename}:{lineno}: {command} command requires pos=_,_")
exit(1)
if not isinstance(hasborder, int):
print(f"{filename}:{lineno}: {command} command requires hasborder=_")
exit(1)
if not isinstance(alpha, int):
print(f"{filename}:{lineno}: {command} command requires alpha=_")
exit(1)
if not isinstance(fadeamount, int):
print(f"{filename}:{lineno}: {command} command requires fadeamount=_")
exit(1)
message.bytes += [
0xFF,
0x18,
index,
pos[0] >> 8,
pos[0] & 0xFF,
pos[1],
hasborder,
alpha,
fadeamount,
]
elif command == "hideimage":
fadeamount = named_args.get("fadeamount", 0)
if not isinstance(fadeamount, int):
print(f"{filename}:{lineno}: {command} command requires fadeamount=_")
exit(1)
message.bytes += [0xFF, 0x19, fadeamount]
elif command == "animdelay":
index = named_args.get("index")
delay = named_args.get("delay")
if not isinstance(index, int):
print(f"{filename}:{lineno}: {command} command requires index=_")
exit(1)
if not isinstance(delay, int):
print(f"{filename}:{lineno}: {command} command requires delay=_")
exit(1)
message.bytes += [0xFF, 0x1A, 0, index, delay]
elif command == "animloop":
if len(args) != 2:
print(f"{filename}:{lineno}: {command} command requires 2 parameters")
exit(1)
message.bytes += [0xFF, 0x1B, args[0], args[1]]
elif command == "animdone":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x1C, args[0]]
elif command == "setcursorpos":
index = named_args.get("index")
pos = named_args.get("pos")
if not isinstance(index, int):
print(f"{filename}:{lineno}: {command} command requires index=_")
exit(1)
if not isinstance(pos, list) or len(pos) != 2:
print(f"{filename}:{lineno}: {command} command requires pos=_,_")
exit(1)
message.bytes += [0xFF, 0x1D, index, pos, pos]
elif command == "cursor":
if len(args) != 1:
print(f"{filename}:{lineno}: cursor command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x1E, *args]
elif command == "option" and choiceindex == -1:
if len(args) != 1:
print(f"{filename}:{lineno}: option command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x21, *args]
elif command == "endchoice" and choiceindex == -1:
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x1F, args[0]]
elif command == "setcancel":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x20, args[0]]
# elif command == "startfx":
# message.bytes += [0xFF, 0x26, resolve_effect(args[0]), *args[1:]]
# elif command == "endfx":
# message.bytes += [0xFF, 0x27, resolve_effect(args[0]), *args[1:]]
elif command == "/fx":
message.bytes += [0xFF, 0x27, fx_stack.pop()]
elif command == "shake":
fx_stack.append(0x00)
message.bytes += [0xFF, 0x26, 0x00]
elif command == "/shake":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x00]
elif command == "wave":
fx_stack.append(0x01)
message.bytes += [0xFF, 0x26, 0x01]
elif command == "/wave":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x01]
elif command == "noiseoutline":
fx_stack.append(0x02)
message.bytes += [0xFF, 0x26, 0x02]
elif command == "/noiseoutline":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x02]
elif command == "static":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
fx_stack.append(0x03)
message.bytes += [0xFF, 0x26, 0x03, args[0]]
elif command == "/static":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x03]
elif command == "blur":
_dir = named_args.get("dir")
if _dir == "x":
_dir = 0
elif _dir == "y":
_dir = 1
elif _dir == "xy":
_dir = 2
if not isinstance(_dir, int):
print(f"{filename}:{lineno}: {command} command requires dir=_")
exit(1)
fx_stack.append(0x05)
message.bytes += [0xFF, 0x26, 0x05, _dir]
elif command == "/blur":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x05]
elif command == "rainbow":
fx_stack.append(0x06)
message.bytes += [0xFF, 0x26, 0x06]
elif command == "/rainbow":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x06]
elif command == "ditherfade":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
fx_stack.append(0x07)
message.bytes += [0xFF, 0x26, 0x07, args[0]]
elif command == "/ditherfade":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x07]
elif command == "printrising":
fx_stack.append(0x0A)
message.bytes += [0xFF, 0x26, 0x0A]
elif command == "/printrising":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x0A]
elif command == "printgrowing":
fx_stack.append(0x0B)
message.bytes += [0xFF, 0x26, 0x0B]
elif command == "/printgrowing":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x0B]
elif command == "sizejitter":
fx_stack.append(0x0C)
message.bytes += [0xFF, 0x26, 0x0C]
elif command == "/sizejitter":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x0C]
elif command == "sizewave":
fx_stack.append(0x0D)
message.bytes += [0xFF, 0x26, 0x0D]
elif command == "/sizewave":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x0D]
elif command == "dropshadow":
fx_stack.append(0x0E)
message.bytes += [0xFF, 0x26, 0x0E]
elif command == "/dropshadow":
fx_stack.pop()
message.bytes += [0xFF, 0x27, 0x0E]
elif command == "var":
if len(args) != 1:
print(f"{filename}:{lineno}: var command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x28, *args]
elif command == "centerx":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x29, *args]
elif command == "rewindoff":
message.bytes += [0xFF, 0x2A, 0]
elif command == "rewindon":
message.bytes += [0xFF, 0x2A, 1]
elif command == "customvoice":
soundids = named_args.get("soundids")
if not isinstance(soundids, list) or len(pos) != 2:
print(f"{filename}:{lineno}: {command} command requires soundids=_,_")
exit(1)
message.bytes += [
0xFF,
0x2C,
soundids[0] >> 24,
(soundids[0] >> 16) & 0xFF,
(soundids[0] >> 8) & 0xFF,
soundids[0] & 0xFF,
soundids[1] >> 24,
(soundids[1] >> 16) & 0xFF,
(soundids[1] >> 8) & 0xFF,
soundids[1] & 0xFF,
]
elif command == "volume":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
message.bytes += [0xFF, 0x2E, *args]
elif command == "voice":
if len(args) != 1:
print(f"{filename}:{lineno}: {command} command requires 1 parameter")
exit(1)
sound = args[0]
if sound == "normal":
sound = 0
elif sound == "bowser":
sound = 1
elif sound == "star" or sound == "spirit":
sound = 2
if type(sound) is not int:
print(f"{filename}:{lineno}: unknown voice '{sound}'")
exit(1)
message.bytes += [0xFF, 0x2F, sound]
# sound_stack.append(sound)
# elif command == "/sound":
# sound_stack.pop()
# message.bytes += [0xFF, 0x2F, sound_stack[0]]
elif command == "a":
color_code = color_to_code("blue", "button")
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x98,
0xFF,
0x25,
]
elif command == "b":
color_code = color_to_code(
named_args.get("color", "green"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x99,
0xFF,
0x25,
]
elif command == "l":
color_code = color_to_code(
named_args.get("color", "gray"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9A,
0xFF,
0x25,
]
elif command == "r":
color_code = color_to_code(
named_args.get("color", "gray"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9B,
0xFF,
0x25,
]
elif command == "z":
color_code = color_to_code("grey", "button")
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9C,
0xFF,
0x25,
]
elif command == "c-up":
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9D,
0xFF,
0x25,
]
elif command == "c-down":
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9E,
0xFF,
0x25,
]
elif command == "c-left":
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0x9F,
0xFF,
0x25,
]
elif command == "c-right":
color_code = color_to_code(
named_args.get("color", "yellow"),
named_args.get("ctx", "button"),
)
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0xA0,
0xFF,
0x25,
]
elif command == "start":
color_code = color_to_code(
named_args.get("color", "red"),
named_args.get("ctx", "button"),
) #
assert color_code is not None
message.bytes += [
0xFF,
0x24,
0xFF,
0x05,
color_code,
0xA1,
0xFF,
0x25,
]
elif command == "~a":
message.bytes += [0x98]
elif command == "~b":
message.bytes += [0x99]
elif command == "~l":
message.bytes += [0x9A]
elif command == "~r":
message.bytes += [0x9B]
elif command == "~z":
message.bytes += [0x9C]
elif command == "~c-up":
message.bytes += [0x9D]
elif command == "~c-down":
message.bytes += [0x9E]
elif command == "~c-left":
message.bytes += [0x9F]
elif command == "~c-right":
message.bytes += [0xA0]
elif command == "~start":
message.bytes += [0xA1]
elif command == "note":
message.bytes += [0x00]
elif command == "heart":
message.bytes += [0x90]
elif command == "star":
message.bytes += [0x91]
elif command == "left":
message.bytes += [0x94]
elif command == "circle":
message.bytes += [0x96]
elif command == "cross":
message.bytes += [0x97]
elif command == "savepos":
message.bytes += [0xFF, 0x22]
elif command == "restorepos":
message.bytes += [0xFF, 0x23]
elif command == "enablecdownnext":
message.bytes += [0xFF, 0x2B]
elif command == "beginchoice":
choiceindex = 0
message.bytes += [0xFF, 0x09] # delayoff
elif command == "option" and choiceindex >= 0:
message.bytes += [0xFF, 0x1E, choiceindex] # cursor n
message.bytes += [0xFF, 0x21, choiceindex] # option n
choiceindex += 1
elif command == "endchoice" and choiceindex >= 0:
cancel = named_args.get("cancel")
message.bytes += [0xFF, 0x21, 255] # option 255
message.bytes += [0xFF, 0x0A] # delayon
if isinstance(cancel, int):
message.bytes += [0xFF, 0x20, cancel] # setcancel n
message.bytes += [0xFF, 0x1F, choiceindex] # endchoice n
choiceindex = -1
elif command == "animation" and choiceindex >= 0:
# TODO
print(f"{filename}:{lineno}: '{command}' tag is not yet implemented")
exit(1)
else:
print(f"{filename}:{lineno}: unknown command '{command}'")
exit(1)
else:
if source[0] == "}":
if not explicit_end:
print(f"{filename}:{lineno}: warning: string lacks an [end] command")
# message.bytes += [0xFD]
explicit_end = False
# sanity check
for b in message.bytes:
if not isinstance(b, int):
print(b)
# padding
while len(message.bytes) % 4 != 0:
message.bytes += [0x00]
message = None
source = source[1:] # }
indent_level = 0
choiceindex = -1
continue
if source[0] == "\\":
source = source[1:]
if source[0] in charset:
data = charset[source[0]]
if type(data) is int:
message.bytes.append(data)
else:
message.bytes += data
source = source[1:]
else:
print(f"{filename}:{lineno}: unsupported character '{source[0]}' for current font")
exit(1)
if message != None:
print(f"{filename}: missing [end]")
exit(1)
if is_output_format_c:
with open(outfile, "w") as f:
f.write(f"#include <ultra64.h>\n")
for message in messages:
f.write(f"static s8 {message.name}[] = {{\n")
for b in message.bytes:
f.write(f"0x{b:02X},")
f.write(f"\n}};\n")
else:
with open(outfile, "wb") as f:
msgpack.pack(
[
{
"section": message.section,
"index": message.index,
"name": message.name,
"bytes": bytes(message.bytes),
}
for message in messages
],
f,
)