mirror of
https://github.com/Xeeynamo/sotn-decomp.git
synced 2024-11-23 13:09:44 +00:00
Enhance sotn_str
& Encode Menu Strings (#1320)
`sotn_str` can now handle escaped quotes, right parenthesis, and more in in `_S` strings. This allows all of the strings in `dra/menu.c` to be encoded as static C-strings. This also converts the spell button sequences to UTF-8. --------- Co-authored-by: Jonathan Hohle <jon@ttkb.co>
This commit is contained in:
parent
d45701638f
commit
078ed30c06
@ -444,34 +444,34 @@ const char* g_MenuStr[] = {
|
||||
};
|
||||
|
||||
SpellDef g_SpellDefs[] = {
|
||||
{"Dark Metamorphosis", "\xe0\xe1\xe2\xe3\xe4\xFF", "Heal HP by shedding blood", 10, 0, 0, 0, 1, 0, 0, 0, 0},
|
||||
{"Summon Spirit", "\xe0\xe4\xe2\xe6\xFF", "Summons a fierce spirit", 5, 20, 8, 2, 1, 0, 0, 15, 0},
|
||||
{"Hellfire", "\xe2\xe6\xe5\xe4\xFF", "Transport and fireball attack", 15, 20, 8, 2, 1, 0, 32768, 30, 0},
|
||||
{"Tetra Spirit", "\xe2\xc0\xd2\xe3\xe4\xe5\xe6\xFF", "Summons four spirits", 20, 20, 8, 2, 1, 0, 0, 15, 0},
|
||||
{"Wolf Charge", "\xe6\xe5\xe4\xFF", "Powerful attack by Wolf", 10, 40, 10, 2, 129, 64, 64, 30, 0},
|
||||
{"Soul Steal", "\xe0\xe4\xe5\xe6\xe7\xe0\xe4\xFF", "Steals HP from nearby enemies", 50, 10, 8, 130, 2, 0, 0, 15, 0},
|
||||
{"Wing Smash", "\xe2\xe1\xe0\xe7\xe6\xe5\xe4\xFF", "Powerful attack by Bat", 8, 10, 10, 2, 1, 16, 0, 50, 0},
|
||||
{"Sword Brothers", "\xe6\xe5\xe4\xe3\xe2\xc0\xd2\xe6\xFF", "Summons Sword Brothers", 30, 20, 8, 130, 129, 0, 64, 20, 0},
|
||||
{"", "\xFF", "", 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{"", "\xFF", "", 8, 10, 10, 2, 1, 0, 32768, 10, 0},
|
||||
{"", "\xFF", "", 0, 20, 20, 2, 1, 0, 0, 10, 0},
|
||||
{"", "\xFF", "", 0, 20, 20, 2, 129, 64, 64, 30, 0},
|
||||
{"", "\xFF", "", 0, 20, 20, 2, 1, 0, 128, 20, 0},
|
||||
{"", "\xFF", "", 5, 10, 10, 2, 1, 16, 0, 50, 0},
|
||||
{"", "\xFF", "", 0, 40, 10, 2, 129, 16, 64, 10, 0},
|
||||
{"", "\xFF", "", 0, 10, 4, 2, 1, 0, 0, 5, 0},
|
||||
{"", "\xFF", "", 0, 10, 10, 2, 1, 0, 32768, 10, 0},
|
||||
{"", "\xFF", "", 0, 2, 4, 2, 1, 0, 0, 5, 0},
|
||||
{"", "\xFF", "", 0, 2, 4, 2, 2, 0, 0, 5, 0},
|
||||
{"", "\xFF", "", 0, 20, 4, 2, 129, 0, 64, 5, 0},
|
||||
{"", "\xFF", "", 0, 20, 4, 2, 129, 0, 64, 10, 0},
|
||||
{"", "\xFF", "", 0, 20, 8, 2, 129, 0, 64, 5, 0},
|
||||
{"", "\xFF", "", 0, 20, 8, 2, 129, 0, 64, 10, 0},
|
||||
{"", "\xFF", "", 0, 4, 8, 2, 129, 0, 64, 5, 0},
|
||||
{"", "\xFF", "", 0, 20, 8, 2, 129, 0, 32832, 30, 0},
|
||||
{"", "\xFF", "", 0, 20, 8, 2, 129, 0, 8256, 30, 0},
|
||||
{"", "\xFF", "", 0, 20, 8, 2, 129, 0, 16448, 30, 0},
|
||||
{"", "\xFF", "", 0, 20, 8, 2, 129, 0, 16448, 45, 0},
|
||||
{"Dark Metamorphosis", _S("←↖↑↗→"), "Heal HP by shedding blood", 10, 0, 0, 0, 1, 0, 0, 0, 0},
|
||||
{"Summon Spirit", _S("←→↑↓"), "Summons a fierce spirit", 5, 20, 8, 2, 1, 0, 0, 15, 0},
|
||||
{"Hellfire", _S("↑↓↘→"), "Transport and fireball attack", 15, 20, 8, 2, 1, 0, 32768, 30, 0},
|
||||
{"Tetra Spirit", _S("↑ため↗→↘↓"), "Summons four spirits", 20, 20, 8, 2, 1, 0, 0, 15, 0},
|
||||
{"Wolf Charge", _S("↓↘→"), "Powerful attack by Wolf", 10, 40, 10, 2, 129, 64, 64, 30, 0},
|
||||
{"Soul Steal", _S("←→↘↓↙←→"), "Steals HP from nearby enemies", 50, 10, 8, 130, 2, 0, 0, 15, 0},
|
||||
{"Wing Smash", _S("↑↖←↙↓↘→"), "Powerful attack by Bat", 8, 10, 10, 2, 1, 16, 0, 50, 0},
|
||||
{"Sword Brothers", _S("↓↘→↗↑ため↓"), "Summons Sword Brothers", 30, 20, 8, 130, 129, 0, 64, 20, 0},
|
||||
{"", _S(""), "", 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
{"", _S(""), "", 8, 10, 10, 2, 1, 0, 32768, 10, 0},
|
||||
{"", _S(""), "", 0, 20, 20, 2, 1, 0, 0, 10, 0},
|
||||
{"", _S(""), "", 0, 20, 20, 2, 129, 64, 64, 30, 0},
|
||||
{"", _S(""), "", 0, 20, 20, 2, 1, 0, 128, 20, 0},
|
||||
{"", _S(""), "", 5, 10, 10, 2, 1, 16, 0, 50, 0},
|
||||
{"", _S(""), "", 0, 40, 10, 2, 129, 16, 64, 10, 0},
|
||||
{"", _S(""), "", 0, 10, 4, 2, 1, 0, 0, 5, 0},
|
||||
{"", _S(""), "", 0, 10, 10, 2, 1, 0, 32768, 10, 0},
|
||||
{"", _S(""), "", 0, 2, 4, 2, 1, 0, 0, 5, 0},
|
||||
{"", _S(""), "", 0, 2, 4, 2, 2, 0, 0, 5, 0},
|
||||
{"", _S(""), "", 0, 20, 4, 2, 129, 0, 64, 5, 0},
|
||||
{"", _S(""), "", 0, 20, 4, 2, 129, 0, 64, 10, 0},
|
||||
{"", _S(""), "", 0, 20, 8, 2, 129, 0, 64, 5, 0},
|
||||
{"", _S(""), "", 0, 20, 8, 2, 129, 0, 64, 10, 0},
|
||||
{"", _S(""), "", 0, 4, 8, 2, 129, 0, 64, 5, 0},
|
||||
{"", _S(""), "", 0, 20, 8, 2, 129, 0, 32832, 30, 0},
|
||||
{"", _S(""), "", 0, 20, 8, 2, 129, 0, 8256, 30, 0},
|
||||
{"", _S(""), "", 0, 20, 8, 2, 129, 0, 16448, 30, 0},
|
||||
{"", _S(""), "", 0, 20, 8, 2, 129, 0, 16448, 45, 0},
|
||||
};
|
||||
|
||||
RelicDesc g_RelicDefs[] = {
|
||||
|
@ -46,62 +46,66 @@ const char* D_800A2D48[] = {
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef SOTN_STR
|
||||
// Similar to `_S`, a second lookup table is used for some menus based on BIOS
|
||||
// fonts in a custom table. This is only relevant to the strings below.
|
||||
// e.g. "\x02\x03\x04\xFF" -> _S2("DEF")
|
||||
#define _S2(x) (x)
|
||||
|
||||
// Like `_S2`, but uses a lookup table unique to the HD version of the game
|
||||
#define _S2_HD(x) (x)
|
||||
#endif
|
||||
|
||||
#if defined(VERSION_US)
|
||||
const char* D_800A2D64[] = {
|
||||
static const char* D_800A2D64[] = {
|
||||
"ATDEF",
|
||||
};
|
||||
|
||||
static const char* D_800A2D68[] = {
|
||||
_S2("ATT"),
|
||||
_S2("DEF"),
|
||||
};
|
||||
#elif defined(VERSION_HD)
|
||||
const char* D_800A2D10[] = {
|
||||
static const char* D_800A2D10[] = {
|
||||
"装備技システム短剣必殺使攻撃力防",
|
||||
};
|
||||
|
||||
const char* D_800A2D14[] = {
|
||||
static const char* D_800A2D14[] = {
|
||||
"御魔導器拳こ一覧棒両手食物爆弾盾",
|
||||
};
|
||||
|
||||
const char* D_800A2D18[] = {
|
||||
static const char* D_800A2D18[] = {
|
||||
"投射薬ん右左武兜鎧マントその他い",
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(VERSION_US)
|
||||
const char* D_800A2D68[] = {
|
||||
"\x00\x01\x01\xFF",
|
||||
"\x02\x03\x04\xFF",
|
||||
};
|
||||
#elif defined(VERSION_HD)
|
||||
extern const char* D_800A2D68[];
|
||||
#endif
|
||||
|
||||
#if defined(VERSION_HD)
|
||||
const char* D_800A2D68[] = {
|
||||
"\x0C\x0D\x0E\xFF", //
|
||||
"\x0F\x10\x0E\xFF", // D_800A83AC
|
||||
"\x09\x0A\x02\x16\x17\xFF", // D_800A2D24
|
||||
"\x00\x01\xFF", // g_MenuStr[93]
|
||||
"\x09\x0A\x02\xFF", // g_MenuStr[96]
|
||||
"\x11\x12\x13\xFF", // g_MenuStr[92]
|
||||
"\x03\x04\x05\x06\xFF", // g_MenuStr[94]
|
||||
"\x07\x08\xFF", // g_MenuStr[99]
|
||||
"\x08\xFF", //
|
||||
"\x20\x08\xFF", //
|
||||
"\x14\xFF", //
|
||||
"\x15\x23\x18\xFF", //
|
||||
"\x19\x1A\x08\xFF", //
|
||||
"\x1B\x1C\xFF", //
|
||||
"\x1D\x1E\xFF", //
|
||||
"\x20\x21\xFF", //
|
||||
"\x1F\xFF", //
|
||||
"\x22\xFF", //
|
||||
"\x24\x1A\x26\x13\xFF", //
|
||||
"\x25\x1A\x26\x13\xFF", //
|
||||
"\x27\xFF", //
|
||||
"\x28\xFF", //
|
||||
"\x29\x2A\x2B\xFF", //
|
||||
"\x2C\x2D\x2E\xFF", //
|
||||
"\x2C\x2D\x2E\xFF", //
|
||||
"\x0B\x2F\x11\xFF", // g_MenuStr[95]
|
||||
"\x0B\x2F\x11\x16\x17\xFF", // D_800A2D84
|
||||
static const char* D_800A2D68[] = {
|
||||
_S2_HD("攻撃力"), //
|
||||
_S2_HD("防御力"), // D_800A83AC
|
||||
_S2_HD("必殺技一覧"), // D_800A2D24
|
||||
_S2_HD("装備"), // g_MenuStr[93]
|
||||
_S2_HD("必殺技"), // g_MenuStr[96]
|
||||
_S2_HD("魔導器"), // g_MenuStr[92]
|
||||
_S2_HD("システム"), // g_MenuStr[94]
|
||||
_S2_HD("短剣"), // g_MenuStr[99]
|
||||
_S2_HD("剣"), //
|
||||
_S2_HD("投剣"), //
|
||||
_S2_HD("拳"), //
|
||||
_S2_HD("こん棒"), //
|
||||
_S2_HD("両手剣"), //
|
||||
_S2_HD("食物"), //
|
||||
_S2_HD("爆弾"), //
|
||||
_S2_HD("投射"), //
|
||||
_S2_HD("盾"), //
|
||||
_S2_HD("薬"), //
|
||||
_S2_HD("右手武器"), //
|
||||
_S2_HD("左手武器"), //
|
||||
_S2_HD("兜"), //
|
||||
_S2_HD("鎧"), //
|
||||
_S2_HD("マント"), //
|
||||
_S2_HD("その他"), //
|
||||
_S2_HD("その他"), //
|
||||
_S2_HD("使い魔"), // g_MenuStr[95]
|
||||
_S2_HD("使い魔一覧"), // D_800A2D84
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -124,6 +124,16 @@ def get_chr(chr):
|
||||
return table[chr]
|
||||
|
||||
|
||||
alt_utf8_to_index = dict((value, index) for index, value in enumerate("ATDEF"))
|
||||
|
||||
alt_hd_utf8_to_index = dict((value, index) for index, value in enumerate("".join([
|
||||
#0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
"装備技システム短剣必殺使攻撃力防",
|
||||
"御魔導器拳こ一覧棒両手食物爆弾盾",
|
||||
"投射薬ん右左武兜鎧マントその他い",
|
||||
])))
|
||||
|
||||
|
||||
def convert_j(f):
|
||||
pos = 0
|
||||
str = ""
|
||||
@ -259,9 +269,8 @@ def dakuten_to_bytes(input_chr):
|
||||
|
||||
|
||||
def utf8_to_byte_literals(input_str):
|
||||
clean_str = input_str.replace("_SJ(", "").replace(")", "")
|
||||
bytes = []
|
||||
for char in clean_str:
|
||||
for char in input_str:
|
||||
if has_dakuten(char) or has_handakuten(char):
|
||||
bytes += dakuten_to_bytes(char)
|
||||
elif char == '月':
|
||||
@ -269,16 +278,21 @@ def utf8_to_byte_literals(input_str):
|
||||
else:
|
||||
bytes.append(utf8_to_index[char])
|
||||
bytes.append(0xFF)
|
||||
hex_list = [hex(num) for num in bytes]
|
||||
return bytes
|
||||
|
||||
def alt_utf8_to_byte_literals(input_str):
|
||||
bytes = []
|
||||
for char in input_str:
|
||||
bytes.append(alt_utf8_to_index[char])
|
||||
bytes.append(0xFF)
|
||||
return bytes
|
||||
|
||||
def utf8_to_byte_literals_wrapped(input):
|
||||
out = utf8_to_byte_literals(input)
|
||||
str = f"_SJ()"
|
||||
escaped_string = "".join([f"\\x{val:02X}" for val in out])
|
||||
out = f"_SJ({escaped_string})"
|
||||
return out
|
||||
def alt_hd_utf8_to_byte_literals(input_str):
|
||||
bytes = []
|
||||
for char in input_str:
|
||||
bytes.append(alt_hd_utf8_to_index[char])
|
||||
bytes.append(0xFF)
|
||||
return bytes
|
||||
|
||||
def utf8_to_byte_literals_escaped(input):
|
||||
out = utf8_to_byte_literals(input)
|
||||
|
@ -3,7 +3,7 @@
|
||||
import argparse
|
||||
import re
|
||||
import sys
|
||||
from jp import utf8_to_byte_literals
|
||||
from jp import utf8_to_byte_literals, alt_utf8_to_byte_literals, alt_hd_utf8_to_byte_literals
|
||||
|
||||
|
||||
def parse(filename, str_offset):
|
||||
@ -27,22 +27,40 @@ def process_string(match: re.Match[str]):
|
||||
r += f"\\x{ch - 0x20:02X}"
|
||||
return f'"{r}\\xFF"'
|
||||
|
||||
def transform_match(transform):
|
||||
def repl(match: re.Match[str]):
|
||||
s = match.group(1)
|
||||
s = s.replace('\\"', '"')
|
||||
out = transform(s)
|
||||
escaped = "".join([f"\\x{val:02X}" for val in out])
|
||||
return f'"{escaped}"'
|
||||
return repl
|
||||
|
||||
def process_string_jp(match: re.Match[str]):
|
||||
s = match.group(1)
|
||||
out = utf8_to_byte_literals(s)
|
||||
escaped = "".join([f"\\x{val:02X}" for val in out])
|
||||
return f'"{escaped}"'
|
||||
def process_macro(line, macro_name, transform):
|
||||
return re.sub(macro_name + r'\("((?:\\["]|[^"])*)"\)',
|
||||
transform,
|
||||
line)
|
||||
|
||||
def process_s_macro(line):
|
||||
return process_macro(line,
|
||||
'_S',
|
||||
transform_match(utf8_to_byte_literals))
|
||||
|
||||
def process_s2_macro(line):
|
||||
return process_macro(line,
|
||||
"_S2",
|
||||
transform_match(alt_utf8_to_byte_literals))
|
||||
|
||||
def process_s2_hd_macro(line):
|
||||
return process_macro(line,
|
||||
"_S2_HD",
|
||||
transform_match(alt_hd_utf8_to_byte_literals))
|
||||
|
||||
def do_sub(line):
|
||||
pattern = r'_S\("([^"]*)"\)'
|
||||
# english_str_processed = re.sub(pattern, process_string, line)
|
||||
# pattern_jp = r'_SJ\("([^"]+)"\)'
|
||||
jp_str_processed = re.sub(pattern, process_string_jp, line)
|
||||
jp_str_processed = jp_str_processed.replace("_S(\"\")", "\"\\xFF\"")
|
||||
return jp_str_processed
|
||||
|
||||
processed = process_s_macro(line)
|
||||
processed = process_s2_macro(processed)
|
||||
processed = process_s2_hd_macro(processed)
|
||||
return processed
|
||||
|
||||
def process(filename):
|
||||
if not filename or filename == "-":
|
||||
|
@ -22,28 +22,28 @@ class TestingJp(unittest.TestCase):
|
||||
bytes = dakuten_to_bytes("で")
|
||||
assert bytes == [0xC3, 0xFF, 0x9E]
|
||||
|
||||
def test_utf8_to_byte_literals_wrapped_dakuten(self):
|
||||
input = "_SJ(すで)"
|
||||
out = utf8_to_byte_literals_wrapped(input)
|
||||
assert out == "_SJ(\\xBD\\xC3\\xFF\\x9E\\xFF)"
|
||||
def test_utf8_to_byte_literals_escaped_dakuten(self):
|
||||
input = "すで"
|
||||
out = utf8_to_byte_literals_escaped(input)
|
||||
assert out == "\\xBD\\xC3\\xFF\\x9E\\xFF"
|
||||
|
||||
def test_utf8_to_byte_literals_wrapped_kanji(self):
|
||||
input = "_SJ(あかつきの剣)"
|
||||
out = utf8_to_byte_literals_wrapped(input)
|
||||
assert out == "_SJ(\\xB1\\xB6\\xC2\\xB7\\xC9\\x3C\\xFF)"
|
||||
def test_utf8_to_byte_literals_escaped_kanji(self):
|
||||
input = "あかつきの剣"
|
||||
out = utf8_to_byte_literals_escaped(input)
|
||||
assert out == "\\xB1\\xB6\\xC2\\xB7\\xC9\\x3C\\xFF"
|
||||
|
||||
def check_sei():
|
||||
assert(utf8_to_index['聖'] == 222)
|
||||
|
||||
def test_glasses(self):
|
||||
input = "_SJ(聖なるめがね)"
|
||||
out = utf8_to_byte_literals_wrapped(input)
|
||||
assert out == "_SJ(\\xEE\\xC5\\xD9\\xD2\\xB6\\xFF\\x9E\\xC8\\xFF)"
|
||||
input = "聖なるめがね"
|
||||
out = utf8_to_byte_literals_escaped(input)
|
||||
assert out == "\\xEE\\xC5\\xD9\\xD2\\xB6\\xFF\\x9E\\xC8\\xFF"
|
||||
|
||||
def test_moon(self):
|
||||
input = "_SJ(バルザイのえん月刀)"
|
||||
out = utf8_to_byte_literals_wrapped(input)
|
||||
assert out == "_SJ(\\x8A\\xFF\\x9E\\x99\\x7B\\xFF\\x9E\\x72\\xC9\\xB4\\xDD\\xFF\\xFF\\xED\\xFF)"
|
||||
input = "バルザイのえん月刀"
|
||||
out = utf8_to_byte_literals_escaped(input)
|
||||
assert out == "\\x8A\\xFF\\x9E\\x99\\x7B\\xFF\\x9E\\x72\\xC9\\xB4\\xDD\\xFF\\xFF\\xED\\xFF"
|
||||
|
||||
def test_str_potion(self):
|
||||
input = "Str. potion"
|
||||
@ -52,17 +52,43 @@ class TestingJp(unittest.TestCase):
|
||||
|
||||
class TestingSotnStr(unittest.TestCase):
|
||||
def test_do_sub_jp(self):
|
||||
line = '{_SJ("すで"), "装備なし(素手)", 0, 0, 0, 3, 255, 0, 0, 36, 42, 0, 5, 128, 0, 0, false, 8, 0, 0, 0, 0, 4, 2, 1, 1, 1, 1, 0},'
|
||||
line = '{_S("すで"), "装備なし(素手)", 0, 0, 0, 3, 255, 0, 0, 36, 42, 0, 5, 128, 0, 0, false, 8, 0, 0, 0, 0, 4, 2, 1, 1, 1, 1, 0},'
|
||||
out = do_sub(line)
|
||||
expected = '{"\\xBD\\xC3\\xFF\\x9E\\xFF", "装備なし(素手)", 0, 0, 0, 3, 255, 0, 0, 36, 42, 0, 5, 128, 0, 0, false, 8, 0, 0, 0, 0, 4, 2, 1, 1, 1, 1, 0},'
|
||||
assert out == expected
|
||||
|
||||
def test_jp_empty(self):
|
||||
line = "_SJ(\"\")"
|
||||
line = "_S(\"\")"
|
||||
out = do_sub(line)
|
||||
expected = '\"\\xFF\"'
|
||||
assert out == expected
|
||||
|
||||
def test_jp_symbols_and_quotes(self):
|
||||
line = "_S(\"\\\"(\\\")\")"
|
||||
out = do_sub(line)
|
||||
expected = '"\\x02\\x08\\x02\\x09\\xFF"'
|
||||
assert out == expected, (out, expected)
|
||||
|
||||
def test_s2_us(self):
|
||||
line = '_S2("ATT")'
|
||||
out = do_sub(line)
|
||||
assert out == '"\\x00\\x01\\x01\\xFF"'
|
||||
|
||||
def test_s2_empty(self):
|
||||
line = '_S2("")'
|
||||
out = do_sub(line)
|
||||
assert out == '"\\xFF"'
|
||||
|
||||
def test_s2_hd(self):
|
||||
line = '_S2_HD("攻撃力")'
|
||||
out = do_sub(line)
|
||||
assert out == '"\\x0C\\x0D\\x0E\\xFF"'
|
||||
|
||||
def test_s2_empty(self):
|
||||
line = '_S2_HD("")'
|
||||
out = do_sub(line)
|
||||
assert out == '"\\xFF"'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
Loading…
Reference in New Issue
Block a user