diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs
index c434def65..31da19b97 100644
--- a/FModel/App.xaml.cs
+++ b/FModel/App.xaml.cs
@@ -92,6 +92,12 @@ protected override void OnStartup(StartupEventArgs e)
UserSettings.Default.AudioDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
}
+ if (!Directory.Exists(UserSettings.Default.CodeDirectory))
+ {
+ createMe = true;
+ UserSettings.Default.CodeDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Exports");
+ }
+
if (!Directory.Exists(UserSettings.Default.ModelDirectory))
{
createMe = true;
diff --git a/FModel/Creator/Bases/FN/BaseAssembledMesh.cs b/FModel/Creator/Bases/FN/BaseAssembledMesh.cs
new file mode 100644
index 000000000..830b90bfc
--- /dev/null
+++ b/FModel/Creator/Bases/FN/BaseAssembledMesh.cs
@@ -0,0 +1,47 @@
+using CUE4Parse.UE4.Assets.Exports;
+using CUE4Parse.UE4.Assets.Objects;
+using CUE4Parse.UE4.Objects.UObject;
+using SkiaSharp;
+
+namespace FModel.Creator.Bases.FN;
+
+public class BaseAssembledMesh : UCreator
+{
+ public BaseAssembledMesh(UObject uObject, EIconStyle style) : base(uObject, style)
+ {
+
+ }
+
+ public override void ParseForInfo()
+ {
+ if (Object.TryGetValue(out FInstancedStruct[] additionalData, "AdditionalData"))
+ {
+ foreach (var data in additionalData)
+ {
+ if (data.NonConstStruct?.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SmallPreviewImage") == true)
+ {
+ Preview = Utils.GetBitmap(largePreview);
+ }
+ }
+ }
+ }
+
+ public override SKBitmap[] Draw()
+ {
+ var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul);
+ using var c = new SKCanvas(ret);
+
+ switch (Style)
+ {
+ case EIconStyle.NoBackground:
+ DrawPreview(c);
+ break;
+ default:
+ DrawBackground(c);
+ DrawPreview(c);
+ break;
+ }
+
+ return new[] { ret };
+ }
+}
diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs
index 13d2bfe1d..d495d17d3 100644
--- a/FModel/Creator/Bases/FN/BaseIconStats.cs
+++ b/FModel/Creator/Bases/FN/BaseIconStats.cs
@@ -87,6 +87,7 @@ public override void ParseForInfo()
weaponRowValue.TryGetValue(out float dmgPb, "DmgPB"); //Damage at point blank
weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge"); //Max damage a weapon can do in a single hit, usually used for shotguns
weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical"); //Headshot multiplier
+ weaponRowValue.TryGetValue(out float envDmgPb, "EnvDmgPB"); //Structure damage at point blank
weaponRowValue.TryGetValue(out int clipSize, "ClipSize"); //Item magazine size
weaponRowValue.TryGetValue(out float firingRate, "FiringRate"); //Item firing rate, value is shots per second
weaponRowValue.TryGetValue(out float swingTime, "SwingTime"); //Item swing rate, value is swing per second
@@ -115,6 +116,15 @@ public override void ParseForInfo()
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 160));
}
}
+ {
+ var envdmgmultiplier = bpc != 0f ? bpc : 1;
+ if (envDmgPb != 0f)
+
+ {
+ _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "11AF67134E0F4E27E5E588806AB475BE", "Structure Damage"), envDmgPb * envdmgmultiplier, 160));
+ }
+ }
+
if (clipSize > 999f || clipSize == 0f)
{
_statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), Utils.GetLocalizedResource("", "0FAE8E5445029F2AA209ADB0FE49B23C", "Infinite"), -1));
diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs
index 0fe8c317b..b2b122848 100644
--- a/FModel/Creator/CreatorPackage.cs
+++ b/FModel/Creator/CreatorPackage.cs
@@ -100,12 +100,14 @@ public bool TryConstructCreator([MaybeNullWhen(false)] out UCreator creator)
case "FortCodeTokenItemDefinition":
case "FortSchematicItemDefinition":
case "FortAlterableItemDefinition":
+ case "SproutHousingItemDefinition":
case "SparksKeyboardItemDefinition":
case "FortWorldMultiItemDefinition":
case "FortAlterationItemDefinition":
case "FortExpeditionItemDefinition":
case "FortIngredientItemDefinition":
case "FortConsumableItemDefinition":
+ case "SproutBuildingItemDefinition":
case "StWFortAccoladeItemDefinition":
case "FortAccountBuffItemDefinition":
case "FortFOBCoreDecoItemDefinition":
@@ -163,6 +165,9 @@ public bool TryConstructCreator([MaybeNullWhen(false)] out UCreator creator)
case "JunoAthenaDanceItemOverrideDefinition":
creator = new BaseJuno(_object.Value, _style);
return true;
+ case "AssembledMeshSchema":
+ creator = new BaseAssembledMesh(_object.Value, _style);
+ return true;
case "FortTandemCharacterData":
creator = new BaseTandem(_object.Value, _style);
return true;
diff --git a/FModel/Resources/Cpp.xshd b/FModel/Resources/Cpp.xshd
index f61b12642..89c827804 100644
--- a/FModel/Resources/Cpp.xshd
+++ b/FModel/Resources/Cpp.xshd
@@ -18,6 +18,7 @@
+ (\/\/.*|\/\*[\s\S]*?\*\/)
@@ -44,10 +45,19 @@
Int16
Int32
Int64
+ int8
+ int16
+ int32
+ int64
uint
+ UInt8
UInt16
UInt32
UInt64
+ uint8
+ uint16
+ uint32
+ uint64
float
double
bool
@@ -83,6 +93,7 @@
inline
constexpr
default
+ &&
@@ -120,8 +131,6 @@
[\[\]\{\}]
- (\/\/.*|\/\*[\s\S]*?\*\/)
-
\b[A-Za-z_][A-Za-z0-9_]*\b(?=<)
diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs
index 441042640..bca1427de 100644
--- a/FModel/Settings/UserSettings.cs
+++ b/FModel/Settings/UserSettings.cs
@@ -119,6 +119,13 @@ public string AudioDirectory
set => SetProperty(ref _audioDirectory, value);
}
+ private string _codeDirectory;
+ public string CodeDirectory
+ {
+ get => _codeDirectory;
+ set => SetProperty(ref _codeDirectory, value);
+ }
+
private string _modelDirectory;
public string ModelDirectory
{
diff --git a/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs b/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs
index 926061bc1..630c08167 100644
--- a/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs
+++ b/FModel/ViewModels/ApiEndpoints/DillyApiEndpoints.cs
@@ -11,6 +11,7 @@ namespace FModel.ViewModels.ApiEndpoints;
public class DillyApiEndpoint : AbstractApiProvider
{
private Backup[] _backups;
+ private ManifestInfoDilly[] _manifests;
public DillyApiEndpoint(RestClient client) : base(client) { }
@@ -27,6 +28,19 @@ public Backup[] GetBackups(CancellationToken token)
return _backups ??= GetBackupsAsync(token).GetAwaiter().GetResult();
}
+ public async Task GetManifestsAsync(CancellationToken token)
+ {
+ var request = new FRestRequest($"https://export-service-new.dillyapis.com/v1/manifests");
+ var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
+ Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
+ return response.Data;
+ }
+
+ public ManifestInfoDilly[] GetManifests(CancellationToken token)
+ {
+ return _manifests ??= GetManifestsAsync(token).GetAwaiter().GetResult();
+ }
+
public async Task>> GetHotfixesAsync(CancellationToken token, string language = "en")
{
var request = new FRestRequest("https://api.fortniteapi.com/v1/cloudstorage/hotfixes")
diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs
index 67c6f6a4a..598f070c3 100644
--- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs
+++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs
@@ -23,6 +23,13 @@ public class Backup
[J] public string Url { get; private set; }
}
+[DebuggerDisplay("{" + nameof(AppName) + "}")]
+public class ManifestInfoDilly
+{
+ [J] public string AppName { get; private set; }
+ [J] public string DownloadUrl { get; private set; }
+}
+
public class Donator
{
[J] public string Username { get; private set; }
diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs
index 51aa8bfe3..42b24d90b 100644
--- a/FModel/ViewModels/ApplicationViewModel.cs
+++ b/FModel/ViewModels/ApplicationViewModel.cs
@@ -104,7 +104,7 @@ public ApplicationViewModel()
if (UserSettings.Default.CurrentDir is null)
{
//If no game is selected, many things will break before a shutdown request is processed in the normal way.
- //A hard exit is preferable to an unhandled expection in this case
+ //A hard exit is preferable to an unhandled exception in this case
Environment.Exit(0);
}
@@ -126,7 +126,6 @@ public ApplicationViewModel()
if (sender is not IAesVfsReader reader) return;
CUE4Parse.GameDirectory.Disable(reader);
};
-
CustomDirectories = new CustomDirectoriesViewModel();
SettingsView = new SettingsViewModel();
AesManager = new AesManagerViewModel(CUE4Parse);
diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs
index 3fa86b0fb..3896d5825 100644
--- a/FModel/ViewModels/CUE4ParseViewModel.cs
+++ b/FModel/ViewModels/CUE4ParseViewModel.cs
@@ -225,14 +225,12 @@ public CUE4ParseViewModel()
public async Task Initialize()
{
- await _apiEndpointView.EpicApi.VerifyAuth(CancellationToken.None);
await _threadWorkerView.Begin(cancellationToken =>
{
Provider.OnDemandOptions = new IoStoreOnDemandOptions
{
ChunkHostUri = new Uri("https://download.epicgames.com/", UriKind.Absolute),
ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")),
- Authorization = new AuthenticationHeaderValue("Bearer", UserSettings.Default.LastAuthResponse.AccessToken),
Timeout = TimeSpan.FromSeconds(30)
};
@@ -287,6 +285,20 @@ await _threadWorkerView.Begin(cancellationToken =>
it => new FRandomAccessStreamArchive(it, manifest.FindFile(it)!.GetStream(), p.Versions));
});
+ var manifests = _apiEndpointView.DillyApi.GetManifests(cancellationToken);
+ var downloadUrl = manifests.First(x => x.AppName == "Fortnite_Studio").DownloadUrl;
+
+ using var client = new HttpClient();
+ var manifestBytes = client.GetByteArrayAsync(downloadUrl).GetAwaiter().GetResult();
+
+ var uefnManifest = FBuildPatchAppManifest.Deserialize(manifestBytes, manifestOptions);
+
+ Parallel.ForEach(uefnManifest.Files.Where(x => _fnLiveRegex.IsMatch(x.FileName)), fileManifest =>
+ {
+ p.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream()],
+ it => new FRandomAccessStreamArchive(it, uefnManifest.FindFile(it)!.GetStream(), p.Versions));
+ });
+
var elapsedTime = Stopwatch.GetElapsedTime(startTs);
FLogger.Append(ELog.Information, () =>
FLogger.Text($"Fortnite [LIVE] has been loaded successfully in {elapsedTime.TotalMilliseconds:F1}ms", Constants.WHITE, true));
@@ -622,6 +634,9 @@ public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder
public void AudioFolder(CancellationToken cancellationToken, TreeItem folder)
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Audio | EBulkType.Auto));
+ public void CodeFolder(CancellationToken cancellationToken, TreeItem folder)
+ => BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Code | EBulkType.Auto));
+
public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None)
{
ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
@@ -635,6 +650,7 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
var saveProperties = HasFlag(bulk, EBulkType.Properties);
var saveTextures = HasFlag(bulk, EBulkType.Textures);
var saveAudio = HasFlag(bulk, EBulkType.Audio);
+ var saveDecompiled = HasFlag(bulk, EBulkType.Code);
switch (entry.Extension)
{
case "uasset":
@@ -649,6 +665,13 @@ public void Extract(CancellationToken cancellationToken, GameFile entry, bool ad
if (saveProperties) break; // do not search for viewable exports if we are dealing with jsons
}
+ if (saveDecompiled)
+ {
+ if (Decompile(entry, false))
+ TabControl.SelectedTab.SaveDecompiled(updateUi);
+ break;
+ }
+
for (var i = result.InclusiveStart; i < result.ExclusiveEnd; i++)
{
if (CheckExport(cancellationToken, result.Package, i, bulk))
@@ -1363,11 +1386,13 @@ public void FindReferences(GameFile entry)
}
- public void Decompile(GameFile entry)
+ public bool Decompile(GameFile entry, bool AddTab = true)
{
- ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
-
- if (TabControl.CanAddTabs) TabControl.AddTab(entry);
+ if (TabControl.CanAddTabs && AddTab)
+ {
+ ApplicationService.ApplicationView.IsAssetsExplorerVisible = false;
+ TabControl.AddTab(entry);
+ }
else TabControl.SelectedTab.SoftReset(entry);
TabControl.SelectedTab.TitleExtra = "Decompiled";
@@ -1405,9 +1430,11 @@ public void Decompile(GameFile entry)
cpp = Regex.Replace(cpp, "__verse_0x[a-fA-F0-9]{8}_", ""); // UnmangleCasedName
}
cpp = Regex.Replace(cpp, @"CallFunc_([A-Za-z0-9_]+)_ReturnValue", "$1");
-
+ cpp = Regex.Replace(cpp, @"K2Node_DynamicCast_([A-Za-z0-9_]+)", "$1");
+ cpp = Regex.Replace(cpp, @"K2Node_([A-Za-z0-9_]+)", "$1");
TabControl.SelectedTab.SetDocumentText(cpp, false, false);
+ return cpp.Length > 0;
}
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool saveAudio, bool updateUi)
diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs
index 6731918da..b490957c0 100644
--- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs
+++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs
@@ -69,6 +69,7 @@ public override async void Execute(ApplicationViewModel contextViewModel, object
"Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes),
"Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations),
"Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio),
+ "Save_Code" => (EAction.Export, EShowAssetType.None, EBulkType.Code),
_ => throw new ArgumentOutOfRangeException("Unsupported asset action."),
};
@@ -109,6 +110,7 @@ await _threadWorkerView.Begin(cancellationToken =>
EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"),
EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"),
EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"),
+ EBulkType.Code => (UserSettings.Default.CodeDirectory, "code files"),
_ => (null, null),
};
diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs
index becbf3a2b..626f4227d 100644
--- a/FModel/ViewModels/SettingsViewModel.cs
+++ b/FModel/ViewModels/SettingsViewModel.cs
@@ -195,6 +195,7 @@ public ulong CriwareDecryptionKey
private string _propertiesSnapshot;
private string _textureSnapshot;
private string _audioSnapshot;
+ private string _codeSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private ETexturePlatform _uePlatformSnapshot;
@@ -227,6 +228,7 @@ public void Initialize()
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
_textureSnapshot = UserSettings.Default.TextureDirectory;
_audioSnapshot = UserSettings.Default.AudioDirectory;
+ _codeSnapshot = UserSettings.Default.CodeDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
@@ -303,12 +305,6 @@ public bool Save(out List whatShouldIDo)
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_mapStructTypesSnapshot != SelectedMapStructTypes ||
- _outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
- _rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
- _propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
- _textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
- _audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
- _modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
restart = true;
diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs
index 748dd1ae0..dbeb44239 100644
--- a/FModel/ViewModels/TabControlViewModel.cs
+++ b/FModel/ViewModels/TabControlViewModel.cs
@@ -409,7 +409,17 @@ public void SaveProperty(bool updateUi)
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
SaveCheck(directory, fileName, updateUi);
}
+ public void SaveDecompiled(bool updateUi)
+ {
+ var fileName = Path.ChangeExtension(Entry.Name, ".cpp");
+ var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
+ UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/');
+
+ Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
+ Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
+ SaveCheck(directory, fileName, updateUi);
+ }
private void SaveCheck(string path, string fileName, bool updateUi)
{
if (File.Exists(path))
diff --git a/FModel/ViewModels/UpdateViewModel.cs b/FModel/ViewModels/UpdateViewModel.cs
index adbc79bd7..26acdb88a 100644
--- a/FModel/ViewModels/UpdateViewModel.cs
+++ b/FModel/ViewModels/UpdateViewModel.cs
@@ -81,6 +81,9 @@ private Task LoadCoAuthors()
if (username.Equals("Asval", StringComparison.OrdinalIgnoreCase))
{
username = "4sval"; // found out the hard way co-authored usernames can't be trusted
+ } else if (username.Equals("Krowe Moh", StringComparison.OrdinalIgnoreCase))
+ {
+ username = "Krowe-moh";
}
coAuthorMap[commit].Add(username);
@@ -101,7 +104,7 @@ private Task LoadCoAuthors()
}
catch
{
- //
+ // Ignore
}
}
diff --git a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml
index ca5a58715..26aa40745 100644
--- a/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml
+++ b/FModel/Views/Resources/Controls/ContextMenus/FolderContextMenu.xaml
@@ -65,6 +65,26 @@
+