Uhm ok so I guess that's the big commit

- Added utoc/ucas (io store) loading support
- Added new unversioned asset parsing support ONLY for asset we have mappings yet
- Integrated that new parsing as good as possible into fmodel's old structure (which certainly wasn't prepared for such a change lol)
- ONLY for new package format disabled caching of files in debug mode (was annoying)
- Type and enum mappings while by default be loaded from a github repo BUT in debug mode it will first attempt to load them from local files (relative to the exe, also for debugging conveniance) but fallback to the ones from the repo
- F12 hotkey for reloading current type mappings (Mainly made for debug but also works in release)
This commit is contained in:
Fabian 2020-10-28 21:41:53 +01:00
parent 3835717dfc
commit 1b2f6c636f
308 changed files with 3250 additions and 933 deletions

View File

@ -2,8 +2,8 @@
using System.Windows;
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases

View File

@ -1,9 +1,9 @@
using FModel.Creator.Bundles;
using FModel.Creator.Texts;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using System.Collections.Generic;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{
@ -53,7 +53,7 @@ namespace FModel.Creator.Bases
{
foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value)
{
PakPackage p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String);
Package p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();

View File

@ -1,10 +1,10 @@
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Windows;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{

View File

@ -7,16 +7,14 @@ using FModel.Creator.Icons;
using FModel.Creator.Rarities;
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Properties;
using FModel.Utils;
using Fortnite_API.Objects;
using Fortnite_API.Objects.V1;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases
@ -68,7 +66,7 @@ namespace FModel.Creator.Bases
LargeSmallImage.GetPreviewImage(this, previewImage);
else if (export.GetExport<ObjectProperty>("access_item") is { } accessItem)
{
PakPackage p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();

View File

@ -1,8 +1,8 @@
using FModel.Creator.Rarities;
using FModel.Creator.Texts;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
@ -44,7 +44,7 @@ namespace FModel.Creator.Bases
if (export.GetExport<ObjectProperty>("access_item") is ObjectProperty accessItem)
{
SItem = accessItem.Value.Resource.ObjectName.String;
PakPackage p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();

View File

@ -1,6 +1,6 @@
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases

View File

@ -1,9 +1,9 @@
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp;
using System;
using System.Windows;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{

View File

@ -1,9 +1,9 @@
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp;
using System;
using System.Windows;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{

View File

@ -2,14 +2,12 @@
using System.Windows;
using FModel.Creator.Texts;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Utils;
using Fortnite_API.Objects;
using Fortnite_API.Objects.V1;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Bases

View File

@ -1,13 +1,13 @@
using FModel.Creator.Bundles;
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Documents;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{

View File

@ -1,9 +1,9 @@
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System.Collections.Generic;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{

View File

@ -1,10 +1,10 @@
using FModel.Creator.Texts;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System.Collections.Generic;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bases
{

View File

@ -1,6 +1,6 @@
using FModel.Utils;
using PakReader.Parsers.PropertyTagData;
using System;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{

View File

@ -1,9 +1,9 @@
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp;
using System;
using System.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{

View File

@ -1,7 +1,7 @@
using FModel.Creator.Texts;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{
@ -37,7 +37,7 @@ namespace FModel.Creator.Bundles
if (obj.TryGetValue("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable)
{
PakPackage p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var u = p.GetExport<UDataTable>();

View File

@ -1,10 +1,10 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Bundles
{
@ -37,7 +37,7 @@ namespace FModel.Creator.Bundles
string[] parts = assetName.Split(':');
if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase))
{
PakPackage p = Utils.GetPropertyPakPackage($"/Game/Items/BannerIcons/{parts[1]}.{parts[1]}");
Package p = Utils.GetPropertyPakPackage($"/Game/Items/BannerIcons/{parts[1]}.{parts[1]}");
if (p.HasExport() && !p.Equals(default))
{
if (p.GetExport<UObject>() is UObject banner)
@ -54,7 +54,7 @@ namespace FModel.Creator.Bundles
public Reward(IntProperty quantity, FSoftObjectPath itemFullPath)
{
RewardQuantity = quantity.Value;
PakPackage p = Utils.GetPropertyPakPackage(itemFullPath.AssetPathName.String);
Package p = Utils.GetPropertyPakPackage(itemFullPath.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
@ -71,7 +71,7 @@ namespace FModel.Creator.Bundles
{
RewardQuantity = quantity.Value;
PakPackage p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String);
Package p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();
@ -135,7 +135,7 @@ namespace FModel.Creator.Bundles
default:
{
string path = Utils.GetFullPath($"/FortniteGame/Content/Athena/.*?/{trigger}.*").Replace("FortniteGame/Content", "Game");
PakPackage p = Utils.GetPropertyPakPackage(path);
Package p = Utils.GetPropertyPakPackage(path);
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UObject>();

View File

@ -5,10 +5,10 @@ using FModel.Creator.Rarities;
using FModel.Creator.Stats;
using FModel.Creator.Texts;
using FModel.ViewModels.ImageBox;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using SkiaSharp;
using System.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
namespace FModel.Creator
{

View File

@ -1,7 +1,7 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Icons
{
@ -33,8 +33,8 @@ namespace FModel.Creator.Icons
if (string.IsNullOrEmpty(path))
path = "/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf("."));
PakPackage p = Utils.GetPropertyPakPackage(path);
if (p.HasExport() && !p.Equals(default))
Package p = Utils.GetPropertyPakPackage(path);
if (p != null && p.HasExport())
{
var obj = p.GetExport<UObject>();
if (obj != null)

View File

@ -1,7 +1,7 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Icons
@ -16,11 +16,11 @@ namespace FModel.Creator.Icons
public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName) => GetPreviewImage(icon, o, assetName, true);
public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName, bool hightRes)
{
string path = o.Value.Resource.OuterIndex.Resource.ObjectName.String;
if (path.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier"))
string path = o.Value.Resource?.OuterIndex.Resource?.ObjectName.String;
if (path?.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier") == true)
path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1);
PakPackage p = Utils.GetPropertyPakPackage(path);
Package p = Utils.GetPropertyPakPackage(path);
if (p.HasExport() && !p.Equals(default))
{
if (GetPreviewImage(icon, p.GetIndexedExport<UObject>(0), hightRes))

View File

@ -1,12 +1,12 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Windows;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Icons
{
@ -16,7 +16,7 @@ namespace FModel.Creator.Icons
{
if (uffs.Count > 0)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories
Package p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories
if (p.HasExport() && !p.Equals(default))
{
var o = p.GetExport<UObject>();

View File

@ -1,10 +1,10 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System.Linq;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Rarities
{
@ -12,8 +12,8 @@ namespace FModel.Creator.Rarities
{
public static void GetInGameRarity(BaseIcon icon, EnumProperty e)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData");
if (p.HasExport() && !p.Equals(default))
Package p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData");
if (p != null && p.HasExport())
{
var d = p.GetExport<UObject>();
if (d != null)
@ -68,13 +68,13 @@ namespace FModel.Creator.Rarities
public static void GetInGameRarity(BaseGCosmetic icon, EnumProperty e)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/UI/UIKit/DT_RarityColors");
if (p.HasExport() && !p.Equals(default))
Package p = Utils.GetPropertyPakPackage("/Game/UI/UIKit/DT_RarityColors");
if (p != null || p.HasExport())
{
var d = p.GetExport<UDataTable>();
if (d != null)
{
if (e != null && d.TryGetValue(e?.Value.String["EXRarity::".Length..], out object r) && r is UObject rarity &&
if (e != null && d.TryGetValue(e.Value.String["EXRarity::".Length..], out object r) && r is UObject rarity &&
rarity.GetExport<ArrayProperty>("Colors") is ArrayProperty colors &&
colors.Value[0] is StructProperty s1 && s1.Value is FLinearColor color1 &&
colors.Value[1] is StructProperty s2 && s2.Value is FLinearColor color2 &&

View File

@ -1,8 +1,8 @@
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
namespace FModel.Creator.Rarities
@ -11,7 +11,7 @@ namespace FModel.Creator.Rarities
{
public static void GetRarity(BaseIcon icon, ObjectProperty o)
{
PakPackage p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();

View File

@ -1,13 +1,13 @@
using FModel.Creator.Bases;
using FModel.Creator.Texts;
using FModel.Utils;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
using System;
using System.Windows;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Stats
{
@ -17,7 +17,7 @@ namespace FModel.Creator.Stats
{
if (!ammoData.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Consumables/"))
{
PakPackage p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String);
Package p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
@ -43,7 +43,7 @@ namespace FModel.Creator.Stats
o1.TryGetValue("DataTable", out var c1) && c1 is ObjectProperty dataTable &&
o1.TryGetValue("RowName", out var c2) && c2 is NameProperty rowName)
{
PakPackage p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UDataTable>();
@ -79,7 +79,7 @@ namespace FModel.Creator.Stats
public static void GetHeroStats(BaseIcon icon, ObjectProperty heroGameplayDefinition)
{
PakPackage p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var obj = p.GetExport<UObject>();
@ -108,7 +108,7 @@ namespace FModel.Creator.Stats
{
if (parent.TryGetValue("GrantedAbilityKit", out var v) && v is SoftObjectProperty grantedAbilityKit)
{
PakPackage k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String);
Package k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String);
if (k.HasExport() && !k.Equals(default))
{
var kit = k.GetExport<UObject>();

View File

@ -1,10 +1,10 @@
using FModel.Creator.Bases;
using FModel.Creator.Icons;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Utils;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
namespace FModel.Creator.Texts
{
@ -32,7 +32,7 @@ namespace FModel.Creator.Texts
private static string GetCosmeticSet(string setName)
{
PakPackage p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets");
Package p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets");
if (p.HasExport() && !p.Equals(default))
{
var d = p.GetExport<UDataTable>();

View File

@ -1,9 +1,9 @@
using System.Collections.Generic;
using FModel.Creator.Bases;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using FModel.PakReader;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using SkiaSharp.HarfBuzz;
@ -25,7 +25,7 @@ namespace FModel.Creator.Texts
return b.SourceString.Replace("<Emphasized>", string.Empty).Replace("</>", string.Empty);
else if (text.Text is FTextHistory.StringTableEntry s)
{
PakPackage p = Utils.GetPropertyPakPackage(s.TableId.String);
Package p = Utils.GetPropertyPakPackage(s.TableId.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UStringTable>();
@ -69,7 +69,7 @@ namespace FModel.Creator.Texts
o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable &&
o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way
{
PakPackage p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UCurveTable>();
@ -98,7 +98,7 @@ namespace FModel.Creator.Texts
o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable &&
o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way
{
PakPackage p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String);
if (p.HasExport() && !p.Equals(default))
{
var table = p.GetExport<UCurveTable>();

View File

@ -82,8 +82,13 @@ namespace FModel.Creator.Texts
if (Globals.Game.ActualGame == EGame.Fortnite)
{
ArraySegment<byte>[] t = Utils.GetPropertyArraySegmentByte(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK);
if (t != null && t.Length == 3 && t[2].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[0].AsStream());
if (BundleDefaultTypeface != null && t[2].Array != null)
BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
}
else BundleDefaultTypeface = DefaultTypeface;
string namePath = _FORTNITE_BASE_PATH + (
@ -97,8 +102,13 @@ namespace FModel.Creator.Texts
if (!namePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(namePath);
if (t != null && t.Length == 3 && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream());
if (DisplayNameTypeface != null && t[2].Array != null)
DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
}
}
else DisplayNameTypeface = DefaultTypeface;
@ -110,8 +120,13 @@ namespace FModel.Creator.Texts
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? string.Empty :
_BURBANK_SMALL_BOLD);
t = Utils.GetPropertyArraySegmentByte(bottomPath);
if (t != null && t.Length == 3 && t[2].Array != null)
BottomDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
BottomDefaultTypeface = SKTypeface.FromStream(t[0].AsStream());
if (BottomDefaultTypeface != null && t[2].Array != null)
BottomDefaultTypeface = SKTypeface.FromStream(t[2].AsStream());
}
string descriptionPath = _FORTNITE_BASE_PATH + (
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR :
@ -121,8 +136,13 @@ namespace FModel.Creator.Texts
Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR :
_NOTO_SANS_REGULAR);
t = Utils.GetPropertyArraySegmentByte(descriptionPath);
if (t != null && t.Length == 3 && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[0].AsStream());
if (DescriptionTypeface != null && t[2].Array != null)
DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream());
}
else DescriptionTypeface = DefaultTypeface;
string bundleNamePath = _FORTNITE_BASE_PATH + (
@ -136,8 +156,13 @@ namespace FModel.Creator.Texts
if (!bundleNamePath.Equals(_FORTNITE_BASE_PATH))
{
t = Utils.GetPropertyArraySegmentByte(bundleNamePath);
if (t != null && t.Length == 3 && t[2].Array != null)
BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
if (t != null && t.Length == 3)
{
if (t[0].Array != null)
BundleDisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream());
if (BundleDisplayNameTypeface != null && t[2].Array != null)
BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream());
}
}
else BundleDisplayNameTypeface = BundleDefaultTypeface;
}

View File

@ -1,15 +1,31 @@
using FModel.Utils;
using PakReader.Pak;
using PakReader.Parsers.Class;
using PakReader.Parsers.PropertyTagData;
using SkiaSharp;
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using FModel.PakReader;
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.PropertyTagData;
namespace FModel.Creator
{
static class Utils
{
public static string GetFullPath(FPackageId id)
{
foreach (var ioStore in Globals.CachedIoStores.Values)
{
if (ioStore.IsInitialized)
{
var entry = ioStore.Files.FirstOrDefault(it => it.Value.ChunkId.ChunkId == id.Id).Value;
if (entry != null)
return ioStore.MountPoint + entry.Name;
}
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string GetFullPath(string partialPath)
{
@ -18,20 +34,35 @@ namespace FModel.Creator
{
return fullPath;
}
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
{
if (ioStoreReader.TryGetPartialKey(partialPath, out var fullPath))
{
return fullPath;
}
}
return string.Empty;
}
public static PakPackage GetPropertyPakPackage(string value)
public static Package GetPropertyPakPackage(string value)
{
string path = Strings.FixPath(value);
foreach (var fileReader in Globals.CachedPakFiles.Values)
if (fileReader.TryGetCaseInsensiteveValue(path, out var entry))
{
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf(".")).Length);
return Assets.GetPakPackage(entry, mount);
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
return Assets.GetPackage(entry, mount);
}
return default;
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
if (ioStoreReader.TryGetCaseInsensiteveValue(path, out var entry))
{
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
return Assets.GetPackage(entry, mount);
}
return null;
}
public static ArraySegment<byte>[] GetPropertyArraySegmentByte(string value)
@ -41,7 +72,14 @@ namespace FModel.Creator
if (fileReader.TryGetCaseInsensiteveValue(path, out var entry))
{
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf(".")).Length);
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
return Assets.GetArraySegmentByte(entry, mount);
}
foreach (var ioStoreReader in Globals.CachedIoStores.Values)
if (ioStoreReader.TryGetCaseInsensiteveValue(path, out var entry))
{
// kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯
string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length);
return Assets.GetArraySegmentByte(entry, mount);
}
return default;
@ -74,7 +112,7 @@ namespace FModel.Creator
s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_TimeWeaver_UIT";
}
PakPackage p = GetPropertyPakPackage(s);
var p = GetPropertyPakPackage(s);
if (p.HasExport() && !p.Equals(default))
{
var i = p.GetExport<UTexture2D>();

View File

@ -137,7 +137,7 @@
<PackageReference Include="AvalonEdit" Version="6.0.1" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
<PackageReference Include="DotNetZip" Version="1.13.8" />
<PackageReference Include="DotNetZip" Version="1.14.0" />
<PackageReference Include="EpicManifestParser" Version="1.2.0" />
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.0.1" />
<PackageReference Include="Fortnite-API-Wrapper" Version="2.1.0" />
@ -149,6 +149,7 @@
<PackageReference Include="Ookii.Dialogs.Wpf" Version="1.1.0" />
<PackageReference Include="SkiaSharp" Version="2.80.2" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.80.2" />
<PackageReference Include="System.Data.HashFunction.CityHash" Version="2.0.0" />
<PackageReference Include="ToastNotifications" Version="2.5.1" />
<PackageReference Include="ToastNotifications.Messages" Version="2.5.1" />
<PackageReference Include="WriteableBitmapEx" Version="1.6.7" />

View File

@ -1,8 +1,9 @@
using PakReader.Pak;
using PakReader.Parsers.Objects;
using System;
using System;
using System.Collections.Generic;
using System.Windows;
using FModel.PakReader.IO;
using FModel.PakReader.Pak;
using FModel.PakReader.Parsers.Objects;
using FModel.Properties;
using ToastNotifications;
using ToastNotifications.Lifetime;
@ -17,6 +18,10 @@ namespace FModel
/// PakFileReader is the reader where you can grab the FPakEntries, MountPoint and more
/// </summary>
public static readonly Dictionary<string, PakFileReader> CachedPakFiles = new Dictionary<string, PakFileReader>();
public static readonly Dictionary<string, FFileIoStoreReader> CachedIoStores = new Dictionary<string, FFileIoStoreReader>();
public static FIoGlobalData GlobalData = null;
public static Dictionary<string, Dictionary<int, PropertyInfo>> TypeMappings;
public static Dictionary<string, Dictionary<int, string>> EnumMappings;
public static readonly Notifier gNotifier = new Notifier(cfg =>
{
cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor(TimeSpan.FromSeconds(7), MaximumNotificationCount.FromCount(15));

View File

@ -8,11 +8,9 @@ using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FModel.PakReader;
using FModel.Utils;
using PakReader;
namespace FModel.Grabber.Manifests
{
public class ValorantAPIManifest

View File

@ -11,12 +11,12 @@ using EpicManifestParser.Objects;
using FModel.Grabber.Manifests;
using FModel.Logger;
using FModel.PakReader.IO;
using FModel.PakReader.Pak;
using FModel.Utils;
using FModel.ViewModels.MenuItem;
using FModel.Windows.Launcher;
using PakReader.Pak;
namespace FModel.Grabber.Paks
{
static class PaksGrabber
@ -143,6 +143,7 @@ namespace FModel.Grabber.Paks
// define the current game thank to the pak path
Folders.SetGameName(Properties.Settings.Default.PakPath);
// paks
string[] paks = Directory.GetFiles(Properties.Settings.Default.PakPath, "*.pak");
for (int i = 0; i < paks.Length; i++)
{
@ -171,6 +172,33 @@ namespace FModel.Grabber.Paks
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", paks[i]);
}
}
// io stores
var utocs = Directory.GetFiles(Properties.Settings.Default.PakPath, "*.utoc");
foreach (var utoc in utocs)
{
var ucas = utoc.Replace(".utoc", ".ucas");
if (!Utils.Paks.IsFileReadLocked(new FileInfo(utoc)) && !Utils.Paks.IsFileReadLocked(new FileInfo(ucas)))
{
var utocStream = new MemoryStream(await File.ReadAllBytesAsync(utoc));
var ucasStream = File.OpenRead(ucas);
var ioStore = new FFileIoStoreReader(ucas.SubstringAfterLast('\\'), utocStream, ucasStream);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Registering]", $"{ioStore.FileName} with GUID {ioStore.TocResource.Header.EncryptionKeyGuid.Hex}");
await Application.Current.Dispatcher.InvokeAsync(delegate
{
MenuItems.pakFiles.Add(new PakMenuItemViewModel
{
IoStore = ioStore,
IsEnabled = false
});
});
}
else
{
FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(utoc)), FColors.Red, true);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Locked]", utoc);
}
}
}
});
}

View File

@ -23,6 +23,7 @@
<CommandBinding Command="{x:Static utils:Commands.AutoSaveImage}" Executed="OnAutoShortcutPressed"/>
<CommandBinding Command="{x:Static utils:Commands.AutoOpenSounds}" Executed="OnAutoShortcutPressed"/>
<CommandBinding Command="{x:Static utils:Commands.OpenImageDoubleClick}" Executed="OnImageOpenClick"/>
<CommandBinding Command="{x:Static utils:Commands.ReloadTypeMappings}" Executed="ReloadMappings"/>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="F1" Command="{x:Static utils:Commands.OpenGeneralSettings}"/>
@ -32,6 +33,7 @@
<KeyBinding Key="F5" Command="{x:Static utils:Commands.AutoSave}"/>
<KeyBinding Key="F6" Command="{x:Static utils:Commands.AutoSaveImage}"/>
<KeyBinding Key="F7" Command="{x:Static utils:Commands.AutoOpenSounds}"/>
<KeyBinding Key="F12" Command="{x:Static utils:Commands.ReloadTypeMappings}"/>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>

View File

@ -23,14 +23,19 @@ using FModel.Windows.Search;
using FModel.Windows.Settings;
using FModel.Windows.SoundPlayer;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using PropertyInfo = FModel.PakReader.IO.PropertyInfo;
namespace FModel
{
@ -72,6 +77,8 @@ namespace FModel
if (!Properties.Settings.Default.SkipVersion) Updater.CheckForUpdate();
DebugHelper.WriteUserSettings();
Folders.CheckWatermarks();
LoadMappings();
await Task.WhenAll(Init()).ContinueWith(t =>
{
@ -97,6 +104,52 @@ namespace FModel
await Folders.DownloadAndExtractVgm().ConfigureAwait(false);
}
private async void LoadMappings()
{
try
{
#if DEBUG
string rawMappings = null;
string rawEnumMappings = null;
try
{
rawMappings = await File.ReadAllTextAsync("TypeMappings.json");
rawEnumMappings = await File.ReadAllTextAsync("EnumMappings.json");
}
catch
{
rawMappings ??= await Endpoints.GetStringEndpoint(Endpoints.FORTNITE_TYPE_MAPPINGS);
rawEnumMappings ??= await Endpoints.GetStringEndpoint(Endpoints.FORTNITE_ENUM_MAPPINGS);
}
#else
var rawMappings = await Endpoints.GetStringEndpoint(Endpoints.FORTNITE_TYPE_MAPPINGS);
var rawEnumMappings = await Endpoints.GetStringEndpoint(Endpoints.FORTNITE_ENUM_MAPPINGS);
#endif
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new DefaultContractResolver
{NamingStrategy = new CamelCaseNamingStrategy(false, false)}
};
Globals.TypeMappings =
JsonConvert.DeserializeObject<Dictionary<string, Dictionary<int, PropertyInfo>>>(rawMappings,
serializerSettings);
Globals.EnumMappings = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<int, string>>>(rawEnumMappings,
serializerSettings);
}
catch (Exception exception)
{
DebugHelper.WriteException(exception, "Failed to load Mappings");
Globals.TypeMappings ??= new Dictionary<string, Dictionary<int, PropertyInfo>>();
Globals.EnumMappings ??= new Dictionary<string, Dictionary<int, string>>();
}
}
public void ReloadMappings(object sender, RoutedEventArgs e)
{
LoadMappings();
}
private void AeConfiguration()
{
AvalonEditFindReplaceHelper Frm = new AvalonEditFindReplaceHelper
@ -206,7 +259,7 @@ namespace FModel
FModel_AssetsList.SelectedItem is ListBoxViewModel selectedItem)
{
bool autoExport = FModel_AssetsList.SelectedItems.Count > 1;
if (!autoExport) Assets.Export(selectedItem.PakEntry, false); // manual export if one
if (!autoExport) Assets.Export(selectedItem.ReaderEntry, false); // manual export if one
else
{
bool ret = Properties.Settings.Default.AutoExport;
@ -375,7 +428,7 @@ namespace FModel
{
FModel_TabCtrl.SelectedIndex = 1;
ExtractStopVm.extractViewModel.IsEnabled = true;
AssetPropertiesVm.assetPropertiesViewModel.Set(selectedItem.PakEntry);
AssetPropertiesVm.assetPropertiesViewModel.Set(selectedItem.ReaderEntry);
}
else
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace PakReader
namespace FModel.PakReader
{
static class AESDecryptor
{

View File

@ -2,7 +2,7 @@
using System.Linq;
using System.Runtime.CompilerServices;
namespace PakReader
namespace FModel.PakReader
{
static class BinaryHelper
{

View File

@ -1,4 +1,4 @@
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
/// <summary>
/// Addressable chunk types.

View File

@ -1,4 +1,4 @@
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public enum EIoContainerFlags : byte
{

View File

@ -0,0 +1,16 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FArc
{
public readonly uint FromNodeIndex;
public readonly uint ToNodeIndex;
public FArc(BinaryReader reader)
{
FromNodeIndex = reader.ReadUInt32();
ToNodeIndex = reader.ReadUInt32();
}
}
}

View File

@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.IO;
namespace FModel.PakReader.IO
{
public class FContainerHeader
{
public readonly FIoContainerId ContainerId;
public readonly uint PackageCount;
public readonly byte[] Names;
public readonly byte[] NameHashes;
public readonly FPackageId[] PackageIds;
public readonly byte[] StoreEntries;
public readonly Dictionary<string, (FPackageId source, FPackageId localized)[]> CulturePackageMap;
public readonly (FPackageId source, FPackageId target)[] PackageRedirects;
public FContainerHeader(BinaryReader reader)
{
ContainerId = new FIoContainerId(reader);
PackageCount = reader.ReadUInt32();
Names = reader.ReadBytes(reader.ReadInt32());
NameHashes = reader.ReadBytes(reader.ReadInt32());
PackageIds = reader.ReadTArray(() => new FPackageId(reader));
StoreEntries = reader.ReadBytes(reader.ReadInt32());
var culturePackageMapCount = reader.ReadInt32();
CulturePackageMap = new Dictionary<string, (FPackageId source, FPackageId localized)[]>(culturePackageMapCount);
for (int i = 0; i < culturePackageMapCount; i++)
{
CulturePackageMap.Add(
reader.ReadFString(),
reader.ReadTArray(() => (new FPackageId(reader), new FPackageId(reader))));
}
PackageRedirects = reader.ReadTArray(() => (new FPackageId(reader), new FPackageId(reader)));
}
}
}

View File

@ -0,0 +1,25 @@
using System.IO;
namespace FModel.PakReader.IO
{
public struct FExportBundleEntry
{
public const int SIZE = 8;
public uint LocalExportIndex;
public EExportCommandType CommandType;
public FExportBundleEntry(BinaryReader reader)
{
LocalExportIndex = reader.ReadUInt32();
CommandType = (EExportCommandType) reader.ReadUInt32();
}
}
public enum EExportCommandType
{
ExportCommandType_Create,
ExportCommandType_Serialize,
ExportCommandType_Count
};
}

View File

@ -0,0 +1,16 @@
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public class FExportDesc
{
public FPackageDesc Package = null;
public FName Name;
public FName FullName;
public FPackageObjectIndex OuterIndex;
public FPackageObjectIndex ClassIndex;
public FPackageObjectIndex SuperIndex;
public FPackageObjectIndex TemplateIndex;
public FPackageObjectIndex GlobalImportIndex;
}
}

View File

@ -0,0 +1,37 @@
using FModel.PakReader.Parsers;
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public readonly struct FExportMapEntry
{
public const int SIZE = 72;
public readonly ulong CookedSerialOffset;
public readonly ulong CookedSerialSize;
public readonly FMappedName ObjectName;
public readonly FPackageObjectIndex OuterIndex;
public readonly FPackageObjectIndex ClassIndex;
public readonly FPackageObjectIndex SuperIndex;
public readonly FPackageObjectIndex TemplateIndex;
public readonly FPackageObjectIndex GlobalImportIndex;
public readonly EObjectFlags ObjectFlags;
public readonly EExportFilterFlags FilterFlags;
public FExportMapEntry(IoPackageReader reader)
{
CookedSerialOffset = reader.ReadUInt64();
CookedSerialSize = reader.ReadUInt64();
ObjectName = new FMappedName(reader);
OuterIndex = new FPackageObjectIndex(reader);
ClassIndex = new FPackageObjectIndex(reader);
SuperIndex = new FPackageObjectIndex(reader);
TemplateIndex = new FPackageObjectIndex(reader);
GlobalImportIndex = new FPackageObjectIndex(reader);
ObjectFlags = (EObjectFlags) reader.ReadUInt32();
FilterFlags = (EExportFilterFlags) reader.ReadByte();
reader.SkipBytes(3);
}
}
}

View File

@ -1,7 +1,7 @@
using System.IO;
using PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.Objects;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public struct FFileIoStoreContainerFile
{

View File

@ -1,27 +1,34 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using FModel.Logger;
using FModel.PakReader.Parsers;
using FModel.PakReader.Parsers.Objects;
using FModel.Utils;
using Ionic.Zlib;
using PakReader.Parsers;
using PakReader.Parsers.Objects;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public class FFileIoStoreReader
public class FFileIoStoreReader : IReadOnlyDictionary<string, FIoStoreEntry>
{
public readonly string FileName;
public readonly FIoStoreTocResource TocResource;
public readonly Dictionary<FIoChunkId, FIoOffsetAndLength> Toc;
public readonly FFileIoStoreContainerFile ContainerFile;
public readonly FIoContainerId ContainerId;
public bool IsInitialized => Files != null;
private byte[] _aesKey;
public byte[] AesKey
{
get => _aesKey;
set
{
if (!HasDirectoryIndex) return;
if (value != null && !TestAesKey(value)) //if value not null, test but fail, throw not working
throw new ArgumentException(string.Format(FModel.Properties.Resources.AesNotWorking, value.ToStringKey(), ContainerFile.FileName));
_aesKey = value; // else, even if value is null, set it
@ -29,22 +36,29 @@ namespace PakReader.Pak.IO
}
}
public readonly bool CaseSensitive;
public bool HasDirectoryIndex => TocResource.DirectoryIndexBuffer != null;
public FGuid EncryptionKeyGuid => ContainerFile.EncryptionKeyGuid;
public bool IsEncrypted => ContainerFile.ContainerFlags.HasAnyFlags(EIoContainerFlags.Encrypted);
public Dictionary<string, FIoStoreEntry> Files;
public FIoDirectoryIndexResource _directoryIndex;
private byte[] _directoryIndexBuffer;
public FFileIoStoreReader(Stream tocStream, Stream containerStream, EIoStoreTocReadOptions tocReadOptions = EIoStoreTocReadOptions.ReadDirectoryIndex)
public FFileIoStoreReader(string fileName, Stream tocStream, Stream containerStream, bool caseSensitive = true, EIoStoreTocReadOptions tocReadOptions = EIoStoreTocReadOptions.ReadDirectoryIndex)
{
FileName = fileName;
CaseSensitive = caseSensitive;
ContainerFile.FileHandle = containerStream;
var tocResource = new FIoStoreTocResource(tocStream, tocReadOptions);
TocResource = tocResource;
var containerUncompressedSize = tocResource.Header.TocCompressedBlockEntryCount > 0
? tocResource.Header.TocCompressedBlockEntryCount * tocResource.Header.CompressionBlockSize
: containerStream.Length;
? (ulong) tocResource.Header.TocCompressedBlockEntryCount * (ulong) tocResource.Header.CompressionBlockSize
: (ulong) containerStream.Length;
Toc = new Dictionary<FIoChunkId, FIoOffsetAndLength>((int) tocResource.Header.TocEntryCount);
@ -79,18 +93,40 @@ namespace PakReader.Pak.IO
_directoryIndexBuffer = tocResource.DirectoryIndexBuffer;
}
public void ReadIndex()
public bool ReadDirectoryIndex()
{
using Stream indexStream = IsEncrypted
? new MemoryStream(AESDecryptor.DecryptAES(_directoryIndexBuffer, _aesKey))
: new MemoryStream(_directoryIndexBuffer);
_directoryIndex = new FIoDirectoryIndexResource(indexStream);
try
{
if (HasDirectoryIndex)
{
using Stream indexStream = IsEncrypted
? new MemoryStream(AESDecryptor.DecryptAES(_directoryIndexBuffer, _aesKey))
: new MemoryStream(_directoryIndexBuffer);
_directoryIndex = new FIoDirectoryIndexResource(indexStream, CaseSensitive);
var firstEntry = GetChildDirectory(FIoDirectoryIndexHandle.Root);
var tempFiles = new Dictionary<string, FIoStoreEntry>();
ReadIndex("", firstEntry, tempFiles);
Paks.Merge(tempFiles, out Files, _directoryIndex.MountPoint);
DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[FFileIoStoreReader]", "[ReadDirectoryIndex]", $"{FileName} contains {Files.Count} files, mount point: \"{MountPoint}\", version: {(int)TocResource.Header.Version}");
return true;
}
}
catch (Exception e)
{
DebugHelper.WriteLine(e.ToString());
}
return false;
}
public string MountPoint => _directoryIndex.MountPoint;
public bool TestAesKey(byte[] key)
{
if (!HasDirectoryIndex)
return false;
if (!IsEncrypted)
return true;
return TestAesKey(_directoryIndexBuffer, key);
@ -122,6 +158,8 @@ namespace PakReader.Pak.IO
}
}
public bool DoesChunkExist(FIoChunkId chunkId) => Toc.ContainsKey(chunkId);
public byte[] Read(FIoChunkId chunkId)
{
var offsetAndLength = Toc[chunkId];
@ -129,16 +167,15 @@ namespace PakReader.Pak.IO
var compressionBlockSize = tocResource.Header.CompressionBlockSize;
var dst = new byte[offsetAndLength.Length];
var firstBlockIndex = (int) (offsetAndLength.Offset / compressionBlockSize);
var lastBlockIndex = (int) ((BinaryHelper.Align(offsetAndLength.Offset + dst.Length, compressionBlockSize) - 1) / compressionBlockSize);
var offsetInBlock = offsetAndLength.Offset % compressionBlockSize;
byte[] src;
var lastBlockIndex = (int) ((BinaryHelper.Align((long) offsetAndLength.Offset + dst.Length, compressionBlockSize) - 1) / compressionBlockSize);
var offsetInBlock = (int) offsetAndLength.Offset % compressionBlockSize;
var remainingSize = dst.Length;
var dstOffset = 0;
for (int blockIndex = firstBlockIndex; blockIndex < lastBlockIndex; blockIndex++)
for (int blockIndex = firstBlockIndex; blockIndex <= lastBlockIndex; blockIndex++)
{
var compressionBlock = tocResource.CompressionBlocks[blockIndex];
var rawSize = BinaryHelper.Align(compressionBlock.CompressedSize, AESDecryptor.ALIGN);
var compressedBuffer = new byte[rawSize];
@ -153,6 +190,8 @@ namespace PakReader.Pak.IO
compressedBuffer = AESDecryptor.DecryptAES(compressedBuffer, _aesKey);
}
byte[] src;
if (compressionBlock.CompressionMethodIndex == 0)
{
src = compressedBuffer;
@ -164,7 +203,7 @@ namespace PakReader.Pak.IO
src = uncompressedBuffer;
}
var sizeInBlock = (int) Math.Min(compressionBlockSize - offsetInBlock, remainingSize);
var sizeInBlock = (int)Math.Min(compressionBlockSize - offsetInBlock, remainingSize);
Buffer.BlockCopy(src, (int) offsetInBlock, dst, dstOffset, sizeInBlock);
offsetInBlock = 0;
remainingSize -= sizeInBlock;
@ -249,5 +288,111 @@ namespace PakReader.Pak.IO
};
compressionStream.Read(outData, 0, outData.Length);
}
private void ReadIndex(string directoryName, FIoDirectoryIndexHandle dir, IDictionary<string, FIoStoreEntry> outFiles)
{
while (dir.IsValid())
{
var subDirectoryName = string.Concat(directoryName, GetDirectoryName(dir), "/");
var file = GetFile(dir);
while (file.IsValid())
{
var name = GetFileName(file);
var path = string.Concat(subDirectoryName, name);
var data = GetFileData(file);
outFiles[path] = new FIoStoreEntry(this, data, path, CaseSensitive);
file = GetNextFile(file);
}
ReadIndex(subDirectoryName, GetChildDirectory(dir), outFiles);
dir = GetNextDirectory(dir);
}
}
public bool TryGetFile(string path, out ArraySegment<byte> ret1, out ArraySegment<byte> ret2, out ArraySegment<byte> ret3)
{
if (!string.IsNullOrEmpty(path) && Files.TryGetValue(CaseSensitive ? path : path.ToLowerInvariant(), out var entry))
{
ret1 = entry.GetData();
if (entry.HasUexp())
{
ret2 = (entry.Uexp as FIoStoreEntry)?.GetData();
ret3 = (entry.Ubulk as FIoStoreEntry)?.GetData();
return true;
}
else // return a fail but keep the uasset data
{
ret2 = null;
ret3 = null;
return false;
}
}
ret1 = null;
ret2 = null;
ret3 = null;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetPartialKey(string partialKey, out string key)
{
foreach (string path in Files.Keys)
{
if (Regex.Match(path, partialKey, RegexOptions.IgnoreCase).Success)
{
key = path;
return true;
}
}
key = string.Empty;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetCaseInsensiteveValue(string key, out FIoStoreEntry value)
{
foreach (var r in Files)
{
if (r.Key.Equals(key, StringComparison.CurrentCultureIgnoreCase))
{
value = r.Value;
return true;
}
}
value = null;
return false;
}
public IEnumerator<KeyValuePair<string, FIoStoreEntry>> GetEnumerator()
{
return Files.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable) Files).GetEnumerator();
}
public int Count => Files.Count;
public bool ContainsKey(string key)
{
return Files.ContainsKey(key);
}
public bool TryGetValue(string key, out FIoStoreEntry value)
{
return Files.TryGetValue(key, out value);
}
public FIoStoreEntry this[string key] => Files[key];
public IEnumerable<string> Keys => Files.Keys;
public IEnumerable<FIoStoreEntry> Values => Files.Values;
}
}

View File

@ -0,0 +1,26 @@
namespace FModel.PakReader.IO
{
public readonly struct FFragment
{
public const uint SkipMax = 127;
public const uint ValueMax = 127;
public const uint SkipNumMask = 0x007fu;
public const uint HasZeroMask = 0x0080u;
public const int ValueNumShift = 9;
public const uint IsLastMask = 0x0100u;
public readonly byte SkipNum; // Number of properties to skip before values
public readonly bool HasAnyZeroes;
public readonly byte ValueNum; // Number of subsequent property values stored
public readonly bool IsLast; // Is this the last fragment of the header?
public FFragment(ushort packed)
{
SkipNum = (byte) (packed & SkipNumMask);
HasAnyZeroes = (packed & HasZeroMask) != 0;
ValueNum = (byte) (packed >> ValueNumShift);
IsLast = (packed & IsLastMask) != 0;
}
}
}

View File

@ -0,0 +1,11 @@
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public class FImportDesc
{
public FName Name;
public FPackageObjectIndex GlobalImportIndex;
public FExportDesc Export;
}
}

View File

@ -1,6 +1,6 @@
using System.IO;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public struct FIoChunkHash
{

View File

@ -1,9 +1,8 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public readonly struct FIoChunkId
{
@ -18,11 +17,18 @@ namespace PakReader.Pak.IO
Id = reader.ReadBytes(12);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public FIoChunkId(ulong chunkId, ushort chunkIndex, EIoChunkType ioChunkType)
{
Id = new byte[12];
Buffer.BlockCopy(BitConverter.GetBytes(chunkId), 0, Id, 0, sizeof(ulong));
Buffer.BlockCopy(BitConverter.GetBytes(chunkIndex), 0, Id, sizeof(ulong), sizeof(ushort));
Id[11] = (byte)ioChunkType;
}
public override int GetHashCode()
{
var hash = 5381;
for (int i = 0; i < 12; i++)
for (var i = 0; i < 12; i++)
{
hash = hash * 33 + Id[i];
}
@ -30,18 +36,29 @@ namespace PakReader.Pak.IO
return hash;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool Equals(object? obj)
{
if (!(obj is FIoChunkId cast)) return false;
if (!(obj is FIoChunkId cast))
{
return false;
}
return Id.SequenceEqual(cast.Id);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator ==(FIoChunkId a, FIoChunkId b) => a.Id.SequenceEqual(b.Id);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool operator !=(FIoChunkId a, FIoChunkId b) => !a.Id.SequenceEqual(b.Id);
public override string ToString() => BitConverter.ToString(Id).Replace("-","");
public static bool operator ==(FIoChunkId a, FIoChunkId b)
{
return a.Id.SequenceEqual(b.Id);
}
public static bool operator !=(FIoChunkId a, FIoChunkId b)
{
return !a.Id.SequenceEqual(b.Id);
}
public override string ToString()
{
return BitConverter.ToString(Id).Replace("-", "");
}
}
}

View File

@ -1,6 +1,6 @@
using System.IO;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public struct FIoContainerId
{

View File

@ -1,6 +1,6 @@
using System.IO;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public readonly struct FIoDirectoryIndexEntry
{

View File

@ -1,10 +1,11 @@
using System.Runtime.CompilerServices;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public readonly struct FIoDirectoryIndexHandle
{
public static FIoDirectoryIndexHandle InvalidHandle = new FIoDirectoryIndexHandle(uint.MaxValue);
public static FIoDirectoryIndexHandle Root = new FIoDirectoryIndexHandle(0);
private readonly uint _handle;
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@ -1,6 +1,6 @@
using System.IO;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public class FIoDirectoryIndexResource
{
@ -9,18 +9,22 @@ namespace PakReader.Pak.IO
public readonly FIoFileIndexEntry[] FileEntries;
public readonly string[] StringTable;
public FIoDirectoryIndexResource(Stream directoryIndexStream)
public FIoDirectoryIndexResource(Stream directoryIndexStream, bool caseSensitive)
{
using var reader = new BinaryReader(directoryIndexStream);
MountPoint = reader.ReadFString();
if (MountPoint.StartsWith("../../.."))
{
MountPoint = MountPoint[9..];
MountPoint = MountPoint[8..];
}
else
{
// Weird mount point location...
MountPoint = "";
MountPoint = "/";
}
if (!caseSensitive)
{
MountPoint = MountPoint.ToLowerInvariant();
}
DirectoryEntries = reader.ReadTArray(() => new FIoDirectoryIndexEntry(reader));
FileEntries = reader.ReadTArray(() => new FIoFileIndexEntry(reader));

View File

@ -1,6 +1,6 @@
using System.IO;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public readonly struct FIoFileIndexEntry
{

View File

@ -0,0 +1,133 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public class FIoGlobalData
{
public readonly FNameEntrySerialized[] GlobalNameMap;
public readonly List<ulong> GlobalNameHashes;
public readonly Dictionary<FPackageObjectIndex, FScriptObjectDesc> ScriptObjectByGlobalId;
public FIoGlobalData(FFileIoStoreReader globalReader, IReadOnlyCollection<FFileIoStoreReader> allReaders)
{
var globalNamesIoBuffer = globalReader.Read(new FIoChunkId(0, 0, EIoChunkType.LoaderGlobalNames));
var globalNameHashesIoBuffer = globalReader.Read(new FIoChunkId(0, 0, EIoChunkType.LoaderGlobalNameHashes));
var globalNameMap = new List<FNameEntrySerialized>();
GlobalNameHashes = new List<ulong>();
FNameEntrySerialized.LoadNameBatch(globalNameMap, GlobalNameHashes, globalNamesIoBuffer, globalNameHashesIoBuffer);
GlobalNameMap = globalNameMap.ToArray();
var initialLoadIoBuffer = globalReader.Read(new FIoChunkId(0, 0, EIoChunkType.LoaderInitialLoadMeta));
var initialLoadIoReader = new BinaryReader(new MemoryStream(initialLoadIoBuffer, false));
var numScriptObjects = initialLoadIoReader.ReadInt32();
var scriptObjectByGlobalIdKeys = new FPackageObjectIndex[numScriptObjects];
var scriptObjectByGlobalIdValues = new FScriptObjectDesc[numScriptObjects];
var globalIndices = new Dictionary<FPackageObjectIndex, int>(numScriptObjects);
for (var i = 0; i < numScriptObjects; i++)
{
var scriptObjectEntry = new FScriptObjectEntry(initialLoadIoReader);
globalIndices.TryAdd(scriptObjectEntry.GlobalIndex, i);
var mappedName = new FMappedName(scriptObjectEntry.ObjectName, GlobalNameMap, null);
if (!mappedName.IsGlobal())
{
Debug.WriteLine(i);
}
scriptObjectByGlobalIdKeys[i] = scriptObjectEntry.GlobalIndex;
scriptObjectByGlobalIdValues[i] = new FScriptObjectDesc(GlobalNameMap[(int)mappedName.GetIndex()], mappedName, scriptObjectEntry);
}
for (var i = 0; i < numScriptObjects; i++)
{
var scriptObjectDesc = scriptObjectByGlobalIdValues[i];
if (!scriptObjectDesc.FullName.IsNone)
{
continue;
}
var scriptObjectStack = new Stack<FScriptObjectDesc>();
var current = i;
string fullName = string.Empty;
while (current > 0)
{
var currentDesc = scriptObjectByGlobalIdValues[current];
if (!currentDesc.FullName.IsNone)
{
fullName = currentDesc.FullName.String;
break;
}
scriptObjectStack.Push(currentDesc);
globalIndices.TryGetValue(currentDesc.OuterIndex, out current);
}
while (scriptObjectStack.Count != 0)
{
var currentStack = scriptObjectStack.Pop();
if (fullName.Length == 0 || fullName.EndsWith('/'))
{
fullName = string.Concat(fullName, currentStack.Name.String);
}
else
{
fullName = string.Concat(fullName, "/", currentStack.Name.String);
}
currentStack.FullName = new FName(fullName);
}
}
ScriptObjectByGlobalId = Enumerable.Range(0, numScriptObjects).ToDictionary(i => scriptObjectByGlobalIdKeys[i], i => scriptObjectByGlobalIdValues[i]);
var packageByPackageIdMap = new Dictionary<FPackageId, FPackageStoreEntry>();
foreach (var reader in allReaders)
{
var headerChunkId = new FIoChunkId(reader.ContainerId.Id, 0, EIoChunkType.ContainerHeader);
if (reader.DoesChunkExist(headerChunkId) && !reader.IsEncrypted)
{
var buffer = reader.Read(headerChunkId);
using var headerReader = new BinaryReader(new MemoryStream(buffer, false));
var containerHeader = new FContainerHeader(headerReader);
using var storeEntryReader = new BinaryReader(new MemoryStream(containerHeader.StoreEntries));
var containerPackages = new List<FPackageDesc>();
for (var i = 0; i < containerHeader.PackageCount; i++)
{
var containerEntry = new FPackageStoreEntry(storeEntryReader);
var packageId = containerHeader.PackageIds[i];
if (!packageByPackageIdMap.TryGetValue(packageId, out var packageDesc))
{
/*
packageDesc = new FPackageDesc();
packageDesc.PackageId = packageId;
packageDesc.Size = containerEntry.ExportBundlesSize;
packageDesc.Exports = new FExportDesc[containerEntry.ExportCount];
packageDesc.ExportBundleCount = containerEntry.ExportBundleCount;
packageDesc.LoadOrder = containerEntry.LoadOrder;
*/
packageByPackageIdMap[packageId] = containerEntry;
}
//containerPackages.Add(packageDesc);
}
}
}
}
}
}

View File

@ -0,0 +1,26 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FIoOffsetAndLength
{
// We use 5 bytes for offset and size, this is enough to represent
// an offset and size of 1PB
public readonly byte[] OffsetAndLength;
public ulong Offset => OffsetAndLength[4]
| ((ulong) OffsetAndLength[3] << 8)
| ((ulong) OffsetAndLength[2] << 16)
| ((ulong) OffsetAndLength[1] << 24)
| ((ulong) OffsetAndLength[0] << 32);
public ulong Length => OffsetAndLength[9]
| ((ulong) OffsetAndLength[8] << 8)
| ((ulong) OffsetAndLength[7] << 16)
| ((ulong) OffsetAndLength[6] << 24)
| ((ulong) OffsetAndLength[5] << 32);
public FIoOffsetAndLength(BinaryReader reader)
{
OffsetAndLength = reader.ReadBytes(5 + 5);
}
}
}

View File

@ -0,0 +1,28 @@
namespace FModel.PakReader.IO
{
public class FIoStoreEntry : ReaderEntry
{
public readonly FFileIoStoreReader ioStore;
public override string ContainerName => ioStore.FileName;
public override string Name { get; }
public readonly uint UserData;
public FIoChunkId ChunkId => ioStore.TocResource.ChunkIds[UserData];
public FIoOffsetAndLength OffsetLength => ioStore.Toc[ChunkId];
public long Offset => (long) OffsetLength.Offset;
public long Length => (long) OffsetLength.Length;
public FIoStoreEntry(FFileIoStoreReader ioStore, uint userData, string name, bool caseSensitive)
{
this.ioStore = ioStore;
UserData = userData;
if (!caseSensitive)
name = name.ToLowerInvariant();
if (name.StartsWith('/'))
name = name.Substring(1);
Name = name;
}
public byte[] GetData() => ioStore.Read(ChunkId);
}
}

View File

@ -1,7 +1,7 @@
using System.IO;
using System.Runtime.CompilerServices;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public readonly struct FIoStoreTocCompressedBlockEntry
{

View File

@ -1,6 +1,6 @@
using System.IO;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public readonly struct FIoStoreTocEntryMeta
{

View File

@ -1,4 +1,4 @@
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public enum FIoStoreTocEntryMetaFlags : byte
{

View File

@ -1,8 +1,8 @@
using System.IO;
using System.Linq;
using PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.Objects;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public enum EIoStoreTocVersion : byte
{

View File

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO;
using System.Text;
using FModel.PakReader.Parsers.Objects;
using FModel.Utils;
using PakReader.Parsers.Objects;
namespace PakReader.Pak.IO
namespace FModel.PakReader.IO
{
public enum EIoStoreTocReadOptions
{
@ -87,7 +85,7 @@ namespace PakReader.Pak.IO
ChunkBlockSignatures[i] = new FSHAHash(reader);
}
// You could very hashes here but nah
// You could verify hashes here but nah
}
// Directory index

View File

@ -0,0 +1,74 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
namespace FModel.PakReader.IO
{
public class FIterator : IEnumerator<(int Val, bool IsNonZero)>
{
private int _schemaIt;
private readonly BitArray _zeroMask;
private int _zeroMaskIndex;
private readonly IEnumerator<FFragment> _fragmentIt;
private int _remainingFragmentValues;
public FIterator(FUnversionedHeader header)
{
_zeroMask = header.ZeroMask;
_fragmentIt = header.Fragments.GetEnumerator();
Skip();
}
public bool MoveNext()
{
_schemaIt++;
_remainingFragmentValues--;
if (_fragmentIt.Current.HasAnyZeroes)
_zeroMaskIndex++;
if (_remainingFragmentValues == 0)
{
if (_fragmentIt.Current.IsLast)
return false;
_fragmentIt.MoveNext();
Skip();
}
return true;
}
private void Skip()
{
_schemaIt += _fragmentIt.Current.SkipNum;
while (_fragmentIt.Current.ValueNum == 0)
{
if (_fragmentIt.Current.IsLast)
throw new FileLoadException("Cannot receive last fragment in Skip()");
_fragmentIt.MoveNext();
_schemaIt += _fragmentIt.Current.SkipNum;
}
_remainingFragmentValues = _fragmentIt.Current.ValueNum;
}
public void Reset()
{
_schemaIt = _zeroMaskIndex = _remainingFragmentValues = 0;
_fragmentIt.Reset();
Skip();
}
public bool IsNonZero => !_fragmentIt.Current.HasAnyZeroes || !_zeroMask[_zeroMaskIndex];
public (int Val, bool IsNonZero) Current => (_schemaIt, IsNonZero);
object IEnumerator.Current => Current;
public void Dispose()
{
_fragmentIt.Dispose();
}
}
}

View File

@ -0,0 +1,71 @@
using FModel.PakReader.Parsers;
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public readonly struct FMappedName
{
public const uint InvalidIndex = ~0u;
public const uint IndexBits = 30u;
public const uint IndexMask = (1u << (int)IndexBits) - 1u;
public const uint TypeMask = ~IndexMask;
public const uint TypeShift = IndexBits;
private readonly IoPackageReader _reader;
private readonly FNameEntrySerialized[] _globalNameMap =>
_reader != null ? _reader.GlobalData.GlobalNameMap : __globalNameMap;
private readonly FNameEntrySerialized[] __globalNameMap;
private readonly FNameEntrySerialized[] _localNameMap =>
_reader != null ? _reader.NameMap : __localNameMap;
private readonly FNameEntrySerialized[] __localNameMap;
public readonly uint Index;
public readonly uint Number;
public string String
{
get
{
var index = GetIndex();
var nameMap = IsGlobal() ? _globalNameMap : _localNameMap;
if (nameMap != null && index < _globalNameMap.Length)
{
return nameMap[index].Name;
}
return null;
}
}
public FMappedName(IoPackageReader reader)
{
Index = reader.ReadUInt32();
Number = reader.ReadUInt32();
_reader = reader;
__globalNameMap = null;
__localNameMap = null;
}
public FMappedName(FMinimalName minimalName, FNameEntrySerialized[] globalNameMap, FNameEntrySerialized[] localNameMap)
{
Index = minimalName.Index.Value;
Number = (uint)minimalName.Number;
_reader = null;
__globalNameMap = globalNameMap;
__localNameMap = localNameMap;
}
public uint GetIndex()
{
return Index & IndexMask;
}
public bool IsGlobal()
{
return (Index & TypeMask) >> (int)TypeShift != 0;
}
public override string ToString() => String;
}
}

View File

@ -0,0 +1,22 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FMinimalName
{
public readonly FNameEntryId Index;
public readonly int Number; // #define NAME_NO_NUMBER_INTERNAL 0
public FMinimalName(BinaryReader reader)
{
Index = new FNameEntryId(reader);
Number = reader.ReadInt32();
}
public FMinimalName(FNameEntryId inIndex, int inNumber)
{
Index = inIndex;
Number = inNumber;
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FNameEntryId : IEquatable<FNameEntryId>
{
public readonly uint Value;
public FNameEntryId(uint value)
{
Value = value;
}
public FNameEntryId(BinaryReader reader)
{
Value = reader.ReadUInt32();
}
public bool Equals(FNameEntryId other)
{
return Value == other.Value;
}
public override bool Equals(object obj)
{
return obj is FNameEntryId other && Equals(other);
}
public override int GetHashCode()
{
return (int)Value;
}
public static bool operator ==(FNameEntryId left, FNameEntryId right)
{
return left.Equals(right);
}
public static bool operator !=(FNameEntryId left, FNameEntryId right)
{
return !left.Equals(right);
}
}
}

View File

@ -0,0 +1,18 @@
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public class FPackageDesc
{
public FPackageId PackageId;
public FName PackageName;
public ulong Size = 0;
public uint LoadOrder = uint.MaxValue;
public uint PackageFlags = 0;
public int NameCount = -1;
public int ExportBundleCount = -1;
public FPackageLocation[] Locations;
public FImportDesc[] Imports;
public FExportDesc[] Exports;
}
}

View File

@ -0,0 +1,21 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FPackageId
{
public readonly ulong Id;
public FPackageId(ulong id)
{
Id = id;
}
public FPackageId(BinaryReader reader)
{
Id = reader.ReadUInt64();
}
public FIoChunkId CreateIoChunkId(ushort chunkIndex, EIoChunkType type = EIoChunkType.ExportBundleData) => new FIoChunkId(Id, chunkIndex, type);
}
}

View File

@ -0,0 +1,10 @@
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public readonly struct FPackageLocation
{
public readonly FName ContainerName;
public readonly ulong Offset;
}
}

View File

@ -0,0 +1,71 @@
using System;
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FPackageObjectIndex : IEquatable<FPackageObjectIndex>
{
public const int IndexBits = 62;
public const ulong IndexMask = (1UL << IndexBits) - 1UL;
public const ulong TypeMask = ~IndexMask;
public const int TypeShift = IndexBits;
public const ulong Invalid = ~0UL;
private readonly ulong _typeAndId;
public EType Type => (EType) (_typeAndId >> TypeShift);
public ulong Value => _typeAndId & IndexMask;
public bool IsNull => _typeAndId == Invalid;
public bool IsExport => Type == EType.Export;
public bool IsImport => IsScriptImport || IsPackageImport;
public bool IsScriptImport => Type == EType.ScriptImport;
public bool IsPackageImport => Type == EType.PackageImport;
public uint AsExport => (uint) _typeAndId;
public FPackageObjectIndex(BinaryReader reader)
{
//TypeAndId = Invalid;
_typeAndId = reader.ReadUInt64();
}
public FPackageObjectIndex(ulong typeAndId)
{
_typeAndId = typeAndId;
}
public bool Equals(FPackageObjectIndex other)
{
return _typeAndId == other._typeAndId;
}
public override bool Equals(object obj)
{
return obj is FPackageObjectIndex other && Equals(other);
}
public override int GetHashCode()
{
return _typeAndId.GetHashCode();
}
public static bool operator ==(FPackageObjectIndex left, FPackageObjectIndex right)
{
return left.Equals(right);
}
public static bool operator !=(FPackageObjectIndex left, FPackageObjectIndex right)
{
return !left.Equals(right);
}
}
public enum EType
{
Export,
ScriptImport,
PackageImport,
Null,
TypeCount = Null
};
}

View File

@ -0,0 +1,36 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FPackageStoreEntry
{
public readonly ulong ExportBundlesSize;
public readonly int ExportCount;
public readonly int ExportBundleCount;
public readonly uint LoadOrder;
public readonly FPackageId[] ImportedPackages;
public FPackageStoreEntry(BinaryReader reader)
{
ExportBundlesSize = reader.ReadUInt64();
ExportCount = reader.ReadInt32();
ExportBundleCount = reader.ReadInt32();
LoadOrder = reader.ReadUInt32();
reader.BaseStream.Position += 4; // Padding
var pos = reader.BaseStream.Position;
var packageStoreArrayNum = reader.ReadInt32();
var packageStoreOffsetToData = reader.ReadUInt32();
reader.BaseStream.Position = pos + packageStoreOffsetToData;
ImportedPackages = new FPackageId[packageStoreArrayNum];
for (var i = 0; i < packageStoreArrayNum; i++)
{
ImportedPackages[i] = new FPackageId(reader);
}
reader.BaseStream.Position = pos + 8;
}
}
}

View File

@ -0,0 +1,39 @@
using FModel.PakReader.Parsers;
namespace FModel.PakReader.IO
{
public class FPackageSummary
{
public readonly FMappedName Name;
public readonly FMappedName SourceName;
public readonly uint PackageFlags;
public readonly uint CookedHeaderSize;
public readonly int NameMapNamesOffset;
public readonly int NameMapNamesSize;
public readonly int NameMapHashesOffset;
public readonly int NameMapHashesSize;
public readonly int ImportMapOffset;
public readonly int ExportMapOffset;
public readonly int ExportBundlesOffset;
public readonly int GraphDataOffset;
public readonly int GraphDataSize;
public FPackageSummary(IoPackageReader reader)
{
Name = new FMappedName(reader);
SourceName = new FMappedName(reader);
PackageFlags = reader.ReadUInt32();
CookedHeaderSize = reader.ReadUInt32();
NameMapNamesOffset = reader.ReadInt32();
NameMapNamesSize = reader.ReadInt32();
NameMapHashesOffset = reader.ReadInt32();
NameMapHashesSize = reader.ReadInt32();
ImportMapOffset = reader.ReadInt32();
ExportMapOffset = reader.ReadInt32();
ExportBundlesOffset = reader.ReadInt32();
GraphDataOffset = reader.ReadInt32();
GraphDataSize = reader.ReadInt32();
reader.SkipBytes(4); // Padding
}
}
}

View File

@ -0,0 +1,20 @@
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.IO
{
public class FScriptObjectDesc
{
public readonly FName Name;
public FName FullName;
public readonly FPackageObjectIndex GlobalImportIndex;
public readonly FPackageObjectIndex OuterIndex;
public FScriptObjectDesc(FNameEntrySerialized name, FMappedName fMappedName, FScriptObjectEntry fScriptObjectEntry)
{
Name = new FName(name.Name, (int)fMappedName.Index, (int)fMappedName.Number);
FullName = default;
GlobalImportIndex = fScriptObjectEntry.GlobalIndex;
OuterIndex = fScriptObjectEntry.OuterIndex;
}
}
}

View File

@ -0,0 +1,20 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FScriptObjectEntry
{
public readonly FMinimalName ObjectName;
public readonly FPackageObjectIndex GlobalIndex;
public readonly FPackageObjectIndex OuterIndex;
public readonly FPackageObjectIndex CDOClassIndex;
public FScriptObjectEntry(BinaryReader reader)
{
ObjectName = new FMinimalName(reader);
GlobalIndex = new FPackageObjectIndex(reader);
OuterIndex = new FPackageObjectIndex(reader);
CDOClassIndex = new FPackageObjectIndex(reader);
}
}
}

View File

@ -0,0 +1,17 @@
using System.IO;
namespace FModel.PakReader.IO
{
public readonly struct FSerializedNameHeader
{
private readonly byte[] _data;
public bool IsUtf16 => (_data[0] & 0x80u) != 0;
public uint Length => ((_data[0] & 0x7Fu) << 8) + _data[1];
public FSerializedNameHeader(BinaryReader reader)
{
_data = reader.ReadBytes(2);
}
}
}

View File

@ -0,0 +1,65 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using FModel.Utils;
namespace FModel.PakReader.IO
{
public class FUnversionedHeader
{
public List<FFragment> Fragments = new List<FFragment>();
public BitArray ZeroMask;
public readonly bool HasNonZeroValues;
public bool HasValues => HasNonZeroValues | (ZeroMask.Count > 0);
public FUnversionedHeader(BinaryReader reader)
{
FFragment fragment;
int zeroMaskNum = 0;
uint unmaskedNum = 0;
do
{
fragment = new FFragment(reader.ReadUInt16());
Fragments.Add(fragment);
if (fragment.HasAnyZeroes)
zeroMaskNum += fragment.ValueNum;
else
unmaskedNum += fragment.ValueNum;
} while (!fragment.IsLast);
if (zeroMaskNum > 0)
{
LoadZeroMaskData(reader, zeroMaskNum, out ZeroMask);
HasNonZeroValues = unmaskedNum > 0 || ZeroMask.Contains(false);
}
else
{
ZeroMask = new BitArray(new int[8]);
HasNonZeroValues = unmaskedNum > 0;
}
}
private static void LoadZeroMaskData(BinaryReader reader, int numBits, out BitArray data)
{
if (numBits <= 8)
{
data = new BitArray(new[] { reader.ReadByte() });
}
else if (numBits <= 16)
{
data = new BitArray(new []{ (int) reader.ReadUInt16() });
}
else
{
var num = numBits.DivideAndRoundUp(32);
var intData = new int[num];
for (int idx = 0; idx < num; idx++)
{
intData[idx] = reader.ReadInt32();
}
data = new BitArray(intData);
}
}
}
}

View File

@ -0,0 +1,74 @@
using System.IO;
using FModel.PakReader.Parsers;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using Newtonsoft.Json;
namespace FModel.PakReader.IO
{
public class IoPackage : Package
{
private byte[] UAsset;
private byte[] UBulk;
private FIoStoreEntry _entry;
private IoPackageReader _reader;
private string _jsonData = null;
internal IoPackage(byte[] asset, byte[] bulk, FIoStoreEntry entry)
{
UAsset = asset;
UBulk = bulk;
_entry = entry;
}
public IoPackageReader Reader
{
get
{
if (_reader == null)
{
var asset = new MemoryStream(UAsset);
var bulk = UBulk != null ? new MemoryStream(UBulk) : null;
asset.Position = 0;
if (bulk != null)
bulk.Position = 0;
return _reader = new IoPackageReader(asset, bulk, Globals.GlobalData, _entry.ioStore, true);
}
return _reader;
}
}
public override string JsonData
{
get
{
if (string.IsNullOrEmpty(_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 => Exports[i].GetJsonDict(),
_ => Exports[i]
}
};
}
#if DEBUG
return JsonConvert.SerializeObject(ret, Formatting.Indented);
#else
return _jsonData = JsonConvert.SerializeObject(ret, Formatting.Indented);
#endif
}
return _jsonData;
}
}
public override FName[] ExportTypes => Reader.DataExportTypes;
public override IUExport[] Exports => Reader.DataExports;
}
}

View File

@ -0,0 +1,31 @@
using Newtonsoft.Json;
namespace FModel.PakReader.IO
{
public class PropertyInfo
{
public string Name;
public string Type;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string StructType;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? Bool;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string EnumName;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string InnerType;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string ValueType;
public PropertyInfo(string name, string type, string structType = null, bool? b = null, string enumName = null, string innerType = null, string valueType = null)
{
Name = name;
Type = type;
StructType = structType;
Bool = b;
EnumName = enumName;
InnerType = innerType;
ValueType = valueType;
}
}
}

View File

@ -1,7 +1,7 @@
using System.IO;
using PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.Objects;
namespace PakReader
namespace FModel.PakReader
{
public class LocMetaReader
{

View File

@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.Objects;
namespace PakReader
namespace FModel.PakReader
{
public class LocResReader
{

View File

@ -0,0 +1,64 @@
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader
{
public abstract class Package
{
public abstract string JsonData { get; }
public abstract FName[] ExportTypes { get; }
public abstract IUExport[] Exports { get; }
public T GetExport<T>() where T : IUExport
{
var exports = Exports;
for (int i = 0; i < exports.Length; i++)
{
if (exports[i] is T)
return (T)exports[i];
}
return default;
}
public T GetIndexedExport<T>(int index) where T : IUExport
{
var exports = Exports;
var foundCount = 0;
for (var i = 0; i < exports.Length; i++)
{
if (exports[i] is T cast)
{
if (foundCount == index)
return cast;
foundCount++;
}
}
return default;
}
public T GetTypedExport<T>(string exportType) where T : IUExport
{
int index = 0;
var exportTypes = ExportTypes;
for (int i = 0; i < exportTypes.Length; i++)
{
if (exportTypes[i].String == exportType)
index = i;
}
return (T)Exports[index];
}
public bool HasExport() => Exports != default;
}
public sealed class ExportList
{
public string JsonData;
public FName[] ExportTypes;
public IUExport[] Exports;
}
public sealed class JsonExport
{
public string ExportType;
public object ExportValue;
}
}

View File

@ -1,4 +1,4 @@
namespace PakReader.Pak
namespace FModel.PakReader.Pak
{
class DefaultPakFilter : IPakFilter
{

View File

@ -1,26 +0,0 @@
using System.IO;
namespace PakReader.Pak.IO
{
public readonly struct FIoOffsetAndLength
{
// We use 5 bytes for offset and size, this is enough to represent
// an offset and size of 1PB
public readonly byte[] OffsetAndLength;
public long Offset => OffsetAndLength[4]
| ((long) OffsetAndLength[3] << 8)
| ((long) OffsetAndLength[2] << 16)
| ((long) OffsetAndLength[1] << 24)
| ((long) OffsetAndLength[0] << 32);
public long Length => OffsetAndLength[9]
| ((long) OffsetAndLength[8] << 8)
| ((long) OffsetAndLength[7] << 16)
| ((long) OffsetAndLength[6] << 24)
| ((long) OffsetAndLength[5] << 32);
public FIoOffsetAndLength(BinaryReader reader)
{
OffsetAndLength = reader.ReadBytes(5 + 5);
}
}
}

View File

@ -1,4 +1,4 @@
namespace PakReader.Pak
namespace FModel.PakReader.Pak
{
public interface IPakFilter
{

View File

@ -8,9 +8,8 @@ using System.Text.RegularExpressions;
using FModel.Logger;
using FModel.PakReader.Parsers.Objects;
using FModel.Utils;
using PakReader.Parsers.Objects;
namespace PakReader.Pak
namespace FModel.PakReader.Pak
{
public sealed class PakFileReader : IReadOnlyDictionary<string, FPakEntry>
{
@ -42,7 +41,7 @@ namespace PakReader.Pak
// Buffered streams increase performance dramatically
public PakFileReader(string file, bool caseSensitive = true)
: this(file, new BufferedStream(new FileInfo(file).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)), caseSensitive)
: this(file, new FileInfo(file).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite), caseSensitive)
{ }
public PakFileReader(string path, Stream stream, bool caseSensitive = true)
@ -446,15 +445,15 @@ namespace PakReader.Pak
ret1 = entry.GetData(Stream, AesKey, Info.CompressionMethods);
if (entry.HasUexp())
{
ret2 = entry.Uexp.GetData(Stream, AesKey, Info.CompressionMethods);
ret3 = entry.HasUbulk() ? entry.Ubulk.GetData(Stream, AesKey, Info.CompressionMethods) : null;
ret2 = ((FPakEntry)entry.Uexp).GetData(Stream, AesKey, Info.CompressionMethods);
ret3 = entry.HasUbulk() ? ((FPakEntry)entry.Ubulk).GetData(Stream, AesKey, Info.CompressionMethods) : null;
return true;
}
else // return a fail but keep the uasset data
{
ret2 = null;
ret3 = null;
return false;
return entry.GetExtension().Contains(".ufont", StringComparison.OrdinalIgnoreCase);
}
}
ret1 = null;

View File

@ -1,7 +1,7 @@
using System.Collections;
using System.Collections.Generic;
namespace PakReader.Pak
namespace FModel.PakReader.Pak
{
// Currently only supports strings that start with a value
// I've just implemented this myself to save tons of memory so you don't have to

View File

@ -2,9 +2,9 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.Objects;
namespace PakReader.Pak
namespace FModel.PakReader.Pak
{
public class PakIndex : IEnumerable<string>
{

View File

@ -1,19 +1,27 @@
using System;
using System.IO;
using FModel.PakReader.Parsers;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using Newtonsoft.Json;
using PakReader.Parsers;
using PakReader.Parsers.Class;
using PakReader.Parsers.Objects;
namespace PakReader.Pak
namespace FModel.PakReader.Pak
{
public readonly struct PakPackage
public sealed class PakPackage : Package
{
readonly ArraySegment<byte> UAsset;
readonly ArraySegment<byte> UExp;
readonly ArraySegment<byte> UBulk;
internal PakPackage(ArraySegment<byte> asset, ArraySegment<byte> exp, ArraySegment<byte> bulk)
{
UAsset = asset;
UExp = exp;
UBulk = bulk;
exports = new ExportList();
}
public string JsonData
public override string JsonData
{
get
{
@ -37,7 +45,7 @@ namespace PakReader.Pak
return exports.JsonData;
}
}
public FName[] ExportTypes
public override FName[] ExportTypes
{
get
{
@ -51,14 +59,14 @@ namespace PakReader.Pak
if (bulk != null)
bulk.Position = 0;
var p = new PackageReader(asset, exp, bulk);
var p = new LegacyPackageReader(asset, exp, bulk);
exports.Exports = p.DataExports;
return exports.ExportTypes = p.DataExportTypes;
}
return exports.ExportTypes;
}
}
public IUExport[] Exports
public override IUExport[] Exports
{
get
{
@ -72,74 +80,17 @@ namespace PakReader.Pak
if (bulk != null)
bulk.Position = 0;
var p = new PackageReader(asset, exp, bulk);
var p = new LegacyPackageReader(asset, exp, bulk);
exports.ExportTypes = p.DataExportTypes;
return exports.Exports = p.DataExports;
}
return exports.Exports;
}
}
readonly ExportList exports;
private readonly ExportList exports;
internal PakPackage(ArraySegment<byte> asset, ArraySegment<byte> exp, ArraySegment<byte> bulk)
{
UAsset = asset;
UExp = exp;
UBulk = bulk;
exports = new ExportList();
}
public T GetExport<T>() where T : IUExport
{
var exports = Exports;
for (int i = 0; i < exports.Length; i++)
{
if (exports[i] is T)
return (T)exports[i];
}
return default;
}
public T GetIndexedExport<T>(int index) where T : IUExport
{
var exports = Exports;
int foundCount = 0;
for (int i = 0; i < exports.Length; i++)
{
if (exports[i] is T)
{
if (foundCount == index)
return (T)exports[i];
foundCount++;
}
}
return default;
}
public T GetTypedExport<T>(string exportType) where T : IUExport
{
int index = 0;
var exportTypes = ExportTypes;
for (int i = 0; i < exportTypes.Length; i++)
{
if (exportTypes[i].String == exportType)
index = i;
}
return (T)Exports[index];
}
public bool HasExport() => exports != null;
// 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;
}
sealed class JsonExport
{
public string ExportType;
public object ExportValue;
}
}
}

View File

@ -1,8 +1,8 @@
using PakReader.Parsers.PropertyTagData;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using FModel.PakReader.Parsers.PropertyTagData;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
/// <summary>
/// IReadOnlyDictionary<string, object> is only used to be able to iterate over properties

View File

@ -1,9 +1,9 @@
using PakReader.Parsers.Objects;
using System;
using System;
using System.IO;
using System.Linq;
using FModel.PakReader.Parsers.Objects;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public sealed class UAkMediaAssetData : UObject
{

View File

@ -1,18 +1,21 @@
using PakReader.Parsers.Objects;
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using FModel.PakReader.Parsers.Objects;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public sealed class UCurveTable : IUExport
{
public ECurveTableMode CurveTableMode { get; }
readonly Dictionary<string, object> RowMap;
internal UCurveTable(PackageReader reader)
{
_ = new UObject(reader); //will break
if (!(reader is IoPackageReader))
{
_ = new UObject(reader); //will break
}
int NumRows = reader.ReadInt32();
CurveTableMode = (ECurveTableMode)reader.ReadByte();

View File

@ -3,12 +3,16 @@ using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace PakReader.Parsers.Class
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Utils;
namespace FModel.PakReader.Parsers.Class
{
public sealed class UDataTable : IUExport
{
/** Map of name of row to row data structure. */
readonly Dictionary<string, object> RowMap;
private readonly Dictionary<string, object> RowMap;
internal UDataTable(PackageReader reader)
{
@ -30,6 +34,55 @@ namespace PakReader.Parsers.Class
}
}
internal UDataTable(IoPackageReader reader, IReadOnlyDictionary<int, PropertyInfo> properties, string type)
{
var baseObj = new UObject(reader, properties, type: type);
if (!baseObj.TryGetValue("RowStruct", out var rowStructProp) || !(rowStructProp is ObjectProperty rowStruct) || !rowStruct.Value.IsImport)
{
return;
}
var rowStructimportIndex = rowStruct.Value.AsImport;
if (rowStructimportIndex >= reader.ImportMap.Length)
{
return;
}
var rowStructimport = reader.ImportMap[rowStructimportIndex];
if (rowStructimport.Type != EType.ScriptImport ||
!Globals.GlobalData.ScriptObjectByGlobalId.TryGetValue(rowStructimport, out var rowStrucDesc) ||
rowStrucDesc.Name.IsNone)
{
return;
}
if (!Globals.TypeMappings.TryGetValue(rowStrucDesc.Name.String, out var rowProperties))
{
FConsole.AppendText($"{reader.Summary.Name.String} can't be parsed yet (RowType: {rowStrucDesc.Name.String})", FColors.Red, true);
return;
}
var NumRows = reader.ReadInt32();
RowMap = new Dictionary<string, object>();
for (var i = 0; i < NumRows; i++)
{
var num = 1;
var RowName = reader.ReadFName().String ?? "";
var baseName = RowName;
while (RowMap.ContainsKey(RowName))
{
RowName = $"{baseName}_NK{num++:00}";
}
RowMap[RowName] = new UObject(reader, rowProperties, true, rowStrucDesc.Name.String);
}
}
public object this[string key] => RowMap[key];
public IEnumerable<string> Keys => RowMap.Keys;
public IEnumerable<object> Values => RowMap.Values;

View File

@ -1,10 +1,10 @@
using PakReader.Parsers.PropertyTagData;
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using FModel.PakReader.Parsers.PropertyTagData;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public sealed class UFontFace : IUExport
{
@ -14,18 +14,16 @@ namespace PakReader.Parsers.Class
internal UFontFace(PackageReader reader, Stream ufont)
{
FontFaceAsset = new UObject(reader, true);
foreach (KeyValuePair<string, object> prop in FontFaceAsset)
{
if (prop.Key.Equals("SourceFilename") && prop.Value is StrProperty str)
{
string FontFilename = Path.GetFileName(str.Value);
string folder = FModel.Properties.Settings.Default.OutputPath + "\\Fonts\\";
if (ufont != null)
{
using var fileStream = new FileStream(folder + FontFilename, FileMode.Create, FileAccess.Write);
ufont.CopyTo(fileStream);
}
if (FontFaceAsset.TryGetValue("SourceFilename", out var prop) && prop is StrProperty str)
{
string FontFilename = Path.GetFileName(str.Value);
string folder = Properties.Settings.Default.OutputPath + "\\Fonts\\";
if (ufont != null)
{
using var fileStream = new FileStream(folder + FontFilename, FileMode.Create, FileAccess.Write);
ufont.CopyTo(fileStream);
}
}
}

View File

@ -1,24 +1,101 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using FModel.Logger;
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
using FModel.Utils;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public class UObject : IUExport, IUStruct
{
readonly Dictionary<string, object> Dict;
private readonly Dictionary<string, object> Dict;
public UObject(IoPackageReader reader, IReadOnlyDictionary<int, PropertyInfo> properties, bool structFallback = false, string type = null)
{
Dict = new Dictionary<string, object>();
var header = new FUnversionedHeader(reader);
using var it = new FIterator(header);
#if DEBUG
var headerWritten = false;
do
{
if (properties.ContainsKey(it.Current.Val))
{
continue;
}
if (!headerWritten)
{
headerWritten = true;
FConsole.AppendText(string.Concat("\n", type ?? "Unknown", ": ", reader.Summary.Name.String), "#CA6C6C", true);
}
FConsole.AppendText($"Val: {it.Current.Val} (IsNonZero: {it.Current.IsNonZero})", FColors.Yellow, true);
}
while (it.MoveNext());
it.Reset();
#endif
var num = 1;
do
{
var (val, isNonZero) = it.Current;
if (properties.TryGetValue(val, out var propertyInfo))
{
if (isNonZero)
{
var obj = BaseProperty.ReadAsObject(reader, new FPropertyTag(propertyInfo), new FName(propertyInfo.Type), ReadType.NORMAL);
var key = Dict.ContainsKey(propertyInfo.Name) ? $"{propertyInfo.Name}_NK{num++:00}" : propertyInfo.Name;
Dict[key] = obj;
}
else
{
var obj = BaseProperty.ReadAsZeroObject(reader, new FPropertyTag(propertyInfo),
new FName(propertyInfo.Type));
var key = Dict.ContainsKey(propertyInfo.Name) ? $"{propertyInfo.Name}_NK{num++:00}" : propertyInfo.Name;
Dict[key] = obj;
}
}
else
{
if (!isNonZero)
{
// We are lucky: We don't know this property but it also has no content
DebugHelper.WriteLine($"Unknown property for {GetType().Name} with value {val} but it's zero so we are good");
}
else
{
DebugHelper.WriteLine($"Unknown property for {GetType().Name} with value {val}. Can't proceed serialization (Serialized {Dict.Count} properties till now)");
return;
//throw new FileLoadException($"Unknown property for {GetType().Name} with value {val}. Can't proceed serialization");
}
}
} while (it.MoveNext());
if (!structFallback && reader.ReadInt32() != 0/* && reader.Position + 16 <= maxSize*/)
{
new FGuid(reader);
}
}
// Empty UObject used for new package format when a property is zero
public UObject()
{
Dict = new Dictionary<string, object>();
}
// 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) { }
public UObject(PackageReader reader, long maxSize) : this(reader, maxSize, false) { }
public UObject(PackageReader reader) : this(reader, false) { }
// Structs that don't use binary serialization
// https://github.com/EpicGames/UnrealEngine/blob/7d9919ac7bfd80b7483012eab342cb427d60e8c9/Engine/Source/Runtime/CoreUObject/Private/UObject/Class.cpp#L2197
internal UObject(PackageReader reader, long maxSize, bool structFallback)
internal UObject(PackageReader reader, bool structFallback)
{
var properties = new Dictionary<string, object>();
int num = 1;
@ -46,7 +123,7 @@ namespace PakReader.Parsers.Class
}
Dict = properties;
if (!structFallback && reader.ReadInt32() != 0 && reader.Position + 16 <= maxSize)
if (!structFallback && reader.ReadInt32() != 0/* && reader.Position + 16 <= maxSize*/)
{
new FGuid(reader);
}

View File

@ -1,10 +1,12 @@
using PakReader.Parsers.Objects;
using PakReader.Parsers.PropertyTagData;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Parsers.PropertyTagData;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public sealed class USoundWave : UObject
{
@ -51,7 +53,18 @@ namespace PakReader.Parsers.Class
}
}
internal USoundWave(IoPackageReader reader, Dictionary<int, PropertyInfo> properties, Stream ubulk,
long ubulkOffset) : base(reader, properties)
{
Serialize(reader, ubulk, ubulkOffset);
}
internal USoundWave(PackageReader reader, Stream ubulk, long ubulkOffset) : base(reader)
{
Serialize(reader, ubulk, ubulkOffset);
}
private void Serialize(PackageReader reader, Stream ubulk, long ubulkOffset)
{
// if UE4.25+ && Windows -> True
bStreaming = FModel.Globals.Game.Version >= EPakVersion.PATH_HASH_INDEX;

View File

@ -1,9 +1,9 @@
using PakReader.Parsers.Objects;
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using FModel.PakReader.Parsers.Objects;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public sealed class UStringTable : IUExport
{

View File

@ -1,14 +1,15 @@
using System.Collections.Generic;
using System.IO;
using PakReader.Parsers.Objects;
using PakReader.Textures;
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.Objects;
using FModel.PakReader.Textures;
using SkiaSharp;
namespace PakReader.Parsers.Class
namespace FModel.PakReader.Parsers.Class
{
public sealed class UTexture2D : UObject
{
public FTexturePlatformData[] PlatformDatas { get; }
public FTexturePlatformData[] PlatformDatas { get; private set; }
SKImage image;
public SKImage Image
@ -49,7 +50,17 @@ namespace PakReader.Parsers.Class
}
}
internal UTexture2D(IoPackageReader reader, Dictionary<int, PropertyInfo> properties, Stream ubulk,
long bulkOffset) : base(reader, properties)
{
Serialize(reader, ubulk, bulkOffset);
}
internal UTexture2D(PackageReader reader, Stream ubulk, long bulkOffset) : base(reader)
{
Serialize(reader, ubulk, bulkOffset);
}
private void Serialize(PackageReader reader, Stream ubulk, long bulkOffset)
{
new FStripDataFlags(reader); // and I quote, "still no idea"
new FStripDataFlags(reader); // "why there are two" :)

View File

@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FModel.PakReader.IO;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
using FModel.Utils;
namespace FModel.PakReader.Parsers
{
public sealed class IoPackageReader : PackageReader
{
public readonly FIoGlobalData GlobalData;
public readonly FPackageSummary Summary;
public readonly FPackageObjectIndex[] ImportMap;
public readonly FExportMapEntry[] ExportMap;
internal List<FObjectResource> FakeImportMap;
public override FNameEntrySerialized[] NameMap { get; }
private IUExport[] _dataExports;
private Stream _ubulk;
public override IUExport[] DataExports {
get
{
if (_dataExports == null)
ReadContent();
return _dataExports;
}
}
private FName[] _dataExportTypes;
public override FName[] DataExportTypes
{
get
{
if (_dataExportTypes == null)
ReadContent();
return _dataExportTypes;
}
}
private Dictionary<FPackageObjectIndex, string> _importMappings;
public IoPackageReader(Stream uasset, Stream ubulk, FIoGlobalData globalData, FFileIoStoreReader reader, bool onlyInfo = false) : this(new BinaryReader(uasset),
ubulk, globalData, reader, onlyInfo) { }
public IoPackageReader(BinaryReader uasset, Stream ubulk, FIoGlobalData globalData, FFileIoStoreReader reader, bool onlyInfo = false)
{
Loader = uasset;
_ubulk = ubulk;
GlobalData = globalData;
Summary = new FPackageSummary(this);
var nameMap = new List<FNameEntrySerialized>();
var nameHashes = new List<ulong>();
if (Summary.NameMapNamesSize > 0)
{
Loader.BaseStream.Position = Summary.NameMapNamesOffset;
var nameMapNames = Loader.ReadBytes(Summary.NameMapNamesSize);
Loader.BaseStream.Position = Summary.NameMapHashesOffset;
var nameMapHashes = Loader.ReadBytes(Summary.NameMapHashesSize);
FNameEntrySerialized.LoadNameBatch(nameMap, nameHashes, nameMapNames, nameMapHashes);
}
NameMap = nameMap.ToArray();
Loader.BaseStream.Position = Summary.ImportMapOffset;
var importMapCount = (Summary.ExportMapOffset - Summary.ImportMapOffset) / /*sizeof(FPackageObjectIndex)*/ sizeof(ulong);
ImportMap = new FPackageObjectIndex[importMapCount];
for (int i = 0; i < importMapCount; i++)
{
ImportMap[i] = new FPackageObjectIndex(Loader);
}
Loader.BaseStream.Position = Summary.ExportMapOffset;
var exportMapCount = (Summary.ExportBundlesOffset - Summary.ExportMapOffset) / FExportMapEntry.SIZE;
ExportMap = new FExportMapEntry[exportMapCount];
for (int i = 0; i < exportMapCount; i++)
{
ExportMap[i] = new FExportMapEntry(this);
}
if (!onlyInfo)
ReadContent();
}
private void ReadContent()
{
Loader.BaseStream.Position = Summary.GraphDataOffset;
var referencedPackagesCount = Loader.ReadInt32();
var graphData = new (FPackageId importedPackageId, FArc[] arcs)[referencedPackagesCount];
_importMappings = new Dictionary<FPackageObjectIndex, string>(referencedPackagesCount);
FakeImportMap = new List<FObjectResource>();
for (int i = 0; i < ImportMap.Length; i++)
FakeImportMap.Add(new FObjectResource(new FName(), new FPackageIndex()));
for (int i = 0; i < referencedPackagesCount; i++)
{
var importedPackageId = new FPackageId(Loader);
var arcs = Loader.ReadTArray(() => new FArc(Loader));
graphData[i] = (importedPackageId, arcs);
var importedPackageName = Creator.Utils.GetFullPath(importedPackageId)
?.Replace($"{Folders.GetGameName()}/Content", "Game");
var package = Creator.Utils.GetPropertyPakPackage(importedPackageName) as IoPackage;
if (package == null) continue;
foreach (var export in package.Reader.ExportMap)
{
var realImportIndex = Array.FindIndex(ImportMap, it => it == export.GlobalImportIndex);
var nextIndex = FakeImportMap.Count;
FakeImportMap[realImportIndex] = new FObjectResource(new FName(export.ObjectName.String), new FPackageIndex(this, -(nextIndex + 1)));
var outerResource = new FObjectResource(new FName(string.Concat(package.Reader.Summary.Name.String, ".", export.ObjectName.String)), new FPackageIndex());
FakeImportMap.Add(outerResource);
}
}
var beginExportOffset = Summary.GraphDataOffset + Summary.GraphDataSize;
var currentExportDataOffset = beginExportOffset;
_dataExports = new IUExport[ExportMap.Length];
_dataExportTypes = new FName[ExportMap.Length];
for (var i = 0; i < ExportMap.Length; i++)
{
var exportMapEntry = ExportMap[i];
FName exportType;
if (GlobalData != null && GlobalData.ScriptObjectByGlobalId.TryGetValue(exportMapEntry.ClassIndex, out var scriptObject))
{
exportType = scriptObject.Name;
}
else
{
exportType = new FName("Unknown");
}
Loader.BaseStream.Position = currentExportDataOffset;
if (Globals.TypeMappings.TryGetValue(exportType.String, out var properties))
{
_dataExports[i] = exportType.String switch
{
"Texture2D" => new UTexture2D(this, properties, _ubulk,
ExportMap.Sum(e => (long) e.CookedSerialSize) + beginExportOffset),
"VirtualTexture2D" => new UTexture2D(this, properties, _ubulk, ExportMap.Sum(e => (long) e.CookedSerialSize) + beginExportOffset),
//"CurveTable" => new UCurveTable(this),
"DataTable" => new UDataTable(this, properties, exportType.String),
//"FontFace" => new UFontFace(this, ubulk),
"SoundWave" => new USoundWave(this, properties, _ubulk, ExportMap.Sum(e => (long) e.CookedSerialSize) + beginExportOffset),
//"StringTable" => new UStringTable(this),
//"AkMediaAssetData" => new UAkMediaAssetData(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
_ => new UObject(this, properties, type: exportType.String),
};
_dataExportTypes[i] = exportType;
}
else
{
#if DEBUG
var header = new FUnversionedHeader(this);
using var it = new FIterator(header);
FConsole.AppendText(string.Concat("\n", exportType.String, ": ", Summary.Name.String), "#CA6C6C", true);
do
{
FConsole.AppendText($"Val: {it.Current.Val} (IsNonZero: {it.Current.IsNonZero})", FColors.Yellow, true);
}
while (it.MoveNext());
#endif
}
currentExportDataOffset += (int) exportMapEntry.CookedSerialSize;
}
}
public override string ToString() => Summary.Name.String;
}
}

View File

@ -0,0 +1,123 @@
using System;
using System.IO;
using System.Linq;
using FModel.PakReader.Parsers.Class;
using FModel.PakReader.Parsers.Objects;
namespace FModel.PakReader.Parsers
{
public sealed class LegacyPackageReader : PackageReader
{
public FPackageFileSummary PackageFileSummary { get; }
public FObjectImport[] ImportMap { get; }
public FObjectExport[] ExportMap { get; }
public override FNameEntrySerialized[] NameMap { get; }
public override IUExport[] DataExports { get; }
public override FName[] DataExportTypes { get; }
public LegacyPackageReader(string uasset, string uexp, string ubulk) : this(File.OpenRead(uasset), File.OpenRead(uexp), File.Exists(ubulk) ? File.OpenRead(ubulk) : null) { }
public LegacyPackageReader(Stream uasset, Stream uexp, Stream ubulk) : this(new BinaryReader(uasset), new BinaryReader(uexp), ubulk) { }
LegacyPackageReader(BinaryReader uasset, BinaryReader uexp, Stream ubulk)
{
Loader = uasset;
PackageFileSummary = new FPackageFileSummary(Loader);
NameMap = SerializeNameMap();
ImportMap = SerializeImportMap();
ExportMap = SerializeExportMap();
DataExports = new IUExport[ExportMap.Length];
DataExportTypes = new FName[ExportMap.Length];
Loader = uexp;
for(int i = 0; i < ExportMap.Length; i++)
{
FObjectExport Export = ExportMap[i];
{
FName ExportType;
if (Export.ClassIndex.IsNull)
ExportType = DataExportTypes[i] = ReadFName(); // check if this is true, I don't know if Fortnite ever uses this
else if (Export.ClassIndex.IsExport)
ExportType = DataExportTypes[i] = ExportMap[Export.ClassIndex.AsExport].SuperIndex.Resource.ObjectName;
else if (Export.ClassIndex.IsImport)
ExportType = DataExportTypes[i] = 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
var pos = Position = Export.SerialOffset - PackageFileSummary.TotalHeaderSize;
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),
"SoundWave" => new USoundWave(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
"StringTable" => new UStringTable(this),
"AkMediaAssetData" => new UAkMediaAssetData(this, ubulk, ExportMap.Sum(e => e.SerialSize) + PackageFileSummary.TotalHeaderSize),
_ => new UObject(this),
};
#if DEBUG
if (pos + Export.SerialSize != Position)
{
System.Diagnostics.Debug.WriteLine($"[ExportType={ExportType.String}] Didn't read {Export.ObjectName} correctly (at {Position}, should be {pos + Export.SerialSize}, {pos + Export.SerialSize - Position} behind)");
}
#endif
}
}
return;
}
FNameEntrySerialized[] SerializeNameMap()
{
if (PackageFileSummary.NameCount > 0)
{
Loader.BaseStream.Position = PackageFileSummary.NameOffset;
var OutNameMap = new FNameEntrySerialized[PackageFileSummary.NameCount];
for (int NameMapIdx = 0; NameMapIdx < PackageFileSummary.NameCount; ++NameMapIdx)
{
// Read the name entry from the file.
OutNameMap[NameMapIdx] = new FNameEntrySerialized(Loader);
}
return OutNameMap;
}
return Array.Empty<FNameEntrySerialized>();
}
FObjectImport[] SerializeImportMap()
{
if (PackageFileSummary.ImportCount > 0)
{
Loader.BaseStream.Position = PackageFileSummary.ImportOffset;
var OutImportMap = new FObjectImport[PackageFileSummary.ImportCount];
for (int ImportMapIdx = 0; ImportMapIdx < PackageFileSummary.ImportCount; ++ImportMapIdx)
{
OutImportMap[ImportMapIdx] = new FObjectImport(this);
}
return OutImportMap;
}
return Array.Empty<FObjectImport>();
}
FObjectExport[] SerializeExportMap()
{
if (PackageFileSummary.ExportCount > 0)
{
Loader.BaseStream.Position = PackageFileSummary.ExportOffset;
var OutExportMap = new FObjectExport[PackageFileSummary.ExportCount];
for (int ExportMapIdx = 0; ExportMapIdx < PackageFileSummary.ExportCount; ++ExportMapIdx)
{
OutExportMap[ExportMapIdx] = new FObjectExport(this);
}
return OutExportMap;
}
return Array.Empty<FObjectExport>();
}
}
}

View File

@ -1,4 +1,4 @@
namespace PakReader.Parsers.Objects
namespace FModel.PakReader.Parsers.Objects
{
public enum EAnimationCompressionFormat
{

View File

@ -1,4 +1,4 @@
namespace PakReader.Parsers.Objects
namespace FModel.PakReader.Parsers.Objects
{
public enum EAnimationKeyFormat : byte
{

Some files were not shown because too many files have changed in this diff Show More