mirror of https://github.com/zeldaret/mm.git
232 lines
7.6 KiB
Python
Executable File
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)
|