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; } }
private void OpenSSE() { // magic and version has been read, at position 8 // read header BSAHeader header = BSAHeader.ReadFrom(this.BinaryReader); uint numFiles = header.FileCount; this.Compressed = (header.ArchiveFlags & OB_BSAARCHIVE_COMPRESSFILES) > 0; this.ContainsFileNameBlobs = (header.ArchiveFlags & F3_BSAARCHIVE_PREFIXFULLFILENAMES) > 0; // Seek to start of Folders, then skip to filename table this.BinaryReader.BaseStream.Seek(header.FolderRecordOffset + header.FolderNameLength + header.FolderCount * (1 + BSAFolderInfo.SizeOf(Version)) + header.FileCount * BSAFileInfo.SizeOf(), SeekOrigin.Begin); List <string> fileNames = new List <string>(); for (int i = 0; i < header.FileCount; i++) { fileNames.Add(this.ReadStringTo(this.BinaryReader, '\0')); } this.BinaryReader.BaseStream.Seek(header.FolderRecordOffset, SeekOrigin.Begin); var folderInfos = new List <BSAFolderInfo>(); for (int i = 0; i < header.FolderCount; i++) { var fi = BSAFolderInfo.ReadFrom(this.BinaryReader, this.Version); folderInfos.Add(fi); } int filenameIndex = 0; for (int i = 0; i < folderInfos.Count; i++) { int len = this.BinaryReader.ReadByte(); byte[] b = this.BinaryReader.ReadBytes(len); folderInfos[i].FolderName = Encoding.Default.GetString(b, 0, b.Length - 1); List <BSAFileInfo> fileInfos = new List <BSAFileInfo>(); while (fileInfos.Count < folderInfos[i].FileCount) { var fi = BSAFileInfo.ReadFrom(this.BinaryReader); fi.NamePrefix = this.ContainsFileNameBlobs; fi.Compressed = this.Compressed; fileInfos.Add(fi); } for (int j = 0; j < fileInfos.Count; j++) { fileInfos[j].Name = fileNames[filenameIndex]; filenameIndex++; var fe = new BSAFileEntry(this, i) .Initialize(this.Compressed, folderInfos[i].FolderName, fileInfos[j].Offset, fileInfos[j].SizeFlags); fe.FullPath = Path.Combine(fe.FullPath, fileInfos[j].Name); fe.fileInfo = fileInfos[j]; this.Files.Add(fe); } folderInfos[i].Files.AddRange(fileInfos); } }
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); } }