Port over OoT's new text pipeline (#1685)

* Port OoT's new msgdis and msgenc

* format

* Remove item_ids try block

* Update assets/text/charmap.txt

Co-authored-by: Tharo <17233964+Thar0@users.noreply.github.com>

---------

Co-authored-by: Tharo <17233964+Thar0@users.noreply.github.com>
This commit is contained in:
Derek Hensley 2024-10-12 17:03:44 -07:00 committed by GitHub
parent 7bb0e9287d
commit 0514d963d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 3695 additions and 1249 deletions

View File

@ -481,6 +481,7 @@ setup-audio:
assets:
$(PYTHON) tools/extract_assets.py $(EXTRACTED_DIR)/baserom assets -j$(N_THREADS) -Z Wno-hardcoded-pointer -v $(VERSION)
$(PYTHON) tools/text/msgdis.py $(EXTRACTED_DIR)/baserom assets/text -v $(VERSION)
$(AUDIO_EXTRACT) -o $(EXTRACTED_DIR) -v $(VERSION) --read-xml
## Assembly generation
@ -533,6 +534,11 @@ $(BUILD_DIR)/src/dmadata/dmadata.o: $(BUILD_DIR)/dmadata/dmadata_table_spec.h
$(BUILD_DIR)/%.o: %.s
$(CPP) $(CPPFLAGS) $(IINC) $< | $(AS) $(ASFLAGS) $(IINC) $(ENDIAN) -o $@
$(BUILD_DIR)/assets/text/%.o: assets/text/%.c
# Preprocess text with modern cpp for varargs macros
$(CPP) -undef -D_LANGUAGE_C -D__sgi $(CPPFLAGS) $(IINC) $< -o $(@:.o=.c)
$(CC) -c $(CFLAGS) $(WARNINGS) $(C_DEFINES) $(MIPS_VERSION) $(ENDIAN) $(OPTFLAGS) -o $@ $(@:.o=.c)
$(BUILD_DIR)/assets/%.o: assets/%.c
$(CC) -c $(CFLAGS) $(IINC) $(WARNINGS) $(C_DEFINES) $(MIPS_VERSION) $(ENDIAN) $(OPTFLAGS) -o $@ $<
$(OBJCOPY_BIN)
@ -545,15 +551,15 @@ $(BUILD_DIR)/%.yar.o: $(BUILD_DIR)/%.o
$(BUILD_DIR)/baserom/%.o: $(EXTRACTED_DIR)/baserom/%
$(OBJCOPY) -I binary -O elf32-big $< $@
$(BUILD_DIR)/assets/text/message_data.enc.h: assets/text/message_data.h
$(PYTHON) tools/msg/nes/msgencNES.py -o $@ $<
$(BUILD_DIR)/assets/text/message_data.enc.h: assets/text/message_data.h assets/text/charmap.txt
$(CPP) $(CPPFLAGS) -I$(EXTRACTED_DIR) $< | $(PYTHON) tools/text/msgenc.py --encoding nes --charmap assets/text/charmap.txt - $@
$(BUILD_DIR)/assets/text/staff_message_data.enc.h: assets/text/staff_message_data.h
$(PYTHON) tools/msg/staff/msgencStaff.py -o $@ $<
$(BUILD_DIR)/assets/text/message_data_staff.enc.h: assets/text/message_data_staff.h assets/text/charmap.txt
$(CPP) $(CPPFLAGS) -I$(EXTRACTED_DIR) $< | $(PYTHON) tools/text/msgenc.py --encoding credits --charmap assets/text/charmap.txt - $@
$(BUILD_DIR)/assets/text/message_data_static.o: $(BUILD_DIR)/assets/text/message_data.enc.h
$(BUILD_DIR)/assets/text/staff_message_data_static.o: $(BUILD_DIR)/assets/text/staff_message_data.enc.h
$(BUILD_DIR)/src/code/z_message.o: $(BUILD_DIR)/assets/text/message_data.enc.h $(BUILD_DIR)/assets/text/staff_message_data.enc.h
$(BUILD_DIR)/assets/text/staff_message_data_static.o: $(BUILD_DIR)/assets/text/message_data_staff.enc.h
$(BUILD_DIR)/src/code/z_message.o: $(BUILD_DIR)/assets/text/message_data.enc.h $(BUILD_DIR)/assets/text/message_data_staff.enc.h
$(BUILD_DIR)/src/overlays/%_reloc.o: $(BUILD_DIR)/$(SPEC)
$(FADO) $$(tools/buildtools/reloc_prereq $< $(*F)) -n $(*F) -o $(@:.o=.s) -M $(@:.o=.d)

21
assets/text/charmap.txt Normal file
View File

@ -0,0 +1,21 @@
# Determines how certain text sequences should be encoded. The text sequence is
# converted to one of the tuple elements based on the chosen encoding:
# Element 1: Non-wide encoding, for text other than Japanese text and the credits.
# Element 2: For Japanese text.
# Element 3: For credits text.
{
'\n' : (0x11, 0x000A, 0x01),
'[A]' : (0xB0, 0x839F, None),
'[B]' : (0xB1, 0x83A0, None),
'[C]' : (0xB2, 0x83A1, None),
'[L]' : (0xB3, 0x83A2, None),
'[R]' : (0xB4, 0x83A3, None),
'[Z]' : (0xB5, 0x83A4, None),
'[C-Up]' : (0xB6, 0x83A5, None),
'[C-Down]' : (0xB7, 0x83A6, None),
'[C-Left]' : (0xB8, 0x83A7, None),
'[C-Right]' : (0xB9, 0x83A8, None),
'▼' : (0xBA, 0x83A9, None),
'[Control-Pad]' : (0xBB, 0x83AA, None),
}

View File

@ -2,8 +2,8 @@
#include "message_data_fmt_nes.h"
#define DEFINE_MESSAGE(textId, typePos, msg) \
const char _message_##textId[sizeof(msg)] = { msg CMD_END };
#define DEFINE_MESSAGE(textId, type, pos, msg) \
const char _message_##textId[] = msg;
#include "assets/text/message_data.enc.h"

View File

@ -2,9 +2,9 @@
#include "message_data_fmt_staff.h"
#define DEFINE_MESSAGE(textId, typePos, msg) \
const char _message_##textId##_staff[sizeof(msg)] = { msg CMD_END };
#define DEFINE_MESSAGE(textId, type, pos, msg) \
const char _message_##textId##_staff[] = msg;
#include "assets/text/staff_message_data.enc.h"
#include "assets/text/message_data_staff.enc.h"
#undef DEFINE_MESSAGE

View File

@ -1,339 +1,221 @@
#ifndef MESSAGE_DATA_FMT_NES_H
#define MESSAGE_DATA_FMT_NES_H
/*
* Macros to create both a constant and a string literal from a magic value
* The constants are used in code files when parsing text for various purposes
* The strings are used in the message_data_static files themselves, as you can only concat strings with other strings
*/
#ifndef GLUE
#define GLUE(a, b) a##b
#endif
#define STRINGIFY(s) #s
#define EXPAND_AND_STRINGIFY(s) STRINGIFY(s)
#define HEX(N) GLUE(0x, N)
#define STR(N) EXPAND_AND_STRINGIFY(GLUE(\x, N))
#include "sfx.h" // For sfx ids
/*
* Text control characters
*/
// Control character magic values, in 2-digit hex without prefix
#define CTRL_COLOR_DEFAULT 00
#define CTRL_COLOR_RED 01
#define CTRL_COLOR_GREEN 02
#define CTRL_COLOR_BLUE 03
#define CTRL_COLOR_YELLOW 04
#define CTRL_COLOR_LIGHTBLUE 05
#define CTRL_COLOR_PINK 06
#define CTRL_COLOR_SILVER 07
#define CTRL_COLOR_ORANGE 08
#define CTRL_TEXT_SPEED 0A // Note this should take an arg 0 to 6, but always just sets next decode char as 0
#define CTRL_HS_BOAT_ARCHERY 0B
#define CTRL_STRAY_FAIRIES 0C
#define CTRL_TOKENS 0D
#define CTRL_POINTS_TENS 0E
#define CTRL_POINTS_THOUSANDS 0F
#define CTRL_BOX_BREAK 10
#define CTRL_NEWLINE 11
#define CTRL_BOX_BREAK2 12
#define CTRL_CARRIAGE_RETURN 13
#define CTRL_SHIFT 14
#define CTRL_CONTINUE 15
#define CTRL_NAME 16
#define CTRL_QUICKTEXT_ENABLE 17
#define CTRL_QUICKTEXT_DISABLE 18
#define CTRL_EVENT 19
#define CTRL_PERSISTENT 1A
#define CTRL_BOX_BREAK_DELAYED 1B
#define CTRL_FADE 1C
#define CTRL_FADE_SKIPPABLE 1D
#define CTRL_SFX 1E
#define CTRL_DELAY 1F
#define CTRL_BTN_A B0
#define CTRL_BTN_B B1
#define CTRL_BTN_C B2
#define CTRL_BTN_L B3
#define CTRL_BTN_R B4
#define CTRL_BTN_Z B5
#define CTRL_BTN_CUP B6
#define CTRL_BTN_CDOWN B7
#define CTRL_BTN_CLEFT B8
#define CTRL_BTN_CRIGHT B9
#define CTRL_Z_TARGET BA
#define CTRL_CONTROL_PAD BB
#define CTRL_END BF
#define CTRL_BACKGROUND C1
#define CTRL_TWO_CHOICE C2
#define CTRL_THREE_CHOICE C3
#define CTRL_TIMER_POSTMAN C4
#define CTRL_TIMER_MINIGAME_1 C5
#define CTRL_TIMER_2 C6
#define CTRL_TIMER_MOON_CRASH C7
#define CTRL_TIMER_MINIGAME_2 C8
#define CTRL_TIMER_ENV_HAZARD C9
#define CTRL_TIME CA
#define CTRL_CHEST_FLAGS CB
#define CTRL_INPUT_BANK CC
#define CTRL_RUPEES_SELECTED CD
#define CTRL_RUPEES_TOTAL CE
#define CTRL_TIME_UNTIL_MOON_CRASH CF
#define CTRL_INPUT_DOGGY_RACETRACK_BET D0
#define CTRL_INPUT_BOMBER_CODE D1
#define CTRL_PAUSE_MENU D2
#define CTRL_TIME_SPEED D3
#define CTRL_OWL_WARP D4
#define CTRL_INPUT_LOTTERY_CODE D5
#define CTRL_SPIDER_HOUSE_MASK_CODE D6
#define CTRL_STRAY_FAIRIES_LEFT_WOODFALL D7
#define CTRL_STRAY_FAIRIES_LEFT_SNOWHEAD D8
#define CTRL_STRAY_FAIRIES_LEFT_GREAT_BAY D9
#define CTRL_STRAY_FAIRIES_LEFT_STONE_TOWER DA
#define CTRL_POINTS_BOAT_ARCHERY DB // Seems to be the exact same as CTRL_POINTS_THOUSANDS
#define CTRL_LOTTERY_CODE DC
#define CTRL_LOTTERY_CODE_GUESS DD
#define CTRL_HELD_ITEM_PRICE DE
#define CTRL_BOMBER_CODE DF
#define CTRL_EVENT2 E0 // Seems to be the exact same as CTRL_EVENT
#define CTRL_SPIDER_HOUSE_MASK_CODE_1 E1
#define CTRL_SPIDER_HOUSE_MASK_CODE_2 E2
#define CTRL_SPIDER_HOUSE_MASK_CODE_3 E3
#define CTRL_SPIDER_HOUSE_MASK_CODE_4 E4
#define CTRL_SPIDER_HOUSE_MASK_CODE_5 E5
#define CTRL_SPIDER_HOUSE_MASK_CODE_6 E6
#define CTRL_HOURS_UNTIL_MOON_CRASH E7
#define CTRL_TIME_UNTIL_NEW_DAY E8
#define CTRL_HS_POINTS_BANK_RUPEES F0
#define CTRL_HS_POINTS_UNK_1 F1
#define CTRL_HS_POINTS_FISHING F2
#define CTRL_HS_TIME_BOAT_ARCHERY F3 // Note this interprets the highscore value as a time (not the actual timer for the minigame)
#define CTRL_HS_TIME_HORSE_BACK_BALLOON F4 // Note this interprets the highscore value as a time (not the actual timer for the minigame)
#define CTRL_HS_TIME_LOTTERY_GUESS F5
#define CTRL_HS_TOWN_SHOOTING_GALLERY F6
#define CTRL_HS_UNK_1 F7
#define CTRL_HS_UNK_3_LOWER F8
#define CTRL_HS_HORSE_BACK_BALLOON F9
#define CTRL_HS_DEKU_PLAYGROUND_DAY_1 FA
#define CTRL_HS_DEKU_PLAYGROUND_DAY_2 FB
#define CTRL_HS_DEKU_PLAYGROUND_DAY_3 FC
#define CTRL_DEKU_PLAYGROUND_NAME_DAY_1 FD
#define CTRL_DEKU_PLAYGROUND_NAME_DAY_2 FE
#define CTRL_DEKU_PLAYGROUND_NAME_DAY_3 FF
#define MESSAGE_COLOR_DEFAULT 0x00
#define MESSAGE_COLOR_RED 0x01
#define MESSAGE_COLOR_GREEN 0x02
#define MESSAGE_COLOR_BLUE 0x03
#define MESSAGE_COLOR_YELLOW 0x04
#define MESSAGE_COLOR_LIGHTBLUE 0x05
#define MESSAGE_COLOR_PINK 0x06
#define MESSAGE_COLOR_SILVER 0x07
#define MESSAGE_COLOR_ORANGE 0x08
#define MESSAGE_TEXT_SPEED 0x0A // Note this should take an arg 0 to 6, but always just sets next decode char as 0
#define MESSAGE_HS_BOAT_ARCHERY 0x0B
#define MESSAGE_STRAY_FAIRIES 0x0C
#define MESSAGE_TOKENS 0x0D
#define MESSAGE_POINTS_TENS 0x0E
#define MESSAGE_POINTS_THOUSANDS 0x0F
#define MESSAGE_BOX_BREAK 0x10
#define MESSAGE_NEWLINE 0x11
#define MESSAGE_BOX_BREAK2 0x12
#define MESSAGE_CARRIAGE_RETURN 0x13
#define MESSAGE_SHIFT 0x14
#define MESSAGE_CONTINUE 0x15
#define MESSAGE_NAME 0x16
#define MESSAGE_QUICKTEXT_ENABLE 0x17
#define MESSAGE_QUICKTEXT_DISABLE 0x18
#define MESSAGE_EVENT 0x19
#define MESSAGE_PERSISTENT 0x1A
#define MESSAGE_BOX_BREAK_DELAYED 0x1B
#define MESSAGE_FADE 0x1C
#define MESSAGE_FADE_SKIPPABLE 0x1D
#define MESSAGE_SFX 0x1E
#define MESSAGE_DELAY 0x1F
#define MESSAGE_BTN_A 0xB0
#define MESSAGE_BTN_B 0xB1
#define MESSAGE_BTN_C 0xB2
#define MESSAGE_BTN_L 0xB3
#define MESSAGE_BTN_R 0xB4
#define MESSAGE_BTN_Z 0xB5
#define MESSAGE_BTN_CUP 0xB6
#define MESSAGE_BTN_CDOWN 0xB7
#define MESSAGE_BTN_CLEFT 0xB8
#define MESSAGE_BTN_CRIGHT 0xB9
#define MESSAGE_Z_TARGET 0xBA
#define MESSAGE_CONTROL_PAD 0xBB
#define MESSAGE_END 0xBF
#define MESSAGE_BACKGROUND 0xC1
#define MESSAGE_TWO_CHOICE 0xC2
#define MESSAGE_THREE_CHOICE 0xC3
#define MESSAGE_TIMER_POSTMAN 0xC4
#define MESSAGE_TIMER_MINIGAME_1 0xC5
#define MESSAGE_TIMER_2 0xC6
#define MESSAGE_TIMER_MOON_CRASH 0xC7
#define MESSAGE_TIMER_MINIGAME_2 0xC8
#define MESSAGE_TIMER_ENV_HAZARD 0xC9
#define MESSAGE_TIME 0xCA
#define MESSAGE_CHEST_FLAGS 0xCB
#define MESSAGE_INPUT_BANK 0xCC
#define MESSAGE_RUPEES_SELECTED 0xCD
#define MESSAGE_RUPEES_TOTAL 0xCE
#define MESSAGE_TIME_UNTIL_MOON_CRASH 0xCF
#define MESSAGE_INPUT_DOGGY_RACETRACK_BET 0xD0
#define MESSAGE_INPUT_BOMBER_CODE 0xD1
#define MESSAGE_PAUSE_MENU 0xD2
#define MESSAGE_TIME_SPEED 0xD3
#define MESSAGE_OWL_WARP 0xD4
#define MESSAGE_INPUT_LOTTERY_CODE 0xD5
#define MESSAGE_SPIDER_HOUSE_MASK_CODE 0xD6
#define MESSAGE_STRAY_FAIRIES_LEFT_WOODFALL 0xD7
#define MESSAGE_STRAY_FAIRIES_LEFT_SNOWHEAD 0xD8
#define MESSAGE_STRAY_FAIRIES_LEFT_GREAT_BAY 0xD9
#define MESSAGE_STRAY_FAIRIES_LEFT_STONE_TOWER 0xDA
#define MESSAGE_POINTS_BOAT_ARCHERY 0xDB // Seems to be the exact same as CTRL_POINTS_THOUSANDS
#define MESSAGE_LOTTERY_CODE 0xDC
#define MESSAGE_LOTTERY_CODE_GUESS 0xDD
#define MESSAGE_HELD_ITEM_PRICE 0xDE
#define MESSAGE_BOMBER_CODE 0xDF
#define MESSAGE_EVENT2 0xE0 // Seems to be the exact same as CTRL_EVENT
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_1 0xE1
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_2 0xE2
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_3 0xE3
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_4 0xE4
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_5 0xE5
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_6 0xE6
#define MESSAGE_HOURS_UNTIL_MOON_CRASH 0xE7
#define MESSAGE_TIME_UNTIL_NEW_DAY 0xE8
#define MESSAGE_HS_POINTS_BANK_RUPEES 0xF0
#define MESSAGE_HS_POINTS_UNK_1 0xF1
#define MESSAGE_HS_POINTS_FISHING 0xF2
#define MESSAGE_HS_TIME_BOAT_ARCHERY 0xF3 // Note this interprets the highscore value as a time (not the actual timer for the minigame)
#define MESSAGE_HS_TIME_HORSE_BACK_BALLOON 0xF4 // Note this interprets the highscore value as a time (not the actual timer for the minigame)
#define MESSAGE_HS_TIME_LOTTERY_GUESS 0xF5
#define MESSAGE_HS_TOWN_SHOOTING_GALLERY 0xF6
#define MESSAGE_HS_UNK_1 0xF7
#define MESSAGE_HS_UNK_3_LOWER 0xF8
#define MESSAGE_HS_HORSE_BACK_BALLOON 0xF9
#define MESSAGE_HS_DEKU_PLAYGROUND_DAY_1 0xFA
#define MESSAGE_HS_DEKU_PLAYGROUND_DAY_2 0xFB
#define MESSAGE_HS_DEKU_PLAYGROUND_DAY_3 0xFC
#define MESSAGE_DEKU_PLAYGROUND_NAME_DAY_1 0xFD
#define MESSAGE_DEKU_PLAYGROUND_NAME_DAY_2 0xFE
#define MESSAGE_DEKU_PLAYGROUND_NAME_DAY_3 0xFF
#ifdef MESSAGE_DATA_STATIC
// For use in message_data_static files
#define ARG(x) x
#define ARG1(x) (x),
#define ARG2(x) (((x) >> 8) & 0xFF), (((x) >> 0) & 0xFF),
#define CTRL_BASE(name) ARG1(MESSAGE_##name)
#define CMD_COLOR_DEFAULT STR(CTRL_COLOR_DEFAULT)
#define CMD_COLOR_RED STR(CTRL_COLOR_RED)
#define CMD_COLOR_GREEN STR(CTRL_COLOR_GREEN)
#define CMD_COLOR_BLUE STR(CTRL_COLOR_BLUE)
#define CMD_COLOR_YELLOW STR(CTRL_COLOR_YELLOW)
#define CMD_COLOR_LIGHTBLUE STR(CTRL_COLOR_LIGHTBLUE)
#define CMD_COLOR_PINK STR(CTRL_COLOR_PINK)
#define CMD_COLOR_SILVER STR(CTRL_COLOR_SILVER)
#define CMD_COLOR_ORANGE STR(CTRL_COLOR_ORANGE)
#define CMD_TEXT_SPEED STR(CTRL_TEXT_SPEED)
#define CMD_HS_BOAT_ARCHERY STR(CTRL_HS_BOAT_ARCHERY)
#define CMD_STRAY_FAIRIES STR(CTRL_STRAY_FAIRIES)
#define CMD_TOKENS STR(CTRL_TOKENS)
#define CMD_POINTS_TENS STR(CTRL_POINTS_TENS)
#define CMD_POINTS_THOUSANDS STR(CTRL_POINTS_THOUSANDS)
#define CMD_BOX_BREAK STR(CTRL_BOX_BREAK)
// For use in message_data_static files
// Encoded text consists of an array of bytes. Since it's in a macro it must be wrapped in a varargs macro so that each
// byte is not treated as a separate macro argument to DEFINE_MESSAGE. IDO doesn't support varargs macros, however we
// preprocess the message_data_static files with modern cpp instead. See the makefile rule for assets/text/
#define MSG(...) \
{ __VA_ARGS__ END }
// Not a control command, used to define the data that begins every message
#define HEADER(unk11F08, itemId, nextTextId, unk1206C, unk12070, unk12074) \
ARG2(unk11F08) ARG1(itemId) ARG2(nextTextId) ARG2(unk1206C) ARG2(unk12070) ARG2(unk12074)
#define COLOR_DEFAULT CTRL_BASE(COLOR_DEFAULT)
#define COLOR_RED CTRL_BASE(COLOR_RED)
#define COLOR_GREEN CTRL_BASE(COLOR_GREEN)
#define COLOR_BLUE CTRL_BASE(COLOR_BLUE)
#define COLOR_YELLOW CTRL_BASE(COLOR_YELLOW)
#define COLOR_LIGHTBLUE CTRL_BASE(COLOR_LIGHTBLUE)
#define COLOR_PINK CTRL_BASE(COLOR_PINK)
#define COLOR_SILVER CTRL_BASE(COLOR_SILVER)
#define COLOR_ORANGE CTRL_BASE(COLOR_ORANGE)
#define TEXT_SPEED CTRL_BASE(TEXT_SPEED)
#define HS_BOAT_ARCHERY CTRL_BASE(HS_BOAT_ARCHERY)
#define STRAY_FAIRIES CTRL_BASE(STRAY_FAIRIES)
#define TOKENS CTRL_BASE(TOKENS)
#define POINTS_TENS CTRL_BASE(POINTS_TENS)
#define POINTS_THOUSANDS CTRL_BASE(POINTS_THOUSANDS)
#define BOX_BREAK CTRL_BASE(BOX_BREAK)
// while a control character, newlines are handled in the charmap conversion
// stage to allow normal newline \n usage in message_data_static files
#define CMD_NEWLINE STR(CTRL_NEWLINE)
#define CMD_BOX_BREAK2 STR(CTRL_BOX_BREAK2)
#define CMD_CARRIAGE_RETURN STR(CTRL_CARRIAGE_RETURN)
#define CMD_SHIFT(x) STR(CTRL_SHIFT) ARG(x) // 1
#define CMD_CONTINUE STR(CTRL_CONTINUE)
#define CMD_NAME STR(CTRL_NAME)
#define CMD_QUICKTEXT_ENABLE STR(CTRL_QUICKTEXT_ENABLE)
#define CMD_QUICKTEXT_DISABLE STR(CTRL_QUICKTEXT_DISABLE)
#define CMD_EVENT STR(CTRL_EVENT)
#define CMD_PERSISTENT STR(CTRL_PERSISTENT)
#define CMD_BOX_BREAK_DELAYED(x) STR(CTRL_BOX_BREAK_DELAYED) ARG(x) // 2
#define CMD_FADE(x) STR(CTRL_FADE) ARG(x) // 2
#define CMD_FADE_SKIPPABLE(x) STR(CTRL_FADE_SKIPPABLE) ARG(x) // 2
#define CMD_SFX(x) STR(CTRL_SFX) ARG(x) // 2
#define CMD_DELAY(x) STR(CTRL_DELAY) ARG(x) // 2
// while control characters, button images are handled in the charmap conversion
#define CMD_BTN_A STR(CTRL_BTN_A) // "[A]"
#define CMD_BTN_B STR(CTRL_BTN_B) // "[B]"
#define CMD_BTN_C STR(CTRL_BTN_C) // "[C]"
#define CMD_BTN_L STR(CTRL_BTN_L) // "[L]"
#define CMD_BTN_R STR(CTRL_BTN_R) // "[R]"
#define CMD_BTN_Z STR(CTRL_BTN_Z) // "[Z]"
#define CMD_BTN_CUP STR(CTRL_BTN_CUP) // "[C-Up]"
#define CMD_BTN_CDOWN STR(CTRL_BTN_CDOWN) // "[C-Down]"
#define CMD_BTN_CLEFT STR(CTRL_BTN_CLEFT) // "[C-Left]"
#define CMD_BTN_CRIGHT STR(CTRL_BTN_CRIGHT) // "[C-Right]"
#define CMD_Z_TARGET STR(CTRL_Z_TARGET) // "▼"
#define CMD_CONTROL_PAD STR(CTRL_CONTROL_PAD) // "[Control-Pad]"
#define CMD_END STR(CTRL_END)
#define CMD_BACKGROUND STR(CTRL_BACKGROUND)
#define CMD_TWO_CHOICE STR(CTRL_TWO_CHOICE)
#define CMD_THREE_CHOICE STR(CTRL_THREE_CHOICE)
#define CMD_TIMER_POSTMAN STR(CTRL_TIMER_POSTMAN)
#define CMD_TIMER_MINIGAME_1 STR(CTRL_TIMER_MINIGAME_1)
#define CMD_TIMER_2 STR(CTRL_TIMER_2)
#define CMD_TIMER_MOON_CRASH STR(CTRL_TIMER_MOON_CRASH)
#define CMD_TIMER_MINIGAME_2 STR(CTRL_TIMER_MINIGAME_2)
#define CMD_TIMER_ENV_HAZARD STR(CTRL_TIMER_ENV_HAZARD)
#define CMD_TIME STR(CTRL_TIME)
#define CMD_CHEST_FLAGS STR(CTRL_CHEST_FLAGS)
#define CMD_INPUT_BANK STR(CTRL_INPUT_BANK)
#define CMD_RUPEES_SELECTED STR(CTRL_RUPEES_SELECTED)
#define CMD_RUPEES_TOTAL STR(CTRL_RUPEES_TOTAL)
#define CMD_TIME_UNTIL_MOON_CRASH STR(CTRL_TIME_UNTIL_MOON_CRASH)
#define CMD_INPUT_DOGGY_RACETRACK_BET STR(CTRL_INPUT_DOGGY_RACETRACK_BET)
#define CMD_INPUT_BOMBER_CODE STR(CTRL_INPUT_BOMBER_CODE)
#define CMD_PAUSE_MENU STR(CTRL_PAUSE_MENU)
#define CMD_TIME_SPEED STR(CTRL_TIME_SPEED)
#define CMD_OWL_WARP STR(CTRL_OWL_WARP)
#define CMD_INPUT_LOTTERY_CODE STR(CTRL_INPUT_LOTTERY_CODE)
#define CMD_SPIDER_HOUSE_MASK_CODE STR(CTRL_SPIDER_HOUSE_MASK_CODE)
#define CMD_STRAY_FAIRIES_LEFT_WOODFALL STR(CTRL_STRAY_FAIRIES_LEFT_WOODFALL)
#define CMD_STRAY_FAIRIES_LEFT_SNOWHEAD STR(CTRL_STRAY_FAIRIES_LEFT_SNOWHEAD)
#define CMD_STRAY_FAIRIES_LEFT_GREAT_BAY STR(CTRL_STRAY_FAIRIES_LEFT_GREAT_BAY)
#define CMD_STRAY_FAIRIES_LEFT_STONE_TOWER STR(CTRL_STRAY_FAIRIES_LEFT_STONE_TOWER)
#define CMD_POINTS_BOAT_ARCHERY STR(CTRL_POINTS_BOAT_ARCHERY)
#define CMD_LOTTERY_CODE STR(CTRL_LOTTERY_CODE)
#define CMD_LOTTERY_CODE_GUESS STR(CTRL_LOTTERY_CODE_GUESS)
#define CMD_HELD_ITEM_PRICE STR(CTRL_HELD_ITEM_PRICE)
#define CMD_BOMBER_CODE STR(CTRL_BOMBER_CODE)
#define CMD_EVENT2 STR(CTRL_EVENT2)
#define CMD_SPIDER_HOUSE_MASK_CODE_1 STR(CTRL_SPIDER_HOUSE_MASK_CODE_1)
#define CMD_SPIDER_HOUSE_MASK_CODE_2 STR(CTRL_SPIDER_HOUSE_MASK_CODE_2)
#define CMD_SPIDER_HOUSE_MASK_CODE_3 STR(CTRL_SPIDER_HOUSE_MASK_CODE_3)
#define CMD_SPIDER_HOUSE_MASK_CODE_4 STR(CTRL_SPIDER_HOUSE_MASK_CODE_4)
#define CMD_SPIDER_HOUSE_MASK_CODE_5 STR(CTRL_SPIDER_HOUSE_MASK_CODE_5)
#define CMD_SPIDER_HOUSE_MASK_CODE_6 STR(CTRL_SPIDER_HOUSE_MASK_CODE_6)
#define CMD_HOURS_UNTIL_MOON_CRASH STR(CTRL_HOURS_UNTIL_MOON_CRASH)
#define CMD_TIME_UNTIL_NEW_DAY STR(CTRL_TIME_UNTIL_NEW_DAY)
#define CMD_HS_POINTS_BANK_RUPEES STR(CTRL_HS_POINTS_BANK_RUPEES)
#define CMD_HS_POINTS_UNK_1 STR(CTRL_HS_POINTS_UNK_1)
#define CMD_HS_POINTS_FISHING STR(CTRL_HS_POINTS_FISHING)
#define CMD_HS_TIME_BOAT_ARCHERY STR(CTRL_HS_TIME_BOAT_ARCHERY)
#define CMD_HS_TIME_HORSE_BACK_BALLOON STR(CTRL_HS_TIME_HORSE_BACK_BALLOON)
#define CMD_HS_TIME_LOTTERY_GUESS STR(CTRL_HS_TIME_LOTTERY_GUESS)
#define CMD_HS_TOWN_SHOOTING_GALLERY STR(CTRL_HS_TOWN_SHOOTING_GALLERY)
#define CMD_HS_UNK_1 STR(CTRL_HS_UNK_1)
#define CMD_HS_UNK_3_LOWER STR(CTRL_HS_UNK_3_LOWER)
#define CMD_HS_HORSE_BACK_BALLOON STR(CTRL_HS_HORSE_BACK_BALLOON)
#define CMD_HS_DEKU_PLAYGROUND_DAY_1 STR(CTRL_HS_DEKU_PLAYGROUND_DAY_1)
#define CMD_HS_DEKU_PLAYGROUND_DAY_2 STR(CTRL_HS_DEKU_PLAYGROUND_DAY_2)
#define CMD_HS_DEKU_PLAYGROUND_DAY_3 STR(CTRL_HS_DEKU_PLAYGROUND_DAY_3)
#define CMD_DEKU_PLAYGROUND_NAME_DAY_1 STR(CTRL_DEKU_PLAYGROUND_NAME_DAY_1)
#define CMD_DEKU_PLAYGROUND_NAME_DAY_2 STR(CTRL_DEKU_PLAYGROUND_NAME_DAY_2)
#define CMD_DEKU_PLAYGROUND_NAME_DAY_3 STR(CTRL_DEKU_PLAYGROUND_NAME_DAY_3)
#else
#define MESSAGE_COLOR_DEFAULT HEX(CTRL_COLOR_DEFAULT)
#define MESSAGE_COLOR_RED HEX(CTRL_COLOR_RED)
#define MESSAGE_COLOR_GREEN HEX(CTRL_COLOR_GREEN)
#define MESSAGE_COLOR_BLUE HEX(CTRL_COLOR_BLUE)
#define MESSAGE_COLOR_YELLOW HEX(CTRL_COLOR_YELLOW)
#define MESSAGE_COLOR_LIGHTBLUE HEX(CTRL_COLOR_LIGHTBLUE)
#define MESSAGE_COLOR_PINK HEX(CTRL_COLOR_PINK)
#define MESSAGE_COLOR_SILVER HEX(CTRL_COLOR_SILVER)
#define MESSAGE_COLOR_ORANGE HEX(CTRL_COLOR_ORANGE)
#define MESSAGE_TEXT_SPEED HEX(CTRL_TEXT_SPEED)
#define MESSAGE_HS_BOAT_ARCHERY HEX(CTRL_HS_BOAT_ARCHERY)
#define MESSAGE_STRAY_FAIRIES HEX(CTRL_STRAY_FAIRIES)
#define MESSAGE_TOKENS HEX(CTRL_TOKENS)
#define MESSAGE_POINTS_TENS HEX(CTRL_POINTS_TENS)
#define MESSAGE_POINTS_THOUSANDS HEX(CTRL_POINTS_THOUSANDS)
#define MESSAGE_BOX_BREAK HEX(CTRL_BOX_BREAK)
#define MESSAGE_NEWLINE HEX(CTRL_NEWLINE)
#define MESSAGE_BOX_BREAK2 HEX(CTRL_BOX_BREAK2)
#define MESSAGE_CARRIAGE_RETURN HEX(CTRL_CARRIAGE_RETURN)
#define MESSAGE_SHIFT HEX(CTRL_SHIFT)
#define MESSAGE_CONTINUE HEX(CTRL_CONTINUE)
#define MESSAGE_NAME HEX(CTRL_NAME)
#define MESSAGE_QUICKTEXT_ENABLE HEX(CTRL_QUICKTEXT_ENABLE)
#define MESSAGE_QUICKTEXT_DISABLE HEX(CTRL_QUICKTEXT_DISABLE)
#define MESSAGE_EVENT HEX(CTRL_EVENT)
#define MESSAGE_PERSISTENT HEX(CTRL_PERSISTENT)
#define MESSAGE_BOX_BREAK_DELAYED HEX(CTRL_BOX_BREAK_DELAYED)
#define MESSAGE_FADE HEX(CTRL_FADE)
#define MESSAGE_FADE_SKIPPABLE HEX(CTRL_FADE_SKIPPABLE)
#define MESSAGE_SFX HEX(CTRL_SFX)
#define MESSAGE_DELAY HEX(CTRL_DELAY)
#define MESSAGE_BTN_A HEX(CTRL_BTN_A)
#define MESSAGE_BTN_B HEX(CTRL_BTN_B)
#define MESSAGE_BTN_C HEX(CTRL_BTN_C)
#define MESSAGE_BTN_L HEX(CTRL_BTN_L)
#define MESSAGE_BTN_R HEX(CTRL_BTN_R)
#define MESSAGE_BTN_Z HEX(CTRL_BTN_Z)
#define MESSAGE_BTN_CUP HEX(CTRL_BTN_CUP)
#define MESSAGE_BTN_CDOWN HEX(CTRL_BTN_CDOWN)
#define MESSAGE_BTN_CLEFT HEX(CTRL_BTN_CLEFT)
#define MESSAGE_BTN_CRIGHT HEX(CTRL_BTN_CRIGHT)
#define MESSAGE_Z_TARGET HEX(CTRL_Z_TARGET)
#define MESSAGE_CONTROL_PAD HEX(CTRL_CONTROL_PAD)
#define MESSAGE_END HEX(CTRL_END)
#define MESSAGE_BACKGROUND HEX(CTRL_BACKGROUND)
#define MESSAGE_TWO_CHOICE HEX(CTRL_TWO_CHOICE)
#define MESSAGE_THREE_CHOICE HEX(CTRL_THREE_CHOICE)
#define MESSAGE_TIMER_POSTMAN HEX(CTRL_TIMER_POSTMAN)
#define MESSAGE_TIMER_MINIGAME_1 HEX(CTRL_TIMER_MINIGAME_1)
#define MESSAGE_TIMER_2 HEX(CTRL_TIMER_2)
#define MESSAGE_TIMER_MOON_CRASH HEX(CTRL_TIMER_MOON_CRASH)
#define MESSAGE_TIMER_MINIGAME_2 HEX(CTRL_TIMER_MINIGAME_2)
#define MESSAGE_TIMER_ENV_HAZARD HEX(CTRL_TIMER_ENV_HAZARD)
#define MESSAGE_TIME HEX(CTRL_TIME)
#define MESSAGE_CHEST_FLAGS HEX(CTRL_CHEST_FLAGS)
#define MESSAGE_INPUT_BANK HEX(CTRL_INPUT_BANK)
#define MESSAGE_RUPEES_SELECTED HEX(CTRL_RUPEES_SELECTED)
#define MESSAGE_RUPEES_TOTAL HEX(CTRL_RUPEES_TOTAL)
#define MESSAGE_TIME_UNTIL_MOON_CRASH HEX(CTRL_TIME_UNTIL_MOON_CRASH)
#define MESSAGE_INPUT_DOGGY_RACETRACK_BET HEX(CTRL_INPUT_DOGGY_RACETRACK_BET)
#define MESSAGE_INPUT_BOMBER_CODE HEX(CTRL_INPUT_BOMBER_CODE)
#define MESSAGE_PAUSE_MENU HEX(CTRL_PAUSE_MENU)
#define MESSAGE_TIME_SPEED HEX(CTRL_TIME_SPEED)
#define MESSAGE_OWL_WARP HEX(CTRL_OWL_WARP)
#define MESSAGE_INPUT_LOTTERY_CODE HEX(CTRL_INPUT_LOTTERY_CODE)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE HEX(CTRL_SPIDER_HOUSE_MASK_CODE)
#define MESSAGE_STRAY_FAIRIES_LEFT_WOODFALL HEX(CTRL_STRAY_FAIRIES_LEFT_WOODFALL)
#define MESSAGE_STRAY_FAIRIES_LEFT_SNOWHEAD HEX(CTRL_STRAY_FAIRIES_LEFT_SNOWHEAD)
#define MESSAGE_STRAY_FAIRIES_LEFT_GREAT_BAY HEX(CTRL_STRAY_FAIRIES_LEFT_GREAT_BAY)
#define MESSAGE_STRAY_FAIRIES_LEFT_STONE_TOWER HEX(CTRL_STRAY_FAIRIES_LEFT_STONE_TOWER)
#define MESSAGE_POINTS_BOAT_ARCHERY HEX(CTRL_POINTS_BOAT_ARCHERY)
#define MESSAGE_LOTTERY_CODE HEX(CTRL_LOTTERY_CODE)
#define MESSAGE_LOTTERY_CODE_GUESS HEX(CTRL_LOTTERY_CODE_GUESS)
#define MESSAGE_HELD_ITEM_PRICE HEX(CTRL_HELD_ITEM_PRICE)
#define MESSAGE_BOMBER_CODE HEX(CTRL_BOMBER_CODE)
#define MESSAGE_EVENT2 HEX(CTRL_EVENT2)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_1 HEX(CTRL_SPIDER_HOUSE_MASK_CODE_1)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_2 HEX(CTRL_SPIDER_HOUSE_MASK_CODE_2)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_3 HEX(CTRL_SPIDER_HOUSE_MASK_CODE_3)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_4 HEX(CTRL_SPIDER_HOUSE_MASK_CODE_4)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_5 HEX(CTRL_SPIDER_HOUSE_MASK_CODE_5)
#define MESSAGE_SPIDER_HOUSE_MASK_CODE_6 HEX(CTRL_SPIDER_HOUSE_MASK_CODE_6)
#define MESSAGE_HOURS_UNTIL_MOON_CRASH HEX(CTRL_HOURS_UNTIL_MOON_CRASH)
#define MESSAGE_TIME_UNTIL_NEW_DAY HEX(CTRL_TIME_UNTIL_NEW_DAY)
#define MESSAGE_HS_POINTS_BANK_RUPEES HEX(CTRL_HS_POINTS_BANK_RUPEES)
#define MESSAGE_HS_POINTS_UNK_1 HEX(CTRL_HS_POINTS_UNK_1)
#define MESSAGE_HS_POINTS_FISHING HEX(CTRL_HS_POINTS_FISHING)
#define MESSAGE_HS_TIME_BOAT_ARCHERY HEX(CTRL_HS_TIME_BOAT_ARCHERY)
#define MESSAGE_HS_TIME_HORSE_BACK_BALLOON HEX(CTRL_HS_TIME_HORSE_BACK_BALLOON)
#define MESSAGE_HS_TIME_LOTTERY_GUESS HEX(CTRL_HS_TIME_LOTTERY_GUESS)
#define MESSAGE_HS_TOWN_SHOOTING_GALLERY HEX(CTRL_HS_TOWN_SHOOTING_GALLERY)
#define MESSAGE_HS_UNK_1 HEX(CTRL_HS_UNK_1)
#define MESSAGE_HS_UNK_3_LOWER HEX(CTRL_HS_UNK_3_LOWER)
#define MESSAGE_HS_HORSE_BACK_BALLOON HEX(CTRL_HS_HORSE_BACK_BALLOON)
#define MESSAGE_HS_DEKU_PLAYGROUND_DAY_1 HEX(CTRL_HS_DEKU_PLAYGROUND_DAY_1)
#define MESSAGE_HS_DEKU_PLAYGROUND_DAY_2 HEX(CTRL_HS_DEKU_PLAYGROUND_DAY_2)
#define MESSAGE_HS_DEKU_PLAYGROUND_DAY_3 HEX(CTRL_HS_DEKU_PLAYGROUND_DAY_3)
#define MESSAGE_DEKU_PLAYGROUND_NAME_DAY_1 HEX(CTRL_DEKU_PLAYGROUND_NAME_DAY_1)
#define MESSAGE_DEKU_PLAYGROUND_NAME_DAY_2 HEX(CTRL_DEKU_PLAYGROUND_NAME_DAY_2)
#define MESSAGE_DEKU_PLAYGROUND_NAME_DAY_3 HEX(CTRL_DEKU_PLAYGROUND_NAME_DAY_3)
#define NEWLINE CTRL_BASE(NEWLINE)
#define BOX_BREAK2 CTRL_BASE(BOX_BREAK2)
#define CARRIAGE_RETURN CTRL_BASE(CARRIAGE_RETURN)
#define SHIFT(amount) CTRL_BASE(SHIFT) ARG1(amount)
#define CONTINUE CTRL_BASE(CONTINUE)
#define NAME CTRL_BASE(NAME)
#define QUICKTEXT_ENABLE CTRL_BASE(QUICKTEXT_ENABLE)
#define QUICKTEXT_DISABLE CTRL_BASE(QUICKTEXT_DISABLE)
#define EVENT CTRL_BASE(EVENT)
#define PERSISTENT CTRL_BASE(PERSISTENT)
#define BOX_BREAK_DELAYED(delay) CTRL_BASE(BOX_BREAK_DELAYED) ARG2(delay)
#define FADE(delay) CTRL_BASE(FADE) ARG2(delay)
#define FADE_SKIPPABLE(delay) CTRL_BASE(FADE_SKIPPABLE) ARG2(delay)
#define SFX(sfxId) CTRL_BASE(SFX) ARG2(sfxId)
#define DELAY(delay) CTRL_BASE(DELAY) ARG2(delay)
#define END CTRL_BASE(END)
#define BACKGROUND CTRL_BASE(BACKGROUND)
#define TWO_CHOICE CTRL_BASE(TWO_CHOICE)
#define THREE_CHOICE CTRL_BASE(THREE_CHOICE)
#define TIMER_POSTMAN CTRL_BASE(TIMER_POSTMAN)
#define TIMER_MINIGAME_1 CTRL_BASE(TIMER_MINIGAME_1)
#define TIMER_2 CTRL_BASE(TIMER_2)
#define TIMER_MOON_CRASH CTRL_BASE(TIMER_MOON_CRASH)
#define TIMER_MINIGAME_2 CTRL_BASE(TIMER_MINIGAME_2)
#define TIMER_ENV_HAZARD CTRL_BASE(TIMER_ENV_HAZARD)
#define TIME CTRL_BASE(TIME)
#define CHEST_FLAGS CTRL_BASE(CHEST_FLAGS)
#define INPUT_BANK CTRL_BASE(INPUT_BANK)
#define RUPEES_SELECTED CTRL_BASE(RUPEES_SELECTED)
#define RUPEES_TOTAL CTRL_BASE(RUPEES_TOTAL)
#define TIME_UNTIL_MOON_CRASH CTRL_BASE(TIME_UNTIL_MOON_CRASH)
#define INPUT_DOGGY_RACETRACK_BET CTRL_BASE(INPUT_DOGGY_RACETRACK_BET)
#define INPUT_BOMBER_CODE CTRL_BASE(INPUT_BOMBER_CODE)
#define PAUSE_MENU CTRL_BASE(PAUSE_MENU)
#define TIME_SPEED CTRL_BASE(TIME_SPEED)
#define OWL_WARP CTRL_BASE(OWL_WARP)
#define INPUT_LOTTERY_CODE CTRL_BASE(INPUT_LOTTERY_CODE)
#define SPIDER_HOUSE_MASK_CODE CTRL_BASE(SPIDER_HOUSE_MASK_CODE)
#define STRAY_FAIRIES_LEFT_WOODFALL CTRL_BASE(STRAY_FAIRIES_LEFT_WOODFALL)
#define STRAY_FAIRIES_LEFT_SNOWHEAD CTRL_BASE(STRAY_FAIRIES_LEFT_SNOWHEAD)
#define STRAY_FAIRIES_LEFT_GREAT_BAY CTRL_BASE(STRAY_FAIRIES_LEFT_GREAT_BAY)
#define STRAY_FAIRIES_LEFT_STONE_TOWER CTRL_BASE(STRAY_FAIRIES_LEFT_STONE_TOWER)
#define POINTS_BOAT_ARCHERY CTRL_BASE(POINTS_BOAT_ARCHERY)
#define LOTTERY_CODE CTRL_BASE(LOTTERY_CODE)
#define LOTTERY_CODE_GUESS CTRL_BASE(LOTTERY_CODE_GUESS)
#define HELD_ITEM_PRICE CTRL_BASE(HELD_ITEM_PRICE)
#define BOMBER_CODE CTRL_BASE(BOMBER_CODE)
#define EVENT2 CTRL_BASE(EVENT2)
#define SPIDER_HOUSE_MASK_CODE_1 CTRL_BASE(SPIDER_HOUSE_MASK_CODE_1)
#define SPIDER_HOUSE_MASK_CODE_2 CTRL_BASE(SPIDER_HOUSE_MASK_CODE_2)
#define SPIDER_HOUSE_MASK_CODE_3 CTRL_BASE(SPIDER_HOUSE_MASK_CODE_3)
#define SPIDER_HOUSE_MASK_CODE_4 CTRL_BASE(SPIDER_HOUSE_MASK_CODE_4)
#define SPIDER_HOUSE_MASK_CODE_5 CTRL_BASE(SPIDER_HOUSE_MASK_CODE_5)
#define SPIDER_HOUSE_MASK_CODE_6 CTRL_BASE(SPIDER_HOUSE_MASK_CODE_6)
#define HOURS_UNTIL_MOON_CRASH CTRL_BASE(HOURS_UNTIL_MOON_CRASH)
#define TIME_UNTIL_NEW_DAY CTRL_BASE(TIME_UNTIL_NEW_DAY)
#define HS_POINTS_BANK_RUPEES CTRL_BASE(HS_POINTS_BANK_RUPEES)
#define HS_POINTS_UNK_1 CTRL_BASE(HS_POINTS_UNK_1)
#define HS_POINTS_FISHING CTRL_BASE(HS_POINTS_FISHING)
#define HS_TIME_BOAT_ARCHERY CTRL_BASE(HS_TIME_BOAT_ARCHERY)
#define HS_TIME_HORSE_BACK_BALLOON CTRL_BASE(HS_TIME_HORSE_BACK_BALLOON)
#define HS_TIME_LOTTERY_GUESS CTRL_BASE(HS_TIME_LOTTERY_GUESS)
#define HS_TOWN_SHOOTING_GALLERY CTRL_BASE(HS_TOWN_SHOOTING_GALLERY)
#define HS_UNK_1 CTRL_BASE(HS_UNK_1)
#define HS_UNK_3_LOWER CTRL_BASE(HS_UNK_3_LOWER)
#define HS_HORSE_BACK_BALLOON CTRL_BASE(HS_HORSE_BACK_BALLOON)
#define HS_DEKU_PLAYGROUND_DAY_1 CTRL_BASE(HS_DEKU_PLAYGROUND_DAY_1)
#define HS_DEKU_PLAYGROUND_DAY_2 CTRL_BASE(HS_DEKU_PLAYGROUND_DAY_2)
#define HS_DEKU_PLAYGROUND_DAY_3 CTRL_BASE(HS_DEKU_PLAYGROUND_DAY_3)
#define DEKU_PLAYGROUND_NAME_DAY_1 CTRL_BASE(DEKU_PLAYGROUND_NAME_DAY_1)
#define DEKU_PLAYGROUND_NAME_DAY_2 CTRL_BASE(DEKU_PLAYGROUND_NAME_DAY_2)
#define DEKU_PLAYGROUND_NAME_DAY_3 CTRL_BASE(DEKU_PLAYGROUND_NAME_DAY_3)
#endif

View File

@ -1,180 +1,102 @@
#ifndef MESSAGE_DATA_FMT_STAFF_H
#define MESSAGE_DATA_FMT_STAFF_H
/*
* Macros to create both a constant and a string literal from a magic value
* The constants are used in code files when parsing text for various purposes
* The strings are used in the message_data_static files themselves, as you can only concat strings with other strings
*/
#ifndef GLUE
#define GLUE(a, b) a##b
#endif
#define STRINGIFY(s) #s
#define EXPAND_AND_STRINGIFY(s) STRINGIFY(s)
#define HEX(N) GLUE(0x, N)
#define STR(N) EXPAND_AND_STRINGIFY(GLUE(\x, N))
/*
* Text control characters
*/
// Control character magic values, in 2-digit hex without prefix
#define MESSAGE_NEWLINE 0x01
#define MESSAGE_END 0x02
#define MESSAGE_BOX_BREAK 0x04
#define MESSAGE_COLOR 0x05
#define MESSAGE_SHIFT 0x06
#define MESSAGE_TEXTID 0x07
#define MESSAGE_QUICKTEXT_ENABLE 0x08
#define MESSAGE_QUICKTEXT_DISABLE 0x09
#define MESSAGE_PERSISTENT 0x0A
#define MESSAGE_EVENT 0x0B
#define MESSAGE_BOX_BREAK_DELAYED 0x0C
#define MESSAGE_AWAIT_BUTTON_PRESS 0x0D
#define MESSAGE_FADE 0x0E
#define MESSAGE_NAME 0x0F
#define MESSAGE_OCARINA 0x10
#define MESSAGE_FADE2 0x11
#define MESSAGE_SFX 0x12
#define MESSAGE_ITEM_ICON 0x13
#define MESSAGE_TEXT_SPEED 0x14
#define MESSAGE_BACKGROUND 0x15
#define MESSAGE_MARATHON_TIME 0x16
#define MESSAGE_RACE_TIME 0x17
#define MESSAGE_POINTS 0x18
#define MESSAGE_TOKENS 0x19
#define MESSAGE_UNSKIPPABLE 0x1A
#define MESSAGE_TWO_CHOICE 0x1B
#define MESSAGE_THREE_CHOICE 0x1C
#define MESSAGE_FISH_INFO 0x1D
#define MESSAGE_HIGHSCORE 0x1E
#define MESSAGE_TIME 0x1F
#define CTRL_NEWLINE 01
#define CTRL_END 02
#define CTRL_BOX_BREAK 04
#define CTRL_COLOR 05
#define CTRL_SHIFT 06
#define CTRL_TEXTID 07
#define CTRL_QUICKTEXT_ENABLE 08
#define CTRL_QUICKTEXT_DISABLE 09
#define CTRL_PERSISTENT 0A
#define CTRL_EVENT 0B
#define CTRL_BOX_BREAK_DELAYED 0C
#define CTRL_AWAIT_BUTTON_PRESS 0D
#define CTRL_FADE 0E
#define CTRL_NAME 0F
#define CTRL_OCARINA 10
#define CTRL_FADE2 11
#define CTRL_SFX 12
#define CTRL_ITEM_ICON 13
#define CTRL_TEXT_SPEED 14
#define CTRL_BACKGROUND 15
#define CTRL_MARATHON_TIME 16
#define CTRL_RACE_TIME 17
#define CTRL_POINTS 18
#define CTRL_TOKENS 19
#define CTRL_UNSKIPPABLE 1A
#define CTRL_TWO_CHOICE 1B
#define CTRL_THREE_CHOICE 1C
#define CTRL_FISH_INFO 1D
#define CTRL_HIGHSCORE 1E
#define CTRL_TIME 1F
/*
* Colors
*/
#define COLOR_STR(N) EXPAND_AND_STRINGIFY(GLUE(\x4, N))
// Color magic values, in single-digit hex without prefix
#define CTRL_DEFAULT 0
#define CTRL_RED 1
#define CTRL_ADJUSTABLE 2
#define CTRL_BLUE 3
#define CTRL_LIGHTBLUE 4
#define CTRL_PURPLE 5
#define CTRL_YELLOW 6
#define CTRL_BLACK 7
typedef enum TextColor {
/* 0 */ TEXT_COLOR_DEFAULT,
/* 1 */ TEXT_COLOR_RED,
/* 2 */ TEXT_COLOR_ADJUSTABLE,
/* 3 */ TEXT_COLOR_BLUE,
/* 4 */ TEXT_COLOR_LIGHTBLUE,
/* 5 */ TEXT_COLOR_PURPLE,
/* 6 */ TEXT_COLOR_YELLOW,
/* 7 */ TEXT_COLOR_BLACK
} TextColor;
#ifdef MESSAGE_DATA_STATIC
// For use in message_data_static files
#define ARG(x) x
// Encoded text consists of an array of bytes. Since it's in a macro it must be wrapped in a varargs macro so that each
// byte is not treated as a separate macro argument to DEFINE_MESSAGE. IDO doesn't support varargs macros, however we
// preprocess the message_data_static files with modern cpp instead. See the makefile rule for assets/text/
#define MSG(...) \
{ __VA_ARGS__ END }
#define ARG1(x) (x),
#define ARG2(x) (((x) >> 8) & 0xFF), (((x) >> 0) & 0xFF),
#define ARGC(x) (0x40 | (TEXT_COLOR_##x)),
#define CTRL_BASE(name) ARG1(MESSAGE_##name)
#define ARGB1(x) ARG1(TEXTBOX_BG_##x)
#define ARGB2(a, b, c, d) \
(((TEXTBOX_BG_FGCOL_##a) << 4) | ((TEXTBOX_BG_BGCOL_##b) << 0)), (((TEXTBOX_BG_Y_OFFSET_##c) << 4) | ((d) << 0)),
// while a control character, newlines are handled in the charmap conversion
// stage to allow normal newline \n usage in message_data_static files
#define CMD_NEWLINE STR(CTRL_NEWLINE)
#define CMD_END STR(CTRL_END)
#define CMD_BOX_BREAK STR(CTRL_BOX_BREAK)
#define CMD_COLOR(x) STR(CTRL_COLOR) ARG(x) // 1
#define CMD_SHIFT(x) STR(CTRL_SHIFT) ARG(x) // 1
#define CMD_TEXTID(x) STR(CTRL_TEXTID) ARG(x) // 2
#define CMD_QUICKTEXT_ENABLE STR(CTRL_QUICKTEXT_ENABLE)
#define CMD_QUICKTEXT_DISABLE STR(CTRL_QUICKTEXT_DISABLE)
#define CMD_PERSISTENT STR(CTRL_PERSISTENT)
#define CMD_EVENT STR(CTRL_EVENT)
#define CMD_BOX_BREAK_DELAYED(x) STR(CTRL_BOX_BREAK_DELAYED) ARG(x) // 1
#define CMD_AWAIT_BUTTON_PRESS STR(CTRL_AWAIT_BUTTON_PRESS)
#define CMD_FADE(x) STR(CTRL_FADE) ARG(x) // 1
#define CMD_NAME STR(CTRL_NAME)
#define CMD_OCARINA STR(CTRL_OCARINA)
#define CMD_FADE2(x) STR(CTRL_FADE2) ARG(x) // 2
#define CMD_SFX(x) STR(CTRL_SFX) ARG(x) // 2
#define CMD_ITEM_ICON(x) STR(CTRL_ITEM_ICON) ARG(x) // 1
#define CMD_TEXT_SPEED(x) STR(CTRL_TEXT_SPEED) ARG(x) // 1
#define CMD_BACKGROUND(x,y,z) STR(CTRL_BACKGROUND) ARG(x) ARG(y) ARG(z)
#define CMD_MARATHON_TIME STR(CTRL_MARATHON_TIME)
#define CMD_RACE_TIME STR(CTRL_RACE_TIME)
#define CMD_POINTS STR(CTRL_POINTS)
#define CMD_TOKENS STR(CTRL_TOKENS)
#define CMD_UNSKIPPABLE STR(CTRL_UNSKIPPABLE)
#define CMD_TWO_CHOICE STR(CTRL_TWO_CHOICE)
#define CMD_THREE_CHOICE STR(CTRL_THREE_CHOICE)
#define CMD_FISH_INFO STR(CTRL_FISH_INFO)
#define CMD_HIGHSCORE(x) STR(CTRL_HIGHSCORE) ARG(x) // 1
#define CMD_TIME STR(CTRL_TIME)
#define NEWLINE CTRL_BASE(NEWLINE)
#define END CTRL_BASE(END)
#define BOX_BREAK CTRL_BASE(BOX_BREAK)
#define COLOR(color) CTRL_BASE(COLOR) ARGC(color)
#define SHIFT(amount) CTRL_BASE(SHIFT) ARG1(amount)
#define TEXTID(textId) CTRL_BASE(TEXTID) ARG2(textId)
#define QUICKTEXT_ENABLE CTRL_BASE(QUICKTEXT_ENABLE)
#define QUICKTEXT_DISABLE CTRL_BASE(QUICKTEXT_DISABLE)
#define PERSISTENT CTRL_BASE(PERSISTENT)
#define EVENT CTRL_BASE(EVENT)
#define BOX_BREAK_DELAYED(delay) CTRL_BASE(BOX_BREAK_DELAYED) ARG1(delay)
#define AWAIT_BUTTON_PRESS CTRL_BASE(AWAIT_BUTTON_PRESS)
#define FADE(delay) CTRL_BASE(FADE) ARG1(delay)
#define NAME CTRL_BASE(NAME)
#define OCARINA CTRL_BASE(OCARINA)
#define FADE2(delay) CTRL_BASE(FADE2) ARG2(delay)
#define SFX(sfxId) CTRL_BASE(SFX) ARG2(sfxId)
#define ITEM_ICON(itemId) CTRL_BASE(ITEM_ICON) ARG1(itemId)
#define TEXT_SPEED(amount) CTRL_BASE(TEXT_SPEED) ARG1(amount)
#define BACKGROUND(bgIdx, fgColor, bgColor, yOffset, unk) \
CTRL_BASE(BACKGROUND) ARGB1(bgIdx) ARGB2(fgColor, bgColor, yOffset, unk)
#define MARATHON_TIME CTRL_BASE(MARATHON_TIME)
#define RACE_TIME CTRL_BASE(RACE_TIME)
#define POINTS CTRL_BASE(POINTS)
#define TOKENS CTRL_BASE(TOKENS)
#define UNSKIPPABLE CTRL_BASE(UNSKIPPABLE)
#define TWO_CHOICE CTRL_BASE(TWO_CHOICE)
#define THREE_CHOICE CTRL_BASE(THREE_CHOICE)
#define FISH_INFO CTRL_BASE(FISH_INFO)
#define HIGHSCORE(highscore) CTRL_BASE(HIGHSCORE) ARG1(x)
#define TIME CTRL_BASE(TIME)
/*
* Highscore values as strings, for code references the HighScores
* enum should be used.
*/
#define HS_BANK_RUPEES "\x00"
#define HS_UNK_1 "\x01"
#define HS_FISHING "\x02"
#define HS_BOAT_ARCHERY "\x03"
#define HS_HORSE_BACK_BALLOON "\x04"
#define HS_SHOOTING_GALLERY "\x06"
/*
* Color values as strings
*/
#define DEFAULT COLOR_STR(CTRL_DEFAULT)
#define RED COLOR_STR(CTRL_RED)
#define ADJUSTABLE COLOR_STR(CTRL_ADJUSTABLE)
#define BLUE COLOR_STR(CTRL_BLUE)
#define LIGHTBLUE COLOR_STR(CTRL_LIGHTBLUE)
#define PURPLE COLOR_STR(CTRL_PURPLE)
#define YELLOW COLOR_STR(CTRL_YELLOW)
#define BLACK COLOR_STR(CTRL_BLACK)
#else
// For use in code files
#define MSGCOL_DEFAULT HEX(CTRL_DEFAULT)
#define MSGCOL_RED HEX(CTRL_RED)
#define MSGCOL_ADJUSTABLE HEX(CTRL_ADJUSTABLE)
#define MSGCOL_BLUE HEX(CTRL_BLUE)
#define MSGCOL_LIGHTBLUE HEX(CTRL_LIGHTBLUE)
#define MSGCOL_PURPLE HEX(CTRL_PURPLE)
#define MSGCOL_YELLOW HEX(CTRL_YELLOW)
#define MSGCOL_BLACK HEX(CTRL_BLACK)
#define MESSAGE_NEWLINE HEX(CTRL_NEWLINE)
#define MESSAGE_END HEX(CTRL_END)
#define MESSAGE_BOX_BREAK HEX(CTRL_BOX_BREAK)
#define MESSAGE_COLOR HEX(CTRL_COLOR)
#define MESSAGE_SHIFT HEX(CTRL_SHIFT)
#define MESSAGE_TEXTID HEX(CTRL_TEXTID)
#define MESSAGE_QUICKTEXT_ENABLE HEX(CTRL_QUICKTEXT_ENABLE)
#define MESSAGE_QUICKTEXT_DISABLE HEX(CTRL_QUICKTEXT_DISABLE)
#define MESSAGE_PERSISTENT HEX(CTRL_PERSISTENT)
#define MESSAGE_EVENT HEX(CTRL_EVENT)
#define MESSAGE_BOX_BREAK_DELAYED HEX(CTRL_BOX_BREAK_DELAYED)
#define MESSAGE_AWAIT_BUTTON_PRESS HEX(CTRL_AWAIT_BUTTON_PRESS)
#define MESSAGE_FADE HEX(CTRL_FADE)
#define MESSAGE_NAME HEX(CTRL_NAME)
#define MESSAGE_OCARINA HEX(CTRL_OCARINA)
#define MESSAGE_FADE2 HEX(CTRL_FADE2)
#define MESSAGE_SFX HEX(CTRL_SFX)
#define MESSAGE_ITEM_ICON HEX(CTRL_ITEM_ICON)
#define MESSAGE_TEXT_SPEED HEX(CTRL_TEXT_SPEED)
#define MESSAGE_BACKGROUND HEX(CTRL_BACKGROUND)
#define MESSAGE_MARATHON_TIME HEX(CTRL_MARATHON_TIME)
#define MESSAGE_RACE_TIME HEX(CTRL_RACE_TIME)
#define MESSAGE_POINTS HEX(CTRL_POINTS)
#define MESSAGE_TOKENS HEX(CTRL_TOKENS)
#define MESSAGE_UNSKIPPABLE HEX(CTRL_UNSKIPPABLE)
#define MESSAGE_TWO_CHOICE HEX(CTRL_TWO_CHOICE)
#define MESSAGE_THREE_CHOICE HEX(CTRL_THREE_CHOICE)
#define MESSAGE_FISH_INFO HEX(CTRL_FISH_INFO)
#define MESSAGE_HIGHSCORE HEX(CTRL_HIGHSCORE)
#define MESSAGE_TIME HEX(CTRL_TIME)
#endif
#endif

View File

@ -9,17 +9,17 @@ typedef struct MessageTableEntry {
/* 0x4 */ const char* segment;
} MessageTableEntry; // size = 0x8;
#define DEFINE_MESSAGE(textId, typePos, msg) \
#define DEFINE_MESSAGE(textId, type, yPos, msg) \
extern const char _message_##textId[];
#include "assets/text/message_data.h"
#undef DEFINE_MESSAGE
#define DEFINE_MESSAGE(textId, typePos, msg) \
#define DEFINE_MESSAGE(textId, type, yPos, msg) \
extern const char _message_##textId##_staff[];
#include "assets/text/staff_message_data.h"
#include "assets/text/message_data_staff.h"
#undef DEFINE_MESSAGE

View File

@ -55,7 +55,7 @@ u16 gBombersNotebookWeekEventFlags[BOMBERS_NOTEBOOK_EVENT_MAX] = {
#undef DEFINE_PERSON
#undef DEFINE_EVENT
#define DEFINE_MESSAGE(textId, typePos, msg) { textId, typePos, _message_##textId },
#define DEFINE_MESSAGE(textId, type, yPos, msg) { textId, (((type)&0xF) << 4) | ((yPos)&0xF), _message_##textId },
MessageTableEntry sMessageTableNES[] = {
#include "assets/text/message_data.h"
@ -64,10 +64,11 @@ MessageTableEntry sMessageTableNES[] = {
#undef DEFINE_MESSAGE
#define DEFINE_MESSAGE(textId, typePos, msg) { textId, typePos, _message_##textId##_staff },
#define DEFINE_MESSAGE(textId, type, yPos, msg) \
{ textId, (((type)&0xF) << 4) | ((yPos)&0xF), _message_##textId##_staff },
MessageTableEntry sMessageTableCredits[] = {
#include "assets/text/staff_message_data.h"
#include "assets/text/message_data_staff.h"
{ 0xFFFF, 0, NULL },
};

View File

@ -133,42 +133,19 @@ def main():
with extractedAssetsFile.open(encoding='utf-8') as f:
extractedAssetsTracker.update(json.load(f, object_hook=manager.dict))
extract_text_path = outputDir / "text/message_data.h"
extract_staff_text_path = outputDir / "text/staff_message_data.h"
asset_path = args.single
if asset_path is not None:
if "text/" in asset_path:
from msg.nes import msgdisNES
print("Extracting message_data")
msgdisNES.main(extract_text_path)
fullPath = os.path.join("assets", "xml", asset_path + ".xml")
if not os.path.exists(fullPath):
print(f"Error. File {fullPath} does not exist.", file=os.sys.stderr)
exit(1)
from msg.staff import msgdisStaff
print("Extracting staff_message_data")
msgdisStaff.main(extract_staff_text_path)
else:
fullPath = os.path.join("assets", "xml", asset_path + ".xml")
if not os.path.exists(fullPath):
print(f"Error. File {fullPath} does not exist.", file=os.sys.stderr)
exit(1)
initializeWorker(mainAbort, args.unaccounted, extractedAssetsTracker, manager, baseromSegmentsDir, outputDir)
# Always extract if -s is used.
if fullPath in extractedAssetsTracker:
del extractedAssetsTracker[fullPath]
ExtractFunc(fullPath)
initializeWorker(mainAbort, args.unaccounted, extractedAssetsTracker, manager, baseromSegmentsDir, outputDir)
# Always extract if -s is used.
if fullPath in extractedAssetsTracker:
del extractedAssetsTracker[fullPath]
ExtractFunc(fullPath)
else:
# Only extract text if the header does not already exist, or if --force was passed
if args.force or not os.path.isfile(extract_text_path):
from msg.nes import msgdisNES
print("Extracting message_data")
msgdisNES.main(baseromSegmentsDir, extract_text_path)
if args.force or not os.path.isfile(extract_staff_text_path):
from msg.staff import msgdisStaff
print("Extracting staff_message_data")
msgdisStaff.main(baseromSegmentsDir, extract_staff_text_path)
xmlFiles = []
for currentPath, _, files in os.walk(os.path.join("assets", "xml")):
for file in files:

View File

@ -1,322 +0,0 @@
#!/usr/bin/env python3
import argparse
from pathlib import Path
import sys
import struct
class MessageHeaderNES:
def __init__(self, unk11F08, itemId, nextTextId, unk1206C, unk12070, unk12074):
self.unk11F08 = unk11F08
self.itemId = itemId
self.nextTextId = nextTextId
self.unk1206C = unk1206C
self.unk12070 = unk12070
self.unk12074 = unk12074
def __str__(self):
return (
f'unk11F08: 0x{self.unk11F08:04X},\n'
f'itemId: 0x{self.itemId:02X},\n'
f'nextTextId: 0x{self.nextTextId:04X},\n'
f'unk1206C: 0x{self.unk1206C:04X},\n'
f'unk12070: 0x{self.unk12070:04X},\n'
f'unk12074: 0x{self.unk12074:04X}'
)
def macro(self):
unk11F08 = struct.pack(">H", self.unk11F08)
itemId = struct.pack(">B", self.itemId)
nextTextId = struct.pack(">H", self.nextTextId)
unk1206C = struct.pack(">H", self.unk1206C)
unk12070 = struct.pack(">H", self.unk12070)
unk12074 = struct.pack(">H", self.unk12074)
return (
f'"\\x{unk11F08[0]:02X}\\x{unk11F08[1]:02X}" '
f'"\\x{itemId[0]:02X}" '
f'"\\x{nextTextId[0]:02X}\\x{nextTextId[1]:02X}" '
f'"\\x{unk1206C[0]:02X}\\x{unk1206C[1]:02X}" '
f'"\\x{unk12070[0]:02X}\\x{unk12070[1]:02X}" '
f'"\\x{unk12074[0]:02X}\\x{unk12074[1]:02X}"'
)
class MessageNES:
BUTTONMAP = {
0xB0: '[A]',
0xB1: '[B]',
0xB2: '[C]',
0xB3: '[L]',
0xB4: '[R]',
0xB5: '[Z]',
0xB6: '[C-Up]',
0xB7: '[C-Down]',
0xB8: '[C-Left]',
0xB9: '[C-Right]',
0xBA: '',
0xBB: '[Control-Pad]'
}
def __init__(self, id, typePos, addr, hdr, text):
self.id = id
self.typePos = typePos
self.addr = addr
self.hdr = hdr
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'{str(self.hdr)}\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}, {self.hdr.macro()}\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(self):
CMDMAP = {
0x00: ("CMD_COLOR_DEFAULT", self.decode_cmd_arg_none),
0x01: ("CMD_COLOR_RED", self.decode_cmd_arg_none),
0x02: ("CMD_COLOR_GREEN", self.decode_cmd_arg_none),
0x03: ("CMD_COLOR_BLUE", self.decode_cmd_arg_none),
0x04: ("CMD_COLOR_YELLOW", self.decode_cmd_arg_none),
0x05: ("CMD_COLOR_LIGHTBLUE", self.decode_cmd_arg_none),
0X06: ("CMD_COLOR_PINK", self.decode_cmd_arg_none),
0x07: ("CMD_COLOR_SILVER", self.decode_cmd_arg_none),
0x08: ("CMD_COLOR_ORANGE", self.decode_cmd_arg_none),
0x0A: ("CMD_TEXT_SPEED", self.decode_cmd_arg_none),
0x0B: ("CMD_HS_BOAT_ARCHERY", self.decode_cmd_arg_none),
0x0C: ("CMD_STRAY_FAIRIES", self.decode_cmd_arg_none),
0x0D: ("CMD_TOKENS", self.decode_cmd_arg_none),
0x0E: ("CMD_POINTS_TENS", self.decode_cmd_arg_none),
0x0F: ("CMD_POINTS_THOUSANDS", self.decode_cmd_arg_none),
0x10: ("CMD_BOX_BREAK", self.decode_cmd_arg_none),
0x12: ("CMD_BOX_BREAK2", self.decode_cmd_arg_none),
0x13: ("CMD_CARRIAGE_RETURN", self.decode_cmd_arg_none),
0x14: ("CMD_SHIFT", self.decode_cmd_arg_1byte),
0x15: ("CMD_CONTINUE", self.decode_cmd_arg_none),
0x16: ("CMD_NAME", self.decode_cmd_arg_none),
0x17: ("CMD_QUICKTEXT_ENABLE", self.decode_cmd_arg_none),
0x18: ("CMD_QUICKTEXT_DISABLE", self.decode_cmd_arg_none),
0x19: ("CMD_EVENT", self.decode_cmd_arg_none),
0x1A: ("CMD_PERSISTENT", self.decode_cmd_arg_none),
0x1B: ("CMD_BOX_BREAK_DELAYED", self.decode_cmd_arg_2byte),
0x1C: ("CMD_FADE", self.decode_cmd_arg_2byte),
0x1D: ("CMD_FADE_SKIPPABLE", self.decode_cmd_arg_2byte),
0x1E: ("CMD_SFX", self.decode_cmd_arg_2byte),
0x1F: ("CMD_DELAY", self.decode_cmd_arg_2byte),
0xC1: ("CMD_BACKGROUND", self.decode_cmd_arg_none),
0xC2: ("CMD_TWO_CHOICE", self.decode_cmd_arg_none),
0xC3: ("CMD_THREE_CHOICE", self.decode_cmd_arg_none),
0xC4: ("CMD_TIMER_POSTMAN", self.decode_cmd_arg_none),
0xC5: ("CMD_TIMER_MINIGAME_1", self.decode_cmd_arg_none),
0xC6: ("CMD_TIMER_2", self.decode_cmd_arg_none),
0xC7: ("CMD_TIMER_MOON_CRASH", self.decode_cmd_arg_none),
0xC8: ("CMD_TIMER_MINIGAME_2", self.decode_cmd_arg_none),
0xC9: ("CMD_TIMER_TIMER_ENV_HAZARD", self.decode_cmd_arg_none),
0xCA: ("CMD_TIME", self.decode_cmd_arg_none),
0xCB: ("CMD_CHEST_FLAGS", self.decode_cmd_arg_none),
0xCC: ("CMD_INPUT_BANK", self.decode_cmd_arg_none),
0xCD: ("CMD_RUPEES_SELECTED", self.decode_cmd_arg_none),
0xCE: ("CMD_RUPEES_TOTAL", self.decode_cmd_arg_none),
0xCF: ("CMD_TIME_UNTIL_MOON_CRASH", self.decode_cmd_arg_none),
0xD0: ("CMD_INPUT_DOGGY_RACETRACK_BET", self.decode_cmd_arg_none),
0xD1: ("CMD_INPUT_BOMBER_CODE", self.decode_cmd_arg_none),
0xD2: ("CMD_PAUSE_MENU", self.decode_cmd_arg_none),
0xD3: ("CMD_TIME_SPEED", self.decode_cmd_arg_none),
0xD4: ("CMD_OWL_WARP", self.decode_cmd_arg_none),
0xD5: ("CMD_INPUT_LOTTERY_CODE", self.decode_cmd_arg_none),
0xD6: ("CMD_SPIDER_HOUSE_MASK_CODE", self.decode_cmd_arg_none),
0xD7: ("CMD_STRAY_FAIRIES_LEFT_WOODFALL", self.decode_cmd_arg_none),
0xD8: ("CMD_STRAY_FAIRIES_LEFT_SNOWHEAD", self.decode_cmd_arg_none),
0xD9: ("CMD_STRAY_FAIRIES_LEFT_GREAT_BAY", self.decode_cmd_arg_none),
0xDA: ("CMD_STRAY_FAIRIES_LEFT_STONE_TOWER", self.decode_cmd_arg_none),
0xDB: ("CMD_POINTS_BOAT_ARCHERY", self.decode_cmd_arg_none),
0xDC: ("CMD_LOTTERY_CODE", self.decode_cmd_arg_none),
0xDD: ("CMD_LOTTERY_CODE_GUESS", self.decode_cmd_arg_none),
0xDE: ("CMD_HELD_ITEM_PRICE", self.decode_cmd_arg_none),
0xDF: ("CMD_BOMBER_CODE", self.decode_cmd_arg_none),
0xE0: ("CMD_EVENT2", self.decode_cmd_arg_none),
0xE1: ("CMD_SPIDER_HOUSE_MASK_CODE_1", self.decode_cmd_arg_none),
0xE2: ("CMD_SPIDER_HOUSE_MASK_CODE_2", self.decode_cmd_arg_none),
0xE3: ("CMD_SPIDER_HOUSE_MASK_CODE_3", self.decode_cmd_arg_none),
0xE4: ("CMD_SPIDER_HOUSE_MASK_CODE_4", self.decode_cmd_arg_none),
0xE5: ("CMD_SPIDER_HOUSE_MASK_CODE_5", self.decode_cmd_arg_none),
0xE6: ("CMD_SPIDER_HOUSE_MASK_CODE_6", self.decode_cmd_arg_none),
0xE7: ("CMD_HOURS_UNTIL_MOON_CRASH", self.decode_cmd_arg_none),
0xE8: ("CMD_TIME_UNTIL_NEW_DAY", self.decode_cmd_arg_none),
0xF0: ("CMD_HS_POINTS_BANK_RUPEES", self.decode_cmd_arg_none),
0xF1: ("CMD_HS_POINTS_UNK_1", self.decode_cmd_arg_none),
0xF2: ("CMD_HS_POINTS_FISHING", self.decode_cmd_arg_none),
0xF3: ("CMD_HS_TIME_BOAT_ARCHERY", self.decode_cmd_arg_none),
0xF4: ("CMD_HS_TIME_HORSE_BACK_BALLOON", self.decode_cmd_arg_none),
0xF5: ("CMD_HS_TIME_LOTTERY_GUESS", self.decode_cmd_arg_none),
0xF6: ("CMD_HS_TOWN_SHOOTING_GALLERY", self.decode_cmd_arg_none),
0xF7: ("CMD_HS_UNK_1", self.decode_cmd_arg_none),
0xF8: ("CMD_HS_UNK_3_LOWER", self.decode_cmd_arg_none),
0xF9: ("CMD_HS_HORSE_BACK_BALLOON", self.decode_cmd_arg_none),
0xFA: ("CMD_HS_DEKU_PLAYGROUND_DAY_1", self.decode_cmd_arg_none),
0xFB: ("CMD_HS_DEKU_PLAYGROUND_DAY_2", self.decode_cmd_arg_none),
0xFC: ("CMD_HS_DEKU_PLAYGROUND_DAY_3", self.decode_cmd_arg_none),
0xFD: ("CMD_DEKU_PLAYGROUND_NAME_DAY_1", self.decode_cmd_arg_none),
0xFE: ("CMD_DEKU_PLAYGROUND_NAME_DAY_2", self.decode_cmd_arg_none),
0xFF: ("CMD_DEKU_PLAYGROUND_NAME_DAY_3", 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 <= 0xBB: # Characters
if prevCmd:
self.decodedText += ' "'
elif prevNewline:
self.decodedText += '"'
if char == 0x22: # Handle escaping "
self.decodedText += '\\"'
elif char >= 0xB0: # Button characters
self.decodedText += f'{MessageNES.BUTTONMAP[char]}'
else:
self.decodedText += chr(char)
prevText = True
prevNewline = False
prevCmd = False
elif char == 0x11: # New line
if prevCmd:
self.decodedText += ' "'
elif prevNewline:
self.decodedText += '"'
self.decodedText += f'\\n"\n'
prevText = False
prevNewline = True
prevCmd = False
elif char == 0x10 or char == 0x12 or char == 0x1B: # 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 == 0xBF: # End command, do nothing
if prevText:
self.decodedText += '"'
else: # Control Codes (see message_data_fmt_nes.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(baseromSegmentsDir: Path, start):
table = {}
with open(baseromSegmentsDir / "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
NES_MESSAGE_TABLE_ADDR = 0x1210D8 # Location of NES message table in extracted/n64-us/baserom/code
NES_SEGMENT_ADDR = 0x08000000
def main(baseromSegmentsDir: Path, outfile):
msgTable = parseTable(baseromSegmentsDir, NES_MESSAGE_TABLE_ADDR)
buf = (baseromSegmentsDir / "message_data_static").read_bytes()
bufLen = len(buf)
i = 0
messages = []
while i + 12 < bufLen: # Next message must be able to fill a header + 1 at minimum
addr = NES_SEGMENT_ADDR + i
unk11F08, itemId, nextTextId, unk1206C, unk12070, unk12074 = struct.unpack(">HBHHHH", buf[i:i+11])
i += 11
hdr = MessageHeaderNES(unk11F08, itemId, nextTextId, unk1206C, unk12070, unk12074)
start = i
while i < bufLen and buf[i] != 0xBF:
i += 1
i += 1
if i >= bufLen:
break
id, typePos, segment = msgTable[addr]
msg = MessageNES(id, typePos, segment, hdr, 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 message_data_static text")
parser.add_argument(
"--baserom-segments",
dest="baserom_segments_dir",
type=Path,
required=True,
help="Directory of uncompressed ROM segments",
)
parser.add_argument('-o', '--outfile', help='output file to write to. None for stdout')
args = parser.parse_args()
main(args.baserom_segments_dir, args.outfile)

View File

@ -1,85 +0,0 @@
#!/usr/bin/env python3
#
# message_data_static text encoder
#
import argparse
import re
import sys
CHAR_REGEX = r'(?P<chr>\\n|\[.*?\])'
# From https://stackoverflow.com/questions/241327/remove-c-and-c-comments-using-python
def remove_comments(text):
def replacer(match):
s = match.group(0)
if s.startswith('/'):
return " " # note: a space and not an empty string
else:
return s
pattern = re.compile(
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
re.DOTALL | re.MULTILINE
)
return re.sub(pattern, replacer, text)
def parse_char(m):
CHARMAP = {
'\\n' : 0x11,
'[A]' : 0xB0,
'[B]' : 0xB1,
'[C]' : 0xB2,
'[L]' : 0xB3,
'[R]' : 0xB4,
'[Z]' : 0xB5,
'[C-Up]' : 0xB6,
'[C-Down]' : 0xB7,
'[C-Left]' : 0xB8,
'[C-Right]' : 0xB9,
'' : 0xBA,
'[Control-Pad]' : 0xBB,
}
return f'{chr(CHARMAP[m.group(0)])}'
def cvt_str(m):
if m.group('chr'):
return parse_char(m)
else:
print(f"Error Unknown match {m}", file=sys.stderr)
return m.group(0) # Just return the string back
def encode(text):
string_regex = re.compile(f'{CHAR_REGEX}')
# Collapse escaped newlines
text = text.replace("\\\n", "")
# Encode
text = re.sub(string_regex, cvt_str, text)
return text
def main(infile, outfile):
text = ""
with open(infile, "r") as f:
text = f.read()
text = remove_comments(text)
text = encode(text)
if outfile is None:
sys.stdout.reconfigure(encoding='raw_unicode_escape')
sys.stdout.write(text)
else:
with open(outfile, "w", encoding="raw_unicode_escape") as f:
f.write(text)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Encode message_data_static text headers")
parser.add_argument("infile", help="path to file to be encoded")
parser.add_argument("-o", "--outfile", help="encoded file. None for stdout")
args = parser.parse_args()
main(args.infile, args.outfile)

View File

@ -1,237 +0,0 @@
#!/usr/bin/env python3
import argparse
from pathlib import Path
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(baseromSegmentsDir: Path, start):
table = {}
with open(baseromSegmentsDir / "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(baseromSegmentsDir: Path, outfile):
msgTable = parseTable(baseromSegmentsDir, STAFF_MESSAGE_TABLE_ADDR)
buf = (baseromSegmentsDir / "staff_message_data_static").read_bytes()
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(
"--baserom-segments",
dest="baserom_segments_dir",
type=Path,
required=True,
help="Directory of uncompressed ROM segments",
)
parser.add_argument('-o', '--outfile', help='output file to write to. None for stdout')
args = parser.parse_args()
main(args.baserom_segments_dir, args.outfile)

View File

@ -1,72 +0,0 @@
#!/usr/bin/env python3
#
# message_data_static text encoder
#
import argparse
import re
import sys
CHAR_REGEX = r'(?P<chr>\\n|\[.*?\])'
# From https://stackoverflow.com/questions/241327/remove-c-and-c-comments-using-python
def remove_comments(text):
def replacer(match):
s = match.group(0)
if s.startswith('/'):
return " " # note: a space and not an empty string
else:
return s
pattern = re.compile(
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
re.DOTALL | re.MULTILINE
)
return re.sub(pattern, replacer, text)
def parse_char(m):
CHARMAP = {
'\\n' : 0x1,
}
return f'{chr(CHARMAP[m.group(0)])}'
def cvt_str(m):
if m.group('chr'):
return parse_char(m)
else:
print(f"Error Unknown match {m}", file=sys.stderr)
return m.group(0) # Just return the string back
def encode(text):
string_regex = re.compile(f'{CHAR_REGEX}')
# Collapse escaped newlines
text = text.replace("\\\n", "")
# Encode
text = re.sub(string_regex, cvt_str, text)
return text
def main(infile, outfile):
text = ""
with open(infile, "r") as f:
text = f.read()
text = remove_comments(text)
text = encode(text)
if outfile is None:
sys.stdout.reconfigure(encoding='raw_unicode_escape')
sys.stdout.write(text)
else:
with open(outfile, "w", encoding="raw_unicode_escape") as f:
f.write(text)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Encode staff_message_data_static text headers")
parser.add_argument("infile", help="path to file to be encoded")
parser.add_argument("-o", "--outfile", help="encoded file. None for stdout")
args = parser.parse_args()
main(args.infile, args.outfile)

3187
tools/text/msgdis.py Executable file

File diff suppressed because it is too large Load Diff

166
tools/text/msgenc.py Normal file
View File

@ -0,0 +1,166 @@
#!/usr/bin/env python3
#
# message_data_static text encoder
#
import argparse, ast, re, sys
from typing import Dict, Optional
def read_charmap(path : str, wchar : bool, index : int) -> Dict[str,str]:
with open(path) as infile:
charmap = infile.read()
charmap = ast.literal_eval(charmap)
out_charmap = {}
for k,v in charmap.items():
v = v[index]
if v is None:
v = 0
assert isinstance(k, str)
assert v in (range(0xFFFF + 1) if wchar else range(0xFF + 1))
k = repr(k)[1:-1]
if wchar:
u = (v >> 8) & 0xFF
l = (v >> 0) & 0xFF
out_charmap[k] = f"0x{u:02X}, 0x{l:02X},"
else:
out_charmap[k] = f"0x{v:02X},"
return out_charmap
# From https://stackoverflow.com/questions/241327/remove-c-and-c-comments-using-python
def remove_comments(text : str) -> str:
def replacer(match : re.Match) -> str:
string : str = match.group(0)
if string.startswith("/"):
return " " # note: a space and not an empty string
else:
return string
pattern = re.compile(
r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', re.DOTALL | re.MULTILINE
)
return re.sub(pattern, replacer, text)
def convert_text(text : str, encoding : str, charmap : Dict[str, str]) -> str:
def cvt_str(match : re.Match) -> str:
string : str = match.group(0)
# strip quotes
string = string[1:-1]
def cvt_escape(s : str):
# Convert escape sequences such as "\\\"" to "\""
return s.encode("ascii").decode("unicode-escape")
run_start = 0
def emit(text : Optional[str], advance : int):
nonlocal out, string, i, run_start
# flush text
to_flush = string[run_start:i]
if len(string[run_start:i]) != 0:
out += ",".join(f"0x{b:02X}" for b in to_flush.encode(encoding))
out += ","
if text is None:
return
# emit + advance source pos
out += text
i += advance
# start new run
run_start = i
out = ""
i = 0
while i != len(string):
# check charmap
for k in charmap.keys():
if string.startswith(k, i):
# is in charmap, emit the mapped sequence
emit(charmap[k], len(k))
break
else:
if string[i] == "\\" and string[i + 1] != "\\":
# is already escaped, emit the escape sequence verbatim
if string[i + 1] == "x":
# \x**
emit("0" + string[i + 1 : i + 4] + ",", 4)
else:
# \*
e = cvt_escape(string[i : i + 2]).encode(encoding)
assert len(e) == 1
emit(f"0x{e[0]:02X},", 2)
else:
# increment pos, accumulating text that requires encoding
i += 1
# emit remaining accumulated text
emit(None, 0)
return out
# Naive string matcher, assumes single line strings and no comments, handles escaped quotations
string_regex = re.compile(r'"((?:[^\\"\n]|\\.)*)"')
# Collapse escaped newlines
text = text.replace("\\\n", "")
# Encode according to charmap
text = re.sub(string_regex, cvt_str, text)
return text
def main():
parser = argparse.ArgumentParser(
description="Encode message_data_static text headers"
)
parser.add_argument(
"input",
help="path to file to be encoded, or - for stdin",
)
parser.add_argument(
"output",
help="path to write encoded file, or - for stdout",
)
parser.add_argument(
"--encoding",
help="encoding (nes, jpn, or credits)",
required=True,
type=str,
choices=("nes", "jpn", "credits"),
)
parser.add_argument(
"--charmap",
help="path to charmap file specifying custom encoding elements",
required=True,
)
args = parser.parse_args()
wchar,encoding,index = {
"nes" : (False, "raw-unicode-escape", 0),
"jpn" : (True, "SHIFT-JIS", 1),
"credits" : (False, "raw-unicode-escape", 2)
}[args.encoding]
charmap = read_charmap(args.charmap, wchar, index)
text = ""
if args.input == "-":
text = sys.stdin.read()
else:
with open(args.input, "r") as infile:
text = infile.read()
text = remove_comments(text)
text = convert_text(text, encoding, charmap)
if args.output == "-":
sys.stdout.buffer.write(text.encode("utf-8"))
else:
with open(args.output, "w") as outfile:
outfile.write(text)
if __name__ == "__main__":
main()