FModel/FModel/ViewModels/ApplicationViewModel.cs
Masusder 8b95b403bb
Some checks failed
FModel QA Builder / build (push) Has been cancelled
New Explorer System (#619)
+ filter by types, find by references (UE5+), and a lot of other improvements

Co-authored-by: Asval <asval.contactme@gmail.com>
Co-authored-by: LongerWarrior <LongerWarrior@gmail.com>
2025-12-19 18:34:33 +01:00

330 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using CUE4Parse_Conversion.Textures.BC;
using CUE4Parse.Compression;
using CUE4Parse.Encryption.Aes;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.VirtualFileSystem;
using FModel.Extensions;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.Commands;
using FModel.Views;
using FModel.Views.Resources.Controls;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
namespace FModel.ViewModels;
public class ApplicationViewModel : ViewModel
{
private EBuildKind _build;
public EBuildKind Build
{
get => _build;
private init
{
SetProperty(ref _build, value);
RaisePropertyChanged(nameof(TitleExtra));
}
}
private FStatus _status;
public FStatus Status
{
get => _status;
private init => SetProperty(ref _status, value);
}
public IEnumerable<EAssetCategory> Categories { get; } = AssetCategoryExtensions.GetBaseCategories();
private bool _isAssetsExplorerVisible;
public bool IsAssetsExplorerVisible
{
get => _isAssetsExplorerVisible;
set
{
if (value && !UserSettings.Default.FeaturePreviewNewAssetExplorer)
return;
SetProperty(ref _isAssetsExplorerVisible, value);
}
}
private int _selectedLeftTabIndex;
public int SelectedLeftTabIndex
{
get => _selectedLeftTabIndex;
set
{
if (value is < 0 or > 2) return;
SetProperty(ref _selectedLeftTabIndex, value);
}
}
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
private RightClickMenuCommand _rightClickMenuCommand;
public MenuCommand MenuCommand => _menuCommand ??= new MenuCommand(this);
private MenuCommand _menuCommand;
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
private CopyCommand _copyCommand;
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID} - {Constants.APP_BUILD_DATE:MMM d, yyyy})";
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
public LoadingModesViewModel LoadingModes { get; }
public CustomDirectoriesViewModel CustomDirectories { get; }
public CUE4ParseViewModel CUE4Parse { get; }
public SettingsViewModel SettingsView { get; }
public AesManagerViewModel AesManager { get; }
public AudioPlayerViewModel AudioPlayer { get; }
public ApplicationViewModel()
{
Status = new FStatus();
#if DEBUG
Build = EBuildKind.Debug;
#elif RELEASE
Build = EBuildKind.Release;
#else
Build = EBuildKind.Unknown;
#endif
LoadingModes = new LoadingModesViewModel();
UserSettings.Default.CurrentDir = AvoidEmptyGameDirectory(false);
if (UserSettings.Default.CurrentDir is null)
{
//If no game is selected, many things will break before a shutdown request is processed in the normal way.
//A hard exit is preferable to an unhandled expection in this case
Environment.Exit(0);
}
CUE4Parse = new CUE4ParseViewModel();
CUE4Parse.Provider.VfsRegistered += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count} Archives ({reader.Name})", "Registered");
CUE4Parse.GameDirectory.Add(reader);
};
CUE4Parse.Provider.VfsMounted += (sender, count) =>
{
if (sender is not IAesVfsReader reader) return;
Status.UpdateStatusLabel($"{count:N0} Packages ({reader.Name})", "Mounted");
CUE4Parse.GameDirectory.Verify(reader);
};
CUE4Parse.Provider.VfsUnmounted += (sender, _) =>
{
if (sender is not IAesVfsReader reader) return;
CUE4Parse.GameDirectory.Disable(reader);
};
CustomDirectories = new CustomDirectoriesViewModel();
SettingsView = new SettingsViewModel();
AesManager = new AesManagerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
Status.SetStatus(EStatusKind.Ready);
}
public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;
if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir))
return currentDir;
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
if (!result.HasValue || !result.Value) return null;
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory;
if (!bAlreadyLaunched || UserSettings.Default.CurrentDir.Equals(gameLauncherViewModel.SelectedDirectory))
return gameLauncherViewModel.SelectedDirectory;
// UserSettings.Save(); // ??? change key then change game, key saved correctly what?
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);
Restart();
}
public void Restart()
{
var path = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
if (path.EndsWith(".dll"))
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"\"{path}\"",
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
}
}.Start();
}
else if (path.EndsWith(".exe"))
{
new Process
{
StartInfo = new ProcessStartInfo
{
FileName = path,
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true
}
}.Start();
}
Application.Current.Shutdown();
}
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !AesManager.HasChange) return;
CUE4Parse.ClearProvider();
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
// TODO: refactor after release, select updated keys only
var aes = AesManager.AesKeys.Select(x =>
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
var k = x.Key.Trim();
if (k.Length != 66) k = Constants.ZERO_64_CHAR;
return new KeyValuePair<FGuid, FAesKey>(x.Guid, new FAesKey(k));
});
CUE4Parse.LoadVfs(aes);
AesManager.SetAesKeys();
});
RaisePropertyChanged(nameof(GameDisplayName));
}
public static async Task InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
var vgmFileInfo = new FileInfo(vgmZipFilePath);
if (!vgmFileInfo.Exists || vgmFileInfo.LastWriteTimeUtc < DateTime.UtcNow.AddMonths(-4))
{
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
vgmFileInfo.Refresh();
if (vgmFileInfo.Length > 0)
{
var zipDir = Path.GetDirectoryName(vgmZipFilePath)!;
await using var zipFs = File.OpenRead(vgmZipFilePath);
using var zip = new ZipArchive(zipFs, ZipArchiveMode.Read);
foreach (var entry in zip.Entries)
{
var entryPath = Path.Combine(zipDir, entry.FullName);
await using var entryFs = File.Create(entryPath);
await using var entryStream = entry.Open();
await entryStream.CopyToAsync(entryFs);
}
}
else
{
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
}
}
}
public static async Task InitImGuiSettings(bool forceDownload)
{
const string imgui = "imgui.ini";
var imguiPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", imgui);
if (File.Exists(imgui)) File.Move(imgui, imguiPath, true);
if (File.Exists(imguiPath) && !forceDownload) return;
await ApplicationService.ApiEndpointView.DownloadFileAsync($"https://cdn.fmodel.app/d/configurations/{imgui}", imguiPath);
if (new FileInfo(imguiPath).Length == 0)
{
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download ImGui settings", Constants.WHITE, true));
}
}
public static async Task InitOodle()
{
if (File.Exists(OodleHelper.OODLE_DLL_NAME_OLD))
{
try
{
File.Delete(OodleHelper.OODLE_DLL_NAME_OLD);
}
catch { /* ignored */}
}
var oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME_OLD);
if (!File.Exists(oodlePath))
{
oodlePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", OodleHelper.OODLE_DLL_NAME);
}
if (!File.Exists(oodlePath))
{
if (!await OodleHelper.DownloadOodleDllAsync(oodlePath))
{
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Oodle", Constants.WHITE, true));
return;
}
}
OodleHelper.Initialize(oodlePath);
}
public static async Task InitZlib()
{
var zlibPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", ZlibHelper.DLL_NAME);
var zlibFileInfo = new FileInfo(zlibPath);
if (!zlibFileInfo.Exists || zlibFileInfo.LastWriteTimeUtc < DateTime.UtcNow.AddMonths(-4))
{
if (!await ZlibHelper.DownloadDllAsync(zlibPath))
{
FLogger.Append(ELog.Error, () => FLogger.Text("Failed to download Zlib-ng", Constants.WHITE, true));
if (!zlibFileInfo.Exists) return;
}
}
ZlibHelper.Initialize(zlibPath);
}
public static async Task InitDetex()
{
var detexPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", DetexHelper.DLL_NAME);
if (File.Exists(DetexHelper.DLL_NAME))
{
File.Move(DetexHelper.DLL_NAME, detexPath, true);
}
else if (!File.Exists(detexPath))
{
await DetexHelper.LoadDllAsync(detexPath);
}
DetexHelper.Initialize(detexPath);
}
}