diff --git a/pkNX.Randomization/Util.cs b/pkNX.Randomization/Util.cs index c405c445..bff16754 100644 --- a/pkNX.Randomization/Util.cs +++ b/pkNX.Randomization/Util.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace pkNX.Randomization { @@ -26,5 +27,38 @@ public static void Shuffle(IList array) array[i] = t; } } + + public static int ToInt32(string value) + { + string val = value?.Replace(" ", "").Replace("_", "").Trim(); + return string.IsNullOrWhiteSpace(val) ? 0 : int.Parse(val); + } + + public static uint ToUInt32(string value) + { + string val = value?.Replace(" ", "").Replace("_", "").Trim(); + return string.IsNullOrWhiteSpace(val) ? 0 : uint.Parse(val); + } + + public static uint GetHexValue(string s) + { + string str = GetOnlyHex(s); + return string.IsNullOrWhiteSpace(str) ? 0 : Convert.ToUInt32(str, 16); + } + + private static bool IsHex(char c) => (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); + private static string TitleCase(string word) => char.ToUpper(word[0]) + word.Substring(1, word.Length - 1).ToLower(); + + /// + /// Filters the string down to only valid hex characters, returning a new string. + /// + /// Input string to filter + public static string GetOnlyHex(string str) => string.IsNullOrWhiteSpace(str) ? string.Empty : string.Concat(str.Where(IsHex)); + + /// + /// Returns a new string with each word converted to its appropriate title case. + /// + /// Input string to modify + public static string ToTitleCase(string str) => string.IsNullOrWhiteSpace(str) ? string.Empty : string.Join(" ", str.Split(' ').Select(TitleCase)); } } diff --git a/pkNX.WinForms/MainEditor/EditorGG.cs b/pkNX.WinForms/MainEditor/EditorGG.cs index 3050c8ae..932d16bd 100644 --- a/pkNX.WinForms/MainEditor/EditorGG.cs +++ b/pkNX.WinForms/MainEditor/EditorGG.cs @@ -1,4 +1,7 @@ -using pkNX.Game; +using System.IO; +using pkNX.Containers; +using pkNX.Game; +using pkNX.Structures; namespace pkNX.WinForms.Controls { @@ -10,5 +13,25 @@ public void EditTrainers() { WinFormsUtil.Alert("Not implemented yet."); } + + public void EditCommon() + { + var text = ROM.GetFile(GameFile.GameText); + ((FolderContainer) text).Initialize(z => Path.GetExtension(z) == ".dat"); + var config = new TextConfig(ROM.Game); + var tc = new TextContainer(text, config); + var editor = new TextEditor(tc, TextEditor.TextEditorMode.Common); + editor.ShowDialog(); + } + + public void EditScript() + { + var text = ROM.GetFile(GameFile.StoryText); + ((FolderContainer)text).Initialize(z => Path.GetExtension(z) == ".dat"); + var config = new TextConfig(ROM.Game); + var tc = new TextContainer(text, config); + var editor = new TextEditor(tc, TextEditor.TextEditorMode.Script); + editor.ShowDialog(); + } } } \ No newline at end of file diff --git a/pkNX.WinForms/Subforms/TextContainer.cs b/pkNX.WinForms/Subforms/TextContainer.cs new file mode 100644 index 00000000..18efdf33 --- /dev/null +++ b/pkNX.WinForms/Subforms/TextContainer.cs @@ -0,0 +1,40 @@ +using System.IO; +using pkNX.Containers; +using pkNX.Structures; + +namespace pkNX.WinForms +{ + public class TextContainer + { + public readonly IFileContainer Container; + public readonly TextConfig Config; + public bool Remap { get; set; } + + private readonly string[][] Cache; + + public TextContainer(IFileContainer c, TextConfig t = null, bool remap = false) + { + Remap = remap; + Config = t; + Container = c; + Cache = new string[Container.Count][]; + } + + public int Length => Cache.Length; + + public string[] this[int index] + { + get => Cache[index] ?? (Cache[index] = GetLines(index)); + set => Cache[index] = value; + } + + private string[] GetLines(int index) => new TextFile(Container[index], Config, remapChars: Remap).Lines; + + public string GetFileName(int i) + { + if (Container is FolderContainer f) + return Path.GetFileNameWithoutExtension(f.GetFileName(i)); + return i.ToString(); + } + } +} \ No newline at end of file diff --git a/pkNX.WinForms/Subforms/TextEditor.cs b/pkNX.WinForms/Subforms/TextEditor.cs new file mode 100644 index 00000000..3e73500f --- /dev/null +++ b/pkNX.WinForms/Subforms/TextEditor.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using Util = pkNX.Randomization.Util; + +namespace pkNX.WinForms +{ + public partial class TextEditor : Form + { + public enum TextEditorMode + { + Common, + Script, + } + + private readonly TextContainer TextData; + + public TextEditor(TextContainer c, TextEditorMode mode) + { + InitializeComponent(); + TextData = c; + Mode = mode; + for (int i = 0; i < TextData.Length; i++) + CB_Entry.Items.Add(c.GetFileName(i)); + CB_Entry.SelectedIndex = 0; + dgv.EditMode = DataGridViewEditMode.EditOnEnter; + } + + private readonly TextEditorMode Mode; + private int entry = -1; + + // IO + private void B_Export_Click(object sender, EventArgs e) + { + if (TextData.Length <= 0) return; + SaveFileDialog Dump = new SaveFileDialog {Filter = "Text File|*.txt"}; + DialogResult sdr = Dump.ShowDialog(); + if (sdr != DialogResult.OK) return; + bool newline = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Remove newline formatting codes? (\\n,\\r,\\c)", "Removing newline formatting will make it more readable but will prevent any importing of that dump.") == DialogResult.Yes; + string path = Dump.FileName; + ExportTextFile(path, newline, TextData); + } + + private void B_Import_Click(object sender, EventArgs e) + { + if (TextData.Length <= 0) return; + OpenFileDialog Dump = new OpenFileDialog { Filter = "Text File|*.txt" }; + DialogResult odr = Dump.ShowDialog(); + if (odr != DialogResult.OK) return; + string path = Dump.FileName; + + if (!ImportTextFiles(path)) return; + + // Reload the form with the new data. + ChangeEntry(null, null); + WinFormsUtil.Alert("Imported Text from Input Path:", path); + } + + public static void ExportTextFile(string fileName, bool newline, TextContainer lineData) + { + using (MemoryStream ms = new MemoryStream()) + { + ms.Write(new byte[] {0xFF, 0xFE}, 0, 2); // Write Unicode BOM + using (TextWriter tw = new StreamWriter(ms, new UnicodeEncoding())) + { + for (int i = 0; i < lineData.Length; i++) + { + // Get Strings for the File + string[] data = lineData[i]; + string fn = lineData.GetFileName(i); + WriteTextFile(tw, fn, data, newline); + } + } + File.WriteAllBytes(fileName, ms.ToArray()); + } + } + + private static void WriteTextFile(TextWriter tw, string fn, string[] data, bool newline = false) + { + // Append the File Header + tw.WriteLine("~~~~~~~~~~~~~~~"); + tw.WriteLine("Text File : " + fn); + tw.WriteLine("~~~~~~~~~~~~~~~"); + // Write the String to the File + if (data == null) return; + foreach (string line in data) + { + tw.WriteLine(newline + ? line.Replace("\\n\\n", " ") + .Replace("\\n", " ") + .Replace("\\c", "") + .Replace("\\r", "") + .Replace("\\\\", "\\") + .Replace("\\[", "[") + : line); + } + } + + private bool ImportTextFiles(string fileName) + { + string[] fileText = File.ReadAllLines(fileName, Encoding.Unicode); + string[][] textLines = new string[TextData.Length][]; + int ctr = 0; + bool newlineFormatting = false; + // Loop through all files + 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) + { WinFormsUtil.Error($"Invalid Line @ {i}, expected Text File : {ctr}"); return false; } + int file = Util.ToInt32(brokenLine[1]); + if (file != ctr) + { WinFormsUtil.Error($"Invalid Line @ {i}, expected Text File : {ctr}"); return false; } + i += 2; // Skip over the other header line + List Lines = new List(); + while (i < fileText.Length && fileText[i] != "~~~~~~~~~~~~~~~") + { + Lines.Add(fileText[i]); + newlineFormatting |= fileText[i].Contains("\\n"); // Check if any line wasn't stripped of ingame formatting codes for human readability. + i++; + } + i--; + textLines[ctr++] = Lines.ToArray(); + } + + // Error Check + if (ctr != TextData.Length) + { + WinFormsUtil.Error("The amount of Text Files in the input file does not match the required for the text file.", + $"Received: {ctr}, Expected: {TextData.Length}"); return false; } + if (!newlineFormatting) + { + WinFormsUtil.Error("The input Text Files do not have the ingame newline formatting codes (\\n,\\r,\\c).", + "When exporting text, do not remove newline formatting."); return false; } + + // All Text Lines received. Store all back. + for (int i = 0; i < TextData.Length; i++) + { + try { TextData[i] = textLines[i]; } + catch (Exception e) { WinFormsUtil.Error($"The input Text File (# {i}) failed to convert:", e.ToString()); return false; } + } + + return true; + } + + private void ChangeEntry(object sender, EventArgs e) + { + // Save All the old text + if (entry > -1 && sender != null) + { + try + { + TextData[entry] = GetCurrentDGLines(); + } + catch (Exception ex) { WinFormsUtil.Error(ex.ToString()); } + } + + // Reset + entry = CB_Entry.SelectedIndex; + SetStringsDataGridView(TextData[entry]); + } + + // Main Handling + private void SetStringsDataGridView(string[] textArray) + { + // Clear the datagrid row content to remove all text lines. + dgv.Rows.Clear(); + // 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 + { + HeaderText = "Line", + DisplayIndex = 0, + Width = 32, + ReadOnly = true, + SortMode = DataGridViewColumnSortMode.NotSortable + }; + dgvLine.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight; + + DataGridViewTextBoxColumn dgvText = new DataGridViewTextBoxColumn + { + HeaderText = "Text", + DisplayIndex = 1, + SortMode = DataGridViewColumnSortMode.NotSortable, + AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill + }; + + dgv.Columns.Add(dgvLine); + dgv.Columns.Add(dgvText); + dgv.Rows.Add(textArray.Length); + + // Add the text lines into their cells. + for (int i = 0; i < textArray.Length; i++) + { + dgv.Rows[i].Cells[0].Value = i; + dgv.Rows[i].Cells[1].Value = textArray[i]; + } + } + + private string[] GetCurrentDGLines() + { + // Get Line Count + string[] lines = new string[dgv.RowCount]; + for (int i = 0; i < dgv.RowCount; i++) + lines[i] = (string)dgv.Rows[i].Cells[1].Value; + return lines; + } + // Meta Usage + private void B_AddLine_Click(object sender, EventArgs e) + { + int currentRow = 0; + try { currentRow = dgv.CurrentRow.Index; } + catch { dgv.Rows.Add(); } + if (dgv.Rows.Count != 1 && (currentRow < dgv.Rows.Count - 1 || currentRow == 0)) + { + if (ModifierKeys != Keys.Control && currentRow != 0) + { + if (WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Inserting in between rows will shift all subsequent lines.", "Continue?") != DialogResult.Yes) + return; + } + // Insert new Row after current row. + dgv.Rows.Insert(currentRow + 1); + } + + for (int i = 0; i < dgv.Rows.Count; i++) + dgv.Rows[i].Cells[0].Value = i.ToString(); + } + + private void B_RemoveLine_Click(object sender, EventArgs e) + { + int currentRow = dgv.CurrentRow.Index; + if (currentRow < dgv.Rows.Count - 1) + { + if (ModifierKeys != Keys.Control && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Deleting a row above other lines will shift all subsequent lines.", "Continue?")) + return; + } + dgv.Rows.RemoveAt(currentRow); + + // Resequence the Index Value column + for (int i = 0; i < dgv.Rows.Count; i++) + dgv.Rows[i].Cells[0].Value = i.ToString(); + } + + private void TextEditor_FormClosing(object sender, FormClosingEventArgs e) + { + // Save any pending edits + dgv.EndEdit(); + // Save All the old text + if (entry > -1) + TextData[entry] = GetCurrentDGLines(); + } + + private void B_Randomize_Click(object sender, EventArgs e) + { + // gametext can be horribly broken if randomized + if (Mode == TextEditorMode.Common && DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Randomizing Game Text is dangerous!", "Continue?")) + return; + + // get if the user wants to randomize current text file or all files + var dr = WinFormsUtil.Prompt(MessageBoxButtons.YesNoCancel, + $"Yes: Randomize ALL{Environment.NewLine}No: Randomize current textfile{Environment.NewLine}Cancel: Abort"); + + if (dr == DialogResult.Cancel) + return; + + // get if pure shuffle or smart shuffle (no shuffle if variable present) + var drs = WinFormsUtil.Prompt(MessageBoxButtons.YesNo, + $"Smart shuffle:{Environment.NewLine}Yes: Shuffle if no Variable present{Environment.NewLine}No: Pure random!"); + + if (drs == DialogResult.Cancel) + return; + + bool all = dr == DialogResult.Yes; + bool smart = drs == DialogResult.Yes; + + // save current + if (entry > -1) + TextData[entry] = GetCurrentDGLines(); + + // single-entire looping + int start = all ? 0 : entry; + int end = all ? TextData.Length - 1 : entry; + + // Gather strings + List strings = new List(); + for (int i = start; i <= end; i++) + { + string[] data = TextData[i]; + strings.AddRange(smart + ? data.Where(line => !line.Contains("[")) + : data); + } + + // Shuffle up + string[] pool = strings.ToArray(); + Util.Shuffle(pool); + + // Apply Text + int ctr = 0; + for (int i = start; i <= end; i++) + { + string[] data = TextData[i]; + + for (int j = 0; j < data.Length; j++) // apply lines + { + if (!smart || !data[j].Contains("[")) + data[j] = pool[ctr++]; + } + + TextData[i] = data; + } + + // Load current text file + SetStringsDataGridView(TextData[entry]); + + WinFormsUtil.Alert("Strings randomized!"); + } + } +} \ No newline at end of file diff --git a/pkNX.WinForms/Subforms/TextEditor.designer.cs b/pkNX.WinForms/Subforms/TextEditor.designer.cs new file mode 100644 index 00000000..e0343c69 --- /dev/null +++ b/pkNX.WinForms/Subforms/TextEditor.designer.cs @@ -0,0 +1,166 @@ +namespace pkNX.WinForms +{ + partial class TextEditor + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.CB_Entry = new System.Windows.Forms.ComboBox(); + this.dgv = new System.Windows.Forms.DataGridView(); + this.B_AddLine = new System.Windows.Forms.Button(); + this.B_RemoveLine = new System.Windows.Forms.Button(); + this.B_Export = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.B_Import = new System.Windows.Forms.Button(); + this.B_Randomize = new System.Windows.Forms.Button(); + ((System.ComponentModel.ISupportInitialize)(this.dgv)).BeginInit(); + this.SuspendLayout(); + // + // CB_Entry + // + this.CB_Entry.FormattingEnabled = true; + this.CB_Entry.Location = new System.Drawing.Point(68, 7); + this.CB_Entry.Name = "CB_Entry"; + this.CB_Entry.Size = new System.Drawing.Size(175, 21); + this.CB_Entry.TabIndex = 5; + this.CB_Entry.SelectedIndexChanged += new System.EventHandler(this.ChangeEntry); + // + // dgv + // + this.dgv.AllowUserToAddRows = false; + this.dgv.AllowUserToDeleteRows = false; + this.dgv.AllowUserToResizeRows = false; + this.dgv.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.dgv.BackgroundColor = System.Drawing.SystemColors.Control; + this.dgv.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; + this.dgv.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dgv.Location = new System.Drawing.Point(12, 33); + this.dgv.Name = "dgv"; + this.dgv.RowHeadersVisible = false; + this.dgv.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.CellSelect; + this.dgv.ShowEditingIcon = false; + this.dgv.Size = new System.Drawing.Size(744, 331); + this.dgv.TabIndex = 0; + // + // B_AddLine + // + this.B_AddLine.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.B_AddLine.Location = new System.Drawing.Point(570, 7); + this.B_AddLine.Name = "B_AddLine"; + this.B_AddLine.Size = new System.Drawing.Size(90, 23); + this.B_AddLine.TabIndex = 6; + this.B_AddLine.Text = "Add Line After"; + this.B_AddLine.UseVisualStyleBackColor = true; + this.B_AddLine.Click += new System.EventHandler(this.B_AddLine_Click); + // + // B_RemoveLine + // + this.B_RemoveLine.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.B_RemoveLine.Location = new System.Drawing.Point(666, 7); + this.B_RemoveLine.Name = "B_RemoveLine"; + this.B_RemoveLine.Size = new System.Drawing.Size(90, 23); + this.B_RemoveLine.TabIndex = 7; + this.B_RemoveLine.Text = "Remove Line"; + this.B_RemoveLine.UseVisualStyleBackColor = true; + this.B_RemoveLine.Click += new System.EventHandler(this.B_RemoveLine_Click); + // + // B_Export + // + this.B_Export.Location = new System.Drawing.Point(249, 6); + this.B_Export.Name = "B_Export"; + this.B_Export.Size = new System.Drawing.Size(90, 23); + this.B_Export.TabIndex = 8; + this.B_Export.Text = "Export All (.txt)"; + this.B_Export.UseVisualStyleBackColor = true; + this.B_Export.Click += new System.EventHandler(this.B_Export_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 10); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(50, 13); + this.label1.TabIndex = 9; + this.label1.Text = "Text File:"; + // + // B_Import + // + this.B_Import.Location = new System.Drawing.Point(345, 6); + this.B_Import.Name = "B_Import"; + this.B_Import.Size = new System.Drawing.Size(90, 23); + this.B_Import.TabIndex = 10; + this.B_Import.Text = "Import All (.txt)"; + this.B_Import.UseVisualStyleBackColor = true; + this.B_Import.Click += new System.EventHandler(this.B_Import_Click); + // + // B_Randomize + // + this.B_Randomize.Location = new System.Drawing.Point(441, 6); + this.B_Randomize.Name = "B_Randomize"; + this.B_Randomize.Size = new System.Drawing.Size(70, 23); + this.B_Randomize.TabIndex = 11; + this.B_Randomize.Text = "Randomize"; + this.B_Randomize.UseVisualStyleBackColor = true; + this.B_Randomize.Click += new System.EventHandler(this.B_Randomize_Click); + // + // TextEditor + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(768, 376); + this.Controls.Add(this.B_Randomize); + this.Controls.Add(this.B_Import); + this.Controls.Add(this.label1); + this.Controls.Add(this.B_Export); + this.Controls.Add(this.B_RemoveLine); + this.Controls.Add(this.B_AddLine); + this.Controls.Add(this.dgv); + this.Controls.Add(this.CB_Entry); + this.MinimumSize = new System.Drawing.Size(650, 300); + this.Name = "TextEditor"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Text Editor"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.TextEditor_FormClosing); + ((System.ComponentModel.ISupportInitialize)(this.dgv)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.ComboBox CB_Entry; + private System.Windows.Forms.DataGridView dgv; + private System.Windows.Forms.Button B_AddLine; + private System.Windows.Forms.Button B_RemoveLine; + private System.Windows.Forms.Button B_Export; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Button B_Import; + private System.Windows.Forms.Button B_Randomize; + } +} diff --git a/pkNX.WinForms/Subforms/TextEditor.resx b/pkNX.WinForms/Subforms/TextEditor.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/pkNX.WinForms/Subforms/TextEditor.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/pkNX.WinForms/pkNX.WinForms.csproj b/pkNX.WinForms/pkNX.WinForms.csproj index 26e80063..5490d242 100644 --- a/pkNX.WinForms/pkNX.WinForms.csproj +++ b/pkNX.WinForms/pkNX.WinForms.csproj @@ -56,6 +56,13 @@ + + + Form + + + TextEditor.cs + Main.cs @@ -70,6 +77,9 @@ Resources.resx True + + TextEditor.cs + SettingsSingleFileGenerator @@ -104,7 +114,6 @@ - \ No newline at end of file