mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Split BSAReader into multiple files
This commit is contained in:
parent
b6d5464682
commit
65ad0457ed
23
Compression.BSA/BSA/ArchiveFlags.cs
Normal file
23
Compression.BSA/BSA/ArchiveFlags.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum ArchiveFlags : uint
|
||||||
|
{
|
||||||
|
HasFolderNames = 0x1,
|
||||||
|
HasFileNames = 0x2,
|
||||||
|
Compressed = 0x4,
|
||||||
|
Unk4 = 0x8,
|
||||||
|
Unk5 = 0x10,
|
||||||
|
Unk6 = 0x20,
|
||||||
|
XBox360Archive = 0x40,
|
||||||
|
Unk8 = 0x80,
|
||||||
|
HasFileNameBlobs = 0x100,
|
||||||
|
Unk10 = 0x200,
|
||||||
|
Unk11 = 0x400
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
Compression.BSA/BSA/FileFlags.cs
Normal file
20
Compression.BSA/BSA/FileFlags.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum FileFlags : uint
|
||||||
|
{
|
||||||
|
Meshes = 0x1,
|
||||||
|
Textures = 0x2,
|
||||||
|
Menus = 0x4,
|
||||||
|
Sounds = 0x8,
|
||||||
|
Voices = 0x10,
|
||||||
|
Shaders = 0x20,
|
||||||
|
Trees = 0x40,
|
||||||
|
Fonts = 0x80,
|
||||||
|
Miscellaneous = 0x100
|
||||||
|
}
|
||||||
|
}
|
23
Compression.BSA/BSA/Reader/BSAFileStateObject.cs
Normal file
23
Compression.BSA/BSA/Reader/BSAFileStateObject.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Wabbajack.Common.Serialization.Json;
|
||||||
|
using File = Alphaleonis.Win32.Filesystem.File;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
[JsonName("BSAFileState")]
|
||||||
|
public class BSAFileStateObject : FileStateObject
|
||||||
|
{
|
||||||
|
public bool FlipCompression { get; set; }
|
||||||
|
|
||||||
|
public BSAFileStateObject() { }
|
||||||
|
|
||||||
|
public BSAFileStateObject(FileRecord fileRecord)
|
||||||
|
{
|
||||||
|
FlipCompression = fileRecord.FlipCompression;
|
||||||
|
Path = fileRecord.Path;
|
||||||
|
Index = fileRecord._index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
Compression.BSA/BSA/Reader/BSAReader.cs
Normal file
123
Compression.BSA/BSA/Reader/BSAReader.cs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.Common.Serialization.Json;
|
||||||
|
using File = Alphaleonis.Win32.Filesystem.File;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
public class BSAReader : IBSAReader
|
||||||
|
{
|
||||||
|
internal uint _fileCount;
|
||||||
|
internal AbsolutePath _fileName;
|
||||||
|
internal uint _folderCount;
|
||||||
|
internal uint _folderRecordOffset;
|
||||||
|
private List<FolderRecord> _folders;
|
||||||
|
internal string _magic;
|
||||||
|
internal uint _totalFileNameLength;
|
||||||
|
internal uint _totalFolderNameLength;
|
||||||
|
|
||||||
|
public VersionType HeaderType { get; private set; }
|
||||||
|
|
||||||
|
public ArchiveFlags ArchiveFlags { get; private set; }
|
||||||
|
|
||||||
|
public FileFlags FileFlags { get; private set; }
|
||||||
|
|
||||||
|
public IEnumerable<IFile> Files
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
foreach (var folder in _folders)
|
||||||
|
foreach (var file in folder._files)
|
||||||
|
yield return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ArchiveStateObject State => new BSAStateObject(this);
|
||||||
|
|
||||||
|
public bool HasFolderNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFolderNames);
|
||||||
|
|
||||||
|
public bool HasFileNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNames);
|
||||||
|
|
||||||
|
public bool CompressedByDefault => ArchiveFlags.HasFlag(ArchiveFlags.Compressed);
|
||||||
|
|
||||||
|
public bool Bit9Set => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNameBlobs);
|
||||||
|
|
||||||
|
public bool HasNameBlobs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (HeaderType == VersionType.FO3 || HeaderType == VersionType.SSE) return Bit9Set;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dump(Action<string> print)
|
||||||
|
{
|
||||||
|
print($"File Name: {_fileName}");
|
||||||
|
print($"File Count: {_fileCount}");
|
||||||
|
print($"Magic: {_magic}");
|
||||||
|
|
||||||
|
foreach (var file in Files)
|
||||||
|
{
|
||||||
|
print("\n");
|
||||||
|
file.Dump(print);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<BSAReader> LoadAsync(AbsolutePath filename)
|
||||||
|
{
|
||||||
|
using var stream = await filename.OpenRead().ConfigureAwait(false);
|
||||||
|
using var br = new BinaryReader(stream);
|
||||||
|
var bsa = new BSAReader { _fileName = filename };
|
||||||
|
bsa.LoadHeaders(br);
|
||||||
|
return bsa;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BSAReader Load(AbsolutePath filename)
|
||||||
|
{
|
||||||
|
using var stream = File.Open(filename.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
using var br = new BinaryReader(stream);
|
||||||
|
var bsa = new BSAReader { _fileName = filename };
|
||||||
|
bsa.LoadHeaders(br);
|
||||||
|
return bsa;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadHeaders(BinaryReader rdr)
|
||||||
|
{
|
||||||
|
var fourcc = Encoding.ASCII.GetString(rdr.ReadBytes(4));
|
||||||
|
|
||||||
|
if (fourcc != "BSA\0")
|
||||||
|
throw new InvalidDataException("Archive is not a BSA");
|
||||||
|
|
||||||
|
_magic = fourcc;
|
||||||
|
HeaderType = (VersionType)rdr.ReadUInt32();
|
||||||
|
_folderRecordOffset = rdr.ReadUInt32();
|
||||||
|
ArchiveFlags = (ArchiveFlags)rdr.ReadUInt32();
|
||||||
|
_folderCount = rdr.ReadUInt32();
|
||||||
|
_fileCount = rdr.ReadUInt32();
|
||||||
|
_totalFolderNameLength = rdr.ReadUInt32();
|
||||||
|
_totalFileNameLength = rdr.ReadUInt32();
|
||||||
|
FileFlags = (FileFlags)rdr.ReadUInt32();
|
||||||
|
|
||||||
|
LoadFolderRecords(rdr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadFolderRecords(BinaryReader rdr)
|
||||||
|
{
|
||||||
|
_folders = new List<FolderRecord>();
|
||||||
|
for (var idx = 0; idx < _folderCount; idx += 1)
|
||||||
|
_folders.Add(new FolderRecord(this, rdr));
|
||||||
|
|
||||||
|
foreach (var folder in _folders)
|
||||||
|
folder.LoadFileRecordBlock(this, rdr);
|
||||||
|
|
||||||
|
foreach (var folder in _folders)
|
||||||
|
foreach (var file in folder._files)
|
||||||
|
file.LoadFileRecord(this, folder, file, rdr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
Compression.BSA/BSA/Reader/BSAStateObject.cs
Normal file
36
Compression.BSA/BSA/Reader/BSAStateObject.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.Common.Serialization.Json;
|
||||||
|
using File = Alphaleonis.Win32.Filesystem.File;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
[JsonName("BSAState")]
|
||||||
|
public class BSAStateObject : ArchiveStateObject
|
||||||
|
{
|
||||||
|
public string Magic { get; set; }
|
||||||
|
public uint Version { get; set; }
|
||||||
|
public uint ArchiveFlags { get; set; }
|
||||||
|
public uint FileFlags { get; set; }
|
||||||
|
|
||||||
|
public BSAStateObject()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public BSAStateObject(BSAReader bsaReader)
|
||||||
|
{
|
||||||
|
Magic = bsaReader._magic;
|
||||||
|
Version = (uint)bsaReader.HeaderType;
|
||||||
|
ArchiveFlags = (uint)bsaReader.ArchiveFlags;
|
||||||
|
FileFlags = (uint)bsaReader.FileFlags;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<IBSABuilder> MakeBuilder(long size)
|
||||||
|
{
|
||||||
|
return await BSABuilder.Create(this, size).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
144
Compression.BSA/BSA/Reader/FileRecord.cs
Normal file
144
Compression.BSA/BSA/Reader/FileRecord.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using K4os.Compression.LZ4.Streams;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using File = Alphaleonis.Win32.Filesystem.File;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
public class FileRecord : IFile
|
||||||
|
{
|
||||||
|
private readonly BSAReader _bsa;
|
||||||
|
private readonly long _dataOffset;
|
||||||
|
private string _name;
|
||||||
|
private readonly string _nameBlob;
|
||||||
|
private readonly uint _offset;
|
||||||
|
private readonly uint _onDiskSize;
|
||||||
|
private readonly uint _originalSize;
|
||||||
|
private readonly uint _size;
|
||||||
|
internal readonly int _index;
|
||||||
|
|
||||||
|
public uint Size { get; }
|
||||||
|
|
||||||
|
public ulong Hash { get; }
|
||||||
|
|
||||||
|
public FolderRecord Folder { get; }
|
||||||
|
|
||||||
|
public bool FlipCompression { get; }
|
||||||
|
|
||||||
|
public FileRecord(BSAReader bsa, FolderRecord folderRecord, BinaryReader src, int index)
|
||||||
|
{
|
||||||
|
_index = index;
|
||||||
|
_bsa = bsa;
|
||||||
|
Hash = src.ReadUInt64();
|
||||||
|
var size = src.ReadUInt32();
|
||||||
|
FlipCompression = (size & (0x1 << 30)) > 0;
|
||||||
|
|
||||||
|
if (FlipCompression)
|
||||||
|
_size = size ^ (0x1 << 30);
|
||||||
|
else
|
||||||
|
_size = size;
|
||||||
|
|
||||||
|
if (Compressed)
|
||||||
|
_size -= 4;
|
||||||
|
|
||||||
|
_offset = src.ReadUInt32();
|
||||||
|
Folder = folderRecord;
|
||||||
|
|
||||||
|
var old_pos = src.BaseStream.Position;
|
||||||
|
|
||||||
|
src.BaseStream.Position = _offset;
|
||||||
|
|
||||||
|
if (bsa.HasNameBlobs)
|
||||||
|
_nameBlob = src.ReadStringLenNoTerm(bsa.HeaderType);
|
||||||
|
|
||||||
|
|
||||||
|
if (Compressed)
|
||||||
|
_originalSize = src.ReadUInt32();
|
||||||
|
|
||||||
|
_onDiskSize = (uint)(_size - (_nameBlob == null ? 0 : _nameBlob.Length + 1));
|
||||||
|
|
||||||
|
if (Compressed)
|
||||||
|
{
|
||||||
|
Size = _originalSize;
|
||||||
|
_onDiskSize -= 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Size = _onDiskSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
_dataOffset = src.BaseStream.Position;
|
||||||
|
|
||||||
|
src.BaseStream.Position = old_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RelativePath Path
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return string.IsNullOrEmpty(Folder.Name) ? new RelativePath(_name) : new RelativePath(Folder.Name + "\\" + _name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Compressed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (FlipCompression) return !_bsa.CompressedByDefault;
|
||||||
|
return _bsa.CompressedByDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileStateObject State => new BSAFileStateObject(this);
|
||||||
|
|
||||||
|
internal void LoadFileRecord(BSAReader bsaReader, FolderRecord folder, FileRecord file, BinaryReader rdr)
|
||||||
|
{
|
||||||
|
_name = rdr.ReadStringTerm(_bsa.HeaderType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask CopyDataTo(Stream output)
|
||||||
|
{
|
||||||
|
await using var in_file = await _bsa._fileName.OpenRead().ConfigureAwait(false);
|
||||||
|
using var rdr = new BinaryReader(in_file);
|
||||||
|
rdr.BaseStream.Position = _dataOffset;
|
||||||
|
|
||||||
|
if (_bsa.HeaderType == VersionType.SSE)
|
||||||
|
{
|
||||||
|
if (Compressed)
|
||||||
|
{
|
||||||
|
using var r = LZ4Stream.Decode(rdr.BaseStream);
|
||||||
|
await r.CopyToLimitAsync(output, (int)_originalSize).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await rdr.BaseStream.CopyToLimitAsync(output, (int)_onDiskSize).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Compressed)
|
||||||
|
{
|
||||||
|
await using var z = new InflaterInputStream(rdr.BaseStream);
|
||||||
|
await z.CopyToLimitAsync(output, (int)_originalSize).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
await rdr.BaseStream.CopyToLimitAsync(output, (int)_onDiskSize).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dump(Action<string> print)
|
||||||
|
{
|
||||||
|
print($"Name: {_name}");
|
||||||
|
print($"Offset: {_offset}");
|
||||||
|
print($"On Disk Size: {_onDiskSize}");
|
||||||
|
print($"Original Size: {_originalSize}");
|
||||||
|
print($"Size: {_size}");
|
||||||
|
print($"Index: {_index}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Compression.BSA/BSA/Reader/FolderRecord.cs
Normal file
44
Compression.BSA/BSA/Reader/FolderRecord.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using File = Alphaleonis.Win32.Filesystem.File;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
public class FolderRecord
|
||||||
|
{
|
||||||
|
private readonly uint _fileCount;
|
||||||
|
internal List<FileRecord> _files;
|
||||||
|
private ulong _offset;
|
||||||
|
private uint _unk;
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
public ulong Hash { get; }
|
||||||
|
|
||||||
|
internal FolderRecord(BSAReader bsa, BinaryReader src)
|
||||||
|
{
|
||||||
|
Hash = src.ReadUInt64();
|
||||||
|
_fileCount = src.ReadUInt32();
|
||||||
|
if (bsa.HeaderType == VersionType.SSE)
|
||||||
|
{
|
||||||
|
_unk = src.ReadUInt32();
|
||||||
|
_offset = src.ReadUInt64();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_offset = src.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void LoadFileRecordBlock(BSAReader bsa, BinaryReader src)
|
||||||
|
{
|
||||||
|
if (bsa.HasFolderNames) Name = src.ReadStringLen(bsa.HeaderType);
|
||||||
|
|
||||||
|
_files = new List<FileRecord>();
|
||||||
|
for (var idx = 0; idx < _fileCount; idx += 1)
|
||||||
|
_files.Add(new FileRecord(bsa, this, src, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
Compression.BSA/BSA/VersionType.cs
Normal file
16
Compression.BSA/BSA/VersionType.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
public enum VersionType : uint
|
||||||
|
{
|
||||||
|
TES4 = 0x67,
|
||||||
|
FO3 = 0x68, // FO3, FNV, TES5
|
||||||
|
SSE = 0x69,
|
||||||
|
FO4 = 0x01,
|
||||||
|
TES3 = 0xFF // Not a real Bethesda version number
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,372 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
|
||||||
using K4os.Compression.LZ4.Streams;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.Common.Serialization.Json;
|
|
||||||
using File = Alphaleonis.Win32.Filesystem.File;
|
|
||||||
|
|
||||||
namespace Compression.BSA
|
|
||||||
{
|
|
||||||
public enum VersionType : uint
|
|
||||||
{
|
|
||||||
TES4 = 0x67,
|
|
||||||
FO3 = 0x68, // FO3, FNV, TES5
|
|
||||||
SSE = 0x69,
|
|
||||||
FO4 = 0x01,
|
|
||||||
TES3 = 0xFF // Not a real Bethesda version number
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum ArchiveFlags : uint
|
|
||||||
{
|
|
||||||
HasFolderNames = 0x1,
|
|
||||||
HasFileNames = 0x2,
|
|
||||||
Compressed = 0x4,
|
|
||||||
Unk4 = 0x8,
|
|
||||||
Unk5 = 0x10,
|
|
||||||
Unk6 = 0x20,
|
|
||||||
XBox360Archive = 0x40,
|
|
||||||
Unk8 = 0x80,
|
|
||||||
HasFileNameBlobs = 0x100,
|
|
||||||
Unk10 = 0x200,
|
|
||||||
Unk11 = 0x400
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum FileFlags : uint
|
|
||||||
{
|
|
||||||
Meshes = 0x1,
|
|
||||||
Textures = 0x2,
|
|
||||||
Menus = 0x4,
|
|
||||||
Sounds = 0x8,
|
|
||||||
Voices = 0x10,
|
|
||||||
Shaders = 0x20,
|
|
||||||
Trees = 0x40,
|
|
||||||
Fonts = 0x80,
|
|
||||||
Miscellaneous = 0x100
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BSAReader : IBSAReader
|
|
||||||
{
|
|
||||||
internal uint _fileCount;
|
|
||||||
internal AbsolutePath _fileName;
|
|
||||||
internal uint _folderCount;
|
|
||||||
internal uint _folderRecordOffset;
|
|
||||||
private List<FolderRecord> _folders;
|
|
||||||
internal string _magic;
|
|
||||||
internal uint _totalFileNameLength;
|
|
||||||
internal uint _totalFolderNameLength;
|
|
||||||
|
|
||||||
public void Dump(Action<string> print)
|
|
||||||
{
|
|
||||||
print($"File Name: {_fileName}");
|
|
||||||
print($"File Count: {_fileCount}");
|
|
||||||
print($"Magic: {_magic}");
|
|
||||||
|
|
||||||
foreach (var file in Files)
|
|
||||||
{
|
|
||||||
print("\n");
|
|
||||||
file.Dump(print);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async ValueTask<BSAReader> LoadAsync(AbsolutePath filename)
|
|
||||||
{
|
|
||||||
using var stream = await filename.OpenRead();
|
|
||||||
using var br = new BinaryReader(stream);
|
|
||||||
var bsa = new BSAReader { _fileName = filename };
|
|
||||||
bsa.LoadHeaders(br);
|
|
||||||
return bsa;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static BSAReader Load(AbsolutePath filename)
|
|
||||||
{
|
|
||||||
using var stream = File.Open(filename.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
||||||
using var br = new BinaryReader(stream);
|
|
||||||
var bsa = new BSAReader { _fileName = filename };
|
|
||||||
bsa.LoadHeaders(br);
|
|
||||||
return bsa;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IFile> Files
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
foreach (var folder in _folders)
|
|
||||||
foreach (var file in folder._files)
|
|
||||||
yield return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArchiveStateObject State => new BSAStateObject(this);
|
|
||||||
|
|
||||||
public VersionType HeaderType { get; set; }
|
|
||||||
|
|
||||||
public ArchiveFlags ArchiveFlags { get; set; }
|
|
||||||
|
|
||||||
public FileFlags FileFlags { get; set; }
|
|
||||||
|
|
||||||
public bool HasFolderNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFolderNames);
|
|
||||||
|
|
||||||
public bool HasFileNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNames);
|
|
||||||
|
|
||||||
public bool CompressedByDefault => ArchiveFlags.HasFlag(ArchiveFlags.Compressed);
|
|
||||||
|
|
||||||
public bool Bit9Set => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNameBlobs);
|
|
||||||
|
|
||||||
public bool HasNameBlobs
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (HeaderType == VersionType.FO3 || HeaderType == VersionType.SSE) return Bit9Set;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadHeaders(BinaryReader rdr)
|
|
||||||
{
|
|
||||||
var fourcc = Encoding.ASCII.GetString(rdr.ReadBytes(4));
|
|
||||||
|
|
||||||
if (fourcc != "BSA\0")
|
|
||||||
throw new InvalidDataException("Archive is not a BSA");
|
|
||||||
|
|
||||||
_magic = fourcc;
|
|
||||||
HeaderType = (VersionType)rdr.ReadUInt32();
|
|
||||||
_folderRecordOffset = rdr.ReadUInt32();
|
|
||||||
ArchiveFlags = (ArchiveFlags)rdr.ReadUInt32();
|
|
||||||
_folderCount = rdr.ReadUInt32();
|
|
||||||
_fileCount = rdr.ReadUInt32();
|
|
||||||
_totalFolderNameLength = rdr.ReadUInt32();
|
|
||||||
_totalFileNameLength = rdr.ReadUInt32();
|
|
||||||
FileFlags = (FileFlags)rdr.ReadUInt32();
|
|
||||||
|
|
||||||
LoadFolderRecords(rdr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadFolderRecords(BinaryReader rdr)
|
|
||||||
{
|
|
||||||
_folders = new List<FolderRecord>();
|
|
||||||
for (var idx = 0; idx < _folderCount; idx += 1)
|
|
||||||
_folders.Add(new FolderRecord(this, rdr));
|
|
||||||
|
|
||||||
foreach (var folder in _folders)
|
|
||||||
folder.LoadFileRecordBlock(this, rdr);
|
|
||||||
|
|
||||||
foreach (var folder in _folders)
|
|
||||||
foreach (var file in folder._files)
|
|
||||||
file.LoadFileRecord(this, folder, file, rdr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonName("BSAState")]
|
|
||||||
public class BSAStateObject : ArchiveStateObject
|
|
||||||
{
|
|
||||||
public BSAStateObject() { }
|
|
||||||
public BSAStateObject(BSAReader bsaReader)
|
|
||||||
{
|
|
||||||
Magic = bsaReader._magic;
|
|
||||||
Version = (uint)bsaReader.HeaderType;
|
|
||||||
ArchiveFlags = (uint)bsaReader.ArchiveFlags;
|
|
||||||
FileFlags = (uint)bsaReader.FileFlags;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<IBSABuilder> MakeBuilder(long size)
|
|
||||||
{
|
|
||||||
return await BSABuilder.Create(this, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Magic { get; set; }
|
|
||||||
public uint Version { get; set; }
|
|
||||||
public uint ArchiveFlags { get; set; }
|
|
||||||
public uint FileFlags { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FolderRecord
|
|
||||||
{
|
|
||||||
private readonly uint _fileCount;
|
|
||||||
internal List<FileRecord> _files;
|
|
||||||
private ulong _offset;
|
|
||||||
private uint _unk;
|
|
||||||
|
|
||||||
internal FolderRecord(BSAReader bsa, BinaryReader src)
|
|
||||||
{
|
|
||||||
Hash = src.ReadUInt64();
|
|
||||||
_fileCount = src.ReadUInt32();
|
|
||||||
if (bsa.HeaderType == VersionType.SSE)
|
|
||||||
{
|
|
||||||
_unk = src.ReadUInt32();
|
|
||||||
_offset = src.ReadUInt64();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_offset = src.ReadUInt32();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; private set; }
|
|
||||||
|
|
||||||
public ulong Hash { get; }
|
|
||||||
|
|
||||||
internal void LoadFileRecordBlock(BSAReader bsa, BinaryReader src)
|
|
||||||
{
|
|
||||||
if (bsa.HasFolderNames) Name = src.ReadStringLen(bsa.HeaderType);
|
|
||||||
|
|
||||||
_files = new List<FileRecord>();
|
|
||||||
for (var idx = 0; idx < _fileCount; idx += 1)
|
|
||||||
_files.Add(new FileRecord(bsa, this, src, idx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileRecord : IFile
|
|
||||||
{
|
|
||||||
private readonly BSAReader _bsa;
|
|
||||||
private readonly long _dataOffset;
|
|
||||||
private readonly uint _dataSize;
|
|
||||||
private string _name;
|
|
||||||
private readonly string _nameBlob;
|
|
||||||
private readonly uint _offset;
|
|
||||||
private readonly uint _onDiskSize;
|
|
||||||
private readonly uint _originalSize;
|
|
||||||
private readonly uint _size;
|
|
||||||
internal readonly int _index;
|
|
||||||
|
|
||||||
|
|
||||||
public void Dump(Action<string> print)
|
|
||||||
{
|
|
||||||
print($"Name: {_name}");
|
|
||||||
print($"Offset: {_offset}");
|
|
||||||
print($"On Disk Size: {_onDiskSize}");
|
|
||||||
print($"Original Size: {_originalSize}");
|
|
||||||
print($"Size: {_size}");
|
|
||||||
print($"Index: {_index}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public FileRecord(BSAReader bsa, FolderRecord folderRecord, BinaryReader src, int index)
|
|
||||||
{
|
|
||||||
_index = index;
|
|
||||||
_bsa = bsa;
|
|
||||||
Hash = src.ReadUInt64();
|
|
||||||
var size = src.ReadUInt32();
|
|
||||||
FlipCompression = (size & (0x1 << 30)) > 0;
|
|
||||||
|
|
||||||
if (FlipCompression)
|
|
||||||
_size = size ^ (0x1 << 30);
|
|
||||||
else
|
|
||||||
_size = size;
|
|
||||||
|
|
||||||
if (Compressed)
|
|
||||||
_size -= 4;
|
|
||||||
|
|
||||||
_offset = src.ReadUInt32();
|
|
||||||
Folder = folderRecord;
|
|
||||||
|
|
||||||
var old_pos = src.BaseStream.Position;
|
|
||||||
|
|
||||||
src.BaseStream.Position = _offset;
|
|
||||||
|
|
||||||
if (bsa.HasNameBlobs)
|
|
||||||
_nameBlob = src.ReadStringLenNoTerm(bsa.HeaderType);
|
|
||||||
|
|
||||||
|
|
||||||
if (Compressed)
|
|
||||||
_originalSize = src.ReadUInt32();
|
|
||||||
|
|
||||||
_onDiskSize = (uint) (_size - (_nameBlob == null ? 0 : _nameBlob.Length + 1));
|
|
||||||
|
|
||||||
if (Compressed)
|
|
||||||
{
|
|
||||||
_dataSize = _originalSize;
|
|
||||||
_onDiskSize -= 4;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_dataSize = _onDiskSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
_dataOffset = src.BaseStream.Position;
|
|
||||||
|
|
||||||
src.BaseStream.Position = old_pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RelativePath Path
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return string.IsNullOrEmpty(Folder.Name) ? new RelativePath(_name) : new RelativePath(Folder.Name + "\\" + _name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Compressed
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (FlipCompression) return !_bsa.CompressedByDefault;
|
|
||||||
return _bsa.CompressedByDefault;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint Size => _dataSize;
|
|
||||||
public FileStateObject State => new BSAFileStateObject(this);
|
|
||||||
|
|
||||||
public ulong Hash { get; }
|
|
||||||
|
|
||||||
public FolderRecord Folder { get; }
|
|
||||||
|
|
||||||
public bool FlipCompression { get; }
|
|
||||||
|
|
||||||
internal void LoadFileRecord(BSAReader bsaReader, FolderRecord folder, FileRecord file, BinaryReader rdr)
|
|
||||||
{
|
|
||||||
_name = rdr.ReadStringTerm(_bsa.HeaderType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask CopyDataTo(Stream output)
|
|
||||||
{
|
|
||||||
await using var in_file = await _bsa._fileName.OpenRead();
|
|
||||||
using var rdr = new BinaryReader(in_file);
|
|
||||||
rdr.BaseStream.Position = _dataOffset;
|
|
||||||
|
|
||||||
if (_bsa.HeaderType == VersionType.SSE)
|
|
||||||
{
|
|
||||||
if (Compressed)
|
|
||||||
{
|
|
||||||
using var r = LZ4Stream.Decode(rdr.BaseStream);
|
|
||||||
await r.CopyToLimitAsync(output, (int) _originalSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await rdr.BaseStream.CopyToLimitAsync(output, (int) _onDiskSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Compressed)
|
|
||||||
{
|
|
||||||
await using var z = new InflaterInputStream(rdr.BaseStream);
|
|
||||||
await z.CopyToLimitAsync(output, (int) _originalSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
await rdr.BaseStream.CopyToLimitAsync(output, (int) _onDiskSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonName("BSAFileState")]
|
|
||||||
public class BSAFileStateObject : FileStateObject
|
|
||||||
{
|
|
||||||
public BSAFileStateObject() { }
|
|
||||||
public BSAFileStateObject(FileRecord fileRecord)
|
|
||||||
{
|
|
||||||
FlipCompression = fileRecord.FlipCompression;
|
|
||||||
Path = fileRecord.Path;
|
|
||||||
Index = fileRecord._index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FlipCompression { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user