mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
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
This commit is contained in:
parent
59b2f1a7a1
commit
a545cb375a
@ -1,6 +1,8 @@
|
|||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
#### Version - 3.6.2.0 - TBD
|
#### Version - 3.7.0.0 - TBD
|
||||||
|
* Added Starfield support
|
||||||
|
* Note: Hashes were added earlier, but the earlier version was not fully compatible due to Wabbajack extracting the BA2 archives incorrectly. This has been fixed.
|
||||||
* Updated GameFinder dependency
|
* Updated GameFinder dependency
|
||||||
* Updated WebView dependency
|
* Updated WebView dependency
|
||||||
* Updated other dependencies
|
* Updated other dependencies
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>bin\Release\Publish</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<_TargetId>Folder</_TargetId>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
|
<SelfContained>true</SelfContained>
|
||||||
|
<PublishSingleFile>false</PublishSingleFile>
|
||||||
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
7
Wabbajack.CLI/Properties/launchSettings.json
Normal file
7
Wabbajack.CLI/Properties/launchSettings.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Wabbajack.CLI": {
|
||||||
|
"commandName": "Project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,7 @@ public class CompressionTests
|
|||||||
{
|
{
|
||||||
if (name == "tes4.bsa") return; // not sure why is is failing
|
if (name == "tes4.bsa") return; // not sure why is is failing
|
||||||
|
|
||||||
|
|
||||||
var reader = await BSADispatch.Open(path);
|
var reader = await BSADispatch.Open(path);
|
||||||
|
|
||||||
var dataStates = await reader.Files
|
var dataStates = await reader.Files
|
||||||
@ -78,7 +79,6 @@ public class CompressionTests
|
|||||||
|
|
||||||
var rebuiltStream = new MemoryStream();
|
var rebuiltStream = new MemoryStream();
|
||||||
await build.Build(rebuiltStream, CancellationToken.None);
|
await build.Build(rebuiltStream, CancellationToken.None);
|
||||||
rebuiltStream.Position = 0;
|
|
||||||
|
|
||||||
var reader2 = await BSADispatch.Open(new MemoryStreamFactory(rebuiltStream, path, path.LastModifiedUtc()));
|
var reader2 = await BSADispatch.Open(new MemoryStreamFactory(rebuiltStream, path, path.LastModifiedUtc()));
|
||||||
await reader.Files.Zip(reader2.Files)
|
await reader.Files.Zip(reader2.Files)
|
||||||
|
@ -10,7 +10,7 @@ using Wabbajack.DTOs.BSA.ArchiveStates;
|
|||||||
using Wabbajack.DTOs.BSA.FileStates;
|
using Wabbajack.DTOs.BSA.FileStates;
|
||||||
using Wabbajack.Paths.IO;
|
using Wabbajack.Paths.IO;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
public class Builder : IBuilder
|
public class Builder : IBuilder
|
||||||
{
|
{
|
||||||
@ -33,7 +33,7 @@ public class Builder : IBuilder
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case BA2EntryType.DX10:
|
case BA2EntryType.DX10:
|
||||||
var resultdx10 = await DX10FileEntryBuilder.Create((BA2DX10File)state, src, _slab, token);
|
var resultdx10 = await DX10FileEntryBuilder.Create((BA2DX10File)state, src, _slab, _state.Compression == 3, token);
|
||||||
lock (_entries)
|
lock (_entries)
|
||||||
{
|
{
|
||||||
_entries.Add(resultdx10);
|
_entries.Add(resultdx10);
|
||||||
@ -59,6 +59,13 @@ public class Builder : IBuilder
|
|||||||
bw.Write((uint) _entries.Count);
|
bw.Write((uint) _entries.Count);
|
||||||
var tableOffsetLoc = bw.BaseStream.Position;
|
var tableOffsetLoc = bw.BaseStream.Position;
|
||||||
bw.Write((ulong) 0);
|
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) entry.WriteHeader(bw, token);
|
||||||
|
|
93
Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs
Normal file
93
Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
||||||
|
using K4os.Compression.LZ4;
|
||||||
|
using K4os.Compression.LZ4.Encoders;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.DTOs.BSA.FileStates;
|
||||||
|
using Wabbajack.DTOs.GitHub;
|
||||||
|
|
||||||
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
|
public class ChunkBuilder
|
||||||
|
{
|
||||||
|
private BA2Chunk _chunk;
|
||||||
|
private Stream _dataSlab;
|
||||||
|
private long _offsetOffset;
|
||||||
|
private uint _packSize;
|
||||||
|
|
||||||
|
public static async Task<ChunkBuilder> Create(BA2DX10File state, BA2Chunk chunk, Stream src,
|
||||||
|
DiskSlabAllocator slab, bool useLz4Compression, CancellationToken token)
|
||||||
|
{
|
||||||
|
var builder = new ChunkBuilder {_chunk = chunk};
|
||||||
|
|
||||||
|
if (!chunk.Compressed)
|
||||||
|
{
|
||||||
|
builder._dataSlab = slab.Allocate(chunk.FullSz);
|
||||||
|
await src.CopyToLimitAsync(builder._dataSlab, (int) chunk.FullSz, token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!useLz4Compression)
|
||||||
|
{
|
||||||
|
var deflater = new Deflater(Deflater.BEST_COMPRESSION);
|
||||||
|
await using var ms = new MemoryStream();
|
||||||
|
await using (var ds = new DeflaterOutputStream(ms, deflater))
|
||||||
|
{
|
||||||
|
ds.IsStreamOwner = false;
|
||||||
|
await src.CopyToLimitAsync(ds, (int)chunk.FullSz, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder._dataSlab = slab.Allocate(ms.Length);
|
||||||
|
ms.Position = 0;
|
||||||
|
await ms.CopyToLimitAsync(builder._dataSlab, (int)ms.Length, token);
|
||||||
|
builder._packSize = (uint)ms.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
byte[] full = new byte[chunk.FullSz];
|
||||||
|
await using (var copyStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
await src.CopyToLimitAsync(copyStream, (int)chunk.FullSz, token);
|
||||||
|
full = copyStream.ToArray();
|
||||||
|
}
|
||||||
|
var maxOutput = LZ4Codec.MaximumOutputSize((int)chunk.FullSz);
|
||||||
|
byte[] compressed = new byte[maxOutput];
|
||||||
|
int compressedSize = LZ4Codec.Encode(full, 0, full.Length, compressed, 0, compressed.Length, LZ4Level.L12_MAX);
|
||||||
|
var ms = new MemoryStream(compressed, 0, compressedSize);
|
||||||
|
builder._dataSlab = slab.Allocate(compressedSize);
|
||||||
|
ms.Position = 0;
|
||||||
|
await ms.CopyToLimitAsync(builder._dataSlab, compressedSize, token);
|
||||||
|
builder._packSize = (uint)compressedSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder._dataSlab.Position = 0;
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask WriteData(BinaryWriter bw, CancellationToken token)
|
||||||
|
{
|
||||||
|
var pos = bw.BaseStream.Position;
|
||||||
|
bw.BaseStream.Position = _offsetOffset;
|
||||||
|
bw.Write((ulong) pos);
|
||||||
|
bw.BaseStream.Position = pos;
|
||||||
|
await _dataSlab.CopyToLimitAsync(bw.BaseStream, (int) _dataSlab.Length, token);
|
||||||
|
await _dataSlab.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
179
Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs
Normal file
179
Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DirectXTex;
|
||||||
|
using ICSharpCode.SharpZipLib.Zip.Compression;
|
||||||
|
using K4os.Compression.LZ4;
|
||||||
|
using K4os.Compression.LZ4.Streams;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
using Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
using Wabbajack.DTOs.BSA.FileStates;
|
||||||
|
using Wabbajack.DTOs.Streams;
|
||||||
|
using Wabbajack.Paths;
|
||||||
|
|
||||||
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
|
public class DX10Entry : IBA2FileEntry
|
||||||
|
{
|
||||||
|
private readonly Reader _bsa;
|
||||||
|
private ushort _chunkHdrLen;
|
||||||
|
private List<TextureChunk> _chunks;
|
||||||
|
private uint _dirHash;
|
||||||
|
private string _extension;
|
||||||
|
private byte _format;
|
||||||
|
private ushort _height;
|
||||||
|
private int _index;
|
||||||
|
private uint _nameHash;
|
||||||
|
private byte _numChunks;
|
||||||
|
private byte _numMips;
|
||||||
|
private ushort _unk16;
|
||||||
|
private byte _unk8;
|
||||||
|
private ushort _width;
|
||||||
|
private readonly byte _isCubemap;
|
||||||
|
private readonly byte _tileMode;
|
||||||
|
|
||||||
|
public DX10Entry(Reader ba2Reader, int idx)
|
||||||
|
{
|
||||||
|
_bsa = ba2Reader;
|
||||||
|
var _rdr = ba2Reader._rdr;
|
||||||
|
_nameHash = _rdr.ReadUInt32();
|
||||||
|
FullPath = _nameHash.ToString("X");
|
||||||
|
_extension = Encoding.UTF8.GetString(_rdr.ReadBytes(4));
|
||||||
|
_dirHash = _rdr.ReadUInt32();
|
||||||
|
_unk8 = _rdr.ReadByte();
|
||||||
|
_numChunks = _rdr.ReadByte();
|
||||||
|
_chunkHdrLen = _rdr.ReadUInt16();
|
||||||
|
_height = _rdr.ReadUInt16();
|
||||||
|
_width = _rdr.ReadUInt16();
|
||||||
|
_numMips = _rdr.ReadByte();
|
||||||
|
_format = _rdr.ReadByte();
|
||||||
|
_isCubemap = _rdr.ReadByte();
|
||||||
|
_tileMode = _rdr.ReadByte();
|
||||||
|
_index = idx;
|
||||||
|
|
||||||
|
_chunks = Enumerable.Range(0, _numChunks)
|
||||||
|
.Select(_ => new TextureChunk(_rdr))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
private DirectXTexUtility.TexMetadata? _metadata = null;
|
||||||
|
|
||||||
|
public DirectXTexUtility.TexMetadata Metadata
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_metadata == null)
|
||||||
|
_metadata = DirectXTexUtility.GenerateMetadata(_width, _height, _numMips, (DirectXTexUtility.DXGIFormat)_format, _isCubemap == 1);
|
||||||
|
return (DirectXTexUtility.TexMetadata)_metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint _headerSize = 0;
|
||||||
|
public uint HeaderSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_headerSize > 0)
|
||||||
|
return _headerSize;
|
||||||
|
uint size = 0;
|
||||||
|
size += (uint)Marshal.SizeOf(DirectXTexUtility.DDSHeader.DDSMagic);
|
||||||
|
size += (uint)Marshal.SizeOf<DirectXTexUtility.DDSHeader>();
|
||||||
|
var pixelFormat = DirectXTexUtility.GetPixelFormat(Metadata);
|
||||||
|
var hasDx10Header = DirectXTexUtility.HasDx10Header(pixelFormat);
|
||||||
|
if (hasDx10Header)
|
||||||
|
size += (uint)Marshal.SizeOf<DirectXTexUtility.DX10Header>();
|
||||||
|
|
||||||
|
return _headerSize = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string FullPath { get; set; }
|
||||||
|
|
||||||
|
public RelativePath Path => FullPath.ToRelativePath();
|
||||||
|
public uint Size => (uint)_chunks.Sum(f => f._fullSz) + HeaderSize;
|
||||||
|
|
||||||
|
public AFile State => new BA2DX10File
|
||||||
|
{
|
||||||
|
Path = Path,
|
||||||
|
NameHash = _nameHash,
|
||||||
|
Extension = _extension,
|
||||||
|
DirHash = _dirHash,
|
||||||
|
Unk8 = _unk8,
|
||||||
|
ChunkHdrLen = _chunkHdrLen,
|
||||||
|
Height = _height,
|
||||||
|
Width = _width,
|
||||||
|
NumMips = _numMips,
|
||||||
|
PixelFormat = _format,
|
||||||
|
IsCubeMap = _isCubemap,
|
||||||
|
TileMode = _tileMode,
|
||||||
|
Index = _index,
|
||||||
|
Chunks = _chunks.Select(ch => new BA2Chunk
|
||||||
|
{
|
||||||
|
FullSz = ch._fullSz,
|
||||||
|
StartMip = ch._startMip,
|
||||||
|
EndMip = ch._endMip,
|
||||||
|
Align = ch._align,
|
||||||
|
Compressed = ch._packSz != 0
|
||||||
|
}).ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
public async ValueTask CopyDataTo(Stream output, CancellationToken token)
|
||||||
|
{
|
||||||
|
var bw = new BinaryWriter(output);
|
||||||
|
|
||||||
|
WriteHeader(bw);
|
||||||
|
|
||||||
|
await using var fs = await _bsa._streamFactory.GetStream();
|
||||||
|
using var br = new BinaryReader(fs);
|
||||||
|
foreach (var chunk in _chunks)
|
||||||
|
{
|
||||||
|
var full = new byte[chunk._fullSz];
|
||||||
|
var isCompressed = chunk._packSz != 0;
|
||||||
|
|
||||||
|
br.BaseStream.Seek((long)chunk._offset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
if (!isCompressed)
|
||||||
|
{
|
||||||
|
await br.BaseStream.ReadAsync(full, token);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var compressed = new byte[chunk._packSz];
|
||||||
|
await br.BaseStream.ReadAsync(compressed, token);
|
||||||
|
if (_bsa._compression == 3)
|
||||||
|
{
|
||||||
|
LZ4Codec.PartialDecode(compressed, full);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var inflater = new Inflater();
|
||||||
|
inflater.SetInput(compressed);
|
||||||
|
inflater.Inflate(full);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await bw.BaseStream.WriteAsync(full, token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async ValueTask<IStreamFactory> GetStreamFactory(CancellationToken token)
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
await CopyDataTo(ms, token);
|
||||||
|
ms.Position = 0;
|
||||||
|
return new MemoryStreamFactory(ms, Path, _bsa._streamFactory.LastModifiedUtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteHeader(BinaryWriter bw)
|
||||||
|
{
|
||||||
|
DirectXTexUtility.GenerateDDSHeader(Metadata, DirectXTexUtility.DDSFlags.FORCEDX10EXTMISC2, out var header, out var header10);
|
||||||
|
var headerBytes = DirectXTexUtility.EncodeDDSHeader(header, header10);
|
||||||
|
bw.Write(headerBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,20 @@
|
|||||||
|
using DirectXTex;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Compression.BSA;
|
|
||||||
using Wabbajack.DTOs.BSA.FileStates;
|
using Wabbajack.DTOs.BSA.FileStates;
|
||||||
using Wabbajack.DTOs.Texture;
|
using Wabbajack.DTOs.Texture;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
public class DX10FileEntryBuilder : IFileBuilder
|
public class DX10FileEntryBuilder : IFileBuilder
|
||||||
{
|
{
|
||||||
private List<ChunkBuilder> _chunks;
|
private List<ChunkBuilder> _chunks;
|
||||||
private BA2DX10File _state;
|
private BA2DX10File _state;
|
||||||
|
private uint _headerSize = 0;
|
||||||
|
|
||||||
public uint FileHash => _state.NameHash;
|
public uint FileHash => _state.NameHash;
|
||||||
public uint DirHash => _state.DirHash;
|
public uint DirHash => _state.DirHash;
|
||||||
@ -43,20 +45,36 @@ public class DX10FileEntryBuilder : IFileBuilder
|
|||||||
foreach (var chunk in _chunks)
|
foreach (var chunk in _chunks)
|
||||||
await chunk.WriteData(wtr, token);
|
await chunk.WriteData(wtr, token);
|
||||||
}
|
}
|
||||||
|
public uint GetHeaderSize(BA2DX10File state)
|
||||||
|
{
|
||||||
|
if (_headerSize > 0)
|
||||||
|
return _headerSize;
|
||||||
|
|
||||||
public static async Task<DX10FileEntryBuilder> Create(BA2DX10File state, Stream src, DiskSlabAllocator slab,
|
uint size = 0;
|
||||||
|
size += (uint)Marshal.SizeOf(DirectXTexUtility.DDSHeader.DDSMagic);
|
||||||
|
size += (uint)Marshal.SizeOf<DirectXTexUtility.DDSHeader>();
|
||||||
|
var metadata = DirectXTexUtility.GenerateMetadata(state.Width, state.Height, state.NumMips, (DirectXTexUtility.DXGIFormat)state.PixelFormat, state.IsCubeMap == 1);
|
||||||
|
var pixelFormat = DirectXTexUtility.GetPixelFormat(metadata);
|
||||||
|
var hasDx10Header = DirectXTexUtility.HasDx10Header(pixelFormat);
|
||||||
|
if (hasDx10Header)
|
||||||
|
size += (uint)Marshal.SizeOf<DirectXTexUtility.DX10Header>();
|
||||||
|
|
||||||
|
return _headerSize = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<DX10FileEntryBuilder> Create(BA2DX10File state, Stream src, DiskSlabAllocator slab, bool useLz4Compression,
|
||||||
CancellationToken token)
|
CancellationToken token)
|
||||||
{
|
{
|
||||||
var builder = new DX10FileEntryBuilder {_state = state};
|
var builder = new DX10FileEntryBuilder {_state = state};
|
||||||
|
|
||||||
var headerSize = DDS.HeaderSizeForFormat((DXGI_FORMAT) state.PixelFormat) + 4;
|
var headerSize = builder.GetHeaderSize(state);
|
||||||
new BinaryReader(src).ReadBytes((int) headerSize);
|
new BinaryReader(src).ReadBytes((int) headerSize);
|
||||||
|
|
||||||
// This can't be parallel because it all runs off the same base IO stream.
|
// This can't be parallel because it all runs off the same base IO stream.
|
||||||
builder._chunks = new List<ChunkBuilder>();
|
builder._chunks = new List<ChunkBuilder>();
|
||||||
|
|
||||||
foreach (var chunk in state.Chunks)
|
foreach (var chunk in state.Chunks)
|
||||||
builder._chunks.Add(await ChunkBuilder.Create(state, chunk, src, slab, token));
|
builder._chunks.Add(await ChunkBuilder.Create(state, chunk, src, slab, useLz4Compression, token));
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
@ -9,7 +9,7 @@ using Wabbajack.DTOs.BSA.FileStates;
|
|||||||
using Wabbajack.DTOs.Streams;
|
using Wabbajack.DTOs.Streams;
|
||||||
using Wabbajack.Paths;
|
using Wabbajack.Paths;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
public class FileEntry : IBA2FileEntry
|
public class FileEntry : IBA2FileEntry
|
||||||
{
|
{
|
@ -6,7 +6,7 @@ using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
|||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.DTOs.BSA.FileStates;
|
using Wabbajack.DTOs.BSA.FileStates;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
public class FileEntryBuilder : IFileBuilder
|
public class FileEntryBuilder : IFileBuilder
|
||||||
{
|
{
|
@ -1,6 +1,6 @@
|
|||||||
using Wabbajack.Compression.BSA.Interfaces;
|
using Wabbajack.Compression.BSA.Interfaces;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
internal interface IBA2FileEntry : IFile
|
internal interface IBA2FileEntry : IFile
|
||||||
{
|
{
|
@ -2,7 +2,7 @@ using System.IO;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
internal interface IFileBuilder
|
internal interface IFileBuilder
|
||||||
{
|
{
|
@ -7,7 +7,7 @@ using Wabbajack.Compression.BSA.Interfaces;
|
|||||||
using Wabbajack.DTOs.BSA.ArchiveStates;
|
using Wabbajack.DTOs.BSA.ArchiveStates;
|
||||||
using Wabbajack.DTOs.Streams;
|
using Wabbajack.DTOs.Streams;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
public class Reader : IReader
|
public class Reader : IReader
|
||||||
{
|
{
|
||||||
@ -15,9 +15,17 @@ public class Reader : IReader
|
|||||||
internal string _headerMagic;
|
internal string _headerMagic;
|
||||||
internal ulong _nameTableOffset;
|
internal ulong _nameTableOffset;
|
||||||
internal uint _numFiles;
|
internal uint _numFiles;
|
||||||
|
internal uint _unknown1;
|
||||||
|
internal uint _unknown2;
|
||||||
|
internal uint _compression;
|
||||||
internal BinaryReader _rdr;
|
internal BinaryReader _rdr;
|
||||||
public IStreamFactory _streamFactory;
|
public IStreamFactory _streamFactory;
|
||||||
internal BA2EntryType _type;
|
internal BA2EntryType _type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fallout 4 - Version 1, 7 or 8
|
||||||
|
/// Starfield - Version 2 or 3
|
||||||
|
/// </summary>
|
||||||
internal uint _version;
|
internal uint _version;
|
||||||
|
|
||||||
private Reader(Stream stream)
|
private Reader(Stream stream)
|
||||||
@ -37,7 +45,10 @@ public class Reader : IReader
|
|||||||
Version = _version,
|
Version = _version,
|
||||||
HeaderMagic = _headerMagic,
|
HeaderMagic = _headerMagic,
|
||||||
Type = _type,
|
Type = _type,
|
||||||
HasNameTable = HasNameTable
|
HasNameTable = HasNameTable,
|
||||||
|
Unknown1 = _unknown1,
|
||||||
|
Unknown2 = _unknown2,
|
||||||
|
Compression = _compression,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -67,6 +78,10 @@ public class Reader : IReader
|
|||||||
_numFiles = _rdr.ReadUInt32();
|
_numFiles = _rdr.ReadUInt32();
|
||||||
_nameTableOffset = _rdr.ReadUInt64();
|
_nameTableOffset = _rdr.ReadUInt64();
|
||||||
|
|
||||||
|
_unknown1 = (_version == 2 || _version == 3) ? _rdr.ReadUInt32() : 0;
|
||||||
|
_unknown2 = (_version == 2 || _version == 3) ? _rdr.ReadUInt32() : 0;
|
||||||
|
_compression = (_version == 3) ? _rdr.ReadUInt32() : 0;
|
||||||
|
|
||||||
var files = new List<IBA2FileEntry>();
|
var files = new List<IBA2FileEntry>();
|
||||||
for (var idx = 0; idx < _numFiles; idx += 1)
|
for (var idx = 0; idx < _numFiles; idx += 1)
|
||||||
switch (_type)
|
switch (_type)
|
||||||
@ -94,4 +109,4 @@ public class Reader : IReader
|
|||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
namespace Wabbajack.Compression.BSA.BA2Archive;
|
||||||
|
|
||||||
public class TextureChunk
|
public class TextureChunk
|
||||||
{
|
{
|
@ -22,7 +22,7 @@ public static class BSADispatch
|
|||||||
{
|
{
|
||||||
FileType.TES3 => await Reader.Load(new NativeFileStreamFactory(filename)),
|
FileType.TES3 => await Reader.Load(new NativeFileStreamFactory(filename)),
|
||||||
FileType.BSA => await TES5Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
FileType.BSA => await TES5Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
||||||
FileType.BA2 => await FO4Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
FileType.BA2 => await BA2Archive.Reader.Load(new NativeFileStreamFactory(filename)),
|
||||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ public static class BSADispatch
|
|||||||
{
|
{
|
||||||
FileType.TES3 => await Reader.Load(factory),
|
FileType.TES3 => await Reader.Load(factory),
|
||||||
FileType.BSA => await TES5Archive.Reader.Load(factory),
|
FileType.BSA => await TES5Archive.Reader.Load(factory),
|
||||||
FileType.BA2 => await FO4Archive.Reader.Load(factory),
|
FileType.BA2 => await BA2Archive.Reader.Load(factory),
|
||||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ public static class BSADispatch
|
|||||||
{
|
{
|
||||||
FileType.TES3 => await Reader.Load(factory),
|
FileType.TES3 => await Reader.Load(factory),
|
||||||
FileType.BSA => await TES5Archive.Reader.Load(factory),
|
FileType.BSA => await TES5Archive.Reader.Load(factory),
|
||||||
FileType.BA2 => await FO4Archive.Reader.Load(factory),
|
FileType.BA2 => await BA2Archive.Reader.Load(factory),
|
||||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ public static class BSADispatch
|
|||||||
{
|
{
|
||||||
TES3State tes3 => new Builder(tes3),
|
TES3State tes3 => new Builder(tes3),
|
||||||
BSAState bsa => TES5Archive.Builder.Create(bsa, manager),
|
BSAState bsa => TES5Archive.Builder.Create(bsa, manager),
|
||||||
BA2State ba2 => FO4Archive.Builder.Create(ba2, manager),
|
BA2State ba2 => BA2Archive.Builder.Create(ba2, manager),
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,215 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Wabbajack.DTOs.Texture;
|
|
||||||
|
|
||||||
namespace Compression.BSA;
|
|
||||||
/*
|
|
||||||
* Copied from https://raw.githubusercontent.com/AlexxEG/BSA_Browser/master/Sharp.BSA.BA2/BA2Util/DDS.cs
|
|
||||||
* which is also GPL3 code. Modified slightly for Wabbajack
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copied from dds.h. Includes (almost) only stuff I need in this project.
|
|
||||||
*
|
|
||||||
* Link: https://github.com/digitalutopia1/BA2Lib/blob/master/BA2Lib/dds.h
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class DDS
|
|
||||||
{
|
|
||||||
public const int DDS_MAGIC = 0x20534444; // "DDS "
|
|
||||||
|
|
||||||
public const int DDS_FOURCC = 0x00000004; // DDPF_FOURCC
|
|
||||||
public const int DDS_RGB = 0x00000040; // DDPF_RGB
|
|
||||||
public const int DDS_RGBA = 0x00000041; // DDPF_RGB | DDPF_ALPHAPIXELS
|
|
||||||
|
|
||||||
public const int
|
|
||||||
DDS_HEADER_FLAGS_TEXTURE = 0x00001007; // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
|
|
||||||
|
|
||||||
public const int DDS_HEADER_FLAGS_MIPMAP = 0x00020000; // DDSD_MIPMAPCOUNT
|
|
||||||
public const int DDS_HEADER_FLAGS_LINEARSIZE = 0x00080000; // DDSD_LINEARSIZE
|
|
||||||
|
|
||||||
public const int DDS_SURFACE_FLAGS_TEXTURE = 0x00001000; // DDSCAPS_TEXTURE
|
|
||||||
public const int DDS_SURFACE_FLAGS_MIPMAP = 0x00400008; // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
|
|
||||||
|
|
||||||
public const int DDS_ALPHA_MODE_UNKNOWN = 0x0;
|
|
||||||
|
|
||||||
public static uint HeaderSizeForFormat(DXGI_FORMAT fmt)
|
|
||||||
{
|
|
||||||
switch (fmt)
|
|
||||||
{
|
|
||||||
case DXGI_FORMAT.BC1_UNORM_SRGB:
|
|
||||||
case DXGI_FORMAT.BC3_UNORM_SRGB:
|
|
||||||
case DXGI_FORMAT.BC4_UNORM:
|
|
||||||
case DXGI_FORMAT.BC5_SNORM:
|
|
||||||
case DXGI_FORMAT.BC6H_UF16:
|
|
||||||
case DXGI_FORMAT.BC7_UNORM:
|
|
||||||
case DXGI_FORMAT.BC7_UNORM_SRGB:
|
|
||||||
return DDS_HEADER_DXT10.Size + DDS_HEADER.Size;
|
|
||||||
default:
|
|
||||||
return DDS_HEADER.Size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint MAKEFOURCC(char ch0, char ch1, char ch2, char ch3)
|
|
||||||
{
|
|
||||||
// This is alien to me...
|
|
||||||
return (byte) ch0 | ((uint) (byte) ch1 << 8) | ((uint) (byte) ch2 << 16) | ((uint) (byte) ch3 << 24);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum DXT10_RESOURCE_DIMENSION
|
|
||||||
{
|
|
||||||
DIMENSION_TEXTURE1D = 2,
|
|
||||||
DIMENSION_TEXTURE2D = 3,
|
|
||||||
DIMENSION_TEXTURE3D = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum DDSCAPS2 : uint
|
|
||||||
{
|
|
||||||
CUBEMAP = 0x200,
|
|
||||||
CUBEMAP_POSITIVEX = 0x400,
|
|
||||||
CUBEMAP_NEGATIVEX = 0x800,
|
|
||||||
CUBEMAP_POSITIVEY = 0x1000,
|
|
||||||
CUBEMAP_NEGATIVEY = 0x2000,
|
|
||||||
CUBEMAP_POSITIVEZ = 0x4000,
|
|
||||||
CUBEMAP_NEGATIVEZ = 0x8000,
|
|
||||||
CUBEMAP_ALLFACES = 0xFC00
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct DDS_HEADER
|
|
||||||
{
|
|
||||||
public uint dwSize;
|
|
||||||
public uint dwHeaderFlags;
|
|
||||||
public uint dwHeight;
|
|
||||||
public uint dwWidth;
|
|
||||||
public uint dwPitchOrLinearSize;
|
|
||||||
public uint dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwHeaderFlags
|
|
||||||
public uint dwMipMapCount;
|
|
||||||
public uint dwReserved1; // [11]
|
|
||||||
public DDS_PIXELFORMAT PixelFormat; // ddspf
|
|
||||||
public uint dwSurfaceFlags;
|
|
||||||
public uint dwCubemapFlags;
|
|
||||||
public uint dwReserved2; // [3]
|
|
||||||
|
|
||||||
public uint GetSize()
|
|
||||||
{
|
|
||||||
// 9 uint + DDS_PIXELFORMAT uints + 2 uint arrays with 14 uints total
|
|
||||||
// each uint 4 bytes each
|
|
||||||
return 9 * 4 + PixelFormat.GetSize() + 14 * 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void Write(BinaryWriter bw)
|
|
||||||
{
|
|
||||||
bw.Write(dwSize);
|
|
||||||
bw.Write(dwHeaderFlags);
|
|
||||||
bw.Write(dwHeight);
|
|
||||||
bw.Write(dwWidth);
|
|
||||||
bw.Write(dwPitchOrLinearSize);
|
|
||||||
bw.Write(dwDepth);
|
|
||||||
bw.Write(dwMipMapCount);
|
|
||||||
|
|
||||||
// Just write it multiple times, since it's never assigned a value anyway
|
|
||||||
for (var i = 0; i < 11; i++)
|
|
||||||
bw.Write(dwReserved1);
|
|
||||||
|
|
||||||
// DDS_PIXELFORMAT
|
|
||||||
bw.Write(PixelFormat.dwSize);
|
|
||||||
bw.Write(PixelFormat.dwFlags);
|
|
||||||
bw.Write(PixelFormat.dwFourCC);
|
|
||||||
bw.Write(PixelFormat.dwRGBBitCount);
|
|
||||||
bw.Write(PixelFormat.dwRBitMask);
|
|
||||||
bw.Write(PixelFormat.dwGBitMask);
|
|
||||||
bw.Write(PixelFormat.dwBBitMask);
|
|
||||||
bw.Write(PixelFormat.dwABitMask);
|
|
||||||
|
|
||||||
bw.Write(dwSurfaceFlags);
|
|
||||||
bw.Write(dwCubemapFlags);
|
|
||||||
|
|
||||||
// Just write it multiple times, since it's never assigned a value anyway
|
|
||||||
for (var i = 0; i < 3; i++)
|
|
||||||
bw.Write(dwReserved2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint Size
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return (uint) (sizeof(DDS_HEADER) + sizeof(int) * 10 + sizeof(int) * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct DDS_HEADER_DXT10
|
|
||||||
{
|
|
||||||
public uint dxgiFormat;
|
|
||||||
public uint resourceDimension;
|
|
||||||
public uint miscFlag;
|
|
||||||
public uint arraySize;
|
|
||||||
public uint miscFlags2;
|
|
||||||
|
|
||||||
public void Write(BinaryWriter bw)
|
|
||||||
{
|
|
||||||
bw.Write(dxgiFormat);
|
|
||||||
bw.Write(resourceDimension);
|
|
||||||
bw.Write(miscFlag);
|
|
||||||
bw.Write(arraySize);
|
|
||||||
bw.Write(miscFlags2);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static uint Size
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return (uint) sizeof(DDS_HEADER_DXT10);
|
|
||||||
}
|
|
||||||
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
||||||
public struct DDS_PIXELFORMAT
|
|
||||||
{
|
|
||||||
public uint dwSize;
|
|
||||||
public uint dwFlags;
|
|
||||||
public uint dwFourCC;
|
|
||||||
public uint dwRGBBitCount;
|
|
||||||
public uint dwRBitMask;
|
|
||||||
public uint dwGBitMask;
|
|
||||||
public uint dwBBitMask;
|
|
||||||
public uint dwABitMask;
|
|
||||||
|
|
||||||
public DDS_PIXELFORMAT(uint size, uint flags, uint fourCC, uint rgbBitCount, uint rBitMask, uint gBitMask,
|
|
||||||
uint bBitMask, uint aBitMask)
|
|
||||||
{
|
|
||||||
dwSize = size;
|
|
||||||
dwFlags = flags;
|
|
||||||
dwFourCC = fourCC;
|
|
||||||
dwRGBBitCount = rgbBitCount;
|
|
||||||
dwRBitMask = rBitMask;
|
|
||||||
dwGBitMask = gBitMask;
|
|
||||||
dwBBitMask = bBitMask;
|
|
||||||
dwABitMask = aBitMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint GetSize()
|
|
||||||
{
|
|
||||||
// 8 uints, each 4 bytes each
|
|
||||||
return 8 * 4;
|
|
||||||
}
|
|
||||||
}
|
|
1116
Wabbajack.Compression.BSA/DirectXTexUtil.cs
Normal file
1116
Wabbajack.Compression.BSA/DirectXTexUtil.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,69 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.DTOs.BSA.FileStates;
|
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
|
||||||
|
|
||||||
public class ChunkBuilder
|
|
||||||
{
|
|
||||||
private BA2Chunk _chunk;
|
|
||||||
private Stream _dataSlab;
|
|
||||||
private long _offsetOffset;
|
|
||||||
private uint _packSize;
|
|
||||||
|
|
||||||
public static async Task<ChunkBuilder> Create(BA2DX10File state, BA2Chunk chunk, Stream src,
|
|
||||||
DiskSlabAllocator slab, CancellationToken token)
|
|
||||||
{
|
|
||||||
var builder = new ChunkBuilder {_chunk = chunk};
|
|
||||||
|
|
||||||
if (!chunk.Compressed)
|
|
||||||
{
|
|
||||||
builder._dataSlab = slab.Allocate(chunk.FullSz);
|
|
||||||
await src.CopyToLimitAsync(builder._dataSlab, (int) chunk.FullSz, token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var deflater = new Deflater(Deflater.BEST_COMPRESSION);
|
|
||||||
await using var ms = new MemoryStream();
|
|
||||||
await using (var ds = new DeflaterOutputStream(ms, deflater))
|
|
||||||
{
|
|
||||||
ds.IsStreamOwner = false;
|
|
||||||
await src.CopyToLimitAsync(ds, (int) chunk.FullSz, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder._dataSlab = slab.Allocate(ms.Length);
|
|
||||||
ms.Position = 0;
|
|
||||||
await ms.CopyToLimitAsync(builder._dataSlab, (int) ms.Length, token);
|
|
||||||
builder._packSize = (uint) ms.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder._dataSlab.Position = 0;
|
|
||||||
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask WriteData(BinaryWriter bw, CancellationToken token)
|
|
||||||
{
|
|
||||||
var pos = bw.BaseStream.Position;
|
|
||||||
bw.BaseStream.Position = _offsetOffset;
|
|
||||||
bw.Write((ulong) pos);
|
|
||||||
bw.BaseStream.Position = pos;
|
|
||||||
await _dataSlab.CopyToLimitAsync(bw.BaseStream, (int) _dataSlab.Length, token);
|
|
||||||
await _dataSlab.DisposeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,249 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Compression.BSA;
|
|
||||||
using ICSharpCode.SharpZipLib.Zip.Compression;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.DTOs.BSA.FileStates;
|
|
||||||
using Wabbajack.DTOs.Streams;
|
|
||||||
using Wabbajack.DTOs.Texture;
|
|
||||||
using Wabbajack.Paths;
|
|
||||||
|
|
||||||
namespace Wabbajack.Compression.BSA.FO4Archive;
|
|
||||||
|
|
||||||
public class DX10Entry : IBA2FileEntry
|
|
||||||
{
|
|
||||||
private readonly Reader _bsa;
|
|
||||||
private ushort _chunkHdrLen;
|
|
||||||
private List<TextureChunk> _chunks;
|
|
||||||
private uint _dirHash;
|
|
||||||
private string _extension;
|
|
||||||
private byte _format;
|
|
||||||
private ushort _height;
|
|
||||||
private int _index;
|
|
||||||
private uint _nameHash;
|
|
||||||
private byte _numChunks;
|
|
||||||
private byte _numMips;
|
|
||||||
private ushort _unk16;
|
|
||||||
private byte _unk8;
|
|
||||||
private ushort _width;
|
|
||||||
private readonly byte _isCubemap;
|
|
||||||
private readonly byte _tileMode;
|
|
||||||
|
|
||||||
public DX10Entry(Reader ba2Reader, int idx)
|
|
||||||
{
|
|
||||||
_bsa = ba2Reader;
|
|
||||||
var _rdr = ba2Reader._rdr;
|
|
||||||
_nameHash = _rdr.ReadUInt32();
|
|
||||||
FullPath = _nameHash.ToString("X");
|
|
||||||
_extension = Encoding.UTF8.GetString(_rdr.ReadBytes(4));
|
|
||||||
_dirHash = _rdr.ReadUInt32();
|
|
||||||
_unk8 = _rdr.ReadByte();
|
|
||||||
_numChunks = _rdr.ReadByte();
|
|
||||||
_chunkHdrLen = _rdr.ReadUInt16();
|
|
||||||
_height = _rdr.ReadUInt16();
|
|
||||||
_width = _rdr.ReadUInt16();
|
|
||||||
_numMips = _rdr.ReadByte();
|
|
||||||
_format = _rdr.ReadByte();
|
|
||||||
_isCubemap = _rdr.ReadByte();
|
|
||||||
_tileMode = _rdr.ReadByte();
|
|
||||||
_index = idx;
|
|
||||||
|
|
||||||
_chunks = Enumerable.Range(0, _numChunks)
|
|
||||||
.Select(_ => new TextureChunk(_rdr))
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public uint HeaderSize => DDS.HeaderSizeForFormat((DXGI_FORMAT) _format);
|
|
||||||
|
|
||||||
public string FullPath { get; set; }
|
|
||||||
|
|
||||||
public RelativePath Path => FullPath.ToRelativePath();
|
|
||||||
public uint Size => (uint) _chunks.Sum(f => f._fullSz) + HeaderSize + sizeof(uint);
|
|
||||||
|
|
||||||
public AFile State => new BA2DX10File
|
|
||||||
{
|
|
||||||
Path = Path,
|
|
||||||
NameHash = _nameHash,
|
|
||||||
Extension = _extension,
|
|
||||||
DirHash = _dirHash,
|
|
||||||
Unk8 = _unk8,
|
|
||||||
ChunkHdrLen = _chunkHdrLen,
|
|
||||||
Height = _height,
|
|
||||||
Width = _width,
|
|
||||||
NumMips = _numMips,
|
|
||||||
PixelFormat = _format,
|
|
||||||
IsCubeMap = _isCubemap,
|
|
||||||
TileMode = _tileMode,
|
|
||||||
Index = _index,
|
|
||||||
Chunks = _chunks.Select(ch => new BA2Chunk
|
|
||||||
{
|
|
||||||
FullSz = ch._fullSz,
|
|
||||||
StartMip = ch._startMip,
|
|
||||||
EndMip = ch._endMip,
|
|
||||||
Align = ch._align,
|
|
||||||
Compressed = ch._packSz != 0
|
|
||||||
}).ToArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
public async ValueTask CopyDataTo(Stream output, CancellationToken token)
|
|
||||||
{
|
|
||||||
var bw = new BinaryWriter(output);
|
|
||||||
|
|
||||||
WriteHeader(bw);
|
|
||||||
|
|
||||||
await using var fs = await _bsa._streamFactory.GetStream();
|
|
||||||
using var br = new BinaryReader(fs);
|
|
||||||
foreach (var chunk in _chunks)
|
|
||||||
{
|
|
||||||
var full = new byte[chunk._fullSz];
|
|
||||||
var isCompressed = chunk._packSz != 0;
|
|
||||||
|
|
||||||
br.BaseStream.Seek((long) chunk._offset, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
if (!isCompressed)
|
|
||||||
{
|
|
||||||
await br.BaseStream.ReadAsync(full, token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var compressed = new byte[chunk._packSz];
|
|
||||||
await br.BaseStream.ReadAsync(compressed, token);
|
|
||||||
var inflater = new Inflater();
|
|
||||||
inflater.SetInput(compressed);
|
|
||||||
inflater.Inflate(full);
|
|
||||||
}
|
|
||||||
|
|
||||||
await bw.BaseStream.WriteAsync(full, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask<IStreamFactory> GetStreamFactory(CancellationToken token)
|
|
||||||
{
|
|
||||||
var ms = new MemoryStream();
|
|
||||||
await CopyDataTo(ms, token);
|
|
||||||
ms.Position = 0;
|
|
||||||
return new MemoryStreamFactory(ms, Path, _bsa._streamFactory.LastModifiedUtc);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WriteHeader(BinaryWriter bw)
|
|
||||||
{
|
|
||||||
var ddsHeader = new DDS_HEADER();
|
|
||||||
|
|
||||||
ddsHeader.dwSize = ddsHeader.GetSize();
|
|
||||||
ddsHeader.dwHeaderFlags = DDS.DDS_HEADER_FLAGS_TEXTURE | DDS.DDS_HEADER_FLAGS_LINEARSIZE |
|
|
||||||
DDS.DDS_HEADER_FLAGS_MIPMAP;
|
|
||||||
ddsHeader.dwHeight = _height;
|
|
||||||
ddsHeader.dwWidth = _width;
|
|
||||||
ddsHeader.dwMipMapCount = _numMips;
|
|
||||||
ddsHeader.PixelFormat.dwSize = ddsHeader.PixelFormat.GetSize();
|
|
||||||
ddsHeader.dwDepth = 1;
|
|
||||||
ddsHeader.dwSurfaceFlags = DDS.DDS_SURFACE_FLAGS_TEXTURE | DDS.DDS_SURFACE_FLAGS_MIPMAP;
|
|
||||||
ddsHeader.dwCubemapFlags = _isCubemap == 1 ? (uint)(DDSCAPS2.CUBEMAP
|
|
||||||
| DDSCAPS2.CUBEMAP_NEGATIVEX | DDSCAPS2.CUBEMAP_POSITIVEX
|
|
||||||
| DDSCAPS2.CUBEMAP_NEGATIVEY | DDSCAPS2.CUBEMAP_POSITIVEY
|
|
||||||
| DDSCAPS2.CUBEMAP_NEGATIVEZ | DDSCAPS2.CUBEMAP_POSITIVEZ
|
|
||||||
| DDSCAPS2.CUBEMAP_ALLFACES) : 0u;
|
|
||||||
|
|
||||||
|
|
||||||
switch ((DXGI_FORMAT) _format)
|
|
||||||
{
|
|
||||||
case DXGI_FORMAT.BC1_UNORM:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
|
||||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '1');
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height / 2); // 4bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.BC2_UNORM:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
|
||||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '3');
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.BC3_UNORM:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
|
||||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '5');
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.BC5_UNORM:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
|
||||||
if (_bsa.UseATIFourCC)
|
|
||||||
ddsHeader.PixelFormat.dwFourCC =
|
|
||||||
DDS.MAKEFOURCC('A', 'T', 'I',
|
|
||||||
'2'); // this is more correct but the only thing I have found that supports it is the nvidia photoshop plugin
|
|
||||||
else
|
|
||||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('B', 'C', '5', 'U');
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.BC1_UNORM_SRGB:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
|
||||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', '1', '0');
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height / 2); // 4bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.BC3_UNORM_SRGB:
|
|
||||||
case DXGI_FORMAT.BC6H_UF16:
|
|
||||||
case DXGI_FORMAT.BC4_UNORM:
|
|
||||||
case DXGI_FORMAT.BC5_SNORM:
|
|
||||||
case DXGI_FORMAT.BC7_UNORM:
|
|
||||||
case DXGI_FORMAT.BC7_UNORM_SRGB:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
|
|
||||||
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', '1', '0');
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.R8G8B8A8_UNORM:
|
|
||||||
case DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGBA;
|
|
||||||
ddsHeader.PixelFormat.dwRGBBitCount = 32;
|
|
||||||
ddsHeader.PixelFormat.dwRBitMask = 0x000000FF;
|
|
||||||
ddsHeader.PixelFormat.dwGBitMask = 0x0000FF00;
|
|
||||||
ddsHeader.PixelFormat.dwBBitMask = 0x00FF0000;
|
|
||||||
ddsHeader.PixelFormat.dwABitMask = 0xFF000000;
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height * 4); // 32bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.B8G8R8A8_UNORM:
|
|
||||||
case DXGI_FORMAT.B8G8R8X8_UNORM:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGBA;
|
|
||||||
ddsHeader.PixelFormat.dwRGBBitCount = 32;
|
|
||||||
ddsHeader.PixelFormat.dwRBitMask = 0x00FF0000;
|
|
||||||
ddsHeader.PixelFormat.dwGBitMask = 0x0000FF00;
|
|
||||||
ddsHeader.PixelFormat.dwBBitMask = 0x000000FF;
|
|
||||||
ddsHeader.PixelFormat.dwABitMask = 0xFF000000;
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height * 4); // 32bpp
|
|
||||||
break;
|
|
||||||
case DXGI_FORMAT.R8_UNORM:
|
|
||||||
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGB;
|
|
||||||
ddsHeader.PixelFormat.dwRGBBitCount = 8;
|
|
||||||
ddsHeader.PixelFormat.dwRBitMask = 0xFF;
|
|
||||||
ddsHeader.dwPitchOrLinearSize = (uint) (_width * _height); // 8bpp
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Exception("Unsupported DDS header format. File: " + FullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
bw.Write((uint) DDS.DDS_MAGIC);
|
|
||||||
ddsHeader.Write(bw);
|
|
||||||
|
|
||||||
switch ((DXGI_FORMAT) _format)
|
|
||||||
{
|
|
||||||
case DXGI_FORMAT.BC1_UNORM_SRGB:
|
|
||||||
case DXGI_FORMAT.BC3_UNORM_SRGB:
|
|
||||||
case DXGI_FORMAT.BC4_UNORM:
|
|
||||||
case DXGI_FORMAT.BC5_SNORM:
|
|
||||||
case DXGI_FORMAT.BC6H_UF16:
|
|
||||||
case DXGI_FORMAT.BC7_UNORM:
|
|
||||||
case DXGI_FORMAT.BC7_UNORM_SRGB:
|
|
||||||
var dxt10 = new DDS_HEADER_DXT10
|
|
||||||
{
|
|
||||||
dxgiFormat = _format,
|
|
||||||
resourceDimension = (uint) DXT10_RESOURCE_DIMENSION.DIMENSION_TEXTURE2D,
|
|
||||||
miscFlag = 0,
|
|
||||||
arraySize = 1,
|
|
||||||
miscFlags2 = DDS.DDS_ALPHA_MODE_UNKNOWN
|
|
||||||
};
|
|
||||||
dxt10.Write(bw);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,4 +17,7 @@ public class BA2State : IArchive
|
|||||||
public BA2EntryType Type { get; set; }
|
public BA2EntryType Type { get; set; }
|
||||||
public string HeaderMagic { get; set; }
|
public string HeaderMagic { get; set; }
|
||||||
public uint Version { get; set; }
|
public uint Version { get; set; }
|
||||||
|
public uint Unknown1 { get; set; }
|
||||||
|
public uint Unknown2 { get; set; }
|
||||||
|
public uint Compression { get; set; }
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user