mirror of
https://github.com/kwsch/pk3DS.git
synced 2026-10-02 13:57:14 -05:00
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:
@@ -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;
|
||||
|
||||
@@ -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
426
pk3DS/Tools/TextFile.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user