mirror of
https://github.com/4sval/FModel.git
synced 2026-03-21 17:24:26 -05:00
Some checks failed
FModel QA Builder / build (push) Has been cancelled
+ 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>
330 lines
12 KiB
C#
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);
|
|
}
|
|
}
|