using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; using System.Drawing.Drawing2D; using System.Diagnostics; using System.Drawing.Imaging; using OMI.Formats.Archive; using OMI.Formats.Pck; using OMI.Formats.GameRule; using OMI.Formats.Languages; using OMI.Formats.Model; using OMI.Workers.Archive; using OMI.Workers.Pck; using OMI.Workers.GameRule; using OMI.Workers.Language; using OMI.Workers.Model; using PckStudio.Properties; using PckStudio.FileFormats; using PckStudio.Forms; using PckStudio.Forms.Editor; using PckStudio.Forms.Additional_Popups.Animation; using PckStudio.Forms.Additional_Popups; using PckStudio.Classes.Misc; using PckStudio.IO.PckAudio; using PckStudio.IO._3DST; using PckStudio.Internal; using PckStudio.Features; using PckStudio.Extensions; using PckStudio.Popups; using PckStudio.Classes.Utils; using PckStudio.Helper; using System.Text.RegularExpressions; using PckStudio.Internal.Json; using PckStudio.Internal.Deserializer; using PckStudio.Internal.Serializer; using OMI.Workers; namespace PckStudio { public partial class MainForm : MetroFramework.Forms.MetroForm { private PckManager PckManager = null; string saveLocation; PckFile currentPCK = null; bool __modified = false; bool wasModified { get => __modified; set { if (__modified == value) return; __modified = value; pckFileLabel.Text = !pckFileLabel.Text.StartsWith("*") && __modified ? "*" + pckFileLabel.Text : pckFileLabel.Text.Substring(1); } } bool isTemplateFile = false; bool isSelectingTab = false; readonly Dictionary> pckFileTypeHandler; public MainForm() { InitializeComponent(); skinToolStripMenuItem1.Click += (sender, e) => SetFileType(PckFileType.SkinFile); capeToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.CapeFile); textureToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.TextureFile); languagesFileLOCToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.LocalisationFile); gameRulesFileGRFToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.GameRulesFile); audioPCKFileToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.AudioFile); coloursCOLFileToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.ColourTableFile); gameRulesHeaderGRHToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.GameRulesHeader); skinsPCKToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.SkinDataFile); modelsFileBINToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.ModelsFile); behavioursFileBINToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.BehavioursFile); entityMaterialsFileBINToolStripMenuItem.Click += (sender, e) => SetFileType(PckFileType.MaterialFile); treeViewMain.TreeViewNodeSorter = new PckNodeSorter(); pckOpen.AllowDrop = true; Text = Application.ProductName; labelVersion.Text = $"{Application.ProductName}: {Application.ProductVersion}"; ChangelogRichTextBox.Text = Resources.CHANGELOG; pckFileTypeHandler = new Dictionary>(15) { [PckFileType.SkinFile] = HandleSkinFile, [PckFileType.CapeFile] = null, [PckFileType.TextureFile] = HandleTextureFile, [PckFileType.UIDataFile] = _ => throw new NotSupportedException("unused in-game"), [PckFileType.InfoFile] = null, [PckFileType.TexturePackInfoFile] = null, // HandleInnerPckFile, [PckFileType.LocalisationFile] = HandleLocalisationFile, [PckFileType.GameRulesFile] = HandleGameRuleFile, [PckFileType.AudioFile] = HandleAudioFile, [PckFileType.ColourTableFile] = HandleColourFile, [PckFileType.GameRulesHeader] = HandleGameRuleFile, [PckFileType.SkinDataFile] = null, // HandleInnerPckFile, [PckFileType.ModelsFile] = null, //HandleModelsFile, // Note: Uncomment when implemented [PckFileType.BehavioursFile] = HandleBehavioursFile, [PckFileType.MaterialFile] = HandleMaterialFile, }; } // TODO: decide on how to handle embedded pck files private void HandleInnerPckFile(PckFileData file) { if (Settings.Default.LoadSubPcks && (file.Filetype == PckFileType.SkinDataFile || file.Filetype == PckFileType.TexturePackInfoFile) && file.Size > 0 && treeViewMain.SelectedNode.Nodes.Count == 0) { try { PckFile subPCKfile = file.GetData(new PckFileReader(LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian)); BuildPckTreeView(treeViewMain.SelectedNode.Nodes, subPCKfile); treeViewMain.SelectedNode.ExpandAll(); } catch (OverflowException ex) { MessageBox.Show(this, "Failed to open pck\n" + "Try checking the 'Open/Save as Switch/Vita/PS4 pck' checkbox in the upper right corner.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); Debug.WriteLine(ex.Message); } return; } treeViewMain.SelectedNode.Nodes.Clear(); treeViewMain.SelectedNode.Collapse(); } public void InitPckFromFile(string filepath) { saveLocation = filepath; } public void LoadPckFromFile(string filepath) { CheckSaveState(); treeViewMain.Nodes.Clear(); currentPCK = OpenPck(filepath); if (currentPCK == null) { MessageBox.Show(this, string.Format("Failed to load {0}", Path.GetFileName(filepath)), "Error"); return; } CheckForPasswordAndRemove(); LoadEditorTab(); } private void MainForm_Load(object sender, EventArgs e) { SettingsManager.RegisterPropertyChangedCallback(nameof(Settings.Default.UseLittleEndianAsDefault), state => { LittleEndianCheckBox.Checked = state; }); SettingsManager.RegisterPropertyChangedCallback(nameof(Settings.Default.LoadSubPcks), () => { if (currentPCK is not null) { BuildMainTreeView(); } }); imageList.Images.Add(Resources.ZZFolder); // Icon for folders imageList.Images.Add(Resources.BINKA_ICON); // Icon for music cue file (audio.pck) imageList.Images.Add(Resources.IMAGE_ICON); // Icon for images (unused for now) imageList.Images.Add(Resources.LOC_ICON); // Icon for string localization files (languages.loc;localisation.loc) imageList.Images.Add(Resources.PCK_ICON); // Icon for generic PCK files (*.pck) imageList.Images.Add(Resources.ZUnknown); // Icon for Unknown formats imageList.Images.Add(Resources.COL_ICON); // Icon for color palette files (colours.col) imageList.Images.Add(Resources.SKINS_ICON); // Icon for Skin.pck archives (skins.pck) imageList.Images.Add(Resources.MODELS_ICON); // Icon for Model files (models.bin) imageList.Images.Add(Resources.GRF_ICON); // Icon for Game Rule files (*.grf) imageList.Images.Add(Resources.GRH_ICON); // Icon for Game Rule Header files (*.grh) imageList.Images.Add(Resources.INFO_ICON); // Icon for Info files (0) imageList.Images.Add(Resources.SKIN_ICON); // Icon for Skin files (*.png) imageList.Images.Add(Resources.CAPE_ICON); // Icon for Cape files (*.png) imageList.Images.Add(Resources.TEXTURE_ICON); // Icon for Texture files (*.png;*.tga) imageList.Images.Add(Resources.BEHAVIOURS_ICON); // Icon for Behaviour files (behaviours.bin) imageList.Images.Add(Resources.ENTITY_MATERIALS_ICON); // Icon for Entity Material files (entityMaterials.bin) LoadRecentFileList(); isSelectingTab = true; tabControl.SelectTab(0); isSelectingTab = false; UpdateRichPresence(); if (!string.IsNullOrWhiteSpace(saveLocation)) LoadPckFromFile(saveLocation); } private void LoadRecentFileList() { Settings.Default.RecentFiles ??= new System.Collections.Specialized.StringCollection(); recentlyOpenToolStripMenuItem.DropDownItems.Clear(); for (int i = 0; i < Settings.Default.RecentFiles.Count && i < 5; i++) { string filepath = Settings.Default.RecentFiles[i]; if (!string.IsNullOrWhiteSpace(filepath)) { string displayFilepath = Regex.Replace(filepath, @"([A-Z]{1}\:\\[Uu]sers\\)([^\\]*\\)(.*)", "~\\$3"); ToolStripItem item = recentlyOpenToolStripMenuItem.DropDownItems.Add(displayFilepath, null, HandleOpenFile); item.Tag = filepath; } } } private void HandleOpenFile(object sender, EventArgs e) { if (sender is ToolStripItem menuItem && menuItem.Tag is string filepath && File.Exists(filepath)) LoadPckFromFile(filepath); } private void SaveToRecentFiles(string filepath) { if (filepath is null || string.IsNullOrWhiteSpace(filepath)) return; if (Settings.Default.RecentFiles.Contains(filepath)) Settings.Default.RecentFiles.Remove(filepath); Settings.Default.RecentFiles.Insert(0, filepath); for (int i = Settings.Default.RecentFiles.Count - 1; i >= 5; i--) { Settings.Default.RecentFiles.RemoveAt(i); } Settings.Default.Save(); LoadRecentFileList(); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { PckManager?.Close(); CheckSaveState(); } private void openPckManagerToolStripMenuItem_Click(object sender, EventArgs e) { PckManager ??= new PckManager(); PckManager.FormClosing += (s, e) => PckManager = null; if (!PckManager.Visible) { // Passing in a parent form will make it stay on top of every other form. -miku PckManager.Show(); } if (PckManager.Focus()) PckManager.BringToFront(); } private void openToolStripMenuItem_Click(object sender, EventArgs e) { using (var ofd = new OpenFileDialog()) { ofd.CheckFileExists = true; ofd.Filter = "PCK (Minecraft Console Package)|*.pck"; if (ofd.ShowDialog(this) == DialogResult.OK) { LoadPckFromFile(ofd.FileName); } } } private PckFile OpenPck(string filePath) { isTemplateFile = false; saveLocation = filePath; SaveToRecentFiles(filePath); var reader = new PckFileReader(LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian); try { PckFile pck = reader.FromFile(filePath); return pck; } catch (OverflowException ex) { MessageBox.Show(this, "Failed to open pck\n" + $"Try {(LittleEndianCheckBox.Checked ? "unchecking" : "checking")} the 'Open/Save as Switch/Vita/PS4 pck' check box in the upper right corner.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); Debug.WriteLine(ex.Message); } catch { MessageBox.Show(this, "Failed to open pck. There's two common reasons for this:\n" + "1. The file is audio/music cues PCK file. Please use the specialized editor while inside of a pck file.\n" + "2. We're aware of an issue where a pck file might fail to load because it contains multiple entries with the same path.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } return null; } private void CheckForPasswordAndRemove() { if (currentPCK.TryGetFile("0", PckFileType.InfoFile, out PckFileData file)) { file.RemoveProperties("LOCK"); } } private void LoadEditorTab() { fileEntryCountLabel.Text = "Files:" + currentPCK.FileCount; if (isTemplateFile) pckFileLabel.Text = "Unsaved File!"; else pckFileLabel.Text = Path.GetFileName(saveLocation); treeViewMain.Enabled = treeMeta.Enabled = true; closeToolStripMenuItem.Visible = true; fullBoxSupportToolStripMenuItem.Checked = currentPCK.HasVerionString; packSettingsToolStripMenuItem.Visible = true; saveToolStripMenuItem.Enabled = true; saveToolStripMenuItem1.Enabled = true; quickChangeToolStripMenuItem.Enabled = true; BuildMainTreeView(); isSelectingTab = true; tabControl.SelectTab(1); isSelectingTab = false; UpdateRichPresence(); } private void CloseEditorTab() { isSelectingTab = true; tabControl.SelectTab(0); isSelectingTab = false; currentPCK = null; wasModified = false; isTemplateFile = false; saveLocation = string.Empty; previewPictureBox.Image = Resources.NoImageFound; treeViewMain.Nodes.Clear(); treeMeta.Nodes.Clear(); treeViewMain.Enabled = false; treeMeta.Enabled = false; saveToolStripMenuItem.Enabled = false; saveToolStripMenuItem1.Enabled = false; quickChangeToolStripMenuItem.Enabled = false; closeToolStripMenuItem.Visible = false; packSettingsToolStripMenuItem.Visible = false; fileEntryCountLabel.Text = string.Empty; pckFileLabel.Text = string.Empty; UpdateRichPresence(); } private void UpdateRichPresence() { if (currentPCK is not null && TryGetLocFile(out LOCFile locfile) && locfile.HasLocEntry("IDS_DISPLAY_NAME") && locfile.Languages.Contains("en-EN")) { RPC.SetPresence($"Editing a Pack: {locfile.GetLocEntry("IDS_DISPLAY_NAME", "en-EN")}"); return; } // default RPC.SetPresence("An Open Source .PCK File Editor"); } /// /// wrapper that allows the use of in TreeNode.Nodes.Find(, ...) and TreeNode.Nodes.ContainsKey() /// /// /// /// new Created TreeNode public static TreeNode CreateNode(string name, object tag = null) { TreeNode node = new TreeNode(name); node.Name = name; node.Tag = tag; return node; } private TreeNode BuildNodeTreeBySeperator(TreeNodeCollection root, string path, char seperator) { _ = root ?? throw new ArgumentNullException(nameof(root)); if (!path.Contains(seperator)) { TreeNode finalNode = CreateNode(path); root.Add(finalNode); return finalNode; } string nodeText = path.Substring(0, path.IndexOf(seperator)); string subPath = path.Substring(path.IndexOf(seperator) + 1); if (string.IsNullOrWhiteSpace(nodeText)) { return BuildNodeTreeBySeperator(root, subPath, seperator); } bool alreadyExists = root.ContainsKey(nodeText); TreeNode subNode = alreadyExists ? root[nodeText] : CreateNode(nodeText); if (!alreadyExists) root.Add(subNode); return BuildNodeTreeBySeperator(subNode.Nodes, subPath, seperator); } private void BuildPckTreeView(TreeNodeCollection root, PckFile pckFile) { foreach (PckFileData file in pckFile.GetFiles()) { // fix any file paths that may be incorrect //if (file.Filename.StartsWith(parentPath)) // file.Filename = file.Filename.Remove(0, parentPath.Length); TreeNode node = BuildNodeTreeBySeperator(root, file.Filename, '/'); node.Tag = file; SetNodeIcon(node, file.Filetype); }; } private void BuildMainTreeView() { // In case the Rename function was just used and the selected node name no longer matches the file name string selectedNodeText = treeViewMain.SelectedNode is TreeNode node ? node.Text : string.Empty; previewPictureBox.Image = Resources.NoImageFound; treeMeta.Nodes.Clear(); treeViewMain.Nodes.Clear(); BuildPckTreeView(treeViewMain.Nodes, currentPCK); if (isTemplateFile && currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) { TreeNode skinsNode = treeViewMain.Nodes.Find("Skins.pck", false).FirstOrDefault(); TreeNode folderNode = CreateNode("Skins"); folderNode.ImageIndex = 0; folderNode.SelectedImageIndex = 0; if (!skinsNode.Nodes.ContainsKey("Skins")) skinsNode.Nodes.Add(folderNode); } treeViewMain.Sort(); TreeNode[] selectedNodes; if (!string.IsNullOrEmpty(selectedNodeText) && (selectedNodes = treeViewMain.Nodes.Find(selectedNodeText, true)).Length > 0) { treeViewMain.SelectedNode = selectedNodes[0]; } } private void HandleTextureFile(PckFileData file) { _ = file.IsMipmappedFile() && currentPCK.TryGetValue(file.GetNormalPath(), PckFileType.TextureFile, out file); if (file.Size <= 0) { Debug.WriteLine($"'{file.Filename}' size is 0.", category: nameof(HandleTextureFile)); return; } bool isTerrain = file.Filename == "res/terrain.png"; bool isItems = file.Filename == "res/items.png"; bool isParticles = file.Filename == "res/particles.png"; bool isMoonPhases = file.Filename == "res/terrain/moon_phases.png"; bool isMapIcons = file.Filename == "res/misc/mapicons.png"; bool isAdditionalMapIcons = file.Filename == "res/misc/additionalmapicons.png"; bool isXPOrbs = file.Filename == "res/item/xporb.png"; bool isExplosions = file.Filename == "res/misc/explosion.png"; bool isPaintings = file.Filename == "res/art/kz.png"; bool isBanners = file.Filename == "res/item/banner/Banner_Atlas.png"; if ( isTerrain || isItems || isParticles || isMoonPhases || isPaintings || isMapIcons || isAdditionalMapIcons || isXPOrbs || isExplosions || isBanners ) { Image img = file.GetTexture(); var tile_size = new Size(); int banner_scale = img.Width / Resources.banners_atlas.Width; if (isBanners) { // The banner atlas has extra space on it that has to be truncated for the editor img = img.GetArea(new Rectangle(0, 0, img.Width - (4 * banner_scale), img.Height - (1 * banner_scale))); // banners are 42x41 because of course they are tile_size = new Size(42 * banner_scale, 41 * banner_scale); } // most atlases have 4 columns int columnCount = isBanners ? 6 : 4; if (isTerrain || isItems || isParticles || isPaintings) columnCount = 16; if (!isBanners) { int resolution = img.Width / columnCount; tile_size = new Size(resolution, resolution); } var viewer = new TextureAtlasEditor(currentPCK, file.Filename, img, tile_size); if (viewer.ShowDialog(this) == DialogResult.OK) { Image texture = viewer.FinalTexture; if(isBanners) { var graphicsConfig = new GraphicsConfig() { InterpolationMode = InterpolationMode.NearestNeighbor, PixelOffsetMode = PixelOffsetMode.HighQuality }; var _img = new Bitmap((Resources.banners_atlas.Width + 4) * banner_scale, (Resources.banners_atlas.Height + 1) * banner_scale); using (Graphics g = Graphics.FromImage(_img)) { g.ApplyConfig(graphicsConfig); g.DrawImage(texture, 0, 0, texture.Width, texture.Height); } texture = _img; } file.SetTexture(texture); wasModified = true; BuildMainTreeView(); } return; } if (!file.Filename.StartsWith(ResourceLocation.GetPathFromCategory(ResourceCategory.ItemAnimation)) && !file.Filename.StartsWith(ResourceLocation.GetPathFromCategory(ResourceCategory.BlockAnimation))) return; Animation animation = file.GetDeserializedData(AnimationDeserializer.DefaultDeserializer); string filename = Path.GetFileNameWithoutExtension(file.Filename); var textureInfos = ResourceLocation.GetCategoryFromPath(file.Filename) switch { ResourceCategory.BlockAnimation => Tiles.BlockTileInfos, ResourceCategory.ItemAnimation => Tiles.ItemTileInfos, _ => Array.Empty().ToList() }; string displayname = textureInfos.FirstOrDefault(p => p.InternalName == filename)?.DisplayName ?? filename; string[] specialTileNames = { "clock", "compass" }; using (AnimationEditor animationEditor = new AnimationEditor(animation, displayname, filename.ToLower().EqualsAny(specialTileNames))) { if (animationEditor.ShowDialog(this) == DialogResult.OK) { wasModified = true; file.SetSerializedData(animationEditor.Result, AnimationSerializer.DefaultSerializer); BuildMainTreeView(); } } } private void HandleGameRuleFile(PckFileData file) { const string use_deflate = "PS3"; const string use_xmem = "Xbox 360"; const string use_zlib = "Wii U, PS Vita"; ItemSelectionPopUp dialog = new ItemSelectionPopUp(use_zlib, use_deflate, use_xmem); dialog.LabelText = "Type"; dialog.ButtonText = "Ok"; if (dialog.ShowDialog() != DialogResult.OK) return; var compressiontype = dialog.SelectedItem switch { use_deflate => GameRuleFile.CompressionType.Deflate, use_xmem => GameRuleFile.CompressionType.XMem, use_zlib => GameRuleFile.CompressionType.Zlib, _ => GameRuleFile.CompressionType.Unknown }; GameRuleFile grf = file.GetData(new GameRuleFileReader(compressiontype)); using GameRuleFileEditor grfEditor = new GameRuleFileEditor(grf); if (grfEditor.ShowDialog(this) == DialogResult.OK) { file.SetData(new GameRuleFileWriter(grfEditor.Result)); wasModified = true; UpdateRichPresence(); } } private void HandleAudioFile(PckFileData file) { try { using AudioEditor audioEditor = new AudioEditor(file, LittleEndianCheckBox.Checked); wasModified = audioEditor.ShowDialog(this) == DialogResult.OK; } catch (OverflowException) { MessageBox.Show(this, $"Failed to open {file.Filename}\n" + "Try converting the file by using the \"Misc. Functions/Set PCK Endianness\" tool and try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); } catch (Exception ex) { MessageBox.Show($"Failed to open {file.Filename}\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } private void HandleLocalisationFile(PckFileData file) { using LOCEditor locedit = new LOCEditor(file); wasModified = locedit.ShowDialog(this) == DialogResult.OK; UpdateRichPresence(); } private void HandleColourFile(PckFileData file) { using COLEditor diag = new COLEditor(file); wasModified = diag.ShowDialog(this) == DialogResult.OK; } public void HandleSkinFile(PckFileData file) { if (file.HasProperty("BOX")) { using generateModel generate = new generateModel(file); if (generate.ShowDialog(this) == DialogResult.OK) { entryDataTextBox.Text = entryTypeTextBox.Text = string.Empty; wasModified = true; ReloadMetaTreeView(); } return; } Image img = file.GetTexture(); using var skinViewer = new SkinPreview(img, file.GetProperty("ANIM", SkinANIM.FromString)); skinViewer.ShowDialog(this); } public void HandleModelsFile(PckFileData file) { MessageBox.Show(this, "Models.bin support has not been implemented. You can use the Spark Editor for the time being to edit these files.", "Not implemented yet."); } public void HandleBehavioursFile(PckFileData file) { using BehaviourEditor edit = new BehaviourEditor(file); wasModified = edit.ShowDialog(this) == DialogResult.OK; } public void HandleMaterialFile(PckFileData file) { using MaterialsEditor edit = new MaterialsEditor(file); wasModified = edit.ShowDialog(this) == DialogResult.OK; } private void selectNode(object sender, TreeViewEventArgs e) { ReloadMetaTreeView(); entryTypeTextBox.Text = entryDataTextBox.Text = labelImageSize.Text = string.Empty; buttonEdit.Visible = false; previewPictureBox.Image = Resources.NoImageFound; viewFileInfoToolStripMenuItem.Visible = false; if (e.Node.TryGetTagData(out PckFileData file)) { viewFileInfoToolStripMenuItem.Visible = true; if (file.HasProperty("BOX")) { buttonEdit.Text = "EDIT BOXES"; buttonEdit.Visible = true; } else if (file.HasProperty("ANIM") && file.GetProperty("ANIM", s => SkinANIM.FromString(s) == (SkinAnimMask.RESOLUTION_64x64 | SkinAnimMask.SLIM_MODEL))) { buttonEdit.Text = "View Skin"; buttonEdit.Visible = true; } switch (file.Filetype) { case PckFileType.SkinFile: case PckFileType.CapeFile: case PckFileType.TextureFile: { Image img = file.GetTexture(); if (img.RawFormat != ImageFormat.Jpeg || img.RawFormat != ImageFormat.Png) { img = new Bitmap(img); } try { previewPictureBox.Image = img; labelImageSize.Text = $"{previewPictureBox.Image.Size.Width}x{previewPictureBox.Image.Size.Height}"; } catch (Exception ex) { labelImageSize.Text = ""; previewPictureBox.Image = Resources.NoImageFound; Debug.WriteLine("Not a supported image format. Setting back to default"); Debug.WriteLine(string.Format("An error occured of type: {0} with message: {1}", ex.GetType(), ex.Message), "Exception"); } if ((file.Filename.StartsWith(ResourceLocation.GetPathFromCategory(ResourceCategory.ItemAnimation)) || file.Filename.StartsWith(ResourceLocation.GetPathFromCategory(ResourceCategory.BlockAnimation))) && file.Filetype == PckFileType.TextureFile && !file.IsMipmappedFile()) { buttonEdit.Text = "EDIT TILE ANIMATION"; buttonEdit.Visible = true; } } break; case PckFileType.LocalisationFile: buttonEdit.Text = "EDIT LOC"; buttonEdit.Visible = true; break; case PckFileType.AudioFile: buttonEdit.Text = "EDIT MUSIC CUES"; buttonEdit.Visible = true; break; case PckFileType.ColourTableFile when file.Filename == "colours.col": buttonEdit.Text = "EDIT COLORS"; buttonEdit.Visible = true; break; case PckFileType.BehavioursFile when file.Filename == "behaviours.bin": buttonEdit.Text = "EDIT BEHAVIOURS"; buttonEdit.Visible = true; break; default: buttonEdit.Visible = false; break; } } } private void extractFile(string outFilePath, PckFileData file) { File.WriteAllBytes(outFilePath, file.Data); if (file.PropertyCount > 0) { using var fs = File.CreateText($"{outFilePath}.txt"); foreach (KeyValuePair property in file.GetProperties()) { fs.WriteLine($"{property.Key}: {property.Value}"); } } } private void extractFolderFile(string outPath, PckFileData file) { TreeNode node = treeViewMain.SelectedNode; // abb = "Abbreviated Path" string abbPath = Path.GetDirectoryName(file.Filename); int startIndex = abbPath.IndexOf(node.Text); abbPath = abbPath.Substring(startIndex, abbPath.Length - startIndex); string finalPath = ($"{outPath}/{abbPath}/").Replace('\\', '/'); if (!Directory.Exists(finalPath)) Directory.CreateDirectory(finalPath); extractFile(finalPath + "/" + Path.GetFileName(file.Filename), file); } private void extractFolder(string outPath) { TreeNode node = treeViewMain.SelectedNode; string selectedFolder = node.FullPath; if (IsSubPCKNode(node.FullPath)) { GetAllChildNodes(node.Nodes).ForEach(fileNode => { if (fileNode.TryGetTagData(out PckFileData file)) { extractFolderFile(outPath, file); } } ); } else { foreach (PckFileData _file in currentPCK.GetFiles()) { if (_file.Filename.StartsWith(selectedFolder)) { extractFolderFile(outPath, _file); } }; } } private void extractToolStripMenuItem_Click(object sender, EventArgs e) { TreeNode node = treeViewMain.SelectedNode; if (node == null) { MessageBox.Show(this, "The selected node was null. Please select a node and try again.", "Node not extracted"); return; } if (node.Tag == null) { OpenFolderDialog dialog = new OpenFolderDialog(); dialog.Title = @"Select destination folder"; if (dialog.ShowDialog(Handle) == true) extractFolder(dialog.ResultPath); } else if (node.TryGetTagData(out PckFileData file)) { using SaveFileDialog exFile = new SaveFileDialog(); exFile.FileName = Path.GetFileName(file.Filename); exFile.Filter = Path.GetExtension(file.Filename).Replace(".", string.Empty) + " File|*" + Path.GetExtension(file.Filename); if (exFile.ShowDialog(this) != DialogResult.OK || // Makes sure chosen directory isn't null or whitespace AKA makes sure its usable string.IsNullOrWhiteSpace(Path.GetDirectoryName(exFile.FileName))) { MessageBox.Show(this, "The chosen directory is invalid. Please choose a different one and try again.", "Node not extracted"); return; } extractFile(exFile.FileName, file); } // Verification that file extraction path was successful MessageBox.Show(this, $"\"{node.Text}\" successfully extracted"); } private void SaveTemplate() { using SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "PCK (Minecraft Console Package)|*.pck"; saveFileDialog.DefaultExt = ".pck"; if (saveFileDialog.ShowDialog(this) == DialogResult.OK) { Save(saveFileDialog.FileName); saveLocation = saveFileDialog.FileName; SaveToRecentFiles(saveFileDialog.FileName); pckFileLabel.Text = Path.GetFileName(saveLocation); isTemplateFile = false; } } private void Save(string filePath) { var writer = new PckFileWriter(currentPCK, LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian); writer.WriteToFile(filePath); wasModified = false; MessageBox.Show(this, "Saved Pck file", "File Saved"); } private void replaceToolStripMenuItem_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.Tag is PckFileData file) { using var ofd = new OpenFileDialog(); // Suddenly, and randomly, this started throwing an exception because it wasn't formatted correctly? So now it's formatted correctly and now displays the file type name in the dialog. string extra_extensions = ""; switch (file.Filetype) { case PckFileType.TextureFile: if (Path.GetExtension(file.Filename) == ".png") extra_extensions = ";*.tga"; else if (Path.GetExtension(file.Filename) == ".tga") extra_extensions = ";*.png"; break; } string fileExt = Path.GetExtension(file.Filename); ofd.Filter = $"{file.Filetype} (*{fileExt}{extra_extensions})|*{fileExt}{extra_extensions}"; if (ofd.ShowDialog(this) == DialogResult.OK) { string newFileExt = Path.GetExtension(ofd.FileName); file.SetData(File.ReadAllBytes(ofd.FileName)); file.Filename = file.Filename.Replace(fileExt, newFileExt); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); wasModified = true; BuildMainTreeView(); } return; } MessageBox.Show(this, "Can't replace a folder."); } /// /// Action to run before a file will be deleted /// /// File to remove /// True if the remove should be canceled, otherwise False private bool BeforeFileRemove(PckFileData file) { string itemPath = ResourceLocation.GetPathFromCategory(ResourceCategory.ItemAnimation); // warn the user about deleting compass.png and clock.png if (file.Filetype == PckFileType.TextureFile && (file.Filename == itemPath + "/compass.png" || file.Filename == itemPath + "/clock.png")) { if (MessageBox.Show(this, "Are you sure want to delete this file? If \"compass.png\" or \"clock.png\" are missing, your game will crash upon loading this pack.", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) return true; } // remove loc key if its a skin/cape if (file.Filetype == PckFileType.SkinFile || file.Filetype == PckFileType.CapeFile) { if (TryGetLocFile(out LOCFile locFile)) { if (file.TryGetProperty("THEMENAMEID", out string value)) locFile.RemoveLocKey(value); if (file.TryGetProperty("DISPLAYNAMEID", out value)) locFile.RemoveLocKey(value); TrySetLocFile(locFile); } } return false; } private void deleteFileToolStripMenuItem_Click(object sender, EventArgs e) { TreeNode node = treeViewMain.SelectedNode; if (node == null) return; string path = node.FullPath; if (node.TryGetTagData(out PckFileData file)) { if (!BeforeFileRemove(file) && currentPCK.RemoveFile(file)) { node.Remove(); wasModified = true; } } else if (MessageBox.Show(this, "Are you sure want to delete this folder? All contents will be deleted", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { string pckFolderDir = node.FullPath; currentPCK.RemoveAll(file => file.Filename.StartsWith(pckFolderDir) && !BeforeFileRemove(file)); node.Remove(); wasModified = true; } RebuildSubPCK(path); } private void renameFileToolStripMenuItem_Click(object sender, EventArgs e) { TreeNode node = treeViewMain.SelectedNode; if (node == null) return; string path = node.FullPath; bool isFile = node.TryGetTagData(out var file); using TextPrompt diag = new TextPrompt(isFile ? file.Filename : Path.GetFileName(node.FullPath)); if (diag.ShowDialog(this) == DialogResult.OK) { if (isFile) { if (currentPCK.Contains(diag.NewText, file.Filetype)) { MessageBox.Show(this, $"{diag.NewText} already exists", "File already exists"); return; } file.Filename = diag.NewText; } else // folders { node.Text = diag.NewText; foreach (TreeNode childNode in GetAllChildNodes(node.Nodes)) { if (childNode.Tag is PckFileData folderFile) { if (folderFile.Filename == diag.NewText) continue; folderFile.Filename = childNode.FullPath; } } } wasModified = true; RebuildSubPCK(path); BuildMainTreeView(); } } private void createSkinToolStripMenuItem_Click(object sender, EventArgs e) { LOCFile locFile = null; TryGetLocFile(out locFile); using AddNewSkin add = new AddNewSkin(locFile); if (add.ShowDialog(this) == DialogResult.OK) { if (currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) // Prioritize Skins.pck { TreeNode subPCK = treeViewMain.Nodes.Find("Skins.pck", false).FirstOrDefault(); if (subPCK.Nodes.ContainsKey("Skins")) add.SkinFile.Filename = add.SkinFile.Filename.Insert(0, "Skins/"); add.SkinFile.Filename = add.SkinFile.Filename.Insert(0, "Skins.pck/"); TreeNode newNode = new TreeNode(Path.GetFileName(add.SkinFile.Filename)); newNode.Tag = add.SkinFile; SetNodeIcon(newNode, PckFileType.SkinFile); subPCK.Nodes.Add(newNode); RebuildSubPCK(newNode.FullPath); } else { if (treeViewMain.Nodes.ContainsKey("Skins")) add.SkinFile.Filename = add.SkinFile.Filename.Insert(0, "Skins/"); // Then Skins folder currentPCK.AddFile(add.SkinFile); } if (add.HasCape) { if (currentPCK.HasFile("Skins.pck", PckFileType.SkinDataFile)) // Prioritize Skins.pck { TreeNode subPCK = treeViewMain.Nodes.Find("Skins.pck", false).FirstOrDefault(); if (subPCK.Nodes.ContainsKey("Skins")) add.CapeFile.Filename = add.CapeFile.Filename.Insert(0, "Skins/"); add.CapeFile.Filename = add.CapeFile.Filename.Insert(0, "Skins.pck/"); TreeNode newNode = new TreeNode(Path.GetFileName(add.CapeFile.Filename)); newNode.Tag = add.CapeFile; SetNodeIcon(newNode, PckFileType.SkinFile); subPCK.Nodes.Add(newNode); RebuildSubPCK(newNode.FullPath); } else { if (treeViewMain.Nodes.ContainsKey("Skins")) add.CapeFile.Filename = add.CapeFile.Filename.Insert(0, "Skins/"); // Then Skins folder currentPCK.AddFile(add.CapeFile); } } TrySetLocFile(locFile); wasModified = true; BuildMainTreeView(); } } private static PckFileData CreateNewAudioFile(bool isLittle) { PckAudioFile audioPck = new PckAudioFile(); audioPck.AddCategory(PckAudioFile.AudioCategory.EAudioType.Overworld); audioPck.AddCategory(PckAudioFile.AudioCategory.EAudioType.Nether); audioPck.AddCategory(PckAudioFile.AudioCategory.EAudioType.End); PckFileData pckFileData = new PckFileData("audio.pck", PckFileType.AudioFile); pckFileData.SetData(new PckAudioFileWriter(audioPck, isLittle ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian)); return pckFileData; } private void audiopckToolStripMenuItem_Click(object sender, EventArgs e) { if (currentPCK.Contains(PckFileType.AudioFile)) { // the chance of this happening is really really slim but just in case MessageBox.Show(this, "There is already an audio file in this PCK!", "Can't create audio.pck"); return; } if (string.IsNullOrEmpty(saveLocation)) { MessageBox.Show(this, "You must save your pck before creating or opening a music cues PCK file", "Can't create audio.pck"); return; } PckFileData file = CreateNewAudioFile(LittleEndianCheckBox.Checked); AudioEditor diag = new AudioEditor(file, LittleEndianCheckBox.Checked); if (diag.ShowDialog(this) == DialogResult.OK) { currentPCK.AddFile(file); } diag.Dispose(); BuildMainTreeView(); } private void createAnimatedTextureToolStripMenuItem_Click(object sender, EventArgs e) { using ChangeTile diag = new ChangeTile(); if (diag.ShowDialog(this) != DialogResult.OK) return; string animationFilepath = $"{ResourceLocation.GetPathFromCategory(diag.Category)}/{diag.SelectedTile}.png"; if (currentPCK.Contains(animationFilepath, PckFileType.TextureFile)) { MessageBox.Show(this, $"{diag.SelectedTile} is already present.", "File already present"); return; } using AnimationEditor animationEditor = new AnimationEditor(Animation.CreateEmpty(), diag.SelectedTile.DisplayName, diag.SelectedTile.InternalName.EqualsAny("clock", "compass")); if (animationEditor.ShowDialog() == DialogResult.OK) { wasModified = true; PckFileData file = currentPCK.CreateNewFile(animationFilepath, PckFileType.TextureFile); file.SetSerializedData(animationEditor.Result, AnimationSerializer.DefaultSerializer); BuildMainTreeView(); ReloadMetaTreeView(); } } [Obsolete()] bool IsSubPCKNode(string nodePath, string extention = ".pck") { // written by miku, implemented and modified by MattNL if (nodePath.EndsWith(extention)) return false; string[] subpaths = nodePath.Split('/'); bool isSubFile = subpaths.Any(s => Path.GetExtension(s).Equals(extention)); Debug.WriteLineIf(isSubFile, $"{nodePath} is a Sub-PCK File", category: nameof(IsSubPCKNode)); return isSubFile; } List GetAllChildNodes(TreeNodeCollection root) { List childNodes = new List(); foreach (TreeNode node in root) { childNodes.Add(node); if (node.Nodes.Count > 0) { childNodes.AddRange(GetAllChildNodes(node.Nodes)); } } return childNodes; } [Obsolete()] TreeNode GetSubPCK(string childPath) { string parentPath = childPath.Replace('\\', '/'); Debug.WriteLine(parentPath); string[] s = parentPath.Split('/'); Debug.WriteLine(s.Length); foreach (string node in s) { TreeNode parent = treeViewMain.Nodes.Find(node, true)[0]; if (parent.TryGetTagData(out PckFileData f) && (f.Filetype is PckFileType.TexturePackInfoFile || f.Filetype is PckFileType.SkinDataFile)) return parent; } return null; } [Obsolete()] void RebuildSubPCK(string childPath) { // Support for if a file is edited within a nested PCK File (AKA SubPCK) if (!IsSubPCKNode(childPath)) return; TreeNode parent = GetSubPCK(childPath); Debug.WriteLine(parent.Name); if (parent == null) return; PckFileData parent_file = parent.Tag as PckFileData; PckFile parent_file_pck = new PckFileReader( LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian ).FromStream(new MemoryStream(parent_file.Data)); if (parent_file.Filetype is PckFileType.TexturePackInfoFile || parent_file.Filetype is PckFileType.SkinDataFile) { Debug.WriteLine("Rebuilding " + parent_file.Filename); PckFile newPCKFile = new PckFile(3, parent_file.Filetype is PckFileType.SkinDataFile); bool hasSkinsFolder = false; // add original pck files to prevent data loss foreach (PckFileData _fd in parent_file_pck.GetFiles()) { PckFileData new_file = newPCKFile.CreateNewFile(_fd.Filename, _fd.Filetype); // check for skins folder so files are placed consistently in final pck if (_fd.Filename.StartsWith("Skins/") && parent_file.Filetype is PckFileType.SkinDataFile) hasSkinsFolder = true; foreach (var prop in _fd.GetProperties()) new_file.AddProperty(prop); new_file.SetData(_fd.Data); } foreach (TreeNode node in GetAllChildNodes(parent.Nodes)) { if (node.Tag is PckFileData node_file) { PckFileData new_file = newPCKFile.CreateNewFile( (hasSkinsFolder ? "Skins/" : String.Empty) + node_file.Filename.Replace(parent_file.Filename + "/", String.Empty), node_file.Filetype); foreach (var prop in node_file.GetProperties()) new_file.AddProperty(prop); new_file.SetData(node_file.Data); } } parent_file.SetData(new PckFileWriter(newPCKFile, LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian)); parent.Tag = parent_file; // erase hidden sub-pck nodes to prevent duplication parent.Nodes.Clear(); BuildMainTreeView(); MessageBox.Show(this, $"Files added successfully to {parent_file.Filename}"); } } private void treeViewMain_DoubleClick(object sender, EventArgs e) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file)) { if (file.Size <= 0) { Trace.WriteLine($"'{file.Filename}' has no data attached.", category: nameof(treeViewMain_DoubleClick)); return; } pckFileTypeHandler[file.Filetype]?.Invoke(file); } } private void treeMeta_AfterSelect(object sender, TreeViewEventArgs e) { if (e.Node is TreeNode t && t.Tag is KeyValuePair property) { entryTypeTextBox.Text = property.Key; entryDataTextBox.Text = property.Value; } } private void treeMeta_DoubleClick(object sender, EventArgs e) { if (treeMeta.SelectedNode is TreeNode subnode && subnode.Tag is KeyValuePair property && treeViewMain.SelectedNode is TreeNode node && node.Tag is PckFileData file) { if (file.HasProperty(property.Key)) { switch (property.Key) { case "ANIM" when file.Filetype == PckFileType.SkinFile: try { using ANIMEditor diag = new ANIMEditor(property.Value); if (diag.ShowDialog(this) == DialogResult.OK) { file.SetProperty(file.GetPropertyIndex(property), new KeyValuePair("ANIM", diag.ResultAnim.ToString())); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); ReloadMetaTreeView(); wasModified = true; } return; } catch (Exception ex) { Debug.WriteLine(ex.Message); Trace.WriteLine("Invalid ANIM value: " + property.Value); MessageBox.Show(this, "Failed to parse ANIM value, aborting to normal functionality. Please make sure the value only includes hexadecimal characters (0-9,A-F) and has no more than 8 characters."); } break; case "BOX" when file.Filetype == PckFileType.SkinFile: try { using BoxEditor diag = new BoxEditor(property.Value, IsSubPCKNode(treeViewMain.SelectedNode.FullPath)); if (diag.ShowDialog(this) == DialogResult.OK) { file.SetProperty(file.GetPropertyIndex(property), new KeyValuePair("BOX", diag.Result.ToString())); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); ReloadMetaTreeView(); wasModified = true; } return; } catch (Exception ex) { Debug.WriteLine(ex.Message); Trace.WriteLine("Invalid BOX value: " + property.Value); MessageBox.Show(this, "Failed to parse BOX value, aborting to normal functionality."); } break; default: break; } using (AddPropertyPrompt addProperty = new AddPropertyPrompt(property)) { if (addProperty.ShowDialog(this) == DialogResult.OK) { file.SetProperty(file.GetPropertyIndex(property), addProperty.Property); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); ReloadMetaTreeView(); wasModified = true; } } } } } private void cloneFileToolStripMenuItem_Click(object sender, EventArgs e) { TreeNode node = treeViewMain.SelectedNode; if (node == null || !node.IsTagOfType()) return; string path = node.FullPath; using TextPrompt diag = new TextPrompt(node.Tag is null ? Path.GetFileName(node.FullPath) : node.FullPath); diag.contextLabel.Text = $"Creating a clone of \"{path}\". Ensure that the path isn't yet."; diag.OKButtonText = "Clone"; if (diag.ShowDialog(this) == DialogResult.OK) { if (node.Tag is PckFileData file) { TreeNode newNode = new TreeNode(); newNode.Text = Path.GetFileName(diag.NewText); var newFile = new PckFileData(diag.NewText, file.Filetype); foreach (var property in file.GetProperties()) { newFile.AddProperty(property); } newFile.SetData(file.Data); newFile.Filename = diag.NewText; newNode.Tag = newFile; newNode.ImageIndex = node.ImageIndex; newNode.SelectedImageIndex = node.SelectedImageIndex; if (GetAllChildNodes(treeViewMain.Nodes).Find(n => n.FullPath == diag.NewText) != null) { MessageBox.Show( this, $"A file with the path \"{diag.NewText}\" already exists. " + $"Please try again with a different name.", "Key already exists"); return; } if (node.Parent == null) treeViewMain.Nodes.Insert(node.Index + 1, newNode); //adds generated file node else node.Parent.Nodes.Insert(node.Index + 1, newNode);//adds generated file node to selected folder if (!IsSubPCKNode(node.FullPath)) currentPCK.InsertFile(node.Index + 1, newFile); else RebuildSubPCK(node.FullPath); BuildMainTreeView(); wasModified = true; } } } private void deleteEntryToolStripMenuItem_Click(object sender, EventArgs e) { if (treeMeta.SelectedNode is TreeNode t && t.Tag is KeyValuePair property && treeViewMain.SelectedNode is TreeNode main && main.Tag is PckFileData file && file.RemoveProperty(property)) { treeMeta.SelectedNode.Remove(); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); wasModified = true; } } private void ReloadMetaTreeView() { treeMeta.Nodes.Clear(); if (treeViewMain.SelectedNode is TreeNode node && node.Tag is PckFileData file) { foreach (var property in file.GetProperties()) { treeMeta.Nodes.Add(CreateNode(property.Key, property)); } } } private void addEntryToolStripMenuItem_Click_1(object sender, EventArgs e) { if (treeViewMain.SelectedNode is TreeNode t && t.Tag is PckFileData file) { using AddPropertyPrompt addProperty = new AddPropertyPrompt(); if (addProperty.ShowDialog(this) == DialogResult.OK) { file.AddProperty(addProperty.Property); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); ReloadMetaTreeView(); wasModified = true; } } } #region drag and drop for main tree node // Most of the code below is modified code from this link: // https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.treeview.itemdrag?view=windowsdesktop-6.0 // - MattNL private void treeViewMain_ItemDrag(object sender, ItemDragEventArgs e) { if (e.Button != MouseButtons.Left || e.Item is not TreeNode node) return; if ((node.TryGetTagData(out PckFileData file) && currentPCK.Contains(file.Filename, file.Filetype)) || node.Parent is TreeNode) { treeViewMain.DoDragDrop(node, DragDropEffects.Move); } } private void treeViewMain_DragOver(object sender, DragEventArgs e) { Point dragLocation = new Point(e.X, e.Y); TreeNode node = treeViewMain.GetNodeAt(treeViewMain.PointToClient(dragLocation)); treeViewMain.SelectedNode = node.IsTagOfType() ? null : node; } private void treeViewMain_DragEnter(object sender, DragEventArgs e) { e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : e.AllowedEffect; } private void treeViewMain_DragDrop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop) && e.Data.GetData(DataFormats.FileDrop) is string[] files) { ImportFiles(files); return; } string dataFormat = typeof(TreeNode).FullName; if (!e.Data.GetDataPresent(dataFormat)) return; // Retrieve the client coordinates of the drop location. Point dragLocation = new Point(e.X, e.Y); Point targetPoint = treeViewMain.PointToClient(dragLocation); if (!treeViewMain.ClientRectangle.Contains(targetPoint)) return; // Retrieve the node at the drop location. TreeNode targetNode = treeViewMain.GetNodeAt(targetPoint); if (targetNode is null) return; bool isTargetPckFile = targetNode.IsTagOfType(); if (e.Data.GetData(dataFormat) is not TreeNode draggedNode) { Debug.WriteLine("Dragged data was not of type TreeNode."); return; } if (targetNode.Equals(draggedNode.Parent)) { Debug.WriteLine("target node is parent of dragged node... nothing done."); return; } if (draggedNode.Equals(targetNode.Parent)) { Debug.WriteLine("dragged node is parent of target node... nothing done."); return; } if (targetNode.Parent == null && isTargetPckFile && draggedNode.Parent == null) { Debug.WriteLine("target node is file and is in the root... nothing done."); return; } if ((targetNode.Parent?.Equals(draggedNode.Parent) ?? false) && isTargetPckFile) { Debug.WriteLine("target node and dragged node have the same parent... nothing done."); return; } Debug.WriteLine($"Target drop location is {(isTargetPckFile ? "file" : "folder")}."); // Retrieve the node that was dragged. if (draggedNode.TryGetTagData(out PckFileData draggedFile) && targetNode.FullPath != draggedFile.Filename) { Debug.WriteLine(draggedFile.Filename + " was droped onto " + targetNode.FullPath); string newFilePath = Path.Combine(isTargetPckFile ? Path.GetDirectoryName(targetNode.FullPath) : targetNode.FullPath, Path.GetFileName(draggedFile.Filename)); Debug.WriteLine("New filepath: " + newFilePath); draggedFile.Filename = newFilePath; wasModified = true; BuildMainTreeView(); return; } else { List pckFiles = GetEndingNodes(draggedNode.Nodes).Where(t => t.IsTagOfType()).Select(t => t.Tag as PckFileData).ToList(); string oldPath = draggedNode.FullPath; string newPath = Path.Combine(isTargetPckFile ? Path.GetDirectoryName(targetNode.FullPath) : targetNode.FullPath, draggedNode.Text).Replace('\\', '/'); foreach (var pckFile in pckFiles) { pckFile.Filename = Path.Combine(newPath, pckFile.Filename.Substring(oldPath.Length + 1)).Replace('\\', '/'); } wasModified = true; BuildMainTreeView(); } } private IEnumerable GetEndingNodes(TreeNodeCollection collection) { List trailingNodes = new List(collection.Count); foreach (TreeNode node in collection) { if (node.Nodes.Count > 0) { trailingNodes.AddRange(GetEndingNodes(node.Nodes)); continue; } trailingNodes.Add(node); } return trailingNodes; } private void ImportFiles(string[] files) { int addedCount = 0; foreach (var file in files) { using AddFilePrompt addFile = new AddFilePrompt(Path.GetFileName(file)); if (addFile.ShowDialog(this) != DialogResult.OK) continue; if (currentPCK.Contains(addFile.Filepath, addFile.Filetype)) { MessageBox.Show(this, $"'{addFile.Filepath}' of type {addFile.Filetype} already exists.", "Import failed", MessageBoxButtons.OK, MessageBoxIcon.Warning); continue; } currentPCK.CreateNewFile(addFile.Filepath, addFile.Filetype, () => File.ReadAllBytes(file)); addedCount++; BuildMainTreeView(); wasModified = true; } Trace.TraceInformation("[{0}] Imported {1} file(s).", nameof(ImportFiles), addedCount); } #endregion private PckFile InitializePack(int packId, int packVersion, string packName, bool createSkinsPCK) { var pack = new PckFile(3); PckFileData zeroFile = pack.CreateNewFile("0", PckFileType.InfoFile); zeroFile.AddProperty("PACKID", packId); zeroFile.AddProperty("PACKVERSION", packVersion); var locFile = new LOCFile(); locFile.InitializeDefault(packName); pack.CreateNewFile("localisation.loc", PckFileType.LocalisationFile, new LOCFileWriter(locFile, 2)); pack.CreateNewFileIf(createSkinsPCK, "Skins.pck", PckFileType.SkinDataFile, new PckFileWriter(new PckFile(3, true), LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian)); return pack; } private PckFile InitializeTexturePack(int packId, int packVersion, string packName, string res, bool createSkinsPCK) { PckFile pack = InitializePack(packId, packVersion, packName, createSkinsPCK); PckFile infoPCK = new PckFile(3); PckFileData icon = infoPCK.CreateNewFile("icon.png", PckFileType.TextureFile); icon.SetTexture(Resources.TexturePackIcon); PckFileData comparison = infoPCK.CreateNewFile("comparison.png", PckFileType.TextureFile); comparison.SetTexture(Resources.Comparison); PckFileData texturepackInfo = pack.CreateNewFile($"{res}/{res}Info.pck", PckFileType.TexturePackInfoFile); texturepackInfo.AddProperty("PACKID", "0"); texturepackInfo.AddProperty("DATAPATH", $"{res}Data.pck"); texturepackInfo.SetData(new PckFileWriter(infoPCK, LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian)); return pack; } private PckFile InitializeMashUpPack(int packId, int packVersion, string packName, string res) { PckFile pack = InitializeTexturePack(packId, packVersion, packName, res, true); PckFileData gameRuleFile = pack.CreateNewFile("GameRules.grf", PckFileType.GameRulesFile); GameRuleFile grfFile = new GameRuleFile(); grfFile.AddRule("MapOptions", new KeyValuePair("seed", "0"), new KeyValuePair("baseSaveName", string.Empty), new KeyValuePair("flatworld", "false"), new KeyValuePair("texturePackId", packId.ToString()) ); grfFile.AddRule("LevelRules") .AddRule("UpdatePlayer", new KeyValuePair("yRot", "0"), new KeyValuePair("xRot", "0"), new KeyValuePair("spawnX", "0"), new KeyValuePair("spawnY", "0"), new KeyValuePair("spawnZ", "0") ); gameRuleFile.SetData(new GameRuleFileWriter(grfFile)); return pack; } private void skinPackToolStripMenuItem_Click(object sender, EventArgs e) { CheckSaveState(); TextPrompt namePrompt = new TextPrompt(); namePrompt.OKButtonText = "Ok"; if (namePrompt.ShowDialog(this) == DialogResult.OK) { currentPCK = InitializePack(new Random().Next(8000, int.MaxValue), 0, namePrompt.NewText, true); isTemplateFile = true; wasModified = true; LoadEditorTab(); } } private void texturePackToolStripMenuItem_Click(object sender, EventArgs e) { CheckSaveState(); CreateTexturePackPrompt packPrompt = new CreateTexturePackPrompt(); if (packPrompt.ShowDialog(this) == DialogResult.OK) { currentPCK = InitializeTexturePack(new Random().Next(8000, int.MaxValue), 0, packPrompt.PackName, packPrompt.PackRes, packPrompt.CreateSkinsPck); isTemplateFile = true; wasModified = true; LoadEditorTab(); } } private void mashUpPackToolStripMenuItem_Click(object sender, EventArgs e) { CheckSaveState(); CreateTexturePackPrompt packPrompt = new CreateTexturePackPrompt(); if (packPrompt.ShowDialog(this) == DialogResult.OK) { currentPCK = InitializeMashUpPack(new Random().Next(8000, int.MaxValue), 0, packPrompt.PackName, packPrompt.PackRes); isTemplateFile = true; wasModified = false; LoadEditorTab(); } } private void quickChangeToolStripMenuItem_Click(object sender, EventArgs e) { using AdvancedOptions advanced = new AdvancedOptions(currentPCK); advanced.IsLittleEndian = LittleEndianCheckBox.Checked; if (advanced.ShowDialog(this) == DialogResult.OK) { wasModified = true; BuildMainTreeView(); } } private void closeToolStripMenuItem_Click(object sender, EventArgs e) { CheckSaveState(); CloseEditorTab(); } private void aboutToolStripMenuItem_Click(object sender, EventArgs e) { using CreditsForm info = new CreditsForm(); info.ShowDialog(this); } private void treeViewMain_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyCode) { case Keys.Delete: deleteFileToolStripMenuItem_Click(sender, e); break; case Keys.F2: renameFileToolStripMenuItem_Click(sender, e); break; } } private void treeViewMain_BeforeLabelEdit(object sender, NodeLabelEditEventArgs e) { // for now name edits are done through the 'rename' context menu item // TODO: add folder renaming //e.CancelEdit = e.Node.Tag is PckFile.FileData; e.CancelEdit = true; } public string GetDataPath() { return Path.Combine(Path.GetDirectoryName(saveLocation), "Data"); } public bool HasDataFolder() { return Directory.Exists(GetDataPath()); } public bool CreateDataFolder() { if (!HasDataFolder()) { DialogResult result = MessageBox.Show(this, "There is not a \"Data\" folder present in the pack folder. Would you like to create one?", "Folder missing", MessageBoxButtons.YesNo); if (result == DialogResult.No) return false; else Directory.CreateDirectory(GetDataPath()); } return true; } private void treeMeta_KeyDown(object sender, KeyEventArgs e) { if (e.KeyData == Keys.Delete) deleteEntryToolStripMenuItem_Click(sender, e); } private void importExtractedSkinsFolder(object sender, EventArgs e) { using FolderBrowserDialog contents = new FolderBrowserDialog(); if (contents.ShowDialog(this) == DialogResult.OK && Directory.Exists(contents.SelectedPath)) { string filepath = treeViewMain.SelectedNode?.FullPath ?? ""; if (treeViewMain.SelectedNode is not null && treeViewMain.SelectedNode.IsTagOfType()) filepath = treeViewMain.SelectedNode.Parent?.FullPath ?? ""; foreach (var fullfilename in Directory.GetFiles(contents.SelectedPath, "dlc*.png")) { string filename = Path.GetFileName(fullfilename); // only accept skin or cape named files if (!filename.StartsWith("dlcskin") && !filename.StartsWith("dlccape")) continue; // sets file type based on wether its a cape or skin PckFileType pckfiletype = filename.StartsWith("dlccape", StringComparison.OrdinalIgnoreCase) ? PckFileType.CapeFile : PckFileType.SkinFile; string pckfilepath = Path.Combine(filepath, filename); PckFileData newFile = currentPCK.CreateNewFile(pckfilepath, pckfiletype); byte[] filedata = File.ReadAllBytes(fullfilename); newFile.SetData(filedata); if (File.Exists(fullfilename + ".txt")) { string propertiesFileContent = File.ReadAllText(fullfilename + ".txt"); string[] properties = propertiesFileContent.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (string property in properties) { string[] param = property.Split(':'); if (param.Length < 2) continue; string key = param[0]; string value = param[1]; newFile.AddProperty(key, value); } } } BuildMainTreeView(); wasModified = true; } } private bool TryGetLocFile(out LOCFile locFile) { if (!currentPCK.TryGetFile("localisation.loc", PckFileType.LocalisationFile, out PckFileData locdata) && !currentPCK.TryGetFile("languages.loc", PckFileType.LocalisationFile, out locdata)) { locFile = null; return false; } try { using (var stream = new MemoryStream(locdata.Data)) { var reader = new LOCFileReader(); locFile = reader.FromStream(stream); } return true; } catch (Exception ex) { Debug.WriteLine(ex.Message); } locFile = null; return false; } private bool TrySetLocFile(in LOCFile locFile) { if (!currentPCK.TryGetFile("localisation.loc", PckFileType.LocalisationFile, out PckFileData locdata) && !currentPCK.TryGetFile("languages.loc", PckFileType.LocalisationFile, out locdata)) { return false; } try { locdata.SetData(new LOCFileWriter(locFile, 2)); return true; } catch (Exception ex) { Debug.WriteLine(ex.Message); } return false; } [Obsolete("Refactor or remove this")] private void importSkinToolStripMenuItem_Click(object sender, EventArgs e) { using (OpenFileDialog contents = new OpenFileDialog()) { contents.Title = "Select Extracted Skin File"; contents.Filter = "Skin File (*.png)|*.png"; if (contents.ShowDialog(this) == DialogResult.OK) { string skinNameImport = Path.GetFileName(contents.FileName); byte[] data = File.ReadAllBytes(contents.FileName); PckFileData mfNew = currentPCK.CreateNewFile(skinNameImport, PckFileType.SkinFile); mfNew.SetData(data); string propertyFile = Path.GetFileNameWithoutExtension(contents.FileName) + ".txt"; if (File.Exists(propertyFile)) { string[] txtProperties = File.ReadAllLines(propertyFile); if ((txtProperties.Contains("DISPLAYNAMEID") && txtProperties.Contains("DISPLAYNAME")) || txtProperties.Contains("THEMENAMEID") && txtProperties.Contains("THEMENAME") && TryGetLocFile(out LOCFile locFile)) { // do stuff //l.AddLocKey(locThemeId, locTheme); //using (var stream = new MemoryStream()) //{ // LOCFileWriter.Write(stream, locFile); // locdata.SetData(stream.ToArray()); //} } try { foreach (string prop in txtProperties) { string[] arg = prop.Split(':'); if (arg.Length < 2) continue; string key = arg[0]; string value = arg[1]; if (key == "DISPLNAMEID" || key == "THEMENAMEID") { } mfNew.AddProperty(key, value); } wasModified = true; } catch (Exception ex) { MessageBox.Show(this, ex.Message); } } } } } private void folderToolStripMenuItem_Click(object sender, EventArgs e) { TextPrompt folderNamePrompt = new TextPrompt(); if (treeViewMain.SelectedNode is not null) folderNamePrompt.contextLabel.Text = $"New folder at the location of \"{treeViewMain.SelectedNode.FullPath}\""; folderNamePrompt.OKButtonText = "Add"; if (folderNamePrompt.ShowDialog(this) == DialogResult.OK) { TreeNode folerNode = CreateNode(folderNamePrompt.NewText); folerNode.ImageIndex = 0; folerNode.SelectedImageIndex = 0; TreeNodeCollection nodeCollection = treeViewMain.Nodes; if (treeViewMain.SelectedNode is TreeNode node) { if (node.Tag is PckFileData fd && (fd.Filetype != PckFileType.TexturePackInfoFile && fd.Filetype != PckFileType.SkinDataFile)) { if (node.Parent is TreeNode parentNode) { nodeCollection = parentNode.Nodes; } } else nodeCollection = node.Nodes; } nodeCollection.Add(folerNode); } } private void howToMakeABasicSkinPackToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=A43aHRHkKxk"); } private void howToMakeACustomSkinModelToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=pEC_ug55lag"); } private void howToMakeCustomSkinModelsbedrockToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=6z8NTogw5x4"); } private void howToMakeCustomMusicToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=v6EYr4zc7rI"); } private void howToInstallPcksDirectlyToWiiUToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=hRQagnEplec"); } private void pckCenterReleaseToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=E_6bXSh6yqw"); } private void howPCKsWorkToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.youtube.com/watch?v=hTlImrRrCKQ"); } private void toPhoenixARCDeveloperToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://cash.app/$PhoenixARC"); } private void toNobledezJackToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://www.paypal.me/realnobledez"); } private void forMattNLContributorToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://ko-fi.com/mattnl"); } private void joinDevelopmentDiscordToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://discord.gg/aJtZNFVQTv"); } private void OpenPck_MouseEnter(object sender, EventArgs e) { pckOpen.Image = Resources.pckOpen; } private void OpenPck_MouseLeave(object sender, EventArgs e) { pckOpen.Image = Resources.pckClosed; } private void CheckSaveState() { if (currentPCK is not null && wasModified && MessageBox.Show(this, "Save PCK?", "Unsaved PCK", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { if (isTemplateFile || string.IsNullOrEmpty(saveLocation)) { SaveTemplate(); return; } Save(saveLocation); } } private void OpenPck_DragEnter(object sender, DragEventArgs e) { pckOpen.Image = Resources.pckDrop; string[] files = (string[])e.Data.GetData(DataFormats.FileDrop); foreach (string file in files) { string ext = Path.GetExtension(file); if (ext.Equals(".pck", StringComparison.CurrentCultureIgnoreCase)) e.Effect = DragDropEffects.Copy; return; } } private void OpenPck_DragDrop(object sender, DragEventArgs e) { string[] Filepaths = (string[])e.Data.GetData(DataFormats.FileDrop, false); if (Filepaths.Length > 1) MessageBox.Show(this, "Only one pck file at a time is currently supported"); LoadPckFromFile(Filepaths[0]); } private void OpenPck_DragLeave(object sender, EventArgs e) { pckOpen.Image = Resources.pckClosed; } private void OpenPck_Click(object sender, EventArgs e) { openToolStripMenuItem_Click(sender, e); } private void savePCK(object sender, EventArgs e) { if (!string.IsNullOrEmpty(saveLocation)) Save(saveLocation); } private void saveAsPCK(object sender, EventArgs e) { SaveTemplate(); } private void SetNodeIcon(TreeNode node, PckFileType type) { switch (type) { case PckFileType.AudioFile: node.ImageIndex = 1; node.SelectedImageIndex = 1; break; case PckFileType.LocalisationFile: node.ImageIndex = 3; node.SelectedImageIndex = 3; break; case PckFileType.TexturePackInfoFile: goto default; node.ImageIndex = 4; node.SelectedImageIndex = 4; break; case PckFileType.ColourTableFile: node.ImageIndex = 6; node.SelectedImageIndex = 6; break; case PckFileType.ModelsFile: goto default; node.ImageIndex = 8; node.SelectedImageIndex = 8; break; case PckFileType.SkinDataFile: goto default; node.ImageIndex = 7; node.SelectedImageIndex = 7; break; case PckFileType.GameRulesFile: node.ImageIndex = 9; node.SelectedImageIndex = 9; break; case PckFileType.GameRulesHeader: node.ImageIndex = 10; node.SelectedImageIndex = 10; break; case PckFileType.InfoFile: node.ImageIndex = 11; node.SelectedImageIndex = 11; break; case PckFileType.SkinFile: node.ImageIndex = 12; node.SelectedImageIndex = 12; break; case PckFileType.CapeFile: node.ImageIndex = 13; node.SelectedImageIndex = 13; break; case PckFileType.TextureFile: node.ImageIndex = 14; node.SelectedImageIndex = 14; break; case PckFileType.BehavioursFile: node.ImageIndex = 15; node.SelectedImageIndex = 15; break; case PckFileType.MaterialFile: node.ImageIndex = 16; node.SelectedImageIndex = 16; break; default: // unknown file format node.ImageIndex = 5; node.SelectedImageIndex = 5; break; } } private void SetFileType(PckFileType type) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file)) { Debug.WriteLine($"Setting {file.Filetype} to {type}"); file.Filetype = type; SetNodeIcon(treeViewMain.SelectedNode, type); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); } } [Obsolete()] private void addTextureToolStripMenuItem_Click(object sender, EventArgs e) { using OpenFileDialog fileDialog = new OpenFileDialog(); fileDialog.Filter = "Texture File(*.png,*.tga)|*.png;*.tga"; if (fileDialog.ShowDialog(this) == DialogResult.OK) { using TextPrompt renamePrompt = new TextPrompt(Path.GetFileName(fileDialog.FileName)); renamePrompt.LabelText = "Path"; if (renamePrompt.ShowDialog(this) == DialogResult.OK && !string.IsNullOrEmpty(renamePrompt.NewText)) { if (currentPCK.Contains(renamePrompt.NewText, PckFileType.TextureFile)) { MessageBox.Show(this, $"'{renamePrompt.NewText}' already exists.", "Import failed", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } PckFileData file = currentPCK.CreateNewFile(renamePrompt.NewText, PckFileType.TextureFile, () => File.ReadAllBytes(fileDialog.FileName)); BuildMainTreeView(); wasModified = true; } } } private void viewFileInfoToolStripMenuItem_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.Tag is PckFileData file) { MessageBox.Show(this, "File path: " + file.Filename + "\nAssigned File type: " + (int)file.Filetype + " (" + file.Filetype + ")" + "\nFile size: " + file.Size + "\nProperties count: " + file.PropertyCount , Path.GetFileName(file.Filename) + " file info"); } } private void generateMipMapTextureToolStripMenuItem_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.Tag is PckFileData file && file.Filetype == PckFileType.TextureFile) { string textureDirectory = Path.GetDirectoryName(file.Filename); string textureName = Path.GetFileNameWithoutExtension(file.Filename); if (file.IsMipmappedFile()) return; string textureExtension = Path.GetExtension(file.Filename); using NumericPrompt numericPrompt = new NumericPrompt(0); numericPrompt.Minimum = 1; numericPrompt.Maximum = 4; // 5 is the presumed max MipMap level numericPrompt.ContextLabel.Text = "You can enter the amount of MipMap levels that you would like to generate. " + "For example: if you enter 2, MipMapLevel1.png and MipMapLevel2.png will be generated"; numericPrompt.TextLabel.Text = "Levels"; if (numericPrompt.ShowDialog(this) == DialogResult.OK) { for (int i = 2; i < 2 + numericPrompt.SelectedValue; i++) { string mippedPath = $"{textureDirectory}/{textureName}MipMapLevel{i}{textureExtension}"; Debug.WriteLine(mippedPath); if (currentPCK.HasFile(mippedPath, PckFileType.TextureFile)) currentPCK.RemoveFile(currentPCK.GetFile(mippedPath, PckFileType.TextureFile)); PckFileData MipMappedFile = new PckFileData(mippedPath, PckFileType.TextureFile); Image originalTexture = file.GetTexture(); int NewWidth = Math.Max(originalTexture.Width / (int)Math.Pow(2, i - 1), 1); int NewHeight = Math.Max(originalTexture.Height / (int)Math.Pow(2, i - 1), 1); Rectangle tileArea = new Rectangle(0, 0, NewWidth, NewHeight); Image mippedTexture = new Bitmap(NewWidth, NewHeight); using (Graphics gfx = Graphics.FromImage(mippedTexture)) { gfx.SmoothingMode = SmoothingMode.None; gfx.InterpolationMode = InterpolationMode.NearestNeighbor; gfx.PixelOffsetMode = PixelOffsetMode.HighQuality; gfx.DrawImage(originalTexture, tileArea); } MipMappedFile.SetTexture(mippedTexture); currentPCK.InsertFile(currentPCK.IndexOfFile(file) + i - 1, MipMappedFile); } BuildMainTreeView(); } } } private void colourscolToolStripMenuItem_Click(object sender, EventArgs e) { if (currentPCK.TryGetFile("colours.col", PckFileType.ColourTableFile, out _)) { MessageBox.Show(this, "A color table file already exists in this PCK and a new one cannot be created.", "Operation aborted"); return; } PckFileData newColorFile = currentPCK.CreateNewFile("colours.col", PckFileType.ColourTableFile); newColorFile.SetData(Resources.tu69colours); BuildMainTreeView(); } private void tabControl_Selecting(object sender, TabControlCancelEventArgs e) { e.Cancel = !isSelectingTab; } private void as3DSTextureFileToolStripMenuItem_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file) && file.Filetype == PckFileType.SkinFile) { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "3DS Texture|*.3dst"; saveFileDialog.DefaultExt = ".3dst"; if (saveFileDialog.ShowDialog(this) == DialogResult.OK) { Image img = file.GetTexture(); var writer = new _3DSTextureWriter(img); writer.WriteToFile(saveFileDialog.FileName); } } } private void addMultipleEntriesToolStripMenuItem1_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file)) { using (var input = new MultiTextPrompt()) { if (input.ShowDialog(this) == DialogResult.OK) { foreach (string line in input.TextOutput) { int idx = line.IndexOf(' '); if (idx == -1 || line.Length - 1 == idx) continue; file.AddProperty(line.Substring(0, idx), line.Substring(idx + 1)); } ReloadMetaTreeView(); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); wasModified = true; } } } } private void correctSkinDecimalsToolStripMenuItem_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file) && file.Filetype == PckFileType.SkinFile) { foreach (KeyValuePair p in file.GetProperties().ToList()) { if (p.Key == "BOX" || p.Key == "OFFSET") file.SetProperty(file.GetPropertyIndex(p), new KeyValuePair(p.Key, p.Value.Replace(',', '.'))); } ReloadMetaTreeView(); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); wasModified = true; } } private void CreateSkinsPCKToolStripMenuItem1_Click(object sender, EventArgs e) { if (currentPCK.TryGetFile("Skins.pck", PckFileType.SkinDataFile, out _)) { MessageBox.Show(this, "A Skins.pck file already exists in this PCK and a new one cannot be created.", "Operation aborted"); return; } currentPCK.CreateNewFile("Skins.pck", PckFileType.SkinDataFile, new PckFileWriter(new PckFile(3, true), LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian)); BuildMainTreeView(); TreeNode skinsNode = treeViewMain.Nodes.Find("Skins.pck", false).FirstOrDefault(); TreeNode folderNode = CreateNode("Skins"); folderNode.ImageIndex = 0; folderNode.SelectedImageIndex = 0; skinsNode.Nodes.Add(folderNode); } private void editAllEntriesToolStripMenuItem_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file)) { string[] props = file.GetProperties().Select(p => p.Key + " " + p.Value).ToArray(); using (var input = new MultiTextPrompt(props)) { if (input.ShowDialog(this) == DialogResult.OK) { file.ClearProperties(); foreach (string line in input.TextOutput) { int idx = line.IndexOf(' '); if (idx == -1 || line.Length - 1 == idx) continue; file.AddProperty(line.Substring(0, idx).Replace(":", string.Empty), line.Substring(idx + 1)); } ReloadMetaTreeView(); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); wasModified = true; } } } } private void addFileToolStripMenuItem_Click(object sender, EventArgs e) { using var ofd = new OpenFileDialog(); // Suddenly, and randomly, this started throwing an exception because it wasn't formatted correctly? // So now it's formatted correctly and now displays the file type name in the dialog. ofd.Filter = "All files (*.*)|*.*"; ofd.Multiselect = false; if (ofd.ShowDialog(this) == DialogResult.OK) { using AddFilePrompt diag = new AddFilePrompt("res/" + Path.GetFileName(ofd.FileName)); if (diag.ShowDialog(this) == DialogResult.OK) { if (currentPCK.Contains(diag.Filepath, diag.Filetype)) { MessageBox.Show(this, $"'{diag.Filepath}' of type {diag.Filetype} already exists.", "Import failed", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } PckFileData file = currentPCK.CreateNewFile(diag.Filepath, diag.Filetype, () => File.ReadAllBytes(ofd.FileName)); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); BuildMainTreeView(); wasModified = true; } } return; } private void behavioursbinToolStripMenuItem_Click(object sender, EventArgs e) { if (currentPCK.TryGetFile("behaviours.bin", PckFileType.BehavioursFile, out _)) { MessageBox.Show(this, "A behaviours file already exists in this PCK and a new one cannot be created.", "Operation aborted"); return; } currentPCK.CreateNewFile("behaviours.bin", PckFileType.BehavioursFile, BehaviourResources.BehaviourFileInitializer); BuildMainTreeView(); } private void entityMaterialsbinToolStripMenuItem_Click(object sender, EventArgs e) { if (currentPCK.TryGetFile("entityMaterials.bin", PckFileType.MaterialFile, out _)) { MessageBox.Show(this, "A behaviours file already exists in this PCK and a new one cannot be created.", "Operation aborted"); return; } currentPCK.CreateNewFile("entityMaterials.bin", PckFileType.MaterialFile, MaterialResources.MaterialsFileInitializer); BuildMainTreeView(); } private void trelloBoardToolStripMenuItem_Click(object sender, EventArgs e) { Process.Start("https://trello.com/b/0XLNOEbe/pck-studio"); } private void wavBinkaToolStripMenuItem_Click(object sender, EventArgs e) { using OpenFileDialog fileDialog = new OpenFileDialog { Multiselect = true, Filter = "WAV files (*.wav)|*.wav", Title = "Please choose WAV files to convert to BINKA" }; if (fileDialog.ShowDialog(this) == DialogResult.OK) { using ItemSelectionPopUp dialog = new ItemSelectionPopUp( "Level 1 (Best Quality)", "Level 2", "Level 3", "Level 4", "Level 5", "Level 6", "Level 7", "Level 8", "Level 9 (Worst Quality)") { LabelText = "Compression", ButtonText = "OK" }; if(dialog.ShowDialog(this) == DialogResult.OK) { BinkaConverter.ToBinka( fileDialog.FileNames, new DirectoryInfo(Path.GetDirectoryName(fileDialog.FileName)), dialog.SelectedIndex + 1 // compression level ); } } } private void binkaWavToolStripMenuItem_Click(object sender, EventArgs e) { using OpenFileDialog fileDialog = new OpenFileDialog { Multiselect = true, Filter = "BINKA files (*.binka)|*.binka", Title = "Please choose BINKA files to convert to WAV" }; if (fileDialog.ShowDialog(this) == DialogResult.OK) { BinkaConverter.ToWav(fileDialog.FileNames, new DirectoryInfo(Path.GetDirectoryName(fileDialog.FileName))); } } private void fullBoxSupportToolStripMenuItem_CheckedChanged(object sender, EventArgs e) { currentPCK.SetVersion(fullBoxSupportToolStripMenuItem.Checked); } private void settingsToolStripMenuItem_Click(object sender, EventArgs e) { var appSettings = new AppSettingsForm(); appSettings.ShowDialog(this); } private void addBOXEntryToolStripMenuItem1_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode is TreeNode t && t.Tag is PckFileData file) { using BoxEditor diag = new BoxEditor(SkinBOX.Empty, IsSubPCKNode(treeViewMain.SelectedNode.FullPath)); if (diag.ShowDialog(this) == DialogResult.OK) { file.AddProperty("BOX", diag.Result); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); ReloadMetaTreeView(); wasModified = true; } return; } } private void addANIMEntryToolStripMenuItem1_Click(object sender, EventArgs e) { if (treeViewMain.SelectedNode.TryGetTagData(out PckFileData file)) { using ANIMEditor diag = new ANIMEditor(SkinANIM.Empty); if (diag.ShowDialog(this) == DialogResult.OK) { file.AddProperty("ANIM", diag.ResultAnim); RebuildSubPCK(treeViewMain.SelectedNode.FullPath); ReloadMetaTreeView(); wasModified = true; } return; } } private void checkForUpdatesToolStripMenuItem_Click(object sender, EventArgs e) { if (Program.Updater.IsUpdateAvailable(Application.ProductVersion)) { Program.UpdateToLatest("Would you like to download it?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, DialogResult.Yes); return; } MessageBox.Show(this, "Already up to date.", "No update available"); } [Obsolete] // the move functions are to eventually be removed in favor of drag and drop private void moveFile(int amount) { if (treeViewMain.SelectedNode is not TreeNode t || t.Tag is not PckFileData) return; PckFileData file = t.Tag as PckFileData; string path = t.FullPath; // skin and cape files only if (!(file.Filetype == PckFileType.SkinFile || file.Filetype == PckFileType.CapeFile)) return; PckFile pck = currentPCK; bool IsSubPCK = IsSubPCKNode(path); if (IsSubPCK) { using (var stream = new MemoryStream((GetSubPCK(path).Tag as PckFileData).Data)) { var reader = new PckFileReader(LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian); pck = reader.FromStream(stream); } } int index = pck.IndexOfFile(file); try { if (index + amount < 0 || index + amount > pck.FileCount) return; pck.RemoveFile(file); pck.InsertFile(index + amount, file); if (IsSubPCK) { using (var stream = new MemoryStream()) { var writer = new PckFileWriter(pck, LittleEndianCheckBox.Checked ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian); writer.WriteToStream(stream); (GetSubPCK(path).Tag as PckFileData).SetData(stream.ToArray()); } } BuildMainTreeView(); wasModified = true; } catch(Exception ex) { MessageBox.Show(this, "Can't move file under or above a folder"); } } [Obsolete] private void moveUpToolStripMenuItem_Click(object sender, EventArgs e) => moveFile(-1); [Obsolete] private void moveDownToolStripMenuItem_Click(object sender, EventArgs e) => moveFile(1); private void exitToolStripMenuItem_Click(object sender, EventArgs e) { Application.Exit(); } private void SetPckEndianness(OMI.Endianness endianness) { try { if (treeViewMain.SelectedNode.Tag is PckFileData file && (file.Filetype is PckFileType.AudioFile || file.Filetype is PckFileType.SkinDataFile || file.Filetype is PckFileType.TexturePackInfoFile)) { IDataFormatReader reader = file.Filetype is PckFileType.AudioFile ? new PckAudioFileReader(endianness == OMI.Endianness.BigEndian ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian) : new PckFileReader(endianness == OMI.Endianness.BigEndian ? OMI.Endianness.LittleEndian : OMI.Endianness.BigEndian); object pck = reader.FromStream(new MemoryStream(file.Data)); IDataFormatWriter writer = file.Filetype is PckFileType.AudioFile ? new PckAudioFileWriter((PckAudioFile)pck, endianness) : new PckFileWriter((PckFile)pck, endianness); file.SetData(writer); wasModified = true; MessageBox.Show($"\"{file.Filename}\" successfully converted to {(endianness == OMI.Endianness.LittleEndian ? "little" : "big")} endian.", "Converted PCK file"); } } catch (OverflowException) { MessageBox.Show(this, $"File was not a valid {(endianness != OMI.Endianness.LittleEndian ? "little" : "big")} endian PCK File.", "Not a valid PCK file"); return; } catch (Exception ex) { MessageBox.Show(this, ex.Message, "Not a valid PCK file"); return; } } private void littleEndianToolStripMenuItem_Click(object sender, EventArgs e) => SetPckEndianness(OMI.Endianness.LittleEndian); private void bigEndianToolStripMenuItem_Click(object sender, EventArgs e) => SetPckEndianness(OMI.Endianness.BigEndian); private void SetModelVersion(int version) { if (treeViewMain.SelectedNode.Tag is PckFileData file && file.Filetype is PckFileType.ModelsFile) { try { ModelContainer container = file.GetData(new ModelFileReader()); if (container.Version == version) { MessageBox.Show( this, $"This model container is already Version {version + 1}.", "Can't convert", MessageBoxButtons.OK, MessageBoxIcon.Error ); return; } if (version == 2 && MessageBox.Show( this, "Conversion to 1.14 models.bin format does not yet support parent declaration and may not be 100% accurate.\n" + "Would you like to continue?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes ) { return; } if (container.Version > 1 && MessageBox.Show( this, "Conversion from 1.14 models.bin format does not yet support parent parts and may not be 100% accurate.\n" + "Would you like to continue?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes ) { return; } file.SetData(new ModelFileWriter(container, version)); wasModified = true; MessageBox.Show( this, $"\"{file.Filename}\" successfully converted to Version {version + 1} format.", "Converted model container file" ); } catch (Exception ex) { MessageBox.Show(this, ex.Message, "Not a valid model container file."); return; } } } private void setModelVersion1ToolStripMenuItem_Click(object sender, EventArgs e) => SetModelVersion(0); private void setModelVersion2ToolStripMenuItem_Click(object sender, EventArgs e) => SetModelVersion(1); private void setModelVersion3ToolStripMenuItem_Click(object sender, EventArgs e) => SetModelVersion(2); } }