├── .gitattributes ├── .gitignore ├── .gitmodules ├── NbtStudio.sln ├── NbtStudio ├── App.config ├── Constants.cs ├── IconSource.cs ├── Models │ ├── ByteProviders.cs │ ├── ExportSettings.cs │ ├── NbtTreeModel.cs │ ├── Nodes │ │ ├── ChunkNode.cs │ │ ├── FolderNode.cs │ │ ├── INode.cs │ │ ├── ModelNode.cs │ │ ├── ModelRootNode.cs │ │ ├── NbtFileNode.cs │ │ ├── NbtTagNode.cs │ │ ├── NodeOperations.cs │ │ └── RegionFileNode.cs │ └── UndoHistory.cs ├── NbtObjects │ ├── Chunk.cs │ ├── Interfaces.cs │ ├── NbtFile.cs │ ├── NbtFolder.cs │ └── RegionFile.cs ├── NbtStudio.csproj ├── NbtUtil.cs ├── Program.cs ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── Resources │ ├── amber │ │ ├── action_add_snbt.png │ │ ├── action_copy.png │ │ ├── action_cut.png │ │ ├── action_delete.png │ │ ├── action_edit.png │ │ ├── action_edit_snbt.png │ │ ├── action_new_file.png │ │ ├── action_open_file.png │ │ ├── action_open_folder.png │ │ ├── action_paste.png │ │ ├── action_redo.png │ │ ├── action_refresh.png │ │ ├── action_rename.png │ │ ├── action_save.png │ │ ├── action_save_all.png │ │ ├── action_search.png │ │ ├── action_sort.png │ │ ├── action_undo.png │ │ ├── file_chunk.png │ │ ├── file_file.png │ │ ├── file_folder.png │ │ ├── file_region.png │ │ ├── tag_byte.png │ │ ├── tag_byte_array.png │ │ ├── tag_compound.png │ │ ├── tag_double.png │ │ ├── tag_float.png │ │ ├── tag_int.png │ │ ├── tag_int_array.png │ │ ├── tag_list.png │ │ ├── tag_long.png │ │ ├── tag_long_array.png │ │ ├── tag_short.png │ │ └── tag_string.png │ ├── nbt_studio_icon_16.ico │ ├── nbt_studio_icon_16.png │ ├── nbt_studio_icon_256.ico │ ├── nbt_studio_icon_256.png │ ├── tryashtar │ │ ├── action_add_snbt.png │ │ ├── action_edit_snbt.png │ │ ├── file_chunk.png │ │ └── file_region.png │ ├── wiki │ │ ├── tag_byte.png │ │ ├── tag_byte_array.png │ │ ├── tag_compound.png │ │ ├── tag_double.png │ │ ├── tag_float.png │ │ ├── tag_int.png │ │ ├── tag_int_array.png │ │ ├── tag_list.png │ │ ├── tag_long.png │ │ ├── tag_long_array.png │ │ ├── tag_short.png │ │ └── tag_string.png │ └── yusuke │ │ ├── action_copy.png │ │ ├── action_cut.png │ │ ├── action_delete.png │ │ ├── action_edit.png │ │ ├── action_new_file.png │ │ ├── action_open_file.png │ │ ├── action_open_folder.png │ │ ├── action_paste.png │ │ ├── action_redo.png │ │ ├── action_refresh.png │ │ ├── action_rename.png │ │ ├── action_save.png │ │ ├── action_save_all.png │ │ ├── action_search.png │ │ ├── action_sort.png │ │ ├── action_undo.png │ │ ├── file_chunk.png │ │ ├── file_file.png │ │ ├── file_folder.png │ │ ├── file_region.png │ │ ├── folder.png │ │ ├── tag_byte.png │ │ ├── tag_byte_array.png │ │ ├── tag_compound.png │ │ ├── tag_double.png │ │ ├── tag_float.png │ │ ├── tag_int.png │ │ ├── tag_int_array.png │ │ ├── tag_list.png │ │ ├── tag_long.png │ │ ├── tag_long_array.png │ │ ├── tag_short.png │ │ └── tag_string.png ├── UI │ ├── Controls │ │ ├── ActionHistory.Designer.cs │ │ ├── ActionHistory.cs │ │ ├── ActionHistory.resx │ │ ├── ChunkCoordEditControl.cs │ │ ├── ColumnConsistinator.cs │ │ ├── ConvenienceControls.cs │ │ ├── DualMenuItem.cs │ │ ├── IconSourceButtons.Designer.cs │ │ ├── IconSourceButtons.cs │ │ ├── IconSourceButtons.resx │ │ ├── IconSourcePreview.Designer.cs │ │ ├── IconSourcePreview.cs │ │ ├── IconSourcePreview.resx │ │ ├── NbtNodeControls.cs │ │ ├── NbtTreeView.cs │ │ └── TextBoxes │ │ │ ├── FileNameTextBox.cs │ │ │ ├── RegexTextBox.cs │ │ │ ├── TagNameTextBox.cs │ │ │ ├── TagSnbtTextBox.cs │ │ │ └── TagValueTextBox.cs │ ├── MainForm.Designer.cs │ ├── MainForm.cs │ ├── MainForm.resx │ └── Windows │ │ ├── AboutWindow.Designer.cs │ │ ├── AboutWindow.cs │ │ ├── AboutWindow.resx │ │ ├── BulkEditWindow.Designer.cs │ │ ├── BulkEditWindow.cs │ │ ├── BulkEditWindow.resx │ │ ├── EditChunkWindow.Designer.cs │ │ ├── EditChunkWindow.cs │ │ ├── EditChunkWindow.resx │ │ ├── EditHexWindow.Designer.cs │ │ ├── EditHexWindow.cs │ │ ├── EditHexWindow.resx │ │ ├── EditSnbtWindow.Designer.cs │ │ ├── EditSnbtWindow.cs │ │ ├── EditSnbtWindow.resx │ │ ├── EditTagWindow.Designer.cs │ │ ├── EditTagWindow.cs │ │ ├── EditTagWindow.resx │ │ ├── ExceptionWindow.Designer.cs │ │ ├── ExceptionWindow.cs │ │ ├── ExceptionWindow.resx │ │ ├── ExportWindow.Designer.cs │ │ ├── ExportWindow.cs │ │ ├── ExportWindow.resx │ │ ├── FindWindow.Designer.cs │ │ ├── FindWindow.cs │ │ ├── FindWindow.resx │ │ ├── FullIconPreviewWindow.Designer.cs │ │ ├── FullIconPreviewWindow.cs │ │ ├── FullIconPreviewWindow.resx │ │ ├── IconSetWindow.Designer.cs │ │ ├── IconSetWindow.cs │ │ ├── IconSetWindow.resx │ │ ├── RenameFileWindow.Designer.cs │ │ ├── RenameFileWindow.cs │ │ ├── RenameFileWindow.resx │ │ ├── UpdateWindow.Designer.cs │ │ ├── UpdateWindow.cs │ │ └── UpdateWindow.resx ├── Updater.cs ├── nbt_studio_icon_256.ico └── packages.config ├── NbtStudioTests ├── DragDrop.cs ├── ModelNodes.cs ├── NbtStudioTests.csproj ├── SetEqualTo.cs ├── Snbt.cs ├── Sorting.cs ├── UndoRedo.cs └── packages.config └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "TreeViewAdv"] 2 | url = https://github.com/tryashtar/TreeViewAdv 3 | path = TreeViewAdv 4 | [submodule "HexBox"] 5 | path = HexBox 6 | url = https://github.com/tryashtar/HexBox 7 | [submodule "fNbt"] 8 | path = fNbt 9 | url = https://github.com/tryashtar/fNbt 10 | [submodule "utils.utility"] 11 | path = utils.utility 12 | url = https://github.com/tryashtar/utils.utility 13 | [submodule "utils.forms"] 14 | path = utils.forms 15 | url = https://github.com/tryashtar/utils.forms 16 | [submodule "utils.nbt"] 17 | path = utils.nbt 18 | url = https://github.com/tryashtar/utils.nbt 19 | -------------------------------------------------------------------------------- /NbtStudio/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | False 14 | 15 | 16 | 9 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | False 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /NbtStudio/Constants.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.Specialized; 5 | using System.Drawing; 6 | 7 | namespace NbtStudio 8 | { 9 | public static class Constants 10 | { 11 | public static readonly Color SelectionColor = Color.FromArgb(181, 215, 243); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /NbtStudio/Models/ExportSettings.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TryashtarUtils.Nbt; 9 | using TryashtarUtils.Utility; 10 | 11 | namespace NbtStudio 12 | { 13 | // settings an NBT file can be saved with, and the implementation for saving as such 14 | public class ExportSettings 15 | { 16 | public readonly bool Snbt; 17 | public readonly bool Minified; 18 | public readonly bool Json; 19 | public readonly NbtCompression Compression; 20 | public readonly bool BigEndian; 21 | public readonly bool BedrockHeader; 22 | 23 | // keep this private and only expose the static constructors below so callers don't have to include irrelevant information 24 | private ExportSettings(bool snbt, bool minified, bool json, NbtCompression compression, bool big_endian, bool bedrock_header) 25 | { 26 | Snbt = snbt; 27 | Minified = minified; 28 | Json = json; 29 | Compression = compression; 30 | BigEndian = big_endian; 31 | BedrockHeader = bedrock_header; 32 | } 33 | 34 | public static ExportSettings AsSnbt(bool minified, bool json) 35 | { 36 | return new ExportSettings(true, minified, json, NbtCompression.None, false, false); 37 | } 38 | 39 | public static ExportSettings AsNbt(NbtCompression compression, bool big_endian, bool bedrock_header) 40 | { 41 | return new ExportSettings(false, false, false, compression, big_endian, bedrock_header); 42 | } 43 | 44 | private SnbtOptions CreateOptions() 45 | { 46 | var options = Json ? SnbtOptions.JsonLike : SnbtOptions.Default; 47 | if (!Minified) 48 | options = options.Expanded(); 49 | return options; 50 | } 51 | 52 | public void Export(string path, NbtTag root) 53 | { 54 | if (Snbt) 55 | File.WriteAllText(path, root.ToSnbt(CreateOptions())); 56 | else 57 | { 58 | var file = new fNbt.NbtFile(); 59 | file.BigEndian = BigEndian; 60 | file.RootTag = root; 61 | using (var writer = File.Create(path)) 62 | { 63 | if (BedrockHeader) 64 | writer.Seek(8, SeekOrigin.Begin); 65 | long size = file.SaveToStream(writer, Compression); 66 | if (BedrockHeader) 67 | { 68 | // bedrock level.dat files start with a header containing a magic number and then the little-endian size of the data 69 | writer.Seek(0, SeekOrigin.Begin); 70 | writer.Write(new byte[] { 8, 0, 0, 0 }, 0, 4); 71 | writer.Write(DataUtils.GetBytes((int)size, little_endian: !BigEndian), 0, 4); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /NbtStudio/Models/NbtTreeModel.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using NbtStudio.UI; 11 | using System.Collections.ObjectModel; 12 | 13 | namespace NbtStudio 14 | { 15 | // the model version of the tree of nodes 16 | // this is mostly necessary because TreeViewAdv requires it, but it has some extra stuff as well 17 | public partial class NbtTreeModel : ITreeModel 18 | { 19 | public event EventHandler NodesChanged; 20 | public event EventHandler NodesInserted; 21 | public event EventHandler NodesRemoved; 22 | public event EventHandler StructureChanged; 23 | public event EventHandler Changed; 24 | 25 | public readonly ModelRootNode Root; 26 | public readonly UndoHistory UndoHistory; 27 | 28 | public NbtTreeModel(IEnumerable roots) 29 | { 30 | UndoHistory = new UndoHistory(GetDescription); 31 | UndoHistory.Changed += UndoHistory_Changed; 32 | Root = new ModelRootNode(this, roots); 33 | } 34 | public NbtTreeModel(object root) : this(new[] { root }) { } 35 | public NbtTreeModel() : this(Enumerable.Empty()) { } 36 | 37 | public void Import(object obj) 38 | { 39 | Root.Add(obj); 40 | } 41 | 42 | public void ImportMany(IEnumerable objects) 43 | { 44 | Root.AddRange(objects); 45 | } 46 | 47 | public void RemoveMany(IEnumerable nodes) 48 | { 49 | Root.RemoveNodes(nodes); 50 | } 51 | 52 | private void UndoHistory_Changed(object sender, EventArgs e) 53 | { 54 | Changed?.Invoke(this, EventArgs.Empty); 55 | } 56 | 57 | public static string GetDescription(object obj) 58 | { 59 | if (obj is INode node) 60 | return node.Description; 61 | if (obj is IEnumerable nodes) 62 | return ExtractNodeOperations.Description(nodes); 63 | if (obj is NbtTag tag) 64 | return NbtUtil.TagDescription(tag); 65 | if (obj is IEnumerable tags) 66 | return NbtUtil.TagDescription(tags); 67 | return obj.ToString(); 68 | } 69 | 70 | public bool HasAnyUnsavedChanges => OpenedFiles.Any(x => x.HasUnsavedChanges); 71 | public IEnumerable OpenedFiles 72 | { 73 | get 74 | { 75 | foreach (var item in BreadthFirstSearch(x => (x is FolderNode folder && folder.Folder.HasScanned) || x.Get() is not null)) 76 | { 77 | var file = item.Get(); 78 | if (file is not null) 79 | yield return file; 80 | } 81 | } 82 | } 83 | 84 | public (INode destination, int index) GetInsertionLocation(INode target, NodePosition position) 85 | { 86 | if (position == NodePosition.Inside) 87 | return (target, target.Children.Count()); 88 | else 89 | { 90 | var parent = target.Parent; 91 | var siblings = parent.Children.ToList(); 92 | int index = siblings.IndexOf(target); 93 | if (position == NodePosition.After) 94 | index++; 95 | return (parent, index); 96 | } 97 | } 98 | 99 | private IEnumerable BreadthFirstSearch(Predicate predicate) 100 | { 101 | var queue = new Queue(); 102 | foreach (var item in Root.Children) 103 | { 104 | // don't just enqueue Root directly because the predicate might not match it 105 | queue.Enqueue(item); 106 | } 107 | while (queue.Any()) 108 | { 109 | var item = queue.Dequeue(); 110 | if (!predicate(item)) 111 | continue; 112 | yield return item; 113 | foreach (var sub in item.Children) 114 | { 115 | queue.Enqueue(sub); 116 | } 117 | } 118 | } 119 | 120 | public void NotifyNodesAdded(TreePath path, INode[] nodes, int[] indices) 121 | { 122 | NodesInserted?.Invoke(this, new TreeModelEventArgs(path, indices, nodes)); 123 | Changed?.Invoke(this, EventArgs.Empty); 124 | } 125 | 126 | public void NotifyNodesRemoved(TreePath path, INode[] nodes, int[] indices) 127 | { 128 | NodesRemoved?.Invoke(this, new TreeModelEventArgs(path, indices, nodes)); 129 | Changed?.Invoke(this, EventArgs.Empty); 130 | } 131 | 132 | public void NotifyNodeChanged(INode node) 133 | { 134 | var path = node.Parent?.Path ?? new TreePath(); 135 | NodesChanged?.Invoke(this, new TreeModelEventArgs(path, new object[] { node })); 136 | Changed?.Invoke(this, EventArgs.Empty); 137 | } 138 | 139 | public void NotifyNodesReordered(TreePath path) 140 | { 141 | StructureChanged?.Invoke(this, new TreePathEventArgs(path)); 142 | } 143 | 144 | IEnumerable ITreeModel.GetChildren(TreePath treePath) => GetChildren(treePath); 145 | public IEnumerable GetChildren(TreePath treePath) 146 | { 147 | if (treePath.IsEmpty()) 148 | return Root.Children; 149 | else 150 | return ((INode)treePath.LastNode).Children; 151 | } 152 | 153 | public bool IsLeaf(TreePath treePath) 154 | { 155 | return !((INode)treePath.LastNode).HasChildren; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/ChunkNode.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.Specialized; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace NbtStudio 12 | { 13 | public class ChunkNode : ModelNode 14 | { 15 | public readonly Chunk Chunk; 16 | private bool HasSetupEvents = false; 17 | public ChunkNode(NbtTreeModel tree, INode parent, Chunk chunk) : base(tree, parent) 18 | { 19 | Chunk = chunk; 20 | if (Chunk.IsLoaded) 21 | SetupEvents(); 22 | else 23 | Chunk.OnLoaded += Chunk_OnLoaded; 24 | } 25 | 26 | public NbtCompound AccessChunkData() 27 | { 28 | if (!Chunk.IsLoaded) 29 | Chunk.Load(); 30 | return Chunk.Data; 31 | } 32 | 33 | public override bool HasChildren 34 | { 35 | get 36 | { 37 | if (Chunk.IsExternal) 38 | return false; 39 | if (!Chunk.IsLoaded) 40 | return true; 41 | return base.HasChildren; 42 | } 43 | } 44 | 45 | private void Chunk_OnLoaded(object sender, EventArgs e) 46 | { 47 | SetupEvents(); 48 | } 49 | 50 | private void SetupEvents() 51 | { 52 | if (!HasSetupEvents) 53 | { 54 | Chunk.Data.OnChanged += Data_Changed; 55 | Chunk.Data.ActionPerformed += Data_ActionPerformed; 56 | HasSetupEvents = true; 57 | } 58 | } 59 | 60 | protected override void SelfDispose() 61 | { 62 | if (HasSetupEvents) 63 | { 64 | Chunk.Data.OnChanged -= Data_Changed; 65 | Chunk.Data.ActionPerformed -= Data_ActionPerformed; 66 | } 67 | } 68 | 69 | private void Data_ActionPerformed(UndoableAction action) 70 | { 71 | NoticeAction(action); 72 | } 73 | 74 | private void Data_Changed(NbtTag tag) 75 | { 76 | if (Chunk.Data == tag) 77 | RefreshChildren(); 78 | } 79 | 80 | protected override IEnumerable GetChildren() 81 | { 82 | return AccessChunkData(); 83 | } 84 | 85 | public override string Description => NbtUtil.ChunkDescription(Chunk); 86 | 87 | public override bool CanCopy => !Chunk.IsExternal; 88 | public override DataObject Copy() => NbtNodeOperations.Copy(AccessChunkData()); 89 | public override bool CanDelete => !Chunk.IsExternal; 90 | public override void Delete() 91 | { 92 | Chunk.Remove(); 93 | base.Delete(); 94 | } 95 | public override bool CanEdit => !Chunk.IsExternal; 96 | public override bool CanPaste => !Chunk.IsExternal; 97 | public override bool CanRename => !Chunk.IsExternal; 98 | public override bool CanSort => !Chunk.IsExternal; 99 | public override void Sort() => NbtNodeOperations.Sort(AccessChunkData()); 100 | public override IEnumerable Paste(IDataObject data) 101 | { 102 | var tags = NbtNodeOperations.Paste(AccessChunkData(), data); 103 | return NodeChildren(tags); 104 | } 105 | public override bool CanReceiveDrop(IEnumerable nodes) => nodes.All(x => x is NbtTagNode); 106 | public override void ReceiveDrop(IEnumerable nodes, int index) 107 | { 108 | var tags = nodes.Filter(x => x.GetNbtTag()); 109 | NbtNodeOperations.ReceiveDrop(AccessChunkData(), tags, index); 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/FolderNode.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using Microsoft.VisualBasic.FileIO; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | using TryashtarUtils.Utility; 12 | 13 | namespace NbtStudio 14 | { 15 | public class FolderNode : ModelNode 16 | { 17 | public readonly NbtFolder Folder; 18 | public FolderNode(NbtTreeModel tree, INode parent, NbtFolder folder) : base(tree, parent) 19 | { 20 | Folder = folder; 21 | Folder.ContentsChanged += Folder_ContentsChanged; 22 | } 23 | 24 | protected override void SelfDispose() 25 | { 26 | Folder.ContentsChanged -= Folder_ContentsChanged; 27 | } 28 | 29 | private void Folder_ContentsChanged(object sender, EventArgs e) 30 | { 31 | RefreshChildren(); 32 | } 33 | 34 | public override bool HasChildren 35 | { 36 | get 37 | { 38 | if (!Folder.HasScanned) 39 | return true; 40 | return base.HasChildren; 41 | } 42 | } 43 | 44 | protected override IEnumerable GetChildren() 45 | { 46 | if (!Folder.HasScanned) 47 | Folder.Scan(); 48 | return Folder.Subfolders.Concat(Folder.Files); 49 | } 50 | 51 | public override string Description => System.IO.Path.GetFileName(Folder.Path); 52 | 53 | public override bool CanCopy => true; 54 | public override DataObject Copy() => FileNodeOperations.Copy(Folder.Path); 55 | public override bool CanCut => true; 56 | public override DataObject Cut() => FileNodeOperations.Cut(Folder.Path); 57 | public override bool CanDelete => true; 58 | public override void Delete() 59 | { 60 | FileNodeOperations.DeleteFolder(Folder.Path); 61 | base.Delete(); 62 | } 63 | public override bool CanEdit => true; 64 | public override bool CanPaste => true; 65 | public override IEnumerable Paste(IDataObject data) 66 | { 67 | var children = GetChildren().ToList(); 68 | var files = (string[])data.GetData("FileDrop"); 69 | var drop_effect = (MemoryStream)data.GetData("Preferred DropEffect"); 70 | if (files is null || drop_effect is null) 71 | return Enumerable.Empty(); 72 | var bytes = new byte[4]; 73 | drop_effect.Read(bytes, 0, bytes.Length); 74 | var drop = (DragDropEffects)BitConverter.ToInt32(bytes, 0); 75 | bool move = drop.HasFlag(DragDropEffects.Move); 76 | foreach (var item in files) 77 | { 78 | var destination = IOUtils.GetUniqueFilename(System.IO.Path.Combine(Folder.Path, System.IO.Path.GetFileName(item))); 79 | if (move) 80 | { 81 | if (Directory.Exists(item)) 82 | FileSystem.MoveDirectory(item, destination, UIOption.AllDialogs); 83 | else if (File.Exists(item)) 84 | FileSystem.MoveFile(item, destination, UIOption.AllDialogs); 85 | } 86 | else 87 | { 88 | if (Directory.Exists(item)) 89 | FileSystem.CopyDirectory(item, destination, UIOption.AllDialogs); 90 | else if (File.Exists(item)) 91 | FileSystem.CopyFile(item, destination, UIOption.AllDialogs); 92 | } 93 | } 94 | Folder.Scan(); 95 | var new_children = children.Except(GetChildren().ToList()); 96 | return NodeChildren(new_children); 97 | } 98 | public override bool CanRename => true; 99 | public override bool CanSort => false; 100 | public override bool CanReceiveDrop(IEnumerable nodes) => nodes.All(x => x.Get() is not null || x is FolderNode); 101 | public override void ReceiveDrop(IEnumerable nodes, int index) 102 | { 103 | var files = nodes.Filter(x => x.Get()); 104 | var folders = nodes.Filter(x => x.Get()); 105 | foreach (var file in files) 106 | { 107 | if (file.Path is not null) 108 | { 109 | var destination = System.IO.Path.Combine(Folder.Path, System.IO.Path.GetFileName(file.Path)); 110 | FileSystem.MoveFile(file.Path, destination, UIOption.AllDialogs); 111 | } 112 | } 113 | foreach (var folder in folders) 114 | { 115 | var destination = System.IO.Path.Combine(Folder.Path, System.IO.Path.GetFileName(folder.Path)); 116 | FileSystem.MoveDirectory(folder.Path, destination, UIOption.AllDialogs); 117 | } 118 | Folder.Scan(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/INode.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace NbtStudio 11 | { 12 | // basic interface for all items that can be interacted with on the form (tags, files, chunks, etc.) 13 | // this allows us to create relationships like NBT tags being the direct children of NBT files 14 | // NbtTreeModel creates nodes for the root objects, and they then create their own children 15 | // nodes can notify the NbtTreeModel of changes, so it can save undo history and synchronize nodes to the NbtTreeView 16 | // the ModelNodes test class verifies that we can create an NbtCompound, attach it to an NbtTreeModel, edit it as we please, and it creates nodes on the model and view automatically 17 | public interface INode 18 | { 19 | INode Parent { get; } 20 | TreePath Path { get; } // path from the root node to this node, can be created by following Parent until null 21 | IReadOnlyList Children { get; } 22 | bool HasChildren { get; } // allows nodes to be expandable without evaluating children yet 23 | string Description { get; } // appears in undo history 24 | int DescendantsCount { get; } // total number of descendant nodes, cached 25 | 26 | // operations to be performed on nodes 27 | bool CanDelete { get; } 28 | void Delete(); 29 | bool CanSort { get; } 30 | void Sort(); 31 | bool CanCopy { get; } 32 | DataObject Copy(); 33 | bool CanCut { get; } 34 | DataObject Cut(); 35 | bool CanPaste { get; } 36 | IEnumerable Paste(IDataObject data); 37 | bool CanRename { get; } 38 | bool CanEdit { get; } 39 | bool CanReceiveDrop(IEnumerable nodes); 40 | void ReceiveDrop(IEnumerable nodes, int index); 41 | } 42 | 43 | // pass in an object and get the best matching node type 44 | public static class NodeRegistry 45 | { 46 | // these used to be included in the static constructor of each class, but they don't run without interacting with the concrete class first 47 | static NodeRegistry() 48 | { 49 | Register((tree, parent, tag) => new NbtTagNode(tree, parent, tag)); 50 | Register((tree, parent, file) => new NbtFileNode(tree, parent, file)); 51 | Register((tree, parent, region) => new RegionFileNode(tree, parent, region)); 52 | Register((tree, parent, chunk) => new ChunkNode(tree, parent, chunk)); 53 | Register((tree, parent, folder) => new FolderNode(tree, parent, folder)); 54 | } 55 | 56 | private static readonly Dictionary> RegisteredConverters = new Dictionary>(); 57 | public static void Register(Func converter) 58 | { 59 | RegisteredConverters[typeof(T)] = (tree, parent, item) => converter(tree, parent, (T)item); 60 | } 61 | 62 | public static INode CreateNode(NbtTreeModel tree, INode parent, object item) 63 | { 64 | foreach (var converter in RegisteredConverters) 65 | { 66 | if (converter.Key.IsInstanceOfType(item)) 67 | return converter.Value(tree, parent, item); 68 | } 69 | throw new InvalidOperationException($"No registered converter for {item.GetType()}"); 70 | } 71 | 72 | public static INode CreateRootNode(NbtTreeModel tree, object item) => CreateNode(tree, null, item); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/ModelRootNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NbtStudio 8 | { 9 | public class ModelRootNode : ModelNode 10 | { 11 | private readonly List ChildObjects; 12 | public ModelRootNode(NbtTreeModel tree, IEnumerable children) : base(tree, null) 13 | { 14 | ChildObjects = children.ToList(); 15 | } 16 | 17 | protected override void SelfDispose() 18 | { } 19 | 20 | public void Add(object obj) 21 | { 22 | ChildObjects.Add(obj); 23 | RefreshChildren(); 24 | } 25 | 26 | public void AddRange(IEnumerable objects) 27 | { 28 | ChildObjects.AddRange(objects); 29 | RefreshChildren(); 30 | } 31 | 32 | public void Remove(INode child) 33 | { 34 | RemoveNodes(new[] { child }); 35 | } 36 | 37 | public void RemoveNodes(IEnumerable nodes) 38 | { 39 | var children = NodeChildrenMap(ChildObjects); 40 | foreach (var sibling in children) 41 | { 42 | if (nodes.Contains(sibling.Value)) 43 | ChildObjects.Remove(sibling.Key); 44 | } 45 | RefreshChildren(); 46 | } 47 | 48 | protected override IEnumerable GetChildren() 49 | { 50 | return ChildObjects; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/NbtFileNode.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | using TryashtarUtils.Forms; 10 | 11 | namespace NbtStudio 12 | { 13 | public class NbtFileNode : ModelNode 14 | { 15 | public readonly NbtFile File; 16 | public NbtFileNode(NbtTreeModel tree, INode parent, NbtFile file) : base(tree, parent) 17 | { 18 | File = file; 19 | File.RootTag.OnChanged += RootTag_Changed; 20 | File.RootTag.ActionPerformed += RootTag_ActionPerformed; 21 | File.OnSaved += File_OnSaved; 22 | } 23 | 24 | protected override void SelfDispose() 25 | { 26 | File.RootTag.OnChanged -= RootTag_Changed; 27 | File.RootTag.ActionPerformed -= RootTag_ActionPerformed; 28 | File.OnSaved -= File_OnSaved; 29 | } 30 | 31 | private void File_OnSaved() 32 | { 33 | NotifyChanged(); 34 | } 35 | 36 | private void RootTag_ActionPerformed(UndoableAction action) 37 | { 38 | NoticeAction(action); 39 | } 40 | 41 | private void RootTag_Changed(NbtTag tag) 42 | { 43 | if (File.RootTag == tag) 44 | RefreshChildren(); 45 | } 46 | 47 | protected override IEnumerable GetChildren() 48 | { 49 | if (File.RootTag is NbtContainerTag container) 50 | return container; 51 | return Enumerable.Empty(); 52 | } 53 | 54 | public override string Description => File.Path is null ? "unsaved file" : System.IO.Path.GetFileName(File.Path); 55 | 56 | // file nodes copy as both a file and a compound 57 | // they can then be pasted as text or into explorer, cool! 58 | public override bool CanCopy => true; 59 | public override DataObject Copy() 60 | { 61 | var data1 = NbtNodeOperations.Copy(File.RootTag); 62 | var data2 = FileNodeOperations.Copy(File.Path); 63 | return Utils.Merge(data1, data2); 64 | } 65 | public override bool CanCut => true; 66 | public override DataObject Cut() 67 | { 68 | var data1 = NbtNodeOperations.Copy(File.RootTag); 69 | var data2 = FileNodeOperations.Cut(File.Path); 70 | return Utils.Merge(data1, data2); 71 | } 72 | public override bool CanDelete => true; 73 | public override void Delete() 74 | { 75 | FileNodeOperations.DeleteFile(File.Path); 76 | base.Delete(); 77 | } 78 | public override bool CanEdit => File.Path is not null; 79 | public override bool CanPaste => NbtNodeOperations.CanPaste(File.RootTag); 80 | public override bool CanRename => File.Path is not null; 81 | public override bool CanSort => NbtNodeOperations.CanSort(File.RootTag); 82 | public override void Sort() => NbtNodeOperations.Sort(File.RootTag); 83 | public override IEnumerable Paste(IDataObject data) 84 | { 85 | var tags = NbtNodeOperations.Paste(File.RootTag, data); 86 | return NodeChildren(tags); 87 | } 88 | public override bool CanReceiveDrop(IEnumerable nodes) => nodes.All(x => x is NbtTagNode) && NbtNodeOperations.CanReceiveDrop(File.RootTag, nodes.Filter(x => x.GetNbtTag())); 89 | public override void ReceiveDrop(IEnumerable nodes, int index) 90 | { 91 | var tags = nodes.Filter(x => x.GetNbtTag()); 92 | NbtNodeOperations.ReceiveDrop(File.RootTag, tags, index); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/NbtTagNode.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace NbtStudio 11 | { 12 | public class NbtTagNode : ModelNode 13 | { 14 | public readonly NbtTag Tag; 15 | public NbtTagNode(NbtTreeModel tree, INode parent, NbtTag tag) : base(tree, parent) 16 | { 17 | Tag = tag; 18 | Tag.OnChanged += Tag_Changed; 19 | Tag.ActionPerformed += Tag_ActionPerformed; 20 | } 21 | 22 | protected override void SelfDispose() 23 | { 24 | Tag.OnChanged -= Tag_Changed; 25 | Tag.ActionPerformed -= Tag_ActionPerformed; 26 | } 27 | 28 | private void Tag_ActionPerformed(UndoableAction action) 29 | { 30 | NoticeAction(action); 31 | } 32 | 33 | private void Tag_Changed(NbtTag tag) 34 | { 35 | if (Tag == tag) 36 | RefreshChildren(); 37 | } 38 | 39 | protected override IEnumerable GetChildren() 40 | { 41 | if (Tag is NbtContainerTag container) 42 | return container; 43 | return Enumerable.Empty(); 44 | } 45 | 46 | public override string Description => Tag.TagDescription(); 47 | 48 | public override bool CanCopy => true; 49 | public override DataObject Copy() => NbtNodeOperations.Copy(Tag); 50 | public override bool CanDelete => true; 51 | public override void Delete() 52 | { 53 | Tag.Remove(); 54 | base.Delete(); 55 | } 56 | public override bool CanEdit => NbtNodeOperations.CanEdit(Tag); 57 | public override bool CanPaste => NbtNodeOperations.CanPaste(Tag); 58 | public override bool CanRename => NbtNodeOperations.CanRename(Tag); 59 | public override bool CanSort => NbtNodeOperations.CanSort(Tag); 60 | public override void Sort() => NbtNodeOperations.Sort(Tag); 61 | public override IEnumerable Paste(IDataObject data) 62 | { 63 | var tags = NbtNodeOperations.Paste(Tag, data); 64 | return NodeChildren(tags); 65 | } 66 | public override bool CanReceiveDrop(IEnumerable nodes) => nodes.All(x => x is NbtTagNode) && NbtNodeOperations.CanReceiveDrop(Tag, nodes.Filter(x => x.GetNbtTag())); 67 | public override void ReceiveDrop(IEnumerable nodes, int index) 68 | { 69 | var tags = nodes.Filter(x => x.GetNbtTag()); 70 | NbtNodeOperations.ReceiveDrop(Tag, tags, index); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /NbtStudio/Models/Nodes/RegionFileNode.cs: -------------------------------------------------------------------------------- 1 | using Aga.Controls.Tree; 2 | using fNbt; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.Specialized; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace NbtStudio 12 | { 13 | public class RegionFileNode : ModelNode 14 | { 15 | public readonly RegionFile Region; 16 | public RegionFileNode(NbtTreeModel tree, INode parent, RegionFile file) : base(tree, parent) 17 | { 18 | Region = file; 19 | Region.ChunksChanged += Region_ChunksChanged; 20 | Region.ActionPerformed += Region_ActionPerformed; 21 | Region.OnSaved += Region_OnSaved; 22 | } 23 | 24 | protected override void SelfDispose() 25 | { 26 | Region.ChunksChanged -= Region_ChunksChanged; 27 | Region.ActionPerformed -= Region_ActionPerformed; 28 | Region.OnSaved -= Region_OnSaved; 29 | } 30 | 31 | private void Region_OnSaved() 32 | { 33 | RefreshChildren(); 34 | } 35 | 36 | private void Region_ActionPerformed(UndoableAction action) 37 | { 38 | NoticeAction(action); 39 | } 40 | 41 | private void Region_ChunksChanged() 42 | { 43 | RefreshChildren(); 44 | } 45 | 46 | protected override IEnumerable GetChildren() 47 | { 48 | return Region.AllChunks; 49 | } 50 | 51 | public override string Description => Region.Path is null ? "unsaved region file" : System.IO.Path.GetFileName(Region.Path); 52 | 53 | public override bool CanCopy => Region.Path is not null; 54 | public override DataObject Copy() 55 | { 56 | var data = new DataObject(); 57 | if (Region.Path is not null) 58 | data.SetFileDropList(new StringCollection { Region.Path }); 59 | return data; 60 | } 61 | public override bool CanCut => Region.Path is not null; 62 | public override DataObject Cut() => FileNodeOperations.Cut(Region.Path); 63 | public override bool CanDelete => true; 64 | public override void Delete() 65 | { 66 | FileNodeOperations.DeleteFile(Region.Path); 67 | base.Delete(); 68 | } 69 | public override bool CanEdit => Region.Path is not null; 70 | public override bool CanPaste => true; 71 | public override bool CanRename => Region.Path is not null; 72 | public override bool CanSort => false; 73 | public override IEnumerable Paste(IDataObject data) 74 | { 75 | var tags = NbtNodeOperations.ParseTags(data).OfType().ToList(); 76 | var available = Region.GetAvailableCoords(); 77 | var chunks = Enumerable.Zip(available, tags, (slot, tag) => Chunk.EmptyChunk(tag, slot.x, slot.z)).ToList(); 78 | foreach (var chunk in chunks) 79 | { 80 | Region.AddChunk(chunk); 81 | } 82 | return NodeChildren(chunks); 83 | } 84 | public override bool CanReceiveDrop(IEnumerable nodes) => nodes.All(x => x is ChunkNode); 85 | public override void ReceiveDrop(IEnumerable nodes, int index) 86 | { 87 | var chunks = nodes.Filter(x => x.Get()).ToList(); 88 | foreach (var chunk in chunks) 89 | { 90 | if (Region.GetChunk(chunk.X, chunk.Z) is null) 91 | Region.AddChunk(chunk); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /NbtStudio/Models/UndoHistory.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace NbtStudio 9 | { 10 | public class UndoHistory 11 | { 12 | private readonly Func DescriptionGenerator; 13 | private readonly Stack UndoStack = new(); 14 | private readonly Stack RedoStack = new(); 15 | public event EventHandler Changed; 16 | 17 | public UndoHistory(Func description_generator) 18 | { 19 | DescriptionGenerator = description_generator; 20 | } 21 | 22 | public string GetDescription(DescriptionHolder holder) 23 | { 24 | return holder.Convert(DescriptionGenerator); 25 | } 26 | 27 | public void SaveAction(UndoableAction action) 28 | { 29 | if (!action.IsDone) 30 | throw new ArgumentException($"Action {GetDescription(action.Description)} hasn't been done yet, we can't save it to the undo stack"); 31 | RedoStack.Clear(); 32 | if (BatchNumber == 0) 33 | { 34 | UndoStack.Push(action); 35 | Changed?.Invoke(this, EventArgs.Empty); 36 | } 37 | else 38 | UndoBatch.Add(action); 39 | #if DEBUG 40 | if (BatchNumber == 0) 41 | Console.WriteLine($"Added action to main stack: \"{GetDescription(action.Description)}\". Undo stack has {UndoStack.Count} items"); 42 | else 43 | Console.WriteLine($"Added action to batch: \"{GetDescription(action.Description)}\". Batch has {UndoBatch.Count} items"); 44 | #endif 45 | } 46 | 47 | public void Undo(int count = 1) 48 | { 49 | for (int i = 0; i < count && UndoStack.Any(); i++) 50 | { 51 | var action = UndoStack.Pop(); 52 | RedoStack.Push(action); 53 | action.Undo(); 54 | #if DEBUG 55 | Console.WriteLine($"Performed undo of action \"{action.Description}\". Undo stack has {UndoStack.Count} items. Redo stack has {RedoStack.Count} items"); 56 | #endif 57 | } 58 | Changed?.Invoke(this, EventArgs.Empty); 59 | } 60 | 61 | public void Redo(int count = 1) 62 | { 63 | for (int i = 0; i < count && RedoStack.Any(); i++) 64 | { 65 | var action = RedoStack.Pop(); 66 | UndoStack.Push(action); 67 | action.Do(); 68 | #if DEBUG 69 | Console.WriteLine($"Performed redo of action \"{action.Description}\". Redo stack has {RedoStack.Count} items. Undo stack has {UndoStack.Count} items"); 70 | #endif 71 | } 72 | Changed?.Invoke(this, EventArgs.Empty); 73 | } 74 | 75 | public void Clear() 76 | { 77 | UndoStack.Clear(); 78 | RedoStack.Clear(); 79 | Changed?.Invoke(this, EventArgs.Empty); 80 | } 81 | 82 | public bool CanUndo => UndoStack.Any(); 83 | public bool CanRedo => RedoStack.Any(); 84 | 85 | public List> GetUndoHistory() 86 | { 87 | return UndoStack.Select((v, i) => new KeyValuePair(i + 1, GetDescription(v.Description))).ToList(); 88 | } 89 | public List> GetRedoHistory() 90 | { 91 | return RedoStack.Select((v, i) => new KeyValuePair(i + 1, GetDescription(v.Description))).ToList(); 92 | } 93 | 94 | private int BatchNumber = 0; 95 | private readonly List UndoBatch = new List(); 96 | // call this and then do things that signal undos, then call FinishBatchOperation to merge all those undos into one 97 | public void StartBatchOperation() 98 | { 99 | BatchNumber++; 100 | #if DEBUG 101 | Console.WriteLine($"Starting a batch operation"); 102 | if (BatchNumber > 1) 103 | Console.WriteLine($"It's nested (batch {BatchNumber}), that's a bit unusual"); 104 | #endif 105 | } 106 | 107 | public void FinishBatchOperation(DescriptionHolder description, bool replace_single) 108 | { 109 | if (BatchNumber == 0) 110 | { 111 | #if DEBUG 112 | Console.WriteLine($"Told to finish a batch operation but we aren't currently doing one?"); 113 | #endif 114 | return; 115 | } 116 | #if DEBUG 117 | if (!UndoBatch.Any()) 118 | Console.WriteLine($"Finished a batch that didn't have any actions in it?"); 119 | if (BatchNumber > 1) 120 | Console.WriteLine($"Finished nested batch {BatchNumber}, continuing to batch"); 121 | #endif 122 | BatchNumber--; 123 | if (BatchNumber == 0 && UndoBatch.Any()) 124 | { 125 | UndoableAction merged_action; 126 | if (replace_single || UndoBatch.Count > 1) 127 | merged_action = UndoableAction.Merge(description, UndoBatch); 128 | else 129 | merged_action = UndoBatch.Single(); 130 | UndoStack.Push(merged_action); 131 | #if DEBUG 132 | Console.WriteLine($"Finished batch of {UndoBatch.Count} actions, merged onto stack as action: \"{GetDescription(description)}\". Stack has {UndoStack.Count} items"); 133 | #endif 134 | UndoBatch.Clear(); 135 | Changed?.Invoke(this, EventArgs.Empty); 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /NbtStudio/NbtObjects/Chunk.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TryashtarUtils.Utility; 9 | 10 | namespace NbtStudio 11 | { 12 | public class Chunk : IExportable 13 | { 14 | public const int BlocksXDimension = 16; 15 | public const int BlocksZDimension = 16; 16 | public RegionFile Region { get; internal set; } 17 | public int X { get; private set; } 18 | public int Z { get; private set; } 19 | public NbtCompound Data { get; private set; } 20 | public bool HasUnsavedChanges { get; private set; } = false; 21 | private readonly int Offset; 22 | private readonly int Size; 23 | private NbtCompression Compression; 24 | public bool IsLoaded => Data is not null; 25 | public event EventHandler OnLoaded; 26 | public bool IsCorrupt { get; private set; } = false; 27 | public bool IsExternal { get; private set; } = false; 28 | private byte ExternalCompression; 29 | internal Chunk(RegionFile region, int x, int z, int offset, int size) 30 | { 31 | Region = region; 32 | X = x; 33 | Z = z; 34 | Offset = offset; 35 | Size = size; 36 | } 37 | 38 | public static Chunk EmptyChunk(NbtCompound data, int x = -1, int z = -1) 39 | { 40 | var chunk = new Chunk(null, x, z, 0, 0); 41 | chunk.SetData(data ?? new NbtCompound("")); 42 | chunk.Compression = NbtCompression.ZLib; 43 | chunk.HasUnsavedChanges = true; 44 | return chunk; 45 | } 46 | 47 | private void SetData(NbtCompound data) 48 | { 49 | Data = data; 50 | Data.OnChanged += _ => HasUnsavedChanges = true; 51 | } 52 | 53 | public byte[] SaveBytes() 54 | { 55 | if (IsExternal) 56 | { 57 | var data = new byte[Size + 5]; 58 | var size = DataUtils.GetBytes(1); 59 | Array.Copy(size, data, 4); 60 | data[4] = ExternalCompression; 61 | return data; 62 | } 63 | if (!IsLoaded) 64 | { 65 | var stream = Region.GetStream(); 66 | stream.Seek(Offset, SeekOrigin.Begin); 67 | byte[] result = new byte[Size]; 68 | stream.Read(result, 0, Size); 69 | stream.Dispose(); 70 | return result; 71 | } 72 | if (IsCorrupt) 73 | return new byte[0]; 74 | var file = new fNbt.NbtFile(Data); 75 | var bytes = file.SaveToBuffer(Compression); 76 | var with_header = new byte[bytes.Length + 5]; 77 | Array.Copy(bytes, 0, with_header, 5, bytes.Length); 78 | var length = DataUtils.GetBytes(bytes.Length); 79 | Array.Copy(length, with_header, 4); 80 | if (Compression == NbtCompression.GZip) 81 | with_header[4] = 1; 82 | else if (Compression == NbtCompression.ZLib) 83 | with_header[4] = 2; 84 | HasUnsavedChanges = false; 85 | return with_header; 86 | } 87 | 88 | public void Load() 89 | { 90 | if (IsCorrupt || IsExternal) return; 91 | var stream = Region.GetStream(); 92 | stream.Seek(Offset + 4, SeekOrigin.Begin); 93 | int compression = stream.ReadByte(); 94 | if (compression == -1) 95 | { 96 | IsCorrupt = true; 97 | Remove(); 98 | } 99 | else 100 | { 101 | if ((compression & (1 << 7)) != 0) 102 | { 103 | IsExternal = true; 104 | ExternalCompression = (byte)compression; 105 | } 106 | else 107 | { 108 | var file = new fNbt.NbtFile(); 109 | try 110 | { 111 | file.LoadFromStream(stream, NbtCompression.AutoDetect); 112 | Compression = file.FileCompression; 113 | SetData(file.GetRootTag()); 114 | } 115 | catch 116 | { 117 | IsCorrupt = true; 118 | Remove(); 119 | } 120 | } 121 | } 122 | stream.Dispose(); 123 | OnLoaded?.Invoke(this, EventArgs.Empty); 124 | } 125 | 126 | public void SaveAs(string path) 127 | { 128 | File.WriteAllBytes(path, SaveBytes()); 129 | } 130 | 131 | public void Remove() 132 | { 133 | if (Region is not null) 134 | Region.RemoveChunk(X, Z); 135 | } 136 | 137 | public void AddTo(RegionFile region) 138 | { 139 | region.AddChunk(this); 140 | } 141 | 142 | public void Move(int x, int z) 143 | { 144 | var region = Region; 145 | if (region is not null) 146 | region.RemoveChunk(X, Z); 147 | X = x; 148 | Z = z; 149 | if (region is not null) 150 | region.AddChunk(this); 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /NbtStudio/NbtObjects/Interfaces.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace NbtStudio 8 | { 9 | public interface IHavePath 10 | { 11 | string Path { get; } 12 | void Move(string path); 13 | } 14 | 15 | public interface ISaveable 16 | { 17 | event Action OnSaved; 18 | bool HasUnsavedChanges { get; } 19 | bool CanSave { get; } 20 | void Save(); 21 | } 22 | 23 | public interface IExportable 24 | { 25 | void SaveAs(string path); 26 | } 27 | 28 | public interface IRefreshable 29 | { 30 | bool CanRefresh { get; } 31 | void Refresh(); 32 | } 33 | 34 | public interface IFile : IHavePath, ISaveable, IExportable, IRefreshable 35 | { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /NbtStudio/NbtObjects/NbtFolder.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using TryashtarUtils.Utility; 9 | 10 | namespace NbtStudio 11 | { 12 | public class NbtFolder : IHavePath, IRefreshable, IDisposable 13 | { 14 | public string Path { get; private set; } 15 | public readonly bool Recursive; 16 | public bool HasScanned { get; private set; } = false; 17 | public event EventHandler ContentsChanged; 18 | public event EventHandler file)>> FilesFailed; 19 | public IReadOnlyCollection Subfolders => SubfolderDict.Values; 20 | public IEnumerable GetAllSubfolders() => Subfolders.Concat(Subfolders.SelectMany(x => x.GetAllSubfolders())); 21 | public IReadOnlyCollection Files => FileDict.Values; 22 | public IEnumerable GetAllFiles() => Files.Concat(Subfolders.SelectMany(x => x.GetAllFiles())); 23 | public IEnumerable<(string path, IFailable file)> FailedFiles => FailedFileDict.Select(x => (x.Key, x.Value)); 24 | private readonly Dictionary SubfolderDict = new(); 25 | private readonly Dictionary FileDict = new(); 26 | private readonly Dictionary> FailedFileDict = new(); 27 | public bool CanRefresh => true; 28 | public void Refresh() => Scan(); 29 | 30 | public NbtFolder(string path, bool recursive) 31 | { 32 | Path = path; 33 | Recursive = recursive; 34 | } 35 | 36 | public void Scan() 37 | { 38 | HasScanned = true; 39 | IEnumerable files; 40 | if (Directory.Exists(Path)) 41 | files = Directory.GetFiles(Path).OrderBy(x => x, LogicalStringComparer.Instance); 42 | else 43 | files = Array.Empty(); 44 | var newly_failed = new List<(string path, IFailable file)>(); 45 | foreach (var path in files) 46 | { 47 | if (!FileDict.ContainsKey(path)) 48 | { 49 | var file = OpenFile(path); 50 | if (file.Failed) 51 | newly_failed.Add((path, file)); 52 | else 53 | FileDict.Add(path, file.Result); 54 | } 55 | } 56 | foreach (var key in FileDict.Keys.ToList()) 57 | { 58 | if (!files.Contains(key)) 59 | FileDict.Remove(key); 60 | } 61 | if (Recursive) 62 | { 63 | string[] folders; 64 | if (Directory.Exists(Path)) 65 | folders = Directory.GetDirectories(Path, "*", SearchOption.TopDirectoryOnly); 66 | else 67 | folders = new string[0]; 68 | foreach (var path in folders) 69 | { 70 | if (!SubfolderDict.ContainsKey(path)) 71 | SubfolderDict.Add(path, new NbtFolder(path, true)); 72 | } 73 | foreach (var key in SubfolderDict.Keys.ToList()) 74 | { 75 | if (!folders.Contains(key)) 76 | SubfolderDict.Remove(key); 77 | } 78 | } 79 | ContentsChanged?.Invoke(this, EventArgs.Empty); 80 | if (newly_failed.Any()) 81 | { 82 | FilesFailed?.Invoke(this, newly_failed); 83 | foreach (var item in newly_failed) 84 | { 85 | FailedFileDict[item.path] = item.file; 86 | } 87 | } 88 | } 89 | 90 | public static IFailable OpenFile(string path) 91 | { 92 | var attempt1 = NbtFile.TryCreate(path); 93 | if (!attempt1.Failed) 94 | return attempt1; 95 | var attempt2 = RegionFile.TryCreate(path); 96 | if (!attempt2.Failed) 97 | return attempt2; 98 | return FailableFactory.Aggregate(attempt1, attempt2); 99 | } 100 | 101 | public static IFailable OpenFileOrFolder(string path) 102 | { 103 | if (Directory.Exists(path)) 104 | return new Failable(() => new NbtFolder(path, true), "Load as folder"); 105 | return OpenFile(path); 106 | } 107 | 108 | public void Move(string path) 109 | { 110 | Directory.Move(Path, path); 111 | Path = path; 112 | } 113 | 114 | public void Dispose() 115 | { 116 | 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /NbtStudio/NbtStudio.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net6.0-windows 6 | true 7 | true 8 | false 9 | win-x64 10 | NbtStudio 11 | NbtStudio 12 | nbt_studio_icon_256.ico 13 | Debug;Release;PublishNormal;PublishSuperStandalone 14 | tryashtar 15 | 16 | NBT Studio 17 | Views and modifies NBT files 18 | Copyright © 2020 tryashtar 19 | https://github.com/tryashtar/nbt-studio 20 | https://github.com/tryashtar/nbt-studio 21 | nbt_studio_icon_256.png 22 | 1.15.3.0 23 | 1.15.3.0 24 | false 25 | 1.15.3 26 | 27 | 28 | 29 | AnyCPU 30 | 31 | 32 | 33 | true 34 | true 35 | true 36 | true 37 | 38 | 39 | 40 | 41 | 42 | 43 | True 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | ResXFileCodeGenerator 62 | Designer 63 | Resources.Designer.cs 64 | 65 | 66 | True 67 | True 68 | Resources.resx 69 | 70 | 71 | True 72 | True 73 | Settings.settings 74 | 75 | 76 | 77 | 78 | SettingsSingleFileGenerator 79 | Settings.Designer.cs 80 | 81 | 82 | -------------------------------------------------------------------------------- /NbtStudio/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Threading.Tasks; 6 | using System.Windows.Forms; 7 | using NbtStudio.UI; 8 | 9 | namespace NbtStudio 10 | { 11 | static class Program 12 | { 13 | /// 14 | /// The main entry point for the application. 15 | /// 16 | [STAThread] 17 | static void Main(string[] args) 18 | { 19 | if (Environment.OSVersion.Version.Major >= 6) 20 | SetProcessDPIAware(); 21 | Application.EnableVisualStyles(); 22 | Application.SetCompatibleTextRenderingDefault(false); 23 | Application.Run(new MainForm(args)); 24 | } 25 | 26 | [DllImport("user32.dll")] 27 | private static extern bool SetProcessDPIAware(); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /NbtStudio/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace NbtStudio.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | public global::System.Collections.Specialized.StringCollection RecentFiles { 29 | get { 30 | return ((global::System.Collections.Specialized.StringCollection)(this["RecentFiles"])); 31 | } 32 | set { 33 | this["RecentFiles"] = value; 34 | } 35 | } 36 | 37 | [global::System.Configuration.UserScopedSettingAttribute()] 38 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 39 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 40 | public bool FindRegex { 41 | get { 42 | return ((bool)(this["FindRegex"])); 43 | } 44 | set { 45 | this["FindRegex"] = value; 46 | } 47 | } 48 | 49 | [global::System.Configuration.UserScopedSettingAttribute()] 50 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 51 | [global::System.Configuration.DefaultSettingValueAttribute("9")] 52 | public int TreeZoom { 53 | get { 54 | return ((int)(this["TreeZoom"])); 55 | } 56 | set { 57 | this["TreeZoom"] = value; 58 | } 59 | } 60 | 61 | [global::System.Configuration.UserScopedSettingAttribute()] 62 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 63 | [global::System.Configuration.DefaultSettingValueAttribute("")] 64 | public string FindName { 65 | get { 66 | return ((string)(this["FindName"])); 67 | } 68 | set { 69 | this["FindName"] = value; 70 | } 71 | } 72 | 73 | [global::System.Configuration.UserScopedSettingAttribute()] 74 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 75 | [global::System.Configuration.DefaultSettingValueAttribute("")] 76 | public string FindValue { 77 | get { 78 | return ((string)(this["FindValue"])); 79 | } 80 | set { 81 | this["FindValue"] = value; 82 | } 83 | } 84 | 85 | [global::System.Configuration.UserScopedSettingAttribute()] 86 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 87 | [global::System.Configuration.DefaultSettingValueAttribute("")] 88 | public string FindText { 89 | get { 90 | return ((string)(this["FindText"])); 91 | } 92 | set { 93 | this["FindText"] = value; 94 | } 95 | } 96 | 97 | [global::System.Configuration.UserScopedSettingAttribute()] 98 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 99 | [global::System.Configuration.DefaultSettingValueAttribute("")] 100 | public string ReplaceText { 101 | get { 102 | return ((string)(this["ReplaceText"])); 103 | } 104 | set { 105 | this["ReplaceText"] = value; 106 | } 107 | } 108 | 109 | [global::System.Configuration.UserScopedSettingAttribute()] 110 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 111 | [global::System.Configuration.DefaultSettingValueAttribute("")] 112 | public string IconSet { 113 | get { 114 | return ((string)(this["IconSet"])); 115 | } 116 | set { 117 | this["IconSet"] = value; 118 | } 119 | } 120 | 121 | [global::System.Configuration.UserScopedSettingAttribute()] 122 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 123 | public global::System.Collections.Specialized.StringCollection CustomIconSets { 124 | get { 125 | return ((global::System.Collections.Specialized.StringCollection)(this["CustomIconSets"])); 126 | } 127 | set { 128 | this["CustomIconSets"] = value; 129 | } 130 | } 131 | 132 | [global::System.Configuration.UserScopedSettingAttribute()] 133 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 134 | [global::System.Configuration.DefaultSettingValueAttribute("False")] 135 | public bool TagWordWrap { 136 | get { 137 | return ((bool)(this["TagWordWrap"])); 138 | } 139 | set { 140 | this["TagWordWrap"] = value; 141 | } 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /NbtStudio/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | False 10 | 11 | 12 | 9 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | False 34 | 35 | 36 | -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_add_snbt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_add_snbt.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_copy.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_cut.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_delete.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_edit.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_edit_snbt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_edit_snbt.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_new_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_new_file.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_open_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_open_file.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_open_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_open_folder.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_paste.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_redo.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_refresh.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_rename.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_save.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_save_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_save_all.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_search.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_sort.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/action_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/action_undo.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/file_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/file_chunk.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/file_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/file_file.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/file_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/file_folder.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/file_region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/file_region.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_byte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_byte.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_byte_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_byte_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_compound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_compound.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_double.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_float.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_int.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_int_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_int_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_list.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_long.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_long_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_long_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_short.png -------------------------------------------------------------------------------- /NbtStudio/Resources/amber/tag_string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/amber/tag_string.png -------------------------------------------------------------------------------- /NbtStudio/Resources/nbt_studio_icon_16.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/nbt_studio_icon_16.ico -------------------------------------------------------------------------------- /NbtStudio/Resources/nbt_studio_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/nbt_studio_icon_16.png -------------------------------------------------------------------------------- /NbtStudio/Resources/nbt_studio_icon_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/nbt_studio_icon_256.ico -------------------------------------------------------------------------------- /NbtStudio/Resources/nbt_studio_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/nbt_studio_icon_256.png -------------------------------------------------------------------------------- /NbtStudio/Resources/tryashtar/action_add_snbt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/tryashtar/action_add_snbt.png -------------------------------------------------------------------------------- /NbtStudio/Resources/tryashtar/action_edit_snbt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/tryashtar/action_edit_snbt.png -------------------------------------------------------------------------------- /NbtStudio/Resources/tryashtar/file_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/tryashtar/file_chunk.png -------------------------------------------------------------------------------- /NbtStudio/Resources/tryashtar/file_region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/tryashtar/file_region.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_byte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_byte.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_byte_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_byte_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_compound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_compound.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_double.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_float.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_int.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_int_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_int_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_list.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_long.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_long_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_long_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_short.png -------------------------------------------------------------------------------- /NbtStudio/Resources/wiki/tag_string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/wiki/tag_string.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_copy.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_cut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_cut.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_delete.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_edit.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_new_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_new_file.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_open_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_open_file.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_open_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_open_folder.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_paste.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_redo.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_refresh.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_rename.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_rename.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_save.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_save_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_save_all.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_search.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_sort.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/action_undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/action_undo.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/file_chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/file_chunk.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/file_file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/file_file.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/file_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/file_folder.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/file_region.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/file_region.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/folder.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_byte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_byte.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_byte_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_byte_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_compound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_compound.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_double.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_double.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_float.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_float.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_int.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_int.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_int_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_int_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_list.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_long.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_long_array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_long_array.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_short.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_short.png -------------------------------------------------------------------------------- /NbtStudio/Resources/yusuke/tag_string.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/Resources/yusuke/tag_string.png -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/ActionHistory.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NbtStudio.UI 2 | { 3 | partial class ActionHistory 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.ActionList = new System.Windows.Forms.ListBox(); 32 | this.InfoPanel = new System.Windows.Forms.Panel(); 33 | this.SelectedLabel = new System.Windows.Forms.Label(); 34 | this.InfoPanel.SuspendLayout(); 35 | this.SuspendLayout(); 36 | // 37 | // ActionList 38 | // 39 | this.ActionList.BorderStyle = System.Windows.Forms.BorderStyle.None; 40 | this.ActionList.Dock = System.Windows.Forms.DockStyle.Fill; 41 | this.ActionList.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; 42 | this.ActionList.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 43 | this.ActionList.ItemHeight = 15; 44 | this.ActionList.Location = new System.Drawing.Point(0, 0); 45 | this.ActionList.Name = "ActionList"; 46 | this.ActionList.SelectionMode = System.Windows.Forms.SelectionMode.MultiSimple; 47 | this.ActionList.Size = new System.Drawing.Size(200, 180); 48 | this.ActionList.TabIndex = 0; 49 | this.ActionList.DrawItem += new System.Windows.Forms.DrawItemEventHandler(this.ActionList_DrawItem); 50 | this.ActionList.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ActionList_MouseDown); 51 | this.ActionList.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ActionList_MouseMove); 52 | // 53 | // InfoPanel 54 | // 55 | this.InfoPanel.BackColor = System.Drawing.SystemColors.ControlLight; 56 | this.InfoPanel.Controls.Add(this.SelectedLabel); 57 | this.InfoPanel.Dock = System.Windows.Forms.DockStyle.Bottom; 58 | this.InfoPanel.Location = new System.Drawing.Point(0, 180); 59 | this.InfoPanel.Name = "InfoPanel"; 60 | this.InfoPanel.Size = new System.Drawing.Size(200, 20); 61 | this.InfoPanel.TabIndex = 1; 62 | // 63 | // SelectedLabel 64 | // 65 | this.SelectedLabel.AutoSize = true; 66 | this.SelectedLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); 67 | this.SelectedLabel.Location = new System.Drawing.Point(3, 4); 68 | this.SelectedLabel.Name = "SelectedLabel"; 69 | this.SelectedLabel.Size = new System.Drawing.Size(0, 15); 70 | this.SelectedLabel.TabIndex = 0; 71 | // 72 | // ActionHistory 73 | // 74 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 75 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 76 | this.Controls.Add(this.ActionList); 77 | this.Controls.Add(this.InfoPanel); 78 | this.Margin = new System.Windows.Forms.Padding(0); 79 | this.Name = "ActionHistory"; 80 | this.Size = new System.Drawing.Size(200, 200); 81 | this.InfoPanel.ResumeLayout(false); 82 | this.InfoPanel.PerformLayout(); 83 | this.ResumeLayout(false); 84 | 85 | } 86 | 87 | #endregion 88 | 89 | private System.Windows.Forms.ListBox ActionList; 90 | private System.Windows.Forms.Panel InfoPanel; 91 | private System.Windows.Forms.Label SelectedLabel; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/ActionHistory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace NbtStudio.UI 12 | { 13 | public partial class ActionHistory : UserControl 14 | { 15 | private int Index = -1; 16 | private readonly Func TextDisplay; 17 | public ActionHistory(IEnumerable> history, Action command, Func display, Font font) 18 | { 19 | InitializeComponent(); 20 | 21 | TextDisplay = display; 22 | this.Font = font; 23 | ActionList.Click += (s, e) => command(Index); 24 | SetItems(history.Select(x => x.Value)); 25 | if (ActionList.Items.Count > 0) 26 | SetIndex(0); 27 | } 28 | 29 | private void SetItems(IEnumerable text) 30 | { 31 | var graphics = ActionList.CreateGraphics(); 32 | var text_array = text.ToArray(); 33 | ActionList.Items.AddRange(text_array); 34 | int width = 0; 35 | int height = 0; 36 | foreach (var item in text_array) 37 | { 38 | var size = graphics.MeasureString(item, ActionList.Font); 39 | width = Math.Max(width, (int)Math.Ceiling(size.Width)); 40 | height = Math.Max(height, (int)Math.Ceiling(size.Height)); 41 | } 42 | var box_size = new Size(width + 20, height * Math.Min(10, text_array.Length)); 43 | var full_size = new Size(box_size.Width, box_size.Height + InfoPanel.Height); 44 | ActionList.MinimumSize = box_size; 45 | ActionList.ItemHeight = Math.Max(1, height); 46 | this.MinimumSize = full_size; 47 | this.Size = full_size; 48 | } 49 | 50 | public void SetIndex(int index) 51 | { 52 | Index = index; 53 | ActionList.BeginUpdate(); 54 | ActionList.ClearSelected(); 55 | for (int i = 0; i <= Index; i++) 56 | { 57 | ActionList.SetSelected(i, true); 58 | } 59 | SelectedLabel.Text = TextDisplay(index); 60 | ActionList.EndUpdate(); 61 | } 62 | 63 | private void ActionList_MouseMove(object sender, MouseEventArgs e) 64 | { 65 | int hovered = ActionList.IndexFromPoint(e.Location); 66 | int top = Math.Max(0, Math.Min(ActionList.TopIndex + e.Delta, ActionList.Items.Count - 1)); 67 | if (hovered != -1 && hovered != Index) 68 | SetIndex(hovered); 69 | ActionList.TopIndex = top; 70 | } 71 | 72 | private void ActionList_MouseDown(object sender, MouseEventArgs e) 73 | { 74 | int clicked = ActionList.IndexFromPoint(e.Location); 75 | if (clicked != -1) 76 | ActionList.SetSelected(clicked, true); 77 | } 78 | 79 | private void ActionList_DrawItem(object sender, DrawItemEventArgs e) 80 | { 81 | if (e.Index < 0) return; 82 | 83 | // change selected color from ugly default 84 | if ((e.State & DrawItemState.Selected) == DrawItemState.Selected) 85 | e = new DrawItemEventArgs(e.Graphics, e.Font, e.Bounds, e.Index, e.State ^ DrawItemState.Selected, e.ForeColor, Constants.SelectionColor); 86 | 87 | e.DrawBackground(); 88 | // draw text using normal ForeColor (black) instead of e.ForeColor (white) 89 | e.Graphics.DrawString(ActionList.Items[e.Index].ToString(), e.Font, new SolidBrush(ActionList.ForeColor), e.Bounds, StringFormat.GenericDefault); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/ChunkCoordEditControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace NbtStudio.UI 10 | { 11 | public class ChunkCoordsEditControls 12 | { 13 | public readonly Chunk Chunk; 14 | public readonly RegionFile Region; 15 | public readonly ChunkCoordEditControl XBox; 16 | public readonly ChunkCoordEditControl ZBox; 17 | public ChunkCoordsEditControls(Chunk chunk, RegionFile region, ChunkCoordEditControl xbox, ChunkCoordEditControl zbox) 18 | { 19 | Chunk = chunk; 20 | Region = region; 21 | XBox = xbox; 22 | ZBox = zbox; 23 | XBox.Maximum = RegionFile.ChunkXDimension - 1; 24 | ZBox.Maximum = RegionFile.ChunkZDimension - 1; 25 | XBox.Value = Math.Min(Math.Max(chunk.X, XBox.Minimum), XBox.Maximum); 26 | ZBox.Value = Math.Min(Math.Max(chunk.Z, ZBox.Minimum), ZBox.Maximum); 27 | if (CheckCoordsInternal() != CoordCheckResult.Valid) 28 | { 29 | var auto = region.GetAvailableCoords(); 30 | if (auto.Any()) 31 | { 32 | var (x, y) = auto.First(); 33 | XBox.Value = x; 34 | ZBox.Value = y; 35 | } 36 | } 37 | XBox.TextChanged += Box_TextChanged; 38 | ZBox.TextChanged += Box_TextChanged; 39 | } 40 | 41 | private CoordCheckResult CheckCoordsInternal() 42 | { 43 | int xval = XBox.GetCoord(); 44 | int zval = ZBox.GetCoord(); 45 | if (xval == Chunk.X && zval == Chunk.Z) 46 | return CoordCheckResult.Valid; 47 | bool x_out = xval < 0 || xval >= RegionFile.ChunkXDimension; 48 | bool z_out = zval < 0 || zval >= RegionFile.ChunkZDimension; 49 | if (x_out && z_out) 50 | return CoordCheckResult.InvalidBothOutOfBounds; 51 | else if (x_out) 52 | return CoordCheckResult.InvalidXOutOfBounds; 53 | else if (z_out) 54 | return CoordCheckResult.InvalidZOutOfBounds; 55 | 56 | if (Region is null) 57 | return CoordCheckResult.Valid; 58 | 59 | if (Region.GetChunk(xval, zval) is not null) 60 | return CoordCheckResult.InvalidAlreadyTaken; 61 | 62 | return CoordCheckResult.Valid; 63 | } 64 | 65 | public bool CheckCoords() 66 | { 67 | var result = CheckCoordsInternal(); 68 | bool valid = result == CoordCheckResult.Valid; 69 | SetColor(result); 70 | if (!valid) 71 | { 72 | ShowTooltip(result); 73 | if (result == CoordCheckResult.InvalidXOutOfBounds) 74 | XBox.Select(); 75 | else if (result == CoordCheckResult.InvalidZOutOfBounds) 76 | ZBox.Select(); 77 | } 78 | return valid; 79 | } 80 | 81 | public void ApplyCoords() 82 | { 83 | int xval = XBox.GetCoord(); 84 | int zval = ZBox.GetCoord(); 85 | Chunk.Move(xval, zval); 86 | } 87 | 88 | private void SetColor(CoordCheckResult result) 89 | { 90 | var bad_color = Color.FromArgb(255, 230, 230); 91 | switch (result) 92 | { 93 | case CoordCheckResult.InvalidXOutOfBounds: 94 | XBox.SetBackColor(bad_color); 95 | ZBox.RestoreBackColor(); 96 | break; 97 | case CoordCheckResult.InvalidZOutOfBounds: 98 | ZBox.SetBackColor(bad_color); 99 | XBox.RestoreBackColor(); 100 | break; 101 | case CoordCheckResult.InvalidAlreadyTaken: 102 | case CoordCheckResult.InvalidBothOutOfBounds: 103 | XBox.SetBackColor(bad_color); 104 | ZBox.SetBackColor(bad_color); 105 | break; 106 | case CoordCheckResult.Valid: 107 | XBox.RestoreBackColor(); 108 | ZBox.RestoreBackColor(); 109 | break; 110 | } 111 | } 112 | 113 | private void ShowTooltip(CoordCheckResult result) 114 | { 115 | var active = ZBox.Focused ? ZBox : XBox; 116 | if (result == CoordCheckResult.InvalidAlreadyTaken) 117 | active.ShowTooltip("Chunk Already Present", "There is already a chunk in the region at these coordinates", TimeSpan.FromSeconds(2)); 118 | else if (result == CoordCheckResult.InvalidXOutOfBounds || result == CoordCheckResult.InvalidZOutOfBounds || result == CoordCheckResult.InvalidBothOutOfBounds) 119 | active.ShowTooltip("Out of Bounds", $"Chunk coordinates must be between 0 and {RegionFile.ChunkXDimension - 1}", TimeSpan.FromSeconds(2)); 120 | } 121 | 122 | private void Box_TextChanged(object sender, EventArgs e) 123 | { 124 | SetColor(CheckCoordsInternal()); 125 | } 126 | 127 | public enum CoordCheckResult 128 | { 129 | Valid, 130 | InvalidAlreadyTaken, 131 | InvalidXOutOfBounds, 132 | InvalidZOutOfBounds, 133 | InvalidBothOutOfBounds 134 | } 135 | } 136 | 137 | public class ChunkCoordEditControl : ConvenienceNumericUpDown 138 | { 139 | public int GetCoord() 140 | { 141 | return (int)this.Value; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/ColumnConsistinator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace NbtStudio.UI 10 | { 11 | public class ColumnConsistinator 12 | { 13 | public readonly Form Form; 14 | public readonly ListView Control; 15 | private readonly decimal[] ColumnRatios; 16 | private bool Changing = false; 17 | public ColumnConsistinator(Form form, ListView control) 18 | { 19 | Form = form; 20 | Control = control; 21 | 22 | Form.Resize += Form_Resize; 23 | Control.ColumnWidthChanged += Control_ColumnWidthChanged; 24 | ColumnRatios = new decimal[control.Columns.Count]; 25 | DetermineColumnRatios(); 26 | FixColumnRatios(); 27 | } 28 | 29 | private void Control_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e) 30 | { 31 | if (Changing) 32 | return; 33 | Changing = true; 34 | int other_index = e.ColumnIndex == 0 ? 1 : 0; 35 | Control.Columns[other_index].Width = Control.Width - Control.Columns[e.ColumnIndex].Width - 5; 36 | DetermineColumnRatios(); 37 | Changing = false; 38 | } 39 | 40 | private void Form_Resize(object sender, EventArgs e) 41 | { 42 | FixColumnRatios(); 43 | } 44 | 45 | private void DetermineColumnRatios() 46 | { 47 | int total_width = Control.Columns.Cast().Sum(x => x.Width); 48 | for (int i = 0; i < ColumnRatios.Length; i++) 49 | { 50 | ColumnRatios[i] = (decimal)Control.Columns[i].Width / total_width; 51 | } 52 | } 53 | 54 | private void FixColumnRatios() 55 | { 56 | if (Changing) 57 | return; 58 | Changing = true; 59 | for (int i = 0; i < ColumnRatios.Length; i++) 60 | { 61 | Control.Columns[i].Width = (int)(Control.Width * ColumnRatios[i]) - 2; 62 | } 63 | Changing = false; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/ConvenienceControls.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows.Forms; 8 | 9 | namespace NbtStudio.UI 10 | { 11 | public class ConvenienceManager 12 | { 13 | public readonly Control Control; 14 | private Color TrueBackColor; 15 | private bool ChangingBackColor; 16 | private ToolTip Tooltip; 17 | public ConvenienceManager(Control control) 18 | { 19 | Control = control; 20 | Control.BackColorChanged += Control_BackColorChanged; 21 | Control.LostFocus += Control_LostFocus; 22 | Control.TextChanged += Control_TextChanged; 23 | } 24 | 25 | public void SetBackColor(Color color) 26 | { 27 | ChangingBackColor = true; 28 | Control.BackColor = color; 29 | ChangingBackColor = false; 30 | } 31 | 32 | public void HideTooltip() 33 | { 34 | if (Tooltip is not null) 35 | Tooltip.Hide(Control); 36 | } 37 | 38 | public void ShowTooltip(string title, string text, TimeSpan duration) 39 | { 40 | HideTooltip(); 41 | Tooltip = new ToolTip() 42 | { 43 | IsBalloon = false, 44 | UseFading = true, 45 | UseAnimation = true 46 | }; 47 | Tooltip.Show(String.Empty, Control, 0); // a bug apparently 48 | Tooltip.ToolTipTitle = title; 49 | Tooltip.Show(text, Control, (int)duration.TotalMilliseconds); 50 | } 51 | 52 | public void RestoreBackColor() => SetBackColor(TrueBackColor); 53 | 54 | private void Control_BackColorChanged(object sender, EventArgs e) 55 | { 56 | if (!ChangingBackColor) 57 | TrueBackColor = Control.BackColor; 58 | } 59 | 60 | private void Control_TextChanged(object sender, EventArgs e) 61 | { 62 | HideTooltip(); 63 | } 64 | 65 | private void Control_LostFocus(object sender, EventArgs e) 66 | { 67 | HideTooltip(); 68 | } 69 | } 70 | 71 | public class ConvenienceTextBox : TextBox 72 | { 73 | private readonly ConvenienceManager Convenience; 74 | public ConvenienceTextBox() 75 | { 76 | Convenience = new ConvenienceManager(this); 77 | } 78 | 79 | public void SetBackColor(Color color) => Convenience.SetBackColor(color); 80 | public void HideTooltip() => Convenience.HideTooltip(); 81 | public void ShowTooltip(string title, string text, TimeSpan duration) => Convenience.ShowTooltip(title, text, duration); 82 | public void RestoreBackColor() => Convenience.RestoreBackColor(); 83 | } 84 | 85 | public class ConvenienceNumericUpDown : NumericUpDown 86 | { 87 | private readonly ConvenienceManager Convenience; 88 | public ConvenienceNumericUpDown() 89 | { 90 | Convenience = new ConvenienceManager(this); 91 | } 92 | 93 | public void SetBackColor(Color color) => Convenience.SetBackColor(color); 94 | public void HideTooltip() => Convenience.HideTooltip(); 95 | public void ShowTooltip(string title, string text, TimeSpan duration) => Convenience.ShowTooltip(title, text, duration); 96 | public void RestoreBackColor() => Convenience.RestoreBackColor(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/IconSourceButtons.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NbtStudio.UI 2 | { 3 | partial class IconSourceButtons 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.DeleteButton = new System.Windows.Forms.Button(); 32 | this.ConfirmButton = new System.Windows.Forms.Button(); 33 | this.SuspendLayout(); 34 | // 35 | // DeleteButton 36 | // 37 | this.DeleteButton.Dock = System.Windows.Forms.DockStyle.Left; 38 | this.DeleteButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 14F); 39 | this.DeleteButton.Location = new System.Drawing.Point(0, 0); 40 | this.DeleteButton.Margin = new System.Windows.Forms.Padding(5); 41 | this.DeleteButton.Name = "DeleteButton"; 42 | this.DeleteButton.Size = new System.Drawing.Size(41, 46); 43 | this.DeleteButton.TabIndex = 1; 44 | this.DeleteButton.Text = "✖"; 45 | this.DeleteButton.UseVisualStyleBackColor = true; 46 | this.DeleteButton.Click += new System.EventHandler(this.DeleteButton_Click); 47 | // 48 | // ConfirmButton 49 | // 50 | this.ConfirmButton.AutoSize = true; 51 | this.ConfirmButton.Dock = System.Windows.Forms.DockStyle.Fill; 52 | this.ConfirmButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F); 53 | this.ConfirmButton.Location = new System.Drawing.Point(41, 0); 54 | this.ConfirmButton.Margin = new System.Windows.Forms.Padding(0, 5, 5, 5); 55 | this.ConfirmButton.Name = "ConfirmButton"; 56 | this.ConfirmButton.Size = new System.Drawing.Size(258, 46); 57 | this.ConfirmButton.TabIndex = 0; 58 | this.ConfirmButton.UseVisualStyleBackColor = true; 59 | this.ConfirmButton.Click += new System.EventHandler(this.ConfirmButton_Click); 60 | // 61 | // IconSourceButtons 62 | // 63 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 64 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 65 | this.AutoSize = true; 66 | this.BackColor = System.Drawing.Color.Transparent; 67 | this.Controls.Add(this.ConfirmButton); 68 | this.Controls.Add(this.DeleteButton); 69 | this.Name = "IconSourceButtons"; 70 | this.Size = new System.Drawing.Size(299, 46); 71 | this.ResumeLayout(false); 72 | this.PerformLayout(); 73 | 74 | } 75 | 76 | #endregion 77 | 78 | private System.Windows.Forms.Button DeleteButton; 79 | private System.Windows.Forms.Button ConfirmButton; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/IconSourceButtons.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | 11 | namespace NbtStudio.UI 12 | { 13 | public partial class IconSourceButtons : UserControl 14 | { 15 | public event EventHandler DeleteClicked; 16 | public event EventHandler ConfirmClicked; 17 | public IconSourceButtons(IconSource source) 18 | { 19 | InitializeComponent(); 20 | ConfirmButton.Text = source.Name; 21 | DeleteButton.Visible = source is ZippedIconSource; 22 | } 23 | 24 | private void DeleteButton_Click(object sender, EventArgs e) 25 | { 26 | DeleteClicked?.Invoke(this, e); 27 | } 28 | 29 | private void ConfirmButton_Click(object sender, EventArgs e) 30 | { 31 | ConfirmClicked?.Invoke(this, e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/IconSourcePreview.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NbtStudio.UI 2 | { 3 | partial class IconSourcePreview 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Component Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.IconsPanel = new System.Windows.Forms.FlowLayoutPanel(); 32 | this.PreviewButton = new System.Windows.Forms.Button(); 33 | this.IconsPanel.SuspendLayout(); 34 | this.SuspendLayout(); 35 | // 36 | // IconsPanel 37 | // 38 | this.IconsPanel.AutoSize = true; 39 | this.IconsPanel.Controls.Add(this.PreviewButton); 40 | this.IconsPanel.Dock = System.Windows.Forms.DockStyle.Fill; 41 | this.IconsPanel.Location = new System.Drawing.Point(0, 0); 42 | this.IconsPanel.Name = "IconsPanel"; 43 | this.IconsPanel.Size = new System.Drawing.Size(299, 46); 44 | this.IconsPanel.TabIndex = 0; 45 | // 46 | // PreviewButton 47 | // 48 | this.PreviewButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F); 49 | this.PreviewButton.Location = new System.Drawing.Point(3, 3); 50 | this.PreviewButton.Name = "PreviewButton"; 51 | this.PreviewButton.Size = new System.Drawing.Size(40, 40); 52 | this.PreviewButton.TabIndex = 1; 53 | this.PreviewButton.Text = "👁️"; 54 | this.PreviewButton.UseVisualStyleBackColor = true; 55 | this.PreviewButton.Click += new System.EventHandler(this.PreviewButton_Click); 56 | // 57 | // IconSourcePreview 58 | // 59 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 60 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 61 | this.AutoSize = true; 62 | this.BackColor = System.Drawing.Color.Transparent; 63 | this.Controls.Add(this.IconsPanel); 64 | this.Name = "IconSourcePreview"; 65 | this.Size = new System.Drawing.Size(299, 46); 66 | this.IconsPanel.ResumeLayout(false); 67 | this.ResumeLayout(false); 68 | this.PerformLayout(); 69 | 70 | } 71 | 72 | #endregion 73 | 74 | private System.Windows.Forms.FlowLayoutPanel IconsPanel; 75 | private System.Windows.Forms.Button PreviewButton; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/IconSourcePreview.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Drawing; 5 | using System.Data; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Forms; 10 | using System.Drawing.Drawing2D; 11 | 12 | namespace NbtStudio.UI 13 | { 14 | public partial class IconSourcePreview : UserControl 15 | { 16 | private FullIconPreviewWindow FullDisplayForm; 17 | private readonly IconSource Source; 18 | public IconSourcePreview(IconSource source, params IconType[] types) : this(source, true, types) 19 | { } 20 | 21 | public IconSourcePreview(IconSource source, bool show_preview_button, params IconType[] types) 22 | { 23 | Source = source; 24 | InitializeComponent(); 25 | 26 | var boxes = types.Select(x => MakePictureBox(source, x)).ToArray(); 27 | IconsPanel.Controls.AddRange(boxes); 28 | IconsPanel.Controls.Add(PreviewButton); 29 | if (!show_preview_button) 30 | PreviewButton.Visible = false; 31 | } 32 | 33 | private PictureBox MakePictureBox(IconSource source, IconType type) 34 | { 35 | var box = new SpecialPictureBox 36 | { 37 | Height = 32, 38 | Width = 32, 39 | Margin = new Padding(5), 40 | Image = source.GetImage(type).Image, 41 | SizeMode = PictureBoxSizeMode.Zoom, 42 | InterpolationMode = InterpolationMode.NearestNeighbor 43 | }; 44 | if (source is DeferToDefaultIconSource defer && defer.IsDeferring(type)) 45 | box.Enabled = false; 46 | return box; 47 | } 48 | 49 | private void PreviewButton_Click(object sender, EventArgs e) 50 | { 51 | if (FullDisplayForm is null || FullDisplayForm.IsDisposed) 52 | FullDisplayForm = new FullIconPreviewWindow(Source); 53 | if (!FullDisplayForm.Visible) 54 | { 55 | FullDisplayForm.Show(this.ParentForm); 56 | FullDisplayForm.Location = this.PointToScreen(new Point(-20, 0)); 57 | } 58 | else 59 | FullDisplayForm.Close(); 60 | FullDisplayForm.Focus(); 61 | } 62 | } 63 | 64 | public class SpecialPictureBox : PictureBox 65 | { 66 | public InterpolationMode InterpolationMode; 67 | protected override void OnPaint(PaintEventArgs pe) 68 | { 69 | pe.Graphics.InterpolationMode = InterpolationMode; 70 | base.OnPaint(pe); 71 | if (!this.Enabled && this.Image != null) 72 | { 73 | using (var img = new Bitmap(this.Image, this.ClientSize)) 74 | { 75 | ControlPaint.DrawImageDisabled(pe.Graphics, img, 0, 0, this.BackColor); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/TextBoxes/FileNameTextBox.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using Microsoft.VisualBasic.FileIO; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Drawing; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows.Forms; 11 | 12 | namespace NbtStudio.UI 13 | { 14 | public class FileNameTextBox : ConvenienceTextBox 15 | { 16 | private IHavePath Item; 17 | public FileNameTextBox() 18 | { 19 | this.TextChanged += FileNameTextBox_TextChanged; 20 | } 21 | 22 | private void FileNameTextBox_TextChanged(object sender, EventArgs e) 23 | { 24 | SetColor(CheckNameInternal()); 25 | } 26 | 27 | private void SetColor(NameCheckResult result) 28 | { 29 | switch (result) 30 | { 31 | case NameCheckResult.InvalidMissingName: 32 | case NameCheckResult.InvalidWhitespace: 33 | case NameCheckResult.InvalidCharacters: 34 | case NameCheckResult.InvalidAlreadyTaken: 35 | SetBackColor(Color.FromArgb(255, 230, 230)); 36 | break; 37 | case NameCheckResult.Valid: 38 | RestoreBackColor(); 39 | break; 40 | } 41 | } 42 | 43 | private void ShowTooltip(NameCheckResult result) 44 | { 45 | if (result == NameCheckResult.InvalidMissingName || result == NameCheckResult.InvalidWhitespace) 46 | ShowTooltip("Missing Name", "You must specify a name for this file", TimeSpan.FromSeconds(2)); 47 | else if (result == NameCheckResult.InvalidCharacters) 48 | ShowTooltip("Illegal Characters", "The name specified contains disallowed characters", TimeSpan.FromSeconds(2)); 49 | else if (result == NameCheckResult.InvalidAlreadyTaken) 50 | ShowTooltip("File Already Exists", "A file with this name already exists", TimeSpan.FromSeconds(2)); 51 | } 52 | 53 | public void SetItem(IHavePath item) 54 | { 55 | Item = item; 56 | this.Text = Path.GetFileName(item.Path); 57 | } 58 | 59 | public string GetName() 60 | { 61 | return this.Text.Trim(); 62 | } 63 | 64 | private NameCheckResult CheckNameInternal() 65 | { 66 | var name = GetName(); 67 | if (name == "") 68 | return NameCheckResult.InvalidMissingName; 69 | if (String.IsNullOrWhiteSpace(name)) 70 | return NameCheckResult.InvalidWhitespace; 71 | if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) 72 | return NameCheckResult.InvalidCharacters; 73 | string destination = GetDestination(name); 74 | if (name != Path.GetFileName(Item.Path) && (File.Exists(destination) || Directory.Exists(destination))) 75 | return NameCheckResult.InvalidAlreadyTaken; 76 | return NameCheckResult.Valid; 77 | } 78 | 79 | public bool CheckName() 80 | { 81 | var result = CheckNameInternal(); 82 | bool valid = result == NameCheckResult.Valid; 83 | SetColor(result); 84 | if (!valid) 85 | { 86 | ShowTooltip(result); 87 | this.Select(); 88 | } 89 | return valid; 90 | } 91 | 92 | public void PerformRename() 93 | { 94 | var name = GetName(); 95 | if (name == Path.GetFileName(Item.Path)) 96 | return; 97 | Item.Move(GetDestination(name)); 98 | } 99 | 100 | private string GetDestination(string name) 101 | { 102 | return Path.Combine(Path.GetDirectoryName(Item.Path), name); 103 | } 104 | 105 | public enum NameCheckResult 106 | { 107 | Valid, 108 | InvalidMissingName, 109 | InvalidWhitespace, 110 | InvalidCharacters, 111 | InvalidAlreadyTaken 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/TextBoxes/RegexTextBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Text.RegularExpressions; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace NbtStudio.UI 11 | { 12 | public class RegexTextBox : ConvenienceTextBox 13 | { 14 | // cache results of regex parsing, so calling IsMatch repeatedly doesn't pointlessly re-parse 15 | private Regex LastRegex = null; 16 | private bool _RegexMode = false; 17 | public bool RegexMode 18 | { 19 | get => _RegexMode; 20 | set 21 | { 22 | _RegexMode = value; 23 | if (value) 24 | SetColor(CheckRegexInternal(out _)); 25 | else 26 | { 27 | RestoreBackColor(); 28 | HideTooltip(); 29 | } 30 | } 31 | } 32 | public RegexTextBox() 33 | { 34 | this.TextChanged += TagNameTextBox_TextChanged; 35 | } 36 | 37 | private void TagNameTextBox_TextChanged(object sender, EventArgs e) 38 | { 39 | if (RegexMode) 40 | SetColor(CheckRegexInternal(out LastRegex)); 41 | } 42 | 43 | private void SetColor(Exception exception) 44 | { 45 | if (exception is null) 46 | RestoreBackColor(); 47 | else 48 | SetBackColor(Color.FromArgb(255, 230, 230)); 49 | } 50 | 51 | private void ShowTooltip(Exception exception) 52 | { 53 | if (exception is not null) 54 | { 55 | string message = exception.Message; 56 | string redundant = $"\"{this.Text}\" - "; 57 | int index = message.IndexOf(redundant); 58 | if (index != -1) 59 | message = message[(index + redundant.Length)..]; 60 | ShowTooltip("Regex Parsing Error", message, TimeSpan.FromSeconds(3)); 61 | } 62 | } 63 | 64 | public static bool IsMatch(string input, string search) 65 | { 66 | if (search == "") 67 | return true; 68 | if (input is null) 69 | return false; 70 | return input.IndexOf(search, StringComparison.OrdinalIgnoreCase) != -1; 71 | } 72 | 73 | public static bool IsMatchRegex(string input, Regex search) 74 | { 75 | if (search is null) 76 | return false; 77 | if (input is null) 78 | return false; 79 | return search.IsMatch(input); 80 | } 81 | 82 | public bool IsMatch(string input) 83 | { 84 | if (RegexMode) 85 | { 86 | if (LastRegex is null) 87 | CheckRegexInternal(out LastRegex); 88 | return IsMatchRegex(input, LastRegex); 89 | } 90 | else 91 | return IsMatch(input, this.Text); 92 | } 93 | 94 | public Regex ReparseRegex() 95 | { 96 | #if DEBUG 97 | Console.WriteLine($"Parsing new regex: \"{this.Text}\""); 98 | #endif 99 | return new Regex(this.Text, RegexOptions.IgnoreCase); 100 | } 101 | 102 | private Exception CheckRegexInternal(out Regex regex) 103 | { 104 | regex = null; 105 | try 106 | { regex = ReparseRegex(); } 107 | catch (Exception ex) { return ex; } 108 | return null; 109 | } 110 | 111 | public bool CheckRegexQuiet(out Regex regex) 112 | { 113 | if (!RegexMode) 114 | { 115 | regex = null; 116 | return true; 117 | } 118 | var error = CheckRegexInternal(out regex); 119 | SetColor(error); 120 | return error is null; 121 | } 122 | 123 | public bool CheckRegex(out Regex regex) 124 | { 125 | if (!RegexMode) 126 | { 127 | regex = null; 128 | return true; 129 | } 130 | var error = CheckRegexInternal(out regex); 131 | bool valid = error is null; 132 | SetColor(error); 133 | if (!valid) 134 | { 135 | ShowTooltip(error); 136 | this.Select(); 137 | } 138 | return valid; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/TextBoxes/TagNameTextBox.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | 10 | namespace NbtStudio.UI 11 | { 12 | public class TagNameTextBox : ConvenienceTextBox 13 | { 14 | private NbtTag NbtTag; 15 | private NbtContainerTag NbtParent; 16 | public TagNameTextBox() 17 | { 18 | this.TextChanged += TagNameTextBox_TextChanged; 19 | } 20 | 21 | private void TagNameTextBox_TextChanged(object sender, EventArgs e) 22 | { 23 | SetColor(CheckNameInternal(out _)); 24 | } 25 | 26 | private void SetColor(NameCheckResult result) 27 | { 28 | switch (result) 29 | { 30 | case NameCheckResult.InvalidMissingName: 31 | case NameCheckResult.InvalidHasName: 32 | SetBackColor(Color.FromArgb(255, 230, 230)); 33 | break; 34 | case NameCheckResult.InvalidDuplicateName: 35 | SetBackColor(Color.FromArgb(255, 230, 230)); 36 | break; 37 | case NameCheckResult.Valid: 38 | RestoreBackColor(); 39 | break; 40 | } 41 | } 42 | 43 | private void ShowTooltip(NameCheckResult result) 44 | { 45 | if (result == NameCheckResult.InvalidMissingName) 46 | ShowTooltip("Missing Name", "You must specify a name for this tag", TimeSpan.FromSeconds(2)); 47 | else if (result == NameCheckResult.InvalidHasName) 48 | ShowTooltip("Name Disallowed", "This tag must not have a name", TimeSpan.FromSeconds(2)); 49 | else if (result == NameCheckResult.InvalidDuplicateName) 50 | ShowTooltip("Duplicate Name", "The compound already contains a tag with this name", TimeSpan.FromSeconds(2)); 51 | } 52 | 53 | public void SetTags(NbtTag tag, NbtContainerTag parent) 54 | { 55 | NbtTag = tag; 56 | NbtParent = parent; 57 | this.Text = tag?.Name; 58 | } 59 | 60 | public string GetName() 61 | { 62 | return this.Text.Trim(); 63 | } 64 | 65 | private NameCheckResult CheckNameInternal(out string name) 66 | { 67 | name = GetName(); 68 | if (NbtParent is null) 69 | return NameCheckResult.Valid; 70 | if (NbtParent is NbtList) 71 | return name == "" ? NameCheckResult.Valid : NameCheckResult.InvalidHasName; 72 | if (NbtParent is NbtCompound compound) 73 | { 74 | if (name == "") 75 | return NameCheckResult.InvalidMissingName; 76 | if (compound.Contains(name) && !(NbtTag is not null && name == NbtTag.Name)) 77 | return NameCheckResult.InvalidDuplicateName; 78 | } 79 | return NameCheckResult.Valid; 80 | } 81 | 82 | public bool CheckName() => CheckName(out _); 83 | public bool CheckName(out string name) 84 | { 85 | var result = CheckNameInternal(out name); 86 | bool valid = result == NameCheckResult.Valid; 87 | SetColor(result); 88 | if (!valid) 89 | { 90 | ShowTooltip(result); 91 | this.Select(); 92 | } 93 | return valid; 94 | } 95 | 96 | public void ApplyName() 97 | { 98 | var name = GetName(); 99 | if (name == "") 100 | name = null; 101 | if (NbtTag.Name != name) 102 | NbtTag.Name = name; 103 | } 104 | 105 | public enum NameCheckResult 106 | { 107 | Valid, 108 | InvalidMissingName, 109 | InvalidHasName, 110 | InvalidDuplicateName 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/TextBoxes/TagSnbtTextBox.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | using TryashtarUtils.Nbt; 10 | 11 | namespace NbtStudio.UI 12 | { 13 | public class TagSnbtTextBox : ConvenienceTextBox 14 | { 15 | public NbtTagType? RequiredType; 16 | public bool HighlightsErrors = false; // set to true to parse and highlight as you type 17 | public TagSnbtTextBox() 18 | { 19 | this.TextChanged += TagSnbtTextBox_TextChanged; 20 | } 21 | 22 | private void TagSnbtTextBox_TextChanged(object sender, EventArgs e) 23 | { 24 | if (HighlightsErrors) 25 | SetColor(CheckTagInternal(out _)); 26 | else 27 | RestoreBackColor(); 28 | } 29 | 30 | private void SetColor(ISnbtCheckResult result) 31 | { 32 | if (result.IsValid) 33 | RestoreBackColor(); 34 | else 35 | { 36 | if (result is SnbtInvalidFormat) 37 | SetBackColor(Color.FromArgb(255, 230, 230)); 38 | else if (result is SnbtInvalidWrongType) 39 | SetBackColor(Color.FromArgb(255, 230, 230)); 40 | } 41 | } 42 | 43 | private void ShowTooltip(ISnbtCheckResult result) 44 | { 45 | ShowTooltip(result.Title, result.Description, TimeSpan.FromSeconds(3)); 46 | } 47 | 48 | public void SetFromTag(NbtTag tag) 49 | { 50 | this.Text = tag.ToSnbt(SnbtOptions.DefaultExpanded); 51 | } 52 | 53 | public string GetValueText() 54 | { 55 | return this.Text.Trim().Replace("\r", ""); 56 | } 57 | 58 | private NbtTag ParseTag() 59 | { 60 | var text = GetValueText(); 61 | return SnbtParser.Parse(text, named: false); 62 | } 63 | 64 | private ISnbtCheckResult CheckTagInternal(out NbtTag tag) 65 | { 66 | tag = null; 67 | try 68 | { tag = ParseTag(); } 69 | catch (Exception ex) 70 | { return new SnbtInvalidFormat(ex); } 71 | if (RequiredType is not null && tag.TagType != RequiredType.Value) 72 | return new SnbtInvalidWrongType(RequiredType.Value, tag.TagType); 73 | return new SnbtValid(); 74 | } 75 | 76 | public bool CheckTag(out NbtTag tag) 77 | { 78 | var result = CheckTagInternal(out tag); 79 | bool valid = result.IsValid; 80 | SetColor(result); 81 | if (!valid) 82 | { 83 | ShowTooltip(result); 84 | this.Select(); 85 | } 86 | return valid; 87 | } 88 | 89 | public bool TryMinify(bool minified) 90 | { 91 | CheckTag(out var tag); // continue to minify even if the required type is not met 92 | if (tag is not null) 93 | { 94 | var options = SnbtOptions.Default; 95 | if (!minified) 96 | options = options.Expanded(); 97 | this.Text = tag.ToSnbt(options); 98 | return true; 99 | } 100 | return false; 101 | } 102 | 103 | private interface ISnbtCheckResult 104 | { 105 | string Title { get; } 106 | string Description { get; } 107 | bool IsValid { get; } 108 | } 109 | 110 | private class SnbtInvalidFormat : ISnbtCheckResult 111 | { 112 | public string Title => "Invalid Format"; 113 | public string Description { get; private set; } 114 | public bool IsValid => false; 115 | public SnbtInvalidFormat(Exception exception) 116 | { 117 | Description = $"Failed to parse the SNBT:\n{exception.Message}"; 118 | } 119 | } 120 | 121 | private class SnbtInvalidWrongType : ISnbtCheckResult 122 | { 123 | private readonly NbtTagType Required; 124 | private readonly NbtTagType Found; 125 | public SnbtInvalidWrongType(NbtTagType required, NbtTagType found) { Required = required; Found = found; } 126 | public string Title => "Wrong Type"; 127 | public string Description => $"The SNBT must be of type {Required}, not {Found}"; 128 | public bool IsValid => false; 129 | } 130 | 131 | private class SnbtValid : ISnbtCheckResult 132 | { 133 | public string Title => "Valid"; 134 | public string Description => "The SNBT parsed successfully"; 135 | public bool IsValid => true; 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /NbtStudio/UI/Controls/TextBoxes/TagValueTextBox.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | using TryashtarUtils.Nbt; 10 | 11 | namespace NbtStudio.UI 12 | { 13 | public class TagValueTextBox : ConvenienceTextBox 14 | { 15 | private NbtTag NbtTag; 16 | private NbtContainerTag NbtParent; 17 | public TagValueTextBox() 18 | { 19 | this.TextChanged += TagValueTextBox_TextChanged; 20 | } 21 | 22 | private void TagValueTextBox_TextChanged(object sender, EventArgs e) 23 | { 24 | SetColor(CheckValueInternal(out _)); 25 | } 26 | 27 | private void SetColor(ValueCheckResult result) 28 | { 29 | switch (result) 30 | { 31 | case ValueCheckResult.InvalidFormat: 32 | case ValueCheckResult.InvalidOutOfRange: 33 | case ValueCheckResult.InvalidUnknown: 34 | SetBackColor(Color.FromArgb(255, 230, 230)); 35 | break; 36 | case ValueCheckResult.Valid: 37 | RestoreBackColor(); 38 | break; 39 | } 40 | } 41 | 42 | private void ShowTooltip(ValueCheckResult result) 43 | { 44 | if (result == ValueCheckResult.InvalidFormat) 45 | ShowTooltip("Invalid Format", $"The value is formatted incorrectly for a {NbtUtil.TagTypeName(NbtTag.TagType).ToLower()}", TimeSpan.FromSeconds(2)); 46 | else if (result == ValueCheckResult.InvalidOutOfRange) 47 | { 48 | var (min, max) = NbtUtil.MinMaxFor(NbtTag.TagType); 49 | ShowTooltip("Out of Range", $"The value for {NbtUtil.TagTypeName(NbtTag.TagType).ToLower()}s must be between {min} and {max}", TimeSpan.FromSeconds(4)); 50 | } 51 | else if (result == ValueCheckResult.InvalidUnknown) 52 | ShowTooltip("Unknown Error", "There was an unknown error attempting to parse the value", TimeSpan.FromSeconds(2)); 53 | } 54 | 55 | public void SetTags(NbtTag tag, NbtContainerTag parent, bool fill_current_value) 56 | { 57 | NbtTag = tag; 58 | NbtParent = parent; 59 | if (fill_current_value) 60 | this.Text = tag.ToSnbt(SnbtOptions.MultilinePreview); 61 | } 62 | 63 | public string GetValueText() 64 | { 65 | return this.Text.Trim().Replace("\r", ""); 66 | } 67 | 68 | private object GetValue() 69 | { 70 | var text = GetValueText(); 71 | if (text == "") 72 | return null; 73 | return NbtUtil.ParseValue(text, NbtTag.TagType); 74 | } 75 | 76 | private ValueCheckResult CheckValueInternal(out object value) 77 | { 78 | value = null; 79 | try 80 | { value = GetValue(); } 81 | catch (FormatException) 82 | { return ValueCheckResult.InvalidFormat; } 83 | catch (OverflowException) 84 | { return ValueCheckResult.InvalidOutOfRange; } 85 | catch 86 | { return ValueCheckResult.InvalidUnknown; } 87 | return ValueCheckResult.Valid; 88 | } 89 | 90 | public bool CheckValue(out object value) 91 | { 92 | var result = CheckValueInternal(out value); 93 | bool valid = result == ValueCheckResult.Valid; 94 | SetColor(result); 95 | if (!valid) 96 | { 97 | ShowTooltip(result); 98 | this.Select(); 99 | } 100 | return valid; 101 | } 102 | 103 | public void ApplyValue() 104 | { 105 | CheckValueInternal(out var value); 106 | ApplyValue(value); 107 | } 108 | 109 | public void ApplyValue(object value) 110 | { 111 | if (value is null) 112 | NbtUtil.ResetValue(NbtTag); 113 | else 114 | { 115 | var current = NbtUtil.GetValue(NbtTag); 116 | if (!current.Equals(value)) 117 | NbtUtil.SetValue(NbtTag, value); 118 | } 119 | } 120 | 121 | public enum ValueCheckResult 122 | { 123 | Valid, 124 | InvalidFormat, 125 | InvalidOutOfRange, 126 | InvalidUnknown 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /NbtStudio/UI/MainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/AboutWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Windows.Forms; 6 | using TryashtarUtils.Utility; 7 | 8 | namespace NbtStudio.UI 9 | { 10 | public partial class AboutWindow : Form 11 | { 12 | public AboutWindow(IconSource source) 13 | { 14 | InitializeComponent(); 15 | this.Icon = source.GetImage(IconType.NbtStudio).Icon; 16 | NameLabel.Text = String.Format(NameLabel.Text, Updater.GetCurrentVersion().ToString(false)); 17 | } 18 | 19 | private void AboutWindow_Load(object sender, EventArgs e) 20 | { 21 | this.CenterToParent(); 22 | } 23 | 24 | protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 25 | { 26 | if (keyData == Keys.Escape) 27 | { 28 | this.Close(); 29 | return true; 30 | } 31 | return base.ProcessCmdKey(ref msg, keyData); 32 | } 33 | 34 | private void NameLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 35 | { 36 | IOUtils.OpenUrlInBrowser("https://github.com/tryashtar/nbt-studio"); 37 | } 38 | 39 | private void NbtExplorerLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 40 | { 41 | IOUtils.OpenUrlInBrowser("https://github.com/jaquadro/NBTExplorer"); 42 | } 43 | 44 | private void GenericIconLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 45 | { 46 | IOUtils.OpenUrlInBrowser("https://p.yusukekamiyamane.com"); 47 | } 48 | 49 | private void NbtIconLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 50 | { 51 | IOUtils.OpenUrlInBrowser("https://github.com/AmberWat"); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/BulkEditWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/EditChunkWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Windows.Forms; 7 | 8 | namespace NbtStudio.UI 9 | { 10 | public partial class EditChunkWindow : Form 11 | { 12 | private readonly Chunk WorkingChunk; 13 | private readonly RegionFile ChunkRegion; 14 | private readonly ChunkCoordsEditControls Manager; 15 | 16 | private EditChunkWindow(IconSource source, Chunk chunk, RegionFile region, ChunkEditPurpose purpose) 17 | { 18 | InitializeComponent(); 19 | 20 | WorkingChunk = chunk; 21 | ChunkRegion = region; 22 | Manager = new ChunkCoordsEditControls(chunk, region, XBox, ZBox); 23 | 24 | this.Icon = source.GetImage(IconType.Chunk).Icon; 25 | if (purpose == ChunkEditPurpose.Create) 26 | this.Text = $"Create Chunk"; 27 | else if (purpose == ChunkEditPurpose.Move) 28 | this.Text = $"Move Chunk"; 29 | 30 | XBox.Select(); 31 | } 32 | 33 | public static Chunk CreateChunk(IconSource source, RegionFile parent, bool bypass_window = false, NbtCompound data = null) 34 | { 35 | var chunk = Chunk.EmptyChunk(data); 36 | 37 | if (bypass_window) 38 | { 39 | // find first available slot 40 | var available = parent.GetAvailableCoords(); 41 | if (!available.Any()) 42 | return null; 43 | var (x, y) = available.First(); 44 | chunk.Move(x, y); 45 | return chunk; 46 | } 47 | else 48 | { 49 | var window = new EditChunkWindow(source, chunk, parent, ChunkEditPurpose.Create); 50 | return window.ShowDialog() == DialogResult.OK ? chunk : null; 51 | } 52 | } 53 | 54 | public static bool MoveChunk(IconSource source, Chunk existing) 55 | { 56 | var region = existing.Region; 57 | var window = new EditChunkWindow(source, existing, region, ChunkEditPurpose.Move); 58 | return window.ShowDialog() == DialogResult.OK; // window moves the chunk by itself 59 | } 60 | 61 | private void Confirm() 62 | { 63 | if (TryModify()) 64 | { 65 | DialogResult = DialogResult.OK; 66 | Close(); 67 | } 68 | } 69 | 70 | private bool TryModify() 71 | { 72 | if (!Manager.CheckCoords()) 73 | return false; 74 | Manager.ApplyCoords(); 75 | return true; 76 | } 77 | 78 | private void ButtonOk_Click(object sender, EventArgs e) 79 | { 80 | Confirm(); 81 | } 82 | } 83 | 84 | public enum ChunkEditPurpose 85 | { 86 | Create, 87 | Move 88 | } 89 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/EditSnbtWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/EditTagWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/ExceptionWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Windows.Forms; 7 | using TryashtarUtils.Utility; 8 | 9 | namespace NbtStudio.UI 10 | { 11 | public partial class ExceptionWindow : Form 12 | { 13 | public readonly IFailable Error; 14 | public bool Expanded { get; private set; } = false; 15 | public ExceptionWindow(string title, string message, IFailable failable, string after = null, ExceptionWindowButtons buttons = ExceptionWindowButtons.OK) 16 | { 17 | InitializeComponent(); 18 | Error = failable; 19 | MessageLabel.Text = message + "\n\n" + failable.ToStringSimple() + (after != null ? "\n\n" + after : ""); 20 | ExtraInfoLabel.Text = failable.ToStringDetailed(); 21 | Text = title; 22 | this.Icon = SystemIcons.Warning; 23 | if (buttons == ExceptionWindowButtons.OKCancel) 24 | { 25 | ButtonCancel.Visible = true; 26 | this.Width += ButtonCancel.Width; 27 | } 28 | } 29 | 30 | private void ExceptionWindow_Load(object sender, EventArgs e) 31 | { 32 | CenterToParent(); 33 | } 34 | 35 | private void OKButton_Click(object sender, EventArgs e) 36 | { 37 | this.DialogResult = DialogResult.OK; 38 | Close(); 39 | } 40 | 41 | private void ButtonCancel_Click(object sender, EventArgs e) 42 | { 43 | this.DialogResult = DialogResult.Cancel; 44 | Close(); 45 | } 46 | 47 | private void ButtonDetails_Click(object sender, EventArgs e) 48 | { 49 | SetExpanded(!Expanded); 50 | } 51 | 52 | public void SetExpanded(bool expanded) 53 | { 54 | Expanded = expanded; 55 | if (expanded) 56 | { 57 | ExtraInfoPanel.Height = 300; 58 | this.Width += 130 + ButtonCopy.Width; 59 | ExtraInfoPanel.Visible = true; 60 | ButtonDetails.Text = "Less Details"; 61 | ButtonCopy.Visible = true; 62 | } 63 | else 64 | { 65 | ExtraInfoPanel.Visible = false; 66 | ExtraInfoPanel.Height = 300; 67 | this.Height -= ExtraInfoPanel.Height; 68 | this.Width -= 130 + ButtonCopy.Width; 69 | ButtonDetails.Text = "More Details"; 70 | ButtonCopy.Visible = false; 71 | } 72 | } 73 | 74 | private void ButtonCopy_Click(object sender, EventArgs e) 75 | { 76 | Clipboard.SetText(MessageLabel.Text + "\n\n" + ExtraInfoLabel.Text); 77 | } 78 | } 79 | 80 | public enum ExceptionWindowButtons 81 | { 82 | OK, 83 | OKCancel 84 | } 85 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/ExceptionWindow.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | text/microsoft-resx 50 | 51 | 52 | 2.0 53 | 54 | 55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 56 | 57 | 58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 59 | 60 | -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/ExportWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using NbtStudio; 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Windows.Forms; 7 | 8 | namespace NbtStudio.UI 9 | { 10 | public partial class ExportWindow : Form 11 | { 12 | public ExportWindow(IconSource source, ExportSettings template, string destination_path) 13 | { 14 | InitializeComponent(); 15 | this.Icon = source.GetImage(IconType.Save).Icon; 16 | CompressionBox.Items.Add(new CompressionDisplay("Uncompressed", NbtCompression.None)); 17 | CompressionBox.Items.Add(new CompressionDisplay("G-Zip", NbtCompression.GZip)); 18 | CompressionBox.Items.Add(new CompressionDisplay("ZLib", NbtCompression.ZLib)); 19 | CompressionBox.SelectedIndex = 0; 20 | if (template is not null) 21 | { 22 | RadioSnbt.Checked = template.Snbt; 23 | RadioNbt.Checked = !template.Snbt; 24 | CompressionBox.SelectedItem = CompressionBox.Items.Cast().FirstOrDefault(x => x.Compression == template.Compression); 25 | CheckMinify.Checked = template.Minified; 26 | CheckJson.Checked = template.Json; 27 | CheckLittleEndian.Checked = !template.BigEndian; 28 | CheckBedrockHeader.Checked = template.BedrockHeader; 29 | } 30 | else 31 | { 32 | string extension = Path.GetExtension(destination_path); 33 | bool? binary = NbtUtil.BinaryExtension(extension); 34 | RadioSnbt.Checked = binary == false; 35 | RadioNbt.Checked = binary == true; 36 | CheckLittleEndian.Checked = extension == ".mcstructure"; 37 | CheckJson.Checked = extension == ".json"; 38 | } 39 | SetEnables(); 40 | Tooltips.SetToolTip(CheckLittleEndian, "Required for all Bedrock Edition files"); 41 | Tooltips.SetToolTip(CheckBedrockHeader, "Required for Bedrock Edition level.dat files"); 42 | Tooltips.SetToolTip(CheckJson, "Quotes all keys, removes type suffixes and list indicators"); 43 | } 44 | 45 | public ExportSettings GetSettings() 46 | { 47 | if (RadioSnbt.Checked) 48 | return ExportSettings.AsSnbt(CheckMinify.Checked, CheckJson.Checked); 49 | else 50 | return ExportSettings.AsNbt(((CompressionDisplay)CompressionBox.SelectedItem).Compression, !CheckLittleEndian.Checked, CheckBedrockHeader.Checked); 51 | } 52 | 53 | private void Apply() 54 | { 55 | DialogResult = DialogResult.OK; 56 | Close(); 57 | } 58 | 59 | private void ButtonOk_Click(object sender, EventArgs e) 60 | { 61 | Apply(); 62 | } 63 | 64 | private void RadioNbt_CheckedChanged(object sender, EventArgs e) 65 | { 66 | SetEnables(); 67 | } 68 | 69 | private void SetEnables() 70 | { 71 | CompressionBox.Enabled = RadioNbt.Checked; 72 | CheckLittleEndian.Enabled = RadioNbt.Checked; 73 | CheckBedrockHeader.Enabled = RadioNbt.Checked; 74 | CheckMinify.Enabled = RadioSnbt.Checked; 75 | CheckJson.Enabled = RadioSnbt.Checked; 76 | ButtonOk.Enabled = RadioNbt.Checked || RadioSnbt.Checked; 77 | } 78 | 79 | private class CompressionDisplay 80 | { 81 | public readonly string Name; 82 | public readonly NbtCompression Compression; 83 | public CompressionDisplay(string name, NbtCompression compression) 84 | { 85 | Name = name; 86 | Compression = compression; 87 | } 88 | 89 | public override string ToString() 90 | { 91 | return Name; 92 | } 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/FullIconPreviewWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NbtStudio.UI 2 | { 3 | partial class FullIconPreviewWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.SuspendLayout(); 32 | // 33 | // FullIconPreviewWindow 34 | // 35 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 37 | this.AutoSize = true; 38 | this.ClientSize = new System.Drawing.Size(159, 167); 39 | this.ControlBox = false; 40 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 41 | this.MaximizeBox = false; 42 | this.MinimizeBox = false; 43 | this.Name = "FullIconPreviewWindow"; 44 | this.ShowInTaskbar = false; 45 | this.ResumeLayout(false); 46 | 47 | } 48 | 49 | #endregion 50 | } 51 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/FullIconPreviewWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Drawing.Drawing2D; 7 | using System.Linq; 8 | using System.Windows.Forms; 9 | 10 | namespace NbtStudio.UI 11 | { 12 | public partial class FullIconPreviewWindow : Form 13 | { 14 | public FullIconPreviewWindow(IconSource source) 15 | { 16 | InitializeComponent(); 17 | var preview = new IconSourcePreview(source, false, (IconType[])Enum.GetValues(typeof(IconType))); 18 | preview.MaximumSize = new Size(470, 0); 19 | preview.Dock = DockStyle.Fill; 20 | Controls.Add(preview); 21 | this.Text = source.Name + " Icon Set"; 22 | this.Icon = source.GetImage(IconType.Search).Icon; 23 | } 24 | 25 | protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 26 | { 27 | if (keyData == Keys.Escape) 28 | { 29 | this.Close(); 30 | return true; 31 | } 32 | return base.ProcessCmdKey(ref msg, keyData); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/IconSetWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NbtStudio.UI 2 | { 3 | partial class IconSetWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.IconTable = new System.Windows.Forms.TableLayoutPanel(); 32 | this.ButtonsPanel = new System.Windows.Forms.Panel(); 33 | this.ImportButton = new System.Windows.Forms.Button(); 34 | this.ButtonsPanel.SuspendLayout(); 35 | this.SuspendLayout(); 36 | // 37 | // IconTable 38 | // 39 | this.IconTable.AutoSize = true; 40 | this.IconTable.ColumnCount = 2; 41 | this.IconTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80F)); 42 | this.IconTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); 43 | this.IconTable.Dock = System.Windows.Forms.DockStyle.Fill; 44 | this.IconTable.Location = new System.Drawing.Point(0, 39); 45 | this.IconTable.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); 46 | this.IconTable.Name = "IconTable"; 47 | this.IconTable.RowCount = 1; 48 | this.IconTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); 49 | this.IconTable.Size = new System.Drawing.Size(283, 50); 50 | this.IconTable.TabIndex = 2; 51 | this.IconTable.CellPaint += new System.Windows.Forms.TableLayoutCellPaintEventHandler(this.IconTable_CellPaint); 52 | // 53 | // ButtonsPanel 54 | // 55 | this.ButtonsPanel.BackColor = System.Drawing.SystemColors.ControlLight; 56 | this.ButtonsPanel.Controls.Add(this.ImportButton); 57 | this.ButtonsPanel.Dock = System.Windows.Forms.DockStyle.Top; 58 | this.ButtonsPanel.Location = new System.Drawing.Point(0, 0); 59 | this.ButtonsPanel.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); 60 | this.ButtonsPanel.Name = "ButtonsPanel"; 61 | this.ButtonsPanel.Size = new System.Drawing.Size(283, 39); 62 | this.ButtonsPanel.TabIndex = 0; 63 | // 64 | // ImportButton 65 | // 66 | this.ImportButton.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); 67 | this.ImportButton.Location = new System.Drawing.Point(4, 3); 68 | this.ImportButton.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); 69 | this.ImportButton.Name = "ImportButton"; 70 | this.ImportButton.Size = new System.Drawing.Size(63, 32); 71 | this.ImportButton.TabIndex = 1; 72 | this.ImportButton.Text = "Import"; 73 | this.ImportButton.UseVisualStyleBackColor = true; 74 | this.ImportButton.Click += new System.EventHandler(this.ImportButton_Click); 75 | // 76 | // IconSetWindow 77 | // 78 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 79 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 80 | this.AutoSize = true; 81 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 82 | this.ClientSize = new System.Drawing.Size(283, 89); 83 | this.Controls.Add(this.IconTable); 84 | this.Controls.Add(this.ButtonsPanel); 85 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 86 | this.MaximizeBox = false; 87 | this.MinimizeBox = false; 88 | this.Name = "IconSetWindow"; 89 | this.ShowInTaskbar = false; 90 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 91 | this.Text = "Select Icon Set"; 92 | this.Load += new System.EventHandler(this.IconSetWindow_Load); 93 | this.ButtonsPanel.ResumeLayout(false); 94 | this.ResumeLayout(false); 95 | this.PerformLayout(); 96 | 97 | } 98 | 99 | #endregion 100 | 101 | private System.Windows.Forms.TableLayoutPanel IconTable; 102 | private System.Windows.Forms.Panel ButtonsPanel; 103 | private System.Windows.Forms.Button ImportButton; 104 | } 105 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/IconSetWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Drawing.Drawing2D; 7 | using System.Linq; 8 | using System.Windows.Forms; 9 | using TryashtarUtils.Utility; 10 | 11 | namespace NbtStudio.UI 12 | { 13 | public partial class IconSetWindow : Form 14 | { 15 | private int SelectedRow = 0; 16 | private readonly IconSource CurrentSource; 17 | public IconSource SelectedSource { get; private set; } 18 | public IconSetWindow(IconSource current) 19 | { 20 | InitializeComponent(); 21 | CurrentSource = current; 22 | this.Icon = current.GetImage(IconType.Refresh).Icon; 23 | RefreshIcons(); 24 | } 25 | 26 | public void RefreshIcons() 27 | { 28 | SuspendLayout(); 29 | Action select = () => { }; 30 | int row = 0; 31 | IconTable.Controls.Clear(); 32 | IconTable.RowStyles.Clear(); 33 | foreach (var item in IconSourceRegistry.RegisteredSources) 34 | { 35 | var source = item.Value; 36 | var buttons = new IconSourceButtons(source); 37 | buttons.Dock = DockStyle.Fill; 38 | IconTable.RowStyles.Add(new RowStyle(SizeType.Absolute, buttons.Height)); 39 | IconTable.Controls.Add(buttons, 0, row); 40 | IconTable.ColumnStyles[0].Width = Math.Max(IconTable.ColumnStyles[0].Width, buttons.PreferredSize.Width + 5); 41 | buttons.ConfirmClicked += (s, e) => 42 | { 43 | SelectedSource = source; 44 | this.Close(); 45 | }; 46 | buttons.DeleteClicked += (s, e) => 47 | { 48 | IconSourceRegistry.Unregister(item.Key); 49 | Properties.Settings.Default.CustomIconSets.Remove(item.Key); 50 | RefreshIcons(); 51 | }; 52 | var preview = new IconSourcePreview(source, 53 | IconType.OpenFile, 54 | IconType.Save, 55 | IconType.Edit, 56 | IconType.Cut, 57 | IconType.Undo, 58 | IconType.ByteTag, 59 | IconType.StringTag, 60 | IconType.IntArrayTag, 61 | IconType.ListTag, 62 | IconType.Region, 63 | IconType.Chunk 64 | ); 65 | preview.Dock = DockStyle.Fill; 66 | IconTable.Controls.Add(preview, 1, row); 67 | IconTable.RowStyles[row].Height = Math.Max(IconTable.RowStyles[row].Height, preview.PreferredSize.Height + 5); 68 | if (CurrentSource == source) 69 | { 70 | SelectedRow = row; 71 | buttons.BackColor = Color.FromArgb(201, 255, 221); 72 | preview.BackColor = Color.FromArgb(201, 255, 221); 73 | select = () => buttons.Select(); 74 | } 75 | row++; 76 | } 77 | select(); 78 | ResumeLayout(); 79 | } 80 | 81 | private void IconTable_CellPaint(object sender, TableLayoutCellPaintEventArgs e) 82 | { 83 | if (e.Row == SelectedRow) 84 | e.Graphics.FillRectangle(new SolidBrush(Color.FromArgb(201, 255, 221)), e.CellBounds); 85 | } 86 | 87 | private void IconSetWindow_Load(object sender, EventArgs e) 88 | { 89 | this.CenterToParent(); 90 | } 91 | 92 | protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 93 | { 94 | if (keyData == Keys.Escape) 95 | { 96 | this.Close(); 97 | return true; 98 | } 99 | return base.ProcessCmdKey(ref msg, keyData); 100 | } 101 | 102 | private void ImportButton_Click(object sender, EventArgs e) 103 | { 104 | using (var dialog = new OpenFileDialog 105 | { 106 | Title = "Select a custom icon ZIP file", 107 | RestoreDirectory = false, 108 | Multiselect = true, 109 | Filter = "ZIP Files|*.zip" 110 | }) 111 | { 112 | if (dialog.ShowDialog() == DialogResult.OK) 113 | { 114 | foreach (var file in dialog.FileNames) 115 | { 116 | var attempt = TryImportSource(file); 117 | if (attempt.Failed) 118 | ShowImportFailed(file, attempt, this); 119 | } 120 | RefreshIcons(); 121 | } 122 | } 123 | } 124 | 125 | public static void ShowImportFailed(string path, IFailable import, IWin32Window owner) 126 | { 127 | var window = new ExceptionWindow("Failed to load icons", $"The custom icon source at '{path}' failed to load.", import); 128 | window.ShowDialog(owner); 129 | } 130 | 131 | public static IFailable TryImportSource(string path) 132 | { 133 | var failable = new Failable(() => 134 | { 135 | IconSourceRegistry.RegisterCustomSource(path); 136 | if (!Properties.Settings.Default.CustomIconSets.Contains(path)) 137 | Properties.Settings.Default.CustomIconSets.Add(path); 138 | }, "Loading icons"); 139 | if (failable.Failed) 140 | Properties.Settings.Default.CustomIconSets.Remove(path); 141 | return failable; 142 | } 143 | } 144 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/RenameFileWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace NbtStudio.UI 2 | { 3 | partial class RenameFileWindow 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.NameBox = new NbtStudio.UI.FileNameTextBox(); 32 | this.ButtonCancel = new System.Windows.Forms.Button(); 33 | this.ButtonOk = new System.Windows.Forms.Button(); 34 | this.SuspendLayout(); 35 | // 36 | // NameBox 37 | // 38 | this.NameBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.Append; 39 | this.NameBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.CustomSource; 40 | this.NameBox.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); 41 | this.NameBox.Location = new System.Drawing.Point(14, 19); 42 | this.NameBox.Margin = new System.Windows.Forms.Padding(5, 10, 10, 0); 43 | this.NameBox.Name = "NameBox"; 44 | this.NameBox.Size = new System.Drawing.Size(260, 21); 45 | this.NameBox.TabIndex = 0; 46 | // 47 | // ButtonCancel 48 | // 49 | this.ButtonCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 50 | this.ButtonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 51 | this.ButtonCancel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); 52 | this.ButtonCancel.Location = new System.Drawing.Point(199, 51); 53 | this.ButtonCancel.Name = "ButtonCancel"; 54 | this.ButtonCancel.Size = new System.Drawing.Size(75, 23); 55 | this.ButtonCancel.TabIndex = 2; 56 | this.ButtonCancel.Text = "Cancel"; 57 | this.ButtonCancel.UseVisualStyleBackColor = true; 58 | // 59 | // ButtonOk 60 | // 61 | this.ButtonOk.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 62 | this.ButtonOk.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F); 63 | this.ButtonOk.Location = new System.Drawing.Point(118, 51); 64 | this.ButtonOk.Name = "ButtonOk"; 65 | this.ButtonOk.Size = new System.Drawing.Size(75, 23); 66 | this.ButtonOk.TabIndex = 1; 67 | this.ButtonOk.Text = "OK"; 68 | this.ButtonOk.UseVisualStyleBackColor = true; 69 | this.ButtonOk.Click += new System.EventHandler(this.ButtonOk_Click); 70 | // 71 | // RenameFileWindow 72 | // 73 | this.AcceptButton = this.ButtonOk; 74 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 75 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 76 | this.AutoSize = true; 77 | this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; 78 | this.CancelButton = this.ButtonCancel; 79 | this.ClientSize = new System.Drawing.Size(287, 86); 80 | this.Controls.Add(this.ButtonOk); 81 | this.Controls.Add(this.ButtonCancel); 82 | this.Controls.Add(this.NameBox); 83 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 84 | this.MaximizeBox = false; 85 | this.MinimizeBox = false; 86 | this.Name = "RenameFileWindow"; 87 | this.ShowInTaskbar = false; 88 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 89 | this.Text = "Rename File"; 90 | this.Load += new System.EventHandler(this.RenameFileWindow_Load); 91 | this.ResumeLayout(false); 92 | this.PerformLayout(); 93 | 94 | } 95 | 96 | #endregion 97 | private FileNameTextBox NameBox; 98 | private System.Windows.Forms.Button ButtonCancel; 99 | private System.Windows.Forms.Button ButtonOk; 100 | } 101 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/RenameFileWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Windows.Forms; 6 | 7 | namespace NbtStudio.UI 8 | { 9 | public partial class RenameFileWindow : Form 10 | { 11 | private readonly IHavePath OriginalItem; 12 | 13 | private RenameFileWindow(IconSource source, IHavePath file) 14 | { 15 | InitializeComponent(); 16 | 17 | OriginalItem = file; 18 | this.Icon = source.GetImage(IconType.Rename).Icon; 19 | NameBox.SetItem(file); 20 | NameBox.SelectAll(); 21 | } 22 | 23 | public static bool RenameFile(IconSource source, IHavePath file) 24 | { 25 | var window = new RenameFileWindow(source, file); 26 | return window.ShowDialog() == DialogResult.OK; 27 | } 28 | 29 | private void Confirm() 30 | { 31 | if (TryModify()) 32 | { 33 | DialogResult = DialogResult.OK; 34 | Close(); 35 | } 36 | } 37 | 38 | private bool TryModify() 39 | { 40 | if (!NameBox.CheckName()) 41 | return false; 42 | NameBox.PerformRename(); 43 | return true; 44 | } 45 | 46 | private void ButtonOk_Click(object sender, EventArgs e) 47 | { 48 | Confirm(); 49 | } 50 | 51 | private void RenameFileWindow_Load(object sender, EventArgs e) 52 | { 53 | CenterToParent(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /NbtStudio/UI/Windows/UpdateWindow.cs: -------------------------------------------------------------------------------- 1 | using fNbt; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Drawing; 6 | using System.Windows.Forms; 7 | using TryashtarUtils.Utility; 8 | 9 | namespace NbtStudio.UI 10 | { 11 | public partial class UpdateWindow : Form 12 | { 13 | private readonly AvailableUpdate AvailableUpdate; 14 | 15 | public UpdateWindow(IconSource source, AvailableUpdate update) 16 | { 17 | InitializeComponent(); 18 | 19 | AvailableUpdate = update; 20 | this.Icon = source.GetImage(IconType.NbtStudio).Icon; 21 | CurrentVersionValue.Text = Updater.GetCurrentVersion().ToString(false); 22 | AvailableVersionValue.Text = update.Version.ToString(false); 23 | ChangelogBox.Text = update.Changelog; 24 | ButtonOk.Select(); 25 | } 26 | 27 | private void Confirm() 28 | { 29 | if (TryUpdate()) 30 | { 31 | DialogResult = DialogResult.OK; 32 | Close(); 33 | } 34 | } 35 | 36 | private bool TryUpdate() 37 | { 38 | try 39 | { 40 | var path = Application.ExecutablePath; 41 | AvailableUpdate.Update(); 42 | Process.Start(path); 43 | Application.Exit(); 44 | return true; 45 | } 46 | catch (Exception ex) 47 | { 48 | var window = new ExceptionWindow("Update error", "Failed to update the application.", FailableFactory.Failure(ex, "Updating")); 49 | window.ShowDialog(this); 50 | return false; 51 | } 52 | } 53 | 54 | private void ButtonOk_Click(object sender, EventArgs e) 55 | { 56 | Confirm(); 57 | } 58 | 59 | private void ButtonCancel_Click(object sender, EventArgs e) 60 | { 61 | this.Close(); 62 | } 63 | 64 | private void UpdateWindow_Load(object sender, EventArgs e) 65 | { 66 | CenterToParent(); 67 | } 68 | 69 | protected override bool ProcessCmdKey(ref Message msg, Keys keyData) 70 | { 71 | if (keyData == Keys.Escape) 72 | { 73 | this.Close(); 74 | return true; 75 | } 76 | return base.ProcessCmdKey(ref msg, keyData); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /NbtStudio/nbt_studio_icon_256.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tryashtar/nbt-studio/aacd4dcfc55d82162d154456710bc76eb281b8cd/NbtStudio/nbt_studio_icon_256.ico -------------------------------------------------------------------------------- /NbtStudio/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NbtStudioTests/DragDrop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using fNbt; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using NbtStudio; 7 | 8 | namespace NbtStudioTests 9 | { 10 | [TestClass] 11 | public class DragDrop 12 | { 13 | [TestMethod] 14 | public void MoveEarlier() 15 | { 16 | var compound = new NbtCompound 17 | { 18 | new NbtByte("a", 0), 19 | new NbtByte("b", 1), 20 | new NbtByte("c", 2), 21 | new NbtByte("d", 3), 22 | new NbtByte("e", 4), 23 | new NbtByte("f", 5), 24 | new NbtByte("g", 6), 25 | }; 26 | var tags = compound.Tags.ToList(); 27 | var moving = new List { tags[1], tags[3], tags[5] }; 28 | var correct_order = new List { /**/ tags[1], tags[3], tags[5], /**/ tags[0], tags[2], tags[4], tags[6] }; 29 | 30 | NbtUtil.TransformInsert(moving, compound, 0); 31 | 32 | Assert.IsTrue(compound.Tags.SequenceEqual(correct_order)); 33 | } 34 | 35 | [TestMethod] 36 | public void MoveBetween() 37 | { 38 | var compound = new NbtCompound 39 | { 40 | new NbtByte("a", 0), 41 | new NbtByte("b", 1), 42 | new NbtByte("c", 2), 43 | new NbtByte("d", 3), 44 | new NbtByte("e", 4), 45 | new NbtByte("f", 5), 46 | new NbtByte("g", 6), 47 | }; 48 | var tags = compound.Tags.ToList(); 49 | var moving = new List { tags[1], tags[3], tags[5] }; 50 | var correct_order = new List { tags[0], tags[2], /**/ tags[1], tags[3], tags[5], /**/ tags[4], tags[6] }; 51 | 52 | NbtUtil.TransformInsert(moving, compound, 3); 53 | 54 | Assert.IsTrue(compound.Tags.SequenceEqual(correct_order)); 55 | } 56 | 57 | [TestMethod] 58 | public void MoveAfter() 59 | { 60 | var compound = new NbtCompound 61 | { 62 | new NbtByte("a", 0), 63 | new NbtByte("b", 1), 64 | new NbtByte("c", 2), 65 | new NbtByte("d", 3), 66 | new NbtByte("e", 4), 67 | new NbtByte("f", 5), 68 | new NbtByte("g", 6), 69 | }; 70 | var tags = compound.Tags.ToList(); 71 | var moving = new List { tags[1], tags[3], tags[5] }; 72 | var correct_order = new List { tags[0], tags[2], tags[4], /**/ tags[1], tags[3], tags[5], /**/ tags[6] }; 73 | 74 | NbtUtil.TransformInsert(moving, compound, 6); 75 | 76 | Assert.IsTrue(compound.Tags.SequenceEqual(correct_order)); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /NbtStudioTests/ModelNodes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Aga.Controls.Tree; 5 | using fNbt; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using NbtStudio; 8 | using NbtStudio.UI; 9 | 10 | namespace NbtStudioTests 11 | { 12 | [TestClass] 13 | public class ModelNodes 14 | { 15 | [TestMethod] 16 | public void Parenting() 17 | { 18 | var compound = new NbtCompound("root") 19 | { 20 | new NbtByte("sub1"), 21 | new NbtShort("sub2"), 22 | new NbtCompound("sub3") 23 | { 24 | new NbtString("grandchild", "") 25 | } 26 | }; 27 | var root = new NbtTagNode(new NbtTreeModel(), null, compound); 28 | AssertChildStructure(root); 29 | } 30 | 31 | [TestMethod] 32 | public void ChildrenRefresh() 33 | { 34 | var compound = new NbtCompound("root") 35 | { 36 | new NbtByte("sub1"), 37 | new NbtShort("sub2"), 38 | new NbtCompound("sub3") 39 | { 40 | new NbtString("grandchild", "") 41 | } 42 | }; 43 | var root = new NbtTagNode(new NbtTreeModel(), null, compound); 44 | Assert.AreEqual(root.Children.Count(), 3); 45 | compound.Add(new NbtInt("more1")); 46 | compound.Add(new NbtInt("more2")); 47 | compound.Add(new NbtInt("more3")); 48 | Assert.AreEqual(root.Children.Count(), 6); 49 | compound.Remove("more1"); 50 | Assert.AreEqual(root.Children.Count(), 5); 51 | } 52 | 53 | [TestMethod] 54 | public void CheckSynchronized() 55 | { 56 | var root = new NbtCompound("test"); 57 | var view = new NbtTreeView(); 58 | var model = new NbtTreeModel((object)root); 59 | view.Model = model; 60 | Assert.AreEqual(model.Root.Children.Count(), 1); 61 | Assert.AreEqual(view.Root.Children.Count, 1); 62 | AssertSynchronized(view, model); 63 | root.Add(new NbtByte("test1")); 64 | AssertSynchronized(view, model); 65 | root.Add(new NbtByte("test2")); 66 | AssertSynchronized(view, model); 67 | root.Add(new NbtCompound("test3")); 68 | AssertSynchronized(view, model); 69 | root.Get("test3").Add(new NbtShort("test4")); 70 | Assert.AreEqual(view.Root.DescendantsCount, 5); 71 | Assert.AreEqual(model.Root.DescendantsCount, 5); 72 | AssertSynchronized(view, model); 73 | root.Remove("test2"); 74 | AssertSynchronized(view, model); 75 | root.Get("test3").Clear(); 76 | Assert.AreEqual(view.Root.DescendantsCount, 3); 77 | Assert.AreEqual(model.Root.DescendantsCount, 3); 78 | AssertSynchronized(view, model); 79 | root.Clear(); 80 | AssertSynchronized(view, model); 81 | } 82 | 83 | private void AssertSynchronized(NbtTreeView view, NbtTreeModel model) 84 | { 85 | var view_queue = new Queue(); 86 | var model_queue = new Queue(); 87 | foreach (var root in view.Root.Children) 88 | { 89 | view_queue.Enqueue(root); 90 | } 91 | foreach (var root in model.Root.Children) 92 | { 93 | model_queue.Enqueue(root); 94 | } 95 | while (view_queue.Any() || model_queue.Any()) 96 | { 97 | Assert.AreEqual(view_queue.Count, model_queue.Count); 98 | var view_item = view_queue.Dequeue(); 99 | var model_item = model_queue.Dequeue(); 100 | Assert.AreEqual(view_item.DescendantsCount, model_item.DescendantsCount); 101 | foreach (var child in view_item.Children) 102 | { 103 | view_queue.Enqueue(child); 104 | } 105 | foreach (var child in model_item.Children) 106 | { 107 | model_queue.Enqueue(child); 108 | } 109 | Assert.AreEqual(view_item.Tag, model_item); 110 | } 111 | } 112 | 113 | private void AssertChildStructure(INode node) 114 | { 115 | var children = node.Children; 116 | foreach (var child in children) 117 | { 118 | Assert.AreEqual(child.Parent, node); 119 | AssertChildStructure(child); 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /NbtStudioTests/NbtStudioTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0-windows 5 | 6 | false 7 | 8 | 9 | 10 | AnyCPU 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /NbtStudioTests/Snbt.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using fNbt; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using NbtStudio; 7 | using TryashtarUtils.Nbt; 8 | 9 | namespace NbtStudioTests 10 | { 11 | [TestClass] 12 | public class Snbt 13 | { 14 | [TestMethod] 15 | public void RoundTripNaN() 16 | { 17 | var compound1 = new NbtCompound 18 | { 19 | new NbtDouble("double", double.NaN), 20 | new NbtFloat("float", float.NaN), 21 | }; 22 | string snbt1 = compound1.ToSnbt(SnbtOptions.Default); 23 | 24 | var compound2 = SnbtParser.Parse(snbt1, named: false); 25 | string snbt2 = compound2.ToSnbt(SnbtOptions.Default); 26 | 27 | Assert.AreEqual(snbt1, snbt2); 28 | } 29 | 30 | [TestMethod] 31 | public void RoundTripInfinity() 32 | { 33 | var compound1 = new NbtCompound 34 | { 35 | new NbtDouble("double_plus", double.PositiveInfinity), 36 | new NbtDouble("double_minus", double.NegativeInfinity), 37 | new NbtFloat("float_plus", float.PositiveInfinity), 38 | new NbtFloat("float_minus", float.NegativeInfinity), 39 | }; 40 | string snbt1 = compound1.ToSnbt(SnbtOptions.Default); 41 | 42 | var compound2 = SnbtParser.Parse(snbt1, named: false); 43 | string snbt2 = compound2.ToSnbt(SnbtOptions.Default); 44 | 45 | Assert.AreEqual(snbt1, snbt2); 46 | } 47 | 48 | [TestMethod] 49 | public void RoundTripEscapes() 50 | { 51 | var compound1 = new NbtCompound 52 | { 53 | new NbtString("simple_name", "simple_value"), 54 | new NbtString("name with spaces", "simple_value"), 55 | new NbtString("simple_name_2", "value with spaces"), 56 | new NbtString("name with spaces 2", "value with spaces"), 57 | new NbtString("name with \"double quotes\"", "value with 'single quotes'"), 58 | new NbtString("name with 'single quotes'", "value with \"double quotes\""), 59 | new NbtString("name with\nnewline", "value with\nnewline"), 60 | }; 61 | string snbt1 = compound1.ToSnbt(SnbtOptions.Default); 62 | 63 | var compound2 = SnbtParser.Parse(snbt1, named: false); 64 | string snbt2 = compound2.ToSnbt(SnbtOptions.Default); 65 | 66 | Assert.AreEqual(snbt1, snbt2); 67 | } 68 | 69 | [TestMethod] 70 | public void SpecialFloatStringsSuffixed() 71 | { 72 | var snbt = "{test1:infinityf,test2:-infinityf,test3:+∞f,test4:∞f,test5:-∞f,test6:nanf}"; 73 | var parsed = (NbtCompound)SnbtParser.Parse(snbt, named: false); 74 | Assert.IsTrue(parsed.Tags.All(x => x.TagType == NbtTagType.Float)); 75 | } 76 | 77 | [TestMethod] 78 | public void SpecialDoubleStringsSuffixed() 79 | { 80 | var snbt = "{test1:infinityd,test2:-infinityd,test3:+∞d,test4:∞d,test5:-∞d,test6:nand}"; 81 | var parsed = (NbtCompound)SnbtParser.Parse(snbt, named: false); 82 | Assert.IsTrue(parsed.Tags.All(x => x.TagType == NbtTagType.Double)); 83 | } 84 | 85 | [TestMethod] 86 | public void SpecialDoubleStrings() 87 | { 88 | var snbt = "{test1:infinity,test2:-infinity,test3:+∞,test4:∞,test5:-∞,test6:nan}"; 89 | var parsed = (NbtCompound)SnbtParser.Parse(snbt, named: false); 90 | Assert.IsTrue(parsed.Tags.All(x => x.TagType == NbtTagType.Double)); 91 | } 92 | 93 | [TestMethod] 94 | public void SpecialByteStrings() 95 | { 96 | var snbt = "{test1:true,test2:false}"; 97 | var parsed = (NbtCompound)SnbtParser.Parse(snbt, named: false); 98 | Assert.IsTrue(parsed.Tags.All(x => x.TagType == NbtTagType.Byte)); 99 | } 100 | 101 | [TestMethod] 102 | public void NonSpecialStrings() 103 | { 104 | var snbt = "{test1:infinitydd,test2:trueb,test3:falseb,test4:--infinity}"; 105 | var parsed = (NbtCompound)SnbtParser.Parse(snbt, named: false); 106 | Assert.IsTrue(parsed.Tags.All(x => x.TagType == NbtTagType.String)); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /NbtStudioTests/Sorting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using fNbt; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using NbtStudio; 7 | 8 | namespace NbtStudioTests 9 | { 10 | [TestClass] 11 | public class Sorting 12 | { 13 | [TestMethod] 14 | public void RestoreSort() 15 | { 16 | var compound = new NbtCompound 17 | { 18 | new NbtLong("ccc", 3), 19 | new NbtShort("aaa", 3), 20 | new NbtCompound("bbb") 21 | { 22 | new NbtInt("ccc", 3), 23 | new NbtInt("bbb", 3), 24 | new NbtInt("aaa", 3), 25 | } 26 | }; 27 | var original = (NbtCompound)compound.Clone(); 28 | UndoableAction action = null; 29 | compound.ActionPerformed += a => action = a; 30 | 31 | compound.Sort(new AlphabeticalSorter(), true); 32 | 33 | Assert.AreEqual(compound[0].Name, "aaa"); 34 | Assert.AreEqual(compound[1].Name, "bbb"); 35 | Assert.AreEqual(compound[2].Name, "ccc"); 36 | Assert.AreEqual(compound["bbb"][0].Name, "aaa"); 37 | Assert.AreEqual(compound["bbb"][1].Name, "bbb"); 38 | Assert.AreEqual(compound["bbb"][2].Name, "ccc"); 39 | 40 | action.Undo(); 41 | 42 | AssertIdentical(compound, original); 43 | } 44 | 45 | private class AlphabeticalSorter : IComparer 46 | { 47 | public int Compare(NbtTag x, NbtTag y) 48 | { 49 | return x.Name.CompareTo(y.Name); 50 | } 51 | } 52 | 53 | private void AssertIdentical(NbtTag c1, NbtTag c2) 54 | { 55 | var snbt1 = c1.ToString(); 56 | var snbt2 = c2.ToString(); 57 | Assert.AreEqual(snbt1, snbt2); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NbtStudioTests/UndoRedo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using fNbt; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using NbtStudio; 7 | 8 | namespace NbtStudioTests 9 | { 10 | [TestClass] 11 | public class UndoRedo 12 | { 13 | [TestMethod] 14 | public void BasicUndo() 15 | { 16 | int before = 10; 17 | int after = 10; 18 | var actions = new Stack(); 19 | var tag = new NbtInt(); 20 | tag.ActionPerformed += a => actions.Push(a); 21 | 22 | tag.Value = before; 23 | tag.Value = after; 24 | actions.Pop().Undo(); 25 | 26 | Assert.AreEqual(tag.Value, before); 27 | } 28 | 29 | [TestMethod] 30 | public void ModelUndo() 31 | { 32 | var compound = new NbtCompound(); 33 | 34 | //compound.Add(new NbtByte("test")); 35 | //Assert.AreEqual(compound.Count, 1); 36 | //model.UndoHistory.Undo(); 37 | //Assert.AreEqual(compound.Count, 0); 38 | 39 | var sub = new NbtCompound("test"); 40 | var b1 = new NbtByte("b1"); 41 | var b2 = new NbtByte("b2"); 42 | compound.Add(sub); 43 | sub.Add(b1); 44 | sub.Add(b2); 45 | 46 | var model = new NbtTreeModel(); 47 | var node = new NbtTagNode(model, null, compound); 48 | model.Import(node); 49 | 50 | model.UndoHistory.StartBatchOperation(); 51 | node.ReceiveDrop(new[] { node.Children.First() }, 0); 52 | model.UndoHistory.FinishBatchOperation(new DescriptionHolder(""), true); 53 | 54 | model.UndoHistory.StartBatchOperation(); 55 | node.Children.First().ReceiveDrop(new[] { node.Children.First().Children.First() }, 0); 56 | model.UndoHistory.FinishBatchOperation(new DescriptionHolder(""), true); 57 | 58 | model.UndoHistory.Undo(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /NbtStudioTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NBT Studio 2 | 3 | ### [Download link](https://github.com/tryashtar/nbt-studio/releases) 4 | 5 | NBT Studio is an [NBT](https://wiki.vg/NBT) editing application, the spiritual successor to [NBTExplorer](https://github.com/jaquadro/NBTExplorer). It has been rewritten completely from scratch to support a vast array of new features, while keeping the familiar layout of controls. New features include Bedrock support, SNBT support, undo/redo functionality, drag and drop, multiselect, and more! It's called Studio to make it sound more important than it really is. 6 | 7 | ## Files 8 | NBT Studio supports reading and writing the following NBT formats. Features marked with a star are new NBT Studio features not present in NBTExplorer. 9 | 10 | * Java NBT files, such as `level.dat` 11 | * Java region files (`.mca` and `.mcr`) 12 | * ☆ Bedrock NBT files, such as `.mcstructure` files (little-endian NBT) 13 | * ☆ SNBT files (stringified NBT, like in commands: `{Enchantments:[{id:sharpness,lvl:10s}]}`) 14 | 15 | ☆ NBT files can be exported to any of these formats using Save as. You can load an SNBT file, then export it as a little-endian g-zipped binary file if you wish. 16 | 17 | ☆ NBT Studio also allows the creation of blank NBT files. You can start from scratch and export to any format. Additionally, you can use Ctrl Alt V to create a file from SNBT data on your clipboard. 18 | 19 | ## SNBT Support 20 | NBT Studio is designed around the easy transfer and conversion between textual SNBT data and structured NBT data. As mentioned, SNBT can be opened as a file or pasted as a new document. 21 | 22 | * You can add a tag as SNBT anywhere in the document. Whatever type is parsed will be the resulting type of the tag. Compounds and lists will be fully parsed with all of their children. 23 | * Existing tags can also be inspected and edited as SNBT. You can modify entire compounds in-place, or simpler tags. 24 | * The cut, copy, and paste actions add the tag to your clipboard as SNBT. You can copy multiple tags as text, or import SNBT data obtained elsewhere. 25 | 26 | ## Tag Editing 27 | NBT Studio adds an assortment of convenience features to make navigating and editing NBT data easier. 28 | 29 | * The tag creation and edit menus have fields for both tag name and value, so you don't have to do it in two steps 30 | * Hold Shift while adding a tag to skip the menu and select its name automatically 31 | * NBT lists of type `byte`, `short`, `int`, or `long` can be edited as hex just like arrays 32 | * Tags can be selected, dragged, and dropped to move them to a different parent 33 | * Undo and redo functionality with Ctrl Z and Ctrl Shift Z 34 | * Deleting tags automatically selects the next tag for easy obliteration 35 | * New and improved search window allows searching by [regular expression](https://en.wikipedia.org/wiki/Regular_expression), as well as an option to select all matching tags 36 | * Press Enter to edit the selected tag 37 | * Press Space to expand/contract the selected tag, or Ctrl Space to expand all 38 | * Right-click on a file to see options to save it, or open it in File Explorer 39 | * Right-click on a container tag to see options to add a child tag 40 | * Files display an asterisk (`*`) to indicate there are unsaved changes 41 | 42 | # Credits 43 | This application was written from scratch by myself, tryashtar. However, it would never have existed without these amazing projects that came before me: 44 | 45 | ### Design 46 | * [NBTExplorer by jaquadro](https://github.com/jaquadro/NBTExplorer) 47 | 48 | ### Technologies 49 | * [fNbt by mstefarov](https://github.com/mstefarov/fNbt) 50 | * [TreeViewAdv by agaman](https://sourceforge.net/projects/treeviewadv) 51 | * [Be.HexEditor by bernhardelbl](https://sourceforge.net/projects/hexbox) 52 | 53 | ### Icons 54 | * [Yusuke Kamiyamane](https://p.yusukekamiyamane.com) 55 | * [AmberW](https://github.com/AmberWat) 56 | --------------------------------------------------------------------------------