private void ImportFromDiskRecursive(string folder, VirtualFilesystemDirectory dir) { if (!Directory.Exists(folder)) { throw new ArgumentException("You must specify a directory that exists", "folder"); } // For each directory that is a child of the specified folder into ourselves, and then import the contents. DirectoryInfo dirInfo = new DirectoryInfo(folder); foreach (var diskDir in dirInfo.GetDirectories()) { VirtualFilesystemDirectory vfDir = new VirtualFilesystemDirectory(diskDir.Name); dir.Children.Add(vfDir); ImportFromDiskRecursive(diskDir.FullName, vfDir); } foreach (var diskFile in dirInfo.GetFiles()) { using (BinaryReader reader = new BinaryReader(File.Open(diskFile.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) { string fileName = Path.GetFileNameWithoutExtension(diskFile.Name); string fileExt = Path.GetExtension(diskFile.Name); byte[] data = reader.ReadBytes((int)reader.BaseStream.Length); VirtualFilesystemFile vfFile = new VirtualFilesystemFile(fileName, fileExt, data); dir.Children.Add(vfFile); } } }
/// <summary> /// Returns a list of files with the given extension, or an empty list if no files are found. Searches /// all child directories recursively to look for files. /// </summary> /// <param name="extension">File extension (including period, ie: ".arc")</param> /// <returns>List of files matching that extension</returns> public List <VirtualFilesystemFile> FindByExtension(params string[] extensions) { if (extensions.Length == 0) { throw new ArgumentException("You must specify at least one extension", "extensions"); } List <VirtualFilesystemFile> validFiles = new List <VirtualFilesystemFile>(); foreach (var child in Children) { if (child.Type == NodeType.File) { VirtualFilesystemFile file = (VirtualFilesystemFile)child; for (int i = 0; i < extensions.Length; i++) { string extension = extensions[i]; if (string.Compare(file.Extension, extension, System.StringComparison.InvariantCultureIgnoreCase) == 0) { validFiles.Add(file); break; } } } else if (child.Type == NodeType.Directory) { VirtualFilesystemDirectory dir = (VirtualFilesystemDirectory)child; validFiles.AddRange(dir.FindByExtension(extensions)); } } return(validFiles); }
private void ExportToDiskRecursive(string folder, VirtualFilesystemDirectory dir) { // Create the directory that this node represents. // If it's a directory, append the directory name to the folder and onwards! folder = string.Format("{0}{1}/", folder, dir.Name); try { Directory.CreateDirectory(folder); } catch (Exception ex) { Console.WriteLine("Caught exception while trying to create folder {0}: {1}", folder, ex.ToString()); } foreach (var node in dir.Children) { VirtualFilesystemDirectory vfDir = node as VirtualFilesystemDirectory; VirtualFilesystemFile vfFile = node as VirtualFilesystemFile; if (vfDir != null) { ExportToDiskRecursive(folder, vfDir); } else if (vfFile != null) { // However, if it's a file we're going to write it to disk. string filePath = string.Format("{0}{1}{2}", folder, vfFile.Name, vfFile.Extension); try { using (EndianBinaryWriter writer = new EndianBinaryWriter(File.Create(filePath), Endian.Big)) { writer.Write(vfFile.Data); } } catch (Exception ex) { Console.WriteLine("Caught exception while trying to write file {0}: {1}", filePath, ex.ToString()); } } } }
/// <summary> /// Returns the file whose node has the given NodeID, or null if there is no such file. Searches /// all child directories recursively to look for the file. /// </summary> /// <param name="id">NodeID</param> /// <returns>File with a matching NodeID</returns> public VirtualFilesystemFile FindByID(ushort id) { VirtualFilesystemFile foundFile = null; foreach (VirtualFilesystemNode child in Children) { if (child.Type == NodeType.File) { if (child.NodeID == id) { foundFile = child as VirtualFilesystemFile; } } else if (child.Type == NodeType.Directory) { VirtualFilesystemDirectory dir = (VirtualFilesystemDirectory)child; foundFile = dir.FindByID(id); } } return(foundFile); }
private void InsertDirOperatorEntries(VirtualFilesystemDirectory currentDir, VirtualFilesystemDirectory parentDir) { FileEntry dot1; FileEntry dot2; // Working dir reference dot1 = new FileEntry { ID = ushort.MaxValue, NameHashcode = HashName("."), Type = 0x02, Name = ".", Data = new byte[] { (byte)(exportNodes.IndexOf(exportNodes.Find(i => i.Name == currentDir.Name))) }, }; if (parentDir != null) { // Parent dir reference. This isn't the root, so we get the parent dot2 = new FileEntry { ID = ushort.MaxValue, NameHashcode = HashName(".."), Type = 0x02, Name = "..", Data = new byte[] { (byte)(exportNodes.IndexOf(exportNodes.Find(i => i.Name == parentDir.Name))) }, }; } else { // Parent dir reference. This IS the root, so we say the parent dir is null dot2 = new FileEntry { ID = ushort.MaxValue, NameHashcode = HashName(".."), Type = 0x02, Name = "..", Data = new byte[] { (byte)(255) }, }; } exportFileEntries.Add(dot1); exportFileEntries.Add(dot2); }
private FileEntry[] GetDirDataRecursive(VirtualFilesystemDirectory rootDir, VirtualFilesystemDirectory parentDir) { List<FileEntry> dirFileEntries = new List<FileEntry>(); FileEntry file; Node dirNode; // I'll admit this isn't ideal. If I'm looking at the native archives right, they tend // to follow the rule of "files first, directories second" when it comes to file entries. // Therefore, I'm going to set it up right now so that it will get files first, *then* directories. foreach (VirtualFilesystemNode node in rootDir.Children) { // We just need a file entry here if (node.Type == NodeType.File) { VirtualFilesystemFile virtFile = node as VirtualFilesystemFile; file = new FileEntry { ID = (ushort)exportFileEntries.Count, NameHashcode = HashName(virtFile.Name + virtFile.Extension), Type = 0x11, Name = virtFile.Name + virtFile.Extension, Data = virtFile.File.GetData(), }; dirFileEntries.Add(file); } } foreach (VirtualFilesystemNode node in rootDir.Children) { // We need a node and a file entry here if (node.Type == NodeType.Directory) { VirtualFilesystemDirectory virtDir = node as VirtualFilesystemDirectory; dirNode = new Node { Type = virtDir.Name.Substring(0, 3).ToUpper() + " ", Name = virtDir.Name, NameHashcode = HashName(virtDir.Name), FirstFileOffset = (uint)exportFileEntries.Count }; exportNodes.Add(dirNode); file = new FileEntry { ID = ushort.MaxValue, NameHashcode = HashName(virtDir.Name), Type = 0x02, Name = virtDir.Name, Data = new byte[] { (byte)(exportNodes.IndexOf(exportNodes.Find(i => i.Name == virtDir.Name))) }, }; dirFileEntries.Add(file); } } exportFileEntries.AddRange(dirFileEntries.ToArray()); InsertDirOperatorEntries(rootDir, parentDir); // The recursive part. One more foreach! foreach (VirtualFilesystemNode node in rootDir.Children) { if (node.Type == NodeType.Directory) { VirtualFilesystemDirectory dir = node as VirtualFilesystemDirectory; Node tempNode = exportNodes.Find(i => i.Name == node.Name); tempNode.Entries = GetDirDataRecursive(dir, rootDir); } } return dirFileEntries.ToArray(); }
public byte[] WriteFile(VirtualFilesystemDirectory root) { // These will hold our data until we're ready to put it in a byte[] exportNodes = new List<Node>(); exportFileEntries = new List<FileEntry>(); exportStringTable = new List<char>(); exportFileData = new List<byte>(); // The first two string table entries, for working (.) and parent (..) directories (?) exportStringTable.AddRange(".\0".ToCharArray()); exportStringTable.AddRange("..\0".ToCharArray()); // Create the root node Node rootNode = new Node { Type = "ROOT", Name = root.Name, NameHashcode = HashName(root.Name), FirstFileOffset = 0, }; exportNodes.Add(rootNode); // Fill the root's file entries, and in the process fill out all of the other data we need, too rootNode.Entries = GetDirDataRecursive(root, null); MemoryStream dataBuffer = new MemoryStream(); EndianBinaryWriter writer = new EndianBinaryWriter(dataBuffer, Endian.Big); writer.Write("RARC".ToCharArray()); // Placeholder for the file size. We'll come back and fill it in at the end. // Offset is 0x04 writer.Write((int)0); // An unknown int in the header. writer.Write((int)0x20); // Placeholder for the data start offset. We'll come back and fill it in at the end. // Offset is 0x0C writer.Write((int)0); // Unknown ints in the header. There are 4, but we'll just write two 64-bit zeros to save space. writer.Write((ulong)0); writer.Write((ulong)0); // Node count writer.Write((int)exportNodes.Count); // Two unknown ints. We'll write them as one 64-bit zero. writer.Write((int)0x20); writer.Write((int)exportFileEntries.Count); // Get the aligned offset to the start of the file entries table. // Alignment formula: ((data length) + 0x1F) & ~0x1F int alignedFileEntriesOffset = 0x20 + (((exportNodes.Count * 0x10) + 0x1F) & ~0x1F); writer.Write(alignedFileEntriesOffset); // An unknown int in the header. writer.Write((int)0); // Get the aligned offset to the string table. int alignedStringTableOffset = (alignedFileEntriesOffset + (((exportFileEntries.Count * 0x14) + 0x1F) & ~0x1F)); writer.Write(alignedStringTableOffset); // Two unknown ints. We'll write them as one 64-bit zero. writer.Write((ulong)0); // Write each node foreach (Node nod in exportNodes) { writer.Write(nod.Type.ToCharArray()); writer.Write((int)exportStringTable.Count); exportStringTable.AddRange(nod.Name.ToCharArray()); // Strings must be null terminated exportStringTable.Add('\0'); writer.Write(nod.NameHashcode); writer.Write((short)(nod.Entries.Length + 2)); writer.Write(exportFileEntries.FindIndex(i => i.Name == nod.Entries[0].Name)); } Pad32(writer); // Write each entry foreach (FileEntry entry in exportFileEntries) { writer.Write(entry.ID); writer.Write(entry.NameHashcode); writer.Write(entry.Type); // Zero padding writer.Write((byte)0); if ((entry.Name == ".")) { writer.Write((ushort)0); } else if ((entry.Name == "..")) { writer.Write((ushort)2); } else { if (exportNodes.Find(i => i.Name == entry.Name) != null) { string test = new string(exportStringTable.ToArray()); int testt = test.IndexOf(entry.Name); writer.Write((ushort)test.IndexOf(entry.Name)); } else { // Offset of name in the string table writer.Write((ushort)exportStringTable.Count); // Add name to string table exportStringTable.AddRange(entry.Name.ToCharArray()); // Strings must be null terminated exportStringTable.Add('\0'); } } // Entry is a directory if (entry.Type == 0x02) { if (entry.Data[0] != 255) { // First element should always be set to the index of the entry's parent node. // If it's not that laws of physics no longer apply. Or I f****d up writer.Write((int)entry.Data[0]); } else { writer.Write((int)-1); } // Entries will always have a data size of 0x10, the size of a node writer.Write((int)0x10); } // Entry is a file if (entry.Type == 0x11) { // Offset of the file's data writer.Write((int)exportFileData.Count); writer.Write(entry.Data.Length); // Add data to the running list of file data exportFileData.AddRange(entry.Data); } writer.Write((int)0); } Pad32(writer); // Write string table writer.Write(exportStringTable.ToArray()); Pad32(writer); // Write file data writer.Write(exportFileData.ToArray()); Pad32(writer); writer.BaseStream.Position = 4; writer.Write((int)writer.BaseStream.Length); writer.BaseStream.Position = 0xC; writer.Write((int)alignedStringTableOffset + (((exportStringTable.Count) + 0x1F) & ~0x1F)); return dataBuffer.ToArray(); }
public VirtualFilesystemDirectory ReadFile(EndianBinaryReader reader) { if (reader.ReadUInt32() != 0x52415243) // "RARC" throw new InvalidDataException("Invalid Magic, not a RARC File"); uint fileSize = reader.ReadUInt32(); reader.SkipUInt32(); // Unknown uint dataOffset = reader.ReadUInt32() + 0x20; reader.Skip(16); // Unknown - 4 unsigned ints uint numNodes = reader.ReadUInt32(); reader.Skip(8); // Unknown - 2 unsigned ints uint fileEntryOffset = reader.ReadUInt32() + 0x20; reader.SkipUInt32(); // Unknown uint stringTableOffset = reader.ReadUInt32() + 0x20; reader.Skip(8); // Unknown - 2 unsigned ints. // Read all of the node headers. Node[] nodes = new Node[numNodes]; for (int i = 0; i < numNodes; i++) { nodes[i] = new Node { Type = new string(reader.ReadChars(4)), Name = ReadStringAtOffset(reader, stringTableOffset, reader.ReadUInt32()), NameHashcode = reader.ReadUInt16(), Entries = new FileEntry[reader.ReadUInt16()], FirstFileOffset = reader.ReadUInt32() }; } // Create a virtual directory for every folder within the ARC before we process any of them. List<VirtualFilesystemDirectory> allDirs = new List<VirtualFilesystemDirectory>(nodes.Length); foreach (Node node in nodes) { VirtualFilesystemDirectory vfDir = new VirtualFilesystemDirectory(node.Name); allDirs.Add(vfDir); } for (int k = 0; k < nodes.Length; k++) { Node node = nodes[k]; VirtualFilesystemDirectory curDir = allDirs[k]; for (int i = 0; i < node.Entries.Length; i++) { // Jump to the entry's offset in the file. reader.BaseStream.Position = fileEntryOffset + ((node.FirstFileOffset + i) * 0x14); // 0x14 is the size of a File Entry in bytes node.Entries[i] = new FileEntry(); node.Entries[i].ID = reader.ReadUInt16(); node.Entries[i].NameHashcode = reader.ReadUInt16(); node.Entries[i].Type = reader.ReadByte(); reader.SkipByte(); // Padding node.Entries[i].Name = ReadStringAtOffset(reader, stringTableOffset, reader.ReadUInt16()); // Skip these ones cause I don't know how computers work. if (node.Entries[i].Name == "." || node.Entries[i].Name == "..") continue; uint entryDataOffset = reader.ReadUInt32(); uint dataSize = reader.ReadUInt32(); // If it's a directory, then entryDataOffset contains the index of the parent node if (node.Entries[i].IsDirectory) { node.Entries[i].SubDirIndex = entryDataOffset; var newSubDir = allDirs[(int)entryDataOffset]; curDir.Children.Add(newSubDir); } else { node.Entries[i].Data = reader.ReadBytesAt(dataOffset + entryDataOffset, (int)dataSize); string fileName = Path.GetFileNameWithoutExtension(node.Entries[i].Name); string extension = Path.GetExtension(node.Entries[i].Name); var vfFileContents = new VirtualFileContents(node.Entries[i].Data); VirtualFilesystemFile vfFile = new VirtualFilesystemFile(fileName, extension, vfFileContents); curDir.Children.Add(vfFile); } reader.SkipInt32(); // Padding } } // The ROOT directory should always be the first node. We don't have access to the node's TYPE anymore // so we're going to assume its always the first one listed. return allDirs.Count > 0 ? allDirs[0] : null; }
/// <summary> /// Creates an archive out of the specified <see cref="VirtualFilesystemDirectory"/>, optionally compressing the resulting file. /// </summary> /// <param name="outputPath">Filepath to which to write the file to.</param> /// <param name="root"><see cref="VirtualFilesystemDirectory"/> to create an archive out of.</param> /// <param name="compression">Optionally compress with Yaz0 or Yay0 compression.</param> public static void WriteArchive(string outputPath, VirtualFilesystemDirectory root, ArchiveCompression compression = ArchiveCompression.Uncompressed) { if (string.IsNullOrEmpty(outputPath)) throw new ArgumentNullException("filePath", "Cannot write archive to empty file path!"); if (root == null) throw new ArgumentNullException("root", "Cannot write null VirtualFilesystemDirectory to archive."); Archive rarc = new Archive(); MemoryStream outputData = new MemoryStream(); // Create an archive structure from the given root and write it to file. Compression will be applied if specified. MemoryStream uncompressedStream = new MemoryStream(); using (EndianBinaryWriter fileWriter = new EndianBinaryWriter(uncompressedStream, Endian.Big)) { byte[] rawData = rarc.WriteFile(root); fileWriter.Write(rawData); fileWriter.Seek(0, SeekOrigin.Begin); fileWriter.BaseStream.CopyTo(outputData); } MemoryStream compressedStream = new MemoryStream(); switch(compression) { case ArchiveCompression.Yay0: throw new NotImplementedException("Yay0 Compression not implemented."); //compressedStream = Yay0.Encode(uncompressedStream); //break; case ArchiveCompression.Yaz0: EndianBinaryWriter encoded = Yaz0.Encode(uncompressedStream); encoded.Seek(0, SeekOrigin.Begin); encoded.BaseStream.CopyTo(compressedStream); break; case ArchiveCompression.Uncompressed: // Well, that was easy. compressedStream = uncompressedStream; break; } compressedStream.Seek(0, SeekOrigin.Begin); compressedStream.WriteTo(File.Create(outputPath)); }
private static void RecursivePrintFS(int indentCount, VirtualFilesystemDirectory dir) { foreach (var node in dir.Children) { string indentStr = string.Empty; if (node.Type == NodeType.File) { for (int i = 0; i <= indentCount; i++) indentStr += " "; indentStr += "-"; Console.WriteLine("{0}{1}{2}", indentStr, node.Name, (node as VirtualFilesystemFile).Extension); } else { for (int i = 0; i < indentCount; i++) indentStr += " "; indentStr += "="; Console.WriteLine("{0}{1}", indentStr, node.Name); RecursivePrintFS(indentCount + 1, node as VirtualFilesystemDirectory); } } }
private static void PrintFileSystem(VirtualFilesystemDirectory root) { Console.WriteLine("Archive Filesystem:"); Console.WriteLine(string.Format("{0} (Root)", root.Name)); RecursivePrintFS(1, root); }
private void ImportFromDiskRecursive(string folder, VirtualFilesystemDirectory dir) { if (!Directory.Exists(folder)) throw new ArgumentException("You must specify a directory that exists", "folder"); // For each directory that is a child of the specified folder into ourselves, and then import the contents. DirectoryInfo dirInfo = new DirectoryInfo(folder); foreach(var diskDir in dirInfo.GetDirectories()) { VirtualFilesystemDirectory vfDir = new VirtualFilesystemDirectory(diskDir.Name); dir.Children.Add(vfDir); ImportFromDiskRecursive(diskDir.FullName, vfDir); } foreach(var diskFile in dirInfo.GetFiles()) { using(BinaryReader reader = new BinaryReader(File.Open(diskFile.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) { string fileName = Path.GetFileNameWithoutExtension(diskFile.Name); string fileExt = Path.GetExtension(diskFile.Name); byte[] data = reader.ReadBytes((int)reader.BaseStream.Length); VirtualFilesystemFile vfFile = new VirtualFilesystemFile(fileName, fileExt, new VirtualFileContents(data)); dir.Children.Add(vfFile); } } }
private void ExportToDiskRecursive(string folder, VirtualFilesystemDirectory dir) { // Create the directory that this node represents. // If it's a directory, append the directory name to the folder and onwards! folder = string.Format("{0}{1}/", folder, dir.Name); try { Directory.CreateDirectory(folder); } catch (Exception ex) { Console.WriteLine("Caught exception while trying to create folder {0}: {1}", folder, ex.ToString()); } foreach (var node in dir.Children) { VirtualFilesystemDirectory vfDir = node as VirtualFilesystemDirectory; VirtualFilesystemFile vfFile = node as VirtualFilesystemFile; if (vfDir != null) { ExportToDiskRecursive(folder, vfDir); } else if (vfFile != null) { // However, if it's a file we're going to write it to disk. string filePath = string.Format("{0}{1}{2}", folder, vfFile.Name, vfFile.Extension); try { using (EndianBinaryWriter writer = new EndianBinaryWriter(File.Create(filePath), Endian.Big)) { writer.Write(vfFile.File.GetData()); } } catch (Exception ex) { Console.WriteLine("Caught exception while trying to write file {0}: {1}", filePath, ex.ToString()); } } } }