2019-10-07 22:13:38 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
using System.IO.MemoryMappedFiles;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
using Wabbajack.Common;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
|
|
|
|
|
namespace Compression.BSA
|
|
|
|
|
{
|
|
|
|
|
interface IFileBuilder
|
|
|
|
|
{
|
|
|
|
|
uint FileHash { get; }
|
|
|
|
|
uint DirHash { get; }
|
|
|
|
|
string FullName { get; }
|
|
|
|
|
|
|
|
|
|
int Index { get; }
|
|
|
|
|
|
2019-11-16 00:01:37 +00:00
|
|
|
|
void WriteData(BinaryWriter wtr);
|
2019-10-07 22:13:38 +00:00
|
|
|
|
void WriteHeader(BinaryWriter wtr);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
public class BA2Builder : IBSABuilder
|
|
|
|
|
{
|
|
|
|
|
private BA2StateObject _state;
|
|
|
|
|
private List<IFileBuilder> _entries = new List<IFileBuilder>();
|
2020-03-05 00:02:16 +00:00
|
|
|
|
private DiskSlabAllocator _slab;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
|
|
|
|
|
public BA2Builder(BA2StateObject state)
|
|
|
|
|
{
|
|
|
|
|
_state = state;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
_slab = new DiskSlabAllocator();
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2020-03-05 00:02:16 +00:00
|
|
|
|
_slab.Dispose();
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 00:01:37 +00:00
|
|
|
|
public void AddFile(FileStateObject state, Stream src)
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
|
|
|
|
switch (_state.Type)
|
|
|
|
|
{
|
|
|
|
|
case EntryType.GNRL:
|
2020-03-05 00:02:16 +00:00
|
|
|
|
var result = BA2FileEntryBuilder.Create((BA2FileEntryState)state, src, _slab);
|
2019-10-07 22:13:38 +00:00
|
|
|
|
lock(_entries) _entries.Add(result);
|
|
|
|
|
break;
|
2019-10-08 04:02:03 +00:00
|
|
|
|
case EntryType.DX10:
|
2020-03-05 00:02:16 +00:00
|
|
|
|
var resultdx10 = BA2DX10FileEntryBuilder.Create((BA2DX10EntryState)state, src, _slab);
|
2019-10-08 04:02:03 +00:00
|
|
|
|
lock(_entries) _entries.Add(resultdx10);
|
|
|
|
|
break;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 00:01:37 +00:00
|
|
|
|
public void Build(string filename)
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
|
|
|
|
SortEntries();
|
2020-01-18 20:52:09 +00:00
|
|
|
|
using (var fs = File.Open(filename, FileMode.Create))
|
2019-10-07 22:13:38 +00:00
|
|
|
|
using (var bw = new BinaryWriter(fs))
|
|
|
|
|
{
|
|
|
|
|
bw.Write(Encoding.ASCII.GetBytes(_state.HeaderMagic));
|
|
|
|
|
bw.Write(_state.Version);
|
|
|
|
|
bw.Write(Encoding.ASCII.GetBytes(Enum.GetName(typeof(EntryType), _state.Type)));
|
|
|
|
|
bw.Write((uint)_entries.Count);
|
|
|
|
|
var table_offset_loc = bw.BaseStream.Position;
|
|
|
|
|
bw.Write((ulong)0);
|
|
|
|
|
|
|
|
|
|
foreach (var entry in _entries)
|
|
|
|
|
{
|
|
|
|
|
entry.WriteHeader(bw);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var entry in _entries)
|
|
|
|
|
{
|
2019-11-16 00:01:37 +00:00
|
|
|
|
entry.WriteData(bw);
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_state.HasNameTable)
|
|
|
|
|
{
|
|
|
|
|
var pos = bw.BaseStream.Position;
|
|
|
|
|
bw.BaseStream.Seek(table_offset_loc, SeekOrigin.Begin);
|
|
|
|
|
bw.Write((ulong) pos);
|
|
|
|
|
bw.BaseStream.Seek(pos, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
foreach (var entry in _entries)
|
|
|
|
|
{
|
|
|
|
|
var bytes = Encoding.UTF7.GetBytes(entry.FullName);
|
|
|
|
|
bw.Write((ushort)bytes.Length);
|
2019-11-16 00:01:37 +00:00
|
|
|
|
bw.BaseStream.Write(bytes, 0, bytes.Length);
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SortEntries()
|
|
|
|
|
{
|
|
|
|
|
_entries = _entries.OrderBy(e => e.Index).ToList();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-08 04:02:03 +00:00
|
|
|
|
public class BA2DX10FileEntryBuilder : IFileBuilder
|
|
|
|
|
{
|
|
|
|
|
private BA2DX10EntryState _state;
|
|
|
|
|
private List<ChunkBuilder> _chunks;
|
|
|
|
|
|
2020-03-05 00:02:16 +00:00
|
|
|
|
public static BA2DX10FileEntryBuilder Create(BA2DX10EntryState state, Stream src, DiskSlabAllocator slab)
|
2019-10-08 04:02:03 +00:00
|
|
|
|
{
|
2019-11-16 00:01:37 +00:00
|
|
|
|
var builder = new BA2DX10FileEntryBuilder {_state = state};
|
2019-10-10 05:04:28 +00:00
|
|
|
|
|
|
|
|
|
var header_size = DDS.HeaderSizeForFormat((DXGI_FORMAT) state.PixelFormat) + 4;
|
|
|
|
|
new BinaryReader(src).ReadBytes((int)header_size);
|
|
|
|
|
|
2019-11-12 04:35:07 +00:00
|
|
|
|
// This can't be parallel because it all runs off the same base IO stream.
|
|
|
|
|
builder._chunks = new List<ChunkBuilder>();
|
|
|
|
|
|
|
|
|
|
foreach (var chunk in state.Chunks)
|
2020-03-05 00:02:16 +00:00
|
|
|
|
builder._chunks.Add(ChunkBuilder.Create(state, chunk, src, slab));
|
2019-11-12 04:35:07 +00:00
|
|
|
|
|
|
|
|
|
return builder;
|
2019-10-08 04:02:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public uint FileHash => _state.NameHash;
|
|
|
|
|
public uint DirHash => _state.DirHash;
|
2019-10-10 05:04:28 +00:00
|
|
|
|
public string FullName => _state.Path;
|
2019-10-08 04:02:03 +00:00
|
|
|
|
public int Index => _state.Index;
|
|
|
|
|
|
|
|
|
|
public void WriteHeader(BinaryWriter bw)
|
|
|
|
|
{
|
|
|
|
|
bw.Write(_state.NameHash);
|
|
|
|
|
bw.Write(Encoding.ASCII.GetBytes(_state.Extension));
|
|
|
|
|
bw.Write(_state.DirHash);
|
|
|
|
|
bw.Write(_state.Unk8);
|
|
|
|
|
bw.Write((byte)_chunks.Count);
|
|
|
|
|
bw.Write(_state.ChunkHdrLen);
|
|
|
|
|
bw.Write(_state.Height);
|
|
|
|
|
bw.Write(_state.Width);
|
|
|
|
|
bw.Write(_state.NumMips);
|
|
|
|
|
bw.Write(_state.PixelFormat);
|
|
|
|
|
bw.Write(_state.Unk16);
|
|
|
|
|
|
|
|
|
|
foreach (var chunk in _chunks)
|
|
|
|
|
chunk.WriteHeader(bw);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 00:01:37 +00:00
|
|
|
|
public void WriteData(BinaryWriter wtr)
|
2019-10-08 04:02:03 +00:00
|
|
|
|
{
|
|
|
|
|
foreach (var chunk in _chunks)
|
2019-11-16 00:01:37 +00:00
|
|
|
|
chunk.WriteData(wtr);
|
2019-10-08 04:02:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class ChunkBuilder
|
|
|
|
|
{
|
|
|
|
|
private ChunkState _chunk;
|
|
|
|
|
private uint _packSize;
|
|
|
|
|
private long _offsetOffset;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
private Stream _dataSlab;
|
2019-10-08 04:02:03 +00:00
|
|
|
|
|
2020-03-05 00:02:16 +00:00
|
|
|
|
public static ChunkBuilder Create(BA2DX10EntryState state, ChunkState chunk, Stream src, DiskSlabAllocator slab)
|
2019-10-08 04:02:03 +00:00
|
|
|
|
{
|
2019-11-12 04:35:07 +00:00
|
|
|
|
var builder = new ChunkBuilder {_chunk = chunk};
|
2019-10-10 05:04:28 +00:00
|
|
|
|
|
2020-03-05 00:02:16 +00:00
|
|
|
|
if (!chunk.Compressed)
|
|
|
|
|
{
|
|
|
|
|
builder._dataSlab = slab.Allocate(chunk.FullSz);
|
|
|
|
|
src.CopyToLimit(builder._dataSlab, (int)chunk.FullSz);
|
2019-10-10 05:04:28 +00:00
|
|
|
|
}
|
2020-03-05 00:02:16 +00:00
|
|
|
|
else
|
2019-10-08 04:02:03 +00:00
|
|
|
|
{
|
2020-03-05 00:02:16 +00:00
|
|
|
|
using var ms = new MemoryStream();
|
2019-11-12 04:35:07 +00:00
|
|
|
|
using (var ds = new DeflaterOutputStream(ms))
|
2019-10-08 04:02:03 +00:00
|
|
|
|
{
|
2020-03-05 05:27:15 +00:00
|
|
|
|
ds.IsStreamOwner = false;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
src.CopyToLimit(ds, (int)chunk.FullSz);
|
2019-10-08 04:02:03 +00:00
|
|
|
|
}
|
2019-11-12 04:35:07 +00:00
|
|
|
|
|
2020-03-05 00:02:16 +00:00
|
|
|
|
builder._dataSlab = slab.Allocate(ms.Length);
|
|
|
|
|
ms.Position = 0;
|
|
|
|
|
ms.CopyTo(builder._dataSlab);
|
|
|
|
|
builder._packSize = (uint)ms.Length;
|
2019-10-08 04:02:03 +00:00
|
|
|
|
}
|
2020-03-05 00:02:16 +00:00
|
|
|
|
builder._dataSlab.Position = 0;
|
2019-11-12 04:35:07 +00:00
|
|
|
|
|
|
|
|
|
return builder;
|
2019-10-08 04:02:03 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteHeader(BinaryWriter bw)
|
|
|
|
|
{
|
|
|
|
|
_offsetOffset = bw.BaseStream.Position;
|
|
|
|
|
bw.Write((ulong)0);
|
|
|
|
|
bw.Write(_packSize);
|
|
|
|
|
bw.Write(_chunk.FullSz);
|
|
|
|
|
bw.Write(_chunk.StartMip);
|
|
|
|
|
bw.Write(_chunk.EndMip);
|
|
|
|
|
bw.Write(_chunk.Align);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 00:01:37 +00:00
|
|
|
|
public void WriteData(BinaryWriter bw)
|
2019-10-08 04:02:03 +00:00
|
|
|
|
{
|
|
|
|
|
var pos = bw.BaseStream.Position;
|
|
|
|
|
bw.BaseStream.Position = _offsetOffset;
|
|
|
|
|
bw.Write((ulong)pos);
|
|
|
|
|
bw.BaseStream.Position = pos;
|
2020-03-05 04:32:38 +00:00
|
|
|
|
_dataSlab.CopyToLimit(bw.BaseStream, (int)_dataSlab.Length);
|
2020-03-05 05:27:15 +00:00
|
|
|
|
_dataSlab.Dispose();
|
2019-10-08 04:02:03 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-07 22:13:38 +00:00
|
|
|
|
public class BA2FileEntryBuilder : IFileBuilder
|
|
|
|
|
{
|
|
|
|
|
private int _rawSize;
|
|
|
|
|
private int _size;
|
|
|
|
|
private BA2FileEntryState _state;
|
|
|
|
|
private long _offsetOffset;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
private Stream _dataSrc;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
|
2020-03-05 00:02:16 +00:00
|
|
|
|
public static BA2FileEntryBuilder Create(BA2FileEntryState state, Stream src, DiskSlabAllocator slab)
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
2020-03-05 00:02:16 +00:00
|
|
|
|
var builder = new BA2FileEntryBuilder
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
2020-03-05 00:02:16 +00:00
|
|
|
|
_state = state,
|
|
|
|
|
_rawSize = (int)src.Length,
|
|
|
|
|
_dataSrc = src
|
|
|
|
|
};
|
|
|
|
|
if (!state.Compressed)
|
|
|
|
|
return builder;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
|
2020-03-05 00:02:16 +00:00
|
|
|
|
using (var ms = new MemoryStream())
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
2020-03-05 00:02:16 +00:00
|
|
|
|
using (var ds = new DeflaterOutputStream(ms))
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
2020-03-05 05:27:15 +00:00
|
|
|
|
ds.IsStreamOwner = false;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
builder._dataSrc.CopyTo(ds);
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
2020-03-05 00:02:16 +00:00
|
|
|
|
|
|
|
|
|
builder._dataSrc = slab.Allocate(ms.Length);
|
|
|
|
|
ms.Position = 0;
|
|
|
|
|
ms.CopyTo(builder._dataSrc);
|
|
|
|
|
builder._dataSrc.Position = 0;
|
|
|
|
|
builder._size = (int)ms.Length;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
2019-11-12 04:35:07 +00:00
|
|
|
|
return builder;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public uint FileHash => _state.NameHash;
|
|
|
|
|
public uint DirHash => _state.DirHash;
|
2019-10-10 05:04:28 +00:00
|
|
|
|
public string FullName => _state.Path;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
public int Index => _state.Index;
|
|
|
|
|
|
|
|
|
|
public void WriteHeader(BinaryWriter wtr)
|
|
|
|
|
{
|
|
|
|
|
wtr.Write(_state.NameHash);
|
|
|
|
|
wtr.Write(Encoding.ASCII.GetBytes(_state.Extension));
|
|
|
|
|
wtr.Write(_state.DirHash);
|
|
|
|
|
wtr.Write(_state.Flags);
|
|
|
|
|
_offsetOffset = wtr.BaseStream.Position;
|
|
|
|
|
wtr.Write((ulong)0);
|
|
|
|
|
wtr.Write(_size);
|
|
|
|
|
wtr.Write(_rawSize);
|
|
|
|
|
wtr.Write(_state.Align);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-16 00:01:37 +00:00
|
|
|
|
public void WriteData(BinaryWriter wtr)
|
2019-10-07 22:13:38 +00:00
|
|
|
|
{
|
|
|
|
|
var pos = wtr.BaseStream.Position;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
wtr.BaseStream.Position = _offsetOffset;
|
2019-10-07 22:13:38 +00:00
|
|
|
|
wtr.Write((ulong)pos);
|
|
|
|
|
wtr.BaseStream.Position = pos;
|
2020-03-05 00:02:16 +00:00
|
|
|
|
_dataSrc.Position = 0;
|
2020-03-05 04:32:38 +00:00
|
|
|
|
_dataSrc.CopyToLimit(wtr.BaseStream, (int)_dataSrc.Length);
|
2020-03-05 05:27:15 +00:00
|
|
|
|
_dataSrc.Dispose();
|
2019-10-07 22:13:38 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|