Rewrite xytext

Have an object do the work.
Makes it much easier to tweak the source / reuse in past gens (by
changing the config).
This commit is contained in:
Kaphotics
2016-05-18 20:17:59 -07:00
parent 0bb169e44c
commit 6b74dda8ff
4 changed files with 451 additions and 451 deletions

View File

@@ -985,11 +985,11 @@ public void restoreGARCs(bool oras_define, params string[] g)
// Text Requests
internal static string[] getText(int file)
{
return xytext.getStringsFromFile("gametext" + Path.DirectorySeparatorChar + file.ToString("000") + ".bin");
return TextFile.getStrings("gametext" + Path.DirectorySeparatorChar + file.ToString("000") + ".bin");
}
internal static bool setText(int file, string[] strings)
{
byte[] data = xytext.getBytesForFile(strings);
byte[] data = TextFile.getBytes(strings);
string path = "gametext" + Path.DirectorySeparatorChar + file.ToString("000") + ".bin";
File.WriteAllBytes(path, data);
return true;

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
@@ -54,7 +53,7 @@ private void exportTextFile(string fileName, bool newline)
for (int i = 0; i < files.Length; i++)
{
// Get Strings for the File
string[] data = getStringsFromFile(files[i]);
string[] data = TextFile.getStrings(files[i]);
// Append the File Header
tw.WriteLine("~~~~~~~~~~~~~~~");
tw.WriteLine("Text File : " + i);
@@ -87,15 +86,15 @@ private bool importTextFile(string fileName)
for (int i = 0; i < fileText.Length; i++)
{
string line = fileText[i];
if (line != "~~~~~~~~~~~~~~~")
continue;
string[] brokenLine = fileText[i++ + 1].Split(new[] {" : "}, StringSplitOptions.None);
if (brokenLine.Length != 2)
{ Util.Error($"Invalid Line @ {i}, expected Text File : {ctr}"); return false; }
if (line != "~~~~~~~~~~~~~~~")
continue;
string[] brokenLine = fileText[i++ + 1].Split(new[] { " : " }, StringSplitOptions.None);
if (brokenLine.Length != 2)
{ Util.Error($"Invalid Line @ {i}, expected Text File : {ctr}"); return false; }
int file = Util.ToInt32(brokenLine[1]);
if (file != ctr)
{ Util.Error($"Invalid Line @ {i}, expected Text File : {ctr}"); return false; }
i+=2; // Skip over the other header line
if (file != ctr)
{ Util.Error($"Invalid Line @ {i}, expected Text File : {ctr}"); return false; }
i += 2; // Skip over the other header line
List<string> Lines = new List<string>();
while (i < fileText.Length && fileText[i] != "~~~~~~~~~~~~~~~")
{
@@ -116,11 +115,9 @@ private bool importTextFile(string fileName)
"When exporting text, do not remove newline formatting."); return false; }
// All Text Lines received. Store all back.
try {
for (int i = 0; i < files.Length; i++)
File.WriteAllBytes(files[i], getBytesForFile(textLines[i]));
}
catch { return false; }
for (int i = 0; i < files.Length; i++)
try { File.WriteAllBytes(files[i], TextFile.getBytes(textLines[i])); }
catch (Exception e) { Util.Error($"The input Text File (# {i}) failed to convert:", e.ToString()); return false; }
return true;
}
private void changeEntry(object sender, EventArgs e)
@@ -128,15 +125,18 @@ private void changeEntry(object sender, EventArgs e)
// Save All the old text
if (entry > -1 && sender != null)
{
byte[] bin = getBytesForFile(getCurrentDGLines());
if (bin != null)
try
{
byte[] bin = TextFile.getBytes(getCurrentDGLines());
File.WriteAllBytes(files[entry], bin);
}
catch (Exception ex) { Util.Error(ex.ToString()); }
}
// Reset
entry = CB_Entry.SelectedIndex;
string file = files[entry];
string[] data = getStringsFromFile(file);
string[] data = TextFile.getStrings(file);
setStringsDataGridView(data);
}
// Main Handling
@@ -144,13 +144,10 @@ private void setStringsDataGridView(string[] textArray)
{
// Clear the datagrid row content to remove all text lines.
dgv.Rows.Clear();
if (textArray == null) // Error handling for bad inputs.
return;
// Clear the header columns, these are repopulated every time.
dgv.Columns.Clear();
if (textArray == null || textArray.Length == 0)
return;
// Reset settings and columns.
dgv.AllowUserToResizeColumns = false;
DataGridViewColumn dgvLine = new DataGridViewTextBoxColumn
@@ -170,12 +167,9 @@ private void setStringsDataGridView(string[] textArray)
SortMode = DataGridViewColumnSortMode.NotSortable,
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
};
// Re-add the columns.
dgv.Columns.Add(dgvLine);
dgv.Columns.Add(dgvText);
// Add empty rows equal to how many entries.
dgv.Rows.Add(textArray.Length);
// Add the text lines into their cells.
@@ -231,431 +225,10 @@ private void B_RemoveLine_Click(object sender, EventArgs e)
for (int i = 0; i < dgv.Rows.Count; i++)
dgv.Rows[i].Cells[0].Value = i.ToString();
}
// Top Level Functions
internal static string[] getStringsFromFile(string path)
{
byte[] data = File.ReadAllBytes(path);
ushort textSections = BitConverter.ToUInt16(data, 0);
ushort lineCount = BitConverter.ToUInt16(data, 2);
if (lineCount == 0) return null;
uint totalLength = BitConverter.ToUInt32(data, 4);
uint initialKey = BitConverter.ToUInt32(data, 8);
int sectionData = BitConverter.ToInt32(data, 12);
try // Some sanity checking to prevent errors.
{
if (initialKey != 0) throw new Exception("Invalid initial key! Not 0?");
if (sectionData + totalLength != data.Length || textSections != 1) throw new Exception("Invalid Text File");
uint sectionLength = BitConverter.ToUInt32(data, sectionData);
if (sectionLength != totalLength) throw new Exception("Section size and overall size do not match.");
}
catch { return null; }
// Prep result storage.
ushort key = 0x7C89;
string[] result = new string[lineCount];
for (int i = 0; i < lineCount; i++)
{
// Init
ushort k = key;
string s = "";
int offset = BitConverter.ToInt32(data, i * 8 + sectionData + 4) + sectionData;
int length = BitConverter.ToInt16(data, i * 8 + sectionData + 8);
int start = offset;
ushort c = 0; // u16 char
while (offset < start + length * 2) // loop through the entire text line
{
decryptU16(data, ref offset, ref c, ref k);
if (c == 0) // Terminated Line
break;
switch (c)
{
case '\n': s += "\\n";
break;
case '\\': s += "\\\\";
break;
case '[': s += "\\[";
break;
case 0x10: decryptVar(data, ref offset, ref s, ref c, ref k);
break;
case 0xE07F: s += (char)0x202F; // nbsp
break;
case 0xE08D: s += (char)0x2026; // …
break;
case 0xE08E: s += (char)0x2642; // ♂
break;
case 0xE08F: s += (char)0x2640; // ♀
break;
default: s += (char)c;
break;
}
}
// store string and set key for next line (if needed)
result[i] = s;
key += 0x2983;
}
return result;
}
internal static byte[] getBytesForFile(string[] lines)
{
using (MemoryStream textFile = new MemoryStream())
using (BinaryWriter bw = new BinaryWriter(textFile))
using (MemoryStream data = new MemoryStream())
using (BinaryWriter bz = new BinaryWriter(data))
{
ushort baseKey = 0x7C89;
// Write up header template
bw.Write((ushort)1); // Always 1 ?
bw.Write((ushort)lines.Length); // Line Count
bw.Write((uint)0); // (Temporary) Data Length - fixed at the end.
bw.Write((uint)0); // Key, constant 0.
bw.Write((uint)0x10); // Pointer to line data.
// Begin data
bw.Write((uint)0); // (Temporary) Data Length - fixed at the end.
for (int i = 0; i < lines.Length; i++)
{
try
{
ushort key = baseKey;
uint pos = (uint) data.Position;
if (lines[i] == null) lines[i] = $"[~ {i}]";
// Get crypted line data.
// Write each character to the data stream, with handling for special characters.
for (int j = 0; j < lines[i].Length; j++)
{
ushort val = lines[i][j];
// Handle special text characters
// Private Use Area characters
if (val == 0x202F) val = 0xE07F; // nbsp
else if (val == 0x2026) val = 0xE08D; // …
else if (val == 0x2642) val = 0xE08E; // ♂
else if (val == 0x2640) val = 0xE08F; // ♀
// Variables
if (val == '[' || val == '\\') // Variable
encryptVar(bz, lines[i], ref j, ref key);
// Text
else bz.Write(encryptU16(val, ref key));
}
bz.Write(encryptU16(0, ref key)); // Add the null terminator, after encrypting it.
// Write the lineOffset and charCount to the header.
bw.Write((uint) (pos + 0x4 + lines.Length*8));
bw.Write((uint) (data.Position - pos)/2);
if (data.Position%4 > 0) bz.Write((ushort) 0);
// Increment the line initial key for the next line.
baseKey += 0x2983;
}
catch (Exception e) { Util.Error(e.ToString()); return null; }
}
// Copy the data stream to the textFile stream.
data.Position = 0; data.CopyTo(textFile);
// Fix the temp values
textFile.Position = 0x4; bw.Write((uint)textFile.Length - 0x10);
textFile.Position = 0x10; bw.Write((uint)textFile.Length - 0x10);
return textFile.ToArray();
}
}
// Text Encrypting
internal static ushort encryptU16(ushort val, ref ushort key)
{
val = (ushort)(key ^ val);
key = (ushort)(key << 3 | key >> 13);
return val;
}
internal static ushort decryptU16(byte[] data, ref int offset, ref ushort val, ref ushort key)
{
val = (ushort)(BitConverter.ToUInt16(data, offset) ^ key);
offset += 2;
key = (ushort)(key << 3 | key >> 13);
return val;
}
// Variable Handling
internal static ushort getVariableBytes(string varType, ref List<ushort> args, bool noArgs = false)
{
// Fetch the variable name...
int bracket = varType.IndexOf('(');
string variable = noArgs ? varType : varType.Substring(0, bracket);
string[] arguments = noArgs ? null : varType.Substring(bracket + 1, varType.Length - bracket - 2).Split(',');
ushort varVal;
switch (variable)
{
case "COLOR": varVal = 0xFF00; break;
case "TRNAME": varVal = 0x0100; break;
case "PKNAME": varVal = 0x0101; break;
case "PKNICK": varVal = 0x0102; break;
case "TYPE": varVal = 0x0103; break;
case "LOCATION": varVal = 0x0105; break;
case "ABILITY": varVal = 0x0106; break;
case "MOVE": varVal = 0x0107; break;
case "ITEM1": varVal = 0x0108; break;
case "ITEM2": varVal = 0x0109; break;
case "sTRBAG": varVal = 0x010A; break;
case "BOX": varVal = 0x010B; break;
case "EVSTAT": varVal = 0x010D; break;
case "OPOWER": varVal = 0x0110; break;
case "RIBBON": varVal = 0x0127; break;
case "MIINAME": varVal = 0x0134; break;
case "WEATHER": varVal = 0x013E; break;
case "TRNICK": varVal = 0x0189; break;
case "1stchrTR": varVal = 0x018A; break;
case "SHOUTOUT": varVal = 0x018B; break;
case "BERRY": varVal = 0x018E; break;
case "REMFEEL": varVal = 0x018F; break;
case "REMQUAL": varVal = 0x0190; break;
case "WEBSITE": varVal = 0x0191; break;
case "CHOICECOS": varVal = 0x019C; break;
case "GSYNCID": varVal = 0x01A1; break;
case "PRVIDSAY": varVal = 0x0192; break;
case "BTLTEST": varVal = 0x0193; break;
case "GENLOC": varVal = 0x0195; break;
case "CHOICEFOOD": varVal = 0x0199; break;
case "HOTELITEM": varVal = 0x019A; break;
case "TAXISTOP": varVal = 0x019B; break;
case "MAISTITLE": varVal = 0x019F; break;
case "ITEMPLUR0": varVal = 0x1000; break;
case "ITEMPLUR1": varVal = 0x1001; break;
case "GENDBR": varVal = 0x1100; break;
case "NUMBRNCH": varVal = 0x1101; break;
case "iCOLOR2": varVal = 0x1302; break;
case "iCOLOR3": varVal = 0x1303; break;
case "NUM1": varVal = 0x0200; break;
case "NUM2": varVal = 0x0201; break;
case "NUM3": varVal = 0x0202; break;
case "NUM4": varVal = 0x0203; break;
case "NUM5": varVal = 0x0204; break;
case "NUM6": varVal = 0x0205; break;
case "NUM7": varVal = 0x0206; break;
case "NUM8": varVal = 0x0207; break;
case "NUM9": varVal = 0x0208; break;
default: varVal = Convert.ToUInt16(variable, 16); break;
}
if (!noArgs)
// Set arguments in.
args.AddRange(arguments.Select(t => Convert.ToUInt16(t, 16)));
// All done.
return varVal;
}
internal static void encryptVar(BinaryWriter bw, string line, ref int i, ref ushort key)
{
ushort val = line[i];
if (val == '\\') // Line Break
if (line[i + 1] == 'n')
{
i++;
bw.Write(encryptU16('\n', ref key));
}
else if (line[i + 1] == 'r')
{
bw.Write(encryptU16(0x10, ref key)); i++;
bw.Write(encryptU16(1, ref key));
bw.Write(encryptU16(0xBE00, ref key));
}
else if (line[i + 1] == 'c')
{
bw.Write(encryptU16(0x10, ref key)); i++;
bw.Write(encryptU16(1, ref key));
bw.Write(encryptU16(0xBE01, ref key));
}
else if (line[i + 1] == '\\') // Not Formatting
{
i++;
bw.Write(encryptU16('\\', ref key));
}
else if (line[i + 1] == '[') // Not Variable
{
i++;
bw.Write(encryptU16('[', ref key));
}
else
{
if (DialogResult.Yes != Util.Prompt(
MessageBoxButtons.YesNo,
"Encoding Error - Please Resolve:",
string.Format("File: {3}{0}Char: {1}{0}Line:{0}{2}", Environment.NewLine, line[i+1], line, i),
"Treat as literal '\\'?"))
throw new Exception("Invalid terminated line: " + line);
i++;
bw.Write(encryptU16('\\', ref key));
}
else if (val == '[') // Special Variable
{
int bracket = line.Substring(i + 1).IndexOf(']');
if (bracket < 3) throw new Exception("Variable encoding error!");
// [VAR X(a, b)]
// Remove the [ ] -> VAR X(a, b)
string varCMD = line.Substring(i + 1, bracket);
i += bracket + 1; // Advance the index to the end of the bracket.
string[] split = varCMD.Split(' ');
string varMethod = split[0]; // Returns VAR or WAIT or ~
string varType = split.Length > 1 ? varCMD.Substring(varMethod.Length + 1) : ""; // Returns the remainder of the var command data.
ushort varValue;
// Set up argument storage (even if it not used)
List<ushort> args = new List<ushort>();
try
{
switch (varMethod)
{
case "~": // Blank Text Line Variable (No text set - debug/quality testing variable?)
{
varValue = 0xBDFF;
args.Add(Convert.ToUInt16(varType));
break;
}
case "WAIT": // Event pause Variable.
{
varValue = 0xBE02;
args.Add(Convert.ToUInt16(varType));
break;
}
case "VAR": // Text Variable
{
varValue = getVariableBytes(varType, ref args, varCMD.IndexOf('(') < 0);
break;
}
default: throw new Exception("Unknown variable method type!");
}
}
catch (Exception e)
{
Util.Alert("Variable error. Current line text is:", line, e.ToString());
throw new Exception("Cannot save the file.");
}
// Write the Variable prefix.
bw.Write(encryptU16(0x0010, ref key));
// Write Length of the following Variable Data
bw.Write(encryptU16((ushort)(1 + args.Count), ref key));
// Write the Variable type.
bw.Write(encryptU16(varValue, ref key));
foreach (ushort t in args)
bw.Write(encryptU16(t, ref key));
// Done.
}
}
internal static void decryptVar(byte[] d, ref int o, ref string s, ref ushort v, ref ushort k)
{
ushort length = decryptU16(d, ref o, ref v, ref k);
ushort varType = decryptU16(d, ref o, ref v, ref k);
switch (varType)
{
// Check the nonvariable types...
case 0xBE00: // "Waitbutton then scroll text;; \r"
{ s += "\\r"; return; }
case 0xBE01: // "Waitbutton then clear text;; \c"
{ s += "\\c"; return; }
case 0xBE02: // Dramatic pause for a text line. New!
{ s += "[WAIT " + decryptU16(d, ref o, ref v, ref k) + "]"; return; }
case 0xBDFF: // Empty Text line? Includes linenum so maybe for betatest finding used-unused lines?
{ s += "[~ " + decryptU16(d, ref o, ref v, ref k) + "]"; return; }
// Else a text variable, so let's loop through all the variable types. If we cannot find it, we just write the u16 val.
default:
{
ushort varCode = varType; // decryptU16(d, ref o, ref v, ref k);
string tvname;
switch (varCode) // get variable's info name
{
case 0xFF00: tvname = "COLOR"; break; // Change text line color (0 = white, 1 = red, 2 = blue...)
case 0x0100: tvname = "TRNAME"; break; //
case 0x0101: tvname = "PKNAME"; break;
case 0x0102: tvname = "PKNICK"; break;
case 0x0103: tvname = "TYPE"; break;
case 0x0105: tvname = "LOCATION"; break;
case 0x0106: tvname = "ABILITY"; break;
case 0x0107: tvname = "MOVE"; break;
case 0x0108: tvname = "ITEM1"; break;
case 0x0109: tvname = "ITEM2"; break;
case 0x010A: tvname = "sTRBAG"; break;
case 0x010B: tvname = "BOX"; break;
case 0x010D: tvname = "EVSTAT"; break;
case 0x0110: tvname = "OPOWER"; break;
case 0x0127: tvname = "RIBBON"; break;
case 0x0134: tvname = "MIINAME"; break;
case 0x013E: tvname = "WEATHER"; break;
case 0x0189: tvname = "TRNICK"; break;
case 0x018A: tvname = "1stchrTR"; break;
case 0x018B: tvname = "SHOUTOUT"; break;
case 0x018E: tvname = "BERRY"; break;
case 0x018F: tvname = "REMFEEL"; break;
case 0x0190: tvname = "REMQUAL"; break;
case 0x0191: tvname = "WEBSITE"; break;
case 0x019C: tvname = "CHOICECOS"; break;
case 0x01A1: tvname = "GSYNCID"; break;
case 0x0192: tvname = "PRVIDSAY"; break;
case 0x0193: tvname = "BTLTEST"; break;
case 0x0195: tvname = "GENLOC"; break;
case 0x0199: tvname = "CHOICEFOOD"; break;
case 0x019A: tvname = "HOTELITEM"; break;
case 0x019B: tvname = "TAXISTOP"; break;
case 0x019F: tvname = "MAISTITLE"; break;
case 0x1000: tvname = "ITEMPLUR0"; break;
case 0x1001: tvname = "ITEMPLUR1"; break;
case 0x1100: tvname = "GENDBR"; break;
case 0x1101: tvname = "NUMBRNCH"; break;
case 0x1302: tvname = "iCOLOR2"; break;
case 0x1303: tvname = "iCOLOR3"; break;
case 0x0200: tvname = "NUM1"; break;
case 0x0201: tvname = "NUM2"; break;
case 0x0202: tvname = "NUM3"; break;
case 0x0203: tvname = "NUM4"; break;
case 0x0204: tvname = "NUM5"; break;
case 0x0205: tvname = "NUM6"; break;
case 0x0206: tvname = "NUM7"; break;
case 0x0207: tvname = "NUM8"; break;
case 0x0208: tvname = "NUM9"; break;
default: tvname = varCode.ToString("X4"); break;
}
s += "[VAR" + " " + tvname;
if (length > 1)
{
s += '(';
while (length > 1)
{
// Write arguments
ushort arg = decryptU16(d, ref o, ref v, ref k);
length--;
s += arg.ToString("X4");
if (length == 1) break;
s += ",";
}
s += ')';
}
s += "]";
break;
}
}
}
private void xytext_FormClosing(object sender, FormClosingEventArgs e)
{
// Save All the old text
if (entry > -1) File.WriteAllBytes(files[entry], getBytesForFile(getCurrentDGLines()));
if (entry > -1) File.WriteAllBytes(files[entry], TextFile.getBytes(getCurrentDGLines()));
}
}
}

426
pk3DS/Tools/TextFile.cs Normal file
View File

@@ -0,0 +1,426 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace pk3DS
{
public class TextFile
{
// Text Formatting Config
private const ushort KEY_BASE = 0x7C89;
private const ushort KEY_ADVANCE = 0x2983;
private const ushort KEY_VARIABLE = 0x0010;
private const ushort KEY_TERMINATOR = 0x0000;
private const ushort KEY_TEXTRETURN = 0xBE00;
private const ushort KEY_TEXTCLEAR = 0xBE01;
private const ushort KEY_TEXTWAIT = 0xBE02;
private const ushort KEY_TEXTNULL = 0xBDFF;
private const bool SETEMPTYTEXT = true;
private static readonly byte[] emptyTextFile = { 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00 };
public TextFile(byte[] data = null)
{
Data = (byte[])(data ?? emptyTextFile).Clone();
if (InitialKey != 0)
throw new Exception("Invalid initial key! Not 0?");
if (SectionDataOffset + TotalLength != Data.Length || TextSections != 1)
throw new Exception("Invalid Text File");
if (SectionLength != TotalLength)
throw new Exception("Section size and overall size do not match.");
}
private ushort TextSections { get { return BitConverter.ToUInt16(Data, 0x0); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x0); } } // Always 0x0001
private ushort LineCount { get { return BitConverter.ToUInt16(Data, 0x2); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x2); } }
private uint TotalLength { get { return BitConverter.ToUInt32(Data, 0x4); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x4); } }
private uint InitialKey { get { return BitConverter.ToUInt32(Data, 0x8); } set { BitConverter.GetBytes(value).CopyTo(Data, 0x8); } } // Always 0x00000000
private uint SectionDataOffset { get { return BitConverter.ToUInt32(Data, 0xC); } set { BitConverter.GetBytes(value).CopyTo(Data, 0xC); } } // Always 0x0010
private uint SectionLength { get { return BitConverter.ToUInt32(Data, (int)SectionDataOffset); } set { BitConverter.GetBytes(value).CopyTo(Data, SectionDataOffset); } }
private LineInfo[] LineOffsets
{
get
{
LineInfo[] result = new LineInfo[LineCount];
int sdo = (int)SectionDataOffset;
for (int i = 0; i < result.Length; i++)
result[i] = new LineInfo
{
Offset = BitConverter.ToInt32(Data, i * 8 + sdo + 4) + sdo,
Length = BitConverter.ToInt32(Data, i * 8 + sdo + 8)
};
return result;
}
set
{
if (value == null)
return;
int sdo = (int)SectionDataOffset;
for (int i = 0; i < value.Length; i++)
{
BitConverter.GetBytes(value[i].Offset).CopyTo(Data, i * 8 + sdo + 4);
BitConverter.GetBytes(value[i].Length).CopyTo(Data, i * 8 + sdo + 8);
}
}
}
private class LineInfo
{
public int Offset, Length;
}
public string[] Lines
{
get
{
ushort key = KEY_BASE;
string[] result = new string[LineCount];
LineInfo[] lines = LineOffsets;
for (int i = 0; i < lines.Length; i++)
{
byte[] EncryptedLineData = Data.Skip(lines[i].Offset).Take(lines[i].Length * 2).ToArray();
byte[] DecryptedLineData = cryptLineData(EncryptedLineData, key);
result[i] = getLineString(DecryptedLineData);
key += KEY_ADVANCE;
}
return result;
}
set
{
if (value == null)
value = new string[0];
ushort key = KEY_BASE;
LineInfo[] lines = new LineInfo[value.Length];
// Get Line Data
byte[][] lineData = new byte[lines.Length][];
int sdo = (int)SectionDataOffset;
int bytesUsed = 0;
for (int i = 0; i < lines.Length; i++)
{
string text = value[i].Trim(); // ReSharper disable once RedundantLogicalConditionalExpressionOperand
if (text.Length == 0 && SETEMPTYTEXT)
text = $"[~ {i}]";
byte[] DecryptedLineData = getLineData(text);
lineData[i] = cryptLineData(DecryptedLineData, key);
if (lineData[i].Length % 4 == 2)
Array.Resize(ref lineData[i], lineData[i].Length + 2);
key += KEY_ADVANCE;
lines[i] = new LineInfo { Offset = 4 + 8 * value.Length + bytesUsed, Length = DecryptedLineData.Length / 2 };
bytesUsed += lineData[i].Length;
}
// Apply Line Data
Array.Resize(ref Data, sdo + 4 + 8 * value.Length + bytesUsed);
LineOffsets = lines; // Handled by LineInfo[] set {}
lineData.SelectMany(i => i).ToArray().CopyTo(Data, Data.Length - bytesUsed);
TotalLength = SectionLength = (uint)(Data.Length - sdo);
LineCount = (ushort)value.Length;
}
}
public byte[] Data;
private static byte[] cryptLineData(byte[] data, ushort key)
{
byte[] result = new byte[data.Length];
for (int i = 0; i < result.Length; i += 2)
{
BitConverter.GetBytes((ushort)(BitConverter.ToUInt16(data, i) ^ key)).CopyTo(result, i);
key = (ushort)(key << 3 | key >> 13);
}
return result;
}
private static byte[] getLineData(string line)
{
if (line == null)
return new byte[2];
MemoryStream ms = new MemoryStream();
using (BinaryWriter bw = new BinaryWriter(ms))
{
int i = 0;
while (i < line.Length)
{
ushort val = line[i++];
if (val == 0x202F) val = 0xE07F; // nbsp
else if (val == 0x2026) val = 0xE08D; // …
else if (val == 0x2642) val = 0xE08E; // ♂
else if (val == 0x2640) val = 0xE08F; // ♀
if (val == '[') // Variable
{
// grab the string
int bracket = line.IndexOf("]", i, StringComparison.Ordinal);
if (bracket < 0)
throw new ArgumentException("Variable text is not capped properly.");
string varText = line.Substring(i, bracket - i);
var varValues = getVariableValues(varText);
foreach (ushort v in varValues) bw.Write(v);
i += 1 + varText.Length;
}
else if (val == '\\') // Escaped Formatting
{
var escapeValues = getEscapeValues(line[i++]);
foreach (ushort v in escapeValues) bw.Write(v);
}
else
bw.Write(val);
}
bw.Write(KEY_TERMINATOR); // cap the line off
return ms.ToArray();
}
}
private static string getLineString(byte[] data)
{
if (data == null)
return null;
string s = "";
int i = 0;
while (i < data.Length)
{
ushort val = BitConverter.ToUInt16(data, i);
if (val == KEY_TERMINATOR) break;
i += 2;
switch (val)
{
case KEY_TERMINATOR: return s;
case KEY_VARIABLE: s += getVariableString(data, ref i); break;
case '\n': s += @"\n"; break;
case '\\': s += @"\\"; break;
case '[': s += @"\["; break;
case 0xE07F: s += (char)0x202F; break; // nbsp
case 0xE08D: s += (char)0x2026; break; // …
case 0xE08E: s += (char)0x2642; break; // ♂
case 0xE08F: s += (char)0x2640; break; // ♀
default: s += (char)val; break;
}
}
return s; // Shouldn't get hit if the string is properly terminated.
}
private static string getVariableString(byte[] data, ref int i)
{
string s = "";
ushort count = BitConverter.ToUInt16(data, i); i += 2;
ushort variable = BitConverter.ToUInt16(data, i); i += 2;
switch (variable)
{
case KEY_TEXTRETURN: // "Waitbutton then scroll text; \r"
return "\\r";
case KEY_TEXTCLEAR: // "Waitbutton then clear text;; \c"
return "\\c";
case KEY_TEXTWAIT: // Dramatic pause for a text line. New!
ushort time = BitConverter.ToUInt16(data, i); i += 2;
return $"[WAIT {time}]";
case KEY_TEXTNULL: // Empty Text line? Includes linenum so maybe for betatest finding used-unused lines?
ushort line = BitConverter.ToUInt16(data, i); i += 2;
return $"[~ {line}]";
}
string varName = getVariableString(variable);
s += "[VAR" + " " + varName;
if (count > 1)
{
s += '(';
while (count > 1)
{
ushort arg = BitConverter.ToUInt16(data, i); i += 2;
s += arg.ToString("X4");
if (--count == 1) break;
s += ",";
}
s += ')';
}
s += "]";
return s;
}
private static IEnumerable<ushort> getEscapeValues(char esc)
{
var vals = new List<ushort>();
switch (esc)
{
case 'n': vals.Add('\n'); return vals;
case '\\': vals.Add('\\'); return vals;
case 'r': vals.AddRange(new ushort[] { KEY_VARIABLE, 1, KEY_TEXTRETURN }); return vals;
case 'c': vals.AddRange(new ushort[] { KEY_VARIABLE, 1, KEY_TEXTCLEAR }); return vals;
default: throw new Exception("Invalid terminated line: \"\\" + esc + "\"");
}
}
private static IEnumerable<ushort> getVariableValues(string variable)
{
string[] split = variable.Split(' ');
if (split.Length < 2)
throw new ArgumentException("Incorrectly formatted variable text!");
var vals = new List<ushort> { KEY_VARIABLE };
switch (split[0])
{
case "~": // Blank Text Line Variable (No text set - debug/quality testing variable?)
vals.Add(1);
vals.Add(KEY_TEXTNULL);
vals.Add(Convert.ToUInt16(split[1]));
break;
case "WAIT": // Event pause Variable.
vals.Add(1);
vals.Add(KEY_TEXTWAIT);
vals.Add(Convert.ToUInt16(split[1]));
break;
case "VAR": // Text Variable
vals.AddRange(getVariableParameters(split[1]));
break;
default: throw new Exception("Unknown variable method type!");
}
return vals;
}
private static IEnumerable<ushort> getVariableParameters(string text)
{
var vals = new List<ushort>();
int bracket = text.IndexOf('(');
bool noArgs = bracket < 0;
string variable = noArgs ? text : text.Substring(0, bracket);
ushort varVal = getVariableNumber(variable);
if (!noArgs)
{
string[] args = text.Substring(bracket + 1, text.Length - bracket - 2).Split(',');
vals.Add((ushort)(1 + args.Length));
vals.Add(varVal);
vals.AddRange(args.Select(t => Convert.ToUInt16(t, 16)));
}
else
{
vals.Add(1);
vals.Add(varVal);
}
return vals;
}
private static ushort getVariableNumber(string variable)
{
switch (variable)
{
case "COLOR": return 0xFF00;
case "TRNAME": return 0x0100;
case "PKNAME": return 0x0101;
case "PKNICK": return 0x0102;
case "TYPE": return 0x0103;
case "LOCATION": return 0x0105;
case "ABILITY": return 0x0106;
case "MOVE": return 0x0107;
case "ITEM1": return 0x0108;
case "ITEM2": return 0x0109;
case "sTRBAG": return 0x010A;
case "BOX": return 0x010B;
case "EVSTAT": return 0x010D;
case "OPOWER": return 0x0110;
case "RIBBON": return 0x0127;
case "MIINAME": return 0x0134;
case "WEATHER": return 0x013E;
case "TRNICK": return 0x0189;
case "1stchrTR": return 0x018A;
case "SHOUTOUT": return 0x018B;
case "BERRY": return 0x018E;
case "REMFEEL": return 0x018F;
case "REMQUAL": return 0x0190;
case "WEBSITE": return 0x0191;
case "CHOICECOS": return 0x019C;
case "GSYNCID": return 0x01A1;
case "PRVIDSAY": return 0x0192;
case "BTLTEST": return 0x0193;
case "GENLOC": return 0x0195;
case "CHOICEFOOD": return 0x0199;
case "HOTELITEM": return 0x019A;
case "TAXISTOP": return 0x019B;
case "MAISTITLE": return 0x019F;
case "ITEMPLUR0": return 0x1000;
case "ITEMPLUR1": return 0x1001;
case "GENDBR": return 0x1100;
case "NUMBRNCH": return 0x1101;
case "iCOLOR2": return 0x1302;
case "iCOLOR3": return 0x1303;
case "NUM1": return 0x0200;
case "NUM2": return 0x0201;
case "NUM3": return 0x0202;
case "NUM4": return 0x0203;
case "NUM5": return 0x0204;
case "NUM6": return 0x0205;
case "NUM7": return 0x0206;
case "NUM8": return 0x0207;
case "NUM9": return 0x0208;
default:
try { return Convert.ToUInt16(variable, 16); }
catch { throw new ArgumentException($"Variable \"{variable}\" parse error."); }
}
}
private static string getVariableString(ushort variable)
{
switch (variable)
{
case 0xFF00: return "COLOR"; // Change text line color (0 = white, 1 = red, 2 = blue...)
case 0x0100: return "TRNAME";
case 0x0101: return "PKNAME";
case 0x0102: return "PKNICK";
case 0x0103: return "TYPE";
case 0x0105: return "LOCATION";
case 0x0106: return "ABILITY";
case 0x0107: return "MOVE";
case 0x0108: return "ITEM1";
case 0x0109: return "ITEM2";
case 0x010A: return "sTRBAG";
case 0x010B: return "BOX";
case 0x010D: return "EVSTAT";
case 0x0110: return "OPOWER";
case 0x0127: return "RIBBON";
case 0x0134: return "MIINAME";
case 0x013E: return "WEATHER";
case 0x0189: return "TRNICK";
case 0x018A: return "1stchrTR";
case 0x018B: return "SHOUTOUT";
case 0x018E: return "BERRY";
case 0x018F: return "REMFEEL";
case 0x0190: return "REMQUAL";
case 0x0191: return "WEBSITE";
case 0x019C: return "CHOICECOS";
case 0x01A1: return "GSYNCID";
case 0x0192: return "PRVIDSAY";
case 0x0193: return "BTLTEST";
case 0x0195: return "GENLOC";
case 0x0199: return "CHOICEFOOD";
case 0x019A: return "HOTELITEM";
case 0x019B: return "TAXISTOP";
case 0x019F: return "MAISTITLE";
case 0x1000: return "ITEMPLUR0";
case 0x1001: return "ITEMPLUR1";
case 0x1100: return "GENDBR";
case 0x1101: return "NUMBRNCH";
case 0x1302: return "iCOLOR2";
case 0x1303: return "iCOLOR3";
case 0x0200: return "NUM1";
case 0x0201: return "NUM2";
case 0x0202: return "NUM3";
case 0x0203: return "NUM4";
case 0x0204: return "NUM5";
case 0x0205: return "NUM6";
case 0x0206: return "NUM7";
case 0x0207: return "NUM8";
case 0x0208: return "NUM9";
default: return variable.ToString("X4");
}
}
// Exposed Methods
internal static string[] getStrings(string path)
{
byte[] data = File.ReadAllBytes(path);
TextFile t;
try { t = new TextFile(data); } catch { return null; }
return t.Lines;
}
internal static byte[] getBytes(string[] lines)
{
return new TextFile { Lines = lines }.Data;
}
}
}

View File

@@ -242,6 +242,7 @@
<DependentUpon>Icon.cs</DependentUpon>
</Compile>
<Compile Include="Tools\Scripts.cs" />
<Compile Include="Tools\TextFile.cs" />
<Compile Include="Tools\ToolsUI.cs">
<SubType>Form</SubType>
</Compile>