shithub: zelda3

Download patch

ref: 71b1c3c020f663a42eadba05437758932cda31cb
parent: aee281aa1fd1ec50acc1c1de00bb0c5c832fd68e
author: Snesrev <snesrev@protonmail.com>
date: Fri Mar 10 01:02:56 EST 2023

Add support for spanish, portuguese, polish, redux, dutch, swedish

--- a/messaging.c
+++ b/messaging.c
@@ -2220,7 +2220,7 @@
     // EU encoding
     if (a < 0x7f)
       return TEXTCMD_MK(a, kTextCmd_IsLetter, 0);
-    static const uint8 kSoundLut[1] = {45};
+    static const uint8 kSoundLut[] = {45};
     static const uint8 kReturns_Simple[] = {
       TEXTCMD_MK(0, kTextCmd_EndMessage, 0),
       TEXTCMD_MK(0, kTextCmd_Scroll, 0),
@@ -2522,6 +2522,7 @@
   
   const uint8 *kFontData = FindIndexInMemblk(g_zenv.dialogue_font_blk, 0).ptr;
   uint8 width = FindIndexInMemblk(g_zenv.dialogue_font_blk, 1).ptr[c];
+  assert(width <= 8);
 
   int i = vwf_var1++;
   uint8 arrval = vwf_arr[i];
--- a/tables/compile_resources.py
+++ b/tables/compile_resources.py
@@ -119,25 +119,28 @@
   add_asset_packed('kBgGfx', all)
 
 def print_dialogue(args):
-  from text_compression import kDialogueFilenames
+  from text_compression import dialogue_filename, kLanguages
 
   languages = ['us']
   if args.languages:
     for a in args.languages.split(','):
-      if a in languages or a not in kDialogueFilenames:
+      if a in languages or a not in kLanguages:
         raise Exception(f'Language {a} is not valid')
-      if not os.path.exists(kDialogueFilenames[a]):
-        raise Exception(f'{kDialogueFilenames[a]} not found. You need to extract it with --extract-dialogue using the ROM of that language.')
+      name = dialogue_filename(a)
+      if not os.path.exists(name):
+        raise Exception(f'{name} not found. You need to extract it with --extract-dialogue using the ROM of that language.')
       languages.append(a)
 
   all_langs, all_fonts, mappings = [], [], []
   for i, lang in enumerate(languages):
     dict_packed = pack_arrays(text_compression.encode_dictionary(lang))
-    dialogue_packed = pack_arrays(compress_dialogue(kDialogueFilenames[lang], lang))
+    dialogue_packed = pack_arrays(compress_dialogue(dialogue_filename(lang), lang))
     all_langs.append(pack_arrays([dict_packed, dialogue_packed]))
     font_data, font_width = sprite_sheets.encode_font_from_png(lang)
     all_fonts.append(pack_arrays([font_data, font_width]))
-    mappings.append(pack_arrays([lang.encode('utf8'), bytearray([i, i, i != 0])]))
+    flags = text_compression.uses_new_format(lang)
+    if i != 0: flags |= 2 # no us rom match?
+    mappings.append(pack_arrays([lang.encode('utf8'), bytearray([i, i, flags])]))
   add_asset_packed('kDialogue', all_langs)
   add_asset_packed('kDialogueFont', all_fonts)
   add_asset_packed('kDialogueMap', mappings)
--- a/tables/extract_resources.py
+++ b/tables/extract_resources.py
@@ -240,7 +240,7 @@
       print_overworld_area(i)
 
 def print_dialogue():
-  text_compression.print_strings(util.ROM, file = open(text_compression.kDialogueFilenames[util.ROM.language], 'w', encoding='utf8'))
+  text_compression.print_strings(util.ROM, file = open(text_compression.dialogue_filename(util.ROM.language), 'w', encoding='utf8'))
 
 def decode_room_objects(p):
   objs = []
--- a/tables/restool.py
+++ b/tables/restool.py
@@ -8,7 +8,7 @@
 
 optional = parser.add_argument_group('Language settings')
 optional.add_argument('--extract-dialogue', action='store_true', help = 'Extract dialogue from the german ROM')
-optional.add_argument('--languages', action='store', metavar='L1,L2', help = 'Comma separated list of additional languages to build (de,fr,fr-c,en).')
+optional.add_argument('--languages', action='store', metavar='L1,L2', help = 'Comma separated list of additional languages to build (de,fr,fr-c,en,es,pl,pt,redux,nl,sv).')
 
 optional = parser.add_argument_group('Debug things')
 optional.add_argument('--no-build', action='store_true', help="Don't actually build zelda3_assets.dat")
@@ -23,8 +23,8 @@
 if args.extract_dialogue:
   ROM = util.load_rom(args.rom, True)
   import extract_resources, sprite_sheets
-  extract_resources.print_dialogue()
   sprite_sheets.decode_font()
+  extract_resources.print_dialogue()
   sys.exit(0)
 
 ROM = util.load_rom(args.rom)
--- a/tables/sprite_sheets.py
+++ b/tables/sprite_sheets.py
@@ -136,13 +136,29 @@
 
   save_as_png((128, 64 * 3), dst, 'hud_icons.png', convert_snes_palette(get_hud_snes_palette()[:128]))
 
+def get_pt_remapper():
+  b = util.ROM.get_bytes(0x8EFC09, 121 * 3)
+  d = {}
+  for i in range(121):
+    ch = (i & 0xf) | (i << 1) & 0xe0
+    d[ch] = b[i*3+0]
+    d[ch|0x10] = b[i*3+1]
+  return d
+
 kFontTypes = {
-  'us'   : (0xe8000, 256, 'font.png', (0x8ECADF, 99)),
+  'us'   : (0x8e8000, 256, 'font.png', (0x8ECADF, 99)),
   'de'   : (0xCC6E8, 256, 'font_de.png', (0x8CDECF, 112)),
   'fr'   : (0xCC6E8, 256, 'font_fr.png', (0x8CDEAF, 112)),
   'fr-c' : (0xCD078, 256, 'font_fr_c.png', (0x8CE83F, 112)),
   'en'   : (0x8E8000, 256, 'font_en.png', (0x8ECAFF, 102)),
+  'es'   : (0x8e8000, 256, 'font_es.png', (0x8ECADF, 99)),
+  'pl'   : (0x8e8000, 256, 'font_pl.png', (0x8ECADF, 99)),
+  'pt'   : (0x8e8000, 256, 'font_pt.png', (0x8ECADF, 121)),
+  'redux': (0x8e8000, 256, 'font_redux.png', (0x8ECADF, 99)),
+  'nl': (0x8e8000, 256, 'font_nl.png', (0x8ECADF, 99)),
+  'sv': (0x8e8000, 256, 'font_sv.png', (0x8ECADF, 99)),
 }
+
 def decode_font():
   lang = util.ROM.language
   def decomp_one_spr_2bit(data, offs, target, toffs, pitch, palette_base):
@@ -152,17 +168,24 @@
         t = ((d0 >> x) & 1) * 1 + ((d1 >> x) & 1) * 2
         target[toffs + y * pitch + (7 - x)] = t + palette_base
   ft = kFontTypes[lang]
-  W = get_bytes(*ft[3])
+  if lang == 'pt':
+    W = util.ROM.get_bytes(0x8EFC09, 121 * 3)
+    W = [W[i*3+2] for i in range(121)]
+    remapper = get_pt_remapper()
+  else:
+    W = get_bytes(*ft[3])
+    remapper = {}
   w = 128 + 15
   hi = ft[1] // 32
   h = hi * 17
   data = get_bytes(ft[0], ft[1] * 16)
   dst = bytearray(w * h)
+  
   for i in range(ft[1]):
     x, y = i % 16, i // 16
     pal_base = 6 * 16
     base_offs = x * 9 + (y * 8 + (y >> 1)) * w
-    decomp_one_spr_2bit(data, i * 16, dst, base_offs + w, w, pal_base)
+    decomp_one_spr_2bit(data, remapper.get(i, i) * 16, dst, base_offs + w, w, pal_base)
     if (y & 1) == 0:
       j = (y >> 1) * 16 + x
       if j < len(W):
@@ -172,7 +195,8 @@
   pal[0], pal[1], pal[2] = 192, 192, 192
   pal[255*3+0], pal[255*3+1], pal[255*3+2] = 128, 128, 128
   save_as_png((w, h), dst, ft[2], pal)
-  assert (data, W) == encode_font_from_png(lang)
+  if lang != 'pt':
+    assert (data, W) == encode_font_from_png(lang)
 
 def encode_font_from_png(lang):
   font_data = Image.open(kFontTypes[lang][2]).tobytes()
--- a/tables/text_compression.py
+++ b/tables/text_compression.py
@@ -6,12 +6,10 @@
   "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
   "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
   "-", ".", ",", 
-
   # 64 - 79
   "[...]", ">", "(", ")",
   "[Ankh]", "[Waves]", "[Snake]", "[LinkL]", "[LinkR]",
   "\"", "[Up]", "[Down]", "[Left]",
-
   # 80 - 95
   "[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
   "[4HeartL]", "[4HeartR]", " ", "<", "[A]", "[B]", "[X]", "[Y]",
@@ -144,6 +142,58 @@
 ]
 
 
+# Uses the original format
+def org_encoder(cmd, param):
+    if cmd not in kText_CommandNames_US:
+      raise Exception(f'Invalid cmd {cmd}')
+    cmd_index = kText_CommandNames_US.index(cmd)
+    if kText_CommandLengths_US[cmd_index] != (1 if param == None else 2):
+      raise Exception(f'Invalid cmd params {cmd} {param}')
+    if param == None:
+      return [cmd_index + 0x67]
+    return [cmd_index + 0x67, int(param)]
+
+kCmdInfo = {
+  "Scroll" : (0x80, ),
+  "Waitkey" : (0x81, ),
+  "1" : (0x82, ),
+  "2" : (0x83, ),
+  "3" : (0x84, ),
+  "Name" : (0x85, ),
+  "Wait" : (0x87, {i:i+0x00 for i in range(16)}),
+  "Color" : (0x87, {i:i+0x10 for i in range(16)}),
+  "Number" : (0x87, {i:i+0x20 for i in range(16)}),
+  "Speed" : (0x87, {i:i+0x30 for i in range(16)}),
+  "Sound" : (0x87, {45 : 0x40, 64 : None}), # pt uses 64 for some reason
+  "Choose" : (0x87, 0x80),
+  "Choose2" : (0x87, 0x81),
+  "Choose3" : (0x87, 0x82),
+  "Selchg" : (0x87, 0x83),
+  "Item" : (0x87, 0x84),
+  "NextPic" : (0x87, 0x85),
+  "Window" : (0x87, {0 : None, 2 : 0x86}),
+  "Position" : (0x87, {0: 0x87, 1: 0x88}),
+  "ScrollSpd" : (0, {0 : None}),
+}
+
+# Uses another format where there's more bytes left for characters
+def new_encoder(cmd, param):
+  if cmd not in kCmdInfo:
+    raise Exception(f'Invalid cmd {cmd}')
+  info = kCmdInfo[cmd]
+  if len(info) <= 1 or isinstance(info[1], int):
+    if param != None:
+      raise Exception(f'Invalid cmd params {cmd} {param}')
+    return info
+  else:
+    if param == None or param not in info[1]:
+      raise Exception(f'Invalid cmd params {cmd} {param}')
+    r = info[1][param]
+    return (info[0], r) if r != None else ()
+
+kEncoders = {'org' : org_encoder, 'new' : new_encoder}
+
+
 class LangUS:
   alphabet = kTextAlphabet_US
   dictionary = kTextDictionary_US
@@ -154,11 +204,8 @@
   SWITCH_BANK = 0x80
   FINISH = 0xff
   DICT_BASE_ENC, DICT_BASE_DEC = 0x88, 0x88
-  def encode_command(self, cmd_index, param):
-    name = self.command_names[cmd_index]
-    if param == None:
-      return [cmd_index + self.COMMAND_START]
-    return [cmd_index + self.COMMAND_START, int(param)]
+  ESCAPE_CHARACTER = None
+  encoder = 'org'
 
 class LangEN(LangUS):
   alphabet = kTextAlphabet_DE
@@ -165,6 +212,157 @@
   dictionary = kTextDictionary_US
   rom_addrs = [0x9c8000, 0x8edf60]
 
+class LangES(LangUS):
+  alphabet = [
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
+    "Q", "R", "S", "T", "U", "V", "W", "é", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
+    "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
+    "ó", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
+    "[Waves]", ".", ",", 
+    # 64 - 79
+    "[...]", ">", "(", ")",
+    "ñ", "ú", "á", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
+    # 80 - 95
+    "[Right]", "í", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
+    "[Ankh]", "[4HeartR]", " ", "[Snake]", "[A]", "[B]", "[X]", "[Y]", "[I]",
+    "¡", "¿", "Ñ"
+  ]
+  # "[Ankh]", "[Waves]", "[Snake]"
+
+  dictionary = [
+    '    ', '   ', '  ', ' en', ' la ', ' el ', ' de ', 'ien', 'tra', ' de', 
+    'te ', 'ar', 'a ', 'ada', 'es', 'as', 'o ', ' con', 'ero', 'ado', 
+    'e ', 'que', 'en', 'al', 'os ', 'ora', 'nte', ' al', 'lo ', 'or', 
+    'os', 'er', 'aci', 'res', ' que ', ' es', 'el', 'los ', 'tar', ' se', 
+    ', ', 'ro', ' de l', ' est', 're', 'on', 'an', 'pued', ' del', 'ás ', 
+    'la', 'ti', 'la ', 'Es', 'to', 'ta', 'para', 'uer', 'ier', ' un ', 
+    ' por', 'oder', 'da', 'in', 'cu', ' ha', 'per', 'ano', ' ve', 'cer', 
+    'lo', ' no ', 'ic', 'ra', 'ab', 'ir', ' una', 'undo', 'es ', 'as ', 
+    'con', 'a, ', 'te', ' m', 'gu', ' tu', 'ando', ' p', 'de', 'le', 
+    'ol', 'o, ', 'ten', 'lle', ' a ', 'aba', 'com', 
+  ]
+  rom_addrs = [0x9c8000, 0x8edf40]
+
+
+class LangNL(LangUS):
+  alphabet = [
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
+    "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
+    "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
+    "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
+    "-", ".", ",", "[...]", ">", "(", ")", "[Ankh]",
+    "[Waves]", "[Snake]", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
+    # 80 - 95
+    "[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
+    "[4HeartL]", "[4HeartR]", " ", "<", "[A]", "[B]", "[X]", "[Y]",
+  ]
+
+  dictionary = [
+    '    ', '   ', '  ', "'s ", 'and ', 'are ', 'all ', 'ain', 'and', 'at ', 
+    'ast', 'an', 'at', 'ble', 'ba', 'be', 'bo', 'can ', 'che', 'com', 
+    'ck', 'des', 'di', 'do', 'en ', 'er ', 'ear', 'ent', 'ed ', 'en', 
+    'er', 'ev', 'for', 'fro', 'give ', 'get', 'go', 'have', 'has', 'her', 
+    'hi', 'ha', 'ight ', 'ing ', 'in', 'is', 'it', 'just', 'know', 'ly ', 
+    'la', 'lo', 'man', 'ma', 'me', 'mu', "n't ", 'non', 'not', 'open', 
+    'ound', 'out ', 'of', 'on', 'or', 'per', 'ple', 'pow', 'pro', 're ', 
+    're', 'some', 'se', 'sh', 'so', 'st', 'ter ', 'thin', 'ter', 'tha', 
+    'the', 'thi', 'to', 'tr', 'up', 'ver', 'with', 'wa', 'we', 'wh', 
+    'wi', 'you', 'Her', 'Tha', 'The', 'Thi', 'You', 
+  ]
+  rom_addrs = [0x9c8000, 0x8edf40]
+
+
+
+class LangSV(LangUS):
+  alphabet = [
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "Ö", "P", # 0 - 15
+    "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
+    "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
+    "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
+    "å", ".", ",", "ä", ">", "(", ")","ö",
+    "Å", "Ä", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
+    # 80 - 95
+    "[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
+    "[4HeartL]", "[4HeartR]", " ", "<", "[Ankh]", "[Waves]", "[Snake]", "-", "[I]",
+    "[i]", "…", " ",
+  ]
+
+  dictionary = [
+    '    ', '   ', '  ', 'Du ', 'till', 'vill', 'bara', 'det', 'den', 'och', 
+    'en ', 'r ', 'n ', 'ett', 'en', ' d', 'a ', 'Hjäl', 'har', 'ter', 
+    't ', 'var', ' s', 'de', 'kan', 'med', 'som', 'för', 'att', 'ar', 
+    ' h', 'er', 'jag', 'dig', 'öppna', 'mig', 'är', 'inte', 'hit', 'på ', 
+    'an', 'e ', 'rupie', '0kej', ' m', 'et', ', ', 'gång', 'måst', 'ten', 
+    ' f', 'u ', 'men', 'te', 'tt', 'ka', 'vara', 'ken', '0m ', 'från', 
+    'myck', 'någo', 'in', ' k', ' i', 'vil', 'bar', 'ond', 'För', 'Jag', 
+    'ra', 'tack', 'll', 'g ', 'ta', 'om', 'anna', 'alla', 'en,', 'ber', 
+    'hem', 'han', 'st', 'ig', ' t', 'tro', 'kraf', 'ör', ' v', 'ag', 
+    '… ', 'får', 'sin', 'mme', 'mma', 'en ', 'tat',
+  ]
+  rom_addrs = [0x9c8000, 0x8edf40]
+
+
+class LangPL(LangUS):
+  alphabet = [
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
+    "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
+    "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
+    "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
+    "-", ".", ",", "ć", "[Right]", "(", ")", "[Ankh]", 
+    "[Waves]", "[Snake]", "[LinkL]", "[LinkR]","\"", "[Up]", "[Down]", "ę",
+
+    "ł", "ń", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
+    "ą", "[4HeartR]", " ", "[Left]", "ó", "ś", "ż", "ź", "Ł",
+    "Ś", "Ż", "Ź",
+  ] # 
+  dictionary = ['Trój', '...', 'ść', 'Nie', ' nie', ' się', 'może', ' że', 'and', 'at ', 
+    ' ty', 'an', 'at', 'kus', 'ba', 'be', 'bo', 'chce', 'che', 'ki ', 
+    'za', 'des', 'di', 'do', 'en ', 'er ', 'sz ', 'ent', 'ed ', 'en', 
+    'er', ' w', 'moc', 'zię', 'przez', 'ale', 'go', 'dzie', 'has', 'rze', 
+    'hi', 'ha', 'który', 'aby ', 'in', 'is', 'it', 'twoj', 'Może', 'łeś', 
+    'la', 'lo', 'czn', 'ma', 'me', 'mu', 'szcz', 'ska', 'śli', 'przy', 
+    'znaj', 'iecz', 'of', 'on', 'or', '   ', 'ple', 'pow', 'pro', 're ', 
+    're', 'mnie', 'se', ' z', 'so', 'st', 'któr', ' jak', 'ksz', 'sze', 
+    'coś', ' je', 'to', 'tr', 'up', 'kie', 'praw', 'wa', 'we', 'mi', 
+    'wi', 'szy', 'chc', 'pra', 'cie', ' i ', 'esz',
+  ]
+
+  rom_addrs = [0x9c8000, 0x8edf40]
+
+class LangPT(LangUS):
+  alphabet = [
+    "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", # 0 - 15
+    "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", # 16 - 31
+    "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", # 32 - 47
+    "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "!", "?", # 48 - 63
+    "-", ".", ",", "[...]", ">", "(", ")", "[Ankh]",
+    "[Waves]", "[Snake]", "[LinkL]", "[LinkR]", "\"", "[Up]", "[Down]", "[Left]",
+    # 80 - 95
+    "[Right]", "'", "[1HeartL]", "[1HeartR]", "[2HeartL]", "[3HeartL]", "[3HeartR]",
+    "[4HeartL]", "[4HeartR]", " ", "<", "[A]", "[B]", "[X]", "[Y]", "[I]",
+
+    "¡", "[!]", "Á", "À", "Â", "Ã", "É", "Ê", "Í", "Ó", "Ô", "Õ", "Ú", "á", "à", "â", 
+    "ã", "é", "ê", "í", "ó", "ô", "õ", "ú", "ç", 
+  ]
+  dictionary = [
+    '     ', '    ', '   ', '                                          ', 'o ', 'a ', 'e ', '..', 'de', 'ar', 
+    's ', 'ra', ' d', 'es', 'ocê ', 'do', ' a', ' p', 'er', ' e', 
+    'que', 'r ', 'os', 'te', ', ', 'as', 'or', 'm ', 'en', ' o', 
+    'nt', 're', ' s', 'co', 'da', 'se', 'st', ' c', ' m', 'em', 
+    'ma', 'ta', ' n', 'ad', 'on', 'al', 'ro', 'an', 'u ', 'nd', 
+    ' um', 'pa', 'ca', 'el', ' f', 'to', 'in', ' t', 'ou', 'ei', 
+    'ss', 'ir', 'no', 'ri', 'tr', 'me', 'la', 'ia', 'le', 've', 
+    'is', 'sa', 'eu', 'pe', 'a.', 'na', 'so', 'mo', 'ga', 'o.', 
+    'á ', 'lo', 'ha', 'pr', 'ua', ' l', '! ', 'ui', 'am', 'ti', 
+    'io', 'gu', 'i ', 'di', 'nh', ' i', 'id', 
+  ]
+  ESCAPE_CHARACTER = 0x62
+  rom_addrs = [0x9c8000, 0x8edf40]
+  encoder = 'new'
+
+  def __init__(self):
+    assert len(self.alphabet) == 121
+
 class LangEU:
   command_lengths = kText_CommandLengths_EU
   command_names = kText_CommandNames_EU
@@ -172,41 +370,9 @@
   SWITCH_BANK = 0x88
   FINISH = 0x8f
   DICT_BASE_ENC, DICT_BASE_DEC = 0x88, 0x90
-  US = False
+  ESCAPE_CHARACTER = None
+  encoder = 'new'
 
-  kCmdInfo = {
-    "Scroll" : (0x80, ),
-    "Waitkey" : (0x81, ),
-    "1" : (0x82, ),
-    "2" : (0x83, ),
-    "3" : (0x84, ),
-    "Name" : (0x85, ),
-    "Wait" : (0x87, {i:i+0x00 for i in range(16)}),
-    "Color" : (0x87, {i:i+0x10 for i in range(16)}),
-    "Number" : (0x87, {i:i+0x20 for i in range(16)}),
-    "Speed" : (0x87, {i:i+0x30 for i in range(16)}),
-    "Sound" : (0x87, {45 : 0x40}),
-    "Choose" : (0x87, 0x80),
-    "Choose2" : (0x87, 0x81),
-    "Choose3" : (0x87, 0x82),
-    "Selchg" : (0x87, 0x83),
-    "Item" : (0x87, 0x84),
-    "NextPic" : (0x87, 0x85),
-    "Window" : (0x87, {0 : None, 2 : 0x86}),
-    "Position" : (0x87, {0: 0x87, 1: 0x88}),
-    "ScrollSpd" : (0, {0 : None}),
-  }
-
-  def encode_command(self, cmd_index, param):
-    info = self.kCmdInfo[self.command_names[cmd_index]]
-    if len(info) <= 1 or isinstance(info[1], int):
-      assert param == None
-      return info
-    else:
-      assert param != None
-      r = info[1][param]
-      return (info[0], r) if r != None else ()
-
 class LangDE(LangEU):
   alphabet = kTextAlphabet_DE
   dictionary = kTextDictionary_DE
@@ -222,8 +388,6 @@
   dictionary = kTextDictionary_FR
   rom_addrs = [0x9c8000, 0x8CF150]
 
-
-
 kLanguages = {
   'us' : LangUS(),
   'de' : LangDE(),
@@ -230,16 +394,21 @@
   'fr' : LangFR(),
   'fr-c' : LangFR_C(),
   'en' : LangEN(),
+  'es' : LangES(),
+  'pl' : LangPL(),
+  'pt' : LangPT(),
+  'redux' : LangUS(),
+  'nl' : LangNL(),
+  'sv' : LangSV(),
 }
 
-kDialogueFilenames = {
-  'us' : 'dialogue.txt',
-  'de' : 'dialogue_de.txt',
-  'fr' : 'dialogue_fr.txt',
-  'fr-c' : 'dialogue_fr_c.txt',
-  'en' : 'dialogue_en.txt',
-}
+def dialogue_filename(s):
+  if s == 'us': return 'dialogue.txt'
+  return f"dialogue_{s.replace('-', '_')}.txt"
 
+def uses_new_format(lang): 
+  return kLanguages[lang].encoder == 'new'
+
 dict_expansion = []
 
 def decode_strings_generic(get_byte, lang):
@@ -257,6 +426,9 @@
       if c == 0x7f: # EndMessage
         break
       if c < info.COMMAND_START:
+        if c == info.ESCAPE_CHARACTER:
+          c = get_byte(p); p += 1
+          srcdata.append(c)
         s += info.alphabet[c]
       elif c < info.SWITCH_BANK:
         if l == 2:
@@ -270,12 +442,20 @@
         p = info.rom_addrs[rom_idx]; rom_idx += 1
         s, srcdata = '', []
       elif c < info.SWITCH_BANK + 8:
-        assert 0
+        if lang != 'pt':
+          assert 0
       else:
         s += info.dictionary[c - info.DICT_BASE_DEC]
         dict_expansion.append(len(info.dictionary[c - info.DICT_BASE_DEC]))
-    result.append((s, srcdata))
+    result.append((s, srcdata)) 
+    if len(result) >= 397 and lang == 'pt':
+      return result
+#    print(len(result), s)   
+#    if len(result) == 89:
+#      print(s)
+#      sys.exit(0)
 
+
   
 def print_strings(rom, file = None):
   texts = decode_strings_generic(rom.get_byte, rom.language)
@@ -302,12 +482,7 @@
     if ' ' in cmd:
       cmd, param = cmd.split(' ', 1)
       param = int(param)
-    if cmd not in info.command_names:
-      raise Exception(f'Invalid cmd {cmd}')
-    i = info.command_names.index(cmd)
-    if info.command_lengths[i] != (1 if param == None else 2):
-      raise Exception(f'Invalid cmd params {cmd} {param}')
-    return info.encode_command(i, param), cmdlen + 2
+    return kEncoders[info.encoder](cmd, param), cmdlen + 2
   else:
     return [a2i[a[0]]], 1
 
--- a/tables/util.py
+++ b/tables/util.py
@@ -19,6 +19,12 @@
   '170d4963a3f5854b2ab3b8203a75ee93034e8f2fff8ff587d9c1bdef9bd04984' : 'fr-c', # French Canadian
    ZELDA3_SHA256_US : 'us',
    'b7640393ff2cb47ec7c4c061b78f9f34df14663f1d52e171697661a542d01d0d' : 'en',  # English PAL ROM
+   '0793101a75f493453f9412b700dc1076a31fc8079665c7af28257f975002d5dd' : 'es',  # Spanish rom https://www.romhacking.net/translations/2195/
+   '773856c208a83b0d2c653675e33436040073915d2087f3803e25eb1506f27512' : 'pl',  # Polish https://www.romhacking.net/translations/5760/
+   'e414324d8c2e2dcdbbf033b3e028897554df0d13affe5ee4484439a3e8d79b18' : 'pt',  # Portuguese https://www.romhacking.net/translations/6530/
+   '813bf9e5460a74aff2b2fdc5bc6ab14821ff0212c7546885e678737de969e5e2' : 'redux',  # English Redux - https://www.romhacking.net/translations/6657/
+   '28275ab10572e93717e8a77a65baae33577cd04ef95f7268d21028b16deb376c' : 'nl', # Netherlands/Dutch - https://www.romhacking.net/translations/1124/
+   'cf86ba00690cc6290ed3a229aa2bb460274b822de39eef694cda3576518184e3' : 'sv', # Sweden/Swedish - https://www.romhacking.net/translations/982/
 }
 
 def load_rom(filename, support_multilanguage = False):
@@ -58,7 +64,7 @@
     hash = hashlib.sha256(self.ROM).hexdigest()
     self.language = ZELDA3_SHA256.get(hash)
 
-    if  len(self.ROM) == 1049088:
+    if  len(self.ROM) == 1049088 or self.language in ('sv',):
       self.ROM = self.ROM[0x200:]
 
     if support_multilanguage: