Switch-Toolbox/File_Format_Library/FileFormats/Archives/SARC.cs
2021-10-11 17:17:52 -04:00

374 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", "*.sbeventpack", "*.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);
if (SzsFiles.Files.Count == 1)
entry.OpenFileFormatOnLoad = true;
}
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;
}
}
}