mirror of
https://github.com/4sval/FModel.git
synced 2026-03-22 09:44:37 -05:00
230 lines
8.8 KiB
C#
230 lines
8.8 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using AdonisUI.Controls;
|
|
using CUE4Parse.UE4.Readers;
|
|
using CUE4Parse.UE4.VirtualFileSystem;
|
|
using FModel.Creator;
|
|
using FModel.Extensions;
|
|
using FModel.Framework;
|
|
using FModel.Services;
|
|
using FModel.Settings;
|
|
using FModel.Views.Resources.Controls;
|
|
using K4os.Compression.LZ4.Streams;
|
|
using Microsoft.Win32;
|
|
|
|
namespace FModel.ViewModels.Commands;
|
|
|
|
/// <summary>
|
|
/// this will always load all files no matter the loading mode
|
|
/// however what this does is filtering what to show to the user
|
|
/// </summary>
|
|
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|
{
|
|
private const uint _IS_LZ4 = 0x184D2204u;
|
|
|
|
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
|
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
|
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
|
|
|
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel) { }
|
|
|
|
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
|
{
|
|
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
|
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
|
|
{
|
|
FLogger.Append(ELog.Error, () =>
|
|
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
|
|
return;
|
|
}
|
|
|
|
#if DEBUG
|
|
var loadingTime = Stopwatch.StartNew();
|
|
#endif
|
|
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
|
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
|
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
|
|
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
|
|
|
|
await Task.WhenAll(
|
|
_applicationView.CUE4Parse.LoadLocalizedResources(), // load locres if not already loaded,
|
|
_applicationView.CUE4Parse.LoadVirtualPaths(), // load virtual paths if not already loaded
|
|
_threadWorkerView.Begin(cancellationToken =>
|
|
{
|
|
// filter what to show
|
|
switch (UserSettings.Default.LoadingMode)
|
|
{
|
|
case ELoadingMode.Single:
|
|
case ELoadingMode.Multiple:
|
|
{
|
|
var l = (IList) parameter;
|
|
if (l.Count < 1) return;
|
|
|
|
var directoryFilesToShow = l.Cast<FileItem>();
|
|
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
|
break;
|
|
}
|
|
case ELoadingMode.All:
|
|
{
|
|
FilterDirectoryFilesToDisplay(cancellationToken, null);
|
|
break;
|
|
}
|
|
case ELoadingMode.AllButNew:
|
|
case ELoadingMode.AllButModified:
|
|
{
|
|
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
|
break;
|
|
}
|
|
default: throw new ArgumentOutOfRangeException();
|
|
}
|
|
|
|
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
|
|
})
|
|
).ConfigureAwait(false);
|
|
#if DEBUG
|
|
loadingTime.Stop();
|
|
FLogger.Append(ELog.Debug, () =>
|
|
FLogger.Text($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true));
|
|
#endif
|
|
}
|
|
|
|
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
|
|
{
|
|
HashSet<string> filter;
|
|
if (directoryFiles == null) filter = null;
|
|
else
|
|
{
|
|
filter = new HashSet<string>();
|
|
foreach (var directoryFile in directoryFiles)
|
|
{
|
|
if (!directoryFile.IsEnabled)
|
|
continue;
|
|
|
|
filter.Add(directoryFile.Name);
|
|
}
|
|
}
|
|
|
|
var hasFilter = filter != null && filter.Count != 0;
|
|
var entries = new List<VfsEntry>();
|
|
|
|
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
|
|
|
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
|
continue;
|
|
|
|
if (hasFilter)
|
|
{
|
|
if (filter.Contains(entry.Vfs.Name))
|
|
{
|
|
entries.Add(entry);
|
|
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
entries.Add(entry);
|
|
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
}
|
|
}
|
|
|
|
_applicationView.Status.UpdateStatusLabel("Folders & Packages");
|
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
|
}
|
|
|
|
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
|
|
{
|
|
var openFileDialog = new OpenFileDialog
|
|
{
|
|
Title = "Select a backup file older than your current game version",
|
|
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
|
|
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
|
|
Multiselect = false
|
|
};
|
|
|
|
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
|
|
|
|
FLogger.Append(ELog.Information, () =>
|
|
FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true));
|
|
|
|
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
|
using var memoryStream = new MemoryStream();
|
|
|
|
if (fileStream.ReadUInt32() == _IS_LZ4)
|
|
{
|
|
fileStream.Position -= 4;
|
|
using var compressionStream = LZ4Stream.Decode(fileStream);
|
|
compressionStream.CopyTo(memoryStream);
|
|
}
|
|
else fileStream.CopyTo(memoryStream);
|
|
|
|
memoryStream.Position = 0;
|
|
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
|
var entries = new List<VfsEntry>();
|
|
|
|
var mode = UserSettings.Default.LoadingMode;
|
|
switch (mode)
|
|
{
|
|
case ELoadingMode.AllButNew:
|
|
{
|
|
var paths = new Dictionary<string, int>();
|
|
while (archive.Position < archive.Length)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
archive.Position += 29;
|
|
paths[archive.ReadString().ToLower()[1..]] = 0;
|
|
archive.Position += 4;
|
|
}
|
|
|
|
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
|
|
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
|
|
|
entries.Add(entry);
|
|
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
case ELoadingMode.AllButModified:
|
|
{
|
|
while (archive.Position < archive.Length)
|
|
{
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
archive.Position += 16;
|
|
var uncompressedSize = archive.Read<long>();
|
|
var isEncrypted = archive.ReadFlag();
|
|
archive.Position += 4;
|
|
var fullPath = archive.ReadString().ToLower()[1..];
|
|
archive.Position += 4;
|
|
|
|
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
|
|
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
|
|
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
|
continue;
|
|
|
|
entries.Add(entry);
|
|
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
|
}
|
|
}
|