55from TextBox import line_wrap
66from Utils import find_last
77
8- TEXT_START = 0x92D000
8+ ENG_TEXT_START = 0x92D000
9+ JPN_TEXT_START = 0x8EB000
910ENG_TEXT_SIZE_LIMIT = 0x39000
10- JPN_TEXT_SIZE_LIMIT = 0x3A150
11+ JPN_TEXT_SIZE_LIMIT = 0x3B000
1112
1213JPN_TABLE_START = 0xB808AC
1314ENG_TABLE_START = 0xB849EC
1920EXTENDED_TABLE_START = JPN_TABLE_START # start writing entries to the jp table instead of english for more space
2021EXTENDED_TABLE_SIZE = JPN_TABLE_SIZE + ENG_TABLE_SIZE # 0x8360 bytes, 4204 entries
2122
23+ EXTENDED_TEXT_START = JPN_TABLE_START # start writing text to the jp table instead of english for more space
24+ EXTENDED_TEXT_SIZE_LIMIT = JPN_TEXT_SIZE_LIMIT + ENG_TEXT_SIZE_LIMIT # 0x74000 bytes
25+
2226# name of type, followed by number of additional bytes to read, follwed by a function that prints the code
2327CONTROL_CODES = {
2428 0x00 : ('pad' , 0 , lambda _ : '<pad>' ),
@@ -455,14 +459,14 @@ def size(self):
455459 return size
456460
457461 # writes the code to the given offset, and returns the offset of the next byte
458- def write (self , rom , offset ):
459- rom .write_byte (TEXT_START + offset , self .code )
462+ def write (self , rom , text_start , offset ):
463+ rom .write_byte (text_start + offset , self .code )
460464
461465 extra_bytes = 0
462466 if self .code in CONTROL_CODES :
463467 extra_bytes = CONTROL_CODES [self .code ][1 ]
464468 bytes_to_write = int_to_bytes (self .data , extra_bytes )
465- rom .write_bytes (TEXT_START + offset + 1 , bytes_to_write )
469+ rom .write_bytes (text_start + offset + 1 , bytes_to_write )
466470
467471 return offset + 1 + extra_bytes
468472
@@ -498,7 +502,7 @@ def get_python_string(self):
498502
499503 # check if this is an unused message that just contains it's own id as text
500504 def is_id_message (self ):
501- if self .unpadded_length != 5 :
505+ if self .unpadded_length != 5 or self . id == 0xFFFC :
502506 return False
503507 for i in range (4 ):
504508 code = self .text_codes [i ].code
@@ -609,20 +613,20 @@ def transform(self, replace_ending=False, ending=None, always_allow_skip=True, s
609613
610614 # writes a Message back into the rom, using the given index and offset to update the table
611615 # returns the offset of the next message
612- def write (self , rom , index , offset ):
616+ def write (self , rom , index , text_start , offset , bank ):
613617 # construct the table entry
614618 id_bytes = int_to_bytes (self .id , 2 )
615619 offset_bytes = int_to_bytes (offset , 3 )
616- entry = id_bytes + bytes ([self .opts , 0x00 , 0x07 ]) + offset_bytes
620+ entry = id_bytes + bytes ([self .opts , 0x00 , bank ]) + offset_bytes
617621 # write it back
618622 entry_offset = EXTENDED_TABLE_START + 8 * index
619623 rom .write_bytes (entry_offset , entry )
620624
621625 for code in self .text_codes :
622- offset = code .write (rom , offset )
626+ offset = code .write (rom , text_start , offset )
623627
624628 while offset % 4 > 0 :
625- offset = Text_Code (0x00 , 0 ).write (rom , offset ) # pad to 4 byte align
629+ offset = Text_Code (0x00 , 0 ).write (rom , text_start , offset ) # pad to 4 byte align
626630
627631 return offset
628632
@@ -651,8 +655,14 @@ def __init__(self, raw_text, index, id, opts, offset, length):
651655
652656 # read a single message from rom
653657 @classmethod
654- def from_rom (cls , rom , index ):
655- entry_offset = ENG_TABLE_START + 8 * index
658+ def from_rom (cls , rom , index , eng = True ):
659+ if eng :
660+ table_start = ENG_TABLE_START
661+ text_start = ENG_TEXT_START
662+ else :
663+ table_start = JPN_TABLE_START
664+ text_start = JPN_TEXT_START
665+ entry_offset = table_start + 8 * index
656666 entry = rom .read_bytes (entry_offset , 8 )
657667 next = rom .read_bytes (entry_offset + 8 , 8 )
658668
@@ -661,7 +671,7 @@ def from_rom(cls, rom, index):
661671 offset = bytes_to_int (entry [5 :8 ])
662672 length = bytes_to_int (next [5 :8 ]) - offset
663673
664- raw_text = rom .read_bytes (TEXT_START + offset , length )
674+ raw_text = rom .read_bytes (text_start + offset , length )
665675
666676 return cls (raw_text , index , id , opts , offset , length )
667677
@@ -922,19 +932,50 @@ def read_messages(rom):
922932 index += 1
923933 table_offset += 8
924934
935+ # Also grab 0xFFFC entry from JP table.
936+ messages .append (read_fffc_message (rom ))
925937 return messages
926938
939+ # The JP text table is the only source for ID 0xFFFC, which is used by the
940+ # title and file select screens. Preserve this table entry and text data when
941+ # overwriting the JP data. The regular read_messages function only reads English
942+ # data.
943+ def read_fffc_message (rom ):
944+ table_offset = JPN_TABLE_START
945+ index = 0
946+ while True :
947+ entry = rom .read_bytes (table_offset , 8 )
948+ id = bytes_to_int (entry [0 :2 ])
949+
950+ if id == 0xFFFC :
951+ message = Message .from_rom (rom , index , eng = False )
952+ break
953+
954+ index += 1
955+ table_offset += 8
956+
957+ return message
958+
927959# write the messages back
928960def repack_messages (rom , messages , permutation = None , always_allow_skip = True , speed_up_text = True ):
929961
930- rom .update_dmadata_record (TEXT_START , TEXT_START , TEXT_START + ENG_TEXT_SIZE_LIMIT )
962+ rom .update_dmadata_record (ENG_TEXT_START , ENG_TEXT_START , ENG_TEXT_START + ENG_TEXT_SIZE_LIMIT )
963+ rom .update_dmadata_record (JPN_TEXT_START , JPN_TEXT_START , JPN_TEXT_START + JPN_TEXT_SIZE_LIMIT )
931964
932965 if permutation is None :
933966 permutation = range (len (messages ))
934967
935968 # repack messages
936969 offset = 0
937- text_size_limit = ENG_TEXT_SIZE_LIMIT
970+ text_start = JPN_TEXT_START
971+ text_size_limit = EXTENDED_TEXT_SIZE_LIMIT
972+ text_bank = 0x08 # start with the Japanese text bank
973+ jp_bytes = 0
974+ # An extra dummy message is inserted after exhausting the JP text file.
975+ # Written message IDs are independent of the python list index, but the
976+ # index has to be maintained for old/new lookups. This wouldn't be an
977+ # issue if text shuffle didn't exist.
978+ jp_index_offset = 0
938979
939980 for old_index , new_index in enumerate (permutation ):
940981 old_message = messages [old_index ]
@@ -943,21 +984,57 @@ def repack_messages(rom, messages, permutation=None, always_allow_skip=True, spe
943984 new_message .id = old_message .id
944985
945986 # modify message, making it represent how we want it to be written
946- new_message .transform (True , old_message .ending , always_allow_skip , speed_up_text )
987+ if new_message .id != 0xFFFC :
988+ new_message .transform (True , old_message .ending , always_allow_skip , speed_up_text )
989+
990+ # check if there is space to write the message
991+ message_size = new_message .size ()
992+ if message_size + offset > JPN_TEXT_SIZE_LIMIT and text_start == JPN_TEXT_START :
993+ # Add a dummy entry to the table for the last entry in the
994+ # JP file. This is used by the game to calculate message
995+ # length. Since the next entry in the English table has an
996+ # offset of zero, which would lead to a negative length.
997+ # 0xFFFD is used as the text ID for this in vanilla.
998+ # Text IDs need to be in order across the table for the
999+ # split to work.
1000+ entry = bytes ([0xFF , 0xFD , 0x00 , 0x00 , text_bank ]) + int_to_bytes (offset , 3 )
1001+ entry_offset = EXTENDED_TABLE_START + 8 * old_index
1002+ rom .write_bytes (entry_offset , entry )
1003+ # if there is no room then switch to the English text bank
1004+ text_bank = 0x07
1005+ text_start = ENG_TEXT_START
1006+ jp_bytes = offset
1007+ jp_index_offset = 1
1008+ offset = 0
1009+
1010+ # Special handling for text ID 0xFFFC, which has hard-coded offsets to
1011+ # the JP file in function Font_LoadOrderedFont in z_kanfont.c
1012+ if new_message .id == 0xFFFC :
1013+ # hard-coded offset including segment
1014+ rom .write_int16 (0xAD1CE2 , (text_bank << 8 ) + ((offset & 0xFFFF0000 ) >> 16 ) + (1 if offset & 0xFFFF > 0x8000 else 0 ))
1015+ rom .write_int16 (0xAD1CE6 , offset & 0XFFFF )
1016+ # hard-coded message length, represented by offset of end of message
1017+ rom .write_int16 (0xAD1D16 , (text_bank << 8 ) + (((offset + new_message .size ()) & 0xFFFF0000 ) >> 16 ) + (1 if (offset + new_message .size ()) & 0xFFFF > 0x8000 else 0 ))
1018+ rom .write_int16 (0xAD1D1E , (offset + new_message .size ()) & 0XFFFF )
1019+ # hard-coded segment, default JP file (0x08)
1020+ rom .write_int16 (0xAD1D12 , (text_bank << 8 ))
1021+ # hard-coded text file start address in rom, default JP
1022+ rom .write_int16 (0xAD1D22 , ((text_start & 0xFFFF0000 ) >> 16 ) + (1 if text_start & 0xFFFF > 0x8000 else 0 ))
1023+ rom .write_int16 (0xAD1D2E , text_start & 0XFFFF )
9471024
9481025 # actually write the message
949- offset = new_message .write (rom , old_index , offset )
1026+ offset = new_message .write (rom , old_index + jp_index_offset , text_start , offset , text_bank )
9501027
9511028 new_message .id = remember_id
9521029
9531030 # raise an exception if too much is written
9541031 # we raise it at the end so that we know how much overflow there is
955- if offset > text_size_limit :
956- raise (TypeError ("Message Text table is too large: 0x" + "{:x}" .format (offset ) + " written / 0x" + "{:x}" .format (ENG_TEXT_SIZE_LIMIT ) + " allowed." ))
1032+ if jp_bytes + offset > text_size_limit :
1033+ raise (TypeError ("Message Text table is too large: 0x" + "{:x}" .format (jp_bytes + offset ) + " written / 0x" + "{:x}" .format (EXTENDED_TEXT_SIZE_LIMIT ) + " allowed." ))
9571034
958- # end the table
959- table_index = len (messages )
960- entry = bytes ([0xFF , 0xFD , 0x00 , 0x00 , 0x07 ]) + int_to_bytes (offset , 3 )
1035+ # end the table, accounting for additional entry for file split
1036+ table_index = len (messages ) + ( 1 if text_bank == 0x07 else 0 )
1037+ entry = bytes ([0xFF , 0xFD , 0x00 , 0x00 , text_bank ]) + int_to_bytes (offset , 3 )
9611038 entry_offset = EXTENDED_TABLE_START + 8 * table_index
9621039 rom .write_bytes (entry_offset , entry )
9631040 table_index += 1
@@ -982,6 +1059,7 @@ def is_exempt(m):
9821059 )
9831060 shuffle_exempt = [
9841061 0x208D , # "One more lap!" for Cow in House race.
1062+ 0xFFFC , # Character data from JP table used on title and file select screens
9851063 ]
9861064 is_hint = (except_hints and m .id in hint_ids )
9871065 is_error_message = (m .id == ERROR_MESSAGE )
0 commit comments