added basic valorant icon support + json type chooser

This commit is contained in:
iAmAsval
2020-06-29 00:35:51 +02:00
parent cbd557e6e7
commit 5439fb8b13
64 changed files with 735 additions and 135 deletions

View File

@@ -39,8 +39,6 @@ namespace FModel
DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Culture]", Thread.CurrentThread.CurrentUICulture);
StatusBarVm.statusBarViewModel.Set(FModel.Properties.Resources.Initializing, FModel.Properties.Resources.Loading);
if (FModel.Properties.Settings.Default.UseDiscordRpc)
DiscordIntegration.StartClient();
base.OnStartup(e);
}

View File

@@ -1,4 +1,4 @@
using FModel.Creator.Icons;
using FModel.Creator.Icons;
using FModel.Creator.Rarities;
using FModel.Creator.Stats;
using FModel.Creator.Texts;

View File

@@ -0,0 +1,90 @@
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator
{
public class BaseUIData
{
private readonly SKPaint descriptionPaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DescriptionTypeface,
TextSize = 13,
Color = SKColors.White,
};
public SKBitmap IconImage;
public string DisplayName;
public string Description;
public int Width = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons
public int Height = 64;
public int Margin = 2;
public BaseUIData()
{
IconImage = null;
DisplayName = "";
Description = "";
}
public BaseUIData(IUExport export) : this()
{
if (export.GetExport<TextProperty>("DisplayName") is TextProperty displayName)
DisplayName = Text.GetTextPropertyBase(displayName);
if (export.GetExport<TextProperty>("Description") is TextProperty description)
{
Description = Text.GetTextPropertyBase(description);
if (!string.IsNullOrEmpty(Description))
{
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Length;
Height += (int)descriptionPaint.TextSize;
}
}
if (export.GetExport<ObjectProperty>("FullRender", "VerticalPromoImage", "LargeIcon", "DisplayIcon") is ObjectProperty icon)
{
SKBitmap raw = Utils.GetObjectTexture(icon);
if (raw != null)
{
int coef = Width / raw.Width;
int sizeX = raw.Width * coef;
int sizeY = raw.Height * coef;
Height += sizeY;
IconImage = raw.Resize(sizeX, sizeY);
}
}
}
public void Draw(SKCanvas c)
{
int textSize = 45;
SKPaint namePaint = new SKPaint
{
IsAntialias = true,
FilterQuality = SKFilterQuality.High,
Typeface = Text.TypeFaces.DisplayNameTypeface,
TextSize = textSize,
Color = SKColors.White,
TextAlign = SKTextAlign.Left,
};
// resize if too long
while (namePaint.MeasureText(DisplayName) > Width)
{
namePaint.TextSize = textSize -= 2;
}
c.DrawText(DisplayName, Margin, Margin + textSize, namePaint);
// wrap if too long
Helper.DrawMultilineText(c, Description, Width, Margin, ETextSide.Left,
new SKRect(Margin, textSize + 25, Width - Margin, Height - 25), descriptionPaint, out var yPos);
c.DrawBitmap(IconImage, new SKRect(0, yPos, Width, Height),
new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true });
}
}
}

View File

@@ -38,8 +38,11 @@ namespace FModel.Creator
if (export.GetExport<TextProperty>("OptionDescription") is TextProperty optionDescription)
{
OptionDescription = Text.GetTextPropertyBase(optionDescription);
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Length;
Height += (int)descriptionPaint.TextSize;
if (!string.IsNullOrEmpty(OptionDescription))
{
Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Length;
Height += (int)descriptionPaint.TextSize;
}
}
if (export.GetExport<ArrayProperty>("OptionValues") is ArrayProperty optionValues)

View File

@@ -85,9 +85,9 @@ namespace FModel.Creator.Bundles
{
if (displayStyle.Value is UObject o)
{
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor", "Context_LimitedTimeColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor)
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor)
PrimaryColor = SKColor.Parse(primaryColor.Hex);
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor", "Context_BaseColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor)
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor)
SecondaryColor = SKColor.Parse(secondaryColor.Hex);
if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue("AccentColor", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor accentColor)
{

View File

@@ -10,14 +10,14 @@ using System.IO;
namespace FModel.Creator
{
static class Creator
static class FortniteCreator
{
/// <summary>
/// we draw based on the fist export type of the asset, no need to check others it's a waste of time
/// i don't cache images because i don't wanna store a lot of SKCanvas in the memory
/// </summary>
/// <returns>true if an icon has been drawn</returns>
public static bool TryDrawIcon(string assetPath, string exportType, IUExport export)
public static bool TryDrawFortniteIcon(string assetPath, string exportType, IUExport export)
{
var d = new DirectoryInfo(assetPath);
string assetName = d.Name;

View File

@@ -1,4 +1,4 @@
using PakReader.Pak;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;

View File

@@ -17,7 +17,9 @@ namespace FModel.Creator.Texts
public static string GetTextPropertyBase(TextProperty t)
{
if (t.Value is FText text)
if (text.Text is FTextHistory.Base b)
if (text.Text is FTextHistory.None n)
return n.CultureInvariantString;
else if (text.Text is FTextHistory.Base b)
return b.SourceString.Replace("<Emphasized>", string.Empty).Replace("</>", string.Empty);
else if (text.Text is FTextHistory.StringTableEntry s)
{

View File

@@ -9,7 +9,7 @@ namespace FModel.Creator.Texts
public class Typefaces
{
#pragma warning disable IDE0051
private const string _BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/";
private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite
private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian
private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); // other languages fortnite unofficial
@@ -36,6 +36,10 @@ namespace FModel.Creator.Texts
private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular";
private const string _BURBANK_SMALL_BLACK = "burbanksmall-black";
private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold";
private const string _VALORANT_BASE_PATH = "/Game/UI/Fonts/FinalFonts/";
private const string _DINNEXT_W1G_BOLD = "DINNextW1G-Bold";
private const string _DINNEXT_W1G_REGULAR = "DINNextW1G-Regular";
#pragma warning restore IDE0051
public SKTypeface DefaultTypeface; // used as default font for all untranslated strings (item source, ...)
@@ -50,12 +54,12 @@ namespace FModel.Creator.Texts
{
DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD).Stream);
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
if (t != null && t.Length == 3)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
else BundleDefaultTypeface = DefaultTypeface;
string namePath = _BASE_PATH + (
string namePath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
@@ -63,7 +67,7 @@ namespace FModel.Creator.Texts
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
string.Empty);
if (!namePath.Equals(_BASE_PATH))
if (!namePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3)
@@ -71,7 +75,7 @@ namespace FModel.Creator.Texts
}
else DisplayNameTypeface = DefaultTypeface;
string descriptionPath = _BASE_PATH + (
string descriptionPath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_BOLD :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_REGULAR :
@@ -83,7 +87,7 @@ namespace FModel.Creator.Texts
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
else DescriptionTypeface = DefaultTypeface;
string bundleNamePath = _BASE_PATH + (
string bundleNamePath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU :
@@ -91,7 +95,7 @@ namespace FModel.Creator.Texts
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK :
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK :
string.Empty);
if (!bundleNamePath.Equals(_BASE_PATH))
if (!bundleNamePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(bundleNamePath);
if (t != null && t.Length == 3)
@@ -99,6 +103,16 @@ namespace FModel.Creator.Texts
}
else BundleDisplayNameTypeface = BundleDefaultTypeface;
}
else if (Globals.Game.ActualGame == EGame.Valorant)
{
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_VALORANT_BASE_PATH + _DINNEXT_W1G_BOLD);
if (t != null && t.Length == 3)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
t = Utils.GetPropertyArraySegmentByte(_VALORANT_BASE_PATH + _DINNEXT_W1G_REGULAR);
if (t != null && t.Length == 3)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
}
}
public bool NeedReload(bool forceReload) => forceReload ?

View File

@@ -0,0 +1,36 @@
using FModel.Creator.Icons;
using FModel.Creator.Texts;
using FModel.ViewModels.ImageBox;
using PakReader.Parsers.Class;
using SkiaSharp;
using System.IO;
namespace FModel.Creator
{
static class ValorantCreator
{
public static bool TryDrawValorantIcon(string assetPath, IUExport export)
{
var d = new DirectoryInfo(assetPath);
string assetName = d.Name;
if (Text.TypeFaces.NeedReload(false))
Text.TypeFaces = new Typefaces(); // when opening bundle creator settings without loading paks first
BaseUIData icon = new BaseUIData(export);
if (icon.IconImage != null)
{
using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul))
using (var c = new SKCanvas(ret))
{
icon.Draw(c);
Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512
ImageBoxVm.imageBoxViewModel.Set(ret, assetName);
}
return true;
}
return false;
}
}
}

View File

@@ -38,7 +38,7 @@ namespace FModel.Discord
{
Assets = _assets,
Timestamps = _baseTimestamp,
State = Properties.Resources.Idling
State = string.Format(Properties.Resources.Idling, Globals.Game.GetName())
});
Initialize();
SaveCurrentPresence();

View File

@@ -55,6 +55,12 @@
TraditionalChinese
}
public enum EJsonType: long
{
Default,
Positioned
}
public enum EIconDesign : long
{
Default,

View File

@@ -90,6 +90,7 @@ namespace FModel
private async Task Init()
{
await PaksGrabber.PopulateMenu().ConfigureAwait(false);
if (Properties.Settings.Default.UseDiscordRpc) DiscordIntegration.StartClient();
await AesGrabber.Load(Properties.Settings.Default.ReloadAesKeys).ConfigureAwait(false);
await CdnDataGrabber.DoCDNStuff().ConfigureAwait(false);
}

View File

@@ -430,7 +430,7 @@ namespace PakReader.Pak
pakLocation += 4;
}
}
return new FPakEntry(this.FileName, name, Offset, Size, UncompressedSize, new byte[20], CompressionBlocks, CompressionBlockSize, CompressionMethodIndex, (byte)((Encrypted ? 0x01 : 0x00) | (Deleted ? 0x02 : 0x00)));
return new FPakEntry(this.FileName, name, Offset, Size, UncompressedSize, CompressionBlocks, CompressionBlockSize, CompressionMethodIndex, (byte)((Encrypted ? 0x01 : 0x00) | (Deleted ? 0x02 : 0x00)));
}
else
{

View File

@@ -1,7 +1,11 @@
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using PakReader.Parsers;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
namespace PakReader.Pak
{
@@ -11,7 +15,31 @@ namespace PakReader.Pak
readonly ArraySegment<byte> UExp;
readonly ArraySegment<byte> UBulk;
public string[] ExportTypes
public string JsonData
{
get
{
if (string.IsNullOrEmpty(exports.JsonData))
{
var ret = new JsonExport[Exports.Length];
for (int i = 0; i < ret.Length; i++)
{
ret[i] = new JsonExport
{
ExportType = ExportTypes[i].String,
ExportValue = (FModel.EJsonType)FModel.Properties.Settings.Default.AssetsJsonType switch
{
FModel.EJsonType.Default => GetJsonDict(Exports[i]),
_ => Exports[i]
}
};
}
return exports.JsonData = JsonConvert.SerializeObject(ret, Formatting.Indented);
}
return exports.JsonData;
}
}
public FName[] ExportTypes
{
get
{
@@ -26,8 +54,8 @@ namespace PakReader.Pak
bulk.Position = 0;
var p = new PackageReader(asset, exp, bulk);
exports.Exports = p.Exports;
return exports.ExportTypes = p.ExportTypes;
exports.Exports = p.DataExports;
return exports.ExportTypes = p.DataExportTypes;
}
return exports.ExportTypes;
}
@@ -47,8 +75,8 @@ namespace PakReader.Pak
bulk.Position = 0;
var p = new PackageReader(asset, exp, bulk);
exports.ExportTypes = p.ExportTypes;
return exports.Exports = p.Exports;
exports.ExportTypes = p.DataExportTypes;
return exports.Exports = p.DataExports;
}
return exports.Exports;
}
@@ -63,10 +91,48 @@ namespace PakReader.Pak
exports = new ExportList();
}
public string GetFirstExportType() => ExportTypes[0];
public IUExport[] GetAllExports() => Exports;
public IUExport GetFirstExport() => Exports[0];
private Dictionary<string, object> GetJsonDict(IUExport export)
{
if (export != null)
{
var ret = new Dictionary<string, object>(export.Count);
foreach (KeyValuePair<string, object> KvP in export)
{
if (KvP.Value == null)
ret[KvP.Key] = null;
else
ret[KvP.Key] = KvP.Value.GetType().Name switch
{
"ByteProperty" => ((ByteProperty)KvP.Value).GetValue(),
"BoolProperty" => ((BoolProperty)KvP.Value).GetValue(),
"IntProperty" => ((IntProperty)KvP.Value).GetValue(),
"FloatProperty" => ((FloatProperty)KvP.Value).GetValue(),
"ObjectProperty" => ((ObjectProperty)KvP.Value).GetValue(),
"NameProperty" => ((NameProperty)KvP.Value).GetValue(),
"DoubleProperty" => ((DoubleProperty)KvP.Value).GetValue(),
"ArrayProperty" => ((ArrayProperty)KvP.Value).GetValue(),
"StructProperty" => ((StructProperty)KvP.Value).GetValue(),
"StrProperty" => ((StrProperty)KvP.Value).GetValue(),
"TextProperty" => ((TextProperty)KvP.Value).GetValue(),
"InterfaceProperty" => ((InterfaceProperty)KvP.Value).GetValue(),
"SoftObjectProperty" => ((SoftObjectProperty)KvP.Value).GetValue(),
"UInt64Property" => ((UInt64Property)KvP.Value).GetValue(),
"UInt32Property" => ((UInt32Property)KvP.Value).GetValue(),
"UInt16Property" => ((UInt16Property)KvP.Value).GetValue(),
"Int64Property" => ((Int64Property)KvP.Value).GetValue(),
"Int16Property" => ((Int16Property)KvP.Value).GetValue(),
"Int8Property" => ((Int8Property)KvP.Value).GetValue(),
"MapProperty" => ((MapProperty)KvP.Value).GetValue(),
"SetProperty" => ((SetProperty)KvP.Value).GetValue(),
"EnumProperty" => ((EnumProperty)KvP.Value).GetValue(),
"UObject" => ((UObject)KvP.Value).GetValue(),
_ => KvP.Value,
};
}
return ret;
}
return null;
}
public T GetExport<T>() where T : IUExport
{
@@ -99,8 +165,15 @@ namespace PakReader.Pak
// hacky way to get the package to be a readonly struct, essentially a double pointer i guess
sealed class ExportList
{
public string JsonData;
public FName[] ExportTypes;
public IUExport[] Exports;
public string[] ExportTypes;
}
sealed class JsonExport
{
public string ExportType;
public object ExportValue;
}
}
}

View File

@@ -7,7 +7,9 @@ namespace PakReader.Parsers.Class
/// The derived class must have a "readonly Dictionary<string, object>" of properties
/// </summary>
public interface IUExport : IReadOnlyDictionary<string, object>
{ }
{
}
public static class IUExportExtension
{
@@ -15,7 +17,7 @@ namespace PakReader.Parsers.Class
{
foreach (string name in names)
{
if (export.TryGetValue(name, out var obj) && obj is T)
if (export != null && export.TryGetValue(name, out var obj) && obj is T)
return (T)obj;
}
return default;

View File

@@ -9,11 +9,8 @@ namespace PakReader.Parsers.Class
{
public class UObject : IUExport, IUStruct
{
public FObjectExport ExportInfo { get; internal set; }
readonly Dictionary<string, object> Dict;
readonly FGuid GUID;
// https://github.com/EpicGames/UnrealEngine/blob/bf95c2cbc703123e08ab54e3ceccdd47e48d224a/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L930
public UObject(PackageReader reader) : this(reader, reader.ExportMap.Sum(e => e.SerialSize), false) { }
public UObject(PackageReader reader, bool structFallback) : this(reader, reader.ExportMap.Sum(e => e.SerialSize), structFallback) { }
@@ -23,7 +20,7 @@ namespace PakReader.Parsers.Class
// https://github.com/EpicGames/UnrealEngine/blob/7d9919ac7bfd80b7483012eab342cb427d60e8c9/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L2197
internal UObject(PackageReader reader, long maxSize, bool structFallback)
{
var props = new Dictionary<string, object>();
var properties = new Dictionary<string, object>();
int i = 1;
while (true)
@@ -33,27 +30,65 @@ namespace PakReader.Parsers.Class
break;
var pos = reader.Position;
if (props.ContainsKey(Tag.Name.String)) // FortniteGame/Content/Balance/RarityData.uasset i really need this
props[$"{Tag.Name.String}_NK{i++}"] = BaseProperty.ReadProperty(reader, Tag, Tag.Type, ReadType.NORMAL) ?? null; // NK = NewKey
else
props[Tag.Name.String] = BaseProperty.ReadProperty(reader, Tag, Tag.Type, ReadType.NORMAL) ?? null;
if (props[Tag.Name.String] is null)
break;
var obj = BaseProperty.ReadAsObject(reader, Tag, Tag.Type, ReadType.NORMAL) ?? null;
var key = properties.ContainsKey(Tag.Name.String) ? $"{Tag.Name.String}_NK{i++}" : Tag.Name.String;
properties[key] = obj;
if (obj == null) break;
if (Tag.Size + pos != reader.Position)
{
System.Diagnostics.Debug.WriteLine($"Didn't read {Tag.Type.String} correctly (at {reader.Position}, should be {Tag.Size + pos}, {Tag.Size + pos - reader.Position} behind)");
#if DEBUG
System.Diagnostics.Debug.WriteLine($"Didn't read {key} correctly (at {reader.Position}, should be {Tag.Size + pos}, {Tag.Size + pos - reader.Position} behind)");
#endif
reader.Position = Tag.Size + pos;
}
}
Dict = props;
Dict = properties;
if (!structFallback && reader.ReadInt32() != 0 && reader.Position + 16 <= maxSize)
{
GUID = new FGuid(reader);
new FGuid(reader);
}
}
public Dictionary<string, object> GetValue()
{
var ret = new Dictionary<string, object>(Dict.Count);
foreach (KeyValuePair<string, object> KvP in Dict)
{
if (KvP.Value == null)
ret[KvP.Key] = null;
else
ret[KvP.Key] = KvP.Value.GetType().Name switch
{
"ByteProperty" => ((ByteProperty)KvP.Value).GetValue(),
"BoolProperty" => ((BoolProperty)KvP.Value).GetValue(),
"IntProperty" => ((IntProperty)KvP.Value).GetValue(),
"FloatProperty" => ((FloatProperty)KvP.Value).GetValue(),
"ObjectProperty" => ((ObjectProperty)KvP.Value).GetValue(),
"NameProperty" => ((NameProperty)KvP.Value).GetValue(),
"DoubleProperty" => ((DoubleProperty)KvP.Value).GetValue(),
"ArrayProperty" => ((ArrayProperty)KvP.Value).GetValue(),
"StructProperty" => ((StructProperty)KvP.Value).GetValue(),
"StrProperty" => ((StrProperty)KvP.Value).GetValue(),
"TextProperty" => ((TextProperty)KvP.Value).GetValue(),
"InterfaceProperty" => ((InterfaceProperty)KvP.Value).GetValue(),
"SoftObjectProperty" => ((SoftObjectProperty)KvP.Value).GetValue(),
"UInt64Property" => ((UInt64Property)KvP.Value).GetValue(),
"UInt32Property" => ((UInt32Property)KvP.Value).GetValue(),
"UInt16Property" => ((UInt16Property)KvP.Value).GetValue(),
"Int64Property" => ((Int64Property)KvP.Value).GetValue(),
"Int16Property" => ((Int16Property)KvP.Value).GetValue(),
"Int8Property" => ((Int8Property)KvP.Value).GetValue(),
"MapProperty" => ((MapProperty)KvP.Value).GetValue(),
"SetProperty" => ((SetProperty)KvP.Value).GetValue(),
"EnumProperty" => ((EnumProperty)KvP.Value).GetValue(),
_ => KvP.Value,
};
}
return ret;
}
public object this[string key] => Dict[key];
public IEnumerable<string> Keys => Dict.Keys;
public IEnumerable<object> Values => Dict.Values;

View File

@@ -0,0 +1,18 @@
namespace PakReader.Parsers.Objects
{
public enum ERichCurveExtrapolation
{
/** Repeat the curve without an offset. */
RCCE_Cycle,
/** Repeat the curve with an offset relative to the first or last key's value. */
RCCE_CycleWithOffset,
/** Sinusoidally extrapolate. */
RCCE_Oscillate,
/** Use a linearly increasing value for extrapolation.*/
RCCE_Linear,
/** Use a constant value for extrapolation */
RCCE_Constant,
/** No Extrapolation */
RCCE_None,
}
}

View File

@@ -0,0 +1,16 @@
namespace PakReader.Parsers.Objects
{
public readonly struct FMovieSceneFloatChannel : IUStruct
{
public readonly ERichCurveExtrapolation PreInfinityExtrap;
public readonly ERichCurveExtrapolation PostInfinityExtrap;
internal FMovieSceneFloatChannel(PackageReader reader)
{
PreInfinityExtrap = (ERichCurveExtrapolation)reader.ReadByte();
PostInfinityExtrap = (ERichCurveExtrapolation)reader.ReadByte();
//todo https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/MovieScene/Private/Channels/MovieSceneFloatChannel.cpp#L1092
}
}
}

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System.Collections.Generic;
namespace PakReader.Parsers.Objects
{
@@ -37,6 +38,20 @@ namespace PakReader.Parsers.Objects
Reader = reader;
}
public object GetValue()
{
if (Resource != null)
{
var ret = new Dictionary<string, object>
{
["ObjectName"] = Resource.ObjectName.String,
["OuterIndex"] = Resource.OuterIndex.GetValue()
};
return ret;
}
return null;
}
[JsonIgnore]
public bool IsNull => Index == 0;
[JsonIgnore]

View File

@@ -17,7 +17,6 @@ namespace PakReader.Parsers.Objects
public readonly long Offset;
public readonly long Size;
public readonly long UncompressedSize;
public readonly byte[] Hash; // why isn't this an FShaHash?
public readonly FPakCompressedBlock[] CompressionBlocks;
public readonly uint CompressionBlockSize;
public readonly uint CompressionMethodIndex;
@@ -72,12 +71,8 @@ namespace PakReader.Parsers.Objects
else
CompressionMethodIndex = reader.ReadUInt32();
}
if (Version <= EPakVersion.INITIAL)
{
// Timestamp of type FDateTime, but the serializer only reads to the Ticks property (int64)
reader.ReadInt64();
}
Hash = reader.ReadBytes(20);
if (Version <= EPakVersion.INITIAL) reader.ReadInt64(); // Timestamp
reader.ReadBytes(20); // Hash
if (Version >= EPakVersion.COMPRESSION_ENCRYPTION)
{
if (CompressionMethodIndex != 0)
@@ -108,7 +103,7 @@ namespace PakReader.Parsers.Objects
Size = reader.ReadInt64();
UncompressedSize = reader.ReadInt64();
CompressionMethodIndex = reader.ReadUInt32();
Hash = reader.ReadBytes(20);
reader.ReadBytes(20); // Hash
if (CompressionMethodIndex != 0)
{
CompressionBlocks = reader.ReadTArray(() => new FPakCompressedBlock(reader));
@@ -120,14 +115,13 @@ namespace PakReader.Parsers.Objects
StructSize = (int)(reader.BaseStream.Position - StartOffset);
}
internal FPakEntry(string pakName, string name, long offset, long size, long uncompressedSize, byte[] hash, FPakCompressedBlock[] compressionBlocks, uint compressionBlockSize, uint compressionMethodIndex, byte flags)
internal FPakEntry(string pakName, string name, long offset, long size, long uncompressedSize, FPakCompressedBlock[] compressionBlocks, uint compressionBlockSize, uint compressionMethodIndex, byte flags)
{
PakFileName = pakName;
Name = name;
Offset = offset;
Size = size;
UncompressedSize = uncompressedSize;
Hash = hash;
CompressionBlocks = compressionBlocks;
CompressionBlockSize = compressionBlockSize;
CompressionMethodIndex = compressionMethodIndex;

View File

@@ -46,6 +46,7 @@ namespace PakReader.Parsers.Objects
"MovieSceneFrameRange" => new FMovieSceneFrameRange(reader),
"MovieSceneEvaluationKey" => new FMovieSceneEvaluationKey(reader),
"MovieSceneFloatValue" => new FRichCurveKey(reader),
"MovieSceneFloatChannel" => new FMovieSceneFloatChannel(reader),
"MovieSceneEvaluationTemplate" => new FMovieSceneEvaluationTemplate(reader),
"SkeletalMeshSamplingLODBuiltData" => new FSkeletalMeshSamplingLODBuiltData(reader),
//"BodyInstance" => new FBodyInstance(reader), // if uncommented, can't parse .umap

View File

@@ -15,8 +15,8 @@ namespace PakReader.Parsers
public FObjectImport[] ImportMap { get; }
public FObjectExport[] ExportMap { get; }
public IUExport[] Exports { get; }
public string[] ExportTypes { get; }
public IUExport[] DataExports { get; }
public FName[] DataExportTypes { get; }
public PackageReader(string path) : this(path + ".uasset", path + ".uexp", path + ".ubulk") { }
public PackageReader(string uasset, string uexp, string ubulk) : this(File.OpenRead(uasset), File.OpenRead(uexp), File.Exists(ubulk) ? File.OpenRead(ubulk) : null) { }
@@ -30,45 +30,41 @@ namespace PakReader.Parsers
NameMap = SerializeNameMap();
ImportMap = SerializeImportMap();
ExportMap = SerializeExportMap();
Exports = new IUExport[ExportMap.Length];
ExportTypes = new string[ExportMap.Length];
DataExports = new IUExport[ExportMap.Length];
DataExportTypes = new FName[ExportMap.Length];
Loader = uexp;
for(int i = 0; i < ExportMap.Length; i++)
{
var Export = ExportMap[i];
// Serialize everything, not just specifically assets
// if (Export.bIsAsset)
FName ObjectClassName;
if (ExportMap[i].ClassIndex.IsNull)
ObjectClassName = DataExportTypes[i] = ReadFName(); // check if this is true, I don't know if Fortnite ever uses this
else if (ExportMap[i].ClassIndex.IsExport)
ObjectClassName = DataExportTypes[i] = ExportMap[ExportMap[i].ClassIndex.AsExport].ObjectName;
else if (ExportMap[i].ClassIndex.IsImport)
ObjectClassName = DataExportTypes[i] = ImportMap[ExportMap[i].ClassIndex.AsImport].ObjectName;
else
throw new FileLoadException("Can't get class name"); // Shouldn't reach this unless the laws of math have bent to MagmaReef's will
if (ObjectClassName.String.Equals("BlueprintGeneratedClass")) continue;
var pos = Position = ExportMap[i].SerialOffset - PackageFileSummary.TotalHeaderSize;
DataExports[i] = ObjectClassName.String switch
{
// We need to get the class name from the import/export maps
FName ObjectClassName;
if (Export.ClassIndex.IsNull)
ObjectClassName = ReadFName(); // check if this is true, I don't know if Fortnite ever uses this
else if (Export.ClassIndex.IsExport)
ObjectClassName = ExportMap[Export.ClassIndex.AsExport].ObjectName;
else if (Export.ClassIndex.IsImport)
ObjectClassName = ImportMap[Export.ClassIndex.AsImport].ObjectName;
else
throw new FileLoadException("Can't get class name"); // Shouldn't reach this unless the laws of math have bent to MagmaReef's will
if (ObjectClassName.String.Equals("BlueprintGeneratedClass")) continue;
"Texture2D" => new UTexture2D(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"CurveTable" => new UCurveTable(this),
"DataTable" => new UDataTable(this),
"FontFace" => new UFontFace(this, ubulk),
"SoundWave" => new USoundWave(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"StringTable" => new UStringTable(this),
_ => new UObject(this),
};
var pos = Position = Export.SerialOffset - PackageFileSummary.TotalHeaderSize;
ExportTypes[i] = ObjectClassName.String;
Exports[i] = ObjectClassName.String switch
{
"Texture2D" => new UTexture2D(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"CurveTable" => new UCurveTable(this),
"DataTable" => new UDataTable(this),
"FontFace" => new UFontFace(this, ubulk),
"SoundWave" => new USoundWave(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"StringTable" => new UStringTable(this),
_ => new UObject(this),
};
if (pos + Export.SerialSize != Position)
{
System.Diagnostics.Debug.WriteLine($"[ExportType={ObjectClassName.String}] Didn't read {Export.ObjectName} correctly (at {Position}, should be {pos + Export.SerialSize}, {pos + Export.SerialSize - Position} behind)");
}
#if DEBUG
if (pos + ExportMap[i].SerialSize != Position)
{
System.Diagnostics.Debug.WriteLine($"[ExportType={ObjectClassName.String}] Didn't read {ExportMap[i].ObjectName} correctly (at {Position}, should be {pos + ExportMap[i].SerialSize}, {pos + ExportMap[i].SerialSize - Position} behind)");
}
#endif
}
return;
}

View File

@@ -2,14 +2,14 @@
namespace PakReader.Parsers.PropertyTagData
{
public sealed class ArrayProperty : BaseProperty<BaseProperty[]>
public sealed class ArrayProperty : BaseProperty<object[]>
{
internal ArrayProperty(PackageReader reader, FPropertyTag tag)
{
Position = reader.Position;
int length = reader.ReadInt32();
Value = new BaseProperty[length];
Value = new object[length];
FPropertyTag InnerTag = default;
// Execute if UE4 version is at least VER_UE4_INNER_ARRAY_TAG_INFO
@@ -20,8 +20,46 @@ namespace PakReader.Parsers.PropertyTagData
}
for (int i = 0; i < length; i++)
{
Value[i] = ReadProperty(reader, InnerTag, tag.InnerType, ReadType.ARRAY);
Value[i] = BaseProperty.ReadAsObject(reader, InnerTag, tag.InnerType, ReadType.ARRAY);
}
}
public object[] GetValue()
{
var ret = new object[Value.Length];
for (int i = 0; i < ret.Length; i++)
{
if (Value[i] == null)
ret[i] = null;
else
ret[i] = ((BaseProperty)Value[i]).GetType().Name switch
{
"ByteProperty" => ((ByteProperty)Value[i]).GetValue(),
"BoolProperty" => ((BoolProperty)Value[i]).GetValue(),
"IntProperty" => ((IntProperty)Value[i]).GetValue(),
"FloatProperty" => ((FloatProperty)Value[i]).GetValue(),
"ObjectProperty" => ((ObjectProperty)Value[i]).GetValue(),
"NameProperty" => ((NameProperty)Value[i]).GetValue(),
"DoubleProperty" => ((DoubleProperty)Value[i]).GetValue(),
"ArrayProperty" => ((ArrayProperty)Value[i]).GetValue(),
"StructProperty" => ((StructProperty)Value[i]).GetValue(),
"StrProperty" => ((StrProperty)Value[i]).GetValue(),
"TextProperty" => ((TextProperty)Value[i]).GetValue(),
"InterfaceProperty" => ((InterfaceProperty)Value[i]).GetValue(),
"SoftObjectProperty" => ((SoftObjectProperty)Value[i]).GetValue(),
"UInt64Property" => ((UInt64Property)Value[i]).GetValue(),
"UInt32Property" => ((UInt32Property)Value[i]).GetValue(),
"UInt16Property" => ((UInt16Property)Value[i]).GetValue(),
"Int64Property" => ((Int64Property)Value[i]).GetValue(),
"Int16Property" => ((Int16Property)Value[i]).GetValue(),
"Int8Property" => ((Int8Property)Value[i]).GetValue(),
"MapProperty" => ((MapProperty)Value[i]).GetValue(),
"SetProperty" => ((SetProperty)Value[i]).GetValue(),
"EnumProperty" => ((EnumProperty)Value[i]).GetValue(),
_ => Value[i],
};
}
return ret;
}
}
}

View File

@@ -4,7 +4,7 @@ namespace PakReader.Parsers.PropertyTagData
{
public class BaseProperty
{
internal static BaseProperty ReadProperty(PackageReader reader, FPropertyTag tag, FName type, ReadType readType)
internal static BaseProperty ReadAsObject(PackageReader reader, FPropertyTag tag, FName type, ReadType readType)
{
BaseProperty prop = type.String switch
{
@@ -37,6 +37,40 @@ namespace PakReader.Parsers.PropertyTagData
};
return prop;
}
internal static object ReadAsValue(PackageReader reader, FPropertyTag tag, FName type, ReadType readType)
{
var prop = type.String switch
{
"ByteProperty" => new ByteProperty(reader, tag, readType).Value,
"BoolProperty" => new BoolProperty(reader, tag, readType).Value,
"IntProperty" => new IntProperty(reader, tag).Value,
"FloatProperty" => new FloatProperty(reader, tag).Value,
"ObjectProperty" => new ObjectProperty(reader, tag).Value,
"NameProperty" => new NameProperty(reader, tag).Value,
"DelegateProperty" => new DelegateProperty(reader, tag),
"DoubleProperty" => new DoubleProperty(reader, tag).Value,
"ArrayProperty" => new ArrayProperty(reader, tag).Value,
"StructProperty" => new StructProperty(reader, tag).Value,
"StrProperty" => new StrProperty(reader, tag).Value,
"TextProperty" => new TextProperty(reader, tag).Value,
"InterfaceProperty" => new InterfaceProperty(reader, tag).Value,
"MulticastDelegateProperty" => new MulticastDelegateProperty(reader, tag).Value,
"LazyObjectProperty" => new LazyObjectProperty(reader, tag).Value,
"SoftObjectProperty" => new SoftObjectProperty(reader, tag, readType).Value,
"UInt64Property" => new UInt64Property(reader, tag).Value,
"UInt32Property" => new UInt32Property(reader, tag).Value,
"UInt16Property" => new UInt16Property(reader, tag).Value,
"Int64Property" => new Int64Property(reader, tag).Value,
"Int16Property" => new Int16Property(reader, tag).Value,
"Int8Property" => new Int8Property(reader, tag).Value,
"MapProperty" => new MapProperty(reader, tag).Value,
"SetProperty" => new SetProperty(reader, tag).Value,
"EnumProperty" => new EnumProperty(reader, tag).Value,
_ => null, //throw new NotImplementedException($"Parsing of {type.String} types aren't supported yet."),
};
return prop;
}
}
public class BaseProperty<T> : BaseProperty

View File

@@ -22,5 +22,7 @@ namespace PakReader.Parsers.PropertyTagData
throw new ArgumentOutOfRangeException(nameof(readType));
}
}
public bool GetValue() => Value;
}
}

View File

@@ -16,5 +16,7 @@ namespace PakReader.Parsers.PropertyTagData
_ => throw new ArgumentOutOfRangeException(nameof(readType)),
};
}
public byte GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadDouble();
}
public double GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadFName();
}
public string GetValue() => Value.String;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadFloat();
}
public float GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadInt16();
}
public short GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadInt64();
}
public long GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadByte();
}
public byte GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadInt32();
}
public int GetValue() => Value;
}
}

View File

@@ -10,5 +10,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadUInt32();
}
public uint GetValue() => Value;
}
}

View File

@@ -4,7 +4,7 @@ using PakReader.Parsers.Objects;
namespace PakReader.Parsers.PropertyTagData
{
public sealed class MapProperty : BaseProperty<IReadOnlyDictionary<BaseProperty, BaseProperty>>
public sealed class MapProperty : BaseProperty<IReadOnlyDictionary<object, object>>
{
// https://github.com/EpicGames/UnrealEngine/blob/7d9919ac7bfd80b7483012eab342cb427d60e8c9/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertyMap.cpp#L243
internal MapProperty(PackageReader reader, FPropertyTag tag)
@@ -18,12 +18,50 @@ namespace PakReader.Parsers.PropertyTagData
}
var NumEntries = reader.ReadInt32();
var dict = new Dictionary<BaseProperty, BaseProperty>(NumEntries);
var dict = new Dictionary<object, object>(NumEntries);
for (int i = 0; i < NumEntries; i++)
{
dict[ReadProperty(reader, tag, tag.InnerType, ReadType.MAP)] = ReadProperty(reader, tag, tag.ValueType, ReadType.MAP);
dict[ReadAsValue(reader, tag, tag.InnerType, ReadType.MAP)] = BaseProperty.ReadAsObject(reader, tag, tag.ValueType, ReadType.MAP);
}
Value = dict;
}
public Dictionary<object, object> GetValue()
{
var ret = new Dictionary<object, object>(Value.Count);
foreach (KeyValuePair<object, object> KvP in Value)
{
if (KvP.Value == null)
ret[KvP.Key] = null;
else
ret[KvP.Key] = KvP.Value.GetType().Name switch
{
"ByteProperty" => ((ByteProperty)KvP.Value).GetValue(),
"BoolProperty" => ((BoolProperty)KvP.Value).GetValue(),
"IntProperty" => ((IntProperty)KvP.Value).GetValue(),
"FloatProperty" => ((FloatProperty)KvP.Value).GetValue(),
"ObjectProperty" => ((ObjectProperty)KvP.Value).GetValue(),
"NameProperty" => ((NameProperty)KvP.Value).GetValue(),
"DoubleProperty" => ((DoubleProperty)KvP.Value).GetValue(),
"ArrayProperty" => ((ArrayProperty)KvP.Value).GetValue(),
"StructProperty" => ((StructProperty)KvP.Value).GetValue(),
"StrProperty" => ((StrProperty)KvP.Value).GetValue(),
"TextProperty" => ((TextProperty)KvP.Value).GetValue(),
"InterfaceProperty" => ((InterfaceProperty)KvP.Value).GetValue(),
"SoftObjectProperty" => ((SoftObjectProperty)KvP.Value).GetValue(),
"UInt64Property" => ((UInt64Property)KvP.Value).GetValue(),
"UInt32Property" => ((UInt32Property)KvP.Value).GetValue(),
"UInt16Property" => ((UInt16Property)KvP.Value).GetValue(),
"Int64Property" => ((Int64Property)KvP.Value).GetValue(),
"Int16Property" => ((Int16Property)KvP.Value).GetValue(),
"Int8Property" => ((Int8Property)KvP.Value).GetValue(),
"MapProperty" => ((MapProperty)KvP.Value).GetValue(),
"SetProperty" => ((SetProperty)KvP.Value).GetValue(),
"EnumProperty" => ((EnumProperty)KvP.Value).GetValue(),
_ => KvP.Value,
};
}
return ret;
}
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadFName();
}
public string GetValue() => Value.String;
}
}

View File

@@ -1,4 +1,5 @@
using PakReader.Parsers.Objects;
using System.Collections.Generic;
namespace PakReader.Parsers.PropertyTagData
{
@@ -9,5 +10,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = new FPackageIndex(reader);
}
public object GetValue() => Value.GetValue();
}
}

View File

@@ -3,7 +3,7 @@ using PakReader.Parsers.Objects;
namespace PakReader.Parsers.PropertyTagData
{
public sealed class SetProperty : BaseProperty<BaseProperty[]>
public sealed class SetProperty : BaseProperty<object[]>
{
// https://github.com/EpicGames/UnrealEngine/blob/bf95c2cbc703123e08ab54e3ceccdd47e48d224a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySet.cpp#L216
internal SetProperty(PackageReader reader, FPropertyTag tag)
@@ -17,11 +17,49 @@ namespace PakReader.Parsers.PropertyTagData
}
var NumEntries = reader.ReadInt32();
Value = new BaseProperty[NumEntries];
Value = new object[NumEntries];
for (int i = 0; i < NumEntries; i++)
{
Value[i] = ReadProperty(reader, tag, tag.InnerType, ReadType.ARRAY);
Value[i] = BaseProperty.ReadAsObject(reader, tag, tag.InnerType, ReadType.ARRAY);
}
}
public object[] GetValue()
{
var ret = new object[Value.Length];
for (int i = 0; i < ret.Length; i++)
{
if (Value[i] == null)
ret[i] = null;
else
ret[i] = ((BaseProperty)Value[i]).GetType().Name switch
{
"ByteProperty" => ((ByteProperty)Value[i]).GetValue(),
"BoolProperty" => ((BoolProperty)Value[i]).GetValue(),
"IntProperty" => ((IntProperty)Value[i]).GetValue(),
"FloatProperty" => ((FloatProperty)Value[i]).GetValue(),
"ObjectProperty" => ((ObjectProperty)Value[i]).GetValue(),
"NameProperty" => ((NameProperty)Value[i]).GetValue(),
"DoubleProperty" => ((DoubleProperty)Value[i]).GetValue(),
"ArrayProperty" => ((ArrayProperty)Value[i]).GetValue(),
"StructProperty" => ((StructProperty)Value[i]).GetValue(),
"StrProperty" => ((StrProperty)Value[i]).GetValue(),
"TextProperty" => ((TextProperty)Value[i]).GetValue(),
"InterfaceProperty" => ((InterfaceProperty)Value[i]).GetValue(),
"SoftObjectProperty" => ((SoftObjectProperty)Value[i]).GetValue(),
"UInt64Property" => ((UInt64Property)Value[i]).GetValue(),
"UInt32Property" => ((UInt32Property)Value[i]).GetValue(),
"UInt16Property" => ((UInt16Property)Value[i]).GetValue(),
"Int64Property" => ((Int64Property)Value[i]).GetValue(),
"Int16Property" => ((Int16Property)Value[i]).GetValue(),
"Int8Property" => ((Int8Property)Value[i]).GetValue(),
"MapProperty" => ((MapProperty)Value[i]).GetValue(),
"SetProperty" => ((SetProperty)Value[i]).GetValue(),
"EnumProperty" => ((EnumProperty)Value[i]).GetValue(),
_ => Value[i],
};
}
return ret;
}
}
}

View File

@@ -11,5 +11,7 @@ namespace PakReader.Parsers.PropertyTagData
if (readType == ReadType.MAP)
reader.Position += 16 - (reader.Position - Position); // skip ahead, putting the total bytes read to 16
}
public FSoftObjectPath GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadFString();
}
public string GetValue() => Value;
}
}

View File

@@ -1,4 +1,6 @@
using PakReader.Parsers.Objects;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using System.Collections.Generic;
namespace PakReader.Parsers.PropertyTagData
{
@@ -9,5 +11,60 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = new UScriptStruct(reader, tag.StructName).Struct;
}
public object GetValue()
{
if (Value is UObject obj)
{
var ret = new Dictionary<string, object>(obj.Count);
foreach (KeyValuePair<string, object> KvP in obj)
{
if (KvP.Value == null)
ret[KvP.Key] = null;
else
ret[KvP.Key] = KvP.Value.GetType().Name switch
{
"ByteProperty" => ((ByteProperty)KvP.Value).GetValue(),
"BoolProperty" => ((BoolProperty)KvP.Value).GetValue(),
"IntProperty" => ((IntProperty)KvP.Value).GetValue(),
"FloatProperty" => ((FloatProperty)KvP.Value).GetValue(),
"ObjectProperty" => ((ObjectProperty)KvP.Value).GetValue(),
"NameProperty" => ((NameProperty)KvP.Value).GetValue(),
"DoubleProperty" => ((DoubleProperty)KvP.Value).GetValue(),
"ArrayProperty" => ((ArrayProperty)KvP.Value).GetValue(),
"StructProperty" => ((StructProperty)KvP.Value).GetValue(),
"StrProperty" => ((StrProperty)KvP.Value).GetValue(),
"TextProperty" => ((TextProperty)KvP.Value).GetValue(),
"InterfaceProperty" => ((InterfaceProperty)KvP.Value).GetValue(),
"SoftObjectProperty" => ((SoftObjectProperty)KvP.Value).GetValue(),
"UInt64Property" => ((UInt64Property)KvP.Value).GetValue(),
"UInt32Property" => ((UInt32Property)KvP.Value).GetValue(),
"UInt16Property" => ((UInt16Property)KvP.Value).GetValue(),
"Int64Property" => ((Int64Property)KvP.Value).GetValue(),
"Int16Property" => ((Int16Property)KvP.Value).GetValue(),
"Int8Property" => ((Int8Property)KvP.Value).GetValue(),
"MapProperty" => ((MapProperty)KvP.Value).GetValue(),
"SetProperty" => ((SetProperty)KvP.Value).GetValue(),
"EnumProperty" => ((EnumProperty)KvP.Value).GetValue(),
_ => KvP.Value,
};
}
return ret;
}
else if (Value is FGameplayTagContainer gTags)
{
var ret = new string[gTags.GameplayTags.Length];
for (int i = 0; i < ret.Length; i++)
{
ret[i] = gTags.GameplayTags[i].String;
}
return ret;
}
else if (Value is FGuid guid)
{
return guid.Hex;
}
return Value;
}
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = new FText(reader);
}
public FText GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadUInt16();
}
public ushort GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadUInt32();
}
public uint GetValue() => Value;
}
}

View File

@@ -9,5 +9,7 @@ namespace PakReader.Parsers.PropertyTagData
Position = reader.Position;
Value = reader.ReadUInt64();
}
public ulong GetValue() => Value;
}
}

View File

@@ -1419,7 +1419,7 @@ namespace FModel.Properties {
}
/// <summary>
/// Recherche une chaîne localisée semblable à Idling.
/// Recherche une chaîne localisée semblable à {0} - Idling.
/// </summary>
public static string Idling {
get {
@@ -1567,6 +1567,15 @@ namespace FModel.Properties {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à JSON Type.
/// </summary>
public static string JsonType {
get {
return ResourceManager.GetString("JsonType", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à KB.
/// </summary>
@@ -2909,6 +2918,15 @@ namespace FModel.Properties {
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à With Position.
/// </summary>
public static string WithPosition {
get {
return ResourceManager.GetString("WithPosition", resourceCulture);
}
}
/// <summary>
/// Recherche une chaîne localisée semblable à Yes.
/// </summary>

View File

@@ -446,7 +446,7 @@ It's now the most used free software to leak on Fortnite.</value>
<value>File not found, watermarking disabled</value>
</data>
<data name="Idling" xml:space="preserve">
<value>Idling</value>
<value>{0} - Idling</value>
</data>
<data name="Images" xml:space="preserve">
<value>الصور</value>

View File

@@ -416,7 +416,7 @@ Jetzt ist es die am häufigsten genutzte freie Software um mit Fortnite zu leake
<value>Datei nicht gefunden, Wasserzeichen deaktiviert</value>
</data>
<data name="Idling" xml:space="preserve">
<value>Leerlauf</value>
<value>{0} - Leerlauf</value>
</data>
<data name="ImageSaved" xml:space="preserve">
<value>Bild erfolgreich gespeichert</value>

View File

@@ -561,7 +561,7 @@ Ahora es el software gratuito más utilizado para filtrar en Fortnite.</value>
<value>..\Resources\icon-creator.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Idling" xml:space="preserve">
<value>Inactivo</value>
<value>{0} - Inactivo</value>
</data>
<data name="Images" xml:space="preserve">
<value>Imágenes</value>

View File

@@ -461,7 +461,7 @@ C'est maintenant le logiciel gratuit le plus utilisé pour leak sur Fortnite.</v
<value>Fichier introuvable, filigrane désactivé</value>
</data>
<data name="Idling" xml:space="preserve">
<value>En Attente</value>
<value>{0} - En Attente</value>
</data>
<data name="Images" xml:space="preserve">
<value>Images</value>
@@ -865,4 +865,10 @@ C'est maintenant le logiciel gratuit le plus utilisé pour leak sur Fortnite.</v
<data name="NoBackground" xml:space="preserve">
<value>Pas de fond</value>
</data>
<data name="JsonType" xml:space="preserve">
<value>Type de JSON</value>
</data>
<data name="WithPosition" xml:space="preserve">
<value>Avec Position</value>
</data>
</root>

View File

@@ -440,7 +440,7 @@ Col tempo sono state aggiunte nuove funzioni e molti altri utenti hanno comincia
<value>File non trovato, filigrana disabilitata</value>
</data>
<data name="Idling" xml:space="preserve">
<value>Nessun incarico assegnato</value>
<value>{0} - Nessun incarico assegnato</value>
</data>
<data name="Images" xml:space="preserve">
<value>Immagini</value>

View File

@@ -214,7 +214,7 @@
<value>新規および変更されたファイルを読み込む</value>
</data>
<data name="Idling" xml:space="preserve">
<value>アイドリング</value>
<value>{0} - アイドリング</value>
</data>
<data name="NoText" xml:space="preserve">
<value>テキストなし</value>

View File

@@ -574,7 +574,7 @@ It's now the most used free software to leak on Fortnite.</value>
<value>..\Resources\icon-creator.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Idling" xml:space="preserve">
<value>Idling</value>
<value>{0} - Idling</value>
</data>
<data name="Images" xml:space="preserve">
<value>Images</value>
@@ -1109,4 +1109,10 @@ It's now the most used free software to leak on Fortnite.</value>
<data name="borderlands3" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\borderlands3.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="JsonType" xml:space="preserve">
<value>JSON Type</value>
</data>
<data name="WithPosition" xml:space="preserve">
<value>With Position</value>
</data>
</root>

View File

@@ -299,6 +299,18 @@ namespace FModel.Properties {
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0")]
public long AssetsJsonType {
get {
return ((long)(this["AssetsJsonType"]));
}
set {
this["AssetsJsonType"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("0")]

View File

@@ -71,6 +71,9 @@
<Setting Name="AssetsLanguage" Type="System.Int64" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
<Setting Name="AssetsJsonType" Type="System.Int64" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>
<Setting Name="AssetsIconDesign" Type="System.Int64" Scope="User">
<Value Profile="(Default)">0</Value>
</Setting>

View File

@@ -20,10 +20,11 @@ using System.Windows;
using FModel.Windows.CustomNotifier;
using FModel.ViewModels.Buttons;
using System.Threading;
using static FModel.Creator.Creator;
using SkiaSharp;
using System.Text;
using FModel.ViewModels.DataGrid;
using static FModel.Creator.FortniteCreator;
using static FModel.Creator.ValorantCreator;
namespace FModel.Utils
{
@@ -173,7 +174,9 @@ namespace FModel.Utils
{
PakPackage p = GetPakPackage(entry, mount, loadContent);
if (!p.Equals(default))
return JsonConvert.SerializeObject(p.GetAllExports(), Formatting.Indented);
{
return p.JsonData;
}
return string.Empty;
}
@@ -238,8 +241,10 @@ namespace FModel.Utils
return p;
}
// Creator Image
if (TryDrawIcon(entry.Name, p.GetFirstExportType(), p.GetFirstExport()))
// Image Creator
if (Globals.Game.ActualGame == EGame.Fortnite && TryDrawFortniteIcon(entry.Name, p.ExportTypes[0].String, p.Exports[0]))
return p;
else if (Globals.Game.ActualGame == EGame.Valorant && TryDrawValorantIcon(entry.Name, p.Exports.Length > 1 ? p.Exports[1] : p.Exports[0]))
return p;
}

View File

@@ -8,7 +8,7 @@ namespace FModel.Utils
static class EGL2
{
const uint FILE_CONFIG_MAGIC = 0x279B21E6;
const ushort FILE_CONFIG_VERSION = (ushort)ESettingsVersion.Version13;
const ushort FILE_CONFIG_VERSION = (ushort)ESettingsVersion.Latest;
public static string GetEGL2PakFilesPath()
{
@@ -18,10 +18,10 @@ namespace FModel.Utils
using Stream stream = new BufferedStream(new FileInfo(configFile).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
using BinaryReader reader = new BinaryReader(stream, Encoding.Default);
if (reader.ReadUInt32() != FILE_CONFIG_MAGIC)
throw new FileLoadException("Invalid file magic");
throw new FileLoadException("Invalid EGL2 Config Magic");
if (reader.ReadUInt16BE() != FILE_CONFIG_VERSION)
throw new FileLoadException("Invalid egl2 version");
if (reader.ReadUInt16BE() < FILE_CONFIG_VERSION)
throw new FileLoadException("Invalid EGL2 Config Version");
int stringLength = reader.ReadUInt16BE();
string cacheDirectory = Encoding.UTF8.GetString(reader.ReadBytes(stringLength));

View File

@@ -116,7 +116,10 @@ namespace FModel.Utils
files = new Dictionary<string, FPakEntry>();
foreach (FPakEntry entry in tempFiles.Values)
{
if (files.ContainsKey(mount + entry.GetPathWithoutExtension()) || entry.GetExtension().Equals(".uptnl"))
if (files.ContainsKey(mount + entry.GetPathWithoutExtension()) ||
entry.GetExtension().Equals(".uptnl") ||
entry.GetExtension().Equals(".uexp") ||
entry.GetExtension().Equals(".ubulk"))
continue;
if (entry.IsUE4Package()) // if .uasset

View File

@@ -23,6 +23,12 @@ namespace FModel.ViewModels.ComboBox
new ComboBoxViewModel { Id = 14, Content = Properties.Resources.TraditionalChinese, Property = ELanguage.TraditionalChinese }
};
public static ObservableCollection<ComboBoxViewModel> jsonCbViewModel = new ObservableCollection<ComboBoxViewModel>
{
new ComboBoxViewModel { Id = 0, Content = Properties.Resources.Default, Property = EJsonType.Default },
new ComboBoxViewModel { Id = 1, Content = Properties.Resources.WithPosition, Property = EJsonType.Positioned }
};
public static ObservableCollection<ComboBoxViewModel> designCbViewModel = new ObservableCollection<ComboBoxViewModel>
{
new ComboBoxViewModel { Id = 0, Content = Properties.Resources.Default, Property = EIconDesign.Default },

View File

@@ -279,7 +279,7 @@ namespace FModel.ViewModels.MenuItem
int compressionMethodIndex = reader.ReadInt32();
// we only need name and uncompressedSize to compare
FPakEntry entry = new FPakEntry("CatsWillDominateTheWorld.pak", name, offset, size, uncompressedSize, new byte[20], null, 0, (uint)compressionMethodIndex, 0);
FPakEntry entry = new FPakEntry("CatsWillDominateTheWorld.pak", name, offset, size, uncompressedSize, null, 0, (uint)compressionMethodIndex, 0);
oldFilesTemp[entry.Name] = entry;
}
}

View File

@@ -22,6 +22,7 @@ namespace FModel.Windows.Search
private void OnLoaded(object sender, RoutedEventArgs e)
{
AssetFilter_TxtBox.Focus();
AssetFilter_TxtBox.SelectAll();
TotalAssets_Lbl.Text = string.Format(Properties.Resources.TotalAssetsLoaded, DataGridVm.dataGridViewModel.Count.ToString("# ### ###", new NumberFormatInfo { NumberGroupSeparator = " " }).Trim());
Assets_DtGrd.ItemsSource = DataGridVm.dataGridViewModel;
}

View File

@@ -81,6 +81,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
@@ -99,21 +100,12 @@
BorderBrush="#7F748198" Background="#FF333C46"
Height="20" VerticalAlignment="Top" Margin="0,3,0,0"/>
<Grid Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Margin="5,0,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="10" />
</Grid.RowDefinitions>
<TextBlock Text="{x:Static properties:Resources.FyiTitle}"
FontWeight="Bold" Foreground="White" Grid.Row="1"
FontFamily="Calibri" FontSize="18"/>
<TextBlock Text="{x:Static properties:Resources.FyiDetails}"
FontWeight="Light" Foreground="White" Grid.Row="2"
TextWrapping="Wrap" FontSize="11" MaxWidth="350"/>
</Grid>
<Label Grid.Row="2" Grid.Column="1"
Content="{x:Static properties:Resources.JsonType}"
HorizontalAlignment="Left" VerticalAlignment="Top"/>
<ComboBox x:Name="Json_CbBox" Grid.Row="2" Grid.Column="2"
BorderBrush="#7F748198" Background="#FF333C46"
Height="20" VerticalAlignment="Top" Margin="0,3,0,0"/>
<Grid Grid.Row="1" Grid.Column="4" Grid.RowSpan="3">
<Grid.RowDefinitions>

View File

@@ -40,6 +40,8 @@ namespace FModel.Windows.Settings
_useEnglish = Properties.Settings.Default.UseEnglish;
Languages_CbBox.ItemsSource = ComboBoxVm.languageCbViewModel;
Languages_CbBox.SelectedItem = ComboBoxVm.languageCbViewModel.Where(x => x.Id == Properties.Settings.Default.AssetsLanguage).FirstOrDefault();
Json_CbBox.ItemsSource = ComboBoxVm.jsonCbViewModel;
Json_CbBox.SelectedItem = ComboBoxVm.jsonCbViewModel.Where(x => x.Id == Properties.Settings.Default.AssetsJsonType).FirstOrDefault();
}
private async Task SaveAndExit()
@@ -52,7 +54,11 @@ namespace FModel.Windows.Settings
Properties.Settings.Default.AssetsLanguage = Languages_CbBox.SelectedIndex;
await Localizations.SetLocalization(Properties.Settings.Default.AssetsLanguage, true).ConfigureAwait(false);
}
if (Properties.Settings.Default.AssetsJsonType != Json_CbBox.SelectedIndex)
{
Properties.Settings.Default.AssetsJsonType = Json_CbBox.SelectedIndex;
Assets.ClearCachedFiles();
}
if (!_inputPath.Equals(Properties.Settings.Default.PakPath) ||
!_outputPath.Equals(Properties.Settings.Default.OutputPath) ||