// add it or return it if it's there private static NarcArchiveDirectoryEntry insertFolderInto(NarcArchiveDirectoryEntry _d, string _searchName) { for (int i = 0; i < _d.Entries.Count; ++i) { if (_d.Entries[i].Name == _searchName && _d.Entries[i] is NarcArchiveDirectoryEntry) { return((NarcArchiveDirectoryEntry)_d.Entries[i]); } } NarcArchiveDirectoryEntry _newEntry = new NarcArchiveDirectoryEntry(); _newEntry.Name = _searchName; _newEntry.Parent = _d; _d.Entries.Add(_newEntry); return(_newEntry); }
/// <summary> /// Initializes an instance of <see cref="NarcArchiveRootDirectoryEntry"/> from a specified path. /// </summary> /// <param name="path">The path.</param> public NarcArchiveRootDirectoryEntry(string path) { Path = path; var directoryEntries = new Queue <NarcArchiveDirectoryEntry>(); directoryEntries.Enqueue(this); while (directoryEntries.Count > 0) { var currentDirectoryEntry = directoryEntries.Dequeue(); var fileSystemEntries = Directory.EnumerateFileSystemEntries(currentDirectoryEntry.Path); foreach (var fileSystemEntry in fileSystemEntries) { var isDirectory = File.GetAttributes(fileSystemEntry).HasFlag(FileAttributes.Directory); if (isDirectory) { var directoryEntry = new NarcArchiveDirectoryEntry { Name = new DirectoryInfo(fileSystemEntry).Name, Path = fileSystemEntry, Parent = currentDirectoryEntry, }; currentDirectoryEntry.Entries.Add(directoryEntry); directoryEntries.Enqueue(directoryEntry); } else { var fileEntry = new NarcArchiveFileEntry { Name = new FileInfo(fileSystemEntry).Name, Path = fileSystemEntry, Directory = currentDirectoryEntry, }; currentDirectoryEntry.Entries.Add(fileEntry); } } } }
public static void create(string[] _inNames, CopyOnlyStream[] _inStreams, string outputPath) { bool _usingFilenames = (_inNames != null); NarcArchiveRootDirectoryEntry _root = new NarcArchiveRootDirectoryEntry(); if (_usingFilenames) { // make all the parent directory thingies and then shove the file entries into them. for (int i = 0; i < _inStreams.Length; ++i) { NarcArchiveDirectoryEntry _curParent = _root; int _startSearchIndex = 0; while (true) { int _nextSlashIndex = _inNames[i].IndexOf('/', _startSearchIndex, _inNames[i].Length - _startSearchIndex); if (_nextSlashIndex != -1) { String _curFolderName = _inNames[i].Substring(_startSearchIndex, _nextSlashIndex - _startSearchIndex); _curParent = insertFolderInto(_curParent, _curFolderName); _startSearchIndex = _nextSlashIndex + 1; } else { break; } } _curParent.Entries.Add(new NarcArchiveFileEntry { dataStream = _inStreams[i], Name = Path.GetFileName(_inNames[i]) }); } } else { for (int i = 0; i < _inStreams.Length; ++i) { _root.Entries.Add(new NarcArchiveFileEntry { dataStream = _inStreams[i] }); } } lowCreate(_root, outputPath, _usingFilenames); }
// returns tuple with: // fimgPosition // hasFilenames // fileEntries public static Tuple <long, bool, List <NarcArchiveFileEntry> > startReadingNarc(FileStream input, BinaryReader reader, string inputPathForErrorMessages, bool ignoreFilenames) { // Read the NARC header if (!(reader.ReadByte() == 'N' && reader.ReadByte() == 'A' && reader.ReadByte() == 'R' && reader.ReadByte() == 'C' && reader.ReadByte() == 0xFE && reader.ReadByte() == 0xFF && reader.ReadByte() == 0 && reader.ReadByte() == 1 && reader.ReadInt32() == input.Length)) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPathForErrorMessages))); } var headerLength = reader.ReadInt16(); var fatbPosition = headerLength; // Read the FATB section input.Position = fatbPosition; if (!(reader.ReadByte() == 'B' && reader.ReadByte() == 'T' && reader.ReadByte() == 'A' && reader.ReadByte() == 'F')) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPathForErrorMessages))); } var fatbLength = reader.ReadInt32(); var fntbPosition = fatbPosition + fatbLength; var fileEntryCount = reader.ReadInt32(); var fileEntries = new List <NarcArchiveFileEntry>(fileEntryCount); for (var i = 0; i < fileEntryCount; i++) { var offset = reader.ReadInt32(); var length = reader.ReadInt32() - offset; fileEntries.Add(new NarcArchiveFileEntry { Offset = offset, Length = length, }); } // Read the FNTB section input.Position = fntbPosition; if (!(reader.ReadByte() == 'B' && reader.ReadByte() == 'T' && reader.ReadByte() == 'N' && reader.ReadByte() == 'F')) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPathForErrorMessages))); } var fntbLength = reader.ReadInt32(); long fimgPosition = fntbPosition + fntbLength; var hasFilenames = !ignoreFilenames; // If the FNTB length is 16 or less, it's impossible for the entries to have filenames. // This section will always be at least 16 bytes long, but technically it's only required to be at least 8 bytes long. if (fntbLength <= 16) { hasFilenames = false; } var rootNameEntryOffset = reader.ReadInt32(); // If the root name entry offset is 4, then the entries don't have filenames. if (rootNameEntryOffset == 4) { hasFilenames = false; } if (hasFilenames) { var rootFirstFileIndex = reader.ReadInt16(); var rootDirectory = new NarcArchiveRootDirectoryEntry(); var directoryEntryCount = reader.ReadInt16(); // This includes the root directory var directoryEntries = new List <NarcArchiveDirectoryEntry>(directoryEntryCount) { rootDirectory, }; // This NARC contains filenames and directory names, so read them for (var i = 1; i < directoryEntryCount; i++) { var nameEntryTableOffset = reader.ReadInt32(); var firstFileIndex = reader.ReadInt16(); var parentDirectoryIndex = reader.ReadInt16() & 0xFFF; directoryEntries.Add(new NarcArchiveDirectoryEntry { Index = i, Parent = directoryEntries[parentDirectoryIndex], NameEntryOffset = nameEntryTableOffset, FirstFileIndex = firstFileIndex, }); } NarcArchiveDirectoryEntry currentDirectory = rootDirectory; var directoryIndex = 0; var fileIndex = 0; while (directoryIndex < directoryEntryCount) { var entryNameLength = reader.ReadByte(); if ((entryNameLength & 0x80) != 0) { // This is a directory name entry var entryName = reader.ReadString(entryNameLength & 0x7F); var entryDirectoryIndex = reader.ReadInt16() & 0xFFF; var directoryEntry = directoryEntries[entryDirectoryIndex]; directoryEntry.Name = entryName; } else if (entryNameLength != 0) { // This is a file name entry var entryName = reader.ReadString(entryNameLength); var fileEntry = fileEntries[fileIndex]; fileEntry.Parent = directoryEntries[directoryIndex]; fileEntry.Name = entryName; fileIndex++; } else { // This is the end of a directory directoryIndex++; if (directoryIndex >= directoryEntryCount) { break; } currentDirectory = directoryEntries[directoryIndex]; } } } return(new Tuple <long, bool, List <NarcArchiveFileEntry> >(fimgPosition, hasFilenames, fileEntries)); }
public static void Extract(string inputPath, string outputPath, bool ignoreFilenames = false, bool _justPrintNames = false) { using (var input = new FileStream(inputPath, FileMode.Open, FileAccess.Read)) using (var reader = new BinaryReader(input)) { // Read the NARC header if (!(reader.ReadByte() == 'N' && reader.ReadByte() == 'A' && reader.ReadByte() == 'R' && reader.ReadByte() == 'C' && reader.ReadByte() == 0xFE && reader.ReadByte() == 0xFF && reader.ReadByte() == 0 && reader.ReadByte() == 1 && reader.ReadInt32() == input.Length)) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPath))); } var headerLength = reader.ReadInt16(); var fatbPosition = headerLength; // Read the FATB section input.Position = fatbPosition; if (!(reader.ReadByte() == 'B' && reader.ReadByte() == 'T' && reader.ReadByte() == 'A' && reader.ReadByte() == 'F')) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPath))); } var fatbLength = reader.ReadInt32(); var fntbPosition = fatbPosition + fatbLength; var fileEntryCount = reader.ReadInt32(); var fileEntries = new List <NarcArchiveFileEntry>(fileEntryCount); for (var i = 0; i < fileEntryCount; i++) { var offset = reader.ReadInt32(); var length = reader.ReadInt32() - offset; fileEntries.Add(new NarcArchiveFileEntry { Offset = offset, Length = length, }); } // Read the FNTB section input.Position = fntbPosition; if (!(reader.ReadByte() == 'B' && reader.ReadByte() == 'T' && reader.ReadByte() == 'N' && reader.ReadByte() == 'F')) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPath))); } var fntbLength = reader.ReadInt32(); var fimgPosition = fntbPosition + fntbLength; var hasFilenames = !ignoreFilenames; // If the FNTB length is 16 or less, it's impossible for the entries to have filenames. // This section will always be at least 16 bytes long, but technically it's only required to be at least 8 bytes long. if (fntbLength <= 16) { hasFilenames = false; } var rootNameEntryOffset = reader.ReadInt32(); // If the root name entry offset is 4, then the entries don't have filenames. if (rootNameEntryOffset == 4) { hasFilenames = false; } if (hasFilenames) { var rootFirstFileIndex = reader.ReadInt16(); var rootDirectory = new NarcArchiveRootDirectoryEntry(); var directoryEntryCount = reader.ReadInt16(); // This includes the root directory var directoryEntries = new List <NarcArchiveDirectoryEntry>(directoryEntryCount) { rootDirectory, }; // This NARC contains filenames and directory names, so read them for (var i = 1; i < directoryEntryCount; i++) { var nameEntryTableOffset = reader.ReadInt32(); var firstFileIndex = reader.ReadInt16(); var parentDirectoryIndex = reader.ReadInt16() & 0xFFF; directoryEntries.Add(new NarcArchiveDirectoryEntry { Index = i, Parent = directoryEntries[parentDirectoryIndex], NameEntryOffset = nameEntryTableOffset, FirstFileIndex = firstFileIndex, }); } NarcArchiveDirectoryEntry currentDirectory = rootDirectory; var directoryIndex = 0; var fileIndex = 0; while (directoryIndex < directoryEntryCount) { var entryNameLength = reader.ReadByte(); if ((entryNameLength & 0x80) != 0) { // This is a directory name entry var entryName = reader.ReadString(entryNameLength & 0x7F); var entryDirectoryIndex = reader.ReadInt16() & 0xFFF; var directoryEntry = directoryEntries[entryDirectoryIndex]; directoryEntry.Name = entryName; if (_justPrintNames) { Console.WriteLine("Dir: " + entryName); } } else if (entryNameLength != 0) { // This is a file name entry var entryName = reader.ReadString(entryNameLength); var fileEntry = fileEntries[fileIndex]; fileEntry.Directory = directoryEntries[directoryIndex]; fileEntry.Name = entryName; fileIndex++; if (_justPrintNames) { Console.WriteLine(entryName); } } else { // This is the end of a directory directoryIndex++; if (directoryIndex >= directoryEntryCount) { break; } currentDirectory = directoryEntries[directoryIndex]; } } } if (_justPrintNames) { return; } // Read the FIMG section input.Position = fimgPosition; if (!(reader.ReadByte() == 'G' && reader.ReadByte() == 'M' && reader.ReadByte() == 'I' && reader.ReadByte() == 'F')) { throw new InvalidFileTypeException(string.Format(ErrorMessages.NotANarcFile, Path.GetFileName(inputPath))); } if (hasFilenames) { foreach (var fileEntry in fileEntries) { var entryOutputPath = Path.Combine(outputPath, fileEntry.FullName); var entryOutputDirectory = Path.GetDirectoryName(entryOutputPath); if (!Directory.Exists(entryOutputDirectory)) { Directory.CreateDirectory(entryOutputDirectory); } using (var output = new FileStream(entryOutputPath, FileMode.Create, FileAccess.Write)) using (var entryStream = new SubReadStream(input, fimgPosition + 8 + fileEntry.Offset, fileEntry.Length)) { entryStream.CopyTo(output); } } } else { // This NARC doesn't contain filenames and directory names, so just use a file index as their filename var index = 0; var numOfDigits = Math.Floor(Math.Log10(fileEntryCount) + 1); foreach (var fileEntry in fileEntries) { var entryName = $"{Path.GetFileNameWithoutExtension(inputPath)}_{index.ToString($"D{numOfDigits}")}"; var entryOutputPath = Path.Combine(outputPath, entryName); if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } using (var output = new FileStream(entryOutputPath, FileMode.Create, FileAccess.Write)) using (var entryStream = new SubReadStream(input, fimgPosition + 8 + fileEntry.Offset, fileEntry.Length)) { entryStream.CopyTo(output); } index++; } } } }