added VirtualTexture "support" + Oodle compression support

This commit is contained in:
iAmAsval
2020-08-18 17:46:52 +02:00
parent 0a99506270
commit 358b03de75
16 changed files with 246 additions and 15 deletions

View File

@@ -85,10 +85,10 @@ namespace FModel.Creator.Bases
{ } // ^^^^ will return false if image not found, if so, we try to get the normal icon
else if (export.GetExport<ObjectProperty>("HeroDefinition", "WeaponDefinition") is ObjectProperty itemDef)
LargeSmallImage.GetPreviewImage(this, itemDef, assetName);
else if (export.GetExport<ObjectProperty>("SmallPreviewImage") is ObjectProperty smallPreviewImage)
this.IconImage = Utils.GetObjectTexture(smallPreviewImage);
else if (export.GetExport<SoftObjectProperty>("LargePreviewImage", "SmallPreviewImage", "ItemDisplayAsset") is SoftObjectProperty previewImage)
LargeSmallImage.GetPreviewImage(this, previewImage);
else if (export.GetExport<ObjectProperty>("SmallPreviewImage") is ObjectProperty smallPreviewImage)
this.IconImage = Utils.GetObjectTexture(smallPreviewImage);
else if (export.GetExport<StructProperty>("IconBrush") is StructProperty iconBrush) // abilities
LargeSmallImage.GetPreviewImage(this, iconBrush);

View File

@@ -250,7 +250,7 @@ namespace FModel.Creator
Process.Start(new ProcessStartInfo
{
FileName = string.Format(
"http://valorant.dyn.riotcdn.net/x/videos/release-01.04/{0}_default_universal.mp4",
"http://valorant.dyn.riotcdn.net/x/videos/release-01.05/{0}_default_universal.mp4",
$"{uuid.A:x8}-{uuid.B >> 16:x4}-{uuid.B & 0xFFFF:x4}-{uuid.C >> 16:x4}-{uuid.C & 0xFFFF:x4}{uuid.D:x8}"),
UseShellExecute = true
});

View File

@@ -46,6 +46,7 @@ namespace FModel.Creator.Rarities
rarity = EFortRarity.Transcendent;
break;
case "EFortRarity::Unattainable":
case "EFortRarity::Badass":
rarity = EFortRarity.Unattainable;
break;
}

View File

@@ -16,8 +16,25 @@ namespace PakReader.Parsers.Class
{
if (image == null)
{
var mip = PlatformDatas[0].Mips[0];
image = TextureDecoder.DecodeImage(mip.BulkData.Data, mip.SizeX, mip.SizeY, mip.SizeZ, PlatformDatas[0].PixelFormat);
int sizeX = 0;
int sizeY = 0;
int sizeZ = 1;
List<byte> data = new List<byte>();
if (PlatformDatas[0].Mips.Length > 0)
{
sizeX = PlatformDatas[0].Mips[0].SizeX;
sizeY = PlatformDatas[0].Mips[0].SizeY;
sizeZ = PlatformDatas[0].Mips[0].SizeZ;
data.AddRange(PlatformDatas[0].Mips[0].BulkData.Data);
}
//if (PlatformDatas[0].bIsVirtual)
//{
// sizeX = PlatformDatas[0].VTData.Width;
// sizeY = PlatformDatas[0].VTData.Height;
//}
image = TextureDecoder.DecodeImage(data.ToArray(), sizeX, sizeY, sizeZ, PlatformDatas[0].PixelFormat);
}
return image;
}

View File

@@ -0,0 +1,14 @@
namespace PakReader.Parsers.Objects
{
public enum EVirtualTextureCodec : byte
{
Black, //Special case codec, always outputs black pixels 0,0,0,0
OpaqueBlack, //Special case codec, always outputs opaque black pixels 0,0,0,255
White, //Special case codec, always outputs white pixels 255,255,255,255
Flat, //Special case codec, always outputs 128,125,255,255 (flat normal map)
RawGPU, //Uncompressed data in an GPU-ready format (e.g R8G8B8A8, BC7, ASTC, ...)
ZippedGPU, //Same as RawGPU but with the data zipped
Crunch, //Use the Crunch library to compress data
Max, // Add new codecs before this entry
}
}

View File

@@ -189,6 +189,7 @@ namespace PakReader.Parsers.Objects
{
"Zlib" => new ZlibStream(blockMs, CompressionMode.Decompress),
"Gzip" => new GZipStream(blockMs, CompressionMode.Decompress),
"Oodle" => new OodleStream(blockBbuffer, uncompressedSize),
_ => throw new NotImplementedException($"Decompression not yet implemented ({compressionMethod})")
};

View File

@@ -80,7 +80,7 @@ namespace PakReader.Parsers.Objects
var MethodList = new List<string>(4);
for (int i = 0; i < 4; i++)
{
if (Methods[i*COMPRESSION_METHOD_NAME_LEN] != 0)
if (Methods[i * COMPRESSION_METHOD_NAME_LEN] != 0)
{
MethodList.Add(Encoding.ASCII.GetString(Methods, i * COMPRESSION_METHOD_NAME_LEN, COMPRESSION_METHOD_NAME_LEN).TrimEnd('\0'));
}

View File

@@ -10,6 +10,8 @@ namespace PakReader.Parsers.Objects
public readonly int NumSlices;
public readonly EPixelFormat PixelFormat;
public readonly FTexture2DMipMap[] Mips;
public readonly FVirtualTextureBuiltData VTData;
public readonly bool bIsVirtual;
internal FTexturePlatformData(PackageReader reader, Stream ubulk, long bulkOffset)
{
@@ -17,6 +19,8 @@ namespace PakReader.Parsers.Objects
SizeY = reader.ReadInt32();
NumSlices = reader.ReadInt32();
PixelFormat = Enum.Parse<EPixelFormat>(reader.ReadFString());
VTData = default;
bIsVirtual = false;
var FirstMipToSerialize = reader.ReadInt32();
FirstMipToSerialize = 0; // what: https://github.com/EpicGames/UnrealEngine/blob/4.24/Engine/Source/Runtime/Engine/Private/TextureDerivedData.cpp#L1316
@@ -25,7 +29,11 @@ namespace PakReader.Parsers.Objects
if (FModel.Globals.Game.Version > EPakVersion.FNAME_BASED_COMPRESSION_METHOD || FModel.Globals.Game.SubVersion == 1)
{
if (reader.ReadInt32() != 0) throw new FileLoadException("VirtualTextures are not supported");
bIsVirtual = reader.ReadInt32() != 0;
if (bIsVirtual)
{
VTData = new FVirtualTextureBuiltData(reader, ubulk, bulkOffset);
}
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.IO;
namespace PakReader.Parsers.Objects
{
public readonly struct FVirtualTextureBuiltData
{
public readonly uint NumLayers;
public readonly uint NumMips;
public readonly int Width; // Width of the texture in pixels. Note the physical width may be larger due to tiling
public readonly int Height; // Height of the texture in pixels. Note the physical height may be larger due to tiling
public readonly uint WidthInBlocks; // Number of UDIM blocks that make up the texture, used to compute UV scaling factor
public readonly uint HeightInBlocks;
public readonly uint TileSize; // Tile size excluding borders
public readonly uint TileBorderSize; // A BorderSize pixel border will be added around all tiles
/**
* The pixel format output of the data on the i'th layer. The actual data
* may still be compressed but will decompress to this pixel format (e.g. zipped DXT5 data).
*/
public readonly EPixelFormat[] LayerTypes;
/**
* Tile data is packed into separate chunks, typically there is 1 mip level in each chunk for high resolution mips.
* After a certain threshold, all remaining low resolution mips will be packed into one final chunk.
*/
public readonly FVirtualTextureDataChunk[] Chunks;
/** Index of the first tile within each chunk */
public readonly uint[] TileIndexPerChunk;
/** Index of the first tile within each mip level */
public readonly uint[] TileIndexPerMip;
/**
* Info for the tiles organized per level. Within a level tile info is organized in Morton order.
* This is in morton order which can waste a lot of space in this array for non-square images
* e.g.:
* - An 8x1 tile image will allocate 8x4 indexes in this array.
* - An 1x8 tile image will allocate 8x8 indexes in this array.
*/
public readonly uint[] TileOffsetInChunk;
internal FVirtualTextureBuiltData(BinaryReader reader, Stream ubulk, long bulkOffset)
{
reader.ReadInt32(); // bCooked
NumLayers = reader.ReadUInt32();
WidthInBlocks = reader.ReadUInt32();
HeightInBlocks = reader.ReadUInt32();
TileSize = reader.ReadUInt32();
TileBorderSize = reader.ReadUInt32();
NumMips = reader.ReadUInt32();
Width = reader.ReadInt32();
Height = reader.ReadInt32();
TileIndexPerChunk = reader.ReadTArray(() => reader.ReadUInt32());
TileIndexPerMip = reader.ReadTArray(() => reader.ReadUInt32());
TileOffsetInChunk = reader.ReadTArray(() => reader.ReadUInt32());
LayerTypes = new EPixelFormat[8];
for (int Layer = 0; Layer < NumLayers; Layer++)
{
LayerTypes[Layer] = Enum.Parse<EPixelFormat>(reader.ReadFString());
}
Chunks = new FVirtualTextureDataChunk[reader.ReadInt32()];
for (int ChunkId = 0; ChunkId < Chunks.Length; ChunkId++)
{
Chunks[ChunkId] = new FVirtualTextureDataChunk(reader, ubulk, bulkOffset, NumLayers);
}
}
}
}

View File

@@ -0,0 +1,30 @@
using System.IO;
namespace PakReader.Parsers.Objects
{
public readonly struct FVirtualTextureDataChunk
{
public readonly FByteBulkData BulkData;
public readonly uint SizeInBytes;
public readonly uint CodecPayloadSize;
public readonly ushort[] CodecPayloadOffset;
public readonly EVirtualTextureCodec[] CodecType;
internal FVirtualTextureDataChunk(BinaryReader reader, Stream ubulk, long bulkOffset, uint numLayers)
{
CodecPayloadOffset = new ushort[8];
CodecType = new EVirtualTextureCodec[8];
SizeInBytes = reader.ReadUInt32();
CodecPayloadSize = reader.ReadUInt32();
for (int LayerIndex = 0; LayerIndex < numLayers; ++LayerIndex)
{
byte CodecTypeAsByte = reader.ReadByte();
CodecType[LayerIndex] = (EVirtualTextureCodec)CodecTypeAsByte;
CodecPayloadOffset[LayerIndex] = reader.ReadUInt16();
}
BulkData = new FByteBulkData(reader, ubulk, bulkOffset);
}
}
}

View File

@@ -0,0 +1,84 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace PakReader.Parsers
{
/// <summary>
/// https://gist.github.com/Scobalula/37229307de57de685d16ec621d5aceb5
/// </summary>
public class OodleStream : Stream
{
protected internal Stream _baseStream;
bool _disposed;
public OodleStream(byte[] input, long decompressedLength)
{
_baseStream = new MemoryStream(Decompress(input, decompressedLength), false)
{
Position = 0
};
}
/// <summary>
/// Oodle Library Path
/// </summary>
private const string OodleLibraryPath = "oo2core_5_win64";
protected override void Dispose(bool disposing)
{
try
{
if (!_disposed)
{
if (disposing && (this._baseStream != null))
this._baseStream.Dispose();
_disposed = true;
}
}
finally
{
base.Dispose(disposing);
}
}
/// <summary>
/// Oodle64 Decompression Method
/// </summary>
[DllImport(OodleLibraryPath, CallingConvention = CallingConvention.Cdecl)]
private static extern long OodleLZ_Decompress(byte[] buffer, long bufferSize, byte[] result, long outputBufferSize, int a, int b, int c, long d, long e, long f, long g, long h, long i, int ThreadModule);
/// <summary>
/// Decompresses a byte array of Oodle Compressed Data (Requires Oodle DLL)
/// </summary>
/// <param name="input">Input Compressed Data</param>
/// <param name="decompressedLength">Decompressed Size</param>
/// <returns>Resulting Array if success, otherwise null.</returns>
public static byte[] Decompress(byte[] input, long decompressedLength)
{
// Resulting decompressed Data
byte[] result = new byte[decompressedLength];
// Decode the data (other parameters such as callbacks not required)
long decodedSize = OodleLZ_Decompress(input, input.Length, result, decompressedLength, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3);
// Check did we fail
if (decodedSize == 0) return null;
// Return Result
return result;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_disposed) throw new ObjectDisposedException("OodleStream");
return _baseStream.Read(buffer, offset, count);
}
public override void Flush() => throw new NotImplementedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException();
public override void SetLength(long value) => throw new NotImplementedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException();
public override bool CanRead => throw new NotImplementedException();
public override bool CanSeek => throw new NotImplementedException();
public override bool CanWrite => throw new NotImplementedException();
public override long Length => throw new NotImplementedException();
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
}

View File

@@ -51,6 +51,7 @@ namespace PakReader.Parsers
DataExports[i] = ExportType.String switch
{
"Texture2D" => new UTexture2D(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"VirtualTexture2D" => new UTexture2D(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"CurveTable" => new UCurveTable(this),
"DataTable" => new UDataTable(this),
"FontFace" => new UFontFace(this, ubulk),

View File

@@ -8,7 +8,7 @@ namespace PakReader.Parsers.PropertyTagData
{
BaseProperty prop = type.String switch
{
"ByteProperty" => new ByteProperty(reader, readType),
"ByteProperty" => new ByteProperty(reader, tag, readType),
"BoolProperty" => new BoolProperty(reader, tag, readType),
"IntProperty" => new IntProperty(reader),
"FloatProperty" => new FloatProperty(reader),
@@ -43,7 +43,7 @@ namespace PakReader.Parsers.PropertyTagData
{
object prop = type.String switch
{
"ByteProperty" => new ByteProperty(reader, readType).Value,
"ByteProperty" => new ByteProperty(reader, tag, readType).Value,
"BoolProperty" => new BoolProperty(reader, tag, readType).Value,
"IntProperty" => new IntProperty(reader).Value,
"FloatProperty" => new FloatProperty(reader).Value,

View File

@@ -1,15 +1,16 @@
using System;
using PakReader.Parsers.Objects;
using System;
namespace PakReader.Parsers.PropertyTagData
{
public sealed class ByteProperty : BaseProperty<byte>
{
internal ByteProperty(PackageReader reader, ReadType readType)
internal ByteProperty(PackageReader reader, FPropertyTag tag, ReadType readType)
{
Position = reader.Position;
Value = readType switch
{
ReadType.NORMAL => (byte)reader.ReadFName().Index,
ReadType.NORMAL => tag.EnumName.IsNone ? reader.ReadByte() : (byte)reader.ReadFName().Index,
ReadType.MAP => (byte)reader.ReadUInt32(),
ReadType.ARRAY => reader.ReadByte(),
_ => throw new ArgumentOutOfRangeException(nameof(readType)),

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
namespace FModel.PakReader
namespace PakReader
{
/// <summary>
/// http://wiki.xentax.com/index.php/Wwise_SoundBank_(*.bnk)
@@ -98,7 +98,7 @@ namespace FModel.PakReader
string key = $"{didxSection.WemFilesRef[i].Id}.wem";
if (stidSection != null && stidSection.SoundBanks.TryGetValue(didxSection.WemFilesRef[i].Id, out string name))
key = $"{name}.wem";
else if (Globals.Game.ActualGame == EGame.Valorant && ValoloWwiseDict.ValorantWemToName.TryGetValue(didxSection.WemFilesRef[i].Id, out string hardcodedname))
else if (FModel.Globals.Game.ActualGame == FModel.EGame.Valorant && ValoloWwiseDict.ValorantWemToName.TryGetValue(didxSection.WemFilesRef[i].Id, out string hardcodedname))
key = $"{hardcodedname}.wem";
AudioFiles[key] = dataSection.WemFiles[i];
@@ -113,7 +113,7 @@ namespace FModel.PakReader
string key = $"{entry.Path.ToUpper()}_{entry.NameHash}.wem";
if (stidSection != null && stidSection.SoundBanks.TryGetValue(entry.NameHash, out string name))
key = $"{name}.wem";
else if (Globals.Game.ActualGame == EGame.Valorant && ValoloWwiseDict.ValorantWemToName.TryGetValue(entry.NameHash, out string hardcodedname))
else if (FModel.Globals.Game.ActualGame == FModel.EGame.Valorant && ValoloWwiseDict.ValorantWemToName.TryGetValue(entry.NameHash, out string hardcodedname))
key = $"{hardcodedname}.wem";
AudioFiles[key] = entry.Data;

View File

@@ -5,6 +5,7 @@ using FModel.ViewModels.ListBox;
using FModel.ViewModels.SoundPlayer;
using FModel.Windows.SoundPlayer.Visualization;
using Microsoft.Win32;
using PakReader;
using System;
using System.Collections.Generic;
using System.Diagnostics;