mirror of
https://github.com/4sval/FModel.git
synced 2026-05-07 13:31:58 -05:00
Merge branch '4sval:dev' into dev
This commit is contained in:
commit
ee0c9d033b
|
|
@ -1 +1 @@
|
|||
Subproject commit 4fb7435973fc57bfb78577c971d776f7577440cf
|
||||
Subproject commit 267d479e721b424e2e913ec195c596b660aa1d09
|
||||
|
|
@ -40,6 +40,12 @@ public static class Constants
|
|||
|
||||
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 readonly Vector3[] COLOR_PALETTE =
|
||||
{
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public class BaseIcon : UCreator
|
|||
|
||||
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||
{
|
||||
GetRarity(dataList);
|
||||
GetSeries(dataList);
|
||||
Preview = Utils.GetBitmap(dataList);
|
||||
}
|
||||
|
|
@ -139,6 +140,12 @@ public class BaseIcon : UCreator
|
|||
GetSeries(export);
|
||||
}
|
||||
|
||||
private void GetRarity(FInstancedStruct[] s)
|
||||
{
|
||||
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out EFortRarity _, "Rarity") == true) is { } dl)
|
||||
GetRarity(dl.NonConstStruct.Get<EFortRarity>("Rarity"));
|
||||
}
|
||||
|
||||
private void GetSeries(FInstancedStruct[] s)
|
||||
{
|
||||
if (s.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FPackageIndex _, "Series") == true) is { } dl)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public enum SettingsOut
|
|||
public enum EStatusKind
|
||||
{
|
||||
Ready, // ready
|
||||
Configuring, // waiting for user input
|
||||
Loading, // doing stuff
|
||||
Stopping, // trying to stop
|
||||
Stopped, // stopped
|
||||
|
|
@ -107,6 +108,7 @@ public enum EBulkType
|
|||
Animations = 1 << 4,
|
||||
Audio = 1 << 5,
|
||||
Code = 1 << 6,
|
||||
Raw = 1 << 7,
|
||||
}
|
||||
|
||||
public enum EAssetCategory : uint
|
||||
|
|
@ -158,4 +160,5 @@ public enum EAssetCategory : uint
|
|||
Particle = AssetCategoryExtensions.CategoryBase + (9 << 16),
|
||||
GameSpecific = AssetCategoryExtensions.CategoryBase + (10 << 16),
|
||||
Borderlands = GameSpecific + 1,
|
||||
Aion2 = GameSpecific + 2,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
|
|
@ -62,7 +63,7 @@ public static class Helper
|
|||
GetOpenedWindow<T>(windowName).Close();
|
||||
}
|
||||
|
||||
private static bool IsWindowOpen<T>(string name = "") where T : Window
|
||||
public static bool IsWindowOpen<T>(string name = "") where T : Window
|
||||
{
|
||||
return string.IsNullOrEmpty(name)
|
||||
? Application.Current.Windows.OfType<T>().Any()
|
||||
|
|
@ -111,4 +112,24 @@ public static class Helper
|
|||
const float ratio = 180f / MathF.PI;
|
||||
return radians * ratio;
|
||||
}
|
||||
|
||||
public static string GetGameName(string path)
|
||||
{
|
||||
// install_folder/
|
||||
// ├─ Engine/
|
||||
// ├─ GameName/
|
||||
// │ ├─ Binaries/
|
||||
// │ ├─ Content/
|
||||
// │ │ ├─ Paks/
|
||||
// our goal is to get the GameName folder
|
||||
var dir = new DirectoryInfo(path);
|
||||
if (dir.Name.Equals("Paks", StringComparison.InvariantCulture) && dir.Parent is { Parent: not null } &&
|
||||
dir.Parent.Name.Equals("Content", StringComparison.InvariantCulture) &&
|
||||
dir.Parent.Parent.GetDirectories().Any(x => x.Name == "Binaries"))
|
||||
{
|
||||
return dir.Parent.Parent.Name;
|
||||
}
|
||||
|
||||
return dir.Name;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" Closing="OnClosing" Loaded="OnLoaded" PreviewKeyDown="OnWindowKeyDown"
|
||||
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.95'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.90'}">
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.90'}"
|
||||
AllowDrop="True">
|
||||
<Window.TaskbarItemInfo>
|
||||
<TaskbarItemInfo ProgressValue="1.0">
|
||||
<TaskbarItemInfo.ProgressState>
|
||||
|
|
@ -628,6 +629,10 @@
|
|||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Configuring}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Stopped}">
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
|
|
@ -709,5 +714,7 @@
|
|||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
<controls:DropOverlay Grid.RowSpan="99"
|
||||
Panel.ZIndex="1000" />
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
|
|
|||
|
|
@ -252,13 +252,6 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _imageMergerMargin, value);
|
||||
}
|
||||
|
||||
private bool _canExportRawData;
|
||||
public bool CanExportRawData
|
||||
{
|
||||
get => _canExportRawData;
|
||||
set => SetProperty(ref _canExportRawData, value);
|
||||
}
|
||||
|
||||
private bool _readScriptData;
|
||||
public bool ReadScriptData
|
||||
{
|
||||
|
|
@ -461,13 +454,6 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _cameraMode, value);
|
||||
}
|
||||
|
||||
private int _wwiseMaxBnkPrefetch;
|
||||
public int WwiseMaxBnkPrefetch
|
||||
{
|
||||
get => _wwiseMaxBnkPrefetch;
|
||||
set => SetProperty(ref _wwiseMaxBnkPrefetch, value);
|
||||
}
|
||||
|
||||
private int _previewMaxTextureSize = 1024;
|
||||
public int PreviewMaxTextureSize
|
||||
{
|
||||
|
|
|
|||
|
|
@ -141,8 +141,10 @@ public class ApplicationViewModel : ViewModel
|
|||
if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
|
||||
return currentDir;
|
||||
|
||||
Status.SetStatus(EStatusKind.Configuring);
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
|
||||
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
|
||||
Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value) return null;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
|
||||
|
|
@ -155,6 +157,35 @@ public class ApplicationViewModel : ViewModel
|
|||
return null;
|
||||
}
|
||||
|
||||
public DirectorySettings AddGameDirectory(string directory)
|
||||
{
|
||||
if (Status.Kind is EStatusKind.Configuring)
|
||||
{
|
||||
var directorySelector = Helper.GetWindow<DirectorySelector>("Directory Selector", null);
|
||||
directorySelector.AddManualGame(directory);
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
Status.SetStatus(EStatusKind.Configuring);
|
||||
var gameLauncherViewModel = new GameSelectorViewModel(UserSettings.Default.GameDirectory);
|
||||
var directorySelector = new DirectorySelector(gameLauncherViewModel);
|
||||
directorySelector.AddManualGame(directory);
|
||||
var result = directorySelector.ShowDialog();
|
||||
Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return null;
|
||||
|
||||
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
|
||||
if (UserSettings.Default.CurrentDir.Equals(gameLauncherViewModel.SelectedDirectory))
|
||||
return gameLauncherViewModel.SelectedDirectory;
|
||||
|
||||
UserSettings.Default.CurrentDir = gameLauncherViewModel.SelectedDirectory;
|
||||
RestartWithWarning();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
{
|
||||
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
|
|
@ -246,7 +277,7 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -298,13 +298,23 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
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)
|
||||
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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -330,12 +340,8 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
}
|
||||
|
||||
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write))
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(fileToSave.Data);
|
||||
writer.Flush();
|
||||
}
|
||||
using var stream = new FileStream(path, FileMode.Create, FileAccess.Write);
|
||||
stream.Write(fileToSave.Data);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
|
|
@ -654,32 +660,42 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private bool TryConvert(out string wavFilePath) => TryConvert(SelectedAudioFile.FilePath, SelectedAudioFile.Data, out wavFilePath);
|
||||
public static bool TryConvert(string inputFilePath, byte[] inputFileData, out string 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, bool updateUi = false)
|
||||
{
|
||||
wavFilePath = string.Empty;
|
||||
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
|
||||
if (!File.Exists(vgmFilePath))
|
||||
{
|
||||
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("/"));
|
||||
File.WriteAllBytes(inputFilePath, inputFileData);
|
||||
var success = TryConvertToWAV(inputFilePath, inputFileData, vgmFilePath, true, out var tempWavFilePath);
|
||||
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
var vgmProcess = Process.Start(new ProcessStartInfo
|
||||
if (!success)
|
||||
{
|
||||
FileName = vgmFilePath,
|
||||
Arguments = $"-o \"{wavFilePath}\" \"{inputFilePath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
vgmProcess?.WaitForExit(5000);
|
||||
Log.Error("Failed to convert {InputFilePath} to .wav format", Path.GetFileName(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File.Delete(inputFilePath);
|
||||
return vgmProcess?.ExitCode == 0 && File.Exists(wavFilePath);
|
||||
return success;
|
||||
}
|
||||
|
||||
private bool TryDecode(string extension, out string rawFilePath)
|
||||
|
|
@ -688,23 +704,46 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
var decoderPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{extension}dec.exe");
|
||||
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;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/"));
|
||||
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
||||
return TryConvertToWAV(SelectedAudioFile.FilePath, SelectedAudioFile.Data, decoderPath, false, out rawFilePath);
|
||||
}
|
||||
|
||||
rawFilePath = Path.ChangeExtension(SelectedAudioFile.FilePath, ".wav");
|
||||
var decoderProcess = Process.Start(new ProcessStartInfo
|
||||
private static bool TryConvertToWAV(string inputFilePath, byte[] inputFileData, string converterPath, bool usevgmstream, out string wavFilePath)
|
||||
{
|
||||
wavFilePath = Path.ChangeExtension(inputFilePath, ".wav");
|
||||
var directory = Path.GetDirectoryName(inputFilePath);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var tempfile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + Path.GetExtension(inputFilePath));
|
||||
File.WriteAllBytes(tempfile, inputFileData);
|
||||
|
||||
var tempWavFilePath = Path.ChangeExtension(tempfile, ".wav");
|
||||
|
||||
var process = Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = decoderPath,
|
||||
Arguments = $"-i \"{SelectedAudioFile.FilePath}\" -o \"{rawFilePath}\"",
|
||||
FileName = converterPath,
|
||||
Arguments = usevgmstream ? $"-o \"{tempWavFilePath}\" \"{tempfile}\"" : $"-i \"{tempfile}\" -o \"{tempWavFilePath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
});
|
||||
decoderProcess?.WaitForExit(5000);
|
||||
process?.WaitForExit(5000);
|
||||
|
||||
File.Delete(SelectedAudioFile.FilePath);
|
||||
return decoderProcess?.ExitCode == 0 && File.Exists(rawFilePath);
|
||||
File.Delete(tempfile);
|
||||
|
||||
var success = process?.ExitCode == 0 && File.Exists(tempWavFilePath);
|
||||
if (success)
|
||||
{
|
||||
File.Move(tempWavFilePath, wavFilePath, true);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public CriWareProvider CriWareProvider => _criWareProviderLazy?.Value;
|
||||
public ConcurrentBag<string> UnknownExtensions = [];
|
||||
|
||||
public int ExportedCount;
|
||||
public int FailedExportCount;
|
||||
|
||||
public CUE4ParseViewModel()
|
||||
{
|
||||
var currentDir = UserSettings.Default.CurrentDir;
|
||||
|
|
@ -322,7 +325,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
Provider.Initialize();
|
||||
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory, UserSettings.Default.WwiseMaxBnkPrefetch));
|
||||
_wwiseProviderLazy = new Lazy<WwiseProvider>(() => new WwiseProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
_fmodProviderLazy = new Lazy<FModProvider>(() => new FModProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
_criWareProviderLazy = new Lazy<CriWareProvider>(() => new CriWareProvider(Provider, UserSettings.Default.GameDirectory));
|
||||
Log.Information($"{Provider.Versions.Game} ({Provider.Versions.Platform}) | Archives: x{Provider.UnloadedVfs.Count} | AES: x{Provider.RequiredKeys.Count} | Loose Files: x{Provider.Files.Count}");
|
||||
|
|
@ -491,7 +494,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var ioStoreOnDemandPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud", inst[0].Value.SubstringAfterLast("/").SubstringBefore("\""));
|
||||
if (!File.Exists(ioStoreOnDemandPath)) return;
|
||||
|
||||
await Provider.RegisterVfsAsync(new IoChunkToc(ioStoreOnDemandPath));
|
||||
await Provider.RegisterVfsAsync(new IoChunkToc(ioStoreOnDemandPath, Provider.Versions));
|
||||
var onDemandCount = await Provider.MountAsync();
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"{onDemandCount} on-demand archive{(onDemandCount > 1 ? "s" : "")} streamed via epicgames.com", Constants.WHITE, true));
|
||||
|
|
@ -598,6 +601,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
|
||||
}
|
||||
|
||||
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder, EBulkType bulk)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs, bulk));
|
||||
|
||||
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset, TabControl.HasNoTabs));
|
||||
|
||||
|
|
@ -802,13 +808,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "pck":
|
||||
{
|
||||
var archive = entry.CreateReader();
|
||||
var wwise = new WwiseReader(archive);
|
||||
var wwise = new WwiseReader(archive, new WwiseGameFileSource(entry));
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
|
||||
|
||||
var medias = WwiseProvider.ExtractBankSounds(wwise);
|
||||
foreach (var media in medias)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, media.OutputPath, media.Extension, media.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, media.OutputPath, media.Extension, media.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -964,7 +970,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
else if (entry.NameWithoutExtension.Equals("L10NString"))
|
||||
{
|
||||
var l10nData = new FAion2L10NFile(entry);
|
||||
var l10nData = new FAion2L10NFile(entry, Provider);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(l10nData, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
else
|
||||
|
|
@ -1115,7 +1121,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case UExternalSource when (isNone || saveAudio) && pointer.Object.Value is UExternalSource externalSource:
|
||||
{
|
||||
var audioName = Path.GetFileNameWithoutExtension(externalSource.ExternalSourcePath);
|
||||
SaveAndPlaySound(cancellationToken, audioName, "wem", externalSource.Data?.WemFile ?? [], saveAudio, updateUi);
|
||||
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
|
||||
SaveAndPlaySound(cancellationToken, outputPath, "wem", externalSource.Data?.WemFile?.GetData() ?? [], saveAudio, updateUi);
|
||||
return false;
|
||||
}
|
||||
case UAkAudioBank when (isNone || saveAudio) && pointer.Object.Value is UAkAudioBank soundBank:
|
||||
|
|
@ -1123,7 +1130,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var extractedSounds = WwiseProvider.ExtractBankSounds(soundBank);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1132,7 +1139,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var extractedSounds = WwiseProvider.ExtractAudioEventSounds(audioEvent);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1179,7 +1186,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
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 (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;
|
||||
|
||||
var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed;
|
||||
|
|
@ -1196,13 +1203,14 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
case UAkMediaAsset when (isNone || saveAudio) && pointer.Object.Value is UAkMediaAsset akMediaAsset:
|
||||
{
|
||||
var audioName = akMediaAsset.MediaName;
|
||||
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
|
||||
var audioName = akMediaAsset.MediaName ?? akMediaAsset.Name;
|
||||
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
|
||||
if (akMediaAsset.CurrentMediaAssetData?.ResolvedObject?.Object?.Value is UAkMediaAssetData akMediaAssetData)
|
||||
{
|
||||
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
|
||||
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
|
||||
|
||||
SaveAndPlaySound(cancellationToken, audioName, audioFormat, data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1211,14 +1219,15 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var shouldDecompress = UserSettings.Default.CompressedAudioMode is ECompressedAudio.PlayDecompressed;
|
||||
foreach (var mediaIndex in akAudioEventData.MediaList)
|
||||
{
|
||||
if (mediaIndex.TryLoad<UAkMediaAsset>(out var akMediaAsset))
|
||||
if (mediaIndex.ResolvedObject?.Object?.Value is UAkMediaAsset akMediaAsset)
|
||||
{
|
||||
if (akMediaAsset.CurrentMediaAssetData?.TryLoad<UAkMediaAssetData>(out var akMediaAssetData) is true)
|
||||
if (akMediaAsset.CurrentMediaAssetData?.ResolvedObject?.Object?.Value is UAkMediaAssetData akMediaAssetData)
|
||||
{
|
||||
var audioName = akMediaAsset.MediaName ?? $"{akAudioEventData.Outer.Name} ({akMediaAsset.ID})";
|
||||
var outputPath = Path.Combine(TabControl.SelectedTab.Entry.PathWithoutExtension.Replace('\\', '/').SubstringBeforeLast('/'), audioName);
|
||||
akMediaAssetData.Decode(shouldDecompress, out var audioFormat, out var data);
|
||||
|
||||
SaveAndPlaySound(cancellationToken, audioName, audioFormat, data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, outputPath, audioFormat, data, saveAudio, updateUi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1230,7 +1239,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var extractedSounds = WwiseProvider.ExtractDialogBorderlands3(dialogPerformanceData);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1240,12 +1249,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.Versions.Game is not EGame.GAME_Borderlands4)
|
||||
return false;
|
||||
|
||||
var ownerDirectory = WwiseProvider.GetOwnerDirectory(faceFXAnimSet);
|
||||
foreach (var faceFXAnimData in faceFXAnimSet.FaceFXAnimDataList)
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(faceFXAnimData.ID.Name, false);
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(ownerDirectory, faceFXAnimData.ID.Name, false);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1254,12 +1264,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
// Borderlands 4
|
||||
case UGbxGraphAsset when (isNone || saveAudio) && pointer.Object.Value is UGbxGraphAsset gbxGraphAsset:
|
||||
{
|
||||
var ownerDirectory = WwiseProvider.GetOwnerDirectory(gbxGraphAsset);
|
||||
foreach (var (eventName, useSoundTag) in GbxAudioUtil.GetAndClearEvents())
|
||||
{
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(eventName, useSoundTag);
|
||||
var extractedSounds = WwiseProvider.ExtractAudioEventBorderlands4(ownerDirectory, eventName, useSoundTag);
|
||||
foreach (var sound in extractedSounds)
|
||||
{
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data, saveAudio, updateUi);
|
||||
SaveAndPlaySound(cancellationToken, sound.OutputPath, sound.Extension, sound.Data?.GetData() ?? [], saveAudio, updateUi);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1399,42 +1410,38 @@ public class CUE4ParseViewModel : ViewModel
|
|||
TabControl.SelectedTab.SetDocumentText(cpp, false, false);
|
||||
}
|
||||
|
||||
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool isBulk, bool updateUi)
|
||||
private void SaveAndPlaySound(CancellationToken cancellationToken, string fullPath, string ext, byte[] data, bool saveAudio, bool updateUi)
|
||||
{
|
||||
if (fullPath.StartsWith('/')) fullPath = fullPath[1..];
|
||||
var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}";
|
||||
|
||||
if (isBulk)
|
||||
if (saveAudio)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Directory.CreateDirectory(savedAudioPath.SubstringBeforeLast('/'));
|
||||
using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
writer.Write(data);
|
||||
writer.Flush();
|
||||
}
|
||||
var directory = Path.GetDirectoryName(savedAudioPath);
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
bool conversionSuccess = true;
|
||||
if (UserSettings.Default.ConvertAudioOnBulkExport)
|
||||
{
|
||||
AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath);
|
||||
if (!string.IsNullOrEmpty(wavFilePath))
|
||||
{
|
||||
if (AudioPlayerViewModel.TryConvert(savedAudioPath, data, out string wavFilePath))
|
||||
savedAudioPath = wavFilePath;
|
||||
}
|
||||
else if (updateUi)
|
||||
else
|
||||
{
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text("Failed to convert audio to WAV format, aborting extraction.", Constants.WHITE, true);
|
||||
});
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using var stream = new FileStream(savedAudioPath, FileMode.Create, FileAccess.Write);
|
||||
stream.Write(data);
|
||||
}
|
||||
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("Successfully saved {FilePath}", savedAudioPath);
|
||||
if (updateUi)
|
||||
if (updateUi && conversionSuccess)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
|
|
@ -1446,6 +1453,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
return;
|
||||
}
|
||||
|
||||
if (!updateUi)
|
||||
return;
|
||||
|
||||
// TODO
|
||||
// since we are currently in a thread, the audio player's lifetime (memory-wise) will keep the current thread up and running until fmodel itself closes
|
||||
// the solution would be to kill the current thread at this line and then open the audio player without "Application.Current.Dispatcher.Invoke"
|
||||
|
|
@ -1463,6 +1473,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
|
||||
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
|
||||
{
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("Successfully saved {FilePath}", savedFilePath);
|
||||
if (updateUi)
|
||||
{
|
||||
|
|
@ -1475,6 +1486,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
Log.Error("{FileName} could not be saved", export.Name);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
|
||||
}
|
||||
|
|
@ -1496,6 +1508,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
});
|
||||
|
||||
Interlocked.Increment(ref ExportedCount);
|
||||
Log.Information("{FileName} successfully exported", entry.Name);
|
||||
if (updateUi)
|
||||
{
|
||||
|
|
@ -1508,6 +1521,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref FailedExportCount);
|
||||
Log.Error("{FileName} could not be exported", entry.Name);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{entry.Name}'", Constants.WHITE, true));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -13,8 +17,21 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
|
||||
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
|
||||
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel) { }
|
||||
|
||||
private enum EAction
|
||||
{
|
||||
Show,
|
||||
Export,
|
||||
}
|
||||
|
||||
private enum EShowAssetType
|
||||
{
|
||||
None,
|
||||
JSON,
|
||||
Metadata,
|
||||
References,
|
||||
Decompile,
|
||||
}
|
||||
|
||||
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
|
||||
|
|
@ -26,189 +43,149 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
if (param.Length == 0) return;
|
||||
|
||||
var folders = param.OfType<TreeItem>().ToArray();
|
||||
var assets = param.SelectMany(item => item switch
|
||||
{
|
||||
GameFile gf => new[] { gf }, // search view passes GameFile directly
|
||||
GameFileViewModel gvm => new[] { gvm.Asset },
|
||||
_ => []
|
||||
}).ToArray();
|
||||
var assets = param
|
||||
.Select(static item => item switch
|
||||
{
|
||||
GameFile gf => gf, // Search view passes GameFile directly
|
||||
GameFileViewModel gvm => gvm.Asset,
|
||||
_ => null
|
||||
})
|
||||
.Where(static gf => gf is not null).ToArray();
|
||||
|
||||
if (folders.Length == 0 && assets.Length == 0)
|
||||
return;
|
||||
|
||||
var updateUi = assets.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||
var assetsGroups = assets.GroupBy(static gf => gf.Directory);
|
||||
var (action, showtype, bulktype) = trigger switch
|
||||
{
|
||||
"Assets_Extract_New_Tab" => (EAction.Show, EShowAssetType.JSON, EBulkType.None),
|
||||
"Assets_Show_Metadata" => (EAction.Show, EShowAssetType.Metadata, EBulkType.None),
|
||||
"Assets_Show_References" => (EAction.Show, EShowAssetType.References, EBulkType.None),
|
||||
"Assets_Decompile" => (EAction.Show, EShowAssetType.Decompile, EBulkType.Code),
|
||||
|
||||
"Save_Data" => (EAction.Export, EShowAssetType.None, EBulkType.Raw),
|
||||
"Save_Properties" => (EAction.Export, EShowAssetType.None, EBulkType.Properties),
|
||||
"Save_Textures" => (EAction.Export, EShowAssetType.None, EBulkType.Textures),
|
||||
"Save_Models" => (EAction.Export, EShowAssetType.None, EBulkType.Meshes),
|
||||
"Save_Animations" => (EAction.Export, EShowAssetType.None, EBulkType.Animations),
|
||||
"Save_Audio" => (EAction.Export, EShowAssetType.None, EBulkType.Audio),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException("Unsupported asset action."),
|
||||
};
|
||||
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.ExportedCount, 0);
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.FailedExportCount, 0);
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
if (action is EAction.Show)
|
||||
{
|
||||
#region Asset Commands
|
||||
case "Assets_Extract_New_Tab":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true);
|
||||
}
|
||||
break;
|
||||
case "Assets_Show_Metadata":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ShowMetadata(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Show_References":
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.FindReferences(assets.FirstOrDefault());
|
||||
}
|
||||
break;
|
||||
case "Assets_Decompile":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Decompile(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Export_Data":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(entry);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Properties":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Properties | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Textures":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Textures | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Models":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Meshes | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Animations":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Animations | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Audio":
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, EBulkType.Audio | updateUi);
|
||||
}
|
||||
break;
|
||||
#endregion
|
||||
if (showtype is EShowAssetType.References)
|
||||
assets = [assets.FirstOrDefault()];
|
||||
|
||||
#region Folder Commands
|
||||
case "Folders_Export_Data":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder);
|
||||
Action<GameFile> entryAction = showtype switch
|
||||
{
|
||||
EShowAssetType.JSON => entry => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, true),
|
||||
EShowAssetType.Metadata => entry => contextViewModel.CUE4Parse.ShowMetadata(entry),
|
||||
EShowAssetType.Decompile => entry => contextViewModel.CUE4Parse.Decompile(entry),
|
||||
EShowAssetType.References => entry => contextViewModel.CUE4Parse.FindReferences(entry),
|
||||
_ => throw new ArgumentOutOfRangeException("Unsupported asset action type."),
|
||||
};
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Properties":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.SaveFolder(cancellationToken, folder);
|
||||
foreach (var entry in assets)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
entryAction(entry);
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Textures":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.TextureFolder(cancellationToken, folder);
|
||||
return;
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved textures from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Models":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ModelFolder(cancellationToken, folder);
|
||||
var (dirType, filetype) = bulktype switch
|
||||
{
|
||||
EBulkType.Raw => (UserSettings.Default.RawDataDirectory, "files"),
|
||||
EBulkType.Properties => (UserSettings.Default.PropertiesDirectory, "json files"),
|
||||
EBulkType.Textures => (UserSettings.Default.TextureDirectory, "textures"),
|
||||
EBulkType.Meshes => (UserSettings.Default.ModelDirectory, "models"),
|
||||
EBulkType.Animations => (UserSettings.Default.ModelDirectory, "animations"),
|
||||
EBulkType.Audio => (UserSettings.Default.AudioDirectory, "audio files"),
|
||||
_ => (null, null),
|
||||
};
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved models from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Animations":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.AnimationFolder(cancellationToken, folder);
|
||||
if (string.IsNullOrEmpty(dirType))
|
||||
return;
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved animations from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.ModelDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Folders_Save_Audio":
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.AudioFolder(cancellationToken, folder);
|
||||
Action<TreeItem> folderAction = bulktype switch
|
||||
{
|
||||
EBulkType.Raw => folder => contextViewModel.CUE4Parse.ExportFolder(cancellationToken, folder),
|
||||
_ => folder => contextViewModel.CUE4Parse.ExtractFolder(cancellationToken, folder, bulktype | EBulkType.Auto),
|
||||
};
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved audio from ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.AudioDirectory, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
#endregion
|
||||
foreach (var folder in folders)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
folderAction(folder);
|
||||
|
||||
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? folder.PathAtThisPoint : folder.PathAtThisPoint.SubstringAfterLast('/')).Replace('\\', '/');
|
||||
LogExport(contextViewModel, folder.PathAtThisPoint, path, dirType, filetype);
|
||||
}
|
||||
|
||||
Action<GameFile, EBulkType, bool> fileAction = bulktype switch
|
||||
{
|
||||
EBulkType.Raw => (entry, _, update) => contextViewModel.CUE4Parse.ExportData(entry, !update),
|
||||
_ => (entry, bulk, update) => contextViewModel.CUE4Parse.Extract(cancellationToken, entry, false, bulk),
|
||||
};
|
||||
|
||||
foreach (var group in assetsGroups)
|
||||
{
|
||||
var directory = group.Key;
|
||||
var list = group.ToArray();
|
||||
var update = list.Length > 1;
|
||||
var bulk = bulktype | (update ? EBulkType.Auto : EBulkType.None);
|
||||
foreach (var entry in list)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
fileAction(entry, bulk, update);
|
||||
}
|
||||
|
||||
if (update)
|
||||
{
|
||||
var path = Path.Combine(dirType, UserSettings.Default.KeepDirectoryStructure ? directory : directory.SubstringAfterLast('/')).Replace('\\', '/');
|
||||
LogExport(contextViewModel, directory, path, dirType, filetype);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void LogExport(ApplicationViewModel contextViewModel, string directory, string path, string basePath, string fileType)
|
||||
{
|
||||
if (contextViewModel.CUE4Parse.ExportedCount > 0)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text($"Successfully exported {contextViewModel.CUE4Parse.ExportedCount} {fileType} from ", Constants.WHITE);
|
||||
FLogger.Link(directory, Path.Exists(path) ? path : basePath, true);
|
||||
});
|
||||
}
|
||||
else if (contextViewModel.CUE4Parse.FailedExportCount == 0)
|
||||
{
|
||||
// Not an error because folder simply might not contain type of asset user is trying to save
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to find any {fileType} in {directory}", Constants.WHITE, true);
|
||||
});
|
||||
}
|
||||
|
||||
if (contextViewModel.CUE4Parse.FailedExportCount > 0)
|
||||
{
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to export {contextViewModel.CUE4Parse.FailedExportCount} {fileType} from {directory}", Constants.WHITE, true);
|
||||
});
|
||||
}
|
||||
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.ExportedCount, 0);
|
||||
Interlocked.Exchange(ref contextViewModel.CUE4Parse.FailedExportCount, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,34 +34,34 @@ public class TabCommand : ViewModelCommand<TabItem>
|
|||
case "Find_References":
|
||||
_applicationView.CUE4Parse.FindReferences(tabViewModel.Entry);
|
||||
break;
|
||||
case "Asset_Export_Data":
|
||||
case "Save_Data":
|
||||
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.ExportData(tabViewModel.Entry));
|
||||
break;
|
||||
case "Asset_Save_Properties":
|
||||
case "Save_Properties":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Properties);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Textures":
|
||||
case "Save_Textures":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Textures);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Models":
|
||||
case "Save_Models":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Meshes);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Animations":
|
||||
case "Save_Animations":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Animations);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Audio":
|
||||
case "Save_Audio":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, tabViewModel.Entry, false, EBulkType.Audio);
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
private const int MaxPreviewSize = 128;
|
||||
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private EGame? GameVersion => _applicationView.CUE4Parse?.Provider.Versions.Game;
|
||||
|
||||
public EResolveCompute Resolved { get; private set; } = EResolveCompute.None;
|
||||
public GameFile Asset { get; } = asset;
|
||||
|
|
@ -260,7 +261,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
// Game specific assets below
|
||||
UBorderlandsDialogObject => (EAssetCategory.Borderlands, EBulkType.None), // Borderlands 3;
|
||||
UGbxGraphAsset or UDialogScriptData or UDialogPerformanceData => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4; Borderlands 3;
|
||||
UFaceFXAnimSet when _applicationView.CUE4Parse?.Provider.Versions.Game is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
|
||||
UFaceFXAnimSet when GameVersion is EGame.GAME_Borderlands4 => (EAssetCategory.Borderlands, EBulkType.Audio), // Borderlands 4;
|
||||
|
||||
_ => (EAssetCategory.All, EBulkType.None),
|
||||
};
|
||||
|
|
@ -355,6 +356,7 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
case "csv":
|
||||
AssetCategory = EAssetCategory.Data;
|
||||
break;
|
||||
case "stinfo":
|
||||
case "ushaderbytecode":
|
||||
AssetCategory = EAssetCategory.ByteCode;
|
||||
break;
|
||||
|
|
@ -430,10 +432,13 @@ public class GameFileViewModel(GameFile asset) : ViewModel
|
|||
});
|
||||
}
|
||||
// Game specific extensions below
|
||||
case "ace": // Borderlands 3
|
||||
case "ncs": // Borderlands 4
|
||||
case "ace" when GameVersion is EGame.GAME_Borderlands3:
|
||||
case "ncs" when GameVersion is EGame.GAME_Borderlands4:
|
||||
AssetCategory = EAssetCategory.Borderlands;
|
||||
break;
|
||||
case "dat" when GameVersion is EGame.GAME_Aion2:
|
||||
AssetCategory = EAssetCategory.Aion2;
|
||||
break;
|
||||
default:
|
||||
AssetCategory = EAssetCategory.All; // just so it sets resolved
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ using Serilog;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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 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;
|
||||
_detectedDirectories.Add(setting);
|
||||
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()
|
||||
{
|
||||
UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.Utils;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.Commands;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
|
@ -8,15 +19,6 @@ using ICSharpCode.AvalonEdit.Document;
|
|||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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;
|
||||
|
||||
|
|
@ -374,8 +376,7 @@ public class TabItem : ViewModel
|
|||
public void SaveImage() => SaveImage(SelectedImage, true);
|
||||
private void SaveImage(TabImage image, bool updateUi)
|
||||
{
|
||||
if (image == null)
|
||||
return;
|
||||
if (image is null) return;
|
||||
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory, UserSettings.Default.KeepDirectoryStructure ? Entry.Directory : "", image.ExportName).Replace('\\', '/');
|
||||
|
||||
|
|
@ -392,6 +393,7 @@ public class TabItem : ViewModel
|
|||
|
||||
private void SaveImage(TabImage image, string path)
|
||||
{
|
||||
if (image.ImageBuffer is null) return;
|
||||
using var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
|
||||
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
|
||||
}
|
||||
|
|
@ -412,6 +414,7 @@ public class TabItem : ViewModel
|
|||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Interlocked.Increment(ref ApplicationService.ApplicationView.CUE4Parse.ExportedCount);
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
if (updateUi)
|
||||
{
|
||||
|
|
@ -424,6 +427,7 @@ public class TabItem : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Increment(ref ApplicationService.ApplicationView.CUE4Parse.FailedExportCount);
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileName}'", Constants.WHITE, true));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CUE4Parse.UE4.Exceptions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
|
@ -100,7 +101,26 @@ public class ThreadWorkerViewModel : ViewModel
|
|||
CurrentCancellationTokenSource = null; // kill token
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
|
||||
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" SizeToContent="Height"
|
||||
MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.20'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.25'}">
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.25'}"
|
||||
AllowDrop="True">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="Directory Selector" />
|
||||
|
|
@ -83,9 +84,12 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" />
|
||||
|
||||
<Expander ExpandDirection="Down" IsExpanded="False">
|
||||
<Separator Style="{StaticResource CustomSeparator}" Tag="ADD UNDETECTED GAME" Margin="0,0,0,0"/>
|
||||
<TextBlock Text="Drag & drop folder to quickly configure new game"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"
|
||||
Margin="0,0,0,5" />
|
||||
<Expander x:Name="ManualGameExpander" ExpandDirection="Down" IsExpanded="False">
|
||||
<Grid MaxWidth="{Binding ActualWidth, ElementName=Hello}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
|
|
@ -137,5 +141,8 @@
|
|||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="Cancel" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<controls:DropOverlay Grid.RowSpan="99"
|
||||
Grid.ColumnSpan="99"
|
||||
Panel.ZIndex="1000" />
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FModel.ViewModels;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using System.Windows;
|
||||
using CUE4Parse.Utils;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
|
|
@ -43,28 +39,7 @@ public partial class DirectorySelector
|
|||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
|
||||
|
||||
// install_folder/
|
||||
// ├─ Engine/
|
||||
// ├─ GameName/
|
||||
// │ ├─ Binaries/
|
||||
// │ ├─ Content/
|
||||
// │ │ ├─ Paks/
|
||||
// our goal is to get the GameName folder
|
||||
var currentFolder = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
if (currentFolder.Equals("Paks", StringComparison.InvariantCulture))
|
||||
{
|
||||
var dir = new DirectoryInfo(folderBrowser.SelectedPath);
|
||||
if (dir.Parent is { Parent: not null } &&
|
||||
dir.Parent.Name.Equals("Content", StringComparison.InvariantCulture) &&
|
||||
dir.Parent.Parent.GetDirectories().Any(x => x.Name == "Binaries"))
|
||||
{
|
||||
HelloMyNameIsGame.Text = dir.Parent.Parent.Name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HelloMyNameIsGame.Text = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
HelloMyNameIsGame.Text = Helper.GetGameName(folderBrowser.SelectedPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,4 +62,11 @@ public partial class DirectorySelector
|
|||
|
||||
gameLauncherViewModel.DeleteSelectedGame();
|
||||
}
|
||||
|
||||
public void AddManualGame(string directory)
|
||||
{
|
||||
ManualGameExpander.IsExpanded = true;
|
||||
HelloMyNameIsGame.Text = Helper.GetGameName(directory);
|
||||
HelloGameMyNameIsDirectory.Text = directory;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,4 +51,5 @@
|
|||
|
||||
<!-- For specific games -->
|
||||
<SolidColorBrush x:Key="BorderlandsBrush" Color="Yellow"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="AionBrush" Color="DeepSkyBlue"></SolidColorBrush>
|
||||
</ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -110,8 +110,7 @@
|
|||
</MenuItem.Style>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{Binding RightClickMenuCommand}"
|
||||
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<MenuItem Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.Header>
|
||||
<TextBlock
|
||||
Text="{Binding PlacementTarget.SelectedItem.Asset.Extension,
|
||||
|
|
@ -121,7 +120,7 @@
|
|||
</MenuItem.Header>
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -136,7 +135,7 @@
|
|||
<MenuItem Header="Save Properties (.json)" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -151,7 +150,7 @@
|
|||
<MenuItem Header="Save Texture" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -177,7 +176,7 @@
|
|||
<MenuItem Header="Save Model" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -203,7 +202,7 @@
|
|||
<MenuItem Header="Save Animation" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -229,7 +228,7 @@
|
|||
<MenuItem Header="Save Audio" Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="PlacementTarget.SelectedItems" RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:settings="clr-namespace:FModel.Settings"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
x:Class="FModel.Views.Resources.Controls.ContextMenus.FolderContextMenuDictionary">
|
||||
<ContextMenu x:Key="FolderContextMenu" x:Shared="False"
|
||||
Opened="FolderContextMenu_OnOpened">
|
||||
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)"
|
||||
Command="{Binding RightClickMenuCommand}"
|
||||
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Export_Data" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -31,7 +29,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -51,7 +49,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -71,7 +69,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -91,7 +89,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
@ -111,7 +109,7 @@
|
|||
Command="{Binding RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Folders_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="Tag"
|
||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||
</MultiBinding>
|
||||
|
|
|
|||
58
FModel/Views/Resources/Controls/DropOverlay.xaml
Normal file
58
FModel/Views/Resources/Controls/DropOverlay.xaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<UserControl x:Class="FModel.Views.Resources.Controls.DropOverlay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:FModel.Views.Resources.Controls"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
Visibility="Collapsed"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800">
|
||||
<Grid Background="#DD000000"
|
||||
IsHitTestVisible="False"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="99">
|
||||
<Border BorderThickness="2"
|
||||
CornerRadius="10"
|
||||
Margin="60"
|
||||
Background="#1FFFFFFF">
|
||||
<Border.BorderBrush>
|
||||
<VisualBrush>
|
||||
<VisualBrush.Visual>
|
||||
<Rectangle Stroke="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}"
|
||||
StrokeThickness="2"
|
||||
StrokeDashArray="4 2"
|
||||
StrokeDashCap="Round"
|
||||
Width="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualWidth}"
|
||||
Height="{Binding RelativeSource={RelativeSource AncestorType=Border}, Path=ActualHeight}"
|
||||
RadiusX="10"
|
||||
RadiusY="10" />
|
||||
</VisualBrush.Visual>
|
||||
</VisualBrush>
|
||||
</Border.BorderBrush>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<Viewbox Width="80"
|
||||
Height="80"
|
||||
Margin="0,0,0,24"
|
||||
HorizontalAlignment="Center">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}"
|
||||
Data="{StaticResource ImportIcon}" />
|
||||
</Viewbox>
|
||||
<TextBlock x:Name="TitleText" Text="Drop .usmap to import"
|
||||
Foreground="White"
|
||||
FontSize="24"
|
||||
FontWeight="Bold"
|
||||
HorizontalAlignment="Center" />
|
||||
<TextBlock x:Name="DescriptionText" Text="Mapping file will be applied immediately"
|
||||
Foreground="LightGray"
|
||||
FontSize="14"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0,10,0,0" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
169
FModel/Views/Resources/Controls/DropOverlay.xaml.cs
Normal file
169
FModel/Views/Resources/Controls/DropOverlay.xaml.cs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public partial class DropOverlay : UserControl
|
||||
{
|
||||
enum DragStatus
|
||||
{
|
||||
None,
|
||||
File,
|
||||
Folder,
|
||||
}
|
||||
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DragStatus _dragStatus = DragStatus.None;
|
||||
private string _path = null;
|
||||
|
||||
public DropOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ResetState()
|
||||
{
|
||||
_dragStatus = DragStatus.None;
|
||||
_path = null;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
if (window is null)
|
||||
return;
|
||||
|
||||
window.PreviewDragEnter += OnPreviewDragEnter;
|
||||
window.PreviewDragOver += OnPreviewDragOver;
|
||||
window.PreviewDragLeave += OnPreviewDragLeave;
|
||||
window.Drop += OnDrop;
|
||||
}
|
||||
|
||||
private void OnPreviewDragEnter(object sender, DragEventArgs e)
|
||||
{
|
||||
GetValidTarget(sender, e);
|
||||
if (_dragStatus is DragStatus.None)
|
||||
{
|
||||
e.Effects = DragDropEffects.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
Visibility = Visibility.Visible;
|
||||
e.Effects = DragDropEffects.Copy;
|
||||
|
||||
if (_dragStatus is DragStatus.Folder)
|
||||
{
|
||||
TitleText.Text = "Drop folder to add new game";
|
||||
DescriptionText.Text = "Folder will be added to the directory selector";
|
||||
}
|
||||
else if (_dragStatus is DragStatus.File)
|
||||
{
|
||||
TitleText.Text = "Drop .usmap to import";
|
||||
DescriptionText.Text = "Mapping file will be applied immediately";
|
||||
}
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPreviewDragOver(object sender, DragEventArgs e)
|
||||
{
|
||||
e.Effects = _dragStatus is DragStatus.None ? DragDropEffects.None : DragDropEffects.Copy;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPreviewDragLeave(object sender, DragEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private async void OnDrop(object sender, DragEventArgs e)
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
e.Handled = true;
|
||||
switch (_dragStatus)
|
||||
{
|
||||
case DragStatus.Folder:
|
||||
await Dispatcher.InvokeAsync(() => _applicationView.AddGameDirectory(_path));
|
||||
break;
|
||||
case DragStatus.File:
|
||||
UserSettings.IsEndpointValid(EEndpointType.Mapping, out var oldMappingsEndpoint);
|
||||
try
|
||||
{
|
||||
var newMappingsEndpoint = new EndpointSettings() { Overwrite = true, FilePath = _path };
|
||||
UserSettings.Default.CurrentDir.Endpoints[(int) EEndpointType.Mapping] = newMappingsEndpoint;
|
||||
await _applicationView.CUE4Parse.InitMappings();
|
||||
_applicationView.SettingsView.MappingEndpoint = newMappingsEndpoint;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UserSettings.Default.CurrentDir.Endpoints[(int) EEndpointType.Mapping] = oldMappingsEndpoint;
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
FLogger.Text($"Failed to load mapping file: {ex.Message}", Constants.WHITE, true);
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ResetState();
|
||||
}
|
||||
|
||||
private void GetValidTarget(object sender, DragEventArgs e)
|
||||
{
|
||||
if (!_applicationView.Status.IsReady || !e.Data.GetDataPresent(DataFormats.FileDrop) || e.Data.GetData(DataFormats.FileDrop) is not string[] files)
|
||||
return;
|
||||
|
||||
|
||||
bool directorySelectorIsVisible = _applicationView.Status.Kind is EStatusKind.Configuring;
|
||||
if (!directorySelectorIsVisible && (Helper.IsWindowOpen<DictionaryEditor>() || Helper.IsWindowOpen<EndpointEditor>()))
|
||||
{
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
ResetState();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (sender)
|
||||
{
|
||||
case MainWindow or SettingsView when !directorySelectorIsVisible:
|
||||
foreach (var path in files)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
_path = path;
|
||||
_dragStatus = DragStatus.Folder;
|
||||
return;
|
||||
}
|
||||
else if (File.Exists(path) && Path.GetExtension(path).Equals(".usmap", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_path = path;
|
||||
_dragStatus = DragStatus.File;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DirectorySelector:
|
||||
if (files.FirstOrDefault(f => Directory.Exists(f)) is { } folder)
|
||||
{
|
||||
_path = folder;
|
||||
_dragStatus = DragStatus.Folder;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ResetState();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -23,8 +24,9 @@ public partial class EndpointEditor
|
|||
InitializeComponent();
|
||||
|
||||
Title = title;
|
||||
TargetResponse.SyntaxHighlighting =
|
||||
EndpointResponse.SyntaxHighlighting = AvalonExtensions.HighlighterSelector("json");
|
||||
TargetResponse.SyntaxHighlighting = EndpointResponse.SyntaxHighlighting = AvalonExtensions.HighlighterSelector("json");
|
||||
TargetResponse.TextArea.TextView.LinkTextForegroundBrush = Brushes.DodgerBlue;
|
||||
EndpointResponse.TextArea.TextView.LinkTextForegroundBrush = Brushes.DodgerBlue;
|
||||
|
||||
InstructionBox.Text = type switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
|
@ -126,13 +126,40 @@ public class FLogger : ITextFormatter
|
|||
{
|
||||
NavigateUri = new Uri(url),
|
||||
OverridesDefaultStyle = true,
|
||||
Style = new Style(typeof(Hyperlink)) { Setters =
|
||||
Style = new Style(typeof(Hyperlink))
|
||||
{
|
||||
new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand),
|
||||
new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline),
|
||||
new Setter(TextElement.ForegroundProperty, Brushes.Cornsilk)
|
||||
}}
|
||||
}.Click += (sender, _) => Process.Start("explorer.exe", $"/select, \"{((Hyperlink)sender).NavigateUri.AbsoluteUri}\"");
|
||||
Setters =
|
||||
{
|
||||
new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand),
|
||||
new Setter(TextElement.ForegroundProperty, Brushes.Goldenrod),
|
||||
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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ public class FileToGeometryConverter : IMultiValueConverter
|
|||
EAssetCategory.ByteCode => ("CodeIcon", "CodeBrush"),
|
||||
|
||||
EAssetCategory.Borderlands => ("BorderlandsIcon", "BorderlandsBrush"),
|
||||
EAssetCategory.Aion2 => ("AionIcon", "AionBrush"),
|
||||
|
||||
_ => ("AssetIcon", "NeutralBrush")
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,7 +98,9 @@
|
|||
<Geometry x:Key="CssIcon">M5,3L4.35,6.34H17.94L17.5,8.5H3.92L3.26,11.83H16.85L16.09,15.64L10.61,17.45L5.86,15.64L6.19,14H2.85L2.06,18L9.91,21L18.96,18L20.16,11.97L20.4,10.76L21.94,3H5Z</Geometry>
|
||||
<Geometry x:Key="CsvIcon">M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2M15 16L13 20H10L12 16H9V11H15V16M13 9V3.5L18.5 9H13Z</Geometry>
|
||||
<Geometry x:Key="AIIcon">M12,2A2,2 0 0,1 14,4C14,4.74 13.6,5.39 13,5.73V7H14A7,7 0 0,1 21,14H22A1,1 0 0,1 23,15V18A1,1 0 0,1 22,19H21V20A2,2 0 0,1 19,22H5A2,2 0 0,1 3,20V19H2A1,1 0 0,1 1,18V15A1,1 0 0,1 2,14H3A7,7 0 0,1 10,7H11V5.73C10.4,5.39 10,4.74 10,4A2,2 0 0,1 12,2M7.5,13A2.5,2.5 0 0,0 5,15.5A2.5,2.5 0 0,0 7.5,18A2.5,2.5 0 0,0 10,15.5A2.5,2.5 0 0,0 7.5,13M16.5,13A2.5,2.5 0 0,0 14,15.5A2.5,2.5 0 0,0 16.5,18A2.5,2.5 0 0,0 19,15.5A2.5,2.5 0 0,0 16.5,13Z</Geometry>
|
||||
<Geometry x:Key="ImportIcon">M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z</Geometry>
|
||||
|
||||
<!-- For specific games-->
|
||||
<Geometry x:Key="BorderlandsIcon">M13,9V3.5L18.5,9M6,2C4.89,2 4,2.89 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6 ZM12,11A3,3 0 1,0 12,17A3,3 0 0,0 12,11 ZM12,12.5L14,16H13L12,14.5L11,16H10L12,12.5Z</Geometry>
|
||||
<Geometry x:Key="AionIcon">M609 180.1c-19.4 2.3-38.2 7.8-56.5 16.8-53.8 26.5-89.2 74.9-98.7 135.1-1.1 7-1.3 133.1-1.3 739 0 693.9.1 731 1.8 741.4 12.8 79.4 70.7 137.8 149.2 150.6 11.8 2 20.6 2 563.5 2s551.7 0 563.5-2c57.2-9.3 104-43 130.8-94.1 9.1-17.2 14.6-33.9 18.4-55.4 1.6-9.4 1.8-40.6 2-554l.3-544-268.2-268.2L1345.5 179l-364.5.1c-200.5.1-367.9.5-372 1m893 378.4L1747.5 804H1256V558.5c0-135 .1-245.5.3-245.5.1 0 110.7 110.5 245.7 245.5m-792.1 532c24.5 1.3 47 2.9 49.8 3.4 7.1 1.3 12.8 5.5 22.5 16.4 6.4 7.3 9.2 11.5 13.6 20.7 9.2 19.4 21.4 47.2 44.4 101.5 27.5 65.1 39.7 92.3 55.5 124 6.9 13.7 16.1 32.9 20.5 42.5 8.2 17.9 15.4 31.9 18.3 35.5 1.5 1.8 1.5-7.2.9-109-.6-108.7-.7-111.1-2.7-116.5-3.1-8.3-10-14.5-24.7-22-10.1-5.1-11.9-6.4-10.9-7.6 1.6-2 5.8-1.8 21.3 1.1 19.3 3.6 39.1 4.5 97.7 4.5 42.1 0 51.9.3 53.4 1.4 1.8 1.3 1.8 1.4-.1 1.9-1 .3-3.9.8-6.4 1.2s-8.5 1.7-13.5 3c-4.9 1.3-12.1 2.6-16 3-7.9.8-14.3 3.2-18 6.9l-2.5 2.5v123.7c0 121.8 0 123.8 2 125.9 1.4 1.5 2.1 4.1 2.6 9.2.8 8.1 2.2 10.1 12.6 17.2 9.3 6.5 9.3 6.5-26.7 7.3-17.6.4-45.3.7-61.5.5-29.2-.2-29.5-.2-29.8-2.3-.2-1.7.7-2.5 5.5-4.5 3.2-1.3 7.5-4.1 9.8-6.4 4.5-4.5 8.3-11 7.2-12.2-1.3-1.2-72.8-1.6-90.2-.5-13.5.9-16.2.8-17.5-.4-1.9-1.9-.9-2.7 8-6.4 9.8-4.1 11-5.2 11-11 0-3.9-3.7-13.2-22.3-56.1l-22.3-51.4-4.5-.6c-13.1-2-52.1-3.1-69.3-2-14.4.9-38.5 3.6-39.3 4.4-.1.1-5.6 13.5-12.3 29.7s-16 38.7-20.7 50c-7.4 17.9-8.5 21.1-8.1 25.1.6 5.6 3.9 10.3 10.2 14.3 4.8 3 5.8 5.1 2.9 5.8-1 .2-10.6 0-21.3-.5-21.7-.9-83.9-.2-108.4 1.3-22.1 1.4-22.5.3-1.7-5 17.9-4.6 33.3-9.6 42.4-13.7l7.7-3.6 6.9-13.1c3.8-7.2 14.2-27.5 23.1-45.2 17.9-35.6 19-38.9 15.5-47.9-1-2.7-1.4-5.6-1.1-7.1.4-1.5 8.1-10.1 17.5-19.8 9.2-9.4 17.6-18.6 18.5-20.4s6.4-15.7 12.2-30.8c5.7-15.1 17.9-46.7 27.1-70.2l16.6-42.7-9.3-18.2c-5.1-10-10.7-19.8-12.5-21.6-6-6.1-16.6-9.9-32-11.4-7.8-.8-8-.9-8.3-3.6s-.3-2.7 5.9-2.7c3.5 0 26.3 1.1 50.8 2.5m999.3 20.4c3 5.2 4.8 10.9 9.7 31 8.8 35.5 11.8 55.6 14.2 93.1 1.5 24.1.6 87-1.5 108-1.8 17.4-4.4 37.5-5.3 40.5-.3 1.3-.2 1.7.5 1 1.2-1.2 11.9-33.8 16.2-49.5 1.8-6.3 3.7-12.4 4.2-13.4 3.4-6.4 2.2 14.4-2 34.9-1.3 6.3-1.5 9.6-.9 10.7 2.1 3.3-9.4 55.8-18.3 83.3-5.5 17.2-15.6 42.7-18.1 45.9-1.1 1.5-2.6 2.3-3.4 1.9-.8-.3-5.7-6.9-10.9-14.7-17.4-26.2-33.3-45.2-60.7-72.1-16.4-16.1-29.1-27.5-45.6-41-4-3.2-7.5-6.5-7.8-7.3-1.2-3.2 5.5.3 19.9 10.4 8.1 5.8 15 10.3 15.2 10.2.4-.5-17.3-14.6-37.1-29.4-43.5-32.7-81.4-58.4-86.1-58.4-1.3 0-1.4 9.9-.8 86.7.4 61.2 1 87.5 1.8 89.3 1.4 3.1 7.5 7.2 14 9.5 6.9 2.4 7.6 3.2 4.6 5.5-2.4 1.9-5 2-74.2 2-45.3 0-71.8-.4-71.8-1 0-.9 3.2-1.9 12-3.9 9.2-2.1 22.3-6.2 26.9-8.6 7.6-3.8 8.9-8.7 10.1-38 1.3-29.8 2.5-203 1.5-217.8-.7-11.3-.9-12-3.8-15.3-1.6-1.9-4.3-4.2-6-5-5.1-2.7-24.7-6.3-42.9-7.9-9.7-.8-18.4-1.8-19.2-2.1-1.4-.5-2.2-2-1.3-2.6.3-.2 73.8-1.9 90.7-2.1 12.6-.2 13.9-.4 19.5-3 4.8-2.3 8.6-3.1 19.5-4.2 18.5-1.9 34.2-2.3 34.7-.9.3 1-2.3 4.6-10.9 14.7l-3.3 3.9-.3 20.2-.3 20.2 5.7 8c17.5 24.5 49.6 55.6 93.9 91 20 16.1 58.7 45.6 59.1 45.1.7-.7 6.5-38.1 8.5-55.7 3-25 3.3-66.8.6-84-2.5-16.2-9.6-43-11.4-43-.2 0-8.1 7.2-17.5 16.1-9.3 8.8-17.2 15.8-17.5 15.5s2.7-4.5 6.8-9.3c13-15.6 35.1-41.7 37.2-44.1 1.1-1.2 1.8-2.2 1.4-2.2-.3 0-16.9 17-36.8 37.7-37.3 38.8-41.5 43-42.6 41.9-.8-.8-4.1 3.1 54-63.3 28.4-32.6 51.7-59.6 51.7-59.9s-3.2 2.3-7 5.7c-14.3 12.9-15.9 13.8-7.5 4.4 16.1-18.1 32.8-35.6 33.9-35.2.6.2 2.8 3.2 4.8 6.6m-578.4 94.3c-.3.7-5 4.4-10.5 8.3-13.5 9.4-17.7 12.9-31.6 26.4-8.1 8-13.5 14.2-17.6 20.5-6.8 10.7-11.5 18.6-10.9 18.6.3 0 2-2.1 3.8-4.8 5.1-7.1 19.5-21.6 28.5-28.7 24.4-19.1 53.7-31.3 89-37.2 13.7-2.2 53.7-2.5 68.5-.5 45.5 6.4 82.4 22.6 109.1 48 26 24.7 40.4 54.7 45 93.7 1.7 14.2.6 42.3-2.1 54.9-8.5 39.6-25.4 68.8-53.5 92.6-15.5 13-29.1 20.9-45.7 26.5-18.4 6.1-35.4 7.7-56 5.3l-10.3-1.3 10.5-.6c11.4-.8 27.2-3.2 31.1-4.9 2.3-.9 2.2-1-.6-.5-15.1 2.4-38.9 3.2-48.9 1.5-11.5-1.9-10.1-4 6.3-9.9 6.2-2.3 15.4-6.2 20.4-8.8 56.5-29.2 84.7-97.9 69.1-168.3-11.5-51.5-43.4-85-89.3-93.6-11.4-2.1-32.9-2.2-44.2 0-40.6 7.7-70 34.9-84.5 78.1-5.8 17.5-7.7 31.8-7.1 54 .6 21.3 2.2 32.3 7.3 49.2 11.8 39 34.4 64.6 78.4 88.7 4.7 2.6 9.6 5.8 11 7.1 2.3 2.4 2.4 2.6.7 4.2-1.6 1.6-3.8 1.8-17 1.7-45.3-.2-82.1-18.2-112.6-55-4.2-5.1-9.6-12.4-12.1-16.2-2.5-3.9-4.9-7.1-5.4-7.1-.5-.1-.6 3.7-.3 8.5l.5 8.5-2.4-2.2c-3-2.8-10.5-17.2-14.3-27.7-7.2-19.5-12.1-46.9-12.1-67.8 0-30.8 5.7-56.3 18.5-81.9 16.2-32.6 43.1-58.4 79-75.7 10.3-4.9 13-5.7 12.3-3.6 M731 1231.2c-6.2 15.5-15.9 40.1-21.6 54.5-5.7 14.5-10.4 26.7-10.4 27.2 0 1.5 7.9 2 39 2.7 26.5.5 51.1-.3 52.6-1.8.6-.6-46.1-108.6-47.5-110.1-.5-.5-5.9 11.9-12.1 27.5</Geometry>
|
||||
</ResourceDictionary>
|
||||
|
|
|
|||
|
|
@ -926,8 +926,7 @@
|
|||
</MenuItem.IsEnabled>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{Binding TabCommand}" CommandParameter="Asset_Export_Data"
|
||||
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<MenuItem Command="{Binding TabCommand}" CommandParameter="Save_Data">
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding Entry.Extension, FallbackValue='Export Raw Data', StringFormat='Export Raw Data (.{0})'}" />
|
||||
</MenuItem.Header>
|
||||
|
|
@ -939,7 +938,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Properties">
|
||||
<MenuItem Header="Save Properties (.json)" Command="{Binding TabCommand}" CommandParameter="Save_Properties">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -948,7 +947,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Textures">
|
||||
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Save_Textures">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -957,7 +956,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Models">
|
||||
<MenuItem Header="Save Model" Command="{Binding TabCommand}" CommandParameter="Save_Models">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -966,7 +965,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Animations">
|
||||
<MenuItem Header="Save Animation" Command="{Binding TabCommand}" CommandParameter="Save_Animations">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -975,7 +974,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Audio" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Audio">
|
||||
<MenuItem Header="Save Audio" Command="{Binding TabCommand}" CommandParameter="Save_Audio">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.SearchView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:settings="clr-namespace:FModel.Settings"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
|
|
@ -210,15 +209,14 @@
|
|||
</MenuItem.IsEnabled>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}"
|
||||
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
|
||||
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" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -233,7 +231,7 @@
|
|||
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -248,7 +246,7 @@
|
|||
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -263,7 +261,7 @@
|
|||
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -278,7 +276,7 @@
|
|||
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -293,7 +291,7 @@
|
|||
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -556,15 +554,14 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}"
|
||||
Visibility="{Binding CanExportRawData, Source={x:Static settings:UserSettings.Default}, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<MenuItem Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.Header>
|
||||
<TextBlock Text="{Binding DataContext.SelectedItem.Extension, FallbackValue='Export Raw Data',
|
||||
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" />
|
||||
<Binding Source="Save_Data" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -579,7 +576,7 @@
|
|||
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Source="Save_Properties" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -594,7 +591,7 @@
|
|||
<MenuItem Header="Save Texture" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Source="Save_Textures" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -609,7 +606,7 @@
|
|||
<MenuItem Header="Save Model" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Source="Save_Models" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -624,7 +621,7 @@
|
|||
<MenuItem Header="Save Animation" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Source="Save_Animations" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
@ -639,7 +636,7 @@
|
|||
<MenuItem Header="Save Audio" Command="{Binding DataContext.mainApplication.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Audio" />
|
||||
<Binding Source="Save_Audio" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
WindowStartupLocation="CenterScreen" ResizeMode="NoResize" IconVisibility="Collapsed" SizeToContent="Height"
|
||||
MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.10'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.45'}">
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.45'}"
|
||||
AllowDrop="True">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="Settings" />
|
||||
|
|
@ -43,8 +44,6 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -162,7 +161,7 @@
|
|||
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 5 0 0" />
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Local Mapping File (drag & drop)" VerticalAlignment="Center" Margin="0 5 0 0" />
|
||||
<CheckBox Grid.Row="10" Grid.Column="2" Margin="0 5 0 0"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
|
|
@ -223,51 +222,41 @@
|
|||
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
|
||||
</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}}"
|
||||
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"
|
||||
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" />
|
||||
<CheckBox Grid.Row="18" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Inlined Shader Maps" 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 ReadShaderMaps, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"
|
||||
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" />
|
||||
<CheckBox Grid.Row="19" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<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="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"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" />
|
||||
|
||||
<TextBlock Grid.Row="20"
|
||||
<TextBlock Grid.Row="19"
|
||||
Grid.Column="0"
|
||||
Text="Convert Audio During Export (.wav)"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="20"
|
||||
<CheckBox Grid.Row="19"
|
||||
Grid.Column="2"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding ConvertAudioOnBulkExport, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Margin="0 5 0 10"
|
||||
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" />
|
||||
<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"
|
||||
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
|
||||
Value="{Binding WwiseMaxBnkPrefetch, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="22"
|
||||
<TextBlock Grid.Row="20"
|
||||
Grid.Column="0"
|
||||
Text="CRIWARE Decryption Key"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0 0 0 10" />
|
||||
|
||||
<TextBox x:Name="CriwareKeyBox"
|
||||
Grid.Row="22"
|
||||
Grid.Row="20"
|
||||
Grid.Column="2"
|
||||
Grid.ColumnSpan="5"
|
||||
Margin="0 5 0 10"
|
||||
|
|
@ -398,6 +387,8 @@
|
|||
<ContentControl.Style>
|
||||
<Style TargetType="{x:Type ContentControl}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<EventSetter Event="Hyperlink.Click" Handler="OnHyperlinkClick" />
|
||||
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DataContext.SettingsView.SelectedMeshExportFormat, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Value="{x:Static c4pMeshes:EMeshFormat.UEFormat}">
|
||||
<Setter Property="ContentTemplate">
|
||||
|
|
@ -711,5 +702,8 @@
|
|||
HorizontalAlignment="Right" VerticalAlignment="Bottom" Content="OK" Click="OnClick" />
|
||||
</Grid>
|
||||
</Border>
|
||||
<controls:DropOverlay Grid.RowSpan="99"
|
||||
Grid.ColumnSpan="99"
|
||||
Panel.ZIndex="1000" />
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels;
|
||||
|
|
@ -153,7 +155,11 @@ public partial class SettingsView
|
|||
private void OpenCustomVersions(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedCustomVersions, "Versioning Configuration (Custom Versions)");
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
var result = editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
|
|
@ -163,7 +169,11 @@ public partial class SettingsView
|
|||
private void OpenOptions(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedOptions, "Versioning Configuration (Options)");
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
var result = editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
|
|
@ -173,7 +183,11 @@ public partial class SettingsView
|
|||
private void OpenMapStructTypes(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(_applicationView.SettingsView.SelectedMapStructTypes, "Versioning Configuration (MapStructTypes)");
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
var result = editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
|
|
@ -184,14 +198,22 @@ public partial class SettingsView
|
|||
{
|
||||
var editor = new EndpointEditor(
|
||||
_applicationView.SettingsView.AesEndpoint, "Endpoint Configuration (AES)", EEndpointType.Aes);
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
}
|
||||
|
||||
private void OpenMappingEndpoint(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new EndpointEditor(
|
||||
_applicationView.SettingsView.MappingEndpoint, "Endpoint Configuration (Mapping)", EEndpointType.Mapping);
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Configuring);
|
||||
editor.ShowDialog();
|
||||
if (_applicationView.Status.IsReady)
|
||||
_applicationView.Status.SetStatus(EStatusKind.Ready);
|
||||
}
|
||||
|
||||
private void CriwareKeyBox_Loaded(object sender, RoutedEventArgs e)
|
||||
|
|
@ -241,4 +263,12 @@ public partial class SettingsView
|
|||
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 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ public abstract class UModel : IRenderableModel
|
|||
{
|
||||
_export = export;
|
||||
Path = _export.GetPathName();
|
||||
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
|
||||
Name = export.Name;
|
||||
Type = export.ExportType;
|
||||
UvCount = 1;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user