tp/tools/libdol2asm/data/string.py

183 lines
5.7 KiB
Python

from dataclasses import dataclass, field
from typing import List
from ..builder import AsyncBuilder
from .. import util
from ..types import *
from .base import *
from .identifier import *
def string_to_cstr(data):
return "".join(data)
def escape_char(v):
if v == "\n":
return "\\n"
elif v == "\t":
return "\\t"
elif v == "\v":
return "\\v"
elif v == "\b":
return "\\b"
elif v == "\r":
return "\\r"
elif v == "\f":
return "\\f"
elif v == "\a":
return "\\a"
elif v == "\\":
return "\\\\"
elif v == "\"":
return "\\\""
elif ord(v) < 32:
return "\"\"\\x%02X\"\"" % ord(v)
else:
return v
def escape_char_hard(v):
return "\"\"\\x%02X\"\"" % v
def escape_string(data):
return [escape_char(x) for x in data]
def escape_char_hex(v):
if v == 0:
return "\\0"
return "\\x%02X" % v
def escape_full_string(data):
return [escape_char_hex(x) for x in data]
@dataclass(eq=False)
class String(ArbitraryData):
encoding: str = None
decoded_string: str = None
string_base: "StringBase" = None
def array_type(self):
return self.element_type()
def asm_reference(self, addr):
if self.string_base:
return self.string_base.asm_reference(addr)
else:
return super().asm_reference(addr)
def cpp_reference(self, accessor, addr):
if self.string_base:
return self.string_base.cpp_reference(accessor, addr)
else:
return super().cpp_reference(accessor, addr)
async def export_declaration(self, exporter, builder: AsyncBuilder, force_active=True):
if force_active:
await builder.write("#pragma push")
await builder.write("#pragma force_active on")
sjis = self.decoded_string.encode("shift_jisx0213")
if 0x5c in sjis:
await builder.write("// MWCC ignores mapping of some japanese characters using the ")
await builder.write("// byte 0x5C (ASCII '\\'). This is why this string is hex-encoded.")
data = escape_full_string(sjis)
else:
data = escape_string(self.decoded_string)
await builder.write_nonewline("SECTION_DEAD ")
if self.is_static:
await self.export_static(builder)
await builder.write_nonewline(self.array_type().decl(self.identifier.label))
await String.export_string(builder, data)
if self.padding > 0:
assert len(self.padding_data) == self.padding
assert self.padding_data[-1] == 0
data = escape_full_string(self.padding_data[:-1])
await builder.write("/* @stringBase0 padding */")
await builder.write_nonewline("SECTION_DEAD ")
await builder.write_nonewline("static ")
await builder.write_nonewline(self.array_type().decl(f"pad_{self.end:08X}"))
await String.export_string(builder, data)
if force_active:
await builder.write("#pragma pop")
@staticmethod
async def export_string(builder: AsyncBuilder, data: List[str]):
if len(data) < 32:
await builder.write(f" = \"{string_to_cstr(data)}\";")
else:
await builder.write(f" = ")
data_chunks = util.chunks(data, 48)
lines = []
for chunk in data_chunks:
lines += [f" \"{string_to_cstr(chunk)}\""]
lines[-1] += ";"
for line in lines:
await builder.write(line)
@dataclass(eq=False)
class StringBase(ArbitraryData):
strings: List[Symbol] = field(default_factory=list, repr=False)
def valid_reference(self, addr):
return addr >= self.start and addr < self.end
def array_type(self):
return self.element_type()
@property
def is_static(self):
# having @stringBase0 as satatic would remove the forward reference to the lcf generated symbol,
# this will not compile correctly.
return False
def set_mlts(self, module: int, library: str, translation_unit: str, section: str):
super().set_mlts(module, library, translation_unit, section)
for string in self.strings:
string.set_mlts(module, library, translation_unit, section)
async def export_declaration(self, exporter, builder: AsyncBuilder):
pass
"""
await builder.write("#pragma push")
await builder.write("#pragma force_active on")
for string in self.strings:
# if the @stringBase0 is static (which it will almost always be), setup
# so that the sub-strings are static.
if self.is_static:
string.reference_count.make_static()
await string.export_declaration(exporter, builder)
if self.padding > 0:
assert len(self.padding_data) == self.padding
assert self.padding_data[-1] == 0
data = escape_full_string(self.padding_data[:-1])
await builder.write("/* @stringBase0 padding */")
await builder.write_nonewline("SECTION_DEAD ")
await builder.write_nonewline("static ")
await builder.write_nonewline(self.array_type().decl(f"pad_{self.end:08X}"))
await String.export_string(builder, data)
await builder.write("#pragma pop")
"""
@staticmethod
def create(symbol, strings, data, padding_data):
return StringBase(
Identifier("stringBase", symbol.addr, symbol.name),
symbol.addr,
symbol.size,
data=data,
data_type=PointerType(ConstType(CHAR)),
padding=len(padding_data),
padding_data=padding_data,
strings=strings)