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>
<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>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<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="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="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="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="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" />
</Grid>
</StackPanel>

View File

@ -6,6 +6,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using AdonisUI.Controls;
using CUE4Parse.FileProvider.Objects;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels;
@ -84,7 +85,7 @@ public partial class MainWindow
#if DEBUG
// await _threadWorkerView.Begin(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 =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "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;
var selectedItems = listBox.SelectedItems.Cast<AssetItem>().ToList();
var selectedItems = listBox.SelectedItems.Cast<GameFile>().ToList();
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExtractSelected(cancellationToken, selectedItems); });
}
@ -266,7 +267,7 @@ public partial class MainWindow
return;
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)
@ -283,7 +284,7 @@ public partial class MainWindow
switch (e.Key)
{
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); });
break;
}

View File

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Text;
using System.Windows;
using System.Windows.Data;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Versions;
using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Framework;
@ -60,12 +61,15 @@ public class TreeItem : ViewModel
public RangeObservableCollection<TreeItem> Folders { 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;
Archive = archive;
MountPoint = mountPoint;
Version = version;
if (entry is VfsEntry vfsEntry)
{
Archive = vfsEntry.Vfs.Name;
MountPoint = vfsEntry.Vfs.MountPoint;
Version = vfsEntry.Vfs.Ver;
}
PathAtThisPoint = pathHere;
AssetsList = new AssetsListViewModel();
Folders = new RangeObservableCollection<TreeItem>();
@ -86,7 +90,7 @@ public class AssetsFolderViewModel
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)
return;
@ -95,54 +99,48 @@ public class AssetsFolderViewModel
{
var treeItems = new RangeObservableCollection<TreeItem>();
treeItems.SetSuppressionState(true);
var items = new List<AssetItem>(entries.Count);
foreach (var entry in entries)
{
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
items.Add(item);
TreeItem lastNode = null;
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 folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;
var folder = folders[i];
builder.Append(folder).Append('/');
lastNode = FindByHeaderOrNull(parentNode, folder);
for (var i = 0; i < folders.Length - 1; i++)
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
{
var folder = folders[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++)
{
for (var i = 0; i < list.Count; i++)
{
if (list[i].Header == header)
return list[i];
}
return null;
if (list[i].Header == header)
return list[i];
}
if (lastNode == 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;
return null;
}
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);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(entries);
foreach (var folder in Folders)
InvokeOnCollectionChanged(folder);

View File

@ -1,109 +1,21 @@
using System.ComponentModel;
using System.Windows.Data;
using CUE4Parse.Compression;
using CUE4Parse.Utils;
using CUE4Parse.FileProvider.Objects;
using FModel.Framework;
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 RangeObservableCollection<AssetItem> Assets { get; }
public RangeObservableCollection<GameFile> Assets { get; }
public ICollectionView AssetsView { get; }
public AssetsListViewModel()
{
Assets = new RangeObservableCollection<AssetItem>();
Assets = new RangeObservableCollection<GameFile>();
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_Conversion;
using CUE4Parse_Conversion.Sounds;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Objects.UObject;
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();
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();
cancellationToken.ThrowIfCancellationRequested();
try
{
action(asset);
action(entry);
}
catch
{
@ -530,10 +531,10 @@ public class CUE4ParseViewModel : ViewModel
public void ExportFolder(CancellationToken cancellationToken, TreeItem folder)
{
Parallel.ForEach(folder.AssetsList.Assets, asset =>
Parallel.ForEach(folder.AssetsList.Assets, entry =>
{
cancellationToken.ThrowIfCancellationRequested();
ExportData(asset, false);
ExportData(entry, false);
});
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
@ -554,23 +555,23 @@ public class CUE4ParseViewModel : ViewModel
public void AnimationFolder(CancellationToken cancellationToken, TreeItem folder)
=> 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);
else TabControl.SelectedTab.SoftReset(asset);
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(asset.Extension);
if (addNewTab && TabControl.CanAddTabs) TabControl.AddTab(entry);
else TabControl.SelectedTab.SoftReset(entry);
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(entry.Extension);
var updateUi = !HasFlag(bulk, EBulkType.Auto);
var saveProperties = HasFlag(bulk, EBulkType.Properties);
var saveTextures = HasFlag(bulk, EBulkType.Textures);
switch (asset.Extension)
switch (entry.Extension)
{
case "uasset":
case "umap":
{
var pkg = Provider.LoadPackage(asset.FullPath, asset.Archive);
var pkg = Provider.LoadPackage(entry);
if (saveProperties || updateUi)
{
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), saveProperties, updateUi);
@ -614,7 +615,7 @@ public class CUE4ParseViewModel : ViewModel
case "po":
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 reader = new StreamReader(stream);
@ -624,7 +625,7 @@ public class CUE4ParseViewModel : ViewModel
}
case "locmeta":
{
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
var archive = entry.CreateReader();
var metadata = new FTextLocalizationMetaDataResource(archive);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), saveProperties, updateUi);
@ -632,23 +633,23 @@ public class CUE4ParseViewModel : ViewModel
}
case "locres":
{
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
var archive = entry.CreateReader();
var locres = new FTextLocalizationResource(archive);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), saveProperties, updateUi);
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);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
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);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
@ -657,26 +658,26 @@ public class CUE4ParseViewModel : ViewModel
case "bnk":
case "pck":
{
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
var archive = entry.CreateReader();
var wwise = new WwiseReader(archive);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
foreach (var (name, data) in wwise.WwiseEncodedMedias)
{
SaveAndPlaySound(asset.FullPath.SubstringBeforeWithLast('/') + name, "WEM", data);
SaveAndPlaySound(entry.Path.SubstringBeforeWithLast('/') + name, "WEM", data);
}
break;
}
case "wem":
{
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
SaveAndPlaySound(asset.FullPath, "WEM", data);
var data = Provider.SaveAsset(entry);
SaveAndPlaySound(entry.Path, "WEM", data);
break;
}
case "udic":
{
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
var archive = entry.CreateReader();
var header = new FOodleDictionaryArchive(archive).Header;
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), saveProperties, updateUi);
@ -686,15 +687,15 @@ public class CUE4ParseViewModel : ViewModel
case "jpg":
case "bmp":
{
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
var data = Provider.SaveAsset(entry);
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;
}
case "svg":
{
var data = Provider.SaveAsset(asset.FullPath, asset.Archive);
var data = Provider.SaveAsset(entry);
using var stream = new MemoryStream(data) { Position = 0 };
var svg = new SkiaSharp.Extended.Svg.SKSvg(new SKSize(512, 512));
svg.Load(stream);
@ -706,7 +707,7 @@ public class CUE4ParseViewModel : ViewModel
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;
}
@ -714,12 +715,12 @@ public class CUE4ParseViewModel : ViewModel
case "otf":
case "ttf":
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;
case "ushaderbytecode":
case "ushadercode":
{
var archive = Provider.CreateReader(asset.FullPath, asset.Archive);
var archive = entry.CreateReader();
var ar = new FShaderCodeArchive(archive);
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), saveProperties, updateUi);
@ -728,7 +729,7 @@ public class CUE4ParseViewModel : ViewModel
default:
{
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;
}
}
@ -737,10 +738,12 @@ public class CUE4ParseViewModel : ViewModel
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType)
{
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;
var pkg = Provider.LoadPackage(fullPath);
var pkg = Provider.LoadPackage(entry);
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(pkg.GetExports(), Formatting.Indented), false, false);
@ -797,7 +800,7 @@ public class CUE4ParseViewModel : ViewModel
{
var fileName = sourceFile.SubstringAfterLast('/');
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('/'));
@ -838,7 +841,7 @@ public class CUE4ParseViewModel : ViewModel
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;
}
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);
if (TabControl.CanAddTabs) TabControl.AddTab(a);
else TabControl.SelectedTab.SoftReset(a);
TabControl.AddTab($"{entry.Name} (Metadata)");
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("");
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(package, Formatting.Indented), false, false);
@ -963,11 +964,9 @@ public class CUE4ParseViewModel : ViewModel
}
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
// 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))
if (Provider.TrySavePackage(entry, out var assets))
{
string path = UserSettings.Default.RawDataDirectory;
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)
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully exported ", Constants.WHITE);
FLogger.Link(asset.FileName, path, true);
FLogger.Link(entry.Name, path, true);
});
}
}
else
{
Log.Error("{FileName} could not be exported", asset.FileName);
Log.Error("{FileName} could not be exported", entry.Name);
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.Text;
using System.Windows;
using CUE4Parse.Utils;
using CUE4Parse.FileProvider.Objects;
using FModel.Framework;
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)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
if (!entries.Any()) return;
var sb = new StringBuilder();
switch (trigger)
{
case "File_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
foreach (var entry in entries) sb.AppendLine(entry.Path);
break;
case "File_Name":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
foreach (var entry in entries) sb.AppendLine(entry.Name);
break;
case "Directory_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
foreach (var entry in entries) sb.AppendLine(entry.Directory);
break;
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;
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;
}

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AdonisUI.Controls;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.UE4.Readers;
using CUE4Parse.UE4.VirtualFileSystem;
using CUE4Parse.Utils;
@ -37,13 +38,17 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
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)
{
FLogger.Append(ELog.Error, () =>
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
return;
}
if (_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
var loadingTime = Stopwatch.StartNew();
@ -59,6 +64,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
_threadWorkerView.Begin(cancellationToken =>
{
// filter what to show
_applicationView.Status.UpdateStatusLabel("Packages", "Filtering");
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.Multiple:
@ -100,42 +106,36 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
if (directoryFiles == null) filter = null;
else
{
filter = new HashSet<string>();
filter = [];
foreach (var directoryFile in directoryFiles)
{
if (!directoryFile.IsEnabled)
continue;
if (!directoryFile.IsEnabled) continue;
filter.Add(directoryFile.Name);
}
}
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)
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
if (asset.IsUePackagePayload) continue;
if (hasFilter)
{
if (filter.Contains(entry.Vfs.Name))
if (asset is VfsEntry entry && filter.Contains(entry.Vfs.Name))
{
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
entries.Add(asset);
}
}
else
{
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
entries.Add(asset);
}
}
_applicationView.Status.UpdateStatusLabel("Folders & Packages");
_applicationView.Status.UpdateStatusLabel($"{entries.Count:### ### ###} Packages");
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
@ -157,11 +157,11 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
var mode = UserSettings.Default.LoadingMode;
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);
}
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 memoryStream = new MemoryStream();
@ -176,7 +176,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
memoryStream.Position = 0;
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
var entries = new List<VfsEntry>();
var entries = new List<GameFile>();
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();
if (value is not VfsEntry entry || paths.Contains(key) || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
if (asset.IsUePackagePayload || paths.Contains(key)) continue;
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
entries.Add(asset);
}
break;
@ -263,14 +261,12 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
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") ||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
if (!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) ||
asset.IsUePackagePayload || asset.Size == uncompressedSize && asset.IsEncrypted == isEncrypted)
return;
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
entries.Add(asset);
}
}

View File

@ -1,6 +1,7 @@
using System.Collections;
using System.Linq;
using System.Threading;
using CUE4Parse.FileProvider.Objects;
using FModel.Framework;
using FModel.Services;
@ -19,68 +20,68 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
var entries = ((IList) parameters[1]).Cast<GameFile>().ToArray();
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 =>
{
switch (trigger)
{
case "Assets_Extract_New_Tab":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, true);
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true);
}
break;
case "Assets_Show_Metadata":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ShowMetadata(asset);
contextViewModel.CUE4Parse.ShowMetadata(entry);
}
break;
case "Assets_Export_Data":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportData(asset);
contextViewModel.CUE4Parse.ExportData(entry);
}
break;
case "Assets_Save_Properties":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Properties | updateUi);
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi);
}
break;
case "Assets_Save_Textures":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Textures | updateUi);
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi);
}
break;
case "Assets_Save_Models":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Meshes | updateUi);
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi);
}
break;
case "Assets_Save_Animations":
foreach (var asset in assetItems)
foreach (var entry in entries)
{
Thread.Yield();
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(cancellationToken, asset, false, EBulkType.Animations | updateUi);
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi);
}
break;
}

View File

@ -32,44 +32,44 @@ public class TabCommand : ViewModelCommand<TabItem>
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
break;
case "Asset_Export_Data":
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Asset));
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(contextViewModel.Entry));
break;
case "Asset_Save_Properties":
await _threadWorkerView.Begin(cancellationToken =>
{
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Properties);
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Properties);
});
break;
case "Asset_Save_Textures":
await _threadWorkerView.Begin(cancellationToken =>
{
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Textures);
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Textures);
});
break;
case "Asset_Save_Models":
await _threadWorkerView.Begin(cancellationToken =>
{
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Meshes);
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Meshes);
});
break;
case "Asset_Save_Animations":
await _threadWorkerView.Begin(cancellationToken =>
{
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Asset, false, EBulkType.Animations);
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.Entry, false, EBulkType.Animations);
});
break;
case "Open_Properties":
if (contextViewModel.Asset.FileName == "New Tab" || contextViewModel.Document == null) return;
Helper.OpenWindow<AdonisWindow>(contextViewModel.Asset.FileName + " (Properties)", () =>
if (contextViewModel.Entry.Name == "New Tab" || contextViewModel.Document == null) return;
Helper.OpenWindow<AdonisWindow>(contextViewModel.Entry.Name + " (Properties)", () =>
{
new PropertiesPopout(contextViewModel)
{
Title = contextViewModel.Asset.FileName + " (Properties)"
Title = contextViewModel.Entry.Name + " (Properties)"
}.Show();
});
break;
case "Copy_Asset_Path":
Clipboard.SetText(contextViewModel.Asset.FullPath);
Clipboard.SetText(contextViewModel.Entry.Path);
break;
}
}

View File

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Data;
using CUE4Parse.FileProvider.Objects;
using FModel.Framework;
namespace FModel.ViewModels;
@ -32,12 +33,12 @@ public class SearchViewModel : ViewModel
}
public int ResultsCount => SearchResults?.Count ?? 0;
public RangeObservableCollection<AssetItem> SearchResults { get; }
public RangeObservableCollection<GameFile> SearchResults { get; }
public ICollectionView SearchResultsView { get; }
public SearchViewModel()
{
SearchResults = new RangeObservableCollection<AssetItem>();
SearchResults = new RangeObservableCollection<GameFile>();
SearchResultsView = new ListCollectionView(SearchResults);
}
@ -51,14 +52,14 @@ public class SearchViewModel : ViewModel
private bool ItemFilter(object item, IEnumerable<string> filters)
{
if (item is not AssetItem assetItem)
if (item is not GameFile entry)
return true;
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;
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 Serilog;
using SkiaSharp;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
@ -16,6 +15,7 @@ using System.Windows;
using System.Windows.Media.Imaging;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.FileProvider.Objects;
using CUE4Parse.Utils;
namespace FModel.ViewModels;
@ -93,11 +93,11 @@ public class TabItem : ViewModel
{
public string ParentExportType { get; private set; }
private AssetItem _asset;
public AssetItem Asset
private GameFile _entry;
public GameFile Entry
{
get => _asset;
set => SetProperty(ref _asset, value);
get => _entry;
set => SetProperty(ref _entry, value);
}
private bool _hasSearchOpen;
@ -209,16 +209,16 @@ public class TabItem : ViewModel
private GoToCommand _goToCommand;
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;
_images = new ObservableCollection<TabImage>();
}
public void SoftReset(AssetItem asset)
public void SoftReset(GameFile entry)
{
Asset = asset;
Entry = entry;
ParentExportType = string.Empty;
ScrollTrigger = null;
Application.Current.Dispatcher.Invoke(() =>
@ -306,7 +306,7 @@ public class TabItem : ViewModel
var fileName = image.ExportName + ext;
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('/'));
@ -327,9 +327,9 @@ public class TabItem : ViewModel
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,
UserSettings.Default.KeepDirectoryStructure ? Asset.Directory : "", fileName).Replace('\\', '/');
UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", fileName).Replace('\\', '/');
Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
@ -386,21 +386,19 @@ public class TabControlViewModel : ViewModel
}
public void AddTab() => AddTab("New Tab");
public void AddTab(string title) => AddTab(new AssetItem(title));
public void AddTab(AssetItem asset, string parentExportType = null)
public void AddTab(string title) => AddTab(new FakeGameFile(title));
public void AddTab(GameFile entry, string parentExportType = null)
{
if (!CanAddTabs) return;
var p = parentExportType ?? string.Empty;
if (SelectedTab?.Asset.FileName == "New Tab")
if (SelectedTab?.Entry.Name == "New Tab")
{
SelectedTab.Asset = asset;
SelectedTab.Entry = entry;
return;
}
if (!CanAddTabs) return;
Application.Current.Dispatcher.Invoke(() =>
{
_tabItems.Add(new TabItem(asset, p));
_tabItems.Add(new TabItem(entry, parentExportType ?? string.Empty));
SelectedTab = _tabItems.Last();
});
}

View File

@ -119,7 +119,7 @@ public partial class AvalonEditor
if (sender is not TextEditor avalonEditor || DataContext is not TabItem tabItem ||
avalonEditor.Document == null || string.IsNullOrEmpty(avalonEditor.Document.Text))
return;
avalonEditor.Document.FileName = tabItem.Asset.FullPath.SubstringBeforeLast('.');
avalonEditor.Document.FileName = tabItem.Entry.PathWithoutExtension;
if (!_savedCarets.ContainsKey(avalonEditor.Document.FileName))
_ignoreCaret = true;

View File

@ -24,7 +24,7 @@ public partial class PropertiesPopout
MyAvalonEditor.Document = new TextDocument
{
Text = contextViewModel.Document.Text,
FileName = contextViewModel.Asset.FullPath.SubstringBeforeLast('.')
FileName = contextViewModel.Entry.PathWithoutExtension
};
MyAvalonEditor.FontSize = contextViewModel.FontSize;
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>
<ControlTemplate>
<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}}" />
</Grid>
</ControlTemplate>
@ -596,19 +596,19 @@
<Image x:Name="ListImage" Source="/FModel;component/Resources/unknown_asset.png"
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>
<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" />
</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" />
</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" />
</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" />
</DataTrigger>
</DataTemplate.Triggers>
@ -651,7 +651,7 @@
<MouseBinding MouseAction="MiddleClick" Command="{Binding SelectedItem.TabCommand, ElementName=TabControlName}" CommandParameter="{Binding}" />
</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>
<MultiBinding Converter="{x:Static converters:TabSizeConverter.Instance}" ConverterParameter="6">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}" />
@ -856,7 +856,10 @@
</MenuItem.Icon>
</MenuItem>
<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>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@ -913,7 +916,7 @@
</Viewbox>
</MenuItem.Icon>
</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>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">

View File

@ -96,14 +96,14 @@
<GridViewColumn Width="900" Header="Path" adonisExtensions:GridViewSortExtension.PropertyName="Path">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Left" Text="{Binding FullPath}" />
<TextBlock HorizontalAlignment="Left" Text="{Binding Path}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="225" Header="Archive" adonisExtensions:GridViewSortExtension.PropertyName="Archive">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Left" Text="{Binding Archive}" />
<TextBlock HorizontalAlignment="Left" Text="{Binding Vfs.Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
@ -155,7 +155,14 @@
</MenuItem.Icon>
</MenuItem>
<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>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Assets_Export_Data" />

View File

@ -2,6 +2,7 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using CUE4Parse.FileProvider.Objects;
using FModel.Services;
using FModel.ViewModels;
@ -30,12 +31,12 @@ public partial class SearchView
private async void OnAssetDoubleClick(object sender, RoutedEventArgs e)
{
if (SearchListView.SelectedItem is not AssetItem assetItem)
if (SearchListView.SelectedItem is not GameFile entry)
return;
WindowState = WindowState.Minimized;
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;
MainWindow.YesWeCats.Activate();
@ -46,18 +47,18 @@ public partial class SearchView
do
{
await Task.Delay(100);
MainWindow.YesWeCats.AssetsListName.SelectedItem = assetItem;
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(assetItem);
MainWindow.YesWeCats.AssetsListName.SelectedItem = entry;
MainWindow.YesWeCats.AssetsListName.ScrollIntoView(entry);
} while (MainWindow.YesWeCats.AssetsListName.SelectedItem == null);
}
private async void OnAssetExtract(object sender, RoutedEventArgs e)
{
if (SearchListView.SelectedItem is not AssetItem assetItem)
if (SearchListView.SelectedItem is not GameFile entry)
return;
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();
}