/// <summary> /// Create a new SgaFileReader. Will immediately read the header information /// for the file and throw <see cref="InvalidSgaException"/> if stream is not a valid SGA. /// </summary> /// <param name="stream">A SGA file stream</param> /// <exception cref="IOException" /> public SgaFileReader(string filePath) { this.reader = new BinaryReader(File.OpenRead(filePath)); try { this.header = ReadFileHeader(); byte[] dataHeaderBuffer = ReadDataHeader(header); SgaDataHeaderInfo dataHeaderInfo = ReadDataHeaderInfo(dataHeaderBuffer); SgaFileEntry[] files = ReadFiles(header, dataHeaderInfo, dataHeaderBuffer); SgaDirectory[] directories = ReadDirs(files, dataHeaderInfo, dataHeaderBuffer); var tocs = ReadTocs(directories, dataHeaderInfo, dataHeaderBuffer); if (tocs.Length != 1) { throw new IOException($"SGA file does not have exactly 1 TOC {filePath}"); } this.directories = new Dictionary <string, SgaDirectory>(directories.Length); foreach (var directory in directories) { this.directories.Add(directory.Name, directory); } } catch (IOException ex) { Dispose(); throw ex; } }
/// <summary> /// Read file header data using Binary Reader. Assumes binary reader is located at beginning /// of header info. /// </summary> /// <exception cref="IOException"/> private SgaFileHeader ReadFileHeader() { string identifier = Encoding.ASCII.GetString(this.reader.ReadBytes(8)); if (!"_ARCHIVE".Equals(identifier)) { throw new InvalidSgaException($"File Identifier '{identifier}' should be '_ARCHIVE'"); } var header = new SgaFileHeader { Identifier = identifier, Version = this.reader.ReadUInt32(), ToolMD5 = this.reader.ReadBytes(16), ArchiveType = Encoding.Unicode.GetString(this.reader.ReadBytes(128)).TrimEnd('\0'), MD5 = this.reader.ReadBytes(16), DataHeaderSize = this.reader.ReadUInt32(), DataOffset = this.reader.ReadUInt32() }; if (header.Version != 2 && header.Version != 4) { throw new InvalidSgaException("SGA Version is not supported"); } if (header.Version == 4) { header.Platform = this.reader.ReadUInt32(); // Value of 1 means Win32, x86 or Little Endian. // It is unknown what a value not of 1 is so we should throw an error. if (header.Platform != 1) { throw new InvalidSgaException($"Unknown Platform '{header.Platform}'"); } } return(header); }
/// <summary> /// Reads the Data header into buffer and verifies the checksum. /// </summary> private byte[] ReadDataHeader(SgaFileHeader header) { var data = this.reader.ReadBytes(Convert.ToInt32(header.DataHeaderSize)); // Here we validate that the checksum matches. If this step passes there is good odds that we are // indeed reading an SGA file. using MD5 md5 = MD5.Create(); byte[] key = Encoding.ASCII.GetBytes("DFC9AF62-FC1B-4180-BC27-11CCE87D3EFF"); var buf = new byte[key.Length + data.Length]; key.CopyTo(buf, 0); data.CopyTo(buf, key.Length); byte[] md5Main = md5.ComputeHash(buf); if (!md5Main.SequenceEqual(header.MD5)) { throw new InvalidSgaException("Header MD5 does not match"); } return(data); }