using ByamlExt.Byaml; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using Syroot.BinaryData; using EditorCore; using Switch_Toolbox.Library.Forms; using Switch_Toolbox.Library; using ByamlExt; using FirstPlugin.Forms; namespace FirstPlugin { //Editor from https://github.com/exelix11/EditorCore/blob/872d210f85ec0409f8a6ac3a12fc162aaf4cd90c/FileFormatPlugins/ByamlLib/Byaml/ByamlViewer.cs //Added as an editor form for saving data back via other plugins public partial class ByamlEditor : STForm, IFIleEditor { public IFileFormat FileFormat; public List GetFileFormats() { return new List() { FileFormat }; } public ByteOrder byteOrder; public dynamic byml; public string FileName = ""; bool pathSupport; ushort bymlVer; bool useMuunt = true; public ByamlEditor(System.Collections.IEnumerable by, bool _pathSupport, ushort _ver, ByteOrder defaultOrder = ByteOrder.LittleEndian, bool IsSaveDialog = false, BYAML byaml = null) { InitializeComponent(); CenterToScreen(); treeView1.BackColor = FormThemes.BaseTheme.FormBackColor; treeView1.ForeColor = FormThemes.BaseTheme.FormForeColor; if (!IsSaveDialog) { stButton1.Visible = false; stButton2.Visible = false; stPanel1.Dock = DockStyle.Fill; } if (byaml.FileName == "course_muunt_debug.byaml" && useMuunt) { pathSupport = true; stPanel1.Controls.Remove(splitContainer1); TurboMunntEditor editor = new TurboMunntEditor(); editor.Dock = DockStyle.Fill; editor.LoadCourseInfo(by, byaml.FilePath); stPanel1.Controls.Add(editor); return; } byteOrder = defaultOrder; FileName = byaml.FileName; byml = by; pathSupport = _pathSupport; bymlVer = _ver; if (byml == null) return; ParseBymlFirstNode(); } void ParseBymlFirstNode() { TreeNode root = new TreeNode(FileName); root.Tag = byml; treeView1.Nodes.Add(root); treeView1.SelectedNode = root; //the first node should always be a dictionary node if (byml is Dictionary) { parseDictNode(byml, root.Nodes); } else if (byml is List) { if (((List)byml).Count == 0) { MessageBox.Show("This byml is empty"); } parseArrayNode(byml, root.Nodes); } else if (byml is List) { MessageBox.Show("Unsupported root node"); } else throw new Exception($"Unsupported root node type {byml.GetType()}"); } Stream saveStream = null; public ByamlEditor(System.Collections.IEnumerable by, bool _pathSupport, Stream saveTo, ushort _ver, ByteOrder defaultOrder = ByteOrder.LittleEndian, bool IsSaveDialog = false, BYAML byaml = null) : this(by, _pathSupport, _ver, defaultOrder, IsSaveDialog, byaml) { treeView1.BackColor = FormThemes.BaseTheme.FormBackColor; treeView1.ForeColor = FormThemes.BaseTheme.FormForeColor; if (!IsSaveDialog) { stButton1.Visible = false; stButton2.Visible = false; stPanel1.Dock = DockStyle.Fill; } saveStream = saveTo; saveToolStripMenuItem.Visible = saveTo != null && saveStream.CanWrite; } //get a reference to the value to change class EditableNode { dynamic Node; public Type type { get { return Node.GetType(); } } public dynamic Get() { return Node; } public void Set(dynamic value) { Node = value; } public string GetTreeViewString() { return Node.ToString(); } public EditableNode(dynamic _node) { Node = _node; } } void parseDictNode(IDictionary node) { foreach (string k in node.Keys) { if ((node[k] is Dictionary) || (node[k] is List) || (node[k] is List)) { continue; } string ValueText = (node[k] == null ? "" : node[k].ToString()); string NameText = k; string TypeText = ""; if (node[k] == null) TypeText = "NULL"; else TypeText = node[k].GetType().ToString(); string TypeString = TypeText.Replace("System.", ""); ListViewItem item = new ListViewItem(NameText); item.SubItems.Add(TypeString); item.SubItems.Add(ValueText); if (node[k] != null) item.Tag = new EditableNode(node); listViewCustom1.Items.Add(item); } } void parseArrayNode(IList list) { foreach (dynamic k in list) { Console.WriteLine("array item " + k.ToString()); if ((k is Dictionary) || (k is List) || (k is List)) { continue; } string ValueText = (k == null ? "" : k.ToString()); string ValueTypeString = ""; if (k == null) ValueTypeString = "NULL"; else { Type ValueType = k.GetType(); ValueTypeString = ValueType.ToString(); } ListViewItem item = new ListViewItem(ValueText); item.SubItems.Add(ValueTypeString); item.SubItems.Add(ValueText); if (k != null) item.Tag = new EditableNode(k); listViewCustom1.Items.Add(item); } } void parseDictNode(IDictionary node, TreeNodeCollection addto) { int dictionaryIndex = 0; int arrayIndex = 0; int pathPointIndex = 0; foreach (string k in node.Keys) { if (node[k] is IDictionary || node[k] is IList || node[k] is IList) { TreeNode current = addto.Add(k); if (node[k] is IDictionary) { current.Text += $" : {dictionaryIndex++}"; current.Tag = node[k]; if (HasDynamicListChildren(current)) current.Nodes.Add("✯✯dummy✯✯"); //a text that can't be in a byml } else if (node[k] is IList) { current.Text += $" : {arrayIndex++}"; current.Tag = ((IList)node[k]); if (HasDynamicListChildren(current)) current.Nodes.Add("✯✯dummy✯✯"); } else if (node[k] is IList) { current.Text += $" : {pathPointIndex++}"; current.Tag = ((IList)node[k]); parsePathPointArray(node[k], current.Nodes); } } } } void parsePathPointArray(IList list, TreeNodeCollection addto) { int index = 0; foreach (var k in list) { index++; var n = addto.Add(k == null ? "" : k.ToString()); if (k != null) n.Tag = new EditableNode(list); } } void parseArrayNode(IList list, TreeNodeCollection addto) { int dictionaryIndex = 0; int arrayIndex = 0; int pathPointIndex = 0; int index = 0; foreach (dynamic k in list) { if (k is IDictionary || k is IList || k is IList) { if (k is IDictionary) { TreeNode current = addto.Add($" {dictionaryIndex++}"); current.Tag = ((IDictionary)k); if (HasDynamicListChildren(current)) current.Nodes.Add("✯✯dummy✯✯"); } else if (k is IList) { TreeNode current = addto.Add($" {arrayIndex++}"); current.Tag = ((IList)k); } else if (k is IList) { TreeNode current = addto.Add($" {pathPointIndex++}"); current.Tag = ((IList)k); parsePathPointArray(k, current.Nodes); } } index++; } } //Search through the properties of a dictionary or list and see if it contains a list/dictionary //Then use this information to add tree nodes. //This is so nodes can be added on click but visually have children private bool HasDynamicListChildren(TreeNode Node) { if (Node.Tag != null) { if (((dynamic)Node.Tag).Count > 0) { if (Node.Tag is IList) return ListHasListChild((IList)Node.Tag); if (Node.Tag is IDictionary) return DictionaryHasListChild((IDictionary)Node.Tag); } } return false; } private bool ListHasListChild(IList list) { foreach (dynamic k in list) { if (k is IDictionary) return true; else if (k is IList) return true; } return false; } private bool DictionaryHasListChild(IDictionary node) { foreach (string k in node.Keys) { if (node[k] is IDictionary) return true; else if (node[k] is IList) return true; } return false; } private void BeforeExpand(object sender, TreeViewCancelEventArgs e) { if (e.Node.Tag != null && e.Node.Nodes.Count == 1 && e.Node.Nodes[0].Text == "✯✯dummy✯✯") { e.Node.Nodes.Clear(); if (((dynamic)e.Node.Tag).Count == 0) { e.Node.Nodes.Add(""); return; } if (e.Node.Tag is IList) parseArrayNode((IList)e.Node.Tag, e.Node.Nodes); else if (e.Node.Tag is IDictionary) parseDictNode((IDictionary)e.Node.Tag, e.Node.Nodes); else throw new Exception("WTF"); } } private void ContextMenuOpening(object sender, CancelEventArgs e) { CopyNode.Enabled = treeView1.SelectedNode != null; editValueNodeMenuItem.Enabled = listViewCustom1.SelectedItems.Count > 0 && listViewCustom1.SelectedItems[0].Tag is EditableNode; } private void CopyNode_Click(object sender, EventArgs e) { Clipboard.SetText(treeView1.SelectedNode.Text); } private void ByamlViewer_Load(object sender, EventArgs e) { } private void exportJsonToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog sav = new SaveFileDialog() { Filter = "Xml file | *.xml" }; if (sav.ShowDialog() != DialogResult.OK) return; File.WriteAllText(sav.FileName, XmlConverter.ToXml(new BymlFileData { Version = bymlVer, byteOrder = byteOrder, SupportPaths = pathSupport, RootNode = byml })); } public static void ImportFromJson() { } public static void OpenByml() { OpenFileDialog opn = new OpenFileDialog(); opn.Filter = "byml file | *.byml"; if (opn.ShowDialog() == DialogResult.OK) { OpenByml(opn.FileName, new BYAML()); } } static bool SupportPaths() { return MessageBox.Show("Does this game support paths ?", "", MessageBoxButtons.YesNo) == DialogResult.Yes; } public static void OpenByml(string Filename, BYAML byaml) { OpenByml(new FileStream(Filename, FileMode.Open), byaml, Filename); } public static void OpenByml(Stream file, BYAML byaml, string FileName = "") { OpenByml(file, byaml, FileName, SupportPaths()); } public static void OpenByml(Stream file, BYAML byaml, string FileName, bool paths) { OpenByml(file, byaml, FileName, paths, null, false); } public static void OpenByml(Stream file, BYAML byaml, string FileName, bool? paths, Stream saveStream, bool AsDialog) { bool _paths = paths == null ? SupportPaths() : paths.Value; var byml = ByamlFile.LoadN(file, _paths); OpenByml(byml, byaml, saveStream, AsDialog); } public static void OpenByml(BymlFileData data, BYAML byaml, Stream saveStream = null, bool AsDialog = false) { var form = new ByamlEditor(data.RootNode, data.SupportPaths, saveStream, data.Version, data.byteOrder, AsDialog, byaml); if (saveStream != null && saveStream.CanWrite) { saveStream.Position = 0; saveStream.SetLength(0); } } private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) { SaveFileDialog sav = new SaveFileDialog() { FileName = FileName, Filter = "byml file | *.byml" }; if (sav.ShowDialog() == DialogResult.OK) { ByamlFile.SaveN(sav.FileName, new BymlFileData { Version = bymlVer, byteOrder = byteOrder, SupportPaths = pathSupport, RootNode = byml }); } } private void editValueNodeMenuItem_Click(object sender, EventArgs e) { if (listViewCustom1.SelectedItems.Count <= 0) return; var node = listViewCustom1.SelectedItems[0].Tag as EditableNode; if (node == null) return; if (node.Get() is ByamlPathPoint) { new BymlPathPointEditor(node.Get()).ShowDialog(); //ByamlPathPoint is a reference type } else { string value = node.Get().ToString(); var dRes = InputDialog.Show("Enter value", $"Enter new value for the node, the value must be of type {node.type}", ref value); if (dRes != DialogResult.OK) return; if (value.Trim() == "") return; node.Set(ByamlTypeHelper.ConvertValue(node.type, value)); } ResetValues(); } private void ResetValues() { if (treeView1.SelectedNode == null) return; listViewCustom1.Items.Clear(); var targetNodeCollection = treeView1.SelectedNode.Nodes; dynamic target = treeView1.SelectedNode.Tag; if (target is IDictionary) { parseDictNode((IDictionary)target); } else if (target is IList) { parseArrayNode((IList)target); } } private void treeView1_AfterSelect(object sender, TreeViewEventArgs e) { ResetValues(); } private void addNodeToolStripMenuItem_Click(object sender, EventArgs e) { dynamic target = treeView1.SelectedNode == null ? byml : treeView1.SelectedNode.Tag; var targetNodeCollection = treeView1.SelectedNode == null ? treeView1.Nodes : treeView1.SelectedNode.Nodes; if (target is EditableNode) { if (treeView1.SelectedNode.Parent == null) { target = byml; targetNodeCollection = treeView1.Nodes; } else { target = treeView1.SelectedNode.Parent.Tag; targetNodeCollection = treeView1.SelectedNode.Parent.Nodes; } } var newProp = AddBymlPropertyDialog.newProperty(!(target is IList)); if (newProp == null) return; bool clone = newProp.Item2 is IDictionary || newProp.Item2 is IList; //reference types must be manually cloned var toAdd = clone ? DeepCloneDictArr.DeepClone(newProp.Item2) : newProp.Item2; targetNodeCollection.Clear(); if (target is IList) { ((IList)target).Insert(((IList)target).Count, toAdd); parseArrayNode((IList)target, targetNodeCollection); } else if (target is IDictionary) { ((IDictionary)target).Add(newProp.Item1, toAdd); parseDictNode((IDictionary)target, targetNodeCollection); } else throw new Exception(); ResetValues(); } private void deleteToolStripMenuItem_Click(object sender, EventArgs e) { if (listViewCustom1.SelectedItems.Count <= 0) return; dynamic target = listViewCustom1.SelectedItems[0].Tag; int index = listViewCustom1.Items.IndexOf(listViewCustom1.SelectedItems[0]); listViewCustom1.Items.RemoveAt(index); } private void renameToolStripMenuItem_Click(object sender, EventArgs e) { } private void deleteNodeToolStripMenuItem_Click(object sender, EventArgs e) { if (treeView1.SelectedNode == null) { MessageBox.Show("Select a node first"); return; } dynamic target; TreeNodeCollection targetNode; if (treeView1.SelectedNode.Parent == null) { target = byml; targetNode = treeView1.Nodes; } else { target = treeView1.SelectedNode.Parent.Tag; targetNode = treeView1.SelectedNode.Parent.Nodes; } int index = targetNode.IndexOf(treeView1.SelectedNode); if (target is Dictionary) { target.Remove(((Dictionary)target).Keys.ToArray()[index]); } else target.RemoveAt(targetNode.IndexOf(treeView1.SelectedNode)); targetNode.RemoveAt(index); } private void saveToolStripMenuItem_Click(object sender, EventArgs e) { saveStream.Position = 0; saveStream.SetLength(0); ByamlFile.SaveN(saveStream, new BymlFileData { Version = bymlVer, byteOrder = byteOrder, SupportPaths = pathSupport, RootNode = byml }); } private void importFromXmlToolStripMenuItem_Click(object sender, EventArgs e) { OpenFileDialog openFile = new OpenFileDialog(); openFile.Filter = "xml file |*.xml| every file | *.*"; if (openFile.ShowDialog() != DialogResult.OK) return; StreamReader t = new StreamReader(new FileStream(openFile.FileName, FileMode.Open), UnicodeEncoding.Unicode); treeView1.Nodes.Clear(); byml = XmlConverter.ToByml(t.ReadToEnd()).RootNode; ParseBymlFirstNode(); } private void contentContainer_Paint(object sender, PaintEventArgs e) { } private void listViewCustom1_MouseClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) { Point pt = listViewCustom1.PointToScreen(e.Location); stContextMenuStrip1.Show(pt); } } private void stButton2_Click(object sender, EventArgs e) { DialogResult = DialogResult.OK; Close(); } private void stButton1_Click(object sender, EventArgs e) { DialogResult = DialogResult.Cancel; Close(); } } }