diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e53377b..c95b062b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ### 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 WebView dependency * Updated other dependencies diff --git a/Wabbajack.App.Wpf/Properties/PublishProfiles/FolderProfile.pubxml b/Wabbajack.App.Wpf/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 00000000..acb3e335 --- /dev/null +++ b/Wabbajack.App.Wpf/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + bin\Release\Publish + FileSystem + <_TargetId>Folder + net8.0-windows + win-x64 + true + false + false + + \ No newline at end of file diff --git a/Wabbajack.CLI/Properties/launchSettings.json b/Wabbajack.CLI/Properties/launchSettings.json new file mode 100644 index 00000000..be163303 --- /dev/null +++ b/Wabbajack.CLI/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "Wabbajack.CLI": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/Wabbajack.Compression.BSA.Test/CompressionTests.cs b/Wabbajack.Compression.BSA.Test/CompressionTests.cs index ead5ee50..d881891e 100644 --- a/Wabbajack.Compression.BSA.Test/CompressionTests.cs +++ b/Wabbajack.Compression.BSA.Test/CompressionTests.cs @@ -55,6 +55,7 @@ public class CompressionTests { if (name == "tes4.bsa") return; // not sure why is is failing + var reader = await BSADispatch.Open(path); var dataStates = await reader.Files @@ -78,7 +79,6 @@ public class CompressionTests var rebuiltStream = new MemoryStream(); await build.Build(rebuiltStream, CancellationToken.None); - rebuiltStream.Position = 0; var reader2 = await BSADispatch.Open(new MemoryStreamFactory(rebuiltStream, path, path.LastModifiedUtc())); await reader.Files.Zip(reader2.Files) diff --git a/Wabbajack.Compression.BSA/FO4Archive/Builder.cs b/Wabbajack.Compression.BSA/BA2Archive/Builder.cs similarity index 88% rename from Wabbajack.Compression.BSA/FO4Archive/Builder.cs rename to Wabbajack.Compression.BSA/BA2Archive/Builder.cs index 7116fdb7..ca13f449 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/Builder.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/Builder.cs @@ -10,7 +10,7 @@ using Wabbajack.DTOs.BSA.ArchiveStates; using Wabbajack.DTOs.BSA.FileStates; using Wabbajack.Paths.IO; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; public class Builder : IBuilder { @@ -33,7 +33,7 @@ public class Builder : IBuilder break; 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) { _entries.Add(resultdx10); @@ -59,6 +59,13 @@ public class Builder : IBuilder 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); diff --git a/Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs b/Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs new file mode 100644 index 00000000..97906795 --- /dev/null +++ b/Wabbajack.Compression.BSA/BA2Archive/ChunkBuilder.cs @@ -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 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(); + } +} \ No newline at end of file diff --git a/Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs b/Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs new file mode 100644 index 00000000..00c88312 --- /dev/null +++ b/Wabbajack.Compression.BSA/BA2Archive/DX10Entry.cs @@ -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 _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(); + var pixelFormat = DirectXTexUtility.GetPixelFormat(Metadata); + var hasDx10Header = DirectXTexUtility.HasDx10Header(pixelFormat); + if (hasDx10Header) + size += (uint)Marshal.SizeOf(); + + 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 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); + } +} + diff --git a/Wabbajack.Compression.BSA/FO4Archive/DX10FileEntryBuilder.cs b/Wabbajack.Compression.BSA/BA2Archive/DX10FileEntryBuilder.cs similarity index 62% rename from Wabbajack.Compression.BSA/FO4Archive/DX10FileEntryBuilder.cs rename to Wabbajack.Compression.BSA/BA2Archive/DX10FileEntryBuilder.cs index 86045356..0af601d9 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/DX10FileEntryBuilder.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/DX10FileEntryBuilder.cs @@ -1,18 +1,20 @@ +using DirectXTex; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using Compression.BSA; using Wabbajack.DTOs.BSA.FileStates; using Wabbajack.DTOs.Texture; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; public class DX10FileEntryBuilder : IFileBuilder { private List _chunks; private BA2DX10File _state; + private uint _headerSize = 0; public uint FileHash => _state.NameHash; public uint DirHash => _state.DirHash; @@ -43,20 +45,36 @@ public class DX10FileEntryBuilder : IFileBuilder foreach (var chunk in _chunks) await chunk.WriteData(wtr, token); } + public uint GetHeaderSize(BA2DX10File state) + { + if (_headerSize > 0) + return _headerSize; - public static async Task Create(BA2DX10File state, Stream src, DiskSlabAllocator slab, + uint size = 0; + size += (uint)Marshal.SizeOf(DirectXTexUtility.DDSHeader.DDSMagic); + size += (uint)Marshal.SizeOf(); + 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(); + + return _headerSize = size; + } + + public static async Task Create(BA2DX10File state, Stream src, DiskSlabAllocator slab, bool useLz4Compression, CancellationToken token) { 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); // This can't be parallel because it all runs off the same base IO stream. builder._chunks = new List(); 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; } diff --git a/Wabbajack.Compression.BSA/FO4Archive/FileEntry.cs b/Wabbajack.Compression.BSA/BA2Archive/FileEntry.cs similarity index 98% rename from Wabbajack.Compression.BSA/FO4Archive/FileEntry.cs rename to Wabbajack.Compression.BSA/BA2Archive/FileEntry.cs index 8b3961b3..7d0edc28 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/FileEntry.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/FileEntry.cs @@ -9,7 +9,7 @@ using Wabbajack.DTOs.BSA.FileStates; using Wabbajack.DTOs.Streams; using Wabbajack.Paths; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; public class FileEntry : IBA2FileEntry { diff --git a/Wabbajack.Compression.BSA/FO4Archive/FileEntryBuilder.cs b/Wabbajack.Compression.BSA/BA2Archive/FileEntryBuilder.cs similarity index 97% rename from Wabbajack.Compression.BSA/FO4Archive/FileEntryBuilder.cs rename to Wabbajack.Compression.BSA/BA2Archive/FileEntryBuilder.cs index c620c624..3d11a0d4 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/FileEntryBuilder.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/FileEntryBuilder.cs @@ -6,7 +6,7 @@ using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using Wabbajack.Common; using Wabbajack.DTOs.BSA.FileStates; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; public class FileEntryBuilder : IFileBuilder { diff --git a/Wabbajack.Compression.BSA/FO4Archive/IBA2FileEntry.cs b/Wabbajack.Compression.BSA/BA2Archive/IBA2FileEntry.cs similarity index 71% rename from Wabbajack.Compression.BSA/FO4Archive/IBA2FileEntry.cs rename to Wabbajack.Compression.BSA/BA2Archive/IBA2FileEntry.cs index a89e965e..d9cd8521 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/IBA2FileEntry.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/IBA2FileEntry.cs @@ -1,6 +1,6 @@ using Wabbajack.Compression.BSA.Interfaces; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; internal interface IBA2FileEntry : IFile { diff --git a/Wabbajack.Compression.BSA/FO4Archive/IFileBuilder.cs b/Wabbajack.Compression.BSA/BA2Archive/IFileBuilder.cs similarity index 87% rename from Wabbajack.Compression.BSA/FO4Archive/IFileBuilder.cs rename to Wabbajack.Compression.BSA/BA2Archive/IFileBuilder.cs index d9f5ffda..cf78a00f 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/IFileBuilder.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/IFileBuilder.cs @@ -2,7 +2,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; internal interface IFileBuilder { diff --git a/Wabbajack.Compression.BSA/FO4Archive/Reader.cs b/Wabbajack.Compression.BSA/BA2Archive/Reader.cs similarity index 81% rename from Wabbajack.Compression.BSA/FO4Archive/Reader.cs rename to Wabbajack.Compression.BSA/BA2Archive/Reader.cs index ecbf4dae..686812c5 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/Reader.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/Reader.cs @@ -7,7 +7,7 @@ using Wabbajack.Compression.BSA.Interfaces; using Wabbajack.DTOs.BSA.ArchiveStates; using Wabbajack.DTOs.Streams; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; public class Reader : IReader { @@ -15,9 +15,17 @@ public class Reader : IReader internal string _headerMagic; internal ulong _nameTableOffset; internal uint _numFiles; + internal uint _unknown1; + internal uint _unknown2; + internal uint _compression; internal BinaryReader _rdr; public IStreamFactory _streamFactory; internal BA2EntryType _type; + + /// + /// Fallout 4 - Version 1, 7 or 8 + /// Starfield - Version 2 or 3 + /// internal uint _version; private Reader(Stream stream) @@ -37,7 +45,10 @@ public class Reader : IReader Version = _version, HeaderMagic = _headerMagic, Type = _type, - HasNameTable = HasNameTable + HasNameTable = HasNameTable, + Unknown1 = _unknown1, + Unknown2 = _unknown2, + Compression = _compression, }; @@ -67,6 +78,10 @@ public class Reader : IReader _numFiles = _rdr.ReadUInt32(); _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(); for (var idx = 0; idx < _numFiles; idx += 1) switch (_type) @@ -94,4 +109,4 @@ public class Reader : IReader return Task.CompletedTask; } -} \ No newline at end of file +} diff --git a/Wabbajack.Compression.BSA/FO4Archive/TextureChunk.cs b/Wabbajack.Compression.BSA/BA2Archive/TextureChunk.cs similarity index 90% rename from Wabbajack.Compression.BSA/FO4Archive/TextureChunk.cs rename to Wabbajack.Compression.BSA/BA2Archive/TextureChunk.cs index 5003348c..6028ccca 100644 --- a/Wabbajack.Compression.BSA/FO4Archive/TextureChunk.cs +++ b/Wabbajack.Compression.BSA/BA2Archive/TextureChunk.cs @@ -1,6 +1,6 @@ using System.IO; -namespace Wabbajack.Compression.BSA.FO4Archive; +namespace Wabbajack.Compression.BSA.BA2Archive; public class TextureChunk { diff --git a/Wabbajack.Compression.BSA/BSADispatch.cs b/Wabbajack.Compression.BSA/BSADispatch.cs index 7e989440..8cff05ac 100644 --- a/Wabbajack.Compression.BSA/BSADispatch.cs +++ b/Wabbajack.Compression.BSA/BSADispatch.cs @@ -22,7 +22,7 @@ public static class BSADispatch { FileType.TES3 => await 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") }; } @@ -34,7 +34,7 @@ public static class BSADispatch { FileType.TES3 => await 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") }; } @@ -46,7 +46,7 @@ public static class BSADispatch { FileType.TES3 => await 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") }; } @@ -57,7 +57,7 @@ public static class BSADispatch { TES3State tes3 => new Builder(tes3), BSAState bsa => TES5Archive.Builder.Create(bsa, manager), - BA2State ba2 => FO4Archive.Builder.Create(ba2, manager), + BA2State ba2 => BA2Archive.Builder.Create(ba2, manager), _ => throw new NotImplementedException() }; } diff --git a/Wabbajack.Compression.BSA/DDS.cs b/Wabbajack.Compression.BSA/DDS.cs deleted file mode 100644 index 8a7a4334..00000000 --- a/Wabbajack.Compression.BSA/DDS.cs +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/Wabbajack.Compression.BSA/DirectXTexUtil.cs b/Wabbajack.Compression.BSA/DirectXTexUtil.cs new file mode 100644 index 00000000..b1c302fb --- /dev/null +++ b/Wabbajack.Compression.BSA/DirectXTexUtil.cs @@ -0,0 +1,1116 @@ +// ------------------------------------------------------------------------ +// DirectXTex Utility - A simple class for generating DDS Headers +// Copyright(c) 2018 Philip/Scobalula +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// ------------------------------------------------------------------------ +// Author: Philip/Scobalula +// Description: DirectXTex DDS Header Utilities +// Source: https://gist.github.com/Scobalula/d9474f3fcf3d5a2ca596fceb64e16c98 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +namespace DirectXTex +{ + public class DirectXTexUtility + { + #region Enumerators + /// + /// DDS Formats + /// + public enum DXGIFormat : uint + { + UNKNOWN = 0, + R32G32B32A32TYPELESS = 1, + R32G32B32A32FLOAT = 2, + R32G32B32A32UINT = 3, + R32G32B32A32SINT = 4, + R32G32B32TYPELESS = 5, + R32G32B32FLOAT = 6, + R32G32B32UINT = 7, + R32G32B32SINT = 8, + R16G16B16A16TYPELESS = 9, + R16G16B16A16FLOAT = 10, + R16G16B16A16UNORM = 11, + R16G16B16A16UINT = 12, + R16G16B16A16SNORM = 13, + R16G16B16A16SINT = 14, + R32G32TYPELESS = 15, + R32G32FLOAT = 16, + R32G32UINT = 17, + R32G32SINT = 18, + R32G8X24TYPELESS = 19, + D32FLOATS8X24UINT = 20, + R32FLOATX8X24TYPELESS = 21, + X32TYPELESSG8X24UINT = 22, + R10G10B10A2TYPELESS = 23, + R10G10B10A2UNORM = 24, + R10G10B10A2UINT = 25, + R11G11B10FLOAT = 26, + R8G8B8A8TYPELESS = 27, + R8G8B8A8UNORM = 28, + R8G8B8A8UNORMSRGB = 29, + R8G8B8A8UINT = 30, + R8G8B8A8SNORM = 31, + R8G8B8A8SINT = 32, + R16G16TYPELESS = 33, + R16G16FLOAT = 34, + R16G16UNORM = 35, + R16G16UINT = 36, + R16G16SNORM = 37, + R16G16SINT = 38, + R32TYPELESS = 39, + D32FLOAT = 40, + R32FLOAT = 41, + R32UINT = 42, + R32SINT = 43, + R24G8TYPELESS = 44, + D24UNORMS8UINT = 45, + R24UNORMX8TYPELESS = 46, + X24TYPELESSG8UINT = 47, + R8G8TYPELESS = 48, + R8G8UNORM = 49, + R8G8UINT = 50, + R8G8SNORM = 51, + R8G8SINT = 52, + R16TYPELESS = 53, + R16FLOAT = 54, + D16UNORM = 55, + R16UNORM = 56, + R16UINT = 57, + R16SNORM = 58, + R16SINT = 59, + R8TYPELESS = 60, + R8UNORM = 61, + R8UINT = 62, + R8SNORM = 63, + R8SINT = 64, + A8UNORM = 65, + R1UNORM = 66, + R9G9B9E5SHAREDEXP = 67, + R8G8B8G8UNORM = 68, + G8R8G8B8UNORM = 69, + BC1TYPELESS = 70, + BC1UNORM = 71, + BC1UNORMSRGB = 72, + BC2TYPELESS = 73, + BC2UNORM = 74, + BC2UNORMSRGB = 75, + BC3TYPELESS = 76, + BC3UNORM = 77, + BC3UNORMSRGB = 78, + BC4TYPELESS = 79, + BC4UNORM = 80, + BC4SNORM = 81, + BC5TYPELESS = 82, + BC5UNORM = 83, + BC5SNORM = 84, + B5G6R5UNORM = 85, + B5G5R5A1UNORM = 86, + B8G8R8A8UNORM = 87, + B8G8R8X8UNORM = 88, + R10G10B10XRBIASA2UNORM = 89, + B8G8R8A8TYPELESS = 90, + B8G8R8A8UNORMSRGB = 91, + B8G8R8X8TYPELESS = 92, + B8G8R8X8UNORMSRGB = 93, + BC6HTYPELESS = 94, + BC6HUF16 = 95, + BC6HSF16 = 96, + BC7TYPELESS = 97, + BC7UNORM = 98, + BC7UNORMSRGB = 99, + AYUV = 100, + Y410 = 101, + Y416 = 102, + NV12 = 103, + P010 = 104, + P016 = 105, + OPAQUE420 = 106, + YUY2 = 107, + Y210 = 108, + Y216 = 109, + NV11 = 110, + AI44 = 111, + IA44 = 112, + P8 = 113, + A8P8 = 114, + B4G4R4A4UNORM = 115, + FORCEUINT = 0xffffffff + } + + /// + /// DDS Flags + /// + [Flags] + public enum DDSFlags + { + NONE = 0x0, + LEGACYDWORD = 0x1, + NOLEGACYEXPANSION = 0x2, + NOR10B10G10A2FIXUP = 0x4, + FORCERGB = 0x8, + NO16BPP = 0x10, + EXPANDLUMINANCE = 0x20, + BADDXTNTAILS = 0x40, + FORCEDX10EXT = 0x10000, + FORCEDX10EXTMISC2 = 0x20000, + } + + /// + /// Texture Dimension + /// + public enum TexDimension + { + TEXTURE1D = 2, + TEXTURE2D = 3, + TEXTURE3D = 4, + } + + /// + /// Misc. Texture Flags + /// + public enum TexMiscFlags : uint + { + TEXTURECUBE = 0x4, + }; + + /// + /// Misc. Texture Flags + /// + public enum TexMiscFlags2 : uint + { + TEXMISC2ALPHAMODEMASK = 0x7, + }; + + /// + /// Texture Alpha Modes + /// + public enum TexAlphaMode + { + UNKNOWN = 0, + STRAIGHT = 1, + PREMULTIPLIED = 2, + OPAQUE = 3, + CUSTOM = 4, + }; + + /// + /// CP Flags + /// + [Flags] + public enum CPFLAGS + { + NONE = 0x0, // Normal operation + LEGACYDWORD = 0x1, // Assume pitch is DWORD aligned instead of BYTE aligned + PARAGRAPH = 0x2, // Assume pitch is 16-byte aligned instead of BYTE aligned + YMM = 0x4, // Assume pitch is 32-byte aligned instead of BYTE aligned + ZMM = 0x8, // Assume pitch is 64-byte aligned instead of BYTE aligned + PAGE4K = 0x200, // Assume pitch is 4096-byte aligned instead of BYTE aligned + BADDXTNTAILS = 0x1000, // BC formats with malformed mipchain blocks smaller than 4x4 + BPP24 = 0x10000, // Override with a legacy 24 bits-per-pixel format size + BPP16 = 0x20000, // Override with a legacy 16 bits-per-pixel format size + BPP8 = 0x40000, // Override with a legacy 8 bits-per-pixel format size + }; + #endregion + + #region Structs/Classes + /// + /// Common Pixel Formats + /// + public class PixelFormats + { + /// + /// DDS Pixel Format Size + /// + public static readonly uint Size = (uint)Marshal.SizeOf(); + + #region PixelFormatsConstants + public const uint DDSFOURCC = 0x00000004; // DDPFFOURCC + public const uint DDSRGB = 0x00000040; // DDPFRGB + public const uint DDSRGBA = 0x00000041; // DDPFRGB | DDPFALPHAPIXELS + public const uint DDSLUMINANCE = 0x00020000; // DDPFLUMINANCE + public const uint DDSLUMINANCEA = 0x00020001; // DDPFLUMINANCE | DDPFALPHAPIXELS + public const uint DDSALPHAPIXELS = 0x00000001; // DDPFALPHAPIXELS + public const uint DDSALPHA = 0x00000002; // DDPFALPHA + public const uint DDSPAL8 = 0x00000020; // DDPFPALETTEINDEXED8 + public const uint DDSPAL8A = 0x00000021; // DDPFPALETTEINDEXED8 | DDPFALPHAPIXELS + public const uint DDSBUMPDUDV = 0x00080000; // DDPFBUMPDUDV + #endregion + + #region DDSPixelFormats + public static DDSHeader.DDSPixelFormat DXT1 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('D', 'X', 'T', '1'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat DXT2 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('D', 'X', 'T', '2'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat DXT3 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('D', 'X', 'T', '3'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat DXT4 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('D', 'X', 'T', '4'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat DXT5 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('D', 'X', 'T', '5'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat BC4UNORM = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('B', 'C', '4', 'U'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat BC4SNORM = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('B', 'C', '4', 'S'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat BC5UNORM = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('B', 'C', '5', 'U'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat BC5SNORM = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('B', 'C', '5', 'S'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat R8G8B8G8 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('R', 'G', 'B', 'G'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat G8R8G8B8 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('G', 'R', 'G', 'B'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat YUY2 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('Y', 'U', 'Y', '2'), 0, 0, 0, 0, 0); + + public static DDSHeader.DDSPixelFormat A8R8G8B8 = + new DDSHeader.DDSPixelFormat(Size, DDSRGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); + + public static DDSHeader.DDSPixelFormat X8R8G8B8 = + new DDSHeader.DDSPixelFormat(Size, DDSRGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000); + + public static DDSHeader.DDSPixelFormat A8B8G8R8 = + new DDSHeader.DDSPixelFormat(Size, DDSRGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + + public static DDSHeader.DDSPixelFormat X8B8G8R8 = + new DDSHeader.DDSPixelFormat(Size, DDSRGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000); + + public static DDSHeader.DDSPixelFormat G16R16 = + new DDSHeader.DDSPixelFormat(Size, DDSRGB, 0, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000); + + public static DDSHeader.DDSPixelFormat R5G6B5 = + new DDSHeader.DDSPixelFormat(Size, DDSRGB, 0, 16, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000); + + public static DDSHeader.DDSPixelFormat A1R5G5B5 = + new DDSHeader.DDSPixelFormat(Size, DDSRGBA, 0, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000); + + public static DDSHeader.DDSPixelFormat A4R4G4B4 = + new DDSHeader.DDSPixelFormat(Size, DDSRGBA, 0, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000); + + public static DDSHeader.DDSPixelFormat R8G8B8 = + new DDSHeader.DDSPixelFormat(Size, DDSRGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000); + + public static DDSHeader.DDSPixelFormat L8 = + new DDSHeader.DDSPixelFormat(Size, DDSLUMINANCE, 0, 8, 0xff, 0x00, 0x00, 0x00); + + public static DDSHeader.DDSPixelFormat L16 = + new DDSHeader.DDSPixelFormat(Size, DDSLUMINANCE, 0, 16, 0xffff, 0x0000, 0x0000, 0x0000); + + public static DDSHeader.DDSPixelFormat A8L8 = + new DDSHeader.DDSPixelFormat(Size, DDSLUMINANCEA, 0, 16, 0x00ff, 0x0000, 0x0000, 0xff00); + + public static DDSHeader.DDSPixelFormat A8L8ALT = + new DDSHeader.DDSPixelFormat(Size, DDSLUMINANCEA, 0, 8, 0x00ff, 0x0000, 0x0000, 0xff00); + + public static DDSHeader.DDSPixelFormat A8 = + new DDSHeader.DDSPixelFormat(Size, DDSALPHA, 0, 8, 0x00, 0x00, 0x00, 0xff); + + public static DDSHeader.DDSPixelFormat V8U8 = + new DDSHeader.DDSPixelFormat(Size, DDSBUMPDUDV, 0, 16, 0x00ff, 0xff00, 0x0000, 0x0000); + + public static DDSHeader.DDSPixelFormat Q8W8V8U8 = + new DDSHeader.DDSPixelFormat(Size, DDSBUMPDUDV, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000); + + public static DDSHeader.DDSPixelFormat V16U16 = + new DDSHeader.DDSPixelFormat(Size, DDSBUMPDUDV, 0, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000); + + public static DDSHeader.DDSPixelFormat DX10 = + new DDSHeader.DDSPixelFormat(Size, DDSFOURCC, MakePixelFormatFourCC('D', 'X', '1', '0'), 0, 0, 0, 0, 0); + #endregion + } + + /// + /// DDS Header + /// + public struct DDSHeader + { + /// + /// DDS Header Flags + /// + [Flags] + public enum HeaderFlags : uint + { + TEXTURE = 0x00001007, // DDSDCAPS | DDSDHEIGHT | DDSDWIDTH | DDSDPIXELFORMAT + MIPMAP = 0x00020000, // DDSDMIPMAPCOUNT + VOLUME = 0x00800000, // DDSDDEPTH + PITCH = 0x00000008, // DDSDPITCH + LINEARSIZE = 0x00080000, // DDSDLINEARSIZE + } + + /// + /// DDS Surface Flags + /// + public enum SurfaceFlags : uint + { + TEXTURE = 0x00001000, // DDSCAPSTEXTURE + MIPMAP = 0x00400008, // DDSCAPSCOMPLEX | DDSCAPSMIPMAP + CUBEMAP = 0x00000008, // DDSCAPSCOMPLEX + } + + /// + /// DDS Magic/Four CC + /// + public const uint DDSMagic = 0x20534444; + + /// + /// DDS Pixel Format + /// + public struct DDSPixelFormat + { + public uint Size; + public uint Flags; + public uint FourCC; + public uint RGBBitCount; + public uint RBitMask; + public uint GBitMask; + public uint BBitMask; + public uint ABitMask; + + /// + /// Creates a new DDS Pixel Format + /// + public DDSPixelFormat(uint size, uint flags, uint fourCC, uint rgbBitCount, uint rBitMask, uint gBitMask, uint bBitMask, uint aBitMask) + { + Size = size; + Flags = flags; + FourCC = fourCC; + RGBBitCount = rgbBitCount; + RBitMask = rBitMask; + GBitMask = gBitMask; + BBitMask = bBitMask; + ABitMask = aBitMask; + } + + } + + public uint Size; + public HeaderFlags Flags; + public uint Height; + public uint Width; + public uint PitchOrLinearSize; + public uint Depth; // only if DDSHEADERFLAGSVOLUME is set in flags + public uint MipMapCount; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)] + public uint[] Reserved1; + public DDSPixelFormat PixelFormat; + public uint Caps; + public uint Caps2; + public uint Caps3; + public uint Caps4; + public uint Reserved2; + } + + /// + /// DDS DX10 Header + /// + public struct DX10Header + { + public DXGIFormat Format; + public TexDimension ResourceDimension; + public TexMiscFlags MiscFlag; // see D3D11RESOURCEMISCFLAG + public uint ArraySize; + public uint MiscFlags2; // see DDSMISCFLAGS2 + } + + /// + /// Texture Metadata + /// + public struct TexMetadata + { + #region Properties + public long Width; + public long Height; // Should be 1 for 1D textures + public long Depth; // Should be 1 for 1D or 2D textures + public long ArraySize; // For cubemap, this is a multiple of 6 + public long MipLevels; + public TexMiscFlags MiscFlags; + public TexMiscFlags2 MiscFlags2; + public DXGIFormat Format; + public TexDimension Dimension; + #endregion + + /// + /// Creates a new Texture Metadata Structe + /// + public TexMetadata(long width, long height, long depth, long arraySize, long mipLevels, TexMiscFlags flags, TexMiscFlags2 flags2, DXGIFormat format, TexDimension dimension) + { + Width = width; + Height = height; + Depth = depth; + ArraySize = arraySize; + MipLevels = mipLevels; + MiscFlags = flags; + MiscFlags2 = flags2; + Format = format; + Dimension = dimension; + } + + /// + /// Checks Alpha Mode + /// + public bool IsPMAlpha() + { + return (TexAlphaMode)(MiscFlags2 & TexMiscFlags2.TEXMISC2ALPHAMODEMASK) == TexAlphaMode.PREMULTIPLIED; + } + + public bool IsCubeMap() + { + return (MiscFlags & TexMiscFlags.TEXTURECUBE) == TexMiscFlags.TEXTURECUBE; + } + } + #endregion + + #region HelperMethods + /// + /// Clamps Value to a range. + /// + /// Value to Clamp + /// Max value + /// Min value + /// Clamped Value + private static T Clamp(T value, T max, T min) where T : IComparable + { + return value.CompareTo(min) < 0 ? min : value.CompareTo(max) > 0 ? max : value; + } + + /// + /// Converts a Struct to a Byte array + /// + private static byte[] StructToBytes(T value) + { + // Size of Struct + int length = Marshal.SizeOf(); + // Destination + byte[] destination = new byte[length]; + // Get Pointer + IntPtr pointer = Marshal.AllocHGlobal(length); + // Convert it + Marshal.StructureToPtr(value, pointer, false); + Marshal.Copy(pointer, destination, 0, length); + Marshal.FreeHGlobal(pointer); + // Done + return destination; + } + + /// + /// Generates a FourCC Integer from Pixel Format Characters + /// + private static uint MakePixelFormatFourCC(char char1, char char2, char char3, char char4) + { + return Convert.ToByte(char1) | (uint)Convert.ToByte(char2) << 8 | (uint)Convert.ToByte(char3) << 16 | (uint)Convert.ToByte(char4) << 24; + } + + /// + /// Gets the Bits Per Pixel for the given format + /// + private static ulong BitsPerPixel(DXGIFormat format) + { + switch (format) + { + case DXGIFormat.R32G32B32A32TYPELESS: + case DXGIFormat.R32G32B32A32FLOAT: + case DXGIFormat.R32G32B32A32UINT: + case DXGIFormat.R32G32B32A32SINT: + return 128; + case DXGIFormat.R32G32B32TYPELESS: + case DXGIFormat.R32G32B32FLOAT: + case DXGIFormat.R32G32B32UINT: + case DXGIFormat.R32G32B32SINT: + return 96; + case DXGIFormat.R16G16B16A16TYPELESS: + case DXGIFormat.R16G16B16A16FLOAT: + case DXGIFormat.R16G16B16A16UNORM: + case DXGIFormat.R16G16B16A16UINT: + case DXGIFormat.R16G16B16A16SNORM: + case DXGIFormat.R16G16B16A16SINT: + case DXGIFormat.R32G32TYPELESS: + case DXGIFormat.R32G32FLOAT: + case DXGIFormat.R32G32UINT: + case DXGIFormat.R32G32SINT: + case DXGIFormat.R32G8X24TYPELESS: + case DXGIFormat.D32FLOATS8X24UINT: + case DXGIFormat.R32FLOATX8X24TYPELESS: + case DXGIFormat.X32TYPELESSG8X24UINT: + case DXGIFormat.Y416: + case DXGIFormat.Y210: + case DXGIFormat.Y216: + return 64; + case DXGIFormat.R10G10B10A2TYPELESS: + case DXGIFormat.R10G10B10A2UNORM: + case DXGIFormat.R10G10B10A2UINT: + case DXGIFormat.R11G11B10FLOAT: + case DXGIFormat.R8G8B8A8TYPELESS: + case DXGIFormat.R8G8B8A8UNORM: + case DXGIFormat.R8G8B8A8UNORMSRGB: + case DXGIFormat.R8G8B8A8UINT: + case DXGIFormat.R8G8B8A8SNORM: + case DXGIFormat.R8G8B8A8SINT: + case DXGIFormat.R16G16TYPELESS: + case DXGIFormat.R16G16FLOAT: + case DXGIFormat.R16G16UNORM: + case DXGIFormat.R16G16UINT: + case DXGIFormat.R16G16SNORM: + case DXGIFormat.R16G16SINT: + case DXGIFormat.R32TYPELESS: + case DXGIFormat.D32FLOAT: + case DXGIFormat.R32FLOAT: + case DXGIFormat.R32UINT: + case DXGIFormat.R32SINT: + case DXGIFormat.R24G8TYPELESS: + case DXGIFormat.D24UNORMS8UINT: + case DXGIFormat.R24UNORMX8TYPELESS: + case DXGIFormat.X24TYPELESSG8UINT: + case DXGIFormat.R9G9B9E5SHAREDEXP: + case DXGIFormat.R8G8B8G8UNORM: + case DXGIFormat.G8R8G8B8UNORM: + case DXGIFormat.B8G8R8A8UNORM: + case DXGIFormat.B8G8R8X8UNORM: + case DXGIFormat.R10G10B10XRBIASA2UNORM: + case DXGIFormat.B8G8R8A8TYPELESS: + case DXGIFormat.B8G8R8A8UNORMSRGB: + case DXGIFormat.B8G8R8X8TYPELESS: + case DXGIFormat.B8G8R8X8UNORMSRGB: + case DXGIFormat.AYUV: + case DXGIFormat.Y410: + case DXGIFormat.YUY2: + return 32; + case DXGIFormat.P010: + case DXGIFormat.P016: + return 24; + case DXGIFormat.R8G8TYPELESS: + case DXGIFormat.R8G8UNORM: + case DXGIFormat.R8G8UINT: + case DXGIFormat.R8G8SNORM: + case DXGIFormat.R8G8SINT: + case DXGIFormat.R16TYPELESS: + case DXGIFormat.R16FLOAT: + case DXGIFormat.D16UNORM: + case DXGIFormat.R16UNORM: + case DXGIFormat.R16UINT: + case DXGIFormat.R16SNORM: + case DXGIFormat.R16SINT: + case DXGIFormat.B5G6R5UNORM: + case DXGIFormat.B5G5R5A1UNORM: + case DXGIFormat.A8P8: + case DXGIFormat.B4G4R4A4UNORM: + return 16; + case DXGIFormat.NV12: + case DXGIFormat.OPAQUE420: + case DXGIFormat.NV11: + return 12; + case DXGIFormat.R8TYPELESS: + case DXGIFormat.R8UNORM: + case DXGIFormat.R8UINT: + case DXGIFormat.R8SNORM: + case DXGIFormat.R8SINT: + case DXGIFormat.A8UNORM: + case DXGIFormat.BC2TYPELESS: + case DXGIFormat.BC2UNORM: + case DXGIFormat.BC2UNORMSRGB: + case DXGIFormat.BC3TYPELESS: + case DXGIFormat.BC3UNORM: + case DXGIFormat.BC3UNORMSRGB: + case DXGIFormat.BC5TYPELESS: + case DXGIFormat.BC5UNORM: + case DXGIFormat.BC5SNORM: + case DXGIFormat.BC6HTYPELESS: + case DXGIFormat.BC6HUF16: + case DXGIFormat.BC6HSF16: + case DXGIFormat.BC7TYPELESS: + case DXGIFormat.BC7UNORM: + case DXGIFormat.BC7UNORMSRGB: + case DXGIFormat.AI44: + case DXGIFormat.IA44: + case DXGIFormat.P8: + return 8; + case DXGIFormat.R1UNORM: + return 1; + case DXGIFormat.BC1TYPELESS: + case DXGIFormat.BC1UNORM: + case DXGIFormat.BC1UNORMSRGB: + case DXGIFormat.BC4TYPELESS: + case DXGIFormat.BC4UNORM: + case DXGIFormat.BC4SNORM: + return 4; + default: + return 0; + } + } + + /// + /// Computes Row and Slice Pitch + /// + private static void ComputePitch(DXGIFormat format, uint width, uint height, out ulong rowPitch, out ulong slicePitch, CPFLAGS flags) + { + switch (format) + { + case DXGIFormat.BC1TYPELESS: + case DXGIFormat.BC1UNORM: + case DXGIFormat.BC1UNORMSRGB: + case DXGIFormat.BC4TYPELESS: + case DXGIFormat.BC4UNORM: + case DXGIFormat.BC4SNORM: + { + if (flags.HasFlag(CPFLAGS.BADDXTNTAILS)) + { + var nbw = width >> 2; + var nbh = height >> 2; + rowPitch = Clamp((ulong) nbw * 8u, ulong.MaxValue, 1u); + slicePitch = Clamp(rowPitch * nbh, ulong.MaxValue, 1u); + } + else + { + var nbw = Clamp(((ulong) width + 3u) / 4u, ulong.MaxValue, 1u); + var nbh = Clamp(((ulong) height + 3u) / 4u, ulong.MaxValue, 1u); + rowPitch = nbw * 8u; + slicePitch = rowPitch * nbh; + } + } + break; + case DXGIFormat.BC2TYPELESS: + case DXGIFormat.BC2UNORM: + case DXGIFormat.BC2UNORMSRGB: + case DXGIFormat.BC3TYPELESS: + case DXGIFormat.BC3UNORM: + case DXGIFormat.BC3UNORMSRGB: + case DXGIFormat.BC5TYPELESS: + case DXGIFormat.BC5UNORM: + case DXGIFormat.BC5SNORM: + case DXGIFormat.BC6HTYPELESS: + case DXGIFormat.BC6HUF16: + case DXGIFormat.BC6HSF16: + case DXGIFormat.BC7TYPELESS: + case DXGIFormat.BC7UNORM: + case DXGIFormat.BC7UNORMSRGB: + { + if (flags.HasFlag(CPFLAGS.BADDXTNTAILS)) + { + var nbw = width >> 2; + var nbh = height >> 2; + rowPitch = Clamp((ulong) nbw * 16u, ulong.MaxValue, 1u); + slicePitch = Clamp(rowPitch * nbh, ulong.MaxValue, 1u); + } + else + { + var nbw = Clamp((width + 3) / 4, ulong.MaxValue, 1u); + var nbh = Clamp((height + 3) / 4, ulong.MaxValue, 1u); + rowPitch = nbw * 16u; + slicePitch = rowPitch * nbh; + } + } + break; + case DXGIFormat.R8G8B8G8UNORM: + case DXGIFormat.G8R8G8B8UNORM: + case DXGIFormat.YUY2: + rowPitch = ((width + 1) >> 1) * 4; + slicePitch = rowPitch * height; + break; + case DXGIFormat.Y210: + case DXGIFormat.Y216: + rowPitch = ((width + 1) >> 1) * 8; + slicePitch = rowPitch * height; + break; + + case DXGIFormat.NV12: + case DXGIFormat.OPAQUE420: + rowPitch = ((width + 1) >> 1) * 2; + slicePitch = rowPitch * (height + ((height + 1) >> 1)); + break; + + case DXGIFormat.P010: + case DXGIFormat.P016: + rowPitch = ((width + 1) >> 1) * 4; + slicePitch = rowPitch * (height + ((height + 1) >> 1)); + break; + case DXGIFormat.NV11: + rowPitch = ((width + 3) >> 2) * 4; + slicePitch = rowPitch * height * 2; + break; + default: + { + ulong bpp; + + if (flags.HasFlag(CPFLAGS.BPP24)) + bpp = 24; + else if (flags.HasFlag(CPFLAGS.BPP16)) + bpp = 16; + else if (flags.HasFlag(CPFLAGS.BPP8)) + bpp = 8; + else + bpp = BitsPerPixel(format); + + if (flags.HasFlag(CPFLAGS.LEGACYDWORD | CPFLAGS.PARAGRAPH | CPFLAGS.YMM | CPFLAGS.ZMM | CPFLAGS.PAGE4K)) + { + if (flags.HasFlag(CPFLAGS.PAGE4K)) + { + rowPitch = (width * bpp + 32767u) / 32768u * 4096u; + slicePitch = rowPitch * height; + } + else if (flags.HasFlag(CPFLAGS.ZMM)) + { + rowPitch = (width * bpp + 511u) / 512u * 64u; + slicePitch = rowPitch * height; + } + else if (flags.HasFlag(CPFLAGS.YMM)) + { + rowPitch = (width * bpp + 255u) / 256u * 32u; + slicePitch = rowPitch * height; + } + else if (flags.HasFlag(CPFLAGS.PARAGRAPH)) + { + rowPitch = (width * bpp + 127u) / 128u * 16u; + slicePitch = rowPitch * height; + } + else // DWORD alignment + { + // Special computation for some incorrectly created DDS files based on + // legacy DirectDraw assumptions about pitch alignment + rowPitch = (width * bpp + 31u) / 32u * sizeof(uint); + slicePitch = rowPitch * height; + } + } + else + { + // Default byte alignment + rowPitch = (width * bpp + 7u) / 8u; + slicePitch = rowPitch * height; + } + } + break; + } + } + + /// + /// Checks is the given format compressed + /// + private static bool IsCompressed(DXGIFormat format) + { + switch (format) + { + case DXGIFormat.BC1TYPELESS: + case DXGIFormat.BC1UNORM: + case DXGIFormat.BC1UNORMSRGB: + case DXGIFormat.BC2TYPELESS: + case DXGIFormat.BC2UNORM: + case DXGIFormat.BC2UNORMSRGB: + case DXGIFormat.BC3TYPELESS: + case DXGIFormat.BC3UNORM: + case DXGIFormat.BC3UNORMSRGB: + case DXGIFormat.BC4TYPELESS: + case DXGIFormat.BC4UNORM: + case DXGIFormat.BC4SNORM: + case DXGIFormat.BC5TYPELESS: + case DXGIFormat.BC5UNORM: + case DXGIFormat.BC5SNORM: + case DXGIFormat.BC6HTYPELESS: + case DXGIFormat.BC6HUF16: + case DXGIFormat.BC6HSF16: + case DXGIFormat.BC7TYPELESS: + case DXGIFormat.BC7UNORM: + case DXGIFormat.BC7UNORMSRGB: + return true; + + default: + return false; + } + } + #endregion + + #region MainMethods + /// + /// Encodes the DDS Header and if DX10, the DX10 Header + /// + /// DDS Header + /// DX10 Header + /// Resulting DDS File Header in bytes + public static byte[] EncodeDDSHeader(DDSHeader header, DX10Header dx10Header) + { + // Create stream + using (var output = new BinaryWriter(new MemoryStream())) + { + // Write DDS Magic + output.Write(DDSHeader.DDSMagic); + // Write Header + output.Write(StructToBytes(header)); + // Check for DX10 Header + if (header.PixelFormat.FourCC == PixelFormats.DX10.FourCC) + // Write Header + output.Write(StructToBytes(dx10Header)); + // Done + return ((MemoryStream)(output.BaseStream)).ToArray(); + } + } + + /// + /// Generates DirectXTex Meta Data + /// + /// Image Width + /// Image Height + /// Number of Mip Maps + /// Compression Format + /// Whether or not this is a cube map + /// Resulting TexMetaData Object + public static TexMetadata GenerateMetadata(int width, int height, int mipMapLevels, DXGIFormat format, bool isCubeMap) + { + // Create Texture MetaData + return new TexMetadata( + width, + height, + 1, + isCubeMap ? 6 : 1, + mipMapLevels, + isCubeMap ? TexMiscFlags.TEXTURECUBE : 0, + 0, + format, + TexDimension.TEXTURE2D + ); + } + + private static readonly uint[] _elevenUInts = Enumerable.Repeat((uint)0, 11).ToArray(); + + /// + /// Generates a DDS Header, and if requires, a DX10 Header + /// + /// Meta Data + /// Flags + /// DDS Header Output + /// DX10 Header Output + public static void GenerateDDSHeader(TexMetadata metaData, DDSFlags flags, out DDSHeader header, out DX10Header dx10Header) + { + // Check array size + if (metaData.ArraySize > 1) + // Check if we have an array and whether we're cube maps/non-2D + if (metaData.ArraySize != 6 || metaData.Dimension != TexDimension.TEXTURE2D || !metaData.IsCubeMap()) + // Texture1D arrays, Texture2D arrays, and Cubemap arrays must be stored using 'DX10' extended header + flags |= DDSFlags.FORCEDX10EXT; + + // Check for DX10 Ext + if (flags.HasFlag(DDSFlags.FORCEDX10EXTMISC2)) + flags |= DDSFlags.FORCEDX10EXT; + + // Create DDS Header + header = new DDSHeader + { + // Set Data + Size = (uint) Marshal.SizeOf(), + Flags = DDSHeader.HeaderFlags.TEXTURE, + Height = 0, + Width = 0, + PitchOrLinearSize = 0, + Depth = 0, + MipMapCount = 0, + Reserved1 = _elevenUInts, + Caps = (uint) DDSHeader.SurfaceFlags.TEXTURE, + Caps2 = 0, + Caps3 = 0, + Caps4 = 0, + Reserved2 = 0, + PixelFormat = new DDSHeader.DDSPixelFormat(), + + }; + + // Create DX10 Header + dx10Header = new DX10Header + { + Format = 0, + ResourceDimension = (TexDimension) 0, + MiscFlag = (TexMiscFlags) 0, + ArraySize = 0, + MiscFlags2 = 0, + }; + + // Switch format + header.PixelFormat = GetPixelFormat(metaData); + // Check for mips + if (metaData.MipLevels > 0) + { + // Set flag + header.Flags |= DDSHeader.HeaderFlags.MIPMAP; + // Check size + if (metaData.MipLevels > UInt16.MaxValue) + throw new ArgumentException(String.Format("Too many mipmaps: {0}. Max: {1}", metaData.MipLevels, UInt16.MaxValue)); + // Set + header.MipMapCount = (uint)metaData.MipLevels; + // Check count + if (header.MipMapCount > 1) + header.Caps |= (uint)DDSHeader.SurfaceFlags.MIPMAP; + } + + // Switch Dimension + switch (metaData.Dimension) + { + case TexDimension.TEXTURE1D: + { + // Check size + if (metaData.Width > Int32.MaxValue) + throw new ArgumentException(String.Format("Image Width too large: {0}. Max: {1}", metaData.Width, Int32.MaxValue)); + // Set + header.Width = (uint)metaData.Width; + header.Height = header.Depth = 1; + // Check size + break; + } + case TexDimension.TEXTURE2D: + { + // Check size + if (metaData.Width > Int32.MaxValue || metaData.Height > Int32.MaxValue) + throw new ArgumentException(String.Format("Image Width and/or Height too large: {0}x{1}. Max: {2}", + metaData.Width, + metaData.Height, + Int32.MaxValue)); + // Set + header.Width = (uint)metaData.Width; + header.Height = (uint)metaData.Height; + header.Depth = 1; + // Check size + break; + } + case TexDimension.TEXTURE3D: + { + // Check size + if (metaData.Width > Int32.MaxValue || metaData.Height > Int32.MaxValue) + throw new ArgumentException(String.Format("Image Width and/or Height too large: {0}x{1}. Max: {2}", + metaData.Width, + metaData.Height, + Int32.MaxValue)); + // Check size + if (metaData.Depth > UInt16.MaxValue) + throw new ArgumentException(String.Format("Image Depth too large: {0}. Max: {1}", metaData.Depth, UInt16.MaxValue)); + // Set + header.Flags |= DDSHeader.HeaderFlags.VOLUME; + header.Caps2 |= 0x00200000; + header.Width = (uint)metaData.Width; + header.Height = (uint)metaData.Height; + header.Depth = (uint)metaData.Depth; + // Check size + break; + } + default: + throw new ArgumentException("Invalid Texture Dimension."); + + } + // Calculate the Pitch + ComputePitch(metaData.Format, (uint) metaData.Width, (uint) metaData.Height, out var rowPitch, out var slicePitch, CPFLAGS.NONE); + // Validate results + if (slicePitch > UInt32.MaxValue || rowPitch > UInt32.MaxValue) + throw new ArgumentException("Failed to calculate row and/or slice pitch, values returned were too large"); + // Check is it compressed + if (IsCompressed(metaData.Format)) + { + header.Flags |= DDSHeader.HeaderFlags.LINEARSIZE; + header.PitchOrLinearSize = (uint)slicePitch; + } + else + { + header.Flags |= DDSHeader.HeaderFlags.PITCH; + header.PitchOrLinearSize = (uint)rowPitch; + } + + // Check for do we need to create the DX10 Header + if (HasDx10Header(header.PixelFormat)) + { + // Check size + if (metaData.ArraySize > UInt16.MaxValue) + throw new ArgumentException(String.Format("Array Size too large: {0}. Max: {1}", metaData.ArraySize, UInt16.MaxValue)); + // Set Pixel format + header.PixelFormat = PixelFormats.DX10; + // Set Data + dx10Header.Format = metaData.Format; + dx10Header.ResourceDimension = metaData.Dimension; + dx10Header.MiscFlag = metaData.MiscFlags & ~TexMiscFlags.TEXTURECUBE; + dx10Header.ArraySize = (uint)metaData.ArraySize; + // Check for Cube Maps + if (metaData.MiscFlags.HasFlag(TexMiscFlags.TEXTURECUBE)) + { + // Check array size, must be a multiple of 6 for cube maps + if ((metaData.ArraySize % 6) != 0) + throw new ArgumentException("Array size must be a multiple of 6"); + // Set Flag + dx10Header.MiscFlag |= TexMiscFlags.TEXTURECUBE; + dx10Header.ArraySize /= 6; + } + // Check for mist flags + if (flags.HasFlag(DDSFlags.FORCEDX10EXTMISC2)) + // This was formerly 'reserved'. D3DX10 and D3DX11 will fail if this value is anything other than 0 + dx10Header.MiscFlags2 = (uint)metaData.MiscFlags2; + } + } + + public static bool HasDx10Header(DDSHeader.DDSPixelFormat pixelFormat) => pixelFormat.Size == 0; + + public static DDSHeader.DDSPixelFormat GetPixelFormat(TexMetadata metaData) => + metaData.Format switch + { + DXGIFormat.R8G8B8A8UNORM => PixelFormats.A8B8G8R8, + DXGIFormat.R16G16UNORM => PixelFormats.G16R16, + DXGIFormat.R8G8UNORM => PixelFormats.A8L8, + DXGIFormat.R16UNORM => PixelFormats.L16, + DXGIFormat.R8UNORM => PixelFormats.L8, + DXGIFormat.A8UNORM => PixelFormats.A8, + DXGIFormat.R8G8B8G8UNORM => PixelFormats.R8G8B8G8, + DXGIFormat.G8R8G8B8UNORM => PixelFormats.G8R8G8B8, + DXGIFormat.BC1UNORM => PixelFormats.DXT1, + DXGIFormat.BC2UNORM => metaData.IsPMAlpha() ? (PixelFormats.DXT2) : (PixelFormats.DXT3), + DXGIFormat.BC3UNORM => metaData.IsPMAlpha() ? (PixelFormats.DXT4) : (PixelFormats.DXT5), + DXGIFormat.BC4UNORM => PixelFormats.BC4UNORM, + DXGIFormat.BC4SNORM => PixelFormats.BC4SNORM, + DXGIFormat.BC5UNORM => PixelFormats.BC5UNORM, + DXGIFormat.BC5SNORM => PixelFormats.BC5SNORM, + DXGIFormat.B5G6R5UNORM => PixelFormats.R5G6B5, + DXGIFormat.B5G5R5A1UNORM => PixelFormats.A1R5G5B5, + DXGIFormat.R8G8SNORM => PixelFormats.V8U8, + DXGIFormat.R8G8B8A8SNORM => PixelFormats.Q8W8V8U8, + DXGIFormat.R16G16SNORM => PixelFormats.V16U16, + DXGIFormat.B8G8R8A8UNORM => PixelFormats.A8R8G8B8, + DXGIFormat.B8G8R8X8UNORM => PixelFormats.X8R8G8B8, + DXGIFormat.B4G4R4A4UNORM => PixelFormats.A4R4G4B4, + DXGIFormat.YUY2 => PixelFormats.YUY2, + // Legacy D3DX formats using D3DFMT enum value as FourCC + DXGIFormat.R32G32B32A32FLOAT => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 116 /* D3DFMTA32B32G32R32F */ }, + DXGIFormat.R16G16B16A16FLOAT => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 113 /* D3DFMTA16B16G16R16F */ }, + DXGIFormat.R16G16B16A16UNORM => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 36 /* D3DFMTA16B16G16R16 */ }, + DXGIFormat.R16G16B16A16SNORM => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 110 /* D3DFMTQ16W16V16U16 */ }, + DXGIFormat.R32G32FLOAT => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 115 /* D3DFMTG32R32F */ }, + DXGIFormat.R16G16FLOAT => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 112 /* D3DFMTG16R16F */ }, + DXGIFormat.R32FLOAT => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 114 /* D3DFMTR32F */ }, + DXGIFormat.R16FLOAT => new DDSHeader.DDSPixelFormat { Flags = PixelFormats.DDSFOURCC, FourCC = 111 /* D3DFMTR16F */ }, + _ => new DDSHeader.DDSPixelFormat() + }; + + #endregion + } +} diff --git a/Wabbajack.Compression.BSA/FO4Archive/ChunkBuilder.cs b/Wabbajack.Compression.BSA/FO4Archive/ChunkBuilder.cs deleted file mode 100644 index 12f930e3..00000000 --- a/Wabbajack.Compression.BSA/FO4Archive/ChunkBuilder.cs +++ /dev/null @@ -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 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(); - } -} \ No newline at end of file diff --git a/Wabbajack.Compression.BSA/FO4Archive/DX10Entry.cs b/Wabbajack.Compression.BSA/FO4Archive/DX10Entry.cs deleted file mode 100644 index a793f4f4..00000000 --- a/Wabbajack.Compression.BSA/FO4Archive/DX10Entry.cs +++ /dev/null @@ -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 _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 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; - } - } -} \ No newline at end of file diff --git a/Wabbajack.DTOs/ModList/BSA/ArchiveStates/BA2State.cs b/Wabbajack.DTOs/ModList/BSA/ArchiveStates/BA2State.cs index dd94cadc..165b07f4 100644 --- a/Wabbajack.DTOs/ModList/BSA/ArchiveStates/BA2State.cs +++ b/Wabbajack.DTOs/ModList/BSA/ArchiveStates/BA2State.cs @@ -17,4 +17,7 @@ public class BA2State : IArchive public BA2EntryType Type { get; set; } public string HeaderMagic { get; set; } public uint Version { get; set; } + public uint Unknown1 { get; set; } + public uint Unknown2 { get; set; } + public uint Compression { get; set; } } \ No newline at end of file