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);
        }
Example #5
0
        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);
        }
Example #6
0
        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();
        }
Example #7
0
        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();
        }
Example #8
0
        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;
        }
Example #9
0
        /// <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));
        }
Example #10
0
        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);
                }
            }
        }
Example #11
0
 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());
                    }
                }
            }
        }