mm/tools/msg/staff/msgdisStaff.py

232 lines
7.6 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import sys
import struct
class MessageCredits:
def __init__(self, id, typePos, addr, text):
self.id = id
self.typePos = typePos
self.addr = addr
self.text = text
self.decodedText = ""
self.decodePos = 0
def __str__(self):
return (f' Message 0x{self.id:04X}:\n'
f' Segment: 0x{self.addr:08X}\n'
f' TypePos: 0x{self.typePos:02X}\n'
f' Text: {self.text}\n'
f' Decoded:\n{self.decodedText}')
def macro(self):
self.decode()
return (
f"DEFINE_MESSAGE(0x{self.id:04X}, 0x{self.typePos:02X},\n"
f"{self.decodedText}\n)\n"
)
def decode_cmd_arg_none(self, cmd):
return f'{cmd}'
def decode_cmd_arg_1byte(self, cmd):
arg = self.text[self.decodePos]
self.decodePos += 1
return f'{cmd}("\\x{arg:02X}")'
def decode_cmd_arg_2byte(self, cmd):
arg1 = self.text[self.decodePos]
arg2 = self.text[self.decodePos + 1]
self.decodePos += 2
return f'{cmd}("\\x{arg1:02X}\\x{arg2:02X}")'
def decode_cmd_color(self, cmd):
COLORS = {
0: "DEFAULT",
1: "RED",
2: "ADJUSTABLE",
3: "BLUE",
4: "LIGHTBLUE",
5: "PURPLE",
6: "YELLOW",
7: "BLACK"
}
color = self.text[self.decodePos]
self.decodePos += 1
return f'{cmd}({COLORS[color]}) '
def decode_cmd_background(self, cmd):
arg1 = self.text[self.decodePos]
arg2 = self.text[self.decodePos + 1]
arg3 = self.text[self.decodePos + 2]
self.decodePos += 3
return f'{cmd}("\\x{arg1:02X}","\\x{arg2:02X}","\\x{arg3:02X}")'
def decode_cmd_highscore(self, cmd):
HIGHSCORES = {
0: "HS_BANK_RUPEES",
1: "HS_UNK_1",
2: "HS_FISHING",
3: "HS_BOAT_ARCHERY",
4: "HS_HORSE_BACK_BALLOON",
6: "HS_SHOOTING_GALLERY"
}
highscore = self.text[self.decodePos]
self.decodePos += 1
return f'{cmd}({HIGHSCORES[highscore]})'
def decode(self):
CMDMAP = {
0x00: ("CMD_COLOR_DEFAULT", self.decode_cmd_arg_none),
0x04: ("CMD_BOX_BREAK", self.decode_cmd_arg_none),
0x05: ("CMD_COLOR", self.decode_cmd_color),
0X06: ("CMD_SHIFT", self.decode_cmd_arg_1byte),
0x07: ("CMD_TEXTID", self.decode_cmd_arg_2byte),
0x08: ("CMD_QUICKTEXT_ENABLE", self.decode_cmd_arg_none),
0x09: ("CMD_QUICKTEXT_DISABLE", self.decode_cmd_arg_none),
0x0A: ("CMD_PERSISTENT", self.decode_cmd_arg_none),
0x0B: ("CMD_EVENT", self.decode_cmd_arg_none),
0x0C: ("CMD_BOX_BREAK_DELAY", self.decode_cmd_arg_2byte),
0x0D: ("CMD_WAIT_INPUT", self.decode_cmd_arg_none),
0x0E: ("CMD_FADE", self.decode_cmd_arg_1byte),
0x0F: ("CMD_NAME", self.decode_cmd_arg_none),
0x10: ("CMD_OCARINA", self.decode_cmd_arg_none),
0x11: ("CMD_FADE2", self.decode_cmd_arg_2byte),
0x12: ("CMD_SFX", self.decode_cmd_arg_2byte),
0x13: ("CMD_ITEM_ICON", self.decode_cmd_arg_1byte),
0x14: ("CMD_TEXT_SPEED", self.decode_cmd_arg_1byte),
0x15: ("CMD_BACKGROUND", self.decode_cmd_background),
0x16: ("CMD_MARATHONTIME", self.decode_cmd_arg_none),
0x17: ("CMD_RACETIME", self.decode_cmd_arg_none),
0x18: ("CMD_POINTS", self.decode_cmd_arg_none),
0x1A: ("CMD_UNSKIPPABLE", self.decode_cmd_arg_none),
0x1B: ("CMD_TWO_CHOICE", self.decode_cmd_arg_none),
0x1C: ("CMD_THREE_CHOICE", self.decode_cmd_arg_none),
0x1D: ("CMD_FISH_INFO", self.decode_cmd_arg_none),
0x1E: ("CMD_HIGHSCORE", self.decode_cmd_highscore),
0x1F: ("CMD_TIME", self.decode_cmd_arg_none),
}
if self.decodedText != "":
return
prevText = False
prevNewline = True
prevCmd = False
self.decodePos = 0
textLen = len(self.text)
while self.decodePos < textLen:
char = self.text[self.decodePos]
self.decodePos += 1
if char >= 0x20 and char <= 0xAF: # Regular Characters
if prevCmd:
self.decodedText += ' "'
elif prevNewline:
self.decodedText += '"'
if char == 0x22: # Handle escaping "
self.decodedText += '\\"'
else:
self.decodedText += chr(char)
prevText = True
prevNewline = False
prevCmd = False
elif char == 0x1: # New line
if prevCmd:
self.decodedText += ' "'
elif prevNewline:
self.decodedText += '"'
self.decodedText += f'\\n"\n'
prevText = False
prevNewline = True
prevCmd = False
elif char == 0x04 or char == 0x0C: # Box Breaks add automatic newlines
if prevText:
self.decodedText += '"'
cmd, decoder = CMDMAP[char]
self.decodedText += f'\n{decoder(cmd)}\n'
prevText = False
prevNewline = True
prevCmd = False
elif char == 0x02: # End command, do nothing
if prevText:
self.decodedText += '"'
else: # Control Codes (see message_data_fmt_staff.h)
if prevText:
self.decodedText += '" '
elif prevCmd:
self.decodedText += ' '
cmd, decoder = CMDMAP[char]
self.decodedText += decoder(cmd)
prevText = False
prevNewline = False
prevCmd = True
def parseTable(start):
table = {}
with open("extracted/n64-us/baserom/code","rb") as f:
f.seek(start)
buf = f.read(8)
textId, typePos, segment = struct.unpack(">HBxI", buf)
while textId != 0xFFFF:
table[segment] = (textId, typePos, segment)
buf = f.read(8)
textId, typePos, segment = struct.unpack(">HBxI", buf)
return table
STAFF_MESSAGE_TABLE_ADDR = 0x12A048 # Location of Staff message table in extracted/n64-us/baserom/code
STAFF_SEGMENT_ADDR = 0x07000000
def main(outfile):
msgTable = parseTable(STAFF_MESSAGE_TABLE_ADDR)
buf = []
with open("extracted/n64-us/baserom/staff_message_data_static", "rb") as f:
buf = f.read()
bufLen = len(buf)
i = 0
messages = []
while i < bufLen:
addr = STAFF_SEGMENT_ADDR + i
start = i
while i < bufLen and buf[i] != 0x2:
i += 1
i += 1
if i >= bufLen:
break
id, typePos, segment = msgTable[addr]
msg = MessageCredits(id, typePos, segment, buf[start:i])
messages.append(msg)
i = (i + 3) & ~0x3 # Next message starts on a 0x4 byte boundary
if outfile is None:
for msg in messages:
sys.stdout.write(msg.macro())
sys.stdout.write("\n")
else:
with open(outfile, "w") as f:
for msg in messages:
f.write(msg.macro())
f.write("\n")
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Extract staff_message_data_static text")
parser.add_argument('-o', '--outfile', help='output file to write to. None for stdout')
args = parser.parse_args()
main(args.outfile)