Skip to content

Commit

Permalink
texture preview, cryxml preview fix
Browse files Browse the repository at this point in the history
  • Loading branch information
diogotr7 committed Jan 14, 2025
1 parent b9e846f commit 7a8a2c2
Show file tree
Hide file tree
Showing 14 changed files with 337 additions and 119 deletions.
12 changes: 8 additions & 4 deletions src/StarBreaker.CryXmlB/CryXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,14 @@ private static string GetString(Span<byte> data, int offset)

public override string ToString()
{
var sb = new StringBuilder();
using var writer = XmlWriter.Create(sb, new XmlWriterSettings { Indent = true });
WriteTo(writer);
return sb.ToString();
using var sw = new StringWriter();

using (var writer = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true }))
{
WriteTo(writer);
}

return sw.ToString();
}

public void Save(string entry)
Expand Down
40 changes: 37 additions & 3 deletions src/StarBreaker.Dds/DXTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,40 @@ public struct DdsPixelFormat
public uint ABitMask;
}

[Flags]
public enum DDSHEADER_FLAGS : uint
{
CAPS = 1,
HEIGHT = 2,
WIDTH = 4,
PITCH = 8,
PIXELFORMAT = 0x1000, // 0x00001000
MIPMAPCOUNT = 0x20000, // 0x00020000
LINEARSIZE = 0x80000, // 0x00080000
DEPTH = 0x800000, // 0x00800000
}

[Flags]
public enum DDSHEADER_CAPS : uint
{
COMPLEX = 8,
MIPMAP = 0x400000,
TEXTURE = 0x1000,
}

[Flags]
public enum DDSHEADER_CAPS2 : uint
{
CUBEMAP = 0x200,
CUBEMAP_POSITIVEX = 0x400,
CUBEMAP_NEGATIVEX = 0x800,
CUBEMAP_POSITIVEY = 0x1000,
CUBEMAP_NEGATIVEY = 0x2000,
CUBEMAP_POSITIVEZ = 0x4000,
CUBEMAP_NEGATIVEZ = 0x8000,
VOLUME = 0x200000,
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public record struct DdsHeader
{
Expand All @@ -63,16 +97,16 @@ public struct DdsReserved
}

public uint Size;
public uint Flags;
public DDSHEADER_FLAGS Flags;
public uint Height;
public uint Width;
public uint PitchOrLinearSize;
public uint Depth;
public uint MipMapCount;
public DdsReserved Reserved1;
public DdsPixelFormat PixelFormat;
public uint Caps;
public uint Caps2;
public DDSHEADER_CAPS Caps;
public DDSHEADER_CAPS2 Caps2;
public uint Caps3;
public uint Caps4;
public uint Reserved2;
Expand Down
91 changes: 83 additions & 8 deletions src/StarBreaker.Dds/DdsFile.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
using System.Buffers;
using System.Runtime.InteropServices;
using DirectXTexNet;
using StarBreaker.Common;
using StarBreaker.FileSystem;

namespace StarBreaker.Dds;

public static class DdsFile
{
public static ReadOnlySpan<byte> Magic => "DDS "u8;

public static Stream MergeToStream(string fullPath)
public static MemoryStream MergeToStream(string fullPath, IFileSystem? fileSystem = null)
{
fileSystem ??= RealFileSystem.Instance;
if (!fullPath.EndsWith(".dds", StringComparison.OrdinalIgnoreCase) && !fullPath.EndsWith(".dds.a", StringComparison.OrdinalIgnoreCase))
throw new ArgumentException("File must be a DDS file");

var containingFolder = Path.GetDirectoryName(fullPath)!;
var files = Directory.GetFiles(containingFolder, Path.GetFileName(fullPath) + ".*").Where(p => char.IsDigit(p[^1]));
var files = fileSystem.GetFiles(containingFolder, Path.GetFileName(fullPath) + ".*")
.Where(p => char.IsDigit(p[^1]))
.ToArray();

var mainFile = new BinaryReader(File.OpenRead(fullPath));
if (files.Length == 0)
return new MemoryStream(fileSystem.ReadAllBytes(fullPath));

var mainFile = new BinaryReader(fileSystem.OpenRead(fullPath));

var magic = mainFile.ReadBytes(4);
if (!Magic.SequenceEqual(magic))
Expand Down Expand Up @@ -47,18 +55,21 @@ public static Stream MergeToStream(string fullPath)

//order by the number at the end. e.g. 8 is the largest, 0 is the smallest.
// we want to write the largest mipmap first.
var mipMapFiles = files.OrderDescending().Select(File.ReadAllBytes).ToArray();
var mipMapFiles = files.OrderDescending().Select(fileSystem.ReadAllBytes).ToArray();

var largest = mipMapSizes[0];
var largestByteCount = GetMipmapSize(largest.Item1, largest.Item2, header.PixelFormat, readDx10Header ? headerDx10 : null);

//DDS_SURFACE_FLAGS_CUBEMAP
var faces = (header.Caps & 0x8) != 0 ? 6 : 1;
var faces = largestByteCount == mipMapFiles[0].Length ? 1 : 6;
// var faces = headerDx10.ResourceDimension == ResourceDimension.D3D10_RESOURCE_DIMENSION_TEXTURE3D ? 6 : 1;
var smallOffset = 0;
for (var cubeFace = 0; cubeFace < faces; cubeFace++)
{
for (var mipMap = 0; mipMap < mipMapSizes.Length; mipMap++)
{
var mipMapSize = mipMapSizes[mipMap];
var mipMapByteCount = GetMipmapSize(mipMapSize.Item1, mipMapSize.Item2);
var mipMapByteCount = GetMipmapSize(mipMapSize.Item1, mipMapSize.Item2, header.PixelFormat, readDx10Header ? headerDx10 : null);

if (mipMap < mipMapFiles.Length)
{
Expand Down Expand Up @@ -92,9 +103,22 @@ private static (int, int)[] MipMapSizes(DdsHeader header)
return mipMapSizes;
}

private static int GetMipmapSize(int width, int height)
private static int GetMipmapSize(int width, int height, DdsPixelFormat format, DdsHeaderDxt10? dx10Header = null)
{
return Math.Max(1, (width + 3) / 4) * Math.Max(1, (height + 3) / 4) * 16;
//TODO: is this even correct?
var blockSize = format.FourCC switch
{
CompressionAlgorithm.D3DFMT_DXT1 => 8,
CompressionAlgorithm.BC4S => 8,
CompressionAlgorithm.BC4U => 8,
_ => 16
};
if (dx10Header?.DxgiFormat is DXGI_FORMAT.BC4_SNORM or DXGI_FORMAT.BC4_UNORM or DXGI_FORMAT.BC4_TYPELESS)
{
blockSize = 8;
}

return Math.Max(1, (width + 3) / 4) * Math.Max(1, (height + 3) / 4) * blockSize;
}

public static void MergeToFile(string ddsFullPath, string pngFullPath)
Expand Down Expand Up @@ -158,6 +182,57 @@ public static void ConvertToPng(string ddsFullPath, string pngFullPath)
{
var path = Path.Combine(Path.GetDirectoryName(pngFullPath)!, $"{pathWithoutExtension}_{i}.jpg");
tex.SaveToWICFile(i, WIC_FLAGS.NONE, TexHelper.Instance.GetWICCodec(WICCodecs.JPEG), path);
var bytes = tex.SaveToWICMemory(i, WIC_FLAGS.NONE, TexHelper.Instance.GetWICCodec(WICCodecs.PNG));
}
}

public static unsafe MemoryStream ConvertToPng(byte[] dds)
{
ScratchImage? tex = null;
fixed (byte* ptr = dds)
{
tex = TexHelper.Instance.LoadFromDDSMemory((IntPtr)ptr, dds.Length, DDS_FLAGS.NONE);
}

var meta = tex.GetMetadata();

// if (!TexHelper.Instance.IsPlanar(meta.Format))
// {
// var planar = tex.ConvertToSinglePlane();
// tex.Dispose();
// tex = planar;
// meta = tex.GetMetadata();
// }

if (TexHelper.Instance.IsCompressed(meta.Format))
{
var decompressed = tex.Decompress(DXGI_FORMAT.UNKNOWN);
tex.Dispose();
tex = decompressed;
meta = tex.GetMetadata();
}

if (meta.Format != DXGI_FORMAT.R8G8B8A8_UNORM)
{
var converted = tex.Convert(0, DXGI_FORMAT.R8G8B8A8_UNORM, TEX_FILTER_FLAGS.DEFAULT, 0.5f);
tex.Dispose();
tex = converted;
meta = tex.GetMetadata();
}

var count = tex.GetImageCount();

if (count == 0)
throw new InvalidOperationException("No images found in DDS file");
var stream = new MemoryStream();

using var bytes = tex.SaveToWICMemory(0, WIC_FLAGS.NONE, TexHelper.Instance.GetWICCodec(WICCodecs.PNG));

//TODO: convert cubemap dds to a x-cross
bytes.CopyTo(stream);

stream.Position = 0;

return stream;
}
}
4 changes: 4 additions & 0 deletions src/StarBreaker.Dds/StarBreaker.Dds.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DirectXTexNet" Version="1.0.7" />
<PackageReference Include="SkiaSharp" Version="3.116.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StarBreaker.Common\StarBreaker.Common.csproj" />
<ProjectReference Include="..\StarBreaker.FileSystem\StarBreaker.FileSystem.csproj" />
</ItemGroup>
</Project>
12 changes: 11 additions & 1 deletion src/StarBreaker.FileSystem/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ public interface IFileSystem
/// <param name="path">The path to the directory.</param>
/// <returns>The files in the directory.</returns>
IEnumerable<string> GetFiles(string path);

/// <summary>
/// Get the files in a directory that match a search pattern.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <param name="searchPattern">The search pattern.</param>
/// <returns>The files in the directory that match the search pattern.</returns>
IEnumerable<string> GetFiles(string path, string searchPattern);

/// <summary>
/// Get the directories in a directory.
/// </summary>
/// <param name="path">The path to the directory.</param>
/// <returns>The directories in the directory.</returns>
IEnumerable<string> GetDirectories(string path);

/// <summary>
/// Check if a file exists.
/// </summary>
Expand All @@ -32,4 +40,6 @@ public interface IFileSystem
/// <param name="path">The path to the file.</param>
/// <returns>A stream for reading the file.</returns>
Stream OpenRead(string path);

byte[] ReadAllBytes(string path);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
namespace StarBreaker.FileSystem;

public sealed class FileSystem : IFileSystem
public sealed class RealFileSystem : IFileSystem
{
public static readonly FileSystem Instance = new();
public static readonly RealFileSystem Instance = new();

public IEnumerable<string> GetFiles(string path) => Directory.EnumerateFiles(path);

public IEnumerable<string> GetFiles(string path, string searchPattern) => Directory.EnumerateFiles(path, searchPattern);

public IEnumerable<string> GetDirectories(string path) => Directory.EnumerateDirectories(path);

public bool FileExists(string path) => File.Exists(path) || Directory.Exists(path);

public Stream OpenRead(string path) => File.OpenRead(path);

public byte[] ReadAllBytes(string path) => File.ReadAllBytes(path);
}
21 changes: 21 additions & 0 deletions src/StarBreaker.FileSystem/ZipFileSystem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO.Compression;
using System.IO.Enumeration;

namespace StarBreaker.FileSystem;

Expand All @@ -18,6 +19,13 @@ public IEnumerable<string> GetFiles(string path)
.Select(entry => entry.FullName);
}

public IEnumerable<string> GetFiles(string path, string searchPattern)
{
return _archive.Entries
.Where(entry => entry.FullName.StartsWith(path) && !entry.FullName.EndsWith('/') && FileSystemName.MatchesSimpleExpression(entry.Name, searchPattern))
.Select(entry => entry.FullName);
}

public IEnumerable<string> GetDirectories(string path)
{
return _archive.Entries
Expand All @@ -36,4 +44,17 @@ public Stream OpenRead(string path)

return entry.Open();
}

public byte[] ReadAllBytes(string path)
{
var entry = _archive.GetEntry(path);

if (entry == null)
throw new FileNotFoundException("File not found in archive.", path);

using var stream = entry.Open();
using var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
Loading

0 comments on commit 7a8a2c2

Please sign in to comment.