/// <summary> /// Generates a ArcFileSystemEntry from a U8Node. /// </summary> /// <param name="inputBinaryStream">The input stream containing the ARC container.</param> /// <param name="stringTableBase">Offset in the ARC of the string table.</param> /// <param name="nodes">Array of all U8Nodes in the ARC.</param> /// <param name="nodeIdx"> /// The index of the node to parse. /// This will be updated to the index of the next node in the directory. /// </param> /// <returns>The ArcFileSystemEntry corresponding to the U8Node.</returns> private ArcFileSystemEntry ParseNode(EndianBinaryReader inputBinaryStream, long stringTableBase, U8Node[] nodes, ref int nodeIdx) { U8Node currentNode = nodes[nodeIdx++]; inputBinaryStream.BaseStream.Position = stringTableBase + currentNode.NameOffset; string name = inputBinaryStream.ReadAsciiString(); if (currentNode.Type == U8Node.TYPE_FILE) { inputBinaryStream.BaseStream.Position = currentNode.DataOffset; byte[] data = inputBinaryStream.ReadBytesOrThrow(currentNode.Size); return(new ArcFileSystemFile(name, data)); } else if (currentNode.Type == U8Node.TYPE_DIRECTORY) { ArcFileSystemDirectory dir = new ArcFileSystemDirectory(name); /* n.Size contains the index of the end entry in the directory. * Since parseNode() updates nodeIdx to the next entry in the directory, * we should entries until we reach the end of the directory. */ while (nodeIdx != currentNode.Size) { dir.Entries.Add(ParseNode(inputBinaryStream, stringTableBase, nodes, ref nodeIdx)); } return(dir); } else { throw new InvalidArcFileException("Invalid node Type in ARC container."); } }
public static U8Node Build(string input_directory) { var node = new U8Node(); Pack(input_directory, node); return(node); }
/// <summary> /// Create a new ArcContainer from the given .arc file. /// </summary> /// <param name="inputStream">Stream containing the .arc file.</param> public ArcContainer(Stream inputStream) { EndianBinaryReader inputBinaryStream = new EndianBinaryReader(EndianBitConverter.Big, inputStream); if (inputBinaryStream.ReadUInt32() != ArcMagic) { throw new InvalidArcFileException("Invalid magic number."); } int rootNodeOffset = inputBinaryStream.ReadInt32(); int nodesAndStringTableSize = inputBinaryStream.ReadInt32(); int dataOffset = inputBinaryStream.ReadInt32(); // Hackily get all the nodes and the offset of the string table inputStream.Position = rootNodeOffset; U8Node rootNode = U8Node.Read(inputBinaryStream); U8Node[] nodes = new U8Node[rootNode.Size]; nodes[0] = rootNode; for (int i = 1; i < rootNode.Size; i++) { nodes[i] = U8Node.Read(inputBinaryStream); } long stringTableBase = inputStream.Position; int nodeIdx = 0; Root = (ArcFileSystemDirectory)ParseNode(inputBinaryStream, stringTableBase, nodes, ref nodeIdx); }
/// <summary> /// Create a directory node. /// </summary> /// <param name="name"></param> /// <param name="parentNode"></param> /// <param name="childCount"></param> /// <returns></returns> private U8Node createDirNode(string dirName, uint parentNode) { U8Node dirNode = new U8Node(); dirNode.type_name = 0x01000000 | (uint)_stringTable.Count; dirNode.data_offset = parentNode; dirNode.compressed_size = 0; // initialized later dirNode.file_size = 0; // Append the name to the string table. string name = Path.GetFileName(dirName); byte[] utfBytes = Encoding.UTF8.GetBytes(name); _stringTable.AddRange(utfBytes); _stringTable.Add(0); _nodes.Add(dirNode); dirNode.node_idx = (uint)_nodes.Count - 1; uint nodeIdxPlusOne = dirNode.node_idx + 1; // Update the "last child index" value of all parent nodes. if (_nodes.Count > 1) { U8Node nextParent = _nodes[(int)parentNode]; U8Node curParent; do { curParent = nextParent; curParent.compressed_size = nodeIdxPlusOne; nextParent = _nodes[(int)curParent.data_offset]; } while (nextParent != curParent); } return(dirNode); }
/// <summary> /// Create a file node. /// </summary> /// <param name="parentNode">Parent node.</param> /// <param name="srcFilename">Source filename.</param> /// <returns></returns> private U8Node createFileNode(U8Node parentNode, string srcFilename) { U8Node fileNode = new U8Node(); fileNode.type_name = (uint)_stringTable.Count; // These are initialized later. fileNode.data_offset = 0; fileNode.compressed_size = 0; fileNode.file_size = 0; fileNode.srcFilename = srcFilename; // Append the name to the string table. string name = Path.GetFileName(srcFilename); byte[] utfBytes = Encoding.UTF8.GetBytes(name); _stringTable.AddRange(utfBytes); _stringTable.Add(0); _nodes.Add(fileNode); fileNode.node_idx = (uint)_nodes.Count - 1; uint nodeIdxPlusOne = fileNode.node_idx + 1; // Update the "last child index" value of all parent nodes. U8Node nextParent = parentNode; U8Node curParent; do { curParent = nextParent; curParent.compressed_size = nodeIdxPlusOne; nextParent = _nodes[(int)curParent.data_offset]; } while (nextParent != curParent); return(fileNode); }
private U8Directory ReadFileTable(uint nodeOffset) { U8Directory root = new U8Directory(null, ROOT_DIR); filestream.Position = nodeOffset; U8Node rootNode = new U8Node() { type = filestream.ReadUInt8(), nameOffset = filestream.ReadUInt24BE(), dataOffset = filestream.ReadUInt32BE(), size = filestream.ReadUInt32BE() }; if (rootNode.type != DIR) { throw new InvalidDataException("Root node of U8 archive was not a directory"); } var stringTableOffset = nodeOffset + 12 * rootNode.size; var lastNodes = new Stack <uint>(); lastNodes.Push(rootNode.size); U8Directory currentDir = root; for (var i = 1; i < rootNode.size; i++) { if (i == lastNodes.Peek()) { lastNodes.Pop(); currentDir = currentDir.Parent as U8Directory; } var node = new U8Node() { type = filestream.ReadUInt8(), nameOffset = filestream.ReadUInt24BE(), dataOffset = filestream.ReadUInt32BE(), size = filestream.ReadUInt32BE() }; var pos = filestream.Position; filestream.Position = stringTableOffset + node.nameOffset; var name = filestream.ReadASCIINullTerminated(); filestream.Position = pos; if (node.type == DIR) { var newDir = new U8Directory(currentDir, name); currentDir.AddDir(newDir); currentDir = newDir; lastNodes.Push(node.size); } else { currentDir.AddFile(new OffsetFile(name, currentDir, filestream, node.dataOffset, node.size)); } } return(root); }
public int Size; // For directories, the index of the end node. public static U8Node Read(EndianBinaryReader s) { U8Node node = new U8Node(); uint tmp = s.ReadUInt32(); node.Type = (byte)(tmp >> 24); node.NameOffset = (int)(tmp & 0x00FFFFFF); node.DataOffset = s.ReadInt32(); node.Size = s.ReadInt32(); return(node); }
public void ImportU8() { if (Program.OpenFile(FileFilters.U8Import, out string path) > 0) { U8Node node = NodeFactory.FromFile(null, path) as U8Node; U8FolderNode n = new U8FolderNode(); foreach (ResourceNode r in node.Children) { n.AddChild(r); } n.Name = node.Name; ((U8FolderNode)_resource).AddChild(n); BaseWrapper w = FindResource(n, true); w.EnsureVisible(); w.TreeView.SelectedNode = w; } }
/// <summary> /// Recursively add files and subdirectories from the specified source directory. /// </summary> /// <param name="parentNode">Parent node.</param> /// <param name="srcDirectory">Source directory.</param> protected void addSubdirNodes(U8Node parentNode, string srcDirectory) { // Get files in the specified directory, non-recursive. string[] files = Directory.GetFiles(srcDirectory, "*", SearchOption.TopDirectoryOnly); Array.Sort(files); foreach (string file in files) { createFileNode(parentNode, file); } // Get subdirectories in this directory. string[] subdirs = Directory.GetDirectories(srcDirectory, "*", SearchOption.TopDirectoryOnly); Array.Sort(subdirs); foreach (string subdir in subdirs) { U8Node dirNode = createDirNode(subdir, parentNode.node_idx); addSubdirNodes(dirNode, subdir); } }
private void GenerateNodes(ArcFileSystemEntry currentEntry, List <U8Node> nodes, List <ArcFileSystemEntry> entries, ref int currentNodeId, int parentNodeId, ref int currentStringTableOffset, ref int currentDataOffset) { // Add the node and the entry to the enumeration U8Node node = new U8Node(); nodes.Add(node); entries.Add(currentEntry); int thisNodeId = currentNodeId; currentNodeId++; // Reserve space for the name in the string table node.NameOffset = currentStringTableOffset; currentStringTableOffset += currentEntry.Name.Length + 1; if (currentEntry is ArcFileSystemDirectory) { ArcFileSystemDirectory currentDirectory = (ArcFileSystemDirectory)currentEntry; foreach (ArcFileSystemEntry subEntry in currentDirectory.Entries) { GenerateNodes(subEntry, nodes, entries, ref currentNodeId, thisNodeId, ref currentStringTableOffset, ref currentDataOffset); } node.Type = U8Node.TYPE_DIRECTORY; node.DataOffset = parentNodeId; node.Size = currentNodeId; } else if (currentEntry is ArcFileSystemFile) { ArcFileSystemFile currentFile = (ArcFileSystemFile)currentEntry; node.Type = U8Node.TYPE_FILE; node.DataOffset = currentDataOffset; node.Size = currentFile.Data.Length; currentDataOffset = PaddingUtils.Align(currentDataOffset + currentFile.Data.Length, 0x20); } }
public void ImportU8() { string path; if (Program.OpenFile("U8 Archive (*.arc)|*.arc|" + "Compressed U8 Archive (*.szs)|*.szs|" + "Archive Pair (*.pair)|*.pair", out path) > 0) { U8Node node = NodeFactory.FromFile(null, path) as U8Node; U8FolderNode n = new U8FolderNode(); foreach (ResourceNode r in node.Children) { n.AddChild(r); } n.Name = node.Name; ((U8FolderNode)_resource).AddChild(n); BaseWrapper w = this.FindResource(n, true); w.EnsureVisible(); w.TreeView.SelectedNode = w; } }
/// <summary> /// Write the ARC file using the specified file list. /// </summary> /// <param name="arcFile">ARC file.</param> /// <param name="srcDirectory">Source directory.</param> public void WriteArc(string arcFile, string srcDirectory) { // Standard U8 header // To be filled in: // - $0008 (BE32): Total size of node table and string table. // - $000C (BE32): Start of data. byte[] U8Header = new byte[32] { 0x55, 0xAA, 0x38, 0x2D, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0xF9, 0x12, 0x00, 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Create the root node. _nodes.Clear(); _stringTable.Clear(); U8Node rootNode = createDirNode("", 0); // Add nodes recursively. addSubdirNodes(rootNode, srcDirectory); // Update the U8 header for the string table length // and data offset. int tableLength = (_nodes.Count * 16) + _stringTable.Count; int dataOffset = U8Header.Length + tableLength; dataOffset = ((dataOffset + 0x1F) & ~0x1F); byte[] b_tableLength = BitConverter.GetBytes((uint)tableLength); byte[] b_dataOffset = BitConverter.GetBytes((uint)dataOffset); if (BitConverter.IsLittleEndian) { Array.Reverse(b_tableLength); Array.Reverse(b_dataOffset); } Array.Copy(b_tableLength, 0, U8Header, 0x08, 4); Array.Copy(b_dataOffset, 0, U8Header, 0x0C, 4); // Write stuff. using (FileStream fs = File.Create(arcFile)) { // Write files, uncompressed, starting at the data offset. // NOTE: Files are aligned on 32-byte offsets. fs.Seek(dataOffset, SeekOrigin.Begin); foreach (U8Node node in _nodes) { if ((node.type_name & 0xFF000000) != 0) { // Directory node. continue; } // Open the file. using (FileStream fs_src = File.OpenRead(node.srcFilename)) { node.data_offset = (uint)fs.Position; node.file_size = (uint)fs_src.Length; // Compress the source data using zlib. using (MemoryStream memStream = new MemoryStream()) { using (ZlibStream zStream = new ZlibStream(memStream, CompressionLevel.Fastest, true)) { fs_src.CopyTo(zStream); } node.compressed_size = (uint)memStream.Length; if (memStream.Length > 0) { // Write the compressed data. memStream.Seek(0, SeekOrigin.Begin); memStream.CopyTo(fs); } } // Make sure we're aligned to 32 bytes. alignFileStreamTo32Bytes(fs); } } // Write the U8 header. fs.Seek(0, SeekOrigin.Begin); fs.Write(U8Header, 0, U8Header.Length); // Write the nodes. foreach (U8Node node in _nodes) { byte[] b_type_name = BitConverter.GetBytes(node.type_name); byte[] b_data_offset = BitConverter.GetBytes(node.data_offset); byte[] b_compressed_size = BitConverter.GetBytes(node.compressed_size); byte[] b_file_size = BitConverter.GetBytes(node.file_size); if (BitConverter.IsLittleEndian) { Array.Reverse(b_type_name); Array.Reverse(b_data_offset); Array.Reverse(b_compressed_size); Array.Reverse(b_file_size); } fs.Write(b_type_name, 0, b_type_name.Length); fs.Write(b_data_offset, 0, b_data_offset.Length); fs.Write(b_compressed_size, 0, b_compressed_size.Length); fs.Write(b_file_size, 0, b_file_size.Length); } // Write the string table. fs.Write(_stringTable.ToArray(), 0, _stringTable.Count); fs.Flush(); fs.Close(); } }