protected override void Open(string filePath) { try { this.Magic = this.BinaryReader.ReadUInt32(); if (this.Magic == MW_HEADER_MAGIC) // Morrowind uses this as version { var header = new BSAHeaderMW(this.BinaryReader, this.Magic); this.Header = header; this.FileCount = (int)header.FileCount; this.Files.Capacity = this.FileCount; uint dataOffset = BSAHeaderMW.Size + header.HashOffset + header.FileCount * 8; // 8 = filename hash size // Store file sizes and offsets for (int i = 0; i < header.FileCount; i++) { uint size = this.BinaryReader.ReadUInt32(); uint offset = this.BinaryReader.ReadUInt32() + dataOffset; this.Files.Add(new BSAFileEntry(this, offset.ToString(), offset, size)); this.Files[i].Index = i; } // Check if archive has name offset and name table, for example for Xbox this.BinaryReader.BaseStream.Position = BSAHeaderMW.Size + header.FileCount * 8; // Skip header and entries, 8 = file entry size // First name offset should be 0 if there is one, otherwise it doesn't have one hasNameTableMW = this.BinaryReader.ReadUInt32() == 0; if (hasNameTableMW) { this.BinaryReader.BaseStream.Position = BSAHeaderMW.Size + header.FileCount * 12; // Seek to name table } else { this.BinaryReader.BaseStream.Position -= 4; // Go Back } for (int i = 0; i < header.FileCount; i++) { if (hasNameTableMW) { this.Files[i].FullPath = this.BinaryReader.ReadStringTo('\0'); } else { this.Files[i].FullPath = string.Format("{0:X}", this.BinaryReader.ReadUInt64()); } this.Files[i].FullPathOriginal = this.Files[i].FullPath; } } else if (this.Magic == BSA_HEADER_MAGIC) { var header = new BSAHeader(this.BinaryReader); this.Header = header; this.FileCount = (int)header.FileCount; this.Files.Capacity = this.FileCount; int[] numfiles = new int[header.FolderCount]; for (int i = 0; i < header.FolderCount; i++) { // Skip hash this.BinaryReader.BaseStream.Position += 8; // Read fileCount numfiles[i] = this.BinaryReader.ReadInt32(); // Skip Unk1 + offset (4 + 8 bytes) if SSE. Otherwise only offset (4 bytes) this.BinaryReader.BaseStream.Position += header.Version == SSE_HEADER_VERSION ? 12 : 4; } for (int i = 0; i < header.FolderCount; i++) { string folder = BinaryReader.ReadString(BinaryReader.ReadByte() - 1); this.BinaryReader.BaseStream.Position++; for (int j = 0; j < numfiles[i]; j++) { // Skip hash this.BinaryReader.BaseStream.Position += 8; uint size = this.BinaryReader.ReadUInt32(); uint offset = this.BinaryReader.ReadUInt32(); bool comp = this.Compressed; if ((size & (1 << 30)) != 0) { comp = !comp; size ^= 1 << 30; } this.Files.Add(new BSAFileEntry(this, comp, folder, offset, size)); this.Files[j].Index = j; } } // Grab the uncompressed file size before each data block if (this.RetrieveRealSize) { // Save the position so we can go back after to the name table long pos = this.BinaryReader.BaseStream.Position; for (int i = 0; i < header.FileCount; i++) { var entry = this.Files[i] as BSAFileEntry; if (!entry.Compressed) { continue; } this.BinaryReader.BaseStream.Position = (long)entry.Offset; if (this.ContainsFileNameBlobs) { this.BinaryReader.BaseStream.Position += this.BinaryReader.ReadByte() + 1; } entry.RealSize = this.BinaryReader.ReadUInt32(); } this.BinaryReader.BaseStream.Position = pos; } // Read name table for (int i = 0; i < header.FileCount; i++) { this.Files[i].FullPath = Path.Combine( this.Files[i].FullPath, this.BinaryReader.ReadStringTo('\0')); this.Files[i].FullPathOriginal = this.Files[i].FullPath; } } else { // Assume it's a Fallout 2 DAT this.BinaryReader.BaseStream.Position = this.BinaryReader.BaseStream.Length - 8; uint treeSize = this.BinaryReader.ReadUInt32(); uint dataSize = this.BinaryReader.ReadUInt32(); if (dataSize != this.BinaryReader.BaseStream.Length) { this.BinaryReader.Close(); throw new ArgumentException("File is not a valid bsa archive.", nameof(filePath)); } this.BinaryReader.BaseStream.Position = dataSize - treeSize - 8; this.FileCount = this.BinaryReader.ReadInt32(); this.Files.Capacity = this.FileCount; for (int i = 0; i < this.FileCount; i++) { var entry = new DAT2FileEntry(this.BinaryReader); this.Files.Add(new BSAFileEntry(this, entry)); this.Files[i].Index = i; } } } catch (Exception) { this.BinaryReader?.Close(); throw; } }
protected override void Open(string filePath) { try { this.Magic = this.BinaryReader.ReadUInt32(); if (this.Magic == MW_HEADER_MAGIC) // Morrowind uses this as version { var header = new BSAHeaderMW(this.BinaryReader, this.Magic); this.Header = header; this.FileCount = (int)header.FileCount; uint dataOffset = 12 + header.HashOffset + header.FileCount * 8; for (int i = 0; i < header.FileCount; i++) { uint size = this.BinaryReader.ReadUInt32(); uint offset = this.BinaryReader.ReadUInt32() + dataOffset; this.Files.Add(new BSAFileEntry(this, offset.ToString(), offset, size)); } // Seek to name table this.BinaryReader.BaseStream.Position = 12 + header.FileCount * 12; for (int i = 0; i < header.FileCount; i++) { this.Files[i].FullPath = this.BinaryReader.ReadStringTo('\0'); } } else if (this.Magic == BSA_HEADER_MAGIC) { var header = new BSAHeader(this.BinaryReader); this.Header = header; this.FileCount = (int)header.FileCount; int[] numfiles = new int[header.FolderCount]; for (int i = 0; i < header.FolderCount; i++) { // Skip hash this.BinaryReader.BaseStream.Position += 8; // Read fileCount numfiles[i] = this.BinaryReader.ReadInt32(); // Skip Unk1 + offset (4 + 8 bytes) if SSE. Otherwise only offset (4 bytes) this.BinaryReader.BaseStream.Position += header.Version == SSE_HEADER_VERSION ? 12 : 4; } for (int i = 0; i < header.FolderCount; i++) { string folder = BinaryReader.ReadString(BinaryReader.ReadByte() - 1); this.BinaryReader.BaseStream.Position++; for (int j = 0; j < numfiles[i]; j++) { // Skip hash this.BinaryReader.BaseStream.Position += 8; uint size = this.BinaryReader.ReadUInt32(); uint offset = this.BinaryReader.ReadUInt32(); bool comp = this.Compressed; if ((size & (1 << 30)) != 0) { comp = !comp; size ^= 1 << 30; } this.Files.Add(new BSAFileEntry(this, comp, folder, offset, size)); } } // Read name table for (int i = 0; i < header.FileCount; i++) { this.Files[i].FullPath = Path.Combine( this.Files[i].FullPath, this.BinaryReader.ReadStringTo('\0')); } } else { // Assume it's a Fallout 2 DAT this.BinaryReader.BaseStream.Position = this.BinaryReader.BaseStream.Length - 8; uint treeSize = this.BinaryReader.ReadUInt32(); uint dataSize = this.BinaryReader.ReadUInt32(); if (dataSize != this.BinaryReader.BaseStream.Length) { this.BinaryReader.Close(); throw new ArgumentException("File is not a valid bsa archive.", nameof(filePath)); } this.BinaryReader.BaseStream.Position = dataSize - treeSize - 8; this.FileCount = this.BinaryReader.ReadInt32(); for (int i = 0; i < this.FileCount; i++) { var entry = new DAT2FileEntry(this.BinaryReader); this.Files.Add(new BSAFileEntry(this, entry)); } } } catch (Exception ex) { this.BinaryReader?.Close(); throw new Exception("An error occured trying to open the archive.", ex); } }