mirror of
https://github.com/KillzXGaming/Switch-Toolbox
synced 2024-11-23 13:03:15 +00:00
0c126e4155
Rewrote the compression handling from scatch. It's way easier and cleaner to add new formats code wise as it's handled like file formats. Added wip TVOL support (Touhou Azure Reflections) Added XCI support. Note I plan to improve NSP, XCI, NCA, etc later for exefs exporting. The compression rework now compresses via streams, so files get decompressed properly within archives as streams. Added hyrule warriors bin.gz compression along with archive rebuilding. Note i do not have texture rebuilding done just yet.
1111 lines
38 KiB
C#
1111 lines
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.ComponentModel;
|
|
using System.Windows.Forms;
|
|
using Toolbox.Library.Forms;
|
|
using Toolbox.Library.IO;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace Toolbox.Library
|
|
{
|
|
public enum ArchiveFileState
|
|
{
|
|
Empty = 0,
|
|
Archived = 1,
|
|
Added = 2,
|
|
Replaced = 4,
|
|
Renamed = 8,
|
|
Deleted = 16
|
|
}
|
|
|
|
/// <summary>
|
|
/// A common archive format interface used to edit archive file formats
|
|
/// </summary>
|
|
public interface IArchiveFile
|
|
{
|
|
bool CanAddFiles { get; }
|
|
bool CanRenameFiles { get; }
|
|
bool CanReplaceFiles { get; }
|
|
bool CanDeleteFiles { get; }
|
|
|
|
IEnumerable<ArchiveFileInfo> Files { get; }
|
|
|
|
void ClearFiles();
|
|
bool AddFile(ArchiveFileInfo archiveFileInfo);
|
|
bool DeleteFile(ArchiveFileInfo archiveFileInfo);
|
|
}
|
|
|
|
public class ArchiveFileInfo : INode
|
|
{
|
|
// Opens the file format automatically (may take longer to open the archive file)
|
|
[Browsable(false)]
|
|
public virtual bool OpenFileFormatOnLoad { get; set; }
|
|
|
|
[Browsable(false)]
|
|
// The source file. If an archive is in another archive, this is necessary to get the original path
|
|
public string SourceFile { get; internal set; }
|
|
|
|
[Browsable(false)]
|
|
public string ImageKey { get; set; }
|
|
|
|
[Browsable(false)]
|
|
public string SelectedImageKey { get; set; }
|
|
|
|
[Browsable(false)]
|
|
public STContextMenuStrip STContextMenuStrip;
|
|
|
|
[Browsable(false)]
|
|
public virtual STToolStripItem[] Menus { get; set; }
|
|
|
|
[Browsable(false)]
|
|
public FileType FileDataType = FileType.Default;
|
|
|
|
//Wether or not to check the file magic to determine the type
|
|
//This sets the icons if there's no proper extension, and may add more special operations
|
|
//This should be disabled on larger archives!
|
|
[Browsable(false)]
|
|
public virtual bool CheckFileMagic { get; set; } = false;
|
|
|
|
//Properties to show for the archive file when selected
|
|
[Browsable(false)]
|
|
public virtual object DisplayProperties { get; set; }
|
|
|
|
[Browsable(false)]
|
|
public virtual bool CanLoadFile { get; set; } = true;
|
|
|
|
|
|
[Browsable(false)]
|
|
public virtual IFileFormat OpenFile()
|
|
{
|
|
if (FileFormat != null)
|
|
return FileFormat;
|
|
|
|
if (FileDataStream != null)
|
|
{
|
|
return STFileLoader.OpenFileFormat(FileDataStream,
|
|
IOExtensions.RemoveIllegaleFolderNameCharacters(FileName), true, true);
|
|
}
|
|
else
|
|
{
|
|
return STFileLoader.OpenFileFormat(new MemoryStream(FileData),
|
|
IOExtensions.RemoveIllegaleFolderNameCharacters( FileName), false, true);
|
|
}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
public bool IsSupportedFileFormat()
|
|
{
|
|
if (FileData == null || FileData.Length <= 4)
|
|
return false;
|
|
|
|
using (var stream = new MemoryStream(FileData))
|
|
{
|
|
foreach (IFileFormat fileFormat in FileManager.GetFileFormats())
|
|
{
|
|
fileFormat.FileName = FileName;
|
|
if (fileFormat.Identify(stream))
|
|
return true;
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
public virtual Dictionary<string, string> ExtensionImageKeyLookup { get; }
|
|
|
|
public virtual void Replace()
|
|
{
|
|
string fileName = Path.GetFileName(FileName.RemoveIllegaleFileNameCharacters());
|
|
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
|
ofd.FileName = fileName;
|
|
ofd.DefaultExt = Path.GetExtension(fileName);
|
|
ofd.Filter = "Raw Data (*.*)|*.*";
|
|
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
FileData = File.ReadAllBytes(ofd.FileName);
|
|
}
|
|
}
|
|
|
|
public virtual void Export()
|
|
{
|
|
string fileName = Path.GetFileName(FileName.RemoveIllegaleFolderNameCharacters());
|
|
|
|
SaveFileDialog sfd = new SaveFileDialog();
|
|
sfd.FileName = fileName;
|
|
sfd.DefaultExt = Path.GetExtension(fileName);
|
|
sfd.Filter = "Raw Data (*.*)|*.*";
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
if (FileDataStream != null)
|
|
FileDataStream.ExportToFile(sfd.FileName);
|
|
else
|
|
File.WriteAllBytes(sfd.FileName, FileData);
|
|
}
|
|
}
|
|
|
|
public virtual string FileSize { get {return STMath.GetFileSize(FileData.Length, 4); } }
|
|
|
|
[Browsable(false)]
|
|
public IFileFormat FileFormat = null; //Format attached for saving
|
|
|
|
[Browsable(false)]
|
|
private byte[] _fileData = null;
|
|
|
|
//Full File Name
|
|
private string _fileName = string.Empty;
|
|
|
|
[Browsable(false)]
|
|
public string FileName
|
|
{
|
|
get
|
|
{
|
|
return _fileName;
|
|
}
|
|
set
|
|
{
|
|
_fileName = value;
|
|
}
|
|
}
|
|
|
|
public static void SaveFileFormat(ArchiveFileInfo archiveFile, IFileFormat fileFormat)
|
|
{
|
|
if (fileFormat != null && fileFormat.CanSave)
|
|
{
|
|
if (archiveFile.FileDataStream != null)
|
|
{
|
|
var mem = new System.IO.MemoryStream();
|
|
fileFormat.Save(mem);
|
|
archiveFile.FileDataStream = mem;
|
|
//Reload file data
|
|
fileFormat.Load(archiveFile.FileDataStream);
|
|
}
|
|
else
|
|
{
|
|
var mem = new System.IO.MemoryStream();
|
|
fileFormat.Save(mem);
|
|
archiveFile.FileData = STLibraryCompression.CompressFile(mem.ToArray(), fileFormat);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SaveFileFormat()
|
|
{
|
|
if (FileFormat != null && FileFormat.CanSave)
|
|
{
|
|
if (FileDataStream != null)
|
|
{
|
|
var mem = new System.IO.MemoryStream();
|
|
FileFormat.Save(mem);
|
|
FileDataStream = mem;
|
|
//Reload file data
|
|
FileFormat.Load(FileDataStream);
|
|
}
|
|
else
|
|
{
|
|
var mem = new System.IO.MemoryStream();
|
|
FileFormat.Save(mem);
|
|
FileData = STLibraryCompression.CompressFile(mem.ToArray(), FileFormat);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
public string Name { get; set; } = string.Empty; //File Name (No Path)
|
|
|
|
[Browsable(false)]
|
|
public virtual byte[] FileData
|
|
{
|
|
get { return _fileData; }
|
|
set { _fileData = value; }
|
|
}
|
|
|
|
public virtual Stream FileDataStream
|
|
{
|
|
get
|
|
{
|
|
if (_fileStream != null)
|
|
_fileStream.Position = 0;
|
|
|
|
return _fileStream;
|
|
}
|
|
set { _fileStream = value; }
|
|
}
|
|
|
|
protected Stream _fileStream = null;
|
|
|
|
[Browsable(false)]
|
|
public ArchiveFileState State { get; set; } = ArchiveFileState.Empty;
|
|
}
|
|
|
|
public class ArchiveBase : TreeNodeCustom
|
|
{
|
|
public IArchiveFile ArchiveFile; //The archive file being edited
|
|
|
|
public ArchiveBase(IArchiveFile archiveFile)
|
|
{
|
|
ArchiveFile = archiveFile;
|
|
}
|
|
}
|
|
|
|
//Wrapper for the archive file itself
|
|
public class ArchiveRootNodeWrapper : ArchiveBase, IContextMenuNode
|
|
{
|
|
//A list that links archive file infos to treenodes of varying types
|
|
public List<Tuple<ArchiveFileInfo, TreeNode>> FileNodes = new List<Tuple<ArchiveFileInfo, TreeNode>>();
|
|
|
|
public virtual object PropertyDisplay { get; set; }
|
|
|
|
public ArchiveRootNodeWrapper(string text, IArchiveFile archiveFile)
|
|
: base(archiveFile)
|
|
{
|
|
Text = text;
|
|
|
|
if (archiveFile is IPropertyContainer)
|
|
PropertyDisplay = ((IPropertyContainer)archiveFile).Property;
|
|
else
|
|
PropertyDisplay = new GenericArchiveProperties(archiveFile, text, this);
|
|
}
|
|
|
|
public void AddFileNode(ArchiveFileWrapper fileWrapper)
|
|
{
|
|
FileNodes.Add(Tuple.Create(fileWrapper.ArchiveFileInfo, (TreeNode)fileWrapper));
|
|
|
|
string FullName = SetFullPath(fileWrapper, this);
|
|
if (FullName != string.Empty)
|
|
{
|
|
Console.WriteLine($"Updating info {FullName}");
|
|
fileWrapper.ArchiveFileInfo.FileName = FullName;
|
|
}
|
|
}
|
|
|
|
public ToolStripItem[] GetContextMenuItems()
|
|
{
|
|
var ToolStrips = new ToolStripItem[]
|
|
{
|
|
new STToolStripItem("Save", SaveAction) { Enabled = ((IFileFormat)ArchiveFile).CanSave},
|
|
new STToolStripSeparator(),
|
|
new STToolStripItem("Repack", RepackAction){ Enabled = ArchiveFile.CanReplaceFiles},
|
|
new STToolStripItem("Extract All", ExtractAllAction),
|
|
new STToolStripSeparator(),
|
|
new STToolStripItem("Preview Archive", PreviewAction),
|
|
new STToolStripSeparator(),
|
|
new STToolStripItem("Add Folder", AddFolderAction) { Enabled = ArchiveFile.CanAddFiles},
|
|
new STToolStripItem("Add File", AddFileAction) { Enabled = ArchiveFile.CanAddFiles},
|
|
};
|
|
|
|
var toolStripList = ToolStrips.ToList();
|
|
if (ArchiveFile is IContextMenuNode)
|
|
{
|
|
toolStripList.AddRange(((IContextMenuNode)ArchiveFile).GetContextMenuItems());
|
|
}
|
|
|
|
return toolStripList.ToArray();
|
|
}
|
|
|
|
private void AddFolderAction(object sender, EventArgs args)
|
|
{
|
|
Nodes.Add(new ArchiveFolderNodeWrapper("NewFolder", ArchiveFile, this));
|
|
}
|
|
|
|
private void AddFileAction(object sender, EventArgs args)
|
|
{
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
|
ofd.Filter = "Raw Data (*.*)|*.*";
|
|
ofd.Multiselect = true;
|
|
|
|
if (ofd.ShowDialog() == DialogResult.OK)
|
|
{
|
|
TreeHelper.AddFiles(this, this, ofd.FileNames);
|
|
}
|
|
}
|
|
|
|
public void UpdateFileNames()
|
|
{
|
|
if (!ArchiveFile.CanRenameFiles)
|
|
return;
|
|
|
|
for (int i = 0; i < FileNodes.Count; i++)
|
|
{
|
|
string NewName = SetFullPath(FileNodes[i].Item2, this);
|
|
if (NewName != string.Empty)
|
|
FileNodes[i].Item1.FileName = NewName;
|
|
}
|
|
}
|
|
|
|
private static string SetFullPath(TreeNode node, TreeNode root)
|
|
{
|
|
if (node.TreeView == null) {
|
|
return string.Empty;
|
|
}
|
|
|
|
string nodePath = node.FullPath;
|
|
int startIndex = nodePath.IndexOf(root.Text);
|
|
if (startIndex > 0)
|
|
nodePath = nodePath.Substring(startIndex);
|
|
|
|
string slash = Path.DirectorySeparatorChar.ToString();
|
|
string slashAlt = Path.AltDirectorySeparatorChar.ToString();
|
|
|
|
string SetPath = nodePath.Replace(root.Text + slash, string.Empty).Replace(slash ?? "", slashAlt);
|
|
|
|
Console.WriteLine($"FullPath { node.FullPath}");
|
|
Console.WriteLine($"SetPath {SetPath}");
|
|
|
|
return SetPath;
|
|
}
|
|
|
|
private void EnableContextMenu(ToolStripItemCollection Items, string Key, bool Enabled)
|
|
{
|
|
foreach (ToolStripItem item in Items)
|
|
{
|
|
if (item.Text == Key)
|
|
item.Enabled = Enabled;
|
|
}
|
|
}
|
|
|
|
private void SaveAction(object sender, EventArgs args)
|
|
{
|
|
Save("", true);
|
|
}
|
|
|
|
private void Save(string FileName, bool UseDialog)
|
|
{
|
|
UpdateFileNames();
|
|
|
|
//Archive files are IFIleFormats
|
|
var FileFormat = ((IFileFormat)ArchiveFile);
|
|
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
if (UseDialog)
|
|
{
|
|
List<IFileFormat> formats = new List<IFileFormat>();
|
|
formats.Add(FileFormat);
|
|
|
|
SaveFileDialog sfd = new SaveFileDialog();
|
|
sfd.Filter = Utils.GetAllFilters(formats);
|
|
sfd.FileName = FileFormat.FileName;
|
|
|
|
if (sfd.ShowDialog() == DialogResult.OK)
|
|
FileName = sfd.FileName;
|
|
else
|
|
return;
|
|
}
|
|
|
|
STFileSaver.SaveFileFormat(FileFormat, FileName);
|
|
|
|
GC.Collect();
|
|
}
|
|
|
|
private void ExtractAllAction(object sender, EventArgs args)
|
|
{
|
|
TreeNode node = this;
|
|
|
|
var ParentPath = string.Empty;
|
|
if (node.Parent != null) //Archive can be attached to another archive
|
|
ParentPath = node.Parent.FullPath;
|
|
|
|
TreeHelper.ExtractAllFiles(ParentPath, Nodes);
|
|
}
|
|
|
|
private void RepackAction(object sender, EventArgs args)
|
|
{
|
|
FolderSelectDialog dlg = new FolderSelectDialog();
|
|
if (dlg.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string FolderPath = dlg.SelectedPath;
|
|
|
|
STProgressBar progressBar = new STProgressBar();
|
|
progressBar.Task = "Reading Directory...";
|
|
progressBar.Value = 0;
|
|
progressBar.StartPosition = FormStartPosition.CenterScreen;
|
|
progressBar.Show();
|
|
progressBar.Refresh();
|
|
|
|
var ProccessedFiles = TreeHelper.ReadFiles(FolderPath);
|
|
|
|
progressBar.Task = "Repacking Files...";
|
|
progressBar.Refresh();
|
|
|
|
ArchiveFile.ClearFiles();
|
|
|
|
for (int i = 0; i < ProccessedFiles.Count; i++)
|
|
{
|
|
progressBar.Value = (i * 100) / ProccessedFiles.Count;
|
|
progressBar.Task = $"Packing {ProccessedFiles[i].Item1}";
|
|
progressBar.Refresh();
|
|
|
|
ArchiveFile.AddFile(new ArchiveFileInfo()
|
|
{
|
|
FileName = ProccessedFiles[i].Item1,
|
|
FileData = File.ReadAllBytes(ProccessedFiles[i].Item2),
|
|
});
|
|
}
|
|
|
|
progressBar.Close();
|
|
progressBar.Dispose();
|
|
ProccessedFiles.Clear();
|
|
|
|
GC.Collect();
|
|
|
|
FillTreeNodes();
|
|
}
|
|
}
|
|
|
|
private void PreviewAction(object sender, EventArgs args)
|
|
{
|
|
ArchiveListPreviewForm previewFormatList = new ArchiveListPreviewForm();
|
|
previewFormatList.LoadArchive(ArchiveFile);
|
|
previewFormatList.Show();
|
|
}
|
|
|
|
public override void OnClick(TreeView treeView)
|
|
{
|
|
STPropertyGrid editor = (STPropertyGrid)LibraryGUI.GetActiveContent(typeof(STPropertyGrid));
|
|
if (editor == null)
|
|
{
|
|
editor = new STPropertyGrid();
|
|
LibraryGUI.LoadEditor(editor);
|
|
}
|
|
editor.Text = Text;
|
|
editor.Dock = DockStyle.Fill;
|
|
editor.LoadProperty(PropertyDisplay, OnPropertyChanged);
|
|
}
|
|
|
|
public virtual void OnPropertyChanged() {
|
|
Text = Name;
|
|
}
|
|
|
|
|
|
public class GenericArchiveProperties
|
|
{
|
|
private ArchiveRootNodeWrapper rootNode;
|
|
|
|
private IArchiveFile ArchiveFile;
|
|
|
|
[Category("Archive Properties")]
|
|
public string Name { get; set; }
|
|
|
|
[Category("Archive Properties")]
|
|
[DisplayName("File Count")]
|
|
public int FileCount
|
|
{
|
|
get
|
|
{
|
|
return rootNode.FileNodes != null ? rootNode.FileNodes.Count : 0;
|
|
}
|
|
}
|
|
|
|
public GenericArchiveProperties(IArchiveFile archiveFile, string text, ArchiveRootNodeWrapper root) {
|
|
ArchiveFile = archiveFile;
|
|
Name = text;
|
|
rootNode = root;
|
|
}
|
|
}
|
|
|
|
public void FillTreeNodes() {
|
|
FillTreeNodes(this, ArchiveFile);
|
|
}
|
|
|
|
private void FillDirectory(TreeNode parent, IEnumerable<INode> Nodes, IArchiveFile archiveFile)
|
|
{
|
|
foreach (var node in Nodes)
|
|
{
|
|
if (node is IDirectoryContainer)
|
|
{
|
|
var folder = new ArchiveFolderNodeWrapper(node.Name, archiveFile, this);
|
|
parent.Nodes.Add(folder);
|
|
|
|
if (((IDirectoryContainer)node).Nodes != null)
|
|
FillDirectory(folder, ((IDirectoryContainer)node).Nodes, archiveFile);
|
|
}
|
|
else if (node is ArchiveFileInfo)
|
|
{
|
|
ArchiveFileWrapper wrapperFile = new ArchiveFileWrapper(node.Name, (ArchiveFileInfo)node, this);
|
|
wrapperFile.Name = node.Name;
|
|
parent.Nodes.Add(wrapperFile);
|
|
AddFileNode(wrapperFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FillTreeNodes(TreeNode root, IArchiveFile archiveFile)
|
|
{
|
|
Nodes.Clear();
|
|
|
|
var rootText = root.Text;
|
|
var rootTextLength = rootText.Length;
|
|
var nodeFiles = archiveFile.Files;
|
|
|
|
if (archiveFile is IDirectoryContainer)
|
|
{
|
|
FillDirectory(root,((IDirectoryContainer)archiveFile).Nodes, archiveFile);
|
|
}
|
|
else //Else create directories by filename paths
|
|
{
|
|
|
|
int I = 0;
|
|
foreach (var node in archiveFile.Files)
|
|
{
|
|
if (!node.CanLoadFile)
|
|
continue;
|
|
|
|
if (!((IFileFormat)archiveFile).IFileInfo.InArchive && File.Exists(((IFileFormat)archiveFile).FilePath))
|
|
node.SourceFile = ((IFileFormat)archiveFile).FilePath;
|
|
|
|
string nodeString = node.FileName;
|
|
|
|
var roots = nodeString.Split(new char[] { '/' },
|
|
StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
// The initial parent is the root node
|
|
var parentNode = root;
|
|
var sb = new System.Text.StringBuilder(rootText, nodeString.Length + rootTextLength);
|
|
for (int rootIndex = 0; rootIndex < roots.Length; rootIndex++)
|
|
{
|
|
// Build the node name
|
|
var parentName = roots[rootIndex];
|
|
sb.Append("/");
|
|
sb.Append(parentName);
|
|
var nodeName = sb.ToString();
|
|
|
|
// Search for the node
|
|
var index = parentNode.Nodes.IndexOfKey(nodeName);
|
|
if (index == -1)
|
|
{
|
|
// Node was not found, add it
|
|
|
|
var folder = new ArchiveFolderNodeWrapper(parentName, archiveFile, this);
|
|
|
|
if (rootIndex == roots.Length - 1)
|
|
{
|
|
ArchiveFileWrapper wrapperFile = new ArchiveFileWrapper(parentName, node, this);
|
|
wrapperFile.Name = nodeName;
|
|
parentNode.Nodes.Add(wrapperFile);
|
|
parentNode = wrapperFile;
|
|
AddFileNode(wrapperFile);
|
|
}
|
|
else
|
|
{
|
|
folder.Name = nodeName;
|
|
parentNode.Nodes.Add(folder);
|
|
parentNode = folder;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Node was found, set that as parent and continue
|
|
parentNode = parentNode.Nodes[index];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Wrapper for folders
|
|
public class ArchiveFolderNodeWrapper : ArchiveBase, IContextMenuNode
|
|
{
|
|
public ArchiveRootNodeWrapper RootNode;
|
|
|
|
public virtual object PropertyDisplay { get; set; }
|
|
|
|
public bool CanReplace
|
|
{
|
|
set
|
|
{
|
|
if (value)
|
|
ContextMenuStrip.Items[1].Enabled = true;
|
|
else
|
|
ContextMenuStrip.Items[1].Enabled = false;
|
|
}
|
|
}
|
|
public bool CanRename = false;
|
|
public bool CanDelete
|
|
{
|
|
set
|
|
{
|
|
if (value)
|
|
ContextMenuStrip.Items[2].Enabled = true;
|
|
else
|
|
ContextMenuStrip.Items[2].Enabled = false;
|
|
}
|
|
}
|
|
|
|
public ArchiveFolderNodeWrapper(string text, IArchiveFile archiveFile, ArchiveRootNodeWrapper root ) : base(archiveFile)
|
|
{
|
|
RootNode = root;
|
|
Text = text;
|
|
PropertyDisplay = new GenericFolderProperties();
|
|
((GenericFolderProperties)PropertyDisplay).Name = Text;
|
|
|
|
// ReloadMenus(archiveFile);
|
|
}
|
|
|
|
public ToolStripItem[] GetContextMenuItems()
|
|
{
|
|
return new ToolStripItem[]
|
|
{
|
|
new STToolStripItem("Rename", RenameAction) { Enabled = ArchiveFile.CanRenameFiles },
|
|
new STToolStripItem("Extract Folder", ExtractAction),
|
|
new STToolStripItem("Replace Folder", ReplaceAction) { Enabled = ArchiveFile.CanReplaceFiles },
|
|
new STToolStripItem("Delete Folder", DeleteAction) { Enabled = ArchiveFile.CanDeleteFiles },
|
|
new STToolStripSeparator(),
|
|
new STToolStripItem("Add Folder", AddFolderAction) { Enabled = ArchiveFile.CanAddFiles },
|
|
new STToolStripItem("Add File", AddFileAction) { Enabled = ArchiveFile.CanAddFiles },
|
|
};
|
|
}
|
|
|
|
private void AddFolderAction(object sender, EventArgs args)
|
|
{
|
|
Nodes.Add(new ArchiveFolderNodeWrapper("NewFolder", ArchiveFile, RootNode));
|
|
}
|
|
|
|
private void AddFileAction(object sender, EventArgs args)
|
|
{
|
|
OpenFileDialog ofd = new OpenFileDialog();
|
|
ofd.Filter = "Raw Data (*.*)|*.*";
|
|
ofd.Multiselect = true;
|
|
|
|
if (ofd.ShowDialog() == DialogResult.OK) {
|
|
TreeHelper.AddFiles(this, RootNode, ofd.FileNames);
|
|
}
|
|
}
|
|
|
|
public override void OnClick(TreeView treeView)
|
|
{
|
|
STPropertyGrid editor = (STPropertyGrid)LibraryGUI.GetActiveContent(typeof(STPropertyGrid));
|
|
if (editor == null)
|
|
{
|
|
editor = new STPropertyGrid();
|
|
LibraryGUI.LoadEditor(editor);
|
|
}
|
|
editor.Text = Text;
|
|
editor.Dock = DockStyle.Fill;
|
|
editor.LoadProperty(PropertyDisplay, OnPropertyChanged);
|
|
}
|
|
|
|
public class GenericFolderProperties
|
|
{
|
|
[Category("Folder Properties")]
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
public virtual void OnPropertyChanged() {
|
|
Text = Name;
|
|
}
|
|
|
|
private void ExtractAction(object sender, EventArgs args)
|
|
{
|
|
TreeNode node = this;
|
|
|
|
var ParentPath = string.Empty;
|
|
if (node.Parent != null)
|
|
ParentPath = node.Parent.FullPath;
|
|
|
|
TreeHelper.ExtractAllFiles(ParentPath, Nodes);
|
|
}
|
|
|
|
private void RenameAction(object sender, EventArgs args)
|
|
{
|
|
RenameDialog dialog = new RenameDialog();
|
|
dialog.SetString(Text);
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
Text = dialog.textBox1.Text;
|
|
}
|
|
}
|
|
|
|
private void ReplaceAction(object sender, EventArgs args)
|
|
{
|
|
}
|
|
|
|
private void DeleteAction(object sender, EventArgs args) {
|
|
TreeHelper.RemoveFolder(this, ArchiveFile);
|
|
}
|
|
}
|
|
|
|
//Wrapper for files
|
|
public class ArchiveFileWrapper : ArchiveBase, IContextMenuNode
|
|
{
|
|
public ArchiveRootNodeWrapper RootNode;
|
|
|
|
public virtual ArchiveFileInfo ArchiveFileInfo { get; set; }
|
|
|
|
public ArchiveFileWrapper(string text, ArchiveFileInfo archiveFileInfo, ArchiveRootNodeWrapper rootNode) : base(rootNode.ArchiveFile)
|
|
{
|
|
Text = text;
|
|
RootNode = rootNode;
|
|
// ReloadMenus(archiveFile);
|
|
|
|
ArchiveFileInfo = archiveFileInfo;
|
|
|
|
string Extension = Utils.GetExtension(text);
|
|
if (ArchiveFileInfo.CheckFileMagic)
|
|
{
|
|
Extension = FindMatch(archiveFileInfo.FileData);
|
|
}
|
|
|
|
switch (Extension)
|
|
{
|
|
case ".bntx": SetImageKey("bntx"); break;
|
|
case ".byaml": SetImageKey("byaml"); break;
|
|
case ".byml": SetImageKey("byaml"); break;
|
|
case ".aamp": SetImageKey("aamp"); break;
|
|
case ".bfres": SetImageKey("bfres"); break;
|
|
case ".sbfres": SetImageKey("sbfres"); break;
|
|
case ".dds":
|
|
case ".tga":
|
|
case ".jpg":
|
|
case ".jpeg":
|
|
case ".tiff":
|
|
case ".png":
|
|
case ".gif":
|
|
case ".astc":
|
|
SetImageKey("texture"); break;
|
|
|
|
default: SetImageKey("fileBlank"); break;
|
|
}
|
|
|
|
if (ArchiveFileInfo.ExtensionImageKeyLookup != null)
|
|
{
|
|
if (ArchiveFileInfo.ExtensionImageKeyLookup.ContainsKey(Extension))
|
|
SetImageKey(ArchiveFileInfo.ExtensionImageKeyLookup[Extension]);
|
|
}
|
|
}
|
|
|
|
private void SetImageKey(string Key) {
|
|
ImageKey = Key;
|
|
SelectedImageKey = Key;
|
|
}
|
|
|
|
private string FindMatch(byte[] f)
|
|
{
|
|
if (f.Matches("SARC")) return ".szs";
|
|
else if (f.Matches("Yaz")) return ".szs";
|
|
else if (f.Matches("YB") || f.Matches("BY")) return ".byaml";
|
|
else if (f.Matches("FRES")) return ".bfres";
|
|
else if (f.Matches("Gfx2")) return ".gtx";
|
|
else if (f.Matches("FLYT")) return ".bflyt";
|
|
else if (f.Matches("CLAN")) return ".bclan";
|
|
else if (f.Matches("CLYT")) return ".bclyt";
|
|
else if (f.Matches("FLIM")) return ".bclim";
|
|
else if (f.Matches("FLAN")) return ".bflan";
|
|
else if (f.Matches("FSEQ")) return ".bfseq";
|
|
else if (f.Matches("VFXB")) return ".pctl";
|
|
else if (f.Matches("AAHS")) return ".sharc";
|
|
else if (f.Matches("BAHS")) return ".sharcb";
|
|
else if (f.Matches("BNTX")) return ".bntx";
|
|
else if (f.Matches("BNSH")) return ".bnsh";
|
|
else if (f.Matches("FSHA")) return ".bfsha";
|
|
else if (f.Matches("FFNT")) return ".bffnt";
|
|
else if (f.Matches("CFNT")) return ".bcfnt";
|
|
else if (f.Matches("CSTM")) return ".bcstm";
|
|
else if (f.Matches("FSTM")) return ".bfstm";
|
|
else if (f.Matches("STM")) return ".bfsha";
|
|
else if (f.Matches("CWAV")) return ".bcwav";
|
|
else if (f.Matches("FWAV")) return ".bfwav";
|
|
else if (f.Matches("CTPK")) return ".ctpk";
|
|
else if (f.Matches("CGFX")) return ".bcres";
|
|
else if (f.Matches("AAMP")) return ".aamp";
|
|
else if (f.Matches("MsgStdBn")) return ".msbt";
|
|
else if (f.Matches("MsgPrjBn")) return ".msbp";
|
|
else if (f.Matches(0x00000004)) return ".gfbanm";
|
|
else if (f.Matches(0x00000014)) return ".gfbanm";
|
|
else if (f.Matches(0x00000018)) return ".gfbanmcfg";
|
|
else if (f.Matches(0x00000020)) return ".gfbmdl";
|
|
else if (f.Matches(0x00000044)) return ".gfbpokecfg";
|
|
else return "";
|
|
}
|
|
|
|
public static ArchiveFileWrapper FromPath(string FilePath, ArchiveRootNodeWrapper rootNode)
|
|
{
|
|
var ArchiveFileInfo = new ArchiveFileInfo();
|
|
ArchiveFileInfo.FileName = FilePath;
|
|
ArchiveFileInfo.FileData = File.ReadAllBytes(FilePath);
|
|
return new ArchiveFileWrapper(Path.GetFileName(FilePath), ArchiveFileInfo, rootNode);
|
|
}
|
|
|
|
public ToolStripItem[] GetContextMenuItems()
|
|
{
|
|
return new ToolStripItem[]
|
|
{
|
|
new STToolStripItem("Rename", RenameAction) { Enabled = ArchiveFile.CanRenameFiles },
|
|
new STToolStripItem("Export Raw Data", ExtractAction),
|
|
new STToolStipMenuItem("Export Raw Data to File Location", null, ExportToFileLocAction, Keys.Control | Keys.F),
|
|
new STToolStripItem("Replace Raw Data", ReplaceAction) { Enabled = ArchiveFile.CanReplaceFiles },
|
|
new STToolStripSeparator(),
|
|
new STToolStipMenuItem("Open With Text Editor", null, OpenTextEditorAction, Keys.Control | Keys.T),
|
|
new STToolStripSeparator(),
|
|
new STToolStripItem("Delete", DeleteAction) { Enabled = ArchiveFile.CanDeleteFiles },
|
|
};
|
|
}
|
|
|
|
private void OpenTextEditorAction(object sender, EventArgs args)
|
|
{
|
|
TextEditor editor = (TextEditor)LibraryGUI.GetActiveContent(typeof(TextEditor));
|
|
if (editor == null)
|
|
{
|
|
editor = new TextEditor();
|
|
LibraryGUI.LoadEditor(editor);
|
|
}
|
|
editor.Text = Text;
|
|
editor.Dock = DockStyle.Fill;
|
|
editor.FillEditor(ArchiveFileInfo.FileData);
|
|
}
|
|
|
|
private void ExtractAction(object sender, EventArgs args)
|
|
{
|
|
ArchiveFileInfo.Export();
|
|
}
|
|
|
|
private void ExportToFileLocAction(object sender, EventArgs args)
|
|
{
|
|
string path = FileManager.GetSourcePath(((IFileFormat)ArchiveFile));
|
|
string folder = Path.GetDirectoryName(path);
|
|
string filePath = Path.Combine(folder, Text);
|
|
|
|
Cursor.Current = Cursors.WaitCursor;
|
|
if (ArchiveFileInfo.FileDataStream != null)
|
|
ArchiveFileInfo.FileDataStream.ExportToFile(filePath);
|
|
else
|
|
File.WriteAllBytes(filePath, ArchiveFileInfo.FileData);
|
|
|
|
Cursor.Current = Cursors.Default;
|
|
}
|
|
|
|
private void DeleteAction(object sender, EventArgs args)
|
|
{
|
|
DialogResult result = MessageBox.Show($"Are your sure you want to remove {Text}? This cannot be undone!", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
|
|
if (result == DialogResult.Yes)
|
|
{
|
|
ArchiveFile.DeleteFile(ArchiveFileInfo);
|
|
Parent.Nodes.Remove(this);
|
|
}
|
|
}
|
|
|
|
private void ReplaceAction(object sender, EventArgs args)
|
|
{
|
|
ArchiveFileInfo.Replace();
|
|
ArchiveFileInfo.FileFormat = null;
|
|
UpdateEditor();
|
|
}
|
|
|
|
public override void OnDoubleMouseClick(TreeView treeview) {
|
|
OpenFileFormat(treeview);
|
|
}
|
|
|
|
public void OpenFileFormat(TreeView treeview)
|
|
{
|
|
IFileFormat file = ArchiveFileInfo.OpenFile();
|
|
if (file == null) //Format not supported so return
|
|
return;
|
|
|
|
if (file.IFileInfo != null)
|
|
file.IFileInfo.ArchiveParent = ArchiveFile;
|
|
|
|
if (Utils.HasInterface(file.GetType(), typeof(IEditor<>)))
|
|
OpenControlDialog(file);
|
|
else if (Utils.HasInterface(file.GetType(), typeof(IEditorForm<>)))
|
|
OpenFormDialog(file);
|
|
else if (file is IArchiveFile)
|
|
{
|
|
var FileRoot = new ArchiveRootNodeWrapper(file.FileName, (IArchiveFile)file);
|
|
FileRoot.FillTreeNodes();
|
|
|
|
if (file is TreeNode) //It can still be both, so add all it's nodes
|
|
{
|
|
foreach (TreeNode n in ((TreeNode)file).Nodes)
|
|
FileRoot.Nodes.Add(n);
|
|
}
|
|
|
|
ReplaceNode(this.Parent, treeview, this, FileRoot, RootNode);
|
|
}
|
|
else if (file is TreeNode)
|
|
{
|
|
ReplaceNode(this.Parent, treeview, this, (TreeNode)file, RootNode);
|
|
}
|
|
|
|
ArchiveFileInfo.FileFormat = file;
|
|
}
|
|
|
|
private static Form activeForm;
|
|
public void OpenFormDialog(IFileFormat fileFormat)
|
|
{
|
|
if (activeForm != null && !activeForm.IsDisposed && !activeForm.Disposing)
|
|
{
|
|
activeForm.Text = (((IFileFormat)fileFormat).FileName);
|
|
System.Reflection.MethodInfo methodFill = fileFormat.GetType().GetMethod("FillEditor");
|
|
methodFill.Invoke(fileFormat, new object[1] { activeForm });
|
|
}
|
|
else
|
|
{
|
|
activeForm = GetEditorForm(fileFormat);
|
|
activeForm.Text = (((IFileFormat)fileFormat).FileName);
|
|
if (fileFormat is IEditorFormParameters)
|
|
{
|
|
((IEditorFormParameters)fileFormat).OnSave += OnFormSaved;
|
|
}
|
|
activeForm.Show();
|
|
}
|
|
}
|
|
|
|
private void OnFormSaved(object sender, EventArgs args)
|
|
{
|
|
if (ArchiveFileInfo.FileFormat == null) return;
|
|
|
|
Console.WriteLine("OnFormSaved");
|
|
if (ArchiveFileInfo.FileFormat.CanSave)
|
|
{
|
|
Console.WriteLine("SaveFileFormat");
|
|
|
|
ArchiveFileInfo.SaveFileFormat();
|
|
UpdateEditor();
|
|
}
|
|
}
|
|
|
|
private void OnFormClosed(object sender, EventArgs args)
|
|
{
|
|
if (ArchiveFileInfo.FileFormat == null) return;
|
|
|
|
if (activeForm.DialogResult == DialogResult.OK)
|
|
{
|
|
if (ArchiveFileInfo.FileFormat.CanSave)
|
|
{
|
|
ArchiveFileInfo.SaveFileFormat();
|
|
UpdateEditor();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OpenControlDialog(IFileFormat fileFormat)
|
|
{
|
|
UserControl form = GetEditorControl(fileFormat);
|
|
|
|
form.Text = (((IFileFormat)fileFormat).FileName);
|
|
|
|
var parentForm = LibraryGUI.GetActiveForm();
|
|
|
|
GenericEditorForm editorForm = new GenericEditorForm(true, form);
|
|
editorForm.FormClosing += (sender, e) => FormClosing(sender, e, fileFormat);
|
|
if (editorForm.ShowDialog() == DialogResult.OK)
|
|
{
|
|
if (fileFormat.CanSave)
|
|
{
|
|
ArchiveFileInfo.SaveFileFormat();
|
|
UpdateEditor();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void FormClosing(object sender, EventArgs args, IFileFormat fileFormat)
|
|
{
|
|
if (((Form)sender).DialogResult != DialogResult.OK)
|
|
return;
|
|
}
|
|
|
|
public Form GetEditorForm(IFileFormat fileFormat)
|
|
{
|
|
Type objectType = fileFormat.GetType();
|
|
foreach (var inter in objectType.GetInterfaces())
|
|
{
|
|
if (inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IEditorForm<>))
|
|
{
|
|
System.Reflection.MethodInfo method = objectType.GetMethod("OpenForm");
|
|
return (Form)method.Invoke(fileFormat, new object[0]);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public UserControl GetEditorControl(IFileFormat fileFormat)
|
|
{
|
|
Type objectType = fileFormat.GetType();
|
|
foreach (var inter in objectType.GetInterfaces())
|
|
{
|
|
if (inter.IsGenericType && inter.GetGenericTypeDefinition() == typeof(IEditor<>))
|
|
{
|
|
System.Reflection.MethodInfo method = objectType.GetMethod("OpenForm");
|
|
System.Reflection.MethodInfo methodFill = fileFormat.GetType().GetMethod("FillEditor");
|
|
|
|
var control = (UserControl)method.Invoke(fileFormat, new object[0]);
|
|
methodFill.Invoke(fileFormat, new object[1] { control });
|
|
return control;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public override void OnClick(TreeView treeView) {
|
|
UpdateEditor();
|
|
}
|
|
|
|
public void UpdateEditor()
|
|
{
|
|
ArchiveFilePanel editor = (ArchiveFilePanel)LibraryGUI.GetActiveContent(typeof(ArchiveFilePanel));
|
|
if (editor == null)
|
|
{
|
|
editor = new ArchiveFilePanel();
|
|
editor.Dock = DockStyle.Fill;
|
|
LibraryGUI.LoadEditor(editor);
|
|
}
|
|
|
|
editor.LoadFile(ArchiveFileInfo, ArchiveFile);
|
|
editor.UpdateEditor();
|
|
}
|
|
|
|
public static void ReplaceNode(TreeNode node, TreeView treeview, ArchiveFileWrapper replaceNode, TreeNode NewNode, ArchiveRootNodeWrapper rootNode)
|
|
{
|
|
if (NewNode == null)
|
|
return;
|
|
|
|
var fileInfo = replaceNode.ArchiveFileInfo;
|
|
|
|
int index = 0;
|
|
if (node == null)
|
|
{
|
|
index = treeview.Nodes.IndexOf(replaceNode);
|
|
treeview.Nodes.RemoveAt(index);
|
|
treeview.Nodes.Insert(index, NewNode);
|
|
}
|
|
else
|
|
{
|
|
index = node.Nodes.IndexOf(replaceNode);
|
|
node.Nodes.RemoveAt(index);
|
|
node.Nodes.Insert(index, NewNode);
|
|
}
|
|
|
|
NewNode.ImageKey = replaceNode.ImageKey;
|
|
NewNode.SelectedImageKey = replaceNode.SelectedImageKey;
|
|
NewNode.Text = replaceNode.Text;
|
|
NewNode.Tag = fileInfo;
|
|
|
|
rootNode.FileNodes.RemoveAt(index);
|
|
rootNode.FileNodes.Insert(index, Tuple.Create(fileInfo, NewNode));
|
|
|
|
if (NewNode is ISingleTextureIconLoader)
|
|
{
|
|
ObjectEditor editor = LibraryGUI.GetObjectEditor();
|
|
if (editor != null) //The editor isn't always in object editor so check
|
|
{
|
|
editor.UpdateTextureIcon((ISingleTextureIconLoader)NewNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenameAction(object sender, EventArgs args)
|
|
{
|
|
RenameDialog dialog = new RenameDialog();
|
|
dialog.SetString(Text);
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK) { Text = dialog.textBox1.Text; }
|
|
}
|
|
}
|
|
}
|