/// <summary> /// Extract a file to byte array using the path /// </summary> /// <param name="fullPath">Internal full path for the file to extract</param> /// <returns>Uncompressed data and no error string on success, null data and an error string otherwise</returns> public (byte[] data, string err) Extract(string fullPath) { // If the file isn't in the archive, we can't extract it if (!Exists(fullPath)) { return(null, $"Path '{fullPath}' does not exist in the archive"); } // Get a local reference to the file we care about CompressedFile file = Files[fullPath]; // Attempt to read the compressed data inputStream.Seek(DataStart + file.Offset, SeekOrigin.Begin); byte[] compressedData = new byte[file.CompressedSize]; int read = inputStream.Read(compressedData, 0, (int)file.CompressedSize); if (read != (int)file.CompressedSize) { return(null, "Could not read all required data"); } // Decompress the data List <byte> output = new List <byte>(); int ret = BlastDecoder.Blast(compressedData, output); if (ret != 0) { return(null, $"Blast error: {ret}"); } // Return the decompressed data return(output.ToArray(), null); }
/// <summary> /// Load the file set as the current path /// </summary> /// <returns>Success and error strings, if applicable</returns> private (bool success, string err) LoadFile() { // If the file doesn't exist, we can't do anything if (!File.Exists(FilePath)) { return(false, $"File '{FilePath}' does not exist"); } // Attempt to open the file for reading try { inputStream = File.OpenRead(FilePath); } catch { return(false, "Cannot open file"); } // Create the header from the input file Header = Header.Create(inputStream); if (Header == null) { return(false, "Header could not be read"); } // Validate the file signature if (Header.Signature != 0x8C655D13) { return(false, "Invalid signature"); } // Validate the TOC long fileSize = inputStream.Length; if (Header.TocAddress >= fileSize) { return(false, $"Invalid TOC address: {Header.TocAddress}"); } // Move to the TOC inputStream.Seek(Header.TocAddress, SeekOrigin.Begin); // Read all directory info for (int i = 0; i < Header.DirCount; i++) { ushort fileCount = inputStream.ReadUInt16(); ushort chunkSize = inputStream.ReadUInt16(); string name = inputStream.ReadUInt16HeaderedString(); inputStream.Seek(chunkSize - name.Length - 6, SeekOrigin.Current); Directories.Add(new ArchiveDirectory { Name = name, FileCount = fileCount }); } // For each directory, read all file info uint accumOffset = 0; foreach (ArchiveDirectory directory in Directories) { for (int i = 0; i < directory.FileCount; i++) { // Read in the file information inputStream.Seek(7, SeekOrigin.Current); // 00-06 uint compressedSize = inputStream.ReadUInt32(); // 07-10 inputStream.Seek(12, SeekOrigin.Current); // 11-22 ushort chunkSize = inputStream.ReadUInt16(); // 23-24 inputStream.Seek(4, SeekOrigin.Current); // 25-28 string filename = inputStream.ReadUInt8HeaderedString(); // 29-XX inputStream.Seek(chunkSize - filename.Length - 30, SeekOrigin.Current); // Determine the full path of the internal file string fullpath; if (!string.IsNullOrWhiteSpace(directory.Name) && directory.Name.Length > 0) { fullpath = Path.Combine(directory.Name, filename); } else { fullpath = filename; } // Add the file to the list Files[fullpath] = new CompressedFile { Name = filename, FullPath = fullpath, CompressedSize = compressedSize, Offset = accumOffset, }; // Accumulate the new offset accumOffset += compressedSize; } } return(true, null); }