diff --git a/ScriptTool/ScriptTool.sln b/ScriptTool/ScriptTool.sln
new file mode 100644
index 0000000..783d62f
--- /dev/null
+++ b/ScriptTool/ScriptTool.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.31101.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScriptTool", "ScriptTool\ScriptTool.csproj", "{CA5C95AE-2CD2-47E3-B5AE-FE6136808AC4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {CA5C95AE-2CD2-47E3-B5AE-FE6136808AC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CA5C95AE-2CD2-47E3-B5AE-FE6136808AC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CA5C95AE-2CD2-47E3-B5AE-FE6136808AC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CA5C95AE-2CD2-47E3-B5AE-FE6136808AC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/ScriptTool/ScriptTool/App.config b/ScriptTool/ScriptTool/App.config
new file mode 100644
index 0000000..9c05822
--- /dev/null
+++ b/ScriptTool/ScriptTool/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ScriptTool/ScriptTool/CommandOptions.cs b/ScriptTool/ScriptTool/CommandOptions.cs
new file mode 100644
index 0000000..21cecda
--- /dev/null
+++ b/ScriptTool/ScriptTool/CommandOptions.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScriptTool
+{
+ class CommandOptions
+ {
+ public string WorkingDirectory { get; set; }
+ public CommandType Command { get; set; }
+ public bool DoMiscText { get; set; }
+ public bool DoMainText { get; set; }
+ public string EbRom { get; set; }
+ public string M12Rom { get; set; }
+ }
+
+ enum CommandType
+ {
+ Compile,
+ Decompile
+ }
+}
diff --git a/ScriptTool/ScriptTool/ControlCode.cs b/ScriptTool/ScriptTool/ControlCode.cs
new file mode 100644
index 0000000..febd911
--- /dev/null
+++ b/ScriptTool/ScriptTool/ControlCode.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+
+namespace ScriptTool
+{
+ class ControlCode
+ {
+ public IList Identifier { get; private set; }
+ public bool End { get; set; }
+ public bool Multiple { get; set; }
+ public int Length { get; set; }
+ public string Description { get; set; }
+ const string HexChars = "0123456789ABCDEFabcdef";
+
+ public ControlCode()
+ {
+ Identifier = new List();
+ }
+
+ public bool Match(byte[] rom, int address)
+ {
+ for (int i = 0; i < Identifier.Count; i++)
+ if (rom[address + i] != Identifier[i])
+ return false;
+ return true;
+ }
+
+ public bool BeginsWith(params byte[] bytes)
+ {
+ if (bytes.Length > Identifier.Count)
+ return false;
+
+ var first = Identifier.Take(bytes.Length);
+ if (!first.SequenceEqual(bytes))
+ return false;
+
+ return true;
+ }
+
+ public override string ToString()
+ {
+ return Description;
+ }
+
+ public static IList LoadEbControlCodes(string path)
+ {
+ var codeList = new List();
+ string[] lines = File.ReadAllLines(path);
+
+ foreach (var line in lines)
+ {
+ var code = new ControlCode();
+ string def = line.Substring(0, line.IndexOf(','));
+ string desc = line.Substring(line.IndexOf(',') + 1);
+ code.Description = desc.Substring(1, desc.Length - 3);
+
+ while (true)
+ {
+ if (def.StartsWith("!"))
+ {
+ code.End = true;
+ def = def.Substring(1);
+ continue;
+ }
+
+ if (def.StartsWith("*"))
+ {
+ code.Multiple = true;
+ def = def.Substring(1);
+ continue;
+ }
+
+ break;
+ }
+
+ string[] defs = def.Split(' ');
+ for (int i = 0; i < defs.Length; i++)
+ {
+ if (!HexChars.Contains(defs[i][0]))
+ break;
+
+ code.Identifier.Add(byte.Parse(defs[i], System.Globalization.NumberStyles.HexNumber));
+ }
+
+ if (code.Multiple)
+ code.Length = -1;
+ else
+ code.Length = defs.Length;
+
+ codeList.Add(code);
+ }
+
+ return codeList;
+ }
+
+ public static IList LoadM12ControlCodes(string path)
+ {
+ var codeList = new List();
+ string[] lines = File.ReadAllLines(path);
+
+ foreach (var line in lines)
+ {
+ var code = new ControlCode();
+ string def = line.Substring(0, line.IndexOf(','));
+ string desc = line.Substring(line.IndexOf(',') + 1);
+ code.Description = desc;
+
+ while (true)
+ {
+ if (def.StartsWith("!"))
+ {
+ code.End = true;
+ def = def.Substring(1);
+ continue;
+ }
+
+ if (def.StartsWith("*"))
+ {
+ code.Multiple = true;
+ def = def.Substring(1);
+ continue;
+ }
+
+ break;
+ }
+
+ string[] defs = def.Split(' ');
+ code.Identifier.Add(byte.Parse(defs[0], System.Globalization.NumberStyles.HexNumber));
+ code.Identifier.Add(0xFF);
+
+ if (code.Multiple)
+ code.Length = -1;
+ else
+ code.Length = defs.Length;
+
+ codeList.Add(code);
+ }
+
+ return codeList;
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/DecompileContext.cs b/ScriptTool/ScriptTool/DecompileContext.cs
new file mode 100644
index 0000000..46bf7f2
--- /dev/null
+++ b/ScriptTool/ScriptTool/DecompileContext.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScriptTool
+{
+ class DecompileContext
+ {
+ public LabelMap LabelMap { get; private set; }
+ public IList Strings { get; private set; }
+
+ public DecompileContext()
+ {
+ LabelMap = new LabelMap();
+ Strings = new List();
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/EbTextDecompiler.cs b/ScriptTool/ScriptTool/EbTextDecompiler.cs
new file mode 100644
index 0000000..fc74972
--- /dev/null
+++ b/ScriptTool/ScriptTool/EbTextDecompiler.cs
@@ -0,0 +1,324 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ class EbTextDecompiler
+ {
+ private IList _controlCodes;
+ public IList ControlCodes
+ {
+ get { return _controlCodes; }
+ set
+ {
+ // Grab the jump table codes
+ foreach (var code in value)
+ {
+ if (code.BeginsWith(9))
+ jumpTableReturn = code;
+
+ if (code.BeginsWith(0x1f, 0xc0))
+ jumpTableNoReturn = code;
+ }
+
+ _controlCodes = value;
+ }
+ }
+
+ private ControlCode jumpTableReturn;
+ private ControlCode jumpTableNoReturn;
+
+ private const string charMapEb = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZØØØØØ`abcdefghijklmnopqrstuvwxyz{|}~\\";
+
+ private static string[][] compressedStringsEb;
+ private static IList textRanges = new List();
+
+ static EbTextDecompiler()
+ {
+ // Load compressed strings
+ compressedStringsEb = new string[3][];
+ string[] stringsFromFile = File.ReadAllLines(@"eb-compressed-strings.txt");
+ compressedStringsEb[0] = stringsFromFile.Take(0x100).ToArray();
+ compressedStringsEb[1] = stringsFromFile.Skip(0x100).Take(0x100).ToArray();
+ compressedStringsEb[2] = stringsFromFile.Skip(0x200).Take(0x100).ToArray();
+
+ // Load text ranges
+ textRanges.Add(new int[] { 0x50000, 0x5FFEC });
+ textRanges.Add(new int[] { 0x60000, 0x6FFE3 });
+ textRanges.Add(new int[] { 0x70000, 0x7FF40 });
+ textRanges.Add(new int[] { 0x80000, 0x8BC2D });
+ textRanges.Add(new int[] { 0x8D9ED, 0x8FFF3 });
+ textRanges.Add(new int[] { 0x90000, 0x9FF2F });
+ textRanges.Add(new int[] { 0x2F4E20, 0x2FA460 });
+ }
+
+ public void Decompile(byte[] rom, int[] addresses, DecompileContext context)
+ {
+ if (ControlCodes == null)
+ throw new Exception("Codelist is null");
+
+ // First pass -- define labels
+ foreach (var address in addresses)
+ context.LabelMap.Append(address);
+
+ foreach(var range in textRanges)
+ ScanAt(rom, range[0], range[1], context, ScanMode.FirstPass);
+
+ // Second pass -- decompile the strings
+ foreach (var range in textRanges)
+ ScanAt(rom, range[0], range[1], context, ScanMode.SecondPass);
+ }
+
+ private void ScanAt(byte[] rom, int startAddress, int endAddress, DecompileContext context, ScanMode mode, bool newLines = true)
+ {
+ bool ended = false;
+ bool foundMatch = false;
+ bool prev1902 = false;
+
+ var sb = new StringBuilder();
+
+ int address = startAddress;
+
+ if (address == 0)
+ {
+ throw new Exception("Null pointer");
+ }
+
+ if (mode == ScanMode.FirstPass)
+ {
+ context.LabelMap.Append(address);
+ }
+
+ while (!ended)
+ {
+ // Check for label definition
+ if (mode == ScanMode.SecondPass && context.LabelMap.Labels.ContainsKey(address))
+ {
+ sb.Append("^" + context.LabelMap[address] + "^");
+ }
+
+ // Check for control codes
+
+ // No codes begin with a value above 0x1F, so check for that first
+ if (rom[address] < 0x20)
+ {
+ // Loop through each control code until we find a match
+ foundMatch = false;
+ foreach (var code in ControlCodes)
+ {
+ if (code.Match(rom, address))
+ {
+ foundMatch = true;
+
+ if (mode == ScanMode.SecondPass && !IsCompressedCode(code))
+ {
+ // Output the start of the code block
+ sb.Append('[');
+
+ // Output the identifier bytes
+ sb.Append(String.Join(" ", code.Identifier.Select(b => b.ToString("X2")).ToArray()));
+ }
+
+ // Skip the identifier bytes
+ address += code.Identifier.Count;
+
+ // Found a match -- check if it's variable-length
+ if (code.Multiple)
+ {
+ if (code.BeginsWith(9) ||
+ code.BeginsWith(0x1F, 0xC0))
+ {
+ int count = rom[address++];
+
+ if (mode == ScanMode.SecondPass)
+ {
+ sb.Append(' ');
+ sb.Append(count.ToString("X2"));
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ int jump = rom.ReadSnesPointer(address);
+ address += 4;
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass)
+ {
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+ }
+ }
+ else
+ {
+ // Check if it references any other addresses -- scan those too
+ if (code.BeginsWith(8) ||
+ code.BeginsWith(0xA) ||
+ code.BeginsWith(0x1B, 0x02) ||
+ code.BeginsWith(0x1B, 0x03) ||
+ code.BeginsWith(0x1F, 0x63))
+ {
+ // Single address at next byte
+ int jump = rom.ReadSnesPointer(address);
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass)
+ {
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+
+ else if (code.BeginsWith(0x1F, 0x66) ||
+ code.BeginsWith(6))
+ {
+ // Skip two bytes; single address afterwards
+ int jump = rom.ReadSnesPointer(address + 2);
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass)
+ {
+ sb.Append(' ');
+ sb.Append(rom[address].ToString("X2"));
+ sb.Append(' ');
+ sb.Append(rom[address + 1].ToString("X2"));
+
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+
+ else if (
+ code.BeginsWith(0x1F, 0x18) ||
+ code.BeginsWith(0x1F, 0x19))
+ {
+ // Check
+ }
+
+ else if (mode == ScanMode.SecondPass &&
+ (code.BeginsWith(0x15) ||
+ code.BeginsWith(0x16) ||
+ code.BeginsWith(0x17)))
+ {
+ // Check for compressed codes
+ int bank = code.Identifier[0] - 0x15;
+ int index = rom[address];
+ sb.Append(compressedStringsEb[bank][index]);
+ }
+
+ else
+ {
+ // Regular control code -- output the rest of the bytes
+ if (mode == ScanMode.SecondPass)
+ {
+ for (int i = 0; i < (code.Length - code.Identifier.Count); i++)
+ {
+ sb.Append(' ');
+ sb.Append(rom[address + i].ToString("X2"));
+ }
+ }
+ }
+
+ // Skip the rest of the bytes
+ address += code.Length - code.Identifier.Count;
+ }
+
+ if (mode == ScanMode.SecondPass && !IsCompressedCode(code))
+ {
+ // Output the end of the code block
+ sb.Append(']');
+ }
+
+ // End the block if necessary
+ if (address >= endAddress)
+ {
+ ended = true;
+ }
+
+ // Insert a newline after each end code for readibility
+ if (mode == ScanMode.SecondPass && code.End && !prev1902)
+ {
+ sb.AppendLine();
+ /*sb.Append('(');
+ sb.Append(address.ToString("X"));
+ sb.Append(')');*/
+ }
+
+ // Check if we're in a menu string
+ if (code.BeginsWith(0x19, 0x02))
+ prev1902 = true;
+ else
+ prev1902 = false;
+
+ break;
+ }
+ }
+
+ if (!foundMatch)
+ {
+ // Bad!
+ throw new Exception("Found unknown control code");
+ }
+ }
+ else
+ {
+ // It's not a control code -- just skip it
+ if (mode == ScanMode.SecondPass)
+ sb.Append(CharLookup(rom[address]));
+
+ address++;
+ }
+ }
+
+ if (mode == ScanMode.SecondPass)
+ context.Strings.Add(sb.ToString());
+
+ return;
+ }
+
+ private string CharLookup(byte value)
+ {
+ if (value >= 0x8B && value <= 0x8F)
+ {
+ // Greek letters -- output hex literals instead
+ return "[" + value.ToString("X2") + "]";
+ }
+ else if (value == 0xAF)
+ {
+ // Musical note -- output \ instead of the garbage char that normally would get outputted
+ return "\\";
+ }
+ else
+ {
+ return charMapEb[value - 0x50].ToString();
+ }
+ }
+
+ private bool IsCompressedCode(ControlCode code)
+ {
+ if (code.Identifier[0] == 0x15 || code.Identifier[0] == 0x16 ||
+ code.Identifier[0] == 0x17)
+ return true;
+
+ return false;
+ }
+
+ private enum ScanMode
+ {
+ FirstPass,
+ SecondPass
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/EbTextTables.cs b/ScriptTool/ScriptTool/EbTextTables.cs
new file mode 100644
index 0000000..13a0e72
--- /dev/null
+++ b/ScriptTool/ScriptTool/EbTextTables.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScriptTool
+{
+ class EbTextTables
+ {
+ public static MainStringRef[] ReadTptRefs(byte[] rom)
+ {
+ var refs = new List();
+
+ int address = 0xF8985;
+ int entries = 1584;
+
+ for (int i = 0; i < entries; i++)
+ {
+ int firstPointer = rom.ReadSnesPointer(address + 9);
+ if (firstPointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 9, OldPointer = firstPointer });
+
+ byte type = rom[address];
+ if (type != 2)
+ {
+ int secondPointer = rom.ReadSnesPointer(address + 13);
+ if (secondPointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 13, OldPointer = secondPointer });
+ }
+
+ address += 17;
+ }
+
+ return refs.ToArray();
+ }
+
+ public static MainStringRef[] ReadBattleActionRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x157B68;
+ for (int i = 0; i < 318; i++)
+ {
+ int pointer = rom.ReadSnesPointer(address + 4);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 4, OldPointer = pointer });
+ address += 12;
+ }
+ return refs.ToArray();
+ }
+
+ public static MainStringRef[] ReadPrayerRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x4A309;
+ for (int i = 0; i < 10; i++)
+ {
+ int pointer = rom.ReadSnesPointer(address);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address, OldPointer = pointer });
+ address += 4;
+ }
+ return refs.ToArray();
+ }
+
+ public static MainStringRef[] ReadItemHelpRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x155000;
+ for (int i = 0; i < 254; i++)
+ {
+ int pointer = rom.ReadSnesPointer(address + 0x23);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 0x23, OldPointer = pointer });
+ address += 39;
+ }
+ return refs.ToArray();
+ }
+
+ public static MainStringRef[] ReadPsiHelpRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x158A50;
+ for (int i = 0; i < 53; i++)
+ {
+ int pointer = rom.ReadSnesPointer(address + 11);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 11, OldPointer = pointer });
+ address += 15;
+ }
+ return refs.ToArray();
+ }
+
+ public static MainStringRef[] ReadPhoneRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x157AAE;
+ for (int i = 0; i < 6; i++)
+ {
+ int pointer = rom.ReadSnesPointer(address + 0x1B);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 0x1B, OldPointer = pointer });
+ address += 31;
+ }
+ return refs.ToArray();
+ }
+
+ public static MainStringRef[] ReadEnemyTextRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x159589;
+ for (int i = 0; i < 231; i++)
+ {
+ int pointer = rom.ReadSnesPointer(address + 0x2D);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 0x2D, OldPointer = pointer });
+
+ pointer = rom.ReadSnesPointer(address + 0x31);
+ if (pointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 0x31, OldPointer = pointer });
+
+ address += 94;
+ }
+ return refs.ToArray();
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/Extensions.cs b/ScriptTool/ScriptTool/Extensions.cs
new file mode 100644
index 0000000..e228831
--- /dev/null
+++ b/ScriptTool/ScriptTool/Extensions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScriptTool
+{
+ public static class Extensions
+ {
+ public static int ReadInt(this byte[] rom, int address)
+ {
+ int value = rom[address] |
+ (rom[address + 1] << 8) |
+ (rom[address + 2] << 16) |
+ (rom[address + 3] << 24);
+ return value;
+ }
+
+ public static int ReadSnesPointer(this byte[] rom, int address)
+ {
+ int offset = rom.ReadInt(address);
+ if (offset == 0) return 0;
+ return offset - 0xC00000;
+ }
+
+ public static int ReadGbaPointer(this byte[] rom, int address)
+ {
+ int offset = rom.ReadInt(address);
+ if (offset == 0) return 0;
+ return offset & 0x1FFFFFF;
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/FixedStringCollection.cs b/ScriptTool/ScriptTool/FixedStringCollection.cs
new file mode 100644
index 0000000..9d05591
--- /dev/null
+++ b/ScriptTool/ScriptTool/FixedStringCollection.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ class FixedStringCollection
+ {
+ [JsonConverter(typeof(JsonHexConverter))]
+ public int StringsLocation { get; set; }
+
+ public int NumEntries { get; set; }
+ public int EntryLength { get; set; }
+ public IList StringRefs { get; set; }
+ }
+}
diff --git a/ScriptTool/ScriptTool/FixedStringRef.cs b/ScriptTool/ScriptTool/FixedStringRef.cs
new file mode 100644
index 0000000..8d19873
--- /dev/null
+++ b/ScriptTool/ScriptTool/FixedStringRef.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ public class FixedStringRef
+ {
+ [JsonConverter(typeof(JsonHexConverter))]
+ public int Index { get; set; }
+
+ [JsonConverter(typeof(JsonHexConverter))]
+ public int OldPointer { get; set; }
+
+ public string Old { get; set; }
+ public string New { get; set; }
+ }
+}
diff --git a/ScriptTool/ScriptTool/JsonHexConverter.cs b/ScriptTool/ScriptTool/JsonHexConverter.cs
new file mode 100644
index 0000000..2a01031
--- /dev/null
+++ b/ScriptTool/ScriptTool/JsonHexConverter.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ public class JsonHexConverter : JsonConverter
+ {
+ private static readonly Type[] allowedTypes = {
+ typeof(int),
+ typeof(uint),
+ typeof(byte),
+ typeof(sbyte),
+ typeof(short),
+ typeof(ushort),
+ typeof(long),
+ typeof(ulong)
+ };
+
+ public override bool CanConvert(Type objectType)
+ {
+ return allowedTypes.Contains(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType == JsonToken.Integer)
+ {
+ var hex = serializer.Deserialize(reader, objectType);
+ return hex;
+ }
+ throw new Exception("Unexpected token");
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ writer.WriteRawValue(String.Format("0x{0:X}", value));
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/LabelMap.cs b/ScriptTool/ScriptTool/LabelMap.cs
new file mode 100644
index 0000000..1243b1f
--- /dev/null
+++ b/ScriptTool/ScriptTool/LabelMap.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScriptTool
+{
+ class LabelMap
+ {
+ public IDictionary Labels { get; private set; }
+ private int counter = 0;
+
+ public LabelMap()
+ {
+ Labels = new Dictionary();
+ }
+
+ public string this[int address]
+ {
+ get
+ {
+ return Labels[address];
+ }
+ set
+ {
+ Labels[address] = value;
+ }
+ }
+
+ public void Append(int address)
+ {
+ if (!Labels.ContainsKey(address))
+ {
+ string newLabel = String.Concat("L", counter.ToString());
+ Labels.Add(address, newLabel);
+ counter++;
+ }
+ }
+
+ public void Reset()
+ {
+ counter = 0;
+ }
+
+ public void Clear()
+ {
+ Reset();
+ Labels.Clear();
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/M12TextDecompiler.cs b/ScriptTool/ScriptTool/M12TextDecompiler.cs
new file mode 100644
index 0000000..efc5dfd
--- /dev/null
+++ b/ScriptTool/ScriptTool/M12TextDecompiler.cs
@@ -0,0 +1,306 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ class M12TextDecompiler
+ {
+ public IList ControlCodes { get; set; }
+ private static string[] charMap;
+ private static IList textRanges = new List();
+ private static DecompileContext staticContext = new DecompileContext();
+
+ static M12TextDecompiler()
+ {
+ // Load strings
+ charMap = File.ReadAllLines("m12-text-table.txt");
+
+ // Load text ranges
+ textRanges.Add(new int[] { 0x3697F, 0x8C4B0 });
+ }
+
+ public void Decompile(byte[] rom, int[] addresses, DecompileContext context)
+ {
+ if (ControlCodes == null)
+ throw new Exception("Codelist is null");
+
+ // First pass -- define labels
+ foreach (var address in addresses)
+ context.LabelMap.Append(address);
+
+ foreach(var range in textRanges)
+ ScanAt(rom, range[0], range[1], context, ScanMode.FirstPass, false, false, false);
+
+ // Second pass -- decompile the strings
+ foreach (var range in textRanges)
+ ScanAt(rom, range[0], range[1], context, ScanMode.SecondPass, false, false, true);
+ }
+
+ public string ReadString(byte[] rom, int address, int endAddress, bool basicMode)
+ {
+ return ScanAt(rom, address, endAddress, null, ScanMode.ReadOnce, true, basicMode, false);
+ }
+
+ private string ScanAt(byte[] rom, int startAddress, int endAddress, DecompileContext context,
+ ScanMode mode, bool stopOnEnd, bool basicMode, bool newLines)
+ {
+ bool ended = false;
+ bool foundMatch = false;
+ var sb = new StringBuilder();
+
+ int address = startAddress;
+
+ if (address == 0)
+ {
+ throw new Exception("Null pointer");
+ }
+
+ if (mode == ScanMode.FirstPass)
+ {
+ context.LabelMap.Append(address);
+ }
+
+ while (!ended)
+ {
+ // Check for label definition
+ if (mode == ScanMode.SecondPass && context.LabelMap.Labels.ContainsKey(address))
+ {
+ sb.Append("^" + context.LabelMap[address] + "^");
+ }
+
+ // Check for control codes (unless it's in basic mode)
+
+ // No codes begin with a value above 0x1F, so check for that first
+ if (rom[address + 1] == 0xFF && (!basicMode || (basicMode && (rom[address] == 0))))
+ {
+ // Loop through each control code until we find a match
+ foundMatch = false;
+ foreach (var code in ControlCodes)
+ {
+ if (code.Match(rom, address))
+ {
+ foundMatch = true;
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ // Output the start of the code block
+ sb.Append('[');
+
+ // Output the identifier bytes
+ sb.Append(String.Join(" ", code.Identifier.Select(b => b.ToString("X2")).ToArray()));
+ }
+
+ // Skip the identifier bytes
+ address += code.Identifier.Count;
+
+ // Found a match -- check if it's variable-length
+ if (code.Multiple)
+ {
+ if (code.BeginsWith(0x95) ||
+ code.BeginsWith(0xBD))
+ {
+ int count = rom[address++];
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ sb.Append(' ');
+ sb.Append(count.ToString("X2"));
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ int jump = rom.ReadInt(address);
+ jump += address;
+
+ address += 4;
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+ }
+ }
+ else
+ {
+ // Check if it references any other addresses -- scan those too
+ if (code.BeginsWith(0x04) ||
+ code.BeginsWith(0x05) ||
+ code.BeginsWith(0x80) ||
+ code.BeginsWith(0x81) ||
+ code.BeginsWith(0x82) ||
+ code.BeginsWith(0x86))
+ {
+ // Single relative address at next byte
+ int jump = rom.ReadInt(address);
+ jump += address;
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+
+ else if (code.BeginsWith(0x1C))
+ {
+ // Skip two bytes; single relative address afterwards
+ int jump = rom.ReadInt(address + 2);
+ jump += address + 2;
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ sb.Append(' ');
+ sb.Append(rom[address].ToString("X2"));
+ sb.Append(' ');
+ sb.Append(rom[address + 1].ToString("X2"));
+
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+
+ else if (code.BeginsWith(0x9D))
+ {
+ // Skip two bytes; single absolute address afterwards
+ int jump = rom.ReadGbaPointer(address + 2);
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ sb.Append(' ');
+ sb.Append(rom[address].ToString("X2"));
+ sb.Append(' ');
+ sb.Append(rom[address + 1].ToString("X2"));
+
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+
+ else if (code.BeginsWith(0xA2))
+ {
+ // Single absolute address at next byte
+ int jump = rom.ReadGbaPointer(address);
+
+ context.LabelMap.Append(jump);
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ sb.Append(" _");
+ sb.Append(context.LabelMap[jump]);
+ sb.Append('_');
+ }
+ }
+
+ else
+ {
+ // Regular control code -- output the rest of the bytes
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ for (int i = 0; i < (code.Length - code.Identifier.Count); i++)
+ {
+ sb.Append(' ');
+ sb.Append(rom[address + i].ToString("X2"));
+ }
+ }
+ }
+
+ // Skip the rest of the bytes
+ address += code.Length - code.Identifier.Count;
+ }
+
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ // Output the end of the code block
+ sb.Append(']');
+ }
+
+ // End the block if necessary
+ if (stopOnEnd && code.End)
+ {
+ ended = true;
+ }
+
+ // Insert a newline after each end code for readibility
+ if (newLines && (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce) && code.End)
+ {
+ sb.AppendLine();
+ /*sb.Append('(');
+ sb.Append(address.ToString("X"));
+ sb.Append(')');*/
+ }
+
+ break;
+ }
+ }
+
+ if (!foundMatch)
+ {
+ // Bad!
+ throw new Exception("Found unknown control code");
+ }
+ }
+ else
+ {
+ // It's not a control code -- just skip it
+ if (mode == ScanMode.SecondPass || mode == ScanMode.ReadOnce)
+ {
+ if (!basicMode || (basicMode && (rom[address] != 0xFF)))
+ sb.Append(CharLookup(rom[address]));
+ }
+
+ address++;
+ }
+
+ if ((endAddress != -1) && (address >= endAddress))
+ ended = true;
+ }
+
+ if (mode == ScanMode.SecondPass)
+ context.Strings.Add(sb.ToString());
+
+ else if (mode == ScanMode.ReadOnce)
+ return sb.ToString();
+
+ return null;
+ }
+
+ private string CharLookup(byte value)
+ {
+ if ((value >= 83 && value <= 95) ||
+ (value >= 180 && value <= 191) ||
+ value == 255)
+ {
+ // Invalid
+ throw new Exception("Invalid character");
+ }
+
+ return charMap[value];
+ }
+
+ private enum ScanMode
+ {
+ FirstPass,
+ SecondPass,
+ ReadOnce
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/M12TextTables.cs b/ScriptTool/ScriptTool/M12TextTables.cs
new file mode 100644
index 0000000..9331e98
--- /dev/null
+++ b/ScriptTool/ScriptTool/M12TextTables.cs
@@ -0,0 +1,233 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ScriptTool
+{
+ class M12TextTables
+ {
+ private static int[] StupidIndices =
+ {
+ 0xB9, 0xBF, 0xC0, 0x164, 0x169,
+ 0x16D, 0x16F, 0x171, 0x1B0, 0x1B1,
+ 0x1B3, 0x1C1, 0x233, 0x239, 0x247,
+ 0x286, 0x2AD, 0x2AE, 0x2AF, 0x2E7,
+ 0x30A, 0x318, 0x3E1, 0x458, 0x45D,
+ 0x48C, 0x48D, 0x514, 0x515, 0x516,
+ 0x57D
+ };
+
+ public static MainStringRef[] ReadTptRefs(byte[] rom)
+ {
+ var refs = new List();
+
+ int address = 0x8EB14;
+ int entries = 1584;
+
+ for (int i = 0; i < entries; i++)
+ {
+ if (StupidIndices.Contains(i))
+ {
+ int firstPointer = rom.ReadGbaPointer(address + 9);
+ if (firstPointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 9, OldPointer = firstPointer });
+
+ byte type = rom[address];
+ if (type != 2)
+ {
+ int secondPointer = rom.ReadGbaPointer(address + 13);
+ if (secondPointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 13, OldPointer = secondPointer });
+ }
+ }
+ else
+ {
+ int firstPointer = rom.ReadGbaPointer(address + 12);
+ if (firstPointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 12, OldPointer = firstPointer });
+
+ byte type = rom[address];
+ if (type != 2)
+ {
+ int secondPointer = rom.ReadGbaPointer(address + 16);
+ if (secondPointer != 0)
+ refs.Add(new MainStringRef { Index = i, PointerLocation = address + 16, OldPointer = secondPointer });
+ }
+ }
+
+ address += 20;
+ }
+
+ return refs.ToArray();
+ }
+
+ /*public static StringRef[] ReadBattleActionRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x157B68;
+ for (int i = 0; i < 318; i++)
+ {
+ int pointer = rom.ReadPointer(address + 4);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address + 4, OldPointer = pointer });
+ address += 12;
+ }
+ return refs.ToArray();
+ }
+
+ public static StringRef[] ReadPrayerRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x4A309;
+ for (int i = 0; i < 10; i++)
+ {
+ int pointer = rom.ReadPointer(address);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address, OldPointer = pointer });
+ address += 4;
+ }
+ return refs.ToArray();
+ }
+
+ public static StringRef[] ReadItemHelpRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x155000;
+ for (int i = 0; i < 254; i++)
+ {
+ int pointer = rom.ReadPointer(address + 0x23);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address + 0x23, OldPointer = pointer });
+ address += 39;
+ }
+ return refs.ToArray();
+ }
+
+ public static StringRef[] ReadPsiHelpRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x158A50;
+ for (int i = 0; i < 53; i++)
+ {
+ int pointer = rom.ReadPointer(address + 11);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address + 11, OldPointer = pointer });
+ address += 15;
+ }
+ return refs.ToArray();
+ }
+
+ public static StringRef[] ReadPhoneRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x157AAE;
+ for (int i = 0; i < 6; i++)
+ {
+ int pointer = rom.ReadPointer(address + 0x1B);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address + 0x1B, OldPointer = pointer });
+ address += 31;
+ }
+ return refs.ToArray();
+ }
+
+ public static StringRef[] ReadEnemyTextRefs(byte[] rom)
+ {
+ var refs = new List();
+ int address = 0x159589;
+ for (int i = 0; i < 231; i++)
+ {
+ int pointer = rom.ReadPointer(address + 0x2D);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address + 0x2D, OldPointer = pointer });
+
+ pointer = rom.ReadPointer(address + 0x31);
+ if (pointer != 0)
+ refs.Add(new StringRef { Index = i, PointerLocation = address + 0x31, OldPointer = pointer });
+
+ address += 94;
+ }
+ return refs.ToArray();
+ }*/
+
+ public static MiscStringCollection ReadPointerTable(byte[] rom, int tableAddress, int stringsAddress)
+ {
+ var refs = new List();
+ int entries = rom.ReadInt(tableAddress);
+
+ int currentTableAddress = tableAddress;
+ currentTableAddress += 4;
+
+ for (int i = 0; i < entries; i++)
+ {
+ int offset = rom.ReadInt(currentTableAddress);
+ refs.Add(new MiscStringRef
+ {
+ OffsetLocation = currentTableAddress,
+ OldPointer = offset + stringsAddress,
+ Index = i
+ });
+ currentTableAddress += 4;
+ }
+
+ return new MiscStringCollection
+ {
+ OffsetTableLocation = tableAddress,
+ StringsLocation = stringsAddress,
+ StringRefs = refs
+ };
+ }
+
+ public static FixedStringCollection ReadFixedStringTable(byte[] rom, int stringsAddress, int numEntries, int entryLength)
+ {
+ var refs = new List();
+ int currentStringAddress = stringsAddress;
+
+ for(int i=0;i StringRefs { get; set; }
+ }
+}
diff --git a/ScriptTool/ScriptTool/MiscStringRef.cs b/ScriptTool/ScriptTool/MiscStringRef.cs
new file mode 100644
index 0000000..b495558
--- /dev/null
+++ b/ScriptTool/ScriptTool/MiscStringRef.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ public class MiscStringRef
+ {
+ [JsonConverter(typeof(JsonHexConverter))]
+ public int Index { get; set; }
+
+ [JsonConverter(typeof(JsonHexConverter))]
+ public int OffsetLocation { get; set; }
+
+ [JsonConverter(typeof(JsonHexConverter))]
+ public int OldPointer { get; set; }
+
+ public bool BasicMode { get; set; }
+ public string Old { get; set; }
+ public string New { get; set; }
+
+ public bool ShouldSerializeBasicMode()
+ {
+ return BasicMode;
+ }
+ }
+}
diff --git a/ScriptTool/ScriptTool/Program.cs b/ScriptTool/ScriptTool/Program.cs
new file mode 100644
index 0000000..39aaf2a
--- /dev/null
+++ b/ScriptTool/ScriptTool/Program.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using Newtonsoft.Json;
+
+namespace ScriptTool
+{
+ class Program
+ {
+ static IList EbControlCodes = new List();
+ static IList M12ControlCodes = new List();
+
+ // Decompiler setup
+ static EbTextDecompiler ebDecompiler;
+ static M12TextDecompiler m12Decompiler;
+
+ static void Main(string[] args)
+ {
+ CommandOptions options = ParseCommandLine(args);
+ if (options == null)
+ {
+ Usage();
+ return;
+ }
+
+ LoadControlCodes();
+
+ if (options.Command == CommandType.Decompile)
+ {
+ ebDecompiler = new EbTextDecompiler() { ControlCodes = EbControlCodes };
+ m12Decompiler = new M12TextDecompiler() { ControlCodes = M12ControlCodes };
+
+ // Load ROMs
+ byte[] ebRom = File.ReadAllBytes(options.EbRom);
+ byte[] m12Rom = File.ReadAllBytes(options.M12Rom);
+
+ // Decompile misc string tables
+ if (options.DoMiscText)
+ {
+ DecompileEbMisc(ebRom, options.WorkingDirectory);
+ DecompileM12Misc(m12Rom, options.WorkingDirectory);
+ }
+
+ // Decompile main string tables
+ if (options.DoMainText)
+ {
+ DecompileEb(ebRom, options.WorkingDirectory);
+ DecompileM12(m12Rom, options.WorkingDirectory);
+ }
+ }
+ else if (options.Command == CommandType.Compile)
+ {
+ // TBD
+ }
+ }
+
+ static void Usage()
+ {
+ Console.WriteLine("Usage:");
+ Console.WriteLine(" ScriptTool.exe [-decompile or -compile] [-misc] [-main] workingdirectory [ebrom m12rom]");;
+ }
+
+ static CommandOptions ParseCommandLine(string[] args)
+ {
+ var argList = new List(args);
+
+ // Check for decompile switch
+ CommandType command;
+ if (argList.Contains("-decompile") && !argList.Contains("-compile"))
+ {
+ command = CommandType.Decompile;
+ argList.Remove("-decompile");
+ }
+ else if (argList.Contains("-compile") && !argList.Contains("-decompile"))
+ {
+ command = CommandType.Compile;
+ argList.Remove("-compile");
+ }
+ else
+ {
+ return null;
+ }
+
+ // Check for main and misc flags
+ bool doMain = false;
+ bool doMisc = false;
+ if (argList.Contains("-main"))
+ {
+ doMain = true;
+ argList.Remove("-main");
+ }
+ if (argList.Contains("-misc"))
+ {
+ doMisc = true;
+ argList.Remove("-misc");
+ }
+
+ // Check for working directory
+ if (argList.Count < 1)
+ return null;
+
+ string working = argList[0];
+ if (!Directory.Exists(working))
+ return null;
+
+ // Check for ROM paths
+ string ebRom = null;
+ string m12Rom = null;
+ if (command == CommandType.Decompile && argList.Count == 3)
+ {
+ ebRom = argList[1];
+ m12Rom = argList[2];
+ if (!File.Exists(ebRom) || !File.Exists(m12Rom))
+ return null;
+ }
+
+ return new CommandOptions
+ {
+ WorkingDirectory = working,
+ EbRom = ebRom,
+ M12Rom = m12Rom,
+ Command = command,
+ DoMainText = doMain,
+ DoMiscText = doMisc
+ };
+ }
+
+ static void LoadControlCodes()
+ {
+ EbControlCodes = ControlCode.LoadEbControlCodes("eb-codelist.txt");
+ M12ControlCodes = ControlCode.LoadM12ControlCodes("m12-codelist.txt");
+ }
+
+ static void DecompileEb(byte[] ebRom, string workingDirectory)
+ {
+ var context = new DecompileContext();
+
+ // Pull all string refs from the ROM
+ var allRefs = new List>();
+ allRefs.Add(Tuple.Create("eb-tpt", EbTextTables.ReadTptRefs(ebRom)));
+ allRefs.Add(Tuple.Create("eb-battle-actions", EbTextTables.ReadBattleActionRefs(ebRom)));
+ allRefs.Add(Tuple.Create("eb-prayers", EbTextTables.ReadPrayerRefs(ebRom)));
+ allRefs.Add(Tuple.Create("eb-item-help", EbTextTables.ReadItemHelpRefs(ebRom)));
+ allRefs.Add(Tuple.Create("eb-psi-help", EbTextTables.ReadPsiHelpRefs(ebRom)));
+ allRefs.Add(Tuple.Create("eb-phone", EbTextTables.ReadPhoneRefs(ebRom)));
+ allRefs.Add(Tuple.Create("eb-enemy-encounters", EbTextTables.ReadEnemyTextRefs(ebRom)));
+
+ // Decompile
+ var allPointers = allRefs.SelectMany(rl => rl.Item2).Select(r => r.OldPointer).ToArray();
+ ebDecompiler.Decompile(ebRom, allPointers, context);
+
+ // Update labels for all refs and write to JSON
+ foreach (var refList in allRefs)
+ {
+ foreach (var stringRef in refList.Item2)
+ stringRef.Label = context.LabelMap[stringRef.OldPointer];
+
+ File.WriteAllText(Path.Combine(workingDirectory, refList.Item1 + ".json"), JsonConvert.SerializeObject(refList.Item2, Formatting.Indented));
+ }
+
+ // Write the strings
+ File.WriteAllText(Path.Combine(workingDirectory, "eb-strings.txt"), String.Join(Environment.NewLine, context.Strings));
+ }
+
+ static void DecompileEbMisc(byte[] ebRom, string workingDirectory)
+ {
+
+ }
+
+ static void DecompileM12(byte[] m12Rom, string workingDirectory)
+ {
+
+ var context = new DecompileContext();
+
+ // Pull all string refs from the ROM
+ var allRefs = new List>();
+ allRefs.Add(Tuple.Create("m12-tpt", M12TextTables.ReadTptRefs(m12Rom)));
+
+ // Decompile
+ var allPointers = allRefs.SelectMany(rl => rl.Item2).Select(r => r.OldPointer).ToArray();
+ m12Decompiler.Decompile(m12Rom, allPointers, context);
+
+ // Update labels for all refs and write to JSON
+ foreach (var refList in allRefs)
+ {
+ foreach (var stringRef in refList.Item2)
+ stringRef.Label = context.LabelMap[stringRef.OldPointer];
+
+ File.WriteAllText(Path.Combine(workingDirectory, refList.Item1 + ".json"), JsonConvert.SerializeObject(refList.Item2, Formatting.Indented));
+ }
+
+ // Write the strings
+ File.WriteAllText(Path.Combine(workingDirectory, "m12-strings.txt"), String.Join(Environment.NewLine, context.Strings));
+ }
+
+ static void DecompileM12Misc(byte[] m12Rom, string workingDirectory)
+ {
+ // Item names
+ var itemNames = M12TextTables.ReadItemNames(m12Rom);
+ DecompileM12MiscStringCollection(m12Rom, workingDirectory, "m12-itemnames", itemNames);
+
+ // Menu choices
+ var menuChoices = M12TextTables.ReadMenuChoices(m12Rom);
+ DecompileM12MiscStringCollection(m12Rom, workingDirectory, "m12-menuchoices", menuChoices);
+
+ // Misc text
+ var miscText = M12TextTables.ReadMiscText(m12Rom);
+ DecompileM12MiscStringCollection(m12Rom, workingDirectory, "m12-misctext", miscText);
+
+ // PSI names
+ var psiNames = M12TextTables.ReadPsiNames(m12Rom);
+ DecompileM12FixedStringCollection(m12Rom, workingDirectory, "m12-psinames", psiNames);
+ }
+
+ static void DecompileM12MiscStringCollection(byte[] rom, string workingDirectory, string name, MiscStringCollection miscStringCollection)
+ {
+ // Decompile the strings
+ foreach (var miscStringRef in miscStringCollection.StringRefs)
+ {
+ miscStringRef.Old =
+ miscStringRef.New =
+ m12Decompiler.ReadString(rom, miscStringRef.OldPointer, -1, miscStringRef.BasicMode);
+ }
+
+ // Write JSON
+ File.WriteAllText(Path.Combine(workingDirectory, name + ".json"), JsonConvert.SerializeObject(miscStringCollection, Formatting.Indented));
+ }
+
+ static void DecompileM12FixedStringCollection(byte[] rom, string workingDirectory, string name, FixedStringCollection fixedStringCollection)
+ {
+ // Decompile the strings
+ foreach (var fixedStringRef in fixedStringCollection.StringRefs)
+ {
+ fixedStringRef.Old =
+ fixedStringRef.New =
+ m12Decompiler.ReadString(rom, fixedStringRef.OldPointer,
+ fixedStringRef.OldPointer + fixedStringCollection.EntryLength,
+ false);
+ }
+
+ // Write JSON
+ File.WriteAllText(Path.Combine(workingDirectory, name + ".json"), JsonConvert.SerializeObject(fixedStringCollection, Formatting.Indented));
+ }
+
+ //static void CompileM12Misc()
+ }
+}
diff --git a/ScriptTool/ScriptTool/Properties/AssemblyInfo.cs b/ScriptTool/ScriptTool/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0e43008
--- /dev/null
+++ b/ScriptTool/ScriptTool/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ScriptTool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ScriptTool")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("04d88ce9-e4a3-457b-9f74-959ddf16f81d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ScriptTool/ScriptTool/ScriptTool.csproj b/ScriptTool/ScriptTool/ScriptTool.csproj
new file mode 100644
index 0000000..245f603
--- /dev/null
+++ b/ScriptTool/ScriptTool/ScriptTool.csproj
@@ -0,0 +1,93 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {CA5C95AE-2CD2-47E3-B5AE-FE6136808AC4}
+ Exe
+ Properties
+ ScriptTool
+ ScriptTool
+ v4.5.1
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\packages\Newtonsoft.Json.6.0.8\lib\net45\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
+
\ No newline at end of file
diff --git a/ScriptTool/ScriptTool/eb-codelist.txt b/ScriptTool/ScriptTool/eb-codelist.txt
new file mode 100644
index 0000000..a1fc584
--- /dev/null
+++ b/ScriptTool/ScriptTool/eb-codelist.txt
@@ -0,0 +1,216 @@
+00,"Line break";
+01,"Start on Blank Line";
+!02,"Signal end of block or string";
+03,"Wait with prompt, brief pause in battle";
+04 XX XX,"Toggle flag $XXXX on";
+05 XX XX,"Toggle flag $XXXX off";
+06 XX XX YY YY YY YY,"If flag $XXXX is set, jump to address $YYYYYYYY";
+07 XX XX,"Set binary flag with the status of flag $XXXX";
+08 XX XX XX XX,"Parse text at $XXXXXXXX and continue here";
+*09,"Multiple-entry pointer table (Jump to address)";
+!0A XX XX XX XX,"Jump to text at address $XXXXXXXX";
+0B XX,"Set binary flag to true if number in memory == $XX";
+0C XX,"Set binary flag to true if number in memory != $XX";
+0D XX,"Copy from memory $XX into argumentative memory";
+0E XX,"Input $XX into a memory bank";
+0F,"Secondary memory +1";
+10 XX,"Pause for ~0.02 sec. * $XX";
+11,"Treats text displayed by [1C (07|0C) XX] as a menu";
+12,"Clear current line of text";
+13,"Wait without prompt";
+14,"Wait with prompt in battle";
+18 00,"Closes most recently used text window";
+18 01 XX,"Open window";
+18 02,"UNKNOWN";
+18 03 XX,"Change parsing focus to window $XX";
+18 04,"Close all windows";
+18 05 XX YY,"Display text $XX pixels over and $YY lines down";
+18 06,"Clear contents from current window";
+18 07 XX XX XX XX YY,"Check for $XX inequality to memory";
+18 08 XX,"UNKNOWN";
+18 09 XX,"UNKNOWN";
+18 0A,"Open wallet window and display current cash on hand";
+18 0D XX YY,"Display Status window for Char$XX YY-01-parameter, 01,02 specific, 02 ?";
+19 02,"Load a text string into memory (end with [02])"
+19 04,"UNKNOWN";
+19 05 XX YY ZZ,"Inflict ailment YY YY on character $XX";
+19 10 XX,"Return the character number of the person in party position $XX"
+19 11 XX,"Return One Letter from a Character's Name (see lexicon.txt)";
+19 14,"UNKNOWN";
+19 16 XX YY,"Return Byte YY of Character XX Status";
+19 18 XX,"UNKNOWN";
+19 19 XX YY,"Sets $YY(01-0e) in item menu of Char$XX into working memory";
+19 1A XX,"UNKNOWN";
+19 1B XX,"UNKNOWN";
+19 1C XX YY,"UNKNOWN";
+19 1D XX YY,"UNKNOWN";
+19 1E,"UNKNOWN";
+19 1F,"UNKNOWN";
+19 20,"UNKNOWN";
+19 21 XX,"UNKNOWN";
+19 22 XX XX YY YY,"Store to memory the direction from character $XX to object $YY";
+19 23 XX XX YY YY ZZ,"Store to memory the direction from TPT entry $XX to object $YY (ZZ unknown)";
+19 24 XX XX YY YY ZZ,"Store to memory the direction from generated sprite $XX to object $YY (ZZ=00)";
+19 25 XX,"UNKNOWN";
+19 26 XX,"UNKNOWN";
+19 27 XX,"UNKNOWN";
+19 28 XX,"UNKNOWN";
+1A 00,"UNKNOWN INCOMPLETE";
+1A 01 WW WW WW WW XX XX XX XX YY YY YY YY ZZ ZZ ZZ ZZ,"Current Party Member Selection Menu";
+1A 04,"UNKNOWN INCOMPLETE";
+1A 05 XX YY,"Display the inventory of character YY in window XX";
+1A 06 XX,"Displays shop window $XX";
+1A 07,"Related to Escargo Express stored goods window";
+1A 08,"UNKNOWN INCOMPLETE";
+1A 09,"UNKNOWN INCOMPLETE";
+1A 0A,"Open non-working phone window";
+1A 0B,"Copy WRAM to Active Memory";
+1B 00,"Copy all memory to storage";
+1B 01,"Copy all storage to memory";
+1B 02 XX XX XX XX,"If binary flag is set 0, jump to $XXXXXXXX";
+1B 03 XX XX XX XX,"If binary flag is set 1, jump to $XXXXXXXX";
+1B 04,"Swap working and argumentary memory";
+1B 05,"Copy Active Memory to WRAM";
+1B 06,"Copy WRAM to Active Memory";
+1C 00 XX,"Text and window background color effects";
+1C 01 XX,"Display statistical data value $XX";
+1C 02 XX,"Display character name $XX";
+1C 03 XX,"Display Text Character XX; 00 as argument";
+1C 04,"Open party HP/PP status windows";
+1C 05 XX,"Display item name $XX";
+1C 06 XX,"Display teleport destination name $XX";
+1C 07 XX,"Display $XX strings centered horizontally (see [19 02])";
+1C 08 XX,"Display text graphic $XX";
+1C 09 XX,"UNKNOWN";
+1C 0A XX XX XX XX,"Displays $XXXXXXXX in decimal";
+1C 0B XX XX XX XX,"Displays $XXXXXXXX in decimal as money";
+1C 0C XX,"Display $XX strings vertically (see [19 02])";
+1C 0D,"Display action user name";
+1C 0E,"Display action target name";
+1C 0F,"UNKNOWN INCOMPLETE;IN PROGRESS (Relates to Level-Up stats editor)";
+1C 11 XX,"UNKNOWN";
+1C 12 XX,"Print PSI name $XX";
+1C 13 XX YY,"Display XX Amimation (YY is usually 00) See Animation code on forum";
+1C 14 XX,"Say $XX pronoun";
+1C 15 XX,"Say $XX pronoun";
+1D 00 XX XX,"Add item $YY to character $XX's inventory and return $YY if successful";
+1D 01 XX YY,"Remove item $YY from character $XX's inventory";
+1D 02 XX,"Set binary flag true if character $XX does not have free space in their inventory";
+1D 03 XX,"Set binary flag true if character $XX has free space in their inventory";
+1D 04 XX YY,"Sets binary flag true if char $XX does not have item $YY";
+1D 05 XX YY,"Sets binary flag true if char $XX has item $YY";
+1D 06 XX XX XX XX,"Add $XX dollars to ATM balance";
+1D 07 XX XX XX XX,"Remove $XX dollars from ATM balance";
+1D 08 XX XX,"Add $XX dollars to wallet balance";
+1D 09 XX XX,"Remove $XX dollars from wallet balance"
+1D 0A XX,"Return price of item";
+1D 0B XX,"Get one half of item $XX";
+1D 0C XX XX,"UNKNOWN";
+1D 0D XX YY YY,"Set binary flag true if character $XX has ailment YY YY";
+1D 0E XX YY,"Add item $YY to character $XX's inventory and return number of items held";
+1D 0F XX YY,"UNKNOWN";
+1D 10 XX XX,"UNKNOWN";
+1D 11 XX XX,"UNKNOWN";
+1D 12 XX XX,"UNKNOWN";
+1D 13 XX XX,"UNKNOWN";
+1D 14 XX XX XX XX,"Sets binary flag true if the player currently holds $XX in the wallet";
+1D 15 XX XX,"Multiply $XXXX by the number of characters in the party, and store in Working Memory";
+1D 17 XX XX XX XX,"Sets binary flag true if the player currently holds $XX in the ATM";
+1D 18 XX,"UNKNOWN";
+1D 19 XX,"Sets binary flag true if there are $XX party members";
+1D 20,"Check same user/target";
+1D 21 XX,"Generate random number from 0 to $XX";
+1D 22,"Set binary flag if current sector is Exit mouse-compatible";
+1D 23 XX,"UNKNOWN";
+1D 24 XX,"May relate to returning cash earned since last phone call to dad";
+1E 00 XX YY,"Character $XX recovers HP by YY% of max";
+1E 01 XX YY,"Character $XX loses HP by YY% of max";
+1E 02 XX YY,"Character $XX recovers $YY HP";
+1E 03 XX YY,"Character $XX loses $YY HP";
+1E 04 XX YY,"Character $XX recovers PP by YY% of max";
+1E 05 XX YY,"Character $XX loses PP by YY% of max";
+1E 06 XX YY,"Character $XX recovers $YY PP";
+1E 07 XX YY,"Character $XX loses $YY PP";
+1E 08 XX YY,"Character $XX becomes level $YY";
+1E 09 XX YY YY YY,"Character $XX gains $YYYYYY EXP";
+1E 0A XX YY,"Boost IQ in character $XX by $YY";
+1E 0B XX YY,"Boost Guts in character $XX by $YY";
+1E 0C XX YY,"Boost Speed in character $XX by $YY";
+1E 0D XX YY,"Boost Vitality in character $XX by $YY";
+1E 0E XX YY,"Boost Luck in character $XX by $YY";
+1F 00 XX YY,"Play music track $YY ($XX is unused)";
+1F 01 00,"Stops music";
+1F 02 XX,"Play sound effect $XX";
+1F 03,"Return music setting to sector default";
+1F 04 XX,"Toggles sound for text printing (?)";
+1F 05,"Disallow Sector Boundaries to Change Music";
+1F 06,"Allow Sector Boundaries to Change Music";
+1F 07 XX,"Apply audio effect $XX";
+1F 11 XX,"Add character $XX to party";
+1F 12 XX,"Remove character $XX from party";
+1F 13 XX YY,"Turn character $XX to direction $YY";
+1F 14 XX,"UNKNOWN";
+1F 15 XX XX YY YY ZZ,"Generate sprite $XXXX with movement pattern $YYYY using style $ZZ";
+1F 16 XX XX YY,"Turn TPT entry $XXXX to direction $YY";
+1F 17 XX XX YY YY ZZ,"Generate TPT entry $XXXX with movement pattern $YYYY using style $ZZ";
+1F 18 XX XX XX XX XX XX XX,"UNKNOWN";
+1F 19 XX XX XX XX XX XX XX,"UNKNOWN";
+1F 1A XX XX YY,"Generate sprite $YY near TPT entry $XXXX";
+1F 1B XX XX,"Delete 1F 1A sprite near TPT entry $XXXX";
+1F 1C XX YY,"Generate sprite $YY near character $XX";
+1F 1D XX,"Delete 1F 1C sprite near character $XX";
+1F 1E XX XX YY,"Make TPT entry $XXXX disappear via style $YY";
+1F 1F XX XX YY,"Delete 1F 15-generated sprite $XXXX via style $YY";
+1F 20 XX YY,"PSI Teleport to destination $XX using method $YY";
+1F 21 XX,"Teleport to 0x15EDAB teleport coordinate entry $XX";
+1F 23 XX XX,"Trigger battle with 0x10D74C enemy group entry $XXXX";
+1F 30,"Change to normal font";
+1F 31,"Change to Mr. Saturn font";
+1F 41 XX,"Call special event $XX";
+1F 50,"Disable controller input";
+1F 51,"Re-enable controller input";
+1F 52 XX,"Number selector with XX digits";
+1F 60 XX,"UNKNOWN";
+1F 61,"Simultaneously commences all prepared movements";
+1F 62 XX,"UNKNOWN";
+1F 63 XX XX XX XX,"Jump to $XXXXXXXX after the next screen refresh";
+1F 64,"Purge all NPCs from party";
+1F 65,"Purge first NPC from party";
+1F 66 XX YY ZZ ZZ ZZ ZZ,"Activate hotspot $XX with address $ZZZZZZZZ";
+1F 67 XX,"Deactivate hotspot $XX";
+1F 68,"Store current coordinates into memory";
+1F 69,"Teleport to coordinates stored by 1F 68";
+1F 71 XX YY,"Character $XX realizes special PSI $YY";
+1F 81 XX XX,"Check If Character Can Use Item";
+1F 83 XX XX,"Equip character XX with his or her YYth item";
+1F 90,"UNKNOWN";
+1F A0,"TPT entry being spoken to/checked faces downwards";
+1F A1,"Change Direction of Current TPT Entry to Down";
+1F A2,"UNKNOWN";
+1F B0,"Save the game in the slot selected at the beginning";
+*1F C0,"Multiple-entry pointer table (Reference address)";
+1F D0 XX,"Attempt to Fix Items";
+1F D1,"Returns a numeric value based on the proximity of a magic truffle TPT entry";
+1F D2 XX,"Summon photographer to location $XX";
+1F D3 XX,"Trigger timed event $XX from the 0x15F845 table";
+1F E1 XX YY ZZ,"Change map palette to that of tileset $XX, pallet $YY at speed $ZZ";
+1F E4 XX XX YY,"Change direction of 1F 15-generated sprite $XXXX to $YY";
+1F E5 XX,"Lock player movement";
+1F E6 XX XX,"Delay appearance of TPT entry $XXXX until end of text parsing";
+1F E7 XX XX,"UNKNOWN";
+1F E8 XX,"Restrict player movement if camera is focused on other sprite (XX is usually FF)";
+1F E9 XX XX,"Restrict player movement until end of text block (use 1F 61 after)";
+1F EA XX XX,"UNKNOWN";
+1F EB XX YY,"Make character $XX disappear via style $YY";
+1F EC XX YY,"Make character $XX appear via style $YY";
+1F ED,"Your party will be teleported (in a glitchy way) on top of some sprite that is nearby when the text block is over if the camera switched to another sprite in the block of text";
+1F EE XX XX,"UNKNOWN";
+1F EF XX XX,"Focuses the camera to where [1f 15]-generated sprite xxxx is but doesn't follow it";
+1F F0,"Activate bicycle";
+1F F1 XX XX YY YY,"Apply movement pattern $YYYY to TPT entry $XXXX";
+1F F2 XX XX YY YY,"Apply movement pattern $YYYY to sprite $XXXX";
+1F F3 XX XX YY,"Generate sprite $YY near sprite $XXXX";
+1F F4 XX XX,"Delete 1F F3 sprite near sprite $XXXX";
+15 XX,"Use compressed string $XX from bank 0";
+16 XX,"Use compressed string $XX from bank 1";
+17 XX,"Use compressed string $XX from bank 2";
\ No newline at end of file
diff --git a/ScriptTool/ScriptTool/eb-compressed-strings.txt b/ScriptTool/ScriptTool/eb-compressed-strings.txt
new file mode 100644
index 0000000..74d6a81
--- /dev/null
+++ b/ScriptTool/ScriptTool/eb-compressed-strings.txt
@@ -0,0 +1,768 @@
+
+ in the
+ that
+...
+ and
+ this
+ to the
+ about
+ just
+ of the
+ something
+ going to
+ to
+ you have
+ your
+ for
+ you're
+@You
+ really
+ don't
+@The
+e the
+e you
+...
+ the
+ will
+ ...Brick Road
+ing
+ some
+
+@Do you want to
+ like
+ou don't have
+ is
+ you
+ you
+ anything else
+ the
+ you want to
+ for the
+ friend
+ at the
+ould you like
+ from
+ would
+he Runaway Five
+ with
+ want to
+@If you
+ you don't
+s the
+ed to
+e...
+ something
+t the
+@...
+@Please
+'s
+ of your
+@It's
+@Thank you
+@I
+ here.
+ in
+@Do you
+ I'll
+ have
+e of
+d you
+@I'm
+me to
+@I don't
+@Well,
+@This is
+ed the
+@You're
+ for a
+ anything
+ing
+ of
+ you should
+ I
+ from the
+s...
+ it's
+ time
+e to
+ed
+e of the
+ to you
+n't you
+ again
+ for you.
+other
+ation
+ little
+ing to
+ can't
+ much
+ someone
+ on the
+ looks like
+ don't you
+ very
+the
+ can
+you
+ that you
+ it
+ you want
+ou can't
+ able to
+ already
+ give you
+understand
+
+ my
+ you can
+ that
+ what
+here's
+ there
+n the
+@What
+Thank you
+ I can't
+one
+@The
+ thought
+ not
+You should
+ou know
+ has
+ back
+ of
+ve been
+ I'm
+ there
+ with you
+@I heard
+ in
+ here
+ Fourside
+I wonder
+ to
+ could
+ think
+ out
+ good
+ the
+ You
+ too much
+ome back
+t...
+ here
+thing
+come
+ly
+ent
+ strong
+ money.
+@I'll
+an
+ou must
+ are you
+ with the
+ on your
+too many
+ you.
+know
+ to be
+ around
+ if you
+@Are you
+ome again
+e and
+ more
+think
+e your
+@Don't
+nd the
+t to
+rea
+ he
+ me
+ strange
+ for you
+ight
+ a
+ be
+ther
+all
+ cannot
+here is
+You have
+Monotoli
+ was
+ll you
+hat
+, but
+ stuff
+eep
+ it
+ didn't
+ like th
+ll right
+ should
+ over
+@Oh,
+ hear
+ every
+I'm not
+ about t
+ zombies
+ damage
+his is
+all
+ some
+@It
+ attack
+ right
+ally
+orry
+ess
+er
+@That's
+carry it
+ake
+t was
+, the
+n you
+sn't
+ help
+king
+ear
+ing the
+ It's
+very
+ talking
+ou've
+that's
+ to me
+@Hello
+ enemy
+ you are
+ by
+ IBNT
+ight
+on't
+est
+ick
+ power
+s that
+s are
+ call
+t is
+ is
+age
+, and
+ great
+@Thanks
+ people
+ter
+not
+ill
+ness
+@This
+ into
+ ha
+ I can
+t your
+ before
+ things
+tion
+ for
+be
+ this
+Happy
+ You
+out
+enough
+ I'm
+@You
+ go
+for
+ all th
+ though
+ing you
+e you
+ring
+one
+get
+t you
+@I'm s
+ the s
+e's
+port
+d...
+ out
+@What
+ get
+I have
+looking
+n this
+ of my
+ have a
+ent
+ do
+d of
+ting
+ncrease
+ Twoson
+through
+s your
+ou'll
+ place
+ right
+ Onett
+
+ our
+ too
+ater
+ be
+ain
+ing.
+take
+e is
+the
+ please
+ do you
+need
+use
+ now.
+ got
+
+and
+ning
+ sta
+ the t
+I was
+y the
+his
+appe
+con
+ ho
+hat's
+Threed
+get
+s of
+inter
+talk
+ man
+day
+ove
+ ha ha
+inally
+monkey
+s and
+ate
+see
+town
+side
+y to
+ever
+equip
+ than
+ who
+long
+care
+room
+e are
+ard
+end
+, you
+ the b
+I know
+think
+s you
+time
+ment
+ so
+!
+I've
+ell
+ wa
+ious
+reat
+live
+ange
+ we
+ble
+@.....
+ecover
+ only
+thing
+er...
+Mr.
+ough
+ now
+ I
+wor
+to
+ed.
+ I
+'re
+ give
+ing a
+return
+better
+ince
+ well
+ one
+still
+ying
+me...
+sta
+ust
+per
+lease
+ling
+point
+ect
+ast
+pretty
+Giygas
+ecause
+member
+carry
+ used
+elieve
+ money
+pres
+way
+ di
+ even
+ound
+s to
+ting
+ted
+oing
+ and
+ine
+ T
+sure
+ on
+@...I
+while
+@but
+@We
+ice
+se
+ive
+ rea
+ com
+est
+have
+ but
+away
+here
+ must
+ but
+ want
+ready
+I'm
+carr
+my
+ay...
+ are
+@Hey,
+world
+ing a
+ happ
+ seem
+ his
+nder
+ se
+ant
+item
+@But
+ sho
+in a
+made
+night
+et's
+ like
+ The
+che
+ de
+ful
+hat
+and
+self
+ould
+@No
+car
+ tr
+ good
+ stor
+ombie
+@Oh
+@It
+s a
+vent
+ant
+ so
+olla
+ree
+Your
+make
+ work
+power
+ home
+also
+ance
+@How
+h...
+@If
+ple
+buy
+e,
+n't
+oke
+n't
+more
+Good
+t a
+ all
+take
+round
+ when
+ name
+being
+attle
+ite
+ack
+y...
+@Whe
+with
+al
+have
+ look
+t of
+aster
+ a lo
+ feel
+here
+count
+monst
+now
+ark
+ous
+'ll
+ bus
+head
+ any
+t you
+erson
+after
+ummer
+ hard
+ful
+ever
+ kid
+ a b
+kay
+tra
+pla
+ook
+ome
+eat
+@Wel
+ off
+turn
+I am
+oney
+ any
+ave
+any
+enem
+ con
+ me
+red
+en
+ bo
+ re
+ell
+sell
+what
+next
+ure
+ bu
+pro
+ on
+res
+es
+ lo
+our
+use
+iste
+ood
+ no
+ope
+ock
+row
+@He
+how
+ may
+ as
+stor
+ are
+ost
+mean
+ We
+ He
+@An
+it
+in
+ound
+one.
+come
+blue
+way,
+ or
+ mo
+.
+each
+some
+@(Th
+otel
+hand
+came
+ ca
+ame
+@Hey
+een
+spec
+red
+ing!
+ ...
+hose
+ind
+ice
+ver
+ W
+min
+et
+on
+ace
+er.
+are
+nter
+o...
+were
+help
+old
+ it.
+hear
+stop
+look
+com
+@Do
+ her
+ pro
+chin
+ble
+e a
+girl
+luck
+ B
+act
+ike
+down
+part
+ see
+ use
+uch
+from
+just
+llow
+@Her
+ at
+@So,
+ ye
+ser
+ying
+ring
+eave
+big
+ried
+ was
+ say
+ su
+her
+it.
+Man
+kin
+ up
+ers
+@Why
+p...
+cour
+ him
+agic
+any
+fin
+eal
+he
+ide
+oor
+ity
+got
+ens
+ish
+ive
+ sa
+oon
+fee
+s a
+nce
+ D
+uck
+ass
+man
+a p
+@(I
+@Wh
+ gu
+ (
+bus
+los
+ts
+ ba
+ S
+ if
+@Ha
+ ma
+unn
+ay
+ner
+@A
+or
+stu
+ust
+kya
+shi
+pos
+ A
+int
+ le
+ary
+ven
+ ch
+own
+ hu
+@My
+@So
+ sp
+ sh
+wan
+ool
+ Y
+old
+eas
+, I
+iki
+@Ky
+@Ye
+spi
diff --git a/ScriptTool/ScriptTool/m12-codelist.txt b/ScriptTool/ScriptTool/m12-codelist.txt
new file mode 100644
index 0000000..59258a0
--- /dev/null
+++ b/ScriptTool/ScriptTool/m12-codelist.txt
@@ -0,0 +1,198 @@
+!00 FF,End (02)
+01 FF,New line (00)
+02 FF,Prompt + new line (03 00)
+03 FF XX XX,Display menu string set {0:X}
+04 FF XX XX XX XX,Jump to offset {0:X} if first choice selected
+05 FF XX XX XX XX,Jump to offset {0:X} if second choice selected
+08 FF XX XX,Set flag {0:X} (04)
+09 FF XX XX,Unset flag {0:X} (05)
+0C FF,Party leader's name
+0D FF,Ness' name (1C 02 01 or 1C 01 08)
+0E FF,Paula's name (1C 02 02)
+0F FF,Jeff's name (1C 02 03)
+10 FF,Poo's name (1C 02 04)
+11 FF,Favorite food (1C 01 04)
+12 FF,Favorite thing (1C 01 05)
+14 FF XX XX,Teleport to entry {0:X} (1F 21)
+15 FF,King's name (1C 02 07)
+!18 FF XX XX,Unknown
+1A FF XX XX,Display string {0:X}? (Appears after 90 FF)
+1B FF XX XX,Pause for {0:X} frames (10)
+1C FF XX XX YY YY YY YY,If flag {0:X} is set, jump to offset {1:X} and return (06)
+1D FF,Wait without prompt (13)
+1E FF XX XX,Return one letter from a character's name (19 11)
+1F FF XX XX,Display text character {0:X} (1C 03)
+20 FF,Display SMAAASH!! graphics (1C 08 01)
+21 FF,Display YOU WIN! graphics (1C 08 02)
+22 FF,Unknown
+23 FF,Display cash in ATM (1C 01 07)
+24 FF,Display character 1 defense (1C 01 12)
+25 FF,Display character 2 defense (1C 01 28)
+26 FF,Display character 3 defense (1C 01 3E)
+27 FF,Display character 4 defense (1C 01 54)
+28 FF,Display character 1 offense (1C 01 11)
+29 FF,Display character 2 offense (1C 01 27)
+2A FF,Display character 3 offense (1C 01 3D)
+2B FF,Display character 4 offense (1C 01 53)
+2D FF,Display PSI name? (1C 12 00)
+2E FF,Show main text window (18 01 01)
+5F FF XX,Set the current rendering location to {0:X} (custom code)
+60 FF XX,Add {0:X} pixels to the current rendering location (custom code)
+61 FF XX XX,Toggles sound for text printing (?) (1F 04)
+62 FF,Display inventory? (?)
+63 FF,Display cash on hand (1C 01 06)
+64 FF,Call special event (value pushed with FC FF?) (1F 41)
+65 FF,Purge all NPCs from party (1F 64)
+66 FF,Unknown (19 1E)
+67 FF,Unknown (19 28)
+68 FF,Clear main text window (sometimes used in place of 18 01 01)
+69 FF,Open party HP/PP status windows (1C 04)
+6A FF XX XX,Set binary flag if number in memory != {0:X} (0C)
+6B FF XX XX,Apply audio effect {0:X} (1F 07)
+6C FF XX XX YY YY,Sets binary flag true if character {0:X} does not have item {1:X} (1D 04)
+6D FF XX XX YY YY,Remove item {1:X} from character {0:X}'s inventory (1D 01)
+6E FF XX XX,Deactivate hotspot {0:X} (1F 67)
+6F FF XX XX YY YY,Boost vitality in character {0:X} by {1:X} (1E 0D, values match)
+70 FF XX XX YY YY,Boost speed in character {0:X} by {1:X} (1E 0C, values match)
+71 FF XX XX YY YY,Boost guts in character {0:X} by {1:X} (1E 0B, values match)
+72 FF XX XX YY YY,Boost IQ in character {0:X} by {1:X} (1E 0A, values match)
+73 FF XX XX YY YY,Boost luck in character {0:X} by {1:X} (1E 0E, values match)
+74 FF,Stop music (1F 01 00)
+75 FF,Unknown (?)
+76 FF XX XX,Show window flavor menu? Argument seems to tell it which string to start with (so a value of 1 will skip the first flavor and show the remaining 6)
+77 FF XX XX,Unknown (1D 18, values match)
+78 FF XX XX YY YY,Unknown (19 1D, values match)
+79 FF XX XX YY YY,Unknown (19 1C, values match)
+7A FF XX XX,Unknown (19 1A, values match)
+7B FF,Change to Mr. Saturn font (1F 31)
+7C FF,Change to normal font (1F 30)
+7D FF,Activate bicycle (1F F0)
+7E FF XX XX,Unknown (19 26)
+7F FF,Save the game in the slot selected at the beginning (1F B0)
+!80 FF XX XX XX XX,Jump to offset {0:X} and don't return (0A)
+81 FF XX XX XX XX,If binary flag is set, jump to offset {0:X} and return (1B 03)
+82 FF XX XX XX XX,If binary flag is unset, jump to offset {0:X} and return (1B 02)
+83 FF XX XX,Set binary flag to status of flag {0:X} (07)
+84 FF XX XX YY YY ZZ ZZ,Generate TPT entry {0:X} with movement pattern {1:X} using style {2:X} (1F 17)
+85 FF XX XX YY YY,Make TPT entry {0:X} disappear via style {1:X} (1F 1E)
+86 FF XX XX XX XX,Jump to offset {0:X} and return (08)
+87 FF,Swap working and argumentary memory (1B 04)
+88 FF,Copy all memory to storage (1B 00)
+89 FF,Copy all storage to memory (1B 01)
+8A FF,Copy active memory to working memory (1B 05)
+8B FF,Copy working memory to active memory (1B 06)
+8C FF,Unknown (1F A2)
+8D FF XX XX,Load character number in party position {0:X} (19 10)
+8E FF,TPT entry being spoken to/checked faces downwards (1F A0)
+8F FF,Change direction of current TPT entry to down (1F A1)
+90 FF XX,Load character/item name {0:X} into memory (?)
+91 FF XX,Set binary flag if character {0:X} has free space in their inventory (1D 03)
+92 FF XX YY YY,Set binary flag true if character {0:X} has ailment {1:X} (corresponds exactly to 1D 0D XX YY YY)
+93 FF XX YY,Add item {1:X} to character {0:X}'s inventory and return number of items held (1D 0E)
+94 FF XX,Display shop window {0:X} (1A 06)
+*95 FF,Jump table with return (09)
+96 FF XX,Set binary flag if there are {0:X} party members (1D 19)
+97 FF XX,Set binary flag if character {0:X} does not have free space in their inventory (1D 02)
+98 FF XX,Return price of item {0:X} (1D 0A)
+99 FF XX XX XX XX,Sets binary flag if the player currently holds {0:X} in the wallet (1D 14)
+9A FF XX XX,Set binary flag if number in memory == {0:X} (0B)
+9B FF XX XX XX XX,Remove {0:X} dollars from wallet (1D 09)
+9C FF,Open wallet window and display current cash on hand (18 0A)
+9D FF XX YY ZZ ZZ ZZ ZZ,Activate hotspot {0:X} with address ${2:X} (1F 66)
+9E FF,Simultaneously commences all prepared movements (1F 61)
+9F FF,Display action user name (1C 0D)
+A0 FF XX XX YY YY ZZ ZZ,Generate sprite {0:X} with movement pattern {1:X} using style {2:X} (1F 15)
+A1 FF XX XX YY YY,Apply movement pattern {1:X} to TPT entry {0:X} (1F F1)
+A2 FF XX XX XX XX,After finishing this text block, close all other windows, jump to offset {0:X} and don't return (?)
+A3 FF XX XX,Restrict player movement until end of text block (1F E9, use 9E FF after)
+A4 FF XX XX,Delay appearance of TPT entry {0:X} until end of text parsing (1F E6)
+A5 FF XX XX YY YY,Delete A0 FF-generated sprite {0:X} via style {1:X} (1F 1F)
+A6 FF XX XX YY YY,Apply movement pattern {1:X} to sprite {0:X} (1F F2)
+A7 FF XX XX,Trigger battle with enemy group entry {0:X} (1F 23)
+A8 FF XX,Copy from memory {0:X} into argumentative memory (0D)
+A9 FF XX XX,Unknown (1F EA, values don't match)
+AA FF XX XX,Unknown (1F E7, values don't match)
+AB FF XX YY,Copies item {1:X} from character {0:X}'s inventory into working memory (19 19)
+AC FF,Unknown (1C 0F)
+AD FF,Display action target name (1C 0E)
+AE FF XX,Add character {0:X} to party (1F 11)
+AF FF XX,Remove character {0:X} from party (1F 12)
+B0 FF XX XX YY YY,Generate sprite {1:X} near TPT entry {0:X} (1F 1A)
+B1 FF XX XX YY YY,Generate sprite {1:X} near sprite {0:X} (1F F3)
+B2 FF XX XX YY YY,Generate sprite {1:X} near character {0:X} (1F 1C)
+B3 FF XX XX,Delete B0 FF sprite near TPT entry {0:X} (1F 1B)
+B4 FF XX XX,Delete B1 FF sprite near sprite {0:X} (1F F4)
+B5 FF XX XX,Delete B2 FF sprite near character {0:X} (1F 1D)
+B6 FF XX YY,Display the inventory of character {1:X} in window {0:X} (1A 05, values match)
+B7 FF XX,Return half-price of item {0:X} (1D 0B)
+B8 FF XX,Load {0:X} into memory as a decimal string (related to 1C 0A)
+B9 FF XX XX YY YY,Unknown (1D 0F)
+BA FF XX XX XX XX,Add {0:X} dollars to wallet (1D 08)
+BB FF XX,Multiply {0:X} by the number of characters in the party, and store in working memory (1D 15)
+BC FF XX XX,Input {0:X} into memory bank (0E)
+*BD FF,Jump table with return (1F C0)
+BE FF XX XX YY YY,Set binary flag if character {0:X} has item {1:X} (1D 05)
+BF FF,Unknown (19 20)
+C0 FF,Increment secondary memory (0F)
+C1 FF,Unknown (19 1F)
+C2 FF,Unknown, appears before 1A FF 05 00 (1C 11 69)
+C3 FF XX XX YY YY,Store to memory the direction from character {0:X} to object {1:X} (19 22)
+C4 FF XX YY,Play music {1:X} (1F 00)
+C5 FF,Return music to sector default (1F 03)
+C6 FF,Close all windows (18 04)
+C7 FF,Close most recently-used window (18 00)
+C8 FF XX XX,Number selector with {0:X} digits (1F 52)
+C9 FF XX XX,Focuses the camera to where A0 FF-generated sprite {0:X} is but doesn't follow it (1F EF)
+CA FF,Your party will be teleported (in a glitchy way) on top of some sprite that is nearby when the text block is over if the camera switched to another sprite in the block of text (1F ED)
+CB FF XX XX YY YY,Make character {0:X} disappear via style {1:X} (1F EB)
+CC FF XX XX YY YY,Make character {0:X} appear via style {1:X} (1F EC)
+CD FF XX XX YY YY ZZ ZZ,Change map palette to that of tileset {0:X}, palette {1:X} at speed {2:X} (1F E1)
+CE FF XX XX YY YY,Turn TPT entry {0:X} to direction {1:X} (1F 16)
+CF FF XX XX,Trigger timed event {0:X} (1F D3)
+D0 FF XX XX XX XX,Sets binary flag true if the player currently holds {0:X} in the ATM (1D 17)
+D1 FF XX XX XX XX,Add {0:X} dollars to ATM balance (1D 06)
+D3 FF XX XX,Remove {0:X} dollars from ATM balance? (1D 07)
+D4 FF XX XX,Play sound effect {0:X} (1F 02)
+D5 FF XX XX,Unknown (1D 23, value matches)
+D6 FF XX XX YY YY,Unknown (1D 11, value matches)
+D7 FF XX XX YY YY,Equip character {0:X} with their {1:X}'th item (1F 83)
+D8 FF XX XX YY YY,Turn character {0:X} to direction {1:X} (1F 13)
+D9 FF XX XX,Unknown (1F 14 XX)
+DA FF XX XX YY YY,Unknown (occurs after A0 FF and DC FF)
+DB FF XX XX YY YY ZZ ZZ,Store to memory the direction from TPT entry {0:X} to object {1:X} (ZZ unknown) (19 23)
+DC FF XX XX YY YY ZZ ZZ,Unknown (sandwiched between DA FF codes?)
+DD FF XX XX XX XX YY YY YY YY,Check for {0:X} inequality to memory (18 07)
+DE FF,Unknown (?)
+DF FF,Open non-working phone window (1A 0A)
+E0 FF XX XX,May relate to returning cash earned since last phone call to dad (1D 24, value matches)
+E1 FF XX XX,Unknown (19 18, value matches)
+E2 FF XX XX,Summon photographer to location {0:X} (1F D2)
+E3 FF,Unknown (19 21 00)
+E4 FF,Unknown (19 25 00)
+E5 FF,Check same user/target (1D 20)
+E6 FF XX XX,Unknown (1F EE, value matches)
+E7 FF XX XX,Show Mach Pizza window? Different arguments yield different menu strings (02 = full A menu, 03 = A menu sans PSI, and more)
+E8 FF XX XX YY YY,Character {0:X} realizes special PSI {1:X} (1F 71)
+E9 FF XX XX YY YY YY YY,Character {0:X} gains {1:X} EXP (1E 09)
+EA FF XX XX,Lock player movement (1F E5, value matches)
+EB FF XX XX,Restrict player movement if camera is focused on other sprite (XX is usually FF) (1F E8)
+EC FF,Display party select menu? (1A 01)
+ED FF XX XX YY YY,Return byte {1:X} of character {0:X} status (19 16, values match)
+EE FF XX XX YY YY ZZ ZZ,Inflict ailment {1:X} on character {0:X} (ZZ might possibly be the value to set? 0 for off, 1 for on) (YY and ZZ flipped w.r.t EB) (19 05)
+EF FF XX XX YY YY,PSI Teleport to destination {0:X} using method {1:X} (1F 20, values match)
+F0 FF XX XX YY YY,Display animation {0:X} (YY is usually 00) (1C 13)
+F1 FF,Show Escargo Express window? ([18 02][1A 07][18 03 0D][18 00][18 03 01][01])
+F2 FF XX XX XX XX,Unknown (1D 13, values match)
+F3 FF XX XX XX XX,Unknown (1D 12, values match)
+F4 FF XX XX YY YY,Unknown (1D 0C, values match)
+F5 FF,Unknown (19 14)
+F6 FF XX XX,Generate random number from 0 to {0:X} (1D 21)
+F7 FF XX XX YY YY,Character {0:X} recovers HP by {1:X} of max (1E 00)
+F8 FF XX XX YY YY,Character {0:X} recovers PP by {1:X} of max (1E 04)
+F9 FF XX XX YY YY,Character {0:X} recovers HP by {1:X} of max (not sure of the difference with F7 FF) (1E 00)
+FA FF XX XX YY YY,Character {0:X} recovers PP by {1:X} of max (not sure of the difference with F8 FF) (1E 04)
+FB FF XX XX,Attempt to fix items (1F D0)
+FC FF XX XX,Unknown; load some kind of value into memory? Observed: [FC FF 06 00] [64 FF] == [1F 41 06]
+FD FF,Set binary flag if current sector is Exit mouse-compatible (1D 22)
+FE FF,Store current coordinates into memory (1F 68)
+FF FF,Returns a numeric value based on the proximity of a magic truffle TPT entry (1F D1)
\ No newline at end of file
diff --git a/ScriptTool/ScriptTool/m12-text-table.txt b/ScriptTool/ScriptTool/m12-text-table.txt
new file mode 100644
index 0000000..8182fd1
--- /dev/null
+++ b/ScriptTool/ScriptTool/m12-text-table.txt
@@ -0,0 +1,256 @@
+ぁ
+あ
+ぃ
+い
+ぅ
+う
+ぇ
+え
+ぉ
+お
+か
+が
+き
+ぎ
+く
+ぐ
+け
+げ
+こ
+ご
+さ
+ざ
+し
+じ
+す
+ず
+せ
+ぜ
+そ
+ぞ
+た
+だ
+ち
+ぢ
+っ
+つ
+づ
+て
+で
+と
+ど
+な
+に
+ぬ
+ね
+の
+は
+ば
+ぱ
+ひ
+び
+ぴ
+ふ
+ぶ
+ぷ
+へ
+べ
+ぺ
+ほ
+ぼ
+ぽ
+ま
+み
+む
+め
+も
+ゃ
+や
+ゅ
+ゆ
+ょ
+よ
+ら
+り
+る
+れ
+ろ
+
+わ
+
+
+を
+ん
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+ァ
+ア
+ィ
+イ
+ゥ
+ウ
+ェ
+エ
+ォ
+オ
+カ
+ガ
+キ
+ギ
+ク
+グ
+ケ
+ゲ
+コ
+ゴ
+サ
+ザ
+シ
+ジ
+ス
+ズ
+セ
+ゼ
+ソ
+ゾ
+タ
+ダ
+チ
+ヂ
+ッ
+ツ
+ヅ
+テ
+デ
+ト
+ド
+ナ
+ニ
+ヌ
+ネ
+ノ
+ハ
+バ
+パ
+ヒ
+ビ
+ピ
+フ
+ブ
+プ
+ヘ
+ベ
+ペ
+ホ
+ボ
+ポ
+マ
+ミ
+.
+ム
+メ
+モ
+ャ
+ヤ
+ュ
+ユ
+ョ
+ヨ
+ラ
+リ
+ル
+レ
+ロ
+
+ワ
+
+
+ヲ
+ン
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+INVALID
+A
+B
+C
+D
+E
+F
+G
+H
+I
+J
+K
+L
+M
+N
+O
+P
+Q
+R
+S
+T
+U
+V
+W
+X
+Y
+Z
+α
+β
+γ
+Σ
+Ω
+
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+#
+!
+?
+/
+:
+ー
+~
+·
+··
+。
+“
+”
+(
+)
+♪
+◯
+$
+"
+°
+◆
+(e)
+CONTROL CODE
\ No newline at end of file
diff --git a/ScriptTool/ScriptTool/packages.config b/ScriptTool/ScriptTool/packages.config
new file mode 100644
index 0000000..af70bc8
--- /dev/null
+++ b/ScriptTool/ScriptTool/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file