added links to some exceptions + auto detect ue version (#657)
Some checks failed
FModel QA Builder / build (push) Has been cancelled

Co-authored-by: Asval <asval.contactme@gmail.com>
Co-authored-by: Krowe-moh <27891447+Krowe-moh@users.noreply.github.com>
This commit is contained in:
Masusder 2026-03-13 21:14:56 +01:00 committed by GitHub
parent c9542f1a91
commit 639f21e574
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 243 additions and 74 deletions

@ -1 +1 @@
Subproject commit 7ed9bd5adf3daada4bd7d884f3a42d163a64247a Subproject commit 6d7157a29b08d583aef9887a56d70f27a2ff36d5

View File

@ -40,6 +40,12 @@ public static class Constants
public const string _NO_PRESET_TRIGGER = "Hand Made"; public const string _NO_PRESET_TRIGGER = "Hand Made";
// Common issues
public const string MAPPING_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/418";
public const string AUDIO_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/658";
public const string RADA_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/422";
public const string VERSION_ISSUE_LINK = "https://github.com/4sval/FModel/discussions/425";
public static int PALETTE_LENGTH => COLOR_PALETTE.Length; public static int PALETTE_LENGTH => COLOR_PALETTE.Length;
public static readonly Vector3[] COLOR_PALETTE = public static readonly Vector3[] COLOR_PALETTE =
{ {

View File

@ -252,13 +252,6 @@ namespace FModel.Settings
set => SetProperty(ref _imageMergerMargin, value); set => SetProperty(ref _imageMergerMargin, value);
} }
private bool _canExportRawData;
public bool CanExportRawData
{
get => _canExportRawData;
set => SetProperty(ref _canExportRawData, value);
}
private bool _readScriptData; private bool _readScriptData;
public bool ReadScriptData public bool ReadScriptData
{ {

View File

@ -246,7 +246,7 @@ public class ApplicationViewModel : ViewModel
} }
else else
{ {
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true)); FLogger.Append(ELog.Error, () => FLogger.Text("Could not download vgmstream", Constants.WHITE, true));
} }
} }
} }

View File

@ -298,13 +298,23 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
Save(a, true); Save(a, true);
} }
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true);
});
if (_audioFiles.Count > 1) if (_audioFiles.Count > 1)
FLogger.Append(ELog.Information, () => FLogger.Text($"Successfully saved {_audioFiles.Count} audio files", Constants.WHITE, true)); {
var dir = new DirectoryInfo(Path.GetDirectoryName(_audioFiles.First().FilePath));
FLogger.Append(ELog.Information, () =>
{
FLogger.Text($"Successfully saved {_audioFiles.Count} audio files to ", Constants.WHITE);
FLogger.Link(dir.Name, dir.FullName, true);
});
}
else
{
FLogger.Append(ELog.Information, () =>
{
FLogger.Text("Successfully saved ", Constants.WHITE);
FLogger.Link(_audioFiles.First().FileName, _audioFiles.First().FilePath, true);
});
}
}); });
} }
@ -654,15 +664,24 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
} }
} }
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath); private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath, true);
public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath) public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string wavFilePath, bool updateUi = false)
{ {
wavFilePath = string.Empty; wavFilePath = string.Empty;
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe"); var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
if (!File.Exists(vgmFilePath)) if (!File.Exists(vgmFilePath))
{ {
vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-cli.exe"); vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-cli.exe");
if (!File.Exists(vgmFilePath)) return false; if (!File.Exists(vgmFilePath))
{
Log.Error("Failed to convert {InputFilePath}, vgmstream is missing", inputFilePath);
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Failed to convert audio because vgmstream is missing. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true);
});
return false;
}
} }
Directory.CreateDirectory(inputFilePath.SubstringBeforeLast("/")); Directory.CreateDirectory(inputFilePath.SubstringBeforeLast("/"));
@ -679,7 +698,22 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
vgmProcess?.WaitForExit(5000); vgmProcess?.WaitForExit(5000);
File.Delete(inputFilePath); File.Delete(inputFilePath);
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
var success = vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
if (!success)
{
Log.Error("Failed to convert {InputFilePath} to .wav format", inputFilePath);
if (updateUi)
{
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Failed to convert audio to .wav format. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.AUDIO_ISSUE_LINK, true);
});
}
}
return success;
} }
private bool TryDecode(string extension, out string rawFilePath) private bool TryDecode(string extension, out string rawFilePath)
@ -688,6 +722,12 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe"); var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe");
if (!File.Exists(decoderPath)) if (!File.Exists(decoderPath))
{ {
Log.Error("Failed to convert {FilePath}, rada decoder is missing", SelectedAudioFile.FilePath);
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Failed to convert audio because rada decoder is missing. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.RADA_ISSUE_LINK, true);
});
return false; return false;
} }

View File

@ -1179,7 +1179,7 @@ public class CUE4ParseViewModel : ViewModel
case USoundWave when isNone || saveAudio: case USoundWave when isNone || saveAudio:
{ {
// If UAkMediaAsset exists in the same package it should be used to handle the audio instead (because it contains actual audio name) // If UAkMediaAsset exists in the same package it should be used to handle the audio instead (because it contains actual audio name)
if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer is UAkMediaAsset) if (pointer.Object.Value is UAkMediaAssetData dataObj && dataObj.Outer.Object.Value is UAkMediaAsset)
return false; return false;
var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed;
@ -1196,7 +1196,7 @@ public class CUE4ParseViewModel : ViewModel
} }
case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset: case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset:
{ {
var audioName = akMediaAsset.MediaName; var audioName = akMediaAsset.MediaName ?? akMediaAsset.Name;
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true) if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
{ {
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed; var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
@ -1416,25 +1416,15 @@ public class CUE4ParseViewModel : ViewModel
writer.Flush(); writer.Flush();
} }
bool conversionSuccess = true;
if (UserSettings.Default.ConvertAudioOnBulkExport) if (UserSettings.Default.ConvertAudioOnBulkExport)
{ {
AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath); conversionSuccess = AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath);
if (!string.IsNullOrEmpty(wavFilePath)) if (conversionSuccess) savedAudioPath = wavFilePath;
{
savedAudioPath = wavFilePath;
}
else if (updateUi)
{
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Failed to convert audio to WAV format, aborting extraction.", Constants.WHITE, true);
});
return;
}
} }
Log.Information("Successfully saved {FilePath}", savedAudioPath); Log.Information("Successfully saved {FilePath}", savedAudioPath);
if (updateUi) if (updateUi && conversionSuccess)
{ {
FLogger.Append(ELog.Information, () => FLogger.Append(ELog.Information, () =>
{ {

View File

@ -356,6 +356,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
case "csv": case "csv":
AssetCategory = EAssetCategory.Data; AssetCategory = EAssetCategory.Data;
break; break;
case "stinfo":
case "ushaderbytecode": case "ushaderbytecode":
AssetCategory = EAssetCategory.ByteCode; AssetCategory = EAssetCategory.ByteCode;
break; break;

View File

@ -4,6 +4,8 @@ using Serilog;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -67,12 +69,103 @@ public class GameSelectorViewModel : ViewModel
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory); public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
public void AddUndetectedDir(string gameName, string gameDirectory) public void AddUndetectedDir(string gameName, string gameDirectory)
{ {
var setting = DirectorySettings.Default(gameName, gameDirectory, true); if (TryDetectUeVersion(gameDirectory, out var ueVersion, out var newGameDirectory))
{
// gameDirectory = newGameDirectory; // directory was changed to point to the correct paks folder
}
var setting = DirectorySettings.Default(gameName, gameDirectory, true, ueVersion);
UserSettings.Default.PerDirectory[gameDirectory] = setting; UserSettings.Default.PerDirectory[gameDirectory] = setting;
_detectedDirectories.Add(setting); _detectedDirectories.Add(setting);
SelectedDirectory = DetectedDirectories.Last(); SelectedDirectory = DetectedDirectories.Last();
} }
private bool TryDetectUeVersion(string gameDirectory, out EGame ueVersion, [MaybeNullWhen(false)] out string newGameDirectory)
{
var targetGameDir = gameDirectory;
if (!targetGameDir.EndsWith("Paks", StringComparison.OrdinalIgnoreCase))
{
var dirs = Directory.GetDirectories(targetGameDir, "Paks", SearchOption.AllDirectories);
var paksDir = dirs.Length == 1 ? dirs[0] : dirs.FirstOrDefault(x => !x.EndsWith("Engine\\Programs\\CrashReportClient\\Content\\Paks"));
if (!string.IsNullOrEmpty(paksDir))
{
Log.Warning("Selected directory \"{GameDirectory}\" does not end with \"Paks\". Looking in \"{PaksDir}\" instead.", targetGameDir, paksDir);
targetGameDir = paksDir;
}
if (Directory.GetFiles(gameDirectory, "*.exe") is { Length: 1 } exe && TryGetUeVersionFromExe(exe[0], out ueVersion))
{
// we checked the exe in the original directory, the BootstrapPackagedGame one
// but we still want c4p to use the paks folder as the game directory (if any), not the original one
newGameDirectory = targetGameDir;
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe[0]);
return true;
}
}
// past this point, we assume targetGameDir is the correct Paks folder
newGameDirectory = targetGameDir;
var projectDir = Path.Combine(targetGameDir, "..", "..");
var projectBinariesDir = Path.Combine(projectDir, "Binaries", "Win64");
if (Directory.Exists(projectBinariesDir))
{
if (Directory.GetFiles(projectBinariesDir, "*-Win64-Shipping.exe") is { Length: > 0 } shipping)
{
foreach (var exe in shipping)
{
if (TryGetUeVersionFromExe(exe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe);
return true;
}
}
}
else if (Directory.GetFiles(projectBinariesDir, "*.exe") is { Length: < 3 } exes)
{
foreach (var exe in exes)
{
if (TryGetUeVersionFromExe(exe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, exe);
return true;
}
}
}
}
var crashReportClientExe = Path.Combine(projectDir, "..", "Engine", "Binaries", "Win64", "CrashReportClient.exe");
if (File.Exists(crashReportClientExe) && TryGetUeVersionFromExe(crashReportClientExe, out ueVersion))
{
Log.Information("Detected UE version {UeVersion} from \"{Exe}\"", ueVersion, crashReportClientExe);
return true;
}
ueVersion = EGame.GAME_UE4_LATEST;
Log.Warning("Failed to detect UE version for \"{GameDirectory}\".", gameDirectory);
return false;
}
private bool TryGetUeVersionFromExe(string exePath, out EGame ueVersion)
{
ueVersion = EGame.GAME_UE4_LATEST;
try
{
var info = FileVersionInfo.GetVersionInfo(exePath);
ueVersion = info.FileMajorPart switch
{
4 => (EGame) Math.Min((uint)(GameUtils.GameUe4Base + (info.FileMinorPart << 16)), (uint) EGame.GAME_UE4_LATEST),
5 => (EGame) Math.Min((uint)(GameUtils.GameUe5Base + (info.FileMinorPart << 16)), (uint) EGame.GAME_UE5_LATEST),
_ => throw new InvalidOperationException($"Unsupported UE major version {info.FileMajorPart} detected from {exePath}")
};
return true;
}
catch
{
return false;
}
}
public void DeleteSelectedGame() public void DeleteSelectedGame()
{ {
UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using CUE4Parse.UE4.Exceptions;
using FModel.Framework; using FModel.Framework;
using FModel.Services; using FModel.Services;
using FModel.Views.Resources.Controls; using FModel.Views.Resources.Controls;
@ -100,7 +101,26 @@ public class ThreadWorkerViewModel : ViewModel
CurrentCancellationTokenSource = null; // kill token CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e); Log.Error("{Exception}", e);
FLogger.Append(e); switch (e)
{
case MappingException:
FLogger.Append(ELog.Error, () =>
{
FLogger.Text("Package has unversioned properties but mapping file (.usmap) is missing, can't serialize. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.MAPPING_ISSUE_LINK, true);
});
break;
case VersionException v: // Error might be unrelated to version, but it's usually the case
FLogger.Append(ELog.Error, () =>
{
FLogger.Text(v.Message[..^1] + ", can't serialize. Make sure the correct UE version is configured. See: ", Constants.WHITE);
FLogger.Link("→ link ←", Constants.VERSION_ISSUE_LINK, true);
});
break;
default:
FLogger.Append(e);
break;
}
return; return;
} }
} }

View File

@ -110,8 +110,7 @@
</MenuItem.Style> </MenuItem.Style>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{Binding RightClickMenuCommand}" <MenuItem Command="{Binding RightClickMenuCommand}">
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.Header> <MenuItem.Header>
<TextBlock <TextBlock
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension, Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,

View File

@ -1,14 +1,12 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary"> x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary">
<ContextMenu x:Key="FolderContextMenu" x:Shared="False" <ContextMenu x:Key="FolderContextMenu" x:Shared="False"
Opened="FolderContextMenu_OnOpened"> Opened="FolderContextMenu_OnOpened">
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" <MenuItem Header="Export Folder's Packages Raw Data (.uasset)"
Command="{Binding RightClickMenuCommand}" Command="{Binding RightClickMenuCommand}">
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.CommandParameter> <MenuItem.CommandParameter>
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}"> <MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
<Binding Source="Folders_Export_Data" /> <Binding Source="Folders_Export_Data" />

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -126,13 +126,40 @@ public class FLogger : ITextFormatter
{ {
NavigateUri = new Uri(url), NavigateUri = new Uri(url),
OverridesDefaultStyle = true, OverridesDefaultStyle = true,
Style = new Style(typeof(Hyperlink)) { Setters = Style = new Style(typeof(Hyperlink))
{ {
new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand), Setters =
new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline), {
new Setter(TextElement.ForegroundProperty, Brushes.Cornsilk) new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand),
}} new Setter(TextElement.ForegroundProperty, Brushes.Goldenrod),
}.Click += (sender, _) => Process.Start("explorer.exe", $"/select, \"{((Hyperlink)sender).NavigateUri.AbsoluteUri}\""); new Setter(TextElement.FontWeightProperty, FontWeights.Bold)
},
Triggers =
{
new Trigger
{
Property = UIElement.IsMouseOverProperty,
Value = true,
Setters =
{
new Setter(TextElement.ForegroundProperty, Brushes.Gold),
new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline)
}
}
}
}
}.Click += (sender, _) =>
{
var uri = ((Hyperlink) sender).NavigateUri;
if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)
{
Process.Start(new ProcessStartInfo(uri.AbsoluteUri) { UseShellExecute = true });
}
else
{
Process.Start("explorer.exe", $"/select, \"{uri.AbsoluteUri}\"");
}
};
} }
finally finally
{ {

View File

@ -926,8 +926,7 @@
</MenuItem.IsEnabled> </MenuItem.IsEnabled>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data" <MenuItem Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data">
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.Header> <MenuItem.Header>
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Export Raw Data', StringFormat='Export Raw Data (.{0})'}" /> <TextBlock Text="{Binding Entry.Extension, FallbackValue='Export Raw Data', StringFormat='Export Raw Data (.{0})'}" />
</MenuItem.Header> </MenuItem.Header>

View File

@ -1,7 +1,6 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.SearchView" <adonisControls:AdonisWindow x:Class="FModel.Views.SearchView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:settings="clr-namespace:FModel.Settings"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
@ -210,8 +209,7 @@
</MenuItem.IsEnabled> </MenuItem.IsEnabled>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}" <MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.Header> <MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data', <TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" /> StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
@ -556,8 +554,7 @@
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<Separator /> <Separator />
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}" <MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
<MenuItem.Header> <MenuItem.Header>
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data', <TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" /> StringFormat='Export Raw Data (.{0})', RelativeSource={RelativeSource AncestorType=ContextMenu}}" />

View File

@ -44,7 +44,6 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@ -223,51 +222,46 @@
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" /> <Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
</Grid> </Grid>
<TextBlock Grid.Row="16" Grid.Column="0" Text="Allow Raw Data Export (.uasset)" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="16" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding CanExportRawData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10" IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="18" Grid.Column="0" Text="Serialize Inlined Shader Maps" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Inlined Shader Maps" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10" IsChecked="{Binding ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="19" Grid.Column="0" Text="Decompile Blueprint to Pseudo C++" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Adds a right click option to decompile UClass packages into a pseudo C++ friendly format" /> <TextBlock Grid.Row="18" Grid.Column="0" Text="Decompile Blueprint to Pseudo C++" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Adds a right click option to decompile UClass packages into a pseudo C++ friendly format" />
<CheckBox Grid.Row="19" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" <CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10" IsChecked="{Binding ShowDecompileOption, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="20" <TextBlock Grid.Row="19"
Grid.Column="0" Grid.Column="0"
Text="Convert Audio During Export (.wav)" Text="Convert Audio During Export (.wav)"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0 0 0 5" /> Margin="0 0 0 5" />
<CheckBox Grid.Row="20" <CheckBox Grid.Row="19"
Grid.Column="2" Grid.Column="2"
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding ConvertAudioOnBulkExport, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" IsChecked="{Binding ConvertAudioOnBulkExport, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Margin="0 5 0 10" Margin="0 5 0 10"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" /> Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
<TextBlock Grid.Row="21" Grid.Column="0" Text="Max Wwise Bank (.BNK) Prefetch" VerticalAlignment="Center" Margin="0 0 0 5" /> <TextBlock Grid.Row="20" Grid.Column="0" Text="Max Wwise Bank (.BNK) Prefetch" VerticalAlignment="Center" Margin="0 0 0 5" />
<Slider Grid.Row="21" Grid.Column="2" Grid.ColumnSpan="5" TickPlacement="None" Minimum="0" Maximum="2048" Ticks="0,8,32,128,256,512,1024,2048" <Slider Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="5" TickPlacement="None" Minimum="0" Maximum="2048" Ticks="0,8,32,128,256,512,1024,2048"
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5" AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
Value="{Binding WwiseMaxBnkPrefetch, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/> Value="{Binding WwiseMaxBnkPrefetch, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
<TextBlock Grid.Row="22" <TextBlock Grid.Row="21"
Grid.Column="0" Grid.Column="0"
Text="CRIWARE Decryption Key" Text="CRIWARE Decryption Key"
VerticalAlignment="Center" VerticalAlignment="Center"
Margin="0 0 0 10" /> Margin="0 0 0 10" />
<TextBox x:Name="CriwareKeyBox" <TextBox x:Name="CriwareKeyBox"
Grid.Row="22" Grid.Row="21"
Grid.Column="2" Grid.Column="2"
Grid.ColumnSpan="5" Grid.ColumnSpan="5"
Margin="0 5 0 10" Margin="0 5 0 10"
@ -398,6 +392,8 @@
<ContentControl.Style> <ContentControl.Style>
<Style TargetType="{x:Type ContentControl}"> <Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" /> <Setter Property="Visibility" Value="Collapsed" />
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
<Style.Triggers> <Style.Triggers>
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}"> <DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
<Setter Property="ContentTemplate"> <Setter Property="ContentTemplate">

View File

@ -1,9 +1,11 @@
using System; using System;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Documents;
using FModel.Services; using FModel.Services;
using FModel.Settings; using FModel.Settings;
using FModel.ViewModels; using FModel.ViewModels;
@ -241,4 +243,12 @@ public partial class SettingsView
out value out value
); );
} }
private void OnHyperlinkClick(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is not Hyperlink hyperlink)
return;
Process.Start(new ProcessStartInfo(hyperlink.NavigateUri.AbsoluteUri) { UseShellExecute = true });
}
} }

View File

@ -93,7 +93,7 @@ public abstract class UModel : IRenderableModel
{ {
_export = export; _export = export;
Path = _export.GetPathName(); Path = _export.GetPathName();
Name = Path.SubstringAfterLast('/').SubstringBefore('.'); Name = export.Name;
Type = export.ExportType; Type = export.ExportType;
UvCount = 1; UvCount = 1;