using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Toolbox; using System.Windows.Forms; using System.IO; using Toolbox.Library.Forms; using Toolbox.Library; using Toolbox.Library.IO; namespace FirstPlugin { public class RARC : IArchiveFile, IFileFormat, IDirectoryContainer, IContextMenuNode { public FileType FileType { get; set; } = FileType.Archive; public bool CanSave { get; set; } public string[] Description { get; set; } = new string[] { "RARC" }; public string[] Extension { get; set; } = new string[] { "*.rarc", "*.arc", "*.crar", "*.yaz0" }; public string FileName { get; set; } public string FilePath { get; set; } public IFileInfo IFileInfo { get; set; } public bool CanAddFiles { get; set; } public bool CanRenameFiles { get; set; } = true; public bool CanReplaceFiles { get; set; } public bool CanDeleteFiles { get; set; } public bool Identify(System.IO.Stream stream) { using (var reader = new Toolbox.Library.IO.FileReader(stream, true)) { return reader.CheckSignature(4, "RARC") || reader.CheckSignature(4, "CRAR"); } } public Type[] Types { get { List types = new List(); return types.ToArray(); } } public List files = new List(); public List nodes = new List(); public IEnumerable Files => files; public IEnumerable Nodes => nodes; public void ClearFiles() { files.Clear(); } public string Name { get { return FileName; } set { FileName = value; } } private DirectoryEntry[] Directories; private uint HeaderSize = 32; private uint Unknown = 256; public RamAllocation RamType = RamAllocation.MRAM; public enum RamAllocation { None, ARAM, MRAM, } public ToolStripItem[] GetContextMenuItems() { List Items = new List(); Items.Add(new ToolStripMenuItem("Save", null, SaveAction, Keys.Control | Keys.S)); Items.Add(new ToolStripMenuItem("Batch Rename Galaxy (Mario Galaxy)", null, BatchRenameGalaxy, Keys.Control | Keys.S)); return Items.ToArray(); } private void BatchRenameGalaxy(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 + ".arc"; foreach (var file in files) { file.FileName = file.FileName.Replace(ActorName, NewActorName); file.UpdateWrapper(); } } } private void SaveAction(object sender, EventArgs args) { SaveFileDialog sfd = new SaveFileDialog(); sfd.Filter = Utils.GetAllFilters(this); sfd.FileName = FileName; if (sfd.ShowDialog() == DialogResult.OK) { STFileSaver.SaveFileFormat(this, sfd.FileName); } } public bool IsLittleEndian { get; set; } = false; public void Load(System.IO.Stream stream) { CanSave = true; CanRenameFiles = true; CanReplaceFiles = true; files.Clear(); using (var reader = new FileReader(stream)) { _savedDirectories.Clear(); reader.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; string signature = reader.ReadString(4, Encoding.ASCII); if (signature == "CRAR") { IsLittleEndian = true; reader.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; } uint FileSize = reader.ReadUInt32(); HeaderSize = reader.ReadUInt32(); uint DataOffset = reader.ReadUInt32(); uint FileDataSize = reader.ReadUInt32(); uint MRamSize = reader.ReadUInt32(); uint ARamSize = reader.ReadUInt32(); byte[] Padding = reader.ReadBytes(4); if (MRamSize != 0) RamType |= RamAllocation.MRAM; else if (ARamSize != 0) RamType |= RamAllocation.ARAM; //Info Block long pos = reader.Position; uint DirectoryCount = reader.ReadUInt32(); uint DirectoryOffset = reader.ReadUInt32() + (uint)pos; uint TotalNodeCount = reader.ReadUInt32(); uint NodeOffset = reader.ReadUInt32() + (uint)pos; uint StringTableSize = reader.ReadUInt32(); uint StringTablOffset = reader.ReadUInt32() + (uint)pos; ushort NodeCount = reader.ReadUInt16(); Unknown = reader.ReadUInt16(); byte[] Padding2 = reader.ReadBytes(4); Directories = new DirectoryEntry[DirectoryCount]; for (int dir = 0; dir < DirectoryCount; dir++) Directories[dir] = new DirectoryEntry(this); Console.WriteLine($"DirectoryCount {DirectoryCount}"); Console.WriteLine($"StringTablOffset {StringTablOffset}"); reader.SeekBegin(DirectoryOffset); for (int dir = 0; dir < DirectoryCount; dir++) { Directories[dir].Read(reader); } for (int dir = 0; dir < DirectoryCount; dir++) { uint NamePointer = StringTablOffset + Directories[dir].NameOffset; Directories[dir].Name = ReadStringAtTable(reader, NamePointer); Console.WriteLine($"{ Directories[dir].Name } {dir}"); for (int n = 0; n < Directories[dir].NodeCount; n++) { reader.SeekBegin(NodeOffset + ((n + Directories[dir].FirstNodeIndex) * 0x14)); FileEntry entry = new FileEntry(); entry.Read(reader, IsLittleEndian); NamePointer = StringTablOffset + entry.NameOffset; entry.Name = ReadStringAtTable(reader, NamePointer); //Root and parent strings. Skip these unecessary nodes. if (entry.Name == "." || entry.Name == "..") continue; if (entry.IsDirectory) { // Directories[entry.Offset].ID = entry.FileId; //0xFFFF or 0 Directories[dir].AddNode(Directories[entry.Offset]); _savedDirectories.Add(entry); } else { using (reader.TemporarySeek(pos + DataOffset + entry.Offset, System.IO.SeekOrigin.Begin)) { entry.FileData = reader.ReadBytes((int)entry.Size); } entry.FileName = entry.Name; files.Add(entry); Directories[dir].AddNode(entry); } } } this.Name = Directories[0].Name; nodes.AddRange(Directories[0].Nodes); } } private void LoadAllDirectoriesAndFiles(DirectoryEntry parentDir) { for (int i = 0; i < parentDir.Children.Count; i++) { if (parentDir.Children[i] is DirectoryEntry) { //_savedDirectories.Add((DirectoryEntry)parentDir.nodes[i]); } else _savedFiles.Add((FileEntry)parentDir.Children[i]); } for (int i = 0; i < parentDir.Children.Count; i++) { if (parentDir.Children[i] is DirectoryEntry) LoadAllDirectoriesAndFiles((DirectoryEntry)parentDir.Children[i]); } } //Save a string table to a char array like WArchive //It's a good idea, and i don't want to deal with offset linking private List _savedFileData; private List _savedFiles = new List(); private List _savedDirectories = new List(); private List _savedNodes = new List(); private void SaveFile(FileWriter writer) { _savedFiles.Clear(); _savedFileData = new List(); _savedNodes = Directories.ToList(); Console.WriteLine("LoadAllDirectoriesAndFiles"); LoadAllDirectoriesAndFiles(Directories[0]); var stringTable = CreateStringTable(Directories[0]); long pos = writer.Position; writer.ByteOrder = Syroot.BinaryData.ByteOrder.BigEndian; if (IsLittleEndian) writer.ByteOrder = Syroot.BinaryData.ByteOrder.LittleEndian; writer.WriteSignature(IsLittleEndian ? "CRAR" : "RARC"); writer.Write(uint.MaxValue); //FileSize writer.Write(HeaderSize); writer.Write(uint.MaxValue); //DataOffset writer.Write(uint.MaxValue); //File Size writer.Write(0); //MRAM (saved later if used) writer.Write(0); //ARAM (saved later if used) writer.Seek(4); //padding writer.SeekBegin(HeaderSize); long InfoPos = writer.Position; writer.Write(_savedNodes.Count); writer.Write(uint.MaxValue); //DirectoryOffset writer.Write(_savedNodes.Sum(x => (x.Children.Count + 2))); writer.Write(uint.MaxValue); //File Node Offset writer.Write(uint.MaxValue); //String pool size writer.Write(uint.MaxValue); //String pool offset writer.Write((ushort)(_savedNodes.Sum(x => (x.Children.Count + 2)))); writer.Write((ushort)Unknown); writer.Write(0); //padding int startIndex = 0; //Write nodes WriteOffset(writer, 4, InfoPos); for (int dir = 0; dir < _savedNodes.Count; dir++) { writer.Write(_savedNodes[dir].Identifier); writer.Write(_savedNodes[dir].NameOffset); writer.Write(_savedNodes[dir].Hash); writer.Write((ushort)(_savedNodes[dir].Children.Count + 2)); writer.Write(startIndex); startIndex += _savedNodes[dir].Children.Count + 2; } foreach (FileEntry entry in Files) entry.SaveFileFormat(); writer.Align(32); Console.WriteLine("Directories"); //Write the directories and files WriteOffset(writer, 12, InfoPos); for (int dir = 0; dir < _savedNodes.Count; dir++) { for (int n = 0; n < _savedNodes[dir].Children.Count; n++) { var entry = _savedNodes[dir].Children[n]; if (entry is DirectoryEntry) { var dirEntry = (DirectoryEntry)_savedNodes[dir].Children[n]; int index = Directories.ToList().IndexOf(dirEntry); writer.Write(dirEntry.ID); writer.Write(dirEntry.Hash); if (IsLittleEndian) { writer.Write((ushort)dirEntry.NameOffset); writer.Write((byte)0); writer.Write((byte)0x2); } else { writer.Write((byte)0x2); writer.Write((byte)0); writer.Write((ushort)dirEntry.NameOffset); } writer.Write((int)index); writer.Write((int)16); writer.Write((int)0); } else { var fileEntry = (FileEntry)entry; writer.Write(fileEntry.FileId); writer.Write(fileEntry.Hash); if (IsLittleEndian) { writer.Write((ushort)fileEntry.NameOffset); writer.Write((byte)0); //Padding writer.Write(fileEntry.Flags); } else { writer.Write(fileEntry.Flags); writer.Write((byte)0); //Padding writer.Write((ushort)fileEntry.NameOffset); } fileEntry._dataOffsetPos = writer.Position; writer.Write(0); writer.Write((uint)fileEntry.FileData.Length); _savedFileData.Add(fileEntry.FileData); writer.Write(0); } } //Save parent and root nodes at end writer.Write((ushort)0); writer.Write((ushort)46); if (IsLittleEndian) { writer.Write((ushort)0); writer.Write((byte)0); writer.Write((byte)2); } else { writer.Write((byte)2); writer.Write((byte)0); writer.Write((ushort)0); } writer.Write(dir); writer.Write(16); writer.Write(0); int rootIndex = Directories.ToList().IndexOf((DirectoryEntry)_savedNodes[dir].Parent); writer.Write((ushort)0); writer.Write((ushort)184); if (IsLittleEndian) { writer.Write((ushort)2); writer.Write((byte)0); writer.Write((byte)2); } else { writer.Write((byte)2); writer.Write((byte)0); writer.Write((ushort)2); } writer.Write(rootIndex); writer.Write(16); writer.Write(0); } writer.Align(32); long stringTablePos = writer.Position; WriteOffset(writer, 20, InfoPos); foreach (var str in stringTable) { writer.WriteString(str); } writer.Align(32); long stringTableSize = writer.Position - stringTablePos; var dataPos = writer.Position; using (writer.TemporarySeek(pos + 12, System.IO.SeekOrigin.Begin)) { writer.Write((uint)(dataPos - InfoPos)); } uint dataOffset = 0; SaveFileData(writer, ref dataOffset, Directories[0]); uint DataSize = (uint)(writer.Position - dataPos); writer.Align(32); uint EndFileSize = (uint)(writer.Position - dataPos); //Write data size using (writer.TemporarySeek(pos + 16, System.IO.SeekOrigin.Begin)) { writer.Write((uint)DataSize); } //Write ram size if (RamType != RamAllocation.None) { using (writer.TemporarySeek(pos + (RamType.HasFlag(RamAllocation.MRAM) ? 20 : 24), System.IO.SeekOrigin.Begin)) { writer.Write((uint)EndFileSize); } } //Write file size using (writer.TemporarySeek(pos + 0x4, System.IO.SeekOrigin.Begin)) { writer.Write((uint)writer.BaseStream.Length); } //Write string table size using (writer.TemporarySeek(InfoPos + 16, System.IO.SeekOrigin.Begin)) { writer.Write((uint)stringTableSize); } } //Generates a string table private List CreateStringTable(DirectoryEntry parentDir) { //Strings are ordered recusively through directories List stringList = new List(); //Add a root string stringList.Add("."); //Then add a parent string after it stringList.Add(".."); uint stringPos = 5; CreateStringTable(stringList, ref stringPos, parentDir); return stringList; } private void CreateStringTable(List stringList, ref uint stringPos, DirectoryEntry parentDir) { if (!stringList.Contains(parentDir.Name)) { parentDir.NameOffset = stringPos; stringList.Add(parentDir.Name); stringPos += (uint)parentDir.Name.Length + 1; } for (int i = 0; i < parentDir.Children.Count; i++) { if (parentDir.Children[i] is FileEntry) { ((FileEntry)parentDir.Children[i]).NameOffset = (ushort)stringPos; ((FileEntry)parentDir.Children[i]).UpdateHash(); } else { ((DirectoryEntry)parentDir.Children[i]).NameOffset = (ushort)stringPos; ((DirectoryEntry)parentDir.Children[i]).UpdateHash(); } Console.WriteLine($"{parentDir.Children[i].Name} {stringPos}"); stringList.Add(parentDir.Children[i].Name); stringPos += (uint)parentDir.Children[i].Name.Length + 1; if (parentDir.Children[i] is DirectoryEntry) CreateStringTable(stringList, ref stringPos, (DirectoryEntry)parentDir.Children[i]); } } //Saves data in recusive order private void SaveFileData(FileWriter writer, ref uint dataPos, DirectoryEntry parentDir) { for (int i = 0; i < parentDir.Children.Count; i++) { if (parentDir.Children[i] is FileEntry) { var entry = (FileEntry)parentDir.Children[i]; long pos = writer.Position; using (writer.TemporarySeek(entry._dataOffsetPos, System.IO.SeekOrigin.Begin)) { writer.Write((uint)dataPos); } writer.Write(entry.FileData); writer.AlignBytes(32); dataPos += (uint)(writer.Position - pos); } if (parentDir.Children[i] is DirectoryEntry) SaveFileData(writer, ref dataPos, (DirectoryEntry)parentDir.Children[i]); } } private void WriteOffset(FileWriter writer, long Target, long RelativePosition) { long Position = writer.Position; using (writer.TemporarySeek(RelativePosition + Target, System.IO.SeekOrigin.Begin)) { writer.Write((uint)(Position - RelativePosition)); } } private string ReadStringAtTable(FileReader reader, uint NameOffset) { using (reader.TemporarySeek(NameOffset, System.IO.SeekOrigin.Begin)) { return reader.ReadZeroTerminatedString(); } } private static ushort CalculateHash(string Name) { ushort Hash = 0; for (int i = 0; i < Name.Length; i++) { Hash *= 3; Hash += Name[i]; } return Hash; } private void CreateDirectoryEntry() { } public class DirectoryEntry : IDirectoryContainer { public RARC ParentArchive { get; } public string Name { get; set; } public uint Identifier; internal uint NameOffset; //Relative to string table public ushort Hash { get; set; } public ushort NodeCount; public uint FirstNodeIndex { get; set; } public ushort ID { get; set; } = 0xFFFF; public DirectoryEntry(RARC rarc) { ParentArchive = rarc; } public IEnumerable Nodes { get { return Children; } } public List Children = new List(); public INode Parent { get; set; } public void AddNode(INode node) { if (node is FileEntry) ((FileEntry)node).Parent = this; else ((DirectoryEntry)node).Parent = this; Children.Add(node); } public void Read(FileReader reader) { Identifier = reader.ReadUInt32(); NameOffset = reader.ReadUInt32(); Hash = reader.ReadUInt16(); NodeCount = reader.ReadUInt16(); FirstNodeIndex = reader.ReadUInt32(); } public void UpdateHash() { Hash = CalculateHash(Name); } } public void Unload() { } public void Save(System.IO.Stream stream) { SaveFile(new FileWriter(stream)); } public bool AddFile(ArchiveFileInfo archiveFileInfo) { return false; } public bool DeleteFile(ArchiveFileInfo archiveFileInfo) { return false; } public class FileEntry : ArchiveFileInfo { //According to this to determine directory or not //https://github.com/LordNed/WArchive-Tools/blob/3c7fdefe54b4c7634a042847b7455de61705033f/ArchiveToolsLib/Archive/Archive.cs#L44 public bool IsDirectory { get { return (Flags & 2) >> 1 == 1; } } public ushort FileId { get; set; } public ushort Hash { get; set; } public byte Flags { get; set; } internal uint Size; internal uint Offset; internal ushort NameOffset; internal long _dataOffsetPos; public INode Parent { get; set; } public void Read(FileReader reader, bool IsLittleEndian) { FileId = reader.ReadUInt16(); Hash = reader.ReadUInt16(); if (IsLittleEndian) { NameOffset = reader.ReadUInt16(); reader.Seek(1); //Padding Flags = reader.ReadByte(); } else { Flags = reader.ReadByte(); reader.Seek(1); //Padding NameOffset = reader.ReadUInt16(); } Offset = reader.ReadUInt32(); Size = reader.ReadUInt32(); } public void UpdateHash() { Hash = CalculateHash(Name); } } } }