ui now deals with GameFile instead of a bad replica
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:
Asval 2025-02-06 18:22:35 +01:00
parent 3d0bdf05e1
commit dbf618e417
20 changed files with 247 additions and 337 deletions

@ -1 +1 @@
Subproject commit ab6dff8e98e94335916a549810466eb4571a072b Subproject commit 11a92870024a088888aae79c74d8ae0c6c8af3e5

View 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();
}
}

View File

@ -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>

View File

@ -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;
} }

View File

@ -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);

View File

@ -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) }
}; };
} }
} }

View File

@ -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));
} }
} }

View File

@ -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;
} }

View File

@ -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);
} }
} }

View File

@ -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;
} }

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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();
}); });
} }

View File

@ -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;

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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">

View File

@ -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" />

View File

@ -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();
} }