45a0ada32e
Check gfbmdl textures list on export for exporting active bntx files. Dispose file formats properly for gfbmdl
372 lines
12 KiB
C#
372 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Toolbox;
|
|
using System.Windows.Forms;
|
|
using SARCExt;
|
|
using Toolbox.Library;
|
|
using Toolbox.Library.IO;
|
|
using Toolbox.Library.Forms;
|
|
using Toolbox.Library.Security.Cryptography;
|
|
|
|
namespace FirstPlugin
|
|
{
|
|
public class SARC : IArchiveFile, IFileFormat, IContextMenuNode
|
|
{
|
|
public FileType FileType { get; set; } = FileType.Archive;
|
|
|
|
public bool CanSave { get; set; }
|
|
public string[] Description { get; set; } = new string[] { "Sead (hashed) Archived Resource" };
|
|
public string[] Extension { get; set; } = new string[] { "*.pack", "*.sarc", "*.bgenv", "*.sbfarc", "*.sblarc", "*.sbactorpack", ".arc" };
|
|
public string FileName { get; set; }
|
|
public string FilePath { get; set; }
|
|
public IFileInfo IFileInfo { get; set; }
|
|
|
|
public bool Identify(System.IO.Stream stream)
|
|
{
|
|
using (var reader = new Toolbox.Library.IO.FileReader(stream, true))
|
|
{
|
|
return reader.CheckSignature(4, "SARC");
|
|
}
|
|
}
|
|
|
|
public Type[] Types
|
|
{
|
|
get
|
|
{
|
|
List<Type> types = new List<Type>();
|
|
types.Add(typeof(MenuExt));
|
|
return types.ToArray();
|
|
}
|
|
}
|
|
|
|
class MenuExt : IFileMenuExtension
|
|
{
|
|
public STToolStripItem[] NewFileMenuExtensions => newFileMenu;
|
|
public STToolStripItem[] NewFromFileMenuExtensions => null;
|
|
public STToolStripItem[] ToolsMenuExtensions => null;
|
|
public STToolStripItem[] TitleBarExtensions => null;
|
|
public STToolStripItem[] CompressionMenuExtensions => null;
|
|
public STToolStripItem[] ExperimentalMenuExtensions => null;
|
|
public STToolStripItem[] EditMenuExtensions => null;
|
|
public ToolStripButton[] IconButtonMenuExtensions => null;
|
|
|
|
STToolStripItem[] newFileMenu = new STToolStripItem[2];
|
|
STToolStripItem[] newFromFileMenu = new STToolStripItem[1];
|
|
|
|
public MenuExt()
|
|
{
|
|
newFileMenu[0] = new STToolStripItem("SARC (Wii U)", CreateNewBigEndianSarc);
|
|
newFileMenu[1] = new STToolStripItem("SARC (Switch/3DS)", CreateNewLittleEndianSarc);
|
|
}
|
|
|
|
private void CreateNewBigEndianSarc(object sender, EventArgs e)
|
|
{
|
|
SARC sarc = new SARC();
|
|
sarc.IFileInfo = new IFileInfo();
|
|
sarc.FileName = "NewArchive.szs";
|
|
CreateNewSARC(sarc, true);
|
|
|
|
ObjectEditor editor = new ObjectEditor(sarc);
|
|
editor.Text = "NewArchive.szs";
|
|
LibraryGUI.CreateMdiWindow(editor);
|
|
}
|
|
|
|
private void CreateNewLittleEndianSarc(object sender, EventArgs e)
|
|
{
|
|
SARC sarc = new SARC();
|
|
sarc.IFileInfo = new IFileInfo();
|
|
sarc.FileName = "NewArchive.szs";
|
|
CreateNewSARC(sarc, false);
|
|
|
|
ObjectEditor editor = new ObjectEditor(sarc);
|
|
editor.Text = "NewArchive.szs";
|
|
LibraryGUI.CreateMdiWindow(editor);
|
|
}
|
|
}
|
|
|
|
public bool CanAddFiles { get; set; } = true;
|
|
public bool CanRenameFiles { get; set; } = true;
|
|
public bool CanReplaceFiles { get; set; } = true;
|
|
public bool CanDeleteFiles { get; set; } = true;
|
|
|
|
public Dictionary<string, SarcEntry> FileLookup = new Dictionary<string, SarcEntry>();
|
|
|
|
public List<SarcEntry> files = new List<SarcEntry>();
|
|
public IEnumerable<ArchiveFileInfo> Files => files;
|
|
|
|
public void ClearFiles() { files.Clear(); }
|
|
|
|
public SarcData sarcData;
|
|
|
|
public static void CreateNewSARC(SARC sarc, bool IsBigEndian)
|
|
{
|
|
sarc.CanSave = true;
|
|
sarc.IFileInfo.UseEditMenu = true;
|
|
|
|
sarc.sarcData = new SarcData();
|
|
sarc.sarcData.HashOnly = false;
|
|
sarc.sarcData.Files = new Dictionary<string, byte[]>();
|
|
|
|
if (IsBigEndian)
|
|
sarc.sarcData.endianness = Syroot.BinaryData.ByteOrder.BigEndian;
|
|
else
|
|
sarc.sarcData.endianness = Syroot.BinaryData.ByteOrder.LittleEndian;
|
|
}
|
|
|
|
public void Load(System.IO.Stream stream)
|
|
{
|
|
CanSave = true;
|
|
IFileInfo.UseEditMenu = true;
|
|
|
|
PluginRuntime.SarcArchives.Add(this);
|
|
|
|
FileLookup.Clear();
|
|
|
|
var SzsFiles = SARCExt.SARC.UnpackRamN(stream);
|
|
sarcData = new SarcData();
|
|
sarcData.HashOnly = SzsFiles.HashOnly;
|
|
sarcData.Files = SzsFiles.Files;
|
|
sarcData.endianness = GetByteOrder(stream);
|
|
|
|
foreach (var file in SzsFiles.Files)
|
|
{
|
|
string fileName = file.Key;
|
|
string Hash = string.Empty;
|
|
if (SzsFiles.HashOnly)
|
|
{
|
|
fileName = SARCExt.SARC.TryGetNameFromHashTable(fileName);
|
|
Hash = file.Key;
|
|
}
|
|
var entry = SetupFileEntry(fileName, file.Value, Hash);
|
|
files.Add(entry);
|
|
FileLookup.Add(fileName, entry);
|
|
}
|
|
|
|
sarcData.Files.Clear();
|
|
}
|
|
|
|
public bool AddFile(ArchiveFileInfo archiveFileInfo)
|
|
{
|
|
files.Add(new SarcEntry()
|
|
{
|
|
FileData = archiveFileInfo.FileData,
|
|
FileName = archiveFileInfo.FileName,
|
|
});
|
|
return true;
|
|
}
|
|
|
|
public bool DeleteFile(ArchiveFileInfo archiveFileInfo)
|
|
{
|
|
files.Remove((SarcEntry)archiveFileInfo);
|
|
return true;
|
|
}
|
|
|
|
public ToolStripItem[] GetContextMenuItems()
|
|
{
|
|
List<ToolStripItem> Items = new List<ToolStripItem>();
|
|
Items.Add(new ToolStripMenuItem("Rename Actor Files (Odyssey)", null, RenameActors, Keys.Control | Keys.R));
|
|
return Items.ToArray();
|
|
}
|
|
|
|
private void RenameActors(object sender, EventArgs args)
|
|
{
|
|
string ActorName = Path.GetFileNameWithoutExtension(FileName);
|
|
|
|
RenameDialog dialog = new RenameDialog();
|
|
dialog.SetString(ActorName);
|
|
|
|
if (dialog.ShowDialog() == DialogResult.OK)
|
|
{
|
|
string NewActorName = dialog.textBox1.Text;
|
|
FileName = NewActorName + ".szs";
|
|
|
|
foreach (var file in files) {
|
|
file.FileName = file.FileName.Replace(ActorName, NewActorName);
|
|
file.UpdateWrapper();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UnpackToFolder(object sender, EventArgs args)
|
|
{
|
|
|
|
}
|
|
|
|
private void PackFromFolder(object sender, EventArgs args)
|
|
{
|
|
|
|
}
|
|
|
|
public Syroot.BinaryData.ByteOrder GetByteOrder(System.IO.Stream data)
|
|
{
|
|
using (FileReader reader = new FileReader(data, true))
|
|
{
|
|
reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian;
|
|
reader.Seek(6);
|
|
ushort bom = reader.ReadUInt16();
|
|
reader.Position = 0;
|
|
|
|
if (bom == 0xFFFE)
|
|
return Syroot.BinaryData.ByteOrder.LittleEndian;
|
|
else
|
|
return Syroot.BinaryData.ByteOrder.BigEndian;
|
|
}
|
|
}
|
|
|
|
public void Unload()
|
|
{
|
|
foreach (var file in files)
|
|
{
|
|
if (file.FileFormat != null)
|
|
file.FileFormat.Unload();
|
|
|
|
file.FileData = null;
|
|
}
|
|
|
|
files.Clear();
|
|
|
|
if (PluginRuntime.SarcArchives.Contains(this))
|
|
PluginRuntime.SarcArchives.Remove(this);
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
static uint NameHash(string name)
|
|
{
|
|
uint result = 0;
|
|
for (int i = 0; i < name.Length; i++)
|
|
{
|
|
result = name[i] + result * 0x00000065;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void Save(System.IO.Stream stream)
|
|
{
|
|
sarcData.Files.Clear();
|
|
foreach (var file in files)
|
|
{
|
|
Console.WriteLine("sarc file name " + file.FileName);
|
|
file.SaveFileFormat();
|
|
|
|
if (sarcData.HashOnly)
|
|
sarcData.Files.Add(file.HashName, file.FileData);
|
|
else
|
|
sarcData.Files.Add(file.FileName, file.FileData);
|
|
}
|
|
|
|
Tuple<int, byte[]> sarc = SARCExt.SARC.PackN(sarcData);
|
|
|
|
IFileInfo.Alignment = sarc.Item1;
|
|
|
|
using (var writer = new FileWriter(stream)) {
|
|
writer.Write(sarc.Item2);
|
|
}
|
|
}
|
|
|
|
public class SarcEntry : ArchiveFileInfo
|
|
{
|
|
public SARC sarc; //Sarc file the entry is located in
|
|
|
|
public string OriginalFileName;
|
|
|
|
private bool NameChanged
|
|
{
|
|
get { return OriginalFileName != FileName; }
|
|
}
|
|
|
|
|
|
|
|
private string hashName;
|
|
public string HashName
|
|
{
|
|
get
|
|
{
|
|
if (hashName == null || NameChanged)
|
|
hashName = NameHash(FileName).ToString("X8");
|
|
|
|
return hashName;
|
|
}
|
|
set
|
|
{
|
|
hashName = value;
|
|
}
|
|
}
|
|
|
|
public string TryGetHash(List<string> names, string folder)
|
|
{
|
|
//Name is not hashed so return
|
|
if (!FileName.Contains(HashName))
|
|
return FileName;
|
|
|
|
for (int i = 0; i < names?.Count; i++)
|
|
{
|
|
uint hash = StringHashToUint(hashName);
|
|
Console.WriteLine($"{FileName} {hash} {names[i]} {NameHash(names[i])}");
|
|
if (hash == NameHash($"{folder}/{names[i]}"))
|
|
return names[i];
|
|
}
|
|
|
|
return FileName;
|
|
}
|
|
|
|
public bool IsHashMatch(string fileName)
|
|
{
|
|
uint hash = StringHashToUint(hashName);
|
|
return hash == NameHash(FileName);
|
|
}
|
|
|
|
static uint NameHash(string name)
|
|
{
|
|
uint result = 0;
|
|
for (int i = 0; i < name.Length; i++)
|
|
{
|
|
result = name[i] + result * 0x00000065;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static uint StringHashToUint(string name)
|
|
{
|
|
if (name.Contains("."))
|
|
name = name.Split('.')[0];
|
|
if (name.Length != 8) throw new Exception("Invalid hash length");
|
|
return Convert.ToUInt32(name, 16);
|
|
}
|
|
|
|
public SarcEntry()
|
|
{
|
|
|
|
}
|
|
|
|
public override Dictionary<string, string> ExtensionImageKeyLookup
|
|
{
|
|
get
|
|
{
|
|
return new Dictionary<string, string>()
|
|
{
|
|
{ ".byml", "byaml" },
|
|
{ ".byaml", "byaml" },
|
|
{ ".bfres", "bfres" },
|
|
{ ".sbfres", "bfres" },
|
|
{ ".bflim", "texture" },
|
|
{ ".aamp", "aamp" },
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
public SarcEntry SetupFileEntry(string fullName, byte[] data, string HashName)
|
|
{
|
|
SarcEntry sarcEntry = new SarcEntry();
|
|
sarcEntry.OriginalFileName = fullName;
|
|
sarcEntry.FileName = fullName;
|
|
sarcEntry.FileData = data;
|
|
sarcEntry.HashName = HashName;
|
|
return sarcEntry;
|
|
}
|
|
}
|
|
}
|