mirror of
https://github.com/4sval/FModel.git
synced 2026-04-26 00:04:53 -05:00
ui now deals with GameFile instead of a bad replica
Some checks failed
FModel QA Builder / build (push) Has been cancelled
Some checks failed
FModel QA Builder / build (push) Has been cancelled
that means loose files are now supported, or should be Export Raw Data shows the correct extension
This commit is contained in:
parent
3d0bdf05e1
commit
dbf618e417
|
|
@ -1 +1 @@
|
||||||
Subproject commit ab6dff8e98e94335916a549810466eb4571a072b
|
Subproject commit 11a92870024a088888aae79c74d8ae0c6c8af3e5
|
||||||
27
FModel/Framework/FakeCUE4Parse.cs
Normal file
27
FModel/Framework/FakeCUE4Parse.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using CUE4Parse.Compression;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
|
using CUE4Parse.UE4.Readers;
|
||||||
|
|
||||||
|
namespace FModel.Framework;
|
||||||
|
|
||||||
|
public class FakeGameFile : GameFile
|
||||||
|
{
|
||||||
|
public FakeGameFile(string path) : base(path, 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool IsEncrypted => false;
|
||||||
|
public override CompressionMethod CompressionMethod => CompressionMethod.None;
|
||||||
|
|
||||||
|
public override byte[] Read()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override FArchive CreateReader()
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -502,7 +502,14 @@
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding DataContext.SelectedItem.Extension,
|
||||||
|
FallbackValue='uasset',
|
||||||
|
StringFormat='Export Raw Data (.{0})',
|
||||||
|
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
</MenuItem.Header>
|
||||||
<MenuItem.CommandParameter>
|
<MenuItem.CommandParameter>
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
<Binding Source="Assets_Export_Data" />
|
<Binding Source="Assets_Export_Data" />
|
||||||
|
|
@ -649,11 +656,11 @@
|
||||||
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="0" Grid.Column="1" Text="Offset" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding SelectedItem.Size, ElementName=AssetsListName, FallbackValue=0, Converter={x:Static converters:SizeToStringConverter.Instance}}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="1" Grid.Column="1" Text="Size" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.Compression, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding SelectedItem.CompressionMethod, ElementName=AssetsListName, FallbackValue='Unknown'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="2" Grid.Column="1" Text="Compression Method" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding SelectedItem.IsEncrypted, ElementName=AssetsListName, FallbackValue='False'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="3" Grid.Column="1" Text="Is Encrypted" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Archive, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
<TextBlock Grid.Row="4" Grid.Column="0" Text="{Binding SelectedItem.Vfs.Name, ElementName=AssetsListName, FallbackValue='None'}" VerticalAlignment="Center" HorizontalAlignment="Left" />
|
||||||
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
<TextBlock Grid.Row="4" Grid.Column="1" Text="Included In Archive" VerticalAlignment="Center" HorizontalAlignment="Right" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using AdonisUI.Controls;
|
using AdonisUI.Controls;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
using FModel.ViewModels;
|
using FModel.ViewModels;
|
||||||
|
|
@ -84,7 +85,7 @@ public partial class MainWindow
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// await _threadWorkerView.Begin(cancellationToken =>
|
// await _threadWorkerView.Begin(cancellationToken =>
|
||||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||||
// "MyProject/Content/FirstPerson/Meshes/FirstPersonProjectileMesh.uasset"));
|
// "Marvel/Content/Marvel/Characters/1016/1016501/Meshes/SK_1016_1016501.uasset"));
|
||||||
// await _threadWorkerView.Begin(cancellationToken =>
|
// await _threadWorkerView.Begin(cancellationToken =>
|
||||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||||
// "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset"));
|
// "RED/Content/Chara/ABA/Costume01/Animation/Charaselect/body/stand_body01.uasset"));
|
||||||
|
|
@ -162,7 +163,7 @@ public partial class MainWindow
|
||||||
{
|
{
|
||||||
if (sender is not ListBox listBox) return;
|
if (sender is not ListBox listBox) return;
|
||||||
|
|
||||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,7 +267,7 @@ public partial class MainWindow
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var filters = textBox.Text.Trim().Split(' ');
|
var filters = textBox.Text.Trim().Split(' ');
|
||||||
folder.AssetsList.AssetsView.Filter = o => { return o is AssetItem assetItem && filters.All(x => assetItem.FileName.Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
folder.AssetsList.AssetsView.Filter = o => { return o is GameFile entry && filters.All(x => entry.Name.Contains(x, StringComparison.OrdinalIgnoreCase)); };
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
|
||||||
|
|
@ -283,7 +284,7 @@ public partial class MainWindow
|
||||||
switch (e.Key)
|
switch (e.Key)
|
||||||
{
|
{
|
||||||
case Key.Enter:
|
case Key.Enter:
|
||||||
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
|
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
|
||||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.UE4.Versions;
|
using CUE4Parse.UE4.Versions;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
@ -60,12 +61,15 @@ public class TreeItem : ViewModel
|
||||||
public RangeObservableCollection<TreeItem> Folders { get; }
|
public RangeObservableCollection<TreeItem> Folders { get; }
|
||||||
public ICollectionView FoldersView { get; }
|
public ICollectionView FoldersView { get; }
|
||||||
|
|
||||||
public TreeItem(string header, string archive, string mountPoint, FPackageFileVersion version, string pathHere)
|
public TreeItem(string header, GameFile entry, string pathHere)
|
||||||
{
|
{
|
||||||
Header = header;
|
Header = header;
|
||||||
Archive = archive;
|
if (entry is VfsEntry vfsEntry)
|
||||||
MountPoint = mountPoint;
|
{
|
||||||
Version = version;
|
Archive = vfsEntry.Vfs.Name;
|
||||||
|
MountPoint = vfsEntry.Vfs.MountPoint;
|
||||||
|
Version = vfsEntry.Vfs.Ver;
|
||||||
|
}
|
||||||
PathAtThisPoint = pathHere;
|
PathAtThisPoint = pathHere;
|
||||||
AssetsList = new AssetsListViewModel();
|
AssetsList = new AssetsListViewModel();
|
||||||
Folders = new RangeObservableCollection<TreeItem>();
|
Folders = new RangeObservableCollection<TreeItem>();
|
||||||
|
|
@ -86,7 +90,7 @@ public class AssetsFolderViewModel
|
||||||
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
|
public void BulkPopulate(IReadOnlyCollection<GameFile> entries)
|
||||||
{
|
{
|
||||||
if (entries == null || entries.Count == 0)
|
if (entries == null || entries.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -95,54 +99,48 @@ public class AssetsFolderViewModel
|
||||||
{
|
{
|
||||||
var treeItems = new RangeObservableCollection<TreeItem>();
|
var treeItems = new RangeObservableCollection<TreeItem>();
|
||||||
treeItems.SetSuppressionState(true);
|
treeItems.SetSuppressionState(true);
|
||||||
var items = new List<AssetItem>(entries.Count);
|
|
||||||
|
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
|
TreeItem lastNode = null;
|
||||||
items.Add(item);
|
var folders = entry.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var builder = new StringBuilder(64);
|
||||||
|
var parentNode = treeItems;
|
||||||
|
|
||||||
|
for (var i = 0; i < folders.Length - 1; i++)
|
||||||
{
|
{
|
||||||
TreeItem lastNode = null;
|
var folder = folders[i];
|
||||||
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
builder.Append(folder).Append('/');
|
||||||
var builder = new StringBuilder(64);
|
lastNode = FindByHeaderOrNull(parentNode, folder);
|
||||||
var parentNode = treeItems;
|
|
||||||
|
|
||||||
for (var i = 0; i < folders.Length - 1; i++)
|
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
|
||||||
{
|
{
|
||||||
var folder = folders[i];
|
for (var i = 0; i < list.Count; i++)
|
||||||
builder.Append(folder).Append('/');
|
|
||||||
lastNode = FindByHeaderOrNull(parentNode, folder);
|
|
||||||
|
|
||||||
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < list.Count; i++)
|
if (list[i].Header == header)
|
||||||
{
|
return list[i];
|
||||||
if (list[i].Header == header)
|
|
||||||
return list[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastNode == null)
|
return null;
|
||||||
{
|
|
||||||
var nodePath = builder.ToString();
|
|
||||||
lastNode = new TreeItem(folder, item.Archive, entry.Vfs.MountPoint, entry.Vfs.Ver, nodePath[..^1]);
|
|
||||||
lastNode.Folders.SetSuppressionState(true);
|
|
||||||
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
|
||||||
parentNode.Add(lastNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
parentNode = lastNode.Folders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastNode?.AssetsList.Assets.Add(item);
|
if (lastNode == null)
|
||||||
|
{
|
||||||
|
var nodePath = builder.ToString();
|
||||||
|
lastNode = new TreeItem(folder, entry, nodePath[..^1]);
|
||||||
|
lastNode.Folders.SetSuppressionState(true);
|
||||||
|
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
||||||
|
parentNode.Add(lastNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode = lastNode.Folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastNode?.AssetsList.Assets.Add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
Folders.AddRange(treeItems);
|
Folders.AddRange(treeItems);
|
||||||
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
|
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(entries);
|
||||||
|
|
||||||
foreach (var folder in Folders)
|
foreach (var folder in Folders)
|
||||||
InvokeOnCollectionChanged(folder);
|
InvokeOnCollectionChanged(folder);
|
||||||
|
|
|
||||||
|
|
@ -1,109 +1,21 @@
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using CUE4Parse.Compression;
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.Utils;
|
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
||||||
public class AssetItem : ViewModel
|
|
||||||
{
|
|
||||||
private string _fullPath;
|
|
||||||
public string FullPath
|
|
||||||
{
|
|
||||||
get => _fullPath;
|
|
||||||
private set => SetProperty(ref _fullPath, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _isEncrypted;
|
|
||||||
public bool IsEncrypted
|
|
||||||
{
|
|
||||||
get => _isEncrypted;
|
|
||||||
private set => SetProperty(ref _isEncrypted, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long _offset;
|
|
||||||
public long Offset
|
|
||||||
{
|
|
||||||
get => _offset;
|
|
||||||
private set => SetProperty(ref _offset, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private long _size;
|
|
||||||
public long Size
|
|
||||||
{
|
|
||||||
get => _size;
|
|
||||||
private set => SetProperty(ref _size, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _archive;
|
|
||||||
public string Archive
|
|
||||||
{
|
|
||||||
get => _archive;
|
|
||||||
private set => SetProperty(ref _archive, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompressionMethod _compression;
|
|
||||||
public CompressionMethod Compression
|
|
||||||
{
|
|
||||||
get => _compression;
|
|
||||||
private set => SetProperty(ref _compression, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _directory;
|
|
||||||
public string Directory
|
|
||||||
{
|
|
||||||
get => _directory;
|
|
||||||
private set => SetProperty(ref _directory, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _fileName;
|
|
||||||
public string FileName
|
|
||||||
{
|
|
||||||
get => _fileName;
|
|
||||||
private set => SetProperty(ref _fileName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _extension;
|
|
||||||
public string Extension
|
|
||||||
{
|
|
||||||
get => _extension;
|
|
||||||
private set => SetProperty(ref _extension, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AssetItem(string titleExtra, AssetItem asset) : this(asset.FullPath, asset.IsEncrypted, asset.Offset, asset.Size, asset.Archive, asset.Compression)
|
|
||||||
{
|
|
||||||
FullPath += titleExtra;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AssetItem(string fullPath, bool isEncrypted = false, long offset = 0, long size = 0, string archive = "", CompressionMethod compression = CompressionMethod.None)
|
|
||||||
{
|
|
||||||
FullPath = fullPath;
|
|
||||||
IsEncrypted = isEncrypted;
|
|
||||||
Offset = offset;
|
|
||||||
Size = size;
|
|
||||||
Archive = archive;
|
|
||||||
Compression = compression;
|
|
||||||
|
|
||||||
Directory = FullPath.SubstringBeforeLast('/');
|
|
||||||
FileName = FullPath.SubstringAfterLast('/');
|
|
||||||
Extension = FullPath.SubstringAfterLast('.').ToLowerInvariant();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => FullPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AssetsListViewModel
|
public class AssetsListViewModel
|
||||||
{
|
{
|
||||||
public RangeObservableCollection<AssetItem> Assets { get; }
|
public RangeObservableCollection<GameFile> Assets { get; }
|
||||||
public ICollectionView AssetsView { get; }
|
public ICollectionView AssetsView { get; }
|
||||||
|
|
||||||
public AssetsListViewModel()
|
public AssetsListViewModel()
|
||||||
{
|
{
|
||||||
Assets = new RangeObservableCollection<AssetItem>();
|
Assets = new RangeObservableCollection<GameFile>();
|
||||||
AssetsView = new ListCollectionView(Assets)
|
AssetsView = new ListCollectionView(Assets)
|
||||||
{
|
{
|
||||||
SortDescriptions = { new SortDescription("FullPath", ListSortDirection.Ascending) }
|
SortDescriptions = { new SortDescription("Path", ListSortDirection.Ascending) }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ using CUE4Parse.UE4.Versions;
|
||||||
using CUE4Parse.UE4.Wwise;
|
using CUE4Parse.UE4.Wwise;
|
||||||
using CUE4Parse_Conversion;
|
using CUE4Parse_Conversion;
|
||||||
using CUE4Parse_Conversion.Sounds;
|
using CUE4Parse_Conversion.Sounds;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.UE4.Assets;
|
using CUE4Parse.UE4.Assets;
|
||||||
using CUE4Parse.UE4.Objects.UObject;
|
using CUE4Parse.UE4.Objects.UObject;
|
||||||
using CUE4Parse.Utils;
|
using CUE4Parse.Utils;
|
||||||
|
|
@ -499,25 +500,25 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExtractSelected(CancellationToken cancellationToken, IEnumerable<AssetItem> assetItems)
|
public void ExtractSelected(CancellationToken cancellationToken, IEnumerable<GameFile> assetItems)
|
||||||
{
|
{
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in assetItems)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
Extract(cancellationToken, asset, TabControl.HasNoTabs);
|
Extract(cancellationToken, entry, TabControl.HasNoTabs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BulkFolder(CancellationToken cancellationToken, TreeItem folder, Action<AssetItem> action)
|
private void BulkFolder(CancellationToken cancellationToken, TreeItem folder, Action<GameFile> action)
|
||||||
{
|
{
|
||||||
foreach (var asset in folder.AssetsList.Assets)
|
foreach (var entry in folder.AssetsList.Assets)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
action(asset);
|
action(entry);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -530,10 +531,10 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
|
|
||||||
public void ExportFolder(CancellationToken cancellationToken, TreeItem folder)
|
public void ExportFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||||
{
|
{
|
||||||
Parallel.ForEach(folder.AssetsList.Assets, asset =>
|
Parallel.ForEach(folder.AssetsList.Assets, entry =>
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
ExportData(asset, false);
|
ExportData(entry, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
|
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
|
||||||
|
|
@ -554,23 +555,23 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder)
|
public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto));
|
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, EBulkType.Animations | EBulkType.Auto));
|
||||||
|
|
||||||
public void Extract(CancellationToken cancellationToken, AssetItem asset, bool addNewTab = false, EBulkType bulk = EBulkType.None)
|
public void Extract(CancellationToken cancellationToken, GameFile entry, bool addNewTab = false, EBulkType bulk = EBulkType.None)
|
||||||
{
|
{
|
||||||
Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", asset.FullPath);
|
Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", entry.Path);
|
||||||
|
|
||||||
if (addNewTab && TabControl.CanAddTabs) TabControl.AddTab(asset);
|
if (addNewTab && TabControl.CanAddTabs) TabControl.AddTab(entry);
|
||||||
else TabControl.SelectedTab.SoftReset(asset);
|
else TabControl.SelectedTab.SoftReset(entry);
|
||||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(asset.Extension);
|
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(entry.Extension);
|
||||||
|
|
||||||
var updateUi = !HasFlag(bulk, EBulkType.Auto);
|
var updateUi = !HasFlag(bulk, EBulkType.Auto);
|
||||||
var saveProperties = HasFlag(bulk, EBulkType.Properties);
|
var saveProperties = HasFlag(bulk, EBulkType.Properties);
|
||||||
var saveTextures = HasFlag(bulk, EBulkType.Textures);
|
var saveTextures = HasFlag(bulk, EBulkType.Textures);
|
||||||
switch (asset.Extension)
|
switch (entry.Extension)
|
||||||
{
|
{
|
||||||
case "uasset":
|
case "uasset":
|
||||||
case "umap":
|
case "umap":
|
||||||
{
|
{
|
||||||
var pkg = Provider.LoadPackage(asset.FullPath, asset.Archive);
|
var pkg = Provider.LoadPackage(entry);
|
||||||
if (saveProperties || updateUi)
|
if (saveProperties || updateUi)
|
||||||
{
|
{
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
@ -614,7 +615,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
case "po":
|
case "po":
|
||||||
case "h":
|
case "h":
|
||||||
{
|
{
|
||||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
var data = Provider.SaveAsset(entry);
|
||||||
using var stream = new MemoryStream(data) { Position = 0 };
|
using var stream = new MemoryStream(data) { Position = 0 };
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
|
|
@ -624,7 +625,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
}
|
}
|
||||||
case "locmeta":
|
case "locmeta":
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var metadata = new FTextLocalizationMetaDataResource(archive);
|
var metadata = new FTextLocalizationMetaDataResource(archive);
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
||||||
|
|
@ -632,23 +633,23 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
}
|
}
|
||||||
case "locres":
|
case "locres":
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var locres = new FTextLocalizationResource(archive);
|
var locres = new FTextLocalizationResource(archive);
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "bin" when asset.FileName.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase):
|
case "bin" when entry.Name.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase):
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var registry = new FAssetRegistryState(archive);
|
var registry = new FAssetRegistryState(archive);
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "bin" when asset.FileName.Contains("GlobalShaderCache", StringComparison.OrdinalIgnoreCase):
|
case "bin" when entry.Name.Contains("GlobalShaderCache", StringComparison.OrdinalIgnoreCase):
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var registry = new FGlobalShaderCache(archive);
|
var registry = new FGlobalShaderCache(archive);
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
||||||
|
|
@ -657,26 +658,26 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
case "bnk":
|
case "bnk":
|
||||||
case "pck":
|
case "pck":
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var wwise = new WwiseReader(archive);
|
var wwise = new WwiseReader(archive);
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
|
||||||
foreach (var (name, data) in wwise.WwiseEncodedMedias)
|
foreach (var (name, data) in wwise.WwiseEncodedMedias)
|
||||||
{
|
{
|
||||||
SaveAndPlaySound(asset.FullPath.SubstringBeforeWithLast('/') + name, "WEM", data);
|
SaveAndPlaySound(entry.Path.SubstringBeforeWithLast('/') + name, "WEM", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "wem":
|
case "wem":
|
||||||
{
|
{
|
||||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
var data = Provider.SaveAsset(entry);
|
||||||
SaveAndPlaySound(asset.FullPath, "WEM", data);
|
SaveAndPlaySound(entry.Path, "WEM", data);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "udic":
|
case "udic":
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var header = new FOodleDictionaryArchive(archive).Header;
|
var header = new FOodleDictionaryArchive(archive).Header;
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
||||||
|
|
@ -686,15 +687,15 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
case "jpg":
|
case "jpg":
|
||||||
case "bmp":
|
case "bmp":
|
||||||
{
|
{
|
||||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
var data = Provider.SaveAsset(entry);
|
||||||
using var stream = new MemoryStream(data) { Position = 0 };
|
using var stream = new MemoryStream(data) { Position = 0 };
|
||||||
TabControl.SelectedTab.AddImage(asset.FileName.SubstringBeforeLast("."), false, SKBitmap.Decode(stream), saveTextures, updateUi);
|
TabControl.SelectedTab.AddImage(entry.NameWithoutExtension, false, SKBitmap.Decode(stream), saveTextures, updateUi);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "svg":
|
case "svg":
|
||||||
{
|
{
|
||||||
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
|
var data = Provider.SaveAsset(entry);
|
||||||
using var stream = new MemoryStream(data) { Position = 0 };
|
using var stream = new MemoryStream(data) { Position = 0 };
|
||||||
var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512));
|
var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512));
|
||||||
svg.Load(stream);
|
svg.Load(stream);
|
||||||
|
|
@ -706,7 +707,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
canvas.DrawPicture(svg.Picture, paint);
|
canvas.DrawPicture(svg.Picture, paint);
|
||||||
}
|
}
|
||||||
|
|
||||||
TabControl.SelectedTab.AddImage(asset.FileName.SubstringBeforeLast("."), false, bitmap, saveTextures, updateUi);
|
TabControl.SelectedTab.AddImage(entry.NameWithoutExtension, false, bitmap, saveTextures, updateUi);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -714,12 +715,12 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
case "otf":
|
case "otf":
|
||||||
case "ttf":
|
case "ttf":
|
||||||
FLogger.Append(ELog.Warning, () =>
|
FLogger.Append(ELog.Warning, () =>
|
||||||
FLogger.Text($"Export '{asset.FileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true));
|
FLogger.Text($"Export '{entry.Name}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true));
|
||||||
break;
|
break;
|
||||||
case "ushaderbytecode":
|
case "ushaderbytecode":
|
||||||
case "ushadercode":
|
case "ushadercode":
|
||||||
{
|
{
|
||||||
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
|
var archive = entry.CreateReader();
|
||||||
var ar = new FShaderCodeArchive(archive);
|
var ar = new FShaderCodeArchive(archive);
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), saveProperties, updateUi);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), saveProperties, updateUi);
|
||||||
|
|
||||||
|
|
@ -728,7 +729,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
FLogger.Append(ELog.Warning, () =>
|
FLogger.Append(ELog.Warning, () =>
|
||||||
FLogger.Text($"The package '{asset.FileName}' is of an unknown type.", Constants.WHITE, true));
|
FLogger.Text($"The package '{entry.Name}' is of an unknown type.", Constants.WHITE, true));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -737,10 +738,12 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
|
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
|
||||||
{
|
{
|
||||||
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
|
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
|
||||||
TabControl.AddTab(new AssetItem(fullPath), parentExportType);
|
|
||||||
|
var entry = Provider[fullPath];
|
||||||
|
TabControl.AddTab(entry, parentExportType);
|
||||||
TabControl.SelectedTab.ScrollTrigger = objectName;
|
TabControl.SelectedTab.ScrollTrigger = objectName;
|
||||||
|
|
||||||
var pkg = Provider.LoadPackage(fullPath);
|
var pkg = Provider.LoadPackage(entry);
|
||||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json
|
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), false, false);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), false, false);
|
||||||
|
|
||||||
|
|
@ -797,7 +800,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
{
|
{
|
||||||
var fileName = sourceFile.SubstringAfterLast('/');
|
var fileName = sourceFile.SubstringAfterLast('/');
|
||||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||||
UserSettings.Default.KeepDirectoryStructure ? TabControl.SelectedTab.Asset.Directory : "", fileName!).Replace('\\', '/');
|
UserSettings.Default.KeepDirectoryStructure ? TabControl.SelectedTab.Entry.Directory : "", fileName!).Replace('\\', '/');
|
||||||
|
|
||||||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||||
|
|
||||||
|
|
@ -838,7 +841,7 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Asset.FullPath.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
|
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension).Replace('\\', '/'), audioFormat, data);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
|
case UWorld when isNone && UserSettings.Default.PreviewWorlds:
|
||||||
|
|
@ -900,13 +903,11 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowMetadata(AssetItem asset)
|
public void ShowMetadata(GameFile entry)
|
||||||
{
|
{
|
||||||
var package = Provider.LoadPackage(asset.FullPath, asset.Archive);
|
var package = Provider.LoadPackage(entry);
|
||||||
|
|
||||||
var a = new AssetItem(" (Metadata)", asset);
|
TabControl.AddTab($"{entry.Name} (Metadata)");
|
||||||
if (TabControl.CanAddTabs) TabControl.AddTab(a);
|
|
||||||
else TabControl.SelectedTab.SoftReset(a);
|
|
||||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("");
|
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("");
|
||||||
|
|
||||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false);
|
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false);
|
||||||
|
|
@ -963,11 +964,9 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _rawData = new ();
|
private readonly object _rawData = new ();
|
||||||
public void ExportData(AssetItem asset, bool updateUi = true)
|
public void ExportData(GameFile entry, bool updateUi = true)
|
||||||
{
|
{
|
||||||
// TODO: export by archive
|
if (Provider.TrySavePackage(entry, out var assets))
|
||||||
// is that even useful? if user doesn't rename manually it's gonna overwrite the file anyway
|
|
||||||
if (Provider.TrySavePackage(asset.FullPath, out var assets))
|
|
||||||
{
|
{
|
||||||
string path = UserSettings.Default.RawDataDirectory;
|
string path = UserSettings.Default.RawDataDirectory;
|
||||||
Parallel.ForEach(assets, kvp =>
|
Parallel.ForEach(assets, kvp =>
|
||||||
|
|
@ -980,21 +979,21 @@ public class CUE4ParseViewModel : ViewModel
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.Information("{FileName} successfully exported", asset.FileName);
|
Log.Information("{FileName} successfully exported", entry.Name);
|
||||||
if (updateUi)
|
if (updateUi)
|
||||||
{
|
{
|
||||||
FLogger.Append(ELog.Information, () =>
|
FLogger.Append(ELog.Information, () =>
|
||||||
{
|
{
|
||||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||||
FLogger.Link(asset.FileName, path, true);
|
FLogger.Link(entry.Name, path, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log.Error("{FileName} could not be exported", asset.FileName);
|
Log.Error("{FileName} could not be exported", entry.Name);
|
||||||
if (updateUi)
|
if (updateUi)
|
||||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{asset.FileName}'", Constants.WHITE, true));
|
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using CUE4Parse.Utils;
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
||||||
namespace FModel.ViewModels.Commands;
|
namespace FModel.ViewModels.Commands;
|
||||||
|
|
@ -18,26 +18,26 @@ public class CopyCommand : ViewModelCommand<ApplicationViewModel>
|
||||||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
|
||||||
if (!assetItems.Any()) return;
|
if (!entries.Any()) return;
|
||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
switch (trigger)
|
switch (trigger)
|
||||||
{
|
{
|
||||||
case "File_Path":
|
case "File_Path":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
|
foreach (var entry in entries) sb.AppendLine(entry.Path);
|
||||||
break;
|
break;
|
||||||
case "File_Name":
|
case "File_Name":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
|
foreach (var entry in entries) sb.AppendLine(entry.Name);
|
||||||
break;
|
break;
|
||||||
case "Directory_Path":
|
case "Directory_Path":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
|
foreach (var entry in entries) sb.AppendLine(entry.Directory);
|
||||||
break;
|
break;
|
||||||
case "File_Path_No_Extension":
|
case "File_Path_No_Extension":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
|
foreach (var entry in entries) sb.AppendLine(entry.PathWithoutExtension);
|
||||||
break;
|
break;
|
||||||
case "File_Name_No_Extension":
|
case "File_Name_No_Extension":
|
||||||
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
|
foreach (var entry in entries) sb.AppendLine(entry.NameWithoutExtension);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AdonisUI.Controls;
|
using AdonisUI.Controls;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.UE4.Readers;
|
using CUE4Parse.UE4.Readers;
|
||||||
using CUE4Parse.UE4.VirtualFileSystem;
|
using CUE4Parse.UE4.VirtualFileSystem;
|
||||||
using CUE4Parse.Utils;
|
using CUE4Parse.Utils;
|
||||||
|
|
@ -37,13 +38,17 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
|
|
||||||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||||
{
|
{
|
||||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
|
||||||
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0)
|
if (_applicationView.CUE4Parse.Provider.Keys.Count == 0 && _applicationView.CUE4Parse.Provider.RequiredKeys.Count > 0)
|
||||||
{
|
{
|
||||||
FLogger.Append(ELog.Error, () =>
|
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));
|
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (_applicationView.CUE4Parse.Provider.Files.Count == 0)
|
||||||
|
{
|
||||||
|
FLogger.Append(ELog.Error, () => FLogger.Text("No files were found in the archives or the specified directory", Constants.WHITE, true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
var loadingTime = Stopwatch.StartNew();
|
var loadingTime = Stopwatch.StartNew();
|
||||||
|
|
@ -59,6 +64,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
_threadWorkerView.Begin(cancellationToken =>
|
_threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
// filter what to show
|
// filter what to show
|
||||||
|
_applicationView.Status.UpdateStatusLabel("Packages", "Filtering");
|
||||||
switch (UserSettings.Default.LoadingMode)
|
switch (UserSettings.Default.LoadingMode)
|
||||||
{
|
{
|
||||||
case ELoadingMode.Multiple:
|
case ELoadingMode.Multiple:
|
||||||
|
|
@ -100,42 +106,36 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
if (directoryFiles == null) filter = null;
|
if (directoryFiles == null) filter = null;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filter = new HashSet<string>();
|
filter = [];
|
||||||
foreach (var directoryFile in directoryFiles)
|
foreach (var directoryFile in directoryFiles)
|
||||||
{
|
{
|
||||||
if (!directoryFile.IsEnabled)
|
if (!directoryFile.IsEnabled) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
filter.Add(directoryFile.Name);
|
filter.Add(directoryFile.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasFilter = filter != null && filter.Count != 0;
|
var hasFilter = filter != null && filter.Count != 0;
|
||||||
var entries = new List<VfsEntry>();
|
var entries = new List<GameFile>();
|
||||||
|
|
||||||
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||||
|
if (asset.IsUePackagePayload) continue;
|
||||||
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (hasFilter)
|
if (hasFilter)
|
||||||
{
|
{
|
||||||
if (filter.Contains(entry.Vfs.Name))
|
if (asset is VfsEntry entry && filter.Contains(entry.Vfs.Name))
|
||||||
{
|
{
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_applicationView.Status.UpdateStatusLabel("Folders & Packages");
|
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
|
||||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,11 +157,11 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
var mode = UserSettings.Default.LoadingMode;
|
var mode = UserSettings.Default.LoadingMode;
|
||||||
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
|
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
|
||||||
|
|
||||||
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
|
||||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<VfsEntry> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
private List<GameFile> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var fileStream = new FileStream(path, FileMode.Open);
|
using var fileStream = new FileStream(path, FileMode.Open);
|
||||||
using var memoryStream = new MemoryStream();
|
using var memoryStream = new MemoryStream();
|
||||||
|
|
@ -176,7 +176,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
|
|
||||||
memoryStream.Position = 0;
|
memoryStream.Position = 0;
|
||||||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||||
var entries = new List<VfsEntry>();
|
var entries = new List<GameFile>();
|
||||||
|
|
||||||
switch (mode)
|
switch (mode)
|
||||||
{
|
{
|
||||||
|
|
@ -209,14 +209,12 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
foreach (var (key, asset) in _applicationView.CUE4Parse.Provider.Files)
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
if (value is not VfsEntry entry || paths.Contains(key) || entry.Path.EndsWith(".uexp") ||
|
if (asset.IsUePackagePayload || paths.Contains(key)) continue;
|
||||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
|
||||||
|
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -263,14 +261,12 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<VfsEntry> entries)
|
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<GameFile> entries)
|
||||||
{
|
{
|
||||||
if (path.EndsWith(".uexp") || path.EndsWith(".ubulk") || path.EndsWith(".uptnl") ||
|
if (!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) ||
|
||||||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) || asset is not VfsEntry entry ||
|
asset.IsUePackagePayload || asset.Size == uncompressedSize && asset.IsEncrypted == isEncrypted)
|
||||||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
entries.Add(entry);
|
entries.Add(asset);
|
||||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
|
|
||||||
|
|
@ -19,68 +20,68 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||||
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
if (parameter is not object[] parameters || parameters[0] is not string trigger)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
|
||||||
if (!assetItems.Any()) return;
|
if (!entries.Any()) return;
|
||||||
|
|
||||||
var updateUi = assetItems.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
var updateUi = entries.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
switch (trigger)
|
switch (trigger)
|
||||||
{
|
{
|
||||||
case "Assets_Extract_New_Tab":
|
case "Assets_Extract_New_Tab":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, true);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Show_Metadata":
|
case "Assets_Show_Metadata":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.ShowMetadata(asset);
|
contextViewModel.CUE4Parse.ShowMetadata(entry);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Export_Data":
|
case "Assets_Export_Data":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.ExportData(asset);
|
contextViewModel.CUE4Parse.ExportData(entry);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Properties":
|
case "Assets_Save_Properties":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Properties | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Textures":
|
case "Assets_Save_Textures":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Textures | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Models":
|
case "Assets_Save_Models":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Meshes | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "Assets_Save_Animations":
|
case "Assets_Save_Animations":
|
||||||
foreach (var asset in assetItems)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
Thread.Yield();
|
Thread.Yield();
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Animations | updateUi);
|
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,44 +32,44 @@ public class TabCommand : ViewModelCommand<TabItem>
|
||||||
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
|
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
|
||||||
break;
|
break;
|
||||||
case "Asset_Export_Data":
|
case "Asset_Export_Data":
|
||||||
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Asset));
|
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Entry));
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Properties":
|
case "Asset_Save_Properties":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Properties);
|
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Properties);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Textures":
|
case "Asset_Save_Textures":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Textures);
|
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Textures);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Models":
|
case "Asset_Save_Models":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Meshes);
|
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Meshes);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Asset_Save_Animations":
|
case "Asset_Save_Animations":
|
||||||
await _threadWorkerView.Begin(cancellationToken =>
|
await _threadWorkerView.Begin(cancellationToken =>
|
||||||
{
|
{
|
||||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Animations);
|
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Animations);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Open_Properties":
|
case "Open_Properties":
|
||||||
if (contextViewModel.Asset.FileName == "New Tab" || contextViewModel.Document == null) return;
|
if (contextViewModel.Entry.Name == "New Tab" || contextViewModel.Document == null) return;
|
||||||
Helper.OpenWindow<AdonisWindow>(contextViewModel.Asset.FileName + " (Properties)", () =>
|
Helper.OpenWindow<AdonisWindow>(contextViewModel.Entry.Name + " (Properties)", () =>
|
||||||
{
|
{
|
||||||
new PropertiesPopout(contextViewModel)
|
new PropertiesPopout(contextViewModel)
|
||||||
{
|
{
|
||||||
Title = contextViewModel.Asset.FileName + " (Properties)"
|
Title = contextViewModel.Entry.Name + " (Properties)"
|
||||||
}.Show();
|
}.Show();
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "Copy_Asset_Path":
|
case "Copy_Asset_Path":
|
||||||
Clipboard.SetText(contextViewModel.Asset.FullPath);
|
Clipboard.SetText(contextViewModel.Entry.Path);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Framework;
|
using FModel.Framework;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
@ -32,12 +33,12 @@ public class SearchViewModel : ViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
public int ResultsCount => SearchResults?.Count ?? 0;
|
public int ResultsCount => SearchResults?.Count ?? 0;
|
||||||
public RangeObservableCollection<AssetItem> SearchResults { get; }
|
public RangeObservableCollection<GameFile> SearchResults { get; }
|
||||||
public ICollectionView SearchResultsView { get; }
|
public ICollectionView SearchResultsView { get; }
|
||||||
|
|
||||||
public SearchViewModel()
|
public SearchViewModel()
|
||||||
{
|
{
|
||||||
SearchResults = new RangeObservableCollection<AssetItem>();
|
SearchResults = new RangeObservableCollection<GameFile>();
|
||||||
SearchResultsView = new ListCollectionView(SearchResults);
|
SearchResultsView = new ListCollectionView(SearchResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,14 +52,14 @@ public class SearchViewModel : ViewModel
|
||||||
|
|
||||||
private bool ItemFilter(object item, IEnumerable<string> filters)
|
private bool ItemFilter(object item, IEnumerable<string> filters)
|
||||||
{
|
{
|
||||||
if (item is not AssetItem assetItem)
|
if (item is not GameFile entry)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!HasRegexEnabled)
|
if (!HasRegexEnabled)
|
||||||
return filters.All(x => assetItem.FullPath.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
return filters.All(x => entry.Path.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var o = RegexOptions.None;
|
var o = RegexOptions.None;
|
||||||
if (!HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
|
if (!HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
|
||||||
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
|
return new Regex(FilterText, o).Match(entry.Path).Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ using ICSharpCode.AvalonEdit.Document;
|
||||||
using ICSharpCode.AvalonEdit.Highlighting;
|
using ICSharpCode.AvalonEdit.Highlighting;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -16,6 +15,7 @@ using System.Windows;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using CUE4Parse.Utils;
|
using CUE4Parse.Utils;
|
||||||
|
|
||||||
namespace FModel.ViewModels;
|
namespace FModel.ViewModels;
|
||||||
|
|
@ -93,11 +93,11 @@ public class TabItem : ViewModel
|
||||||
{
|
{
|
||||||
public string ParentExportType { get; private set; }
|
public string ParentExportType { get; private set; }
|
||||||
|
|
||||||
private AssetItem _asset;
|
private GameFile _entry;
|
||||||
public AssetItem Asset
|
public GameFile Entry
|
||||||
{
|
{
|
||||||
get => _asset;
|
get => _entry;
|
||||||
set => SetProperty(ref _asset, value);
|
set => SetProperty(ref _entry, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _hasSearchOpen;
|
private bool _hasSearchOpen;
|
||||||
|
|
@ -209,16 +209,16 @@ public class TabItem : ViewModel
|
||||||
private GoToCommand _goToCommand;
|
private GoToCommand _goToCommand;
|
||||||
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
|
||||||
|
|
||||||
public TabItem(AssetItem asset, string parentExportType)
|
public TabItem(GameFile entry, string parentExportType)
|
||||||
{
|
{
|
||||||
Asset = asset;
|
Entry = entry;
|
||||||
ParentExportType = parentExportType;
|
ParentExportType = parentExportType;
|
||||||
_images = new ObservableCollection<TabImage>();
|
_images = new ObservableCollection<TabImage>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SoftReset(AssetItem asset)
|
public void SoftReset(GameFile entry)
|
||||||
{
|
{
|
||||||
Asset = asset;
|
Entry = entry;
|
||||||
ParentExportType = string.Empty;
|
ParentExportType = string.Empty;
|
||||||
ScrollTrigger = null;
|
ScrollTrigger = null;
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
|
@ -306,7 +306,7 @@ public class TabItem : ViewModel
|
||||||
|
|
||||||
var fileName = image.ExportName + ext;
|
var fileName = image.ExportName + ext;
|
||||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||||
UserSettings.Default.KeepDirectoryStructure ? Asset.Directory : "", fileName!).Replace('\\', '/');
|
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName!).Replace('\\', '/');
|
||||||
|
|
||||||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||||
|
|
||||||
|
|
@ -327,9 +327,9 @@ public class TabItem : ViewModel
|
||||||
|
|
||||||
public void SaveProperty(bool updateUi)
|
public void SaveProperty(bool updateUi)
|
||||||
{
|
{
|
||||||
var fileName = Path.ChangeExtension(Asset.FileName, ".json");
|
var fileName = Path.ChangeExtension(Entry.Name, ".json");
|
||||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||||
UserSettings.Default.KeepDirectoryStructure ? Asset.Directory : "", fileName).Replace('\\', '/');
|
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/');
|
||||||
|
|
||||||
Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||||
|
|
||||||
|
|
@ -386,21 +386,19 @@ public class TabControlViewModel : ViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddTab() => AddTab("New Tab");
|
public void AddTab() => AddTab("New Tab");
|
||||||
public void AddTab(string title) => AddTab(new AssetItem(title));
|
public void AddTab(string title) => AddTab(new FakeGameFile(title));
|
||||||
public void AddTab(AssetItem asset, string parentExportType = null)
|
public void AddTab(GameFile entry, string parentExportType = null)
|
||||||
{
|
{
|
||||||
if (!CanAddTabs) return;
|
if (SelectedTab?.Entry.Name == "New Tab")
|
||||||
|
|
||||||
var p = parentExportType ?? string.Empty;
|
|
||||||
if (SelectedTab?.Asset.FileName == "New Tab")
|
|
||||||
{
|
{
|
||||||
SelectedTab.Asset = asset;
|
SelectedTab.Entry = entry;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CanAddTabs) return;
|
||||||
Application.Current.Dispatcher.Invoke(() =>
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
_tabItems.Add(new TabItem(asset, p));
|
_tabItems.Add(new TabItem(entry, parentExportType ?? string.Empty));
|
||||||
SelectedTab = _tabItems.Last();
|
SelectedTab = _tabItems.Last();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ public partial class AvalonEditor
|
||||||
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
|
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
|
||||||
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
|
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
|
||||||
return;
|
return;
|
||||||
avalonEditor.Document.FileName = tabItem.Asset.FullPath.SubstringBeforeLast('.');
|
avalonEditor.Document.FileName = tabItem.Entry.PathWithoutExtension;
|
||||||
|
|
||||||
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
|
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
|
||||||
_ignoreCaret = true;
|
_ignoreCaret = true;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public partial class PropertiesPopout
|
||||||
MyAvalonEditor.Document = new TextDocument
|
MyAvalonEditor.Document = new TextDocument
|
||||||
{
|
{
|
||||||
Text = contextViewModel.Document.Text,
|
Text = contextViewModel.Document.Text,
|
||||||
FileName = contextViewModel.Asset.FullPath.SubstringBeforeLast('.')
|
FileName = contextViewModel.Entry.PathWithoutExtension
|
||||||
};
|
};
|
||||||
MyAvalonEditor.FontSize = contextViewModel.FontSize;
|
MyAvalonEditor.FontSize = contextViewModel.FontSize;
|
||||||
MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter;
|
MyAvalonEditor.SyntaxHighlighting = contextViewModel.Highlighter;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows.Data;
|
|
||||||
|
|
||||||
namespace FModel.Views.Resources.Converters;
|
|
||||||
|
|
||||||
public class FileExtensionEqualsConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public static readonly FileExtensionEqualsConverter Instance = new();
|
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
return value.ToString().EndsWith(parameter.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using CUE4Parse.Utils;
|
|
||||||
|
|
||||||
namespace FModel.Views.Resources.Converters;
|
|
||||||
|
|
||||||
public class FullPathToFileConverter : IValueConverter
|
|
||||||
{
|
|
||||||
public static readonly FullPathToFileConverter Instance = new();
|
|
||||||
|
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
return value.ToString().SubstringAfterLast('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -203,7 +203,7 @@
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<Grid>
|
<Grid>
|
||||||
<TextBlock Text="No archives found in given directory yet" FontWeight="SemiBold" TextAlignment="Center"
|
<TextBlock Text="No archives found in the specified directory" FontWeight="SemiBold" TextAlignment="Center"
|
||||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
|
|
@ -596,19 +596,19 @@
|
||||||
|
|
||||||
<Image x:Name="ListImage" Source="/FModel;component/Resources/unknown_asset.png"
|
<Image x:Name="ListImage" Source="/FModel;component/Resources/unknown_asset.png"
|
||||||
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3 0" />
|
Width="16" Height="16" HorizontalAlignment="Center" Margin="0 0 3 0" />
|
||||||
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding FullPath, Converter={x:Static converters:FullPathToFileConverter.Instance}}" />
|
<TextBlock Grid.Column="1" HorizontalAlignment="Left" Text="{Binding Name}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
<DataTemplate.Triggers>
|
<DataTemplate.Triggers>
|
||||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.uasset'}" Value="True">
|
<DataTrigger Binding="{Binding Extension}" Value="uasset">
|
||||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset.png" />
|
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset.png" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.ini'}" Value="True">
|
<DataTrigger Binding="{Binding Extension}" Value="ini">
|
||||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_ini.png" />
|
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_ini.png" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.png'}" Value="True">
|
<DataTrigger Binding="{Binding Extension}" Value="png">
|
||||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_png.png" />
|
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_png.png" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
<DataTrigger Binding="{Binding FullPath, Converter={x:Static converters:FileExtensionEqualsConverter.Instance}, ConverterParameter='.psd'}" Value="True">
|
<DataTrigger Binding="{Binding Extension}" Value="psd">
|
||||||
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_psd.png" />
|
<Setter TargetName="ListImage" Property="Source" Value="/FModel;component/Resources/asset_psd.png" />
|
||||||
</DataTrigger>
|
</DataTrigger>
|
||||||
</DataTemplate.Triggers>
|
</DataTemplate.Triggers>
|
||||||
|
|
@ -651,7 +651,7 @@
|
||||||
<MouseBinding MouseAction="MiddleClick" Command="{Binding SelectedItem.TabCommand, ElementName=TabControlName}" CommandParameter="{Binding}" />
|
<MouseBinding MouseAction="MiddleClick" Command="{Binding SelectedItem.TabCommand, ElementName=TabControlName}" CommandParameter="{Binding}" />
|
||||||
</DockPanel.InputBindings>
|
</DockPanel.InputBindings>
|
||||||
|
|
||||||
<TextBlock DockPanel.Dock="Left" Text="{Binding Asset.FileName}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Asset.FileName}">
|
<TextBlock DockPanel.Dock="Left" Text="{Binding Entry.Name}" TextTrimming="CharacterEllipsis" ToolTip="{Binding Entry.Name}">
|
||||||
<TextBlock.Width>
|
<TextBlock.Width>
|
||||||
<MultiBinding Converter="{x:Static converters:TabSizeConverter.Instance}" ConverterParameter="6">
|
<MultiBinding Converter="{x:Static converters:TabSizeConverter.Instance}" ConverterParameter="6">
|
||||||
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}" />
|
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}" />
|
||||||
|
|
@ -856,7 +856,10 @@
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data">
|
<MenuItem Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<TextBlock Text="{Binding Entry.Extension, FallbackValue='uasset', StringFormat='Export Raw Data (.{0})'}" />
|
||||||
|
</MenuItem.Header>
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Viewbox Width="16" Height="16">
|
<Viewbox Width="16" Height="16">
|
||||||
<Canvas Width="24" Height="24">
|
<Canvas Width="24" Height="24">
|
||||||
|
|
@ -913,7 +916,7 @@
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem Header="Jump to Package Folder" Command="{Binding GoToCommand}" CommandParameter="{Binding Asset.Directory}">
|
<MenuItem Header="Jump to Package Folder" Command="{Binding GoToCommand}" CommandParameter="{Binding Entry.Directory}">
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<Viewbox Width="16" Height="16">
|
<Viewbox Width="16" Height="16">
|
||||||
<Canvas Width="24" Height="24">
|
<Canvas Width="24" Height="24">
|
||||||
|
|
|
||||||
|
|
@ -96,14 +96,14 @@
|
||||||
<GridViewColumn Width="900" Header="Path" adonisExtensions:GridViewSortExtension.PropertyName="Path">
|
<GridViewColumn Width="900" Header="Path" adonisExtensions:GridViewSortExtension.PropertyName="Path">
|
||||||
<GridViewColumn.CellTemplate>
|
<GridViewColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock HorizontalAlignment="Left" Text="{Binding FullPath}" />
|
<TextBlock HorizontalAlignment="Left" Text="{Binding Path}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</GridViewColumn.CellTemplate>
|
</GridViewColumn.CellTemplate>
|
||||||
</GridViewColumn>
|
</GridViewColumn>
|
||||||
<GridViewColumn Width="225" Header="Archive" adonisExtensions:GridViewSortExtension.PropertyName="Archive">
|
<GridViewColumn Width="225" Header="Archive" adonisExtensions:GridViewSortExtension.PropertyName="Archive">
|
||||||
<GridViewColumn.CellTemplate>
|
<GridViewColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock HorizontalAlignment="Left" Text="{Binding Archive}" />
|
<TextBlock HorizontalAlignment="Left" Text="{Binding Vfs.Name}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</GridViewColumn.CellTemplate>
|
</GridViewColumn.CellTemplate>
|
||||||
</GridViewColumn>
|
</GridViewColumn>
|
||||||
|
|
@ -155,7 +155,14 @@
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
<MenuItem Command="{Binding DataContext.RightClickMenuCommand}">
|
||||||
|
<MenuItem.Header>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding DataContext.SelectedItem.Extension,
|
||||||
|
FallbackValue='uasset',
|
||||||
|
StringFormat='Export Raw Data (.{0})',
|
||||||
|
RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
</MenuItem.Header>
|
||||||
<MenuItem.CommandParameter>
|
<MenuItem.CommandParameter>
|
||||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||||
<Binding Source="Assets_Export_Data" />
|
<Binding Source="Assets_Export_Data" />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
|
using CUE4Parse.FileProvider.Objects;
|
||||||
using FModel.Services;
|
using FModel.Services;
|
||||||
using FModel.ViewModels;
|
using FModel.ViewModels;
|
||||||
|
|
||||||
|
|
@ -30,12 +31,12 @@ public partial class SearchView
|
||||||
|
|
||||||
private async void OnAssetDoubleClick(object sender, RoutedEventArgs e)
|
private async void OnAssetDoubleClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (SearchListView.SelectedItem is not AssetItem assetItem)
|
if (SearchListView.SelectedItem is not GameFile entry)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
MainWindow.YesWeCats.AssetsListName.ItemsSource = null;
|
MainWindow.YesWeCats.AssetsListName.ItemsSource = null;
|
||||||
var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(assetItem.Directory);
|
var folder = _applicationView.CustomDirectories.GoToCommand.JumpTo(entry.Directory);
|
||||||
if (folder == null) return;
|
if (folder == null) return;
|
||||||
|
|
||||||
MainWindow.YesWeCats.Activate();
|
MainWindow.YesWeCats.Activate();
|
||||||
|
|
@ -46,18 +47,18 @@ public partial class SearchView
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
MainWindow.YesWeCats.AssetsListName.SelectedItem = assetItem;
|
MainWindow.YesWeCats.AssetsListName.SelectedItem = entry;
|
||||||
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(assetItem);
|
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(entry);
|
||||||
} while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null);
|
} while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnAssetExtract(object sender, RoutedEventArgs e)
|
private async void OnAssetExtract(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (SearchListView.SelectedItem is not AssetItem assetItem)
|
if (SearchListView.SelectedItem is not GameFile entry)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
WindowState = WindowState.Minimized;
|
WindowState = WindowState.Minimized;
|
||||||
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, assetItem, true));
|
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, entry, true));
|
||||||
|
|
||||||
MainWindow.YesWeCats.Activate();
|
MainWindow.YesWeCats.Activate();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user