mirror of
https://github.com/4sval/FModel.git
synced 2026-09-14 20:46:45 -05:00
added VirtualTexture "support" + Oodle compression support
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace FModel.Creator.Rarities
|
||||
rarity = EFortRarity.Transcendent;
|
||||
break;
|
||||
case "EFortRarity::Unattainable":
|
||||
case "EFortRarity::Badass":
|
||||
rarity = EFortRarity.Unattainable;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
14
FModel/PakReader/Parsers/Objects/EVirtualTextureCodec.cs
Normal file
14
FModel/PakReader/Parsers/Objects/EVirtualTextureCodec.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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})")
|
||||
};
|
||||
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
FModel/PakReader/Parsers/Objects/FVirtualTextureBuiltData.cs
Normal file
73
FModel/PakReader/Parsers/Objects/FVirtualTextureBuiltData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
FModel/PakReader/Parsers/Objects/FVirtualTextureDataChunk.cs
Normal file
30
FModel/PakReader/Parsers/Objects/FVirtualTextureDataChunk.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
FModel/PakReader/Parsers/OodleStream.cs
Normal file
84
FModel/PakReader/Parsers/OodleStream.cs
Normal 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(); }
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user