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