wabbajack/Wabbajack.Compression.BSA/BA2Archive/Builder.cs
trawzified a545cb375a
Add Starfield support (#2589)
* Add support for new Starfield BA2 versions

* Add Starfield BA2 LZ4 texture compression support (WIP, not finished)

* Work on replacing DDS.cs with DirectXTexUtility

* Fix reading Starfield BA2s with new DirectXTexUtil

* Fix builder not exporting new Starfield header options

* Fix writing LZ4 chunks in frame format instead of block format

* Rename FO4Archive to BA2Archive, merge SFArchive and FO4Archive

* Clean up testing code & CLI launch settings

* Update changelog
2024-06-17 12:10:53 -06:00

104 lines
3.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Wabbajack.Compression.BSA.Interfaces;
using Wabbajack.DTOs.BSA.ArchiveStates;
using Wabbajack.DTOs.BSA.FileStates;
using Wabbajack.Paths.IO;
namespace Wabbajack.Compression.BSA.BA2Archive;
public class Builder : IBuilder
{
private List<IFileBuilder> _entries = new();
private DiskSlabAllocator _slab;
private BA2State _state;
public async ValueTask AddFile(AFile state, Stream src, CancellationToken token)
{
try
{
switch (_state.Type)
{
case BA2EntryType.GNRL:
var result = await FileEntryBuilder.Create((BA2File)state, src, _slab, token);
lock (_entries)
{
_entries.Add(result);
}
break;
case BA2EntryType.DX10:
var resultdx10 = await DX10FileEntryBuilder.Create((BA2DX10File)state, src, _slab, _state.Compression == 3, token);
lock (_entries)
{
_entries.Add(resultdx10);
}
break;
}
}
catch (Exception ex)
{
throw new InvalidDataException($"Error adding file {state.Path} to archive: {ex.Message}", ex);
}
}
public async ValueTask Build(Stream fs, CancellationToken token)
{
SortEntries();
await using var bw = new BinaryWriter(fs, Encoding.Default, true);
bw.Write(Encoding.ASCII.GetBytes(_state.HeaderMagic));
bw.Write(_state.Version);
bw.Write(Encoding.ASCII.GetBytes(Enum.GetName(_state.Type)!));
bw.Write((uint) _entries.Count);
var tableOffsetLoc = bw.BaseStream.Position;
bw.Write((ulong) 0);
if(_state.Version == 2 || _state.Version == 3)
{
bw.Write(_state.Unknown1);
bw.Write(_state.Unknown2);
if (_state.Version == 3)
bw.Write(_state.Compression);
}
foreach (var entry in _entries) entry.WriteHeader(bw, token);
foreach (var entry in _entries) await entry.WriteData(bw, token);
if (!_state.HasNameTable) return;
var pos = bw.BaseStream.Position;
bw.BaseStream.Seek(tableOffsetLoc, SeekOrigin.Begin);
bw.Write((ulong) pos);
bw.BaseStream.Seek(pos, SeekOrigin.Begin);
foreach (var entry in _entries)
{
var bytes = Encoding.UTF8.GetBytes(entry.FullName);
bw.Write((ushort) bytes.Length);
await bw.BaseStream.WriteAsync(bytes, token);
}
}
public static Builder Create(BA2State state, TemporaryFileManager manager)
{
var self = new Builder {_state = state, _slab = new DiskSlabAllocator(manager)};
return self;
}
public async ValueTask DisposeAsync()
{
await _slab.DisposeAsync();
}
private void SortEntries()
{
_entries = _entries.OrderBy(e => e.Index).ToList();
}
}