Update/net7 (#290)

* file-scoped namespace & net7.0

* Workflow
This commit is contained in:
GMatrixGames
2022-06-11 20:07:59 -04:00
committed by GitHub
parent 85158296a9
commit e21a3be55b
144 changed files with 14135 additions and 14472 deletions

View File

@@ -10,144 +10,143 @@ using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using Serilog;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class AesManagerViewModel : ViewModel
{
public class AesManagerViewModel : ViewModel
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
public FullyObservableCollection<FileItem> AesKeys { get; private set; } // holds all aes keys even the main one
public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui
public bool HasChange { get; set; }
private AesResponse _keysFromSettings;
private HashSet<FGuid> _uniqueGuids;
private readonly CUE4ParseViewModel _cue4Parse;
private readonly FileItem _mainKey = new("Main Static Key", 0) { Guid = Constants.ZERO_GUID }; // just so main key gets refreshed in the ui
public AesManagerViewModel(CUE4ParseViewModel cue4Parse)
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
_cue4Parse = cue4Parse;
HasChange = false;
}
public FullyObservableCollection<FileItem> AesKeys { get; private set; } // holds all aes keys even the main one
public ICollectionView AesKeysView { get; private set; } // holds all aes key ordered by name for the ui
public bool HasChange { get; set; }
private AesResponse _keysFromSettings;
private HashSet<FGuid> _uniqueGuids;
private readonly CUE4ParseViewModel _cue4Parse;
private readonly FileItem _mainKey = new("Main Static Key", 0) {Guid = Constants.ZERO_GUID}; // just so main key gets refreshed in the ui
public AesManagerViewModel(CUE4ParseViewModel cue4Parse)
public async Task InitAes()
{
await _threadWorkerView.Begin(_ =>
{
_cue4Parse = cue4Parse;
HasChange = false;
}
public async Task InitAes()
{
await _threadWorkerView.Begin(_ =>
if (_cue4Parse.Game == FGame.Unknown &&
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
{
if (_cue4Parse.Game == FGame.Unknown &&
UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings))
{
_keysFromSettings = settings.AesKeys;
}
else
{
_keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game];
}
_keysFromSettings ??= new AesResponse
{
MainKey = string.Empty,
DynamicKeys = null
};
_mainKey.Key = FixKey(_keysFromSettings.MainKey);
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
AesKeysView = new ListCollectionView(AesKeys) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}};
});
}
private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
if (e.PropertyName != "Key" || sender is not FullyObservableCollection<FileItem> collection)
return;
var key = FixKey(collection[e.CollectionIndex].Key);
if (e.CollectionIndex == 0)
{
if (!HasChange)
HasChange = FixKey(_keysFromSettings.MainKey) != key;
_keysFromSettings.MainKey = key;
}
else if (!_keysFromSettings.HasDynamicKeys)
{
HasChange = true;
_keysFromSettings.DynamicKeys = new List<DynamicKey>
{
new()
{
Key = key,
FileName = collection[e.CollectionIndex].Name,
Guid = collection[e.CollectionIndex].Guid.ToString()
}
};
}
else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d)
{
if (!HasChange)
HasChange = FixKey(d.Key) != key;
d.Key = key;
_keysFromSettings = settings.AesKeys;
}
else
{
HasChange = true;
_keysFromSettings.DynamicKeys.Add(new DynamicKey
_keysFromSettings = UserSettings.Default.AesKeys[_cue4Parse.Game];
}
_keysFromSettings ??= new AesResponse
{
MainKey = string.Empty,
DynamicKeys = null
};
_mainKey.Key = FixKey(_keysFromSettings.MainKey);
AesKeys = new FullyObservableCollection<FileItem>(EnumerateAesKeys());
AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged;
AesKeysView = new ListCollectionView(AesKeys) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
});
}
private void AesKeysOnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs e)
{
if (e.PropertyName != "Key" || sender is not FullyObservableCollection<FileItem> collection)
return;
var key = FixKey(collection[e.CollectionIndex].Key);
if (e.CollectionIndex == 0)
{
if (!HasChange)
HasChange = FixKey(_keysFromSettings.MainKey) != key;
_keysFromSettings.MainKey = key;
}
else if (!_keysFromSettings.HasDynamicKeys)
{
HasChange = true;
_keysFromSettings.DynamicKeys = new List<DynamicKey>
{
new()
{
Key = key,
FileName = collection[e.CollectionIndex].Name,
Guid = collection[e.CollectionIndex].Guid.ToString()
});
}
}
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !HasChange) return;
_cue4Parse.ClearProvider();
await _cue4Parse.LoadVfs(AesKeys);
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
Log.Information("{@Json}", UserSettings.Default);
}
private string FixKey(string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
if (key.StartsWith("0x"))
key = key[2..];
return "0x" + key.ToUpper().Trim();
}
private IEnumerable<FileItem> EnumerateAesKeys()
{
yield return _mainKey;
_uniqueGuids = new HashSet<FGuid> {Constants.ZERO_GUID};
var hasDynamicKeys = _keysFromSettings.HasDynamicKeys;
foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles)
{
if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid))
continue;
var k = string.Empty;
if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey)
{
k = dynamicKey.Key;
}
};
}
else if (_keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == collection[e.CollectionIndex].Guid.ToString()) is { } d)
{
if (!HasChange)
HasChange = FixKey(d.Key) != key;
file.Key = FixKey(k);
yield return file;
}
d.Key = key;
}
else
{
HasChange = true;
_keysFromSettings.DynamicKeys.Add(new DynamicKey
{
Key = key,
FileName = collection[e.CollectionIndex].Name,
Guid = collection[e.CollectionIndex].Guid.ToString()
});
}
}
}
public async Task UpdateProvider(bool isLaunch)
{
if (!isLaunch && !HasChange) return;
_cue4Parse.ClearProvider();
await _cue4Parse.LoadVfs(AesKeys);
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
Log.Information("{@Json}", UserSettings.Default);
}
private string FixKey(string key)
{
if (string.IsNullOrEmpty(key))
return string.Empty;
if (key.StartsWith("0x"))
key = key[2..];
return "0x" + key.ToUpper().Trim();
}
private IEnumerable<FileItem> EnumerateAesKeys()
{
yield return _mainKey;
_uniqueGuids = new HashSet<FGuid> { Constants.ZERO_GUID };
var hasDynamicKeys = _keysFromSettings.HasDynamicKeys;
foreach (var file in _cue4Parse.GameDirectory.DirectoryFiles)
{
if (file.Guid == Constants.ZERO_GUID || !_uniqueGuids.Add(file.Guid))
continue;
var k = string.Empty;
if (hasDynamicKeys && _keysFromSettings.DynamicKeys.FirstOrDefault(x => x.Guid == file.Guid.ToString()) is { } dynamicKey)
{
k = dynamicKey.Key;
}
file.Key = FixKey(k);
yield return file;
}
}
}

View File

@@ -2,29 +2,28 @@
using FModel.ViewModels.ApiEndpoints;
using RestSharp;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class ApiEndpointViewModel
{
public class ApiEndpointViewModel
private readonly IRestClient _client = new RestClient
{
private readonly IRestClient _client = new RestClient
{
UserAgent = $"FModel/{Constants.APP_VERSION}",
Timeout = 3 * 1000
}.UseSerializer<JsonNetSerializer>();
UserAgent = $"FModel/{Constants.APP_VERSION}",
Timeout = 3 * 1000
}.UseSerializer<JsonNetSerializer>();
public FortniteApiEndpoint FortniteApi { get; }
public ValorantApiEndpoint ValorantApi { get; }
public BenbotApiEndpoint BenbotApi { get; }
public EpicApiEndpoint EpicApi { get; }
public FModelApi FModelApi { get; }
public FortniteApiEndpoint FortniteApi { get; }
public ValorantApiEndpoint ValorantApi { get; }
public BenbotApiEndpoint BenbotApi { get; }
public EpicApiEndpoint EpicApi { get; }
public FModelApi FModelApi { get; }
public ApiEndpointViewModel()
{
FortniteApi = new FortniteApiEndpoint(_client);
ValorantApi = new ValorantApiEndpoint(_client);
BenbotApi = new BenbotApiEndpoint(_client);
EpicApi = new EpicApiEndpoint(_client);
FModelApi = new FModelApi(_client);
}
public ApiEndpointViewModel()
{
FortniteApi = new FortniteApiEndpoint(_client);
ValorantApi = new ValorantApiEndpoint(_client);
BenbotApi = new BenbotApiEndpoint(_client);
EpicApi = new EpicApiEndpoint(_client);
FModelApi = new FModelApi(_client);
}
}

View File

@@ -1,14 +1,13 @@
using RestSharp;
namespace FModel.ViewModels.ApiEndpoints
{
public abstract class AbstractApiProvider
{
protected readonly IRestClient _client;
namespace FModel.ViewModels.ApiEndpoints;
public AbstractApiProvider(IRestClient client)
{
_client = client;
}
public abstract class AbstractApiProvider
{
protected readonly IRestClient _client;
protected AbstractApiProvider(IRestClient client)
{
_client = client;
}
}

View File

@@ -6,73 +6,72 @@ using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using Serilog;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class BenbotApiEndpoint : AbstractApiProvider
{
public class BenbotApiEndpoint : AbstractApiProvider
public BenbotApiEndpoint(IRestClient client) : base(client)
{
public BenbotApiEndpoint(IRestClient client) : base(client)
{
}
}
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token)
{
var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET)
{
var request = new RestRequest("https://benbot.app/api/v2/aes", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<AesResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<AesResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public AesResponse GetAesKeys(CancellationToken token)
{
return GetAesKeysAsync(token).GetAwaiter().GetResult();
}
public AesResponse GetAesKeys(CancellationToken token)
{
return GetAesKeysAsync(token).GetAwaiter().GetResult();
}
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token)
{
var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET)
{
var request = new RestRequest("https://benbot.app/api/v1/mappings", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<MappingsResponse[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
var response = await _client.ExecuteAsync<MappingsResponse[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public MappingsResponse[] GetMappings(CancellationToken token)
{
return GetMappingsAsync(token).GetAwaiter().GetResult();
}
public MappingsResponse[] GetMappings(CancellationToken token)
{
return GetMappingsAsync(token).GetAwaiter().GetResult();
}
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en-US")
{
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
{
var request = new RestRequest("https://benbot.app/api/v1/hotfixes", Method.GET)
{
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
request.AddParameter("lang", language);
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int)response.StatusCode, request.Resource);
return response.Data;
}
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
};
request.AddParameter("lang", language);
var response = await _client.ExecuteAsync<Dictionary<string, Dictionary<string, string>>>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
{
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
}
public Dictionary<string, Dictionary<string, string>> GetHotfixes(CancellationToken token, string language = "en-US")
{
return GetHotfixesAsync(token, language).GetAwaiter().GetResult();
}
public async Task DownloadFileAsync(string fileLink, string installationPath)
{
var request = new RestRequest(fileLink, Method.GET);
var data = _client.DownloadData(request);
await File.WriteAllBytesAsync(installationPath, data);
}
public async Task DownloadFileAsync(string fileLink, string installationPath)
{
var request = new RestRequest(fileLink, Method.GET);
var data = _client.DownloadData(request);
await File.WriteAllBytesAsync(installationPath, data);
}
public void DownloadFile(string fileLink, string installationPath)
{
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
}
public void DownloadFile(string fileLink, string installationPath)
{
DownloadFileAsync(fileLink, installationPath).GetAwaiter().GetResult();
}
}

View File

@@ -7,55 +7,54 @@ using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using Serilog;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class EpicApiEndpoint : AbstractApiProvider
{
public class EpicApiEndpoint : AbstractApiProvider
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
public EpicApiEndpoint(IRestClient client) : base(client)
{
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
}
public EpicApiEndpoint(IRestClient client) : base(client)
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
{
if (IsExpired())
{
}
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
{
if (IsExpired())
var auth = await GetAuthAsync(token);
if (auth != null)
{
var auth = await GetAuthAsync(token);
if (auth != null)
{
UserSettings.Default.LastAuthResponse = auth;
}
UserSettings.Default.LastAuthResponse = auth;
}
var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return new ManifestInfo(response.Content);
}
public ManifestInfo GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
var request = new RestRequest(_LAUNCHER_ASSETS, Method.GET);
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return new ManifestInfo(response.Content);
}
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
{
var request = new RestRequest(_OAUTH_URL, Method.POST);
request.AddHeader("Authorization", _BASIC_TOKEN);
request.AddParameter("grant_type", "client_credentials");
var response = await _client.ExecuteAsync<AuthResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public ManifestInfo GetManifest(CancellationToken token)
{
return GetManifestAsync(token).GetAwaiter().GetResult();
}
private bool IsExpired()
{
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
}
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
{
var request = new RestRequest(_OAUTH_URL, Method.POST);
request.AddHeader("Authorization", _BASIC_TOKEN);
request.AddParameter("grant_type", "client_credentials");
var response = await _client.ExecuteAsync<AuthResponse>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
private bool IsExpired()
{
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
}
}

View File

@@ -17,169 +17,168 @@ using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class FModelApi : AbstractApiProvider
{
public class FModelApi : AbstractApiProvider
private News _news;
private Info _infos;
private Backup[] _backups;
private Game _game;
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public FModelApi(IRestClient client) : base(client)
{
private News _news;
private Info _infos;
private Backup[] _backups;
private Game _game;
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
}
public FModelApi(IRestClient client) : base(client)
{
}
public async Task<News> GetNewsAsync(CancellationToken token)
{
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET);
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<News> GetNewsAsync(CancellationToken token)
{
var request = new RestRequest($"https://api.fmodel.app/v1/news/{Constants.APP_VERSION}", Method.GET);
var response = await _client.ExecuteAsync<News>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public News GetNews(CancellationToken token)
{
return _news ??= GetNewsAsync(token).GetAwaiter().GetResult();
}
public News GetNews(CancellationToken token)
{
return _news ??= GetNewsAsync(token).GetAwaiter().GetResult();
}
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
{
var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET);
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
{
var request = new RestRequest($"https://api.fmodel.app/v1/infos/{updateMode}", Method.GET);
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
{
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
}
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
{
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
}
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<Backup[]> GetBackupsAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/backups/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Backup[]>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Backup[] GetBackups(CancellationToken token, string gameName)
{
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
}
public Backup[] GetBackups(CancellationToken token, string gameName)
{
return _backups ??= GetBackupsAsync(token, gameName).GetAwaiter().GetResult();
}
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public async Task<Game> GetGamesAsync(CancellationToken token, string gameName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/games/{gameName}", Method.GET);
var response = await _client.ExecuteAsync<Game>(request, token).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data;
}
public Game GetGames(CancellationToken token, string gameName)
{
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
}
public Game GetGames(CancellationToken token, string gameName)
{
return _game ??= GetGamesAsync(token, gameName).GetAwaiter().GetResult();
}
public async Task<CommunityDesign> GetDesignAsync(string designName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET);
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data != null ? new CommunityDesign(response.Data) : null;
}
public async Task<CommunityDesign> GetDesignAsync(string designName)
{
var request = new RestRequest($"https://api.fmodel.app/v1/designs/{designName}", Method.GET);
var response = await _client.ExecuteAsync<Community>(request).ConfigureAwait(false);
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, request.Resource);
return response.Data != null ? new CommunityDesign(response.Data) : null;
}
public CommunityDesign GetDesign(string designName)
{
if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null)
return communityDesign;
communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult();
_communityDesigns[designName] = communityDesign;
public CommunityDesign GetDesign(string designName)
{
if (_communityDesigns.TryGetValue(designName, out var communityDesign) && communityDesign != null)
return communityDesign;
}
public void CheckForUpdates(EUpdateMode updateMode)
{
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
}
communityDesign = GetDesignAsync(designName).GetAwaiter().GetResult();
_communityDesigns[designName] = communityDesign;
return communityDesign;
}
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
public void CheckForUpdates(EUpdateMode updateMode)
{
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
}
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
{
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
if (_infos != null)
{
_infos = JsonConvert.DeserializeObject<Info>(args.RemoteData);
if (_infos != null)
args.UpdateInfo = new UpdateInfoEventArgs
{
args.UpdateInfo = new UpdateInfoEventArgs
{
CurrentVersion = _infos.Version,
ChangelogURL = _infos.ChangelogUrl,
DownloadURL = _infos.DownloadUrl
};
}
}
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args is {CurrentVersion: { }})
{
var currentVersion = new System.Version(args.CurrentVersion);
if (currentVersion == args.InstalledVersion)
{
if (UserSettings.Default.ShowChangelog)
ShowChangelog(args);
return;
}
var downgrade = currentVersion < args.InstalledVersion;
var messageBox = new MessageBoxModel
{
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
Icon = MessageBoxImage.Question,
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result != MessageBoxResult.Yes) return;
try
{
if (AutoUpdater.DownloadUpdate(args))
{
UserSettings.Default.ShowChangelog = true;
Application.Current.Shutdown();
}
}
catch (Exception exception)
{
UserSettings.Default.ShowChangelog = false;
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
MessageBox.Show(
"There is a problem reaching the update server, please check your internet connection or try again later.",
"Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ShowChangelog(UpdateInfoEventArgs args)
{
var request = new RestRequest(args.ChangelogURL, Method.GET);
var response = _client.Execute(request);
if (string.IsNullOrEmpty(response.Content)) return;
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
UserSettings.Default.ShowChangelog = false;
CurrentVersion = _infos.Version,
ChangelogURL = _infos.ChangelogUrl,
DownloadURL = _infos.DownloadUrl
};
}
}
}
private void CheckForUpdateEvent(UpdateInfoEventArgs args)
{
if (args is { CurrentVersion: { } })
{
var currentVersion = new System.Version(args.CurrentVersion);
if (currentVersion == args.InstalledVersion)
{
if (UserSettings.Default.ShowChangelog)
ShowChangelog(args);
return;
}
var downgrade = currentVersion < args.InstalledVersion;
var messageBox = new MessageBoxModel
{
Text = $"The latest version of FModel {UserSettings.Default.UpdateMode} is {args.CurrentVersion}. You are using version {args.InstalledVersion}. Do you want to {(downgrade ? "downgrade" : "update")} the application now?",
Caption = $"{(downgrade ? "Downgrade" : "Update")} Available",
Icon = MessageBoxImage.Question,
Buttons = MessageBoxButtons.YesNo(),
IsSoundEnabled = false
};
MessageBox.Show(messageBox);
if (messageBox.Result != MessageBoxResult.Yes) return;
try
{
if (AutoUpdater.DownloadUpdate(args))
{
UserSettings.Default.ShowChangelog = true;
Application.Current.Shutdown();
}
}
catch (Exception exception)
{
UserSettings.Default.ShowChangelog = false;
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
}
}
else
{
MessageBox.Show(
"There is a problem reaching the update server, please check your internet connection or try again later.",
"Update Check Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ShowChangelog(UpdateInfoEventArgs args)
{
var request = new RestRequest(args.ChangelogURL, Method.GET);
var response = _client.Execute(request);
if (string.IsNullOrEmpty(response.Content)) return;
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
UserSettings.Default.ShowChangelog = false;
}
}

View File

@@ -3,31 +3,30 @@ using FModel.ViewModels.ApiEndpoints.Models;
using RestSharp;
using System.Threading.Tasks;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class FortniteApiEndpoint : AbstractApiProvider
{
public class FortniteApiEndpoint : AbstractApiProvider
public FortniteApiEndpoint(IRestClient client) : base(client)
{
public FortniteApiEndpoint(IRestClient client) : base(client)
{
}
}
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
{
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET);
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
return response.Data;
}
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
{
var request = new RestRequest($"https://fortnite-api.com/v1/playlists/{playlistId}", Method.GET);
var response = await _client.ExecuteAsync<PlaylistResponse>(request).ConfigureAwait(false);
return response.Data;
}
public PlaylistResponse GetPlaylist(string playlistId)
{
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
}
public PlaylistResponse GetPlaylist(string playlistId)
{
return GetPlaylistAsync(playlistId).GetAwaiter().GetResult();
}
public bool TryGetBytes(Uri link, out byte[] data)
{
var request = new RestRequest(link, Method.GET);
data = _client.DownloadData(request);
return data != null;
}
public bool TryGetBytes(Uri link, out byte[] data)
{
var request = new RestRequest(link, Method.GET);
data = _client.DownloadData(request);
return data != null;
}
}

View File

@@ -2,23 +2,22 @@
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(Version) + "}")]
public class AesResponse
{
[DebuggerDisplay("{" + nameof(Version) + "}")]
public class AesResponse
{
[J("version")] public string Version { get; private set; }
[J("mainKey")] public string MainKey { get; set; }
[J("dynamicKeys")] public List<DynamicKey> DynamicKeys { get; set; }
[J("version")] public string Version { get; private set; }
[J("mainKey")] public string MainKey { get; set; }
[J("dynamicKeys")] public List<DynamicKey> DynamicKeys { get; set; }
public bool HasDynamicKeys => DynamicKeys is {Count: > 0};
}
public bool HasDynamicKeys => DynamicKeys is { Count: > 0 };
}
[DebuggerDisplay("{" + nameof(Key) + "}")]
public class DynamicKey
{
[J("fileName")] public string FileName { get; set; }
[J("guid")] public string Guid { get; set; }
[J("key")] public string Key { get; set; }
}
[DebuggerDisplay("{" + nameof(Key) + "}")]
public class DynamicKey
{
[J("fileName")] public string FileName { get; set; }
[J("guid")] public string Guid { get; set; }
[J("key")] public string Key { get; set; }
}

View File

@@ -2,12 +2,11 @@
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(AccessToken) + "}")]
public class AuthResponse
{
[DebuggerDisplay("{" + nameof(AccessToken) + "}")]
public class AuthResponse
{
[J("access_token")] public string AccessToken { get; set; }
[J("expires_at")] public DateTime ExpiresAt { get; set; }
}
[J("access_token")] public string AccessToken { get; set; }
[J("expires_at")] public DateTime ExpiresAt { get; set; }
}

View File

@@ -6,193 +6,193 @@ using FModel.Extensions;
using SkiaSharp;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(Messages) + "}")]
public class News
{
[DebuggerDisplay("{" + nameof(Messages) + "}")]
public class News
{
[J] public string[] Messages { get; private set; }
[J] public string[] Colors { get; private set; }
[J] public string[] NewLines { get; private set; }
}
[J] public string[] Messages { get; private set; }
[J] public string[] Colors { get; private set; }
[J] public string[] NewLines { get; private set; }
}
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class Backup
{
[J] public string GameName { get; private set; }
[J] public string FileName { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public long FileSize { get; private set; }
}
[DebuggerDisplay("{" + nameof(DisplayName) + "}")]
public class Game
{
[J] public string DisplayName { get; private set; }
[J] public Dictionary<string, Version> Versions { get; private set; }
}
[DebuggerDisplay("{" + nameof(GameEnum) + "}")]
public class Version
{
[J("game")] public string GameEnum { get; private set; }
[J] public int UeVer { get; private set; }
[J] public Dictionary<string, int> CustomVersions { get; private set; }
[J] public Dictionary<string, bool> Options { get; private set; }
}
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class Backup
{
[J] public string GameName { get; private set; }
[J] public string FileName { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public long FileSize { get; private set; }
}
[DebuggerDisplay("{" + nameof(Mode) + "}")]
public class Info
{
[J] public string Mode { get; private set; }
[J] public string Version { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public string ChangelogUrl { get; private set; }
[J] public string CommunityDesign { get; private set; }
[J] public string CommunityPreview { get; private set; }
}
[DebuggerDisplay("{" + nameof(DisplayName) + "}")]
public class Game
{
[J] public string DisplayName { get; private set; }
[J] public Dictionary<string, Version> Versions { get; private set; }
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class Community
{
[J] public string Name { get; private set; }
[J] public bool DrawSource { get; private set; }
[J] public bool DrawSeason { get; private set; }
[J] public bool DrawSeasonShort { get; private set; }
[J] public bool DrawSet { get; private set; }
[J] public bool DrawSetShort { get; private set; }
[J] public IDictionary<string, Font> Fonts { get; private set; }
[J] public GameplayTag GameplayTags { get; private set; }
[J] public IDictionary<string, Rarity> Rarities { get; private set; }
}
[DebuggerDisplay("{" + nameof(GameEnum) + "}")]
public class Version
{
[J("game")] public string GameEnum { get; private set; }
[J] public int UeVer { get; private set; }
[J] public Dictionary<string, int> CustomVersions { get; private set; }
[J] public Dictionary<string, bool> Options { get; private set; }
}
public class Font
{
[J] public IDictionary<string, string> Typeface { get; private set; }
[J] public float FontSize { get; private set; }
[J] public float FontScale { get; private set; }
[J] public string FontColor { get; private set; }
[J] public float SkewValue { get; private set; }
[J] public byte ShadowValue { get; private set; }
[J] public int MaxLineCount { get; private set; }
[J] public string Alignment { get; private set; }
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
}
[DebuggerDisplay("{" + nameof(Mode) + "}")]
public class Info
{
[J] public string Mode { get; private set; }
[J] public string Version { get; private set; }
[J] public string DownloadUrl { get; private set; }
[J] public string ChangelogUrl { get; private set; }
[J] public string CommunityDesign { get; private set; }
[J] public string CommunityPreview { get; private set; }
}
public class FontDesign
{
[J] public IDictionary<ELanguage, string> Typeface { get; set; }
[J] public float FontSize { get; set; }
[J] public float FontScale { get; set; }
[J] public SKColor FontColor { get; set; }
[J] public float SkewValue { get; set; }
[J] public byte ShadowValue { get; set; }
[J] public int MaxLineCount { get; set; }
[J] public SKTextAlign Alignment { get; set; }
[J] public int X { get; set; }
[J] public int Y { get; set; }
}
[DebuggerDisplay("{" + nameof(Name) + "}")]
public class Community
{
[J] public string Name { get; private set; }
[J] public bool DrawSource { get; private set; }
[J] public bool DrawSeason { get; private set; }
[J] public bool DrawSeasonShort { get; private set; }
[J] public bool DrawSet { get; private set; }
[J] public bool DrawSetShort { get; private set; }
[J] public IDictionary<string, Font> Fonts { get; private set; }
[J] public GameplayTag GameplayTags { get; private set; }
[J] public IDictionary<string, Rarity> Rarities { get; private set; }
}
public class GameplayTag
{
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
[J] public bool DrawCustomOnly { get; private set; }
[J] public string Custom { get; private set; }
[J] public IDictionary<string, string> Tags { get; private set; }
}
public class Font
{
[J] public IDictionary<string, string> Typeface { get; private set; }
[J] public float FontSize { get; private set; }
[J] public float FontScale { get; private set; }
[J] public string FontColor { get; private set; }
[J] public float SkewValue { get; private set; }
[J] public byte ShadowValue { get; private set; }
[J] public int MaxLineCount { get; private set; }
[J] public string Alignment { get; private set; }
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
}
public class GameplayTagDesign
{
[J] public int X { get; set; }
[J] public int Y { get; set; }
[J] public bool DrawCustomOnly { get; set; }
[J] public SKBitmap Custom { get; set; }
[J] public IDictionary<string, SKBitmap> Tags { get; set; }
}
public class FontDesign
{
[J] public IDictionary<ELanguage, string> Typeface { get; set; }
[J] public float FontSize { get; set; }
[J] public float FontScale { get; set; }
[J] public SKColor FontColor { get; set; }
[J] public float SkewValue { get; set; }
[J] public byte ShadowValue { get; set; }
[J] public int MaxLineCount { get; set; }
[J] public SKTextAlign Alignment { get; set; }
[J] public int X { get; set; }
[J] public int Y { get; set; }
}
public class Rarity
{
[J] public string Background { get; private set; }
[J] public string Upper { get; private set; }
[J] public string Lower { get; private set; }
}
public class GameplayTag
{
[J] public int X { get; private set; }
[J] public int Y { get; private set; }
[J] public bool DrawCustomOnly { get; private set; }
[J] public string Custom { get; private set; }
[J] public IDictionary<string, string> Tags { get; private set; }
}
public class RarityDesign
{
[J] public SKBitmap Background { get; set; }
[J] public SKBitmap Upper { get; set; }
[J] public SKBitmap Lower { get; set; }
}
public class GameplayTagDesign
{
[J] public int X { get; set; }
[J] public int Y { get; set; }
[J] public bool DrawCustomOnly { get; set; }
[J] public SKBitmap Custom { get; set; }
[J] public IDictionary<string, SKBitmap> Tags { get; set; }
}
public class CommunityDesign
{
public bool DrawSource { get; }
public bool DrawSeason { get; }
public bool DrawSeasonShort { get; }
public bool DrawSet { get; }
public bool DrawSetShort { get; }
public IDictionary<string, FontDesign> Fonts { get; }
public GameplayTagDesign GameplayTags { get; }
public IDictionary<string, RarityDesign> Rarities { get; }
public class Rarity
{
[J] public string Background { get; private set; }
[J] public string Upper { get; private set; }
[J] public string Lower { get; private set; }
}
public CommunityDesign(Community response)
public class RarityDesign
{
[J] public SKBitmap Background { get; set; }
[J] public SKBitmap Upper { get; set; }
[J] public SKBitmap Lower { get; set; }
}
public class CommunityDesign
{
public bool DrawSource { get; }
public bool DrawSeason { get; }
public bool DrawSeasonShort { get; }
public bool DrawSet { get; }
public bool DrawSetShort { get; }
public IDictionary<string, FontDesign> Fonts { get; }
public GameplayTagDesign GameplayTags { get; }
public IDictionary<string, RarityDesign> Rarities { get; }
public CommunityDesign(Community response)
{
DrawSource = response.DrawSource;
DrawSeason = response.DrawSeason;
DrawSeasonShort = response.DrawSeasonShort;
DrawSet = response.DrawSet;
DrawSetShort = response.DrawSetShort;
Fonts = new Dictionary<string, FontDesign>();
foreach (var (k, font) in response.Fonts)
{
DrawSource = response.DrawSource;
DrawSeason = response.DrawSeason;
DrawSeasonShort = response.DrawSeasonShort;
DrawSet = response.DrawSet;
DrawSetShort = response.DrawSetShort;
Fonts = new Dictionary<string, FontDesign>();
foreach (var (k, font) in response.Fonts)
var typeface = new Dictionary<ELanguage, string>();
foreach (var (key, value) in font.Typeface)
{
var typeface = new Dictionary<ELanguage, string>();
foreach (var (key, value) in font.Typeface)
{
typeface[key.ToEnum(ELanguage.English)] = value;
}
Fonts[k] = new FontDesign
{
Typeface = typeface,
FontSize = font.FontSize,
FontScale = font.FontScale,
FontColor = SKColor.Parse(font.FontColor),
SkewValue = font.SkewValue,
ShadowValue = font.ShadowValue,
MaxLineCount = font.MaxLineCount,
Alignment = font.Alignment.ToEnum(SKTextAlign.Center),
X = font.X,
Y = font.Y
};
typeface[key.ToEnum(ELanguage.English)] = value;
}
var tags = new Dictionary<string, SKBitmap>();
foreach (var (key, value) in response.GameplayTags.Tags)
Fonts[k] = new FontDesign
{
tags[key] = Utils.GetB64Bitmap(value);
}
GameplayTags = new GameplayTagDesign
{
X = response.GameplayTags.X,
Y = response.GameplayTags.Y,
DrawCustomOnly = response.GameplayTags.DrawCustomOnly,
Custom = Utils.GetB64Bitmap(response.GameplayTags.Custom),
Tags = tags
Typeface = typeface,
FontSize = font.FontSize,
FontScale = font.FontScale,
FontColor = SKColor.Parse(font.FontColor),
SkewValue = font.SkewValue,
ShadowValue = font.ShadowValue,
MaxLineCount = font.MaxLineCount,
Alignment = font.Alignment.ToEnum(SKTextAlign.Center),
X = font.X,
Y = font.Y
};
}
Rarities = new Dictionary<string, RarityDesign>();
foreach (var (key, value) in response.Rarities)
var tags = new Dictionary<string, SKBitmap>();
foreach (var (key, value) in response.GameplayTags.Tags)
{
tags[key] = Utils.GetB64Bitmap(value);
}
GameplayTags = new GameplayTagDesign
{
X = response.GameplayTags.X,
Y = response.GameplayTags.Y,
DrawCustomOnly = response.GameplayTags.DrawCustomOnly,
Custom = Utils.GetB64Bitmap(response.GameplayTags.Custom),
Tags = tags
};
Rarities = new Dictionary<string, RarityDesign>();
foreach (var (key, value) in response.Rarities)
{
Rarities[key] = new RarityDesign
{
Rarities[key] = new RarityDesign
{
Background = Utils.GetB64Bitmap(value.Background),
Upper = Utils.GetB64Bitmap(value.Upper),
Lower = Utils.GetB64Bitmap(value.Lower)
};
}
Background = Utils.GetB64Bitmap(value.Background),
Upper = Utils.GetB64Bitmap(value.Upper),
Lower = Utils.GetB64Bitmap(value.Lower)
};
}
}
}

View File

@@ -1,23 +1,22 @@
using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
{
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class MappingsResponse
{
[J] public string Url { get; private set; }
[J] public string FileName { get; private set; }
[J] public string Hash { get; private set; }
[J] public long Length { get; private set; }
[J] public string Uploaded { get; private set; }
[J] public Meta Meta { get; private set; }
}
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(CompressionMethod) + "}")]
public class Meta
{
[J] public string Version { get; private set; }
[J] public string CompressionMethod { get; private set; }
}
[DebuggerDisplay("{" + nameof(FileName) + "}")]
public class MappingsResponse
{
[J] public string Url { get; private set; }
[J] public string FileName { get; private set; }
[J] public string Hash { get; private set; }
[J] public long Length { get; private set; }
[J] public string Uploaded { get; private set; }
[J] public Meta Meta { get; private set; }
}
[DebuggerDisplay("{" + nameof(CompressionMethod) + "}")]
public class Meta
{
[J] public string Version { get; private set; }
[J] public string CompressionMethod { get; private set; }
}

View File

@@ -3,34 +3,33 @@ using System.Diagnostics;
using J = Newtonsoft.Json.JsonPropertyAttribute;
using I = Newtonsoft.Json.JsonIgnoreAttribute;
namespace FModel.ViewModels.ApiEndpoints.Models
namespace FModel.ViewModels.ApiEndpoints.Models;
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
public class PlaylistResponse
{
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")]
public class PlaylistResponse
{
[J] public int Status { get; private set; }
[J] public Playlist Data { get; private set; }
[J] public string Error { get; private set; }
[J] public int Status { get; private set; }
[J] public Playlist Data { get; private set; }
[J] public string Error { get; private set; }
public bool IsSuccess => Status == 200;
public bool HasError => Error != null;
public bool IsSuccess => Status == 200;
public bool HasError => Error != null;
private object DebuggerDisplay => IsSuccess ? Data : $"Error: {Status} | {Error}";
}
private object DebuggerDisplay => IsSuccess ? Data : $"Error: {Status} | {Error}";
}
[DebuggerDisplay("{" + nameof(Id) + "}")]
public class Playlist
{
[J] public string Id { get; private set; }
[J] public PlaylistImages Images { get; private set; }
}
[DebuggerDisplay("{" + nameof(Id) + "}")]
public class Playlist
{
[J] public string Id { get; private set; }
[J] public PlaylistImages Images { get; private set; }
}
public class PlaylistImages
{
[J] public Uri Showcase { get; private set; }
[J] public Uri MissionIcon { get; private set; }
public class PlaylistImages
{
[J] public Uri Showcase { get; private set; }
[J] public Uri MissionIcon { get; private set; }
[I] public bool HasShowcase => Showcase != null;
[I] public bool HasMissionIcon => MissionIcon != null;
}
[I] public bool HasShowcase => Showcase != null;
[I] public bool HasMissionIcon => MissionIcon != null;
}

View File

@@ -1,12 +1,8 @@
using CUE4Parse.UE4.Exceptions;
using CUE4Parse.UE4.Readers;
using FModel.Settings;
using Ionic.Zlib;
using RestSharp;
using System;
using System.Collections.Generic;
using System.IO;
@@ -18,295 +14,300 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FModel.ViewModels.ApiEndpoints
namespace FModel.ViewModels.ApiEndpoints;
public class ValorantApiEndpoint : AbstractApiProvider
{
public class ValorantApiEndpoint : AbstractApiProvider
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
public ValorantApiEndpoint(IRestClient client) : base(client)
{
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
public ValorantApiEndpoint(IRestClient client) : base(client) { }
public async Task<VManifest> GetManifestAsync(CancellationToken token)
{
var request = new RestRequest(_URL, Method.GET);
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
return new VManifest(response.RawBytes);
}
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
}
public class VManifest
public async Task<VManifest> GetManifestAsync(CancellationToken token)
{
private readonly HttpClient _client;
public readonly VHeader Header;
public readonly VChunk[] Chunks;
public readonly VPak[] Paks;
var request = new RestRequest(_URL, Method.GET);
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
return new VManifest(response.RawBytes);
}
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data)) { }
public VManifest GetManifest(CancellationToken token) => GetManifestAsync(token).GetAwaiter().GetResult();
}
private VManifest(FArchive Ar)
public class VManifest
{
private readonly HttpClient _client;
public readonly VHeader Header;
public readonly VChunk[] Chunks;
public readonly VPak[] Paks;
public VManifest(byte[] data) : this(new FByteArchive("CompressedValorantManifest", data))
{
}
private VManifest(FArchive Ar)
{
using (Ar)
{
using (Ar)
{
Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
if (uncompressedBuffer.Length != Header.UncompressedSize)
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
Header = new VHeader(Ar);
var compressedBuffer = Ar.ReadBytes((int) Header.CompressedSize);
var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer);
if (uncompressedBuffer.Length != Header.UncompressedSize)
throw new ParserException(Ar, $"Decompression failed, {uncompressedBuffer.Length} != {Header.UncompressedSize}");
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
using var manifest = new FByteArchive("UncompressedValorantManifest", uncompressedBuffer);
Chunks = manifest.ReadArray<VChunk>((int) Header.ChunkCount);
Paks = manifest.ReadArray((int) Header.PakCount, () => new VPak(manifest));
if (manifest.Position != manifest.Length)
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
}
_client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false,
MaxConnectionsPerServer = 1337,
UseDefaultCredentials = false,
AllowAutoRedirect = false
});
if (manifest.Position != manifest.Length)
throw new ParserException(manifest, $"Parsing failed, {manifest.Position} != {manifest.Length}");
}
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
_client = new HttpClient(new HttpClientHandler
{
UseProxy = false,
UseCookies = false,
AutomaticDecompression = DecompressionMethods.All,
CheckCertificateRevocationList = false,
PreAuthenticate = false,
MaxConnectionsPerServer = 1337,
UseDefaultCredentials = false,
AllowAutoRedirect = false
});
}
public async ValueTask PrefetchChunk(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
if (File.Exists(chunkPath)) return;
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
byte[] chunkBytes;
if (File.Exists(chunkPath))
{
chunkBytes = new byte[chunk.Size];
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
}
else
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
if (File.Exists(chunkPath)) return;
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
await using var fileStream = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
}
}
public async Task<byte[]> GetChunkBytes(VChunk chunk, CancellationToken cancellationToken)
{
var chunkPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", $"{chunk.Id}.chunk");
byte[] chunkBytes;
if (File.Exists(chunkPath))
{
chunkBytes = new byte[chunk.Size];
await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read);
await fs.ReadAsync(chunkBytes, cancellationToken).ConfigureAwait(false);
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
else
{
using var response = await _client.GetAsync(chunk.GetUrl(), cancellationToken).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.OK)
{
chunkBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read);
await response.Content.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
else
{
chunkBytes = null; // Maybe add logging?
}
chunkBytes = null; // Maybe add logging?
}
return chunkBytes;
}
public Stream GetPakStream(int index) => new VPakStream(this, index);
return chunkBytes;
}
public readonly struct VHeader
public Stream GetPakStream(int index) => new VPakStream(this, index);
}
public readonly struct VHeader
{
private const uint _MAGIC = 0xC3D088F7u;
public readonly uint Magic;
public readonly uint HeaderSize;
public readonly ulong ManifestId;
public readonly uint UncompressedSize;
public readonly uint CompressedSize;
public readonly uint ChunkCount;
public readonly uint PakCount;
public readonly string GameVersion;
public VHeader(FArchive Ar)
{
private const uint _MAGIC = 0xC3D088F7u;
Magic = Ar.Read<uint>();
public readonly uint Magic;
public readonly uint HeaderSize;
public readonly ulong ManifestId;
public readonly uint UncompressedSize;
public readonly uint CompressedSize;
public readonly uint ChunkCount;
public readonly uint PakCount;
public readonly string GameVersion;
if (Magic != _MAGIC)
throw new ParserException(Ar, "Invalid manifest magic");
public VHeader(FArchive Ar)
{
Magic = Ar.Read<uint>();
if (Magic != _MAGIC)
throw new ParserException(Ar, "Invalid manifest magic");
HeaderSize = Ar.Read<uint>();
ManifestId = Ar.Read<ulong>();
UncompressedSize = Ar.Read<uint>();
CompressedSize = Ar.Read<uint>();
ChunkCount = Ar.Read<uint>();
PakCount = Ar.Read<uint>();
var gameVersionLength = Ar.ReadByte();
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
Ar.Position = HeaderSize;
}
HeaderSize = Ar.Read<uint>();
ManifestId = Ar.Read<ulong>();
UncompressedSize = Ar.Read<uint>();
CompressedSize = Ar.Read<uint>();
ChunkCount = Ar.Read<uint>();
PakCount = Ar.Read<uint>();
var gameVersionLength = Ar.ReadByte();
GameVersion = gameVersionLength == 0 ? null : Encoding.ASCII.GetString(Ar.ReadBytes(gameVersionLength));
Ar.Position = HeaderSize;
}
}
public readonly struct VPak
public readonly struct VPak
{
public readonly ulong Id;
public readonly uint Size;
public readonly uint[] ChunkIndices;
public readonly string Name;
public VPak(FArchive Ar)
{
public readonly ulong Id;
public readonly uint Size;
public readonly uint[] ChunkIndices;
public readonly string Name;
public VPak(FArchive Ar)
{
Id = Ar.Read<ulong>();
Size = Ar.Read<uint>();
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
}
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
Id = Ar.Read<ulong>();
Size = Ar.Read<uint>();
ChunkIndices = Ar.ReadArray<uint>(Ar.Read<int>());
Name = Encoding.ASCII.GetString(Ar.ReadBytes(Ar.ReadByte()));
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct VChunk
public string GetFullName() => $"ValorantLive/ShooterGame/Content/Paks/{Name}";
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct VChunk
{
public readonly ulong Id;
public readonly uint Size;
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
}
public class VPakStream : Stream, ICloneable
{
private readonly VManifest _manifest;
private readonly int _pakIndex;
private readonly VChunk[] _chunks;
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
{
public readonly ulong Id;
public readonly uint Size;
_manifest = manifest;
_pakIndex = pakIndex;
_position = position;
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
var pak = manifest.Paks[pakIndex];
_chunks = new VChunk[pak.ChunkIndices.Length];
for (var i = 0; i < _chunks.Length; i++)
{
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
}
Length = pak.Size;
}
public class VPakStream : Stream, ICloneable
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
private readonly VManifest _manifest;
private readonly int _pakIndex;
private readonly VChunk[] _chunks;
var (i, startPos) = GetChunkIndex(_position);
if (i == -1) return 0;
public VPakStream(VManifest manifest, int pakIndex, long position = 0L)
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
var bytesRead = 0;
while (true)
{
_manifest = manifest;
_pakIndex = pakIndex;
_position = position;
var chunk = _chunks[i];
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
var chunkBytes = chunk.Size - startPos;
var bytesLeft = count - bytesRead;
var pak = manifest.Paks[pakIndex];
_chunks = new VChunk[pak.ChunkIndices.Length];
for (var i = 0; i < _chunks.Length; i++)
if (bytesLeft <= chunkBytes)
{
_chunks[i] = manifest.Chunks[pak.ChunkIndices[i]];
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint) bytesLeft);
bytesRead += bytesLeft;
break;
}
Length = pak.Size;
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
bytesRead += (int) chunkBytes;
startPos = 0u;
if (++i == _chunks.Length) break;
}
public object Clone() => new VPakStream(_manifest, _pakIndex, _position);
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var (i, startPos) = GetChunkIndex(_position);
if (i == -1) return 0;
await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false);
var bytesRead = 0;
while (true)
{
var chunk = _chunks[i];
var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false);
var chunkBytes = chunk.Size - startPos;
var bytesLeft = count - bytesRead;
if (bytesLeft <= chunkBytes)
{
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft);
bytesRead += bytesLeft;
break;
}
Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes);
bytesRead += (int)chunkBytes;
startPos = 0u;
if (++i == _chunks.Length) break;
}
_position += bytesRead;
return bytesRead;
}
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
var s = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
var chunk = _chunks[i++];
tasks.Add(PrefetchChunkAsync(chunk));
if (i == _chunks.Length) break;
count -= chunk.Size - startPos;
startPos = 0u;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
s.Dispose();
async Task PrefetchChunkAsync(VChunk chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
s.Release(); // This is intended
}
}
private (int Index, uint ChunkPos) GetChunkIndex(long position)
{
for (var i = 0; i < _chunks.Length; i++)
{
var size = _chunks[i].Size;
if (position < size) return (i, (uint) position);
position -= size;
}
return (-1, 0u);
}
private long _position;
public override long Position
{
get => _position;
set
{
if (value >= Length || value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
_position = value;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
Position = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => offset + _position,
SeekOrigin.End => Length + offset,
_ => throw new ArgumentOutOfRangeException(nameof(offset))
};
return _position;
}
public override long Length { get; }
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override void Flush() => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
_position += bytesRead;
return bytesRead;
}
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
{
var tasks = new List<Task>();
var s = new SemaphoreSlim(concurrentDownloads);
while (count > 0)
{
await s.WaitAsync(cancellationToken).ConfigureAwait(false);
var chunk = _chunks[i++];
tasks.Add(PrefetchChunkAsync(chunk));
if (i == _chunks.Length) break;
count -= chunk.Size - startPos;
startPos = 0u;
}
await Task.WhenAll(tasks).ConfigureAwait(false);
s.Dispose();
async Task PrefetchChunkAsync(VChunk chunk)
{
await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false);
s.Release(); // This is intended
}
}
private (int Index, uint ChunkPos) GetChunkIndex(long position)
{
for (var i = 0; i < _chunks.Length; i++)
{
var size = _chunks[i].Size;
if (position < size) return (i, (uint) position);
position -= size;
}
return (-1, 0u);
}
private long _position;
public override long Position
{
get => _position;
set
{
if (value >= Length || value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
_position = value;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
Position = origin switch
{
SeekOrigin.Begin => offset,
SeekOrigin.Current => offset + _position,
SeekOrigin.End => Length + offset,
_ => throw new ArgumentOutOfRangeException(nameof(offset))
};
return _position;
}
public override long Length { get; }
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override void Flush() => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
}

View File

@@ -5,202 +5,200 @@ using FModel.Settings;
using FModel.ViewModels.Commands;
using FModel.Views;
using FModel.Views.Resources.Controls;
using Ionic.Zip;
using Oodle.NET;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using System.Windows;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
using OodleCUE4 = CUE4Parse.Compression.Oodle;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class ApplicationViewModel : ViewModel
{
public class ApplicationViewModel : ViewModel
private EBuildKind _build;
public EBuildKind Build
{
private EBuildKind _build;
public EBuildKind Build
get => _build;
private set
{
get => _build;
private set
{
SetProperty(ref _build, value);
RaisePropertyChanged(nameof(TitleExtra));
}
}
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _status;
public EStatusKind Status
{
get => _status;
set
{
SetProperty(ref _status, value);
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
}
}
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 TitleExtra =>
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
$"{(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 MapViewerViewModel MapViewer { get; }
public ModelViewerViewModel ModelViewer { get; }
private OodleCompressor _oodle;
public ApplicationViewModel()
{
Status = EStatusKind.Loading;
#if DEBUG
Build = EBuildKind.Debug;
#elif RELEASE
Build = EBuildKind.Release;
#else
Build = EBuildKind.Unknown;
#endif
LoadingModes = new LoadingModesViewModel();
AvoidEmptyGameDirectoryAndSetEGame(false);
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
SettingsView = new SettingsViewModel(CUE4Parse.Game);
AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
Status = EStatusKind.Ready;
}
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
if (!result.HasValue || !result.Value) return;
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
if (!bAlreadyLaunched || gameDirectory.Equals(gameLauncherViewModel.SelectedDetectedGame.GameDirectory)) return;
RestartWithWarning();
}
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.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
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 InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
if (File.Exists(vgmZipFilePath)) return;
await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0)
{
var zip = ZipFile.Read(vgmZipFilePath);
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
}
else
{
FLogger.AppendError();
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
}
}
public async Task InitOodle()
{
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
{
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
}
else if (!File.Exists(oodlePath))
{
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
if (!result) return;
}
if (File.Exists("oo2core_8_win64.dll"))
File.Delete("oo2core_8_win64.dll");
_oodle = new OodleCompressor(oodlePath);
unsafe
{
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
(OodleLZ_FuzzSafe)a, (OodleLZ_CheckCRC)b, (OodleLZ_Verbosity)c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase)threadModule);
}
SetProperty(ref _build, value);
RaisePropertyChanged(nameof(TitleExtra));
}
}
}
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _status;
public EStatusKind Status
{
get => _status;
set
{
SetProperty(ref _status, value);
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
}
}
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 TitleExtra =>
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
$"{(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 MapViewerViewModel MapViewer { get; }
public ModelViewerViewModel ModelViewer { get; }
private OodleCompressor _oodle;
public ApplicationViewModel()
{
Status = EStatusKind.Loading;
#if DEBUG
Build = EBuildKind.Debug;
#elif RELEASE
Build = EBuildKind.Release;
#else
Build = EBuildKind.Unknown;
#endif
LoadingModes = new LoadingModesViewModel();
AvoidEmptyGameDirectoryAndSetEGame(false);
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
SettingsView = new SettingsViewModel(CUE4Parse.Game);
AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
Status = EStatusKind.Ready;
}
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
{
var gameDirectory = UserSettings.Default.GameDirectory;
if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return;
var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory);
var result = new DirectorySelector(gameLauncherViewModel).ShowDialog();
if (!result.HasValue || !result.Value) return;
UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory;
if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return;
RestartWithWarning();
}
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.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
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 InitVgmStream()
{
var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip");
if (File.Exists(vgmZipFilePath)) return;
await ApplicationService.ApiEndpointView.BenbotApi.DownloadFileAsync("https://github.com/vgmstream/vgmstream/releases/latest/download/vgmstream-win.zip", vgmZipFilePath);
if (new FileInfo(vgmZipFilePath).Length > 0)
{
var zip = ZipFile.Read(vgmZipFilePath);
var zipDir = vgmZipFilePath.SubstringBeforeLast("\\");
foreach (var e in zip) e.Extract(zipDir, ExtractExistingFileAction.OverwriteSilently);
}
else
{
FLogger.AppendError();
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
}
}
public async Task InitOodle()
{
var dataDir = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
var oodlePath = Path.Combine(dataDir.FullName, OodleCUE4.OODLE_DLL_NAME);
if (File.Exists(OodleCUE4.OODLE_DLL_NAME))
{
File.Move(OodleCUE4.OODLE_DLL_NAME, oodlePath, true);
}
else if (!File.Exists(oodlePath))
{
var result = await OodleCUE4.DownloadOodleDll(oodlePath);
if (!result) return;
}
if (File.Exists("oo2core_8_win64.dll"))
File.Delete("oo2core_8_win64.dll");
_oodle = new OodleCompressor(oodlePath);
unsafe
{
OodleCUE4.DecompressFunc = (bufferPtr, bufferSize, outputPtr, outputSize, a, b, c, d, e, f, g, h, i, threadModule) =>
_oodle.Decompress(new IntPtr(bufferPtr), bufferSize, new IntPtr(outputPtr), outputSize,
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
}
}
}

View File

@@ -10,162 +10,161 @@ using CUE4Parse.UE4.Vfs;
using FModel.Framework;
using FModel.Services;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class TreeItem : ViewModel
{
public class TreeItem : ViewModel
private string _header;
public string Header
{
private string _header;
public string Header
{
get => _header;
private set => SetProperty(ref _header, value);
}
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
private set => SetProperty(ref _mountPoint, value);
}
private int _version;
public int Version
{
get => _version;
private set => SetProperty(ref _version, value);
}
public string PathAtThisPoint { get; }
public AssetsListViewModel AssetsList { get; }
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public TreeItem(string header, string package, string mountPoint, int version, string pathHere)
{
Header = header;
Package = package;
MountPoint = mountPoint;
Version = version;
PathAtThisPoint = pathHere;
AssetsList = new AssetsListViewModel();
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) {SortDescriptions = {new SortDescription("Header", ListSortDirection.Ascending)}};
}
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
get => _header;
private set => SetProperty(ref _header, value);
}
public class AssetsFolderViewModel
private bool _isExpanded;
public bool IsExpanded
{
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
public AssetsFolderViewModel()
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
private set => SetProperty(ref _mountPoint, value);
}
private int _version;
public int Version
{
get => _version;
private set => SetProperty(ref _version, value);
}
public string PathAtThisPoint { get; }
public AssetsListViewModel AssetsList { get; }
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public TreeItem(string header, string package, string mountPoint, int version, string pathHere)
{
Header = header;
Package = package;
MountPoint = mountPoint;
Version = version;
PathAtThisPoint = pathHere;
AssetsList = new AssetsListViewModel();
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
}
public override string ToString() => $"{Header} | {Folders.Count} Folders | {AssetsList.Assets.Count} Files";
}
public class AssetsFolderViewModel
{
public RangeObservableCollection<TreeItem> Folders { get; }
public ICollectionView FoldersView { get; }
public AssetsFolderViewModel()
{
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) { SortDescriptions = { new SortDescription("Header", ListSortDirection.Ascending) } };
}
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
{
if (entries == null || entries.Count == 0)
return;
Application.Current.Dispatcher.Invoke(() =>
{
Folders = new RangeObservableCollection<TreeItem>();
FoldersView = new ListCollectionView(Folders) {SortDescriptions = {new SortDescription("Header", ListSortDirection.Ascending)}};
}
var treeItems = new RangeObservableCollection<TreeItem>();
treeItems.SetSuppressionState(true);
var items = new List<AssetItem>(entries.Count);
public void BulkPopulate(IReadOnlyCollection<VfsEntry> entries)
{
if (entries == null || entries.Count == 0)
return;
Application.Current.Dispatcher.Invoke(() =>
foreach (var entry in entries)
{
var treeItems = new RangeObservableCollection<TreeItem>();
treeItems.SetSuppressionState(true);
var items = new List<AssetItem>(entries.Count);
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
items.Add(item);
foreach (var entry in entries)
{
var item = new AssetItem(entry.Path, entry.IsEncrypted, entry.Offset, entry.Size, entry.Vfs.Name, entry.CompressionMethod);
items.Add(item);
TreeItem lastNode = null;
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;
for (var i = 0; i < folders.Length - 1; i++)
{
TreeItem lastNode = null;
var folders = item.FullPath.Split('/', StringSplitOptions.RemoveEmptyEntries);
var builder = new StringBuilder(64);
var parentNode = treeItems;
var folder = folders[i];
builder.Append(folder).Append('/');
lastNode = FindByHeaderOrNull(parentNode, folder);
for (var i = 0; i < folders.Length - 1; i++)
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
{
var folder = folders[i];
builder.Append(folder).Append('/');
lastNode = FindByHeaderOrNull(parentNode, folder);
static TreeItem FindByHeaderOrNull(IReadOnlyList<TreeItem> list, string header)
for (var i = 0; i < list.Count; i++)
{
for (var i = 0; i < list.Count; i++)
{
if (list[i].Header == header)
return list[i];
}
return null;
if (list[i].Header == header)
return list[i];
}
if (lastNode == null)
{
var nodePath = builder.ToString();
lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]);
lastNode.Folders.SetSuppressionState(true);
lastNode.AssetsList.Assets.SetSuppressionState(true);
parentNode.Add(lastNode);
}
parentNode = lastNode.Folders;
return null;
}
lastNode?.AssetsList.Assets.Add(item);
if (lastNode == null)
{
var nodePath = builder.ToString();
lastNode = new TreeItem(folder, item.Package, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]);
lastNode.Folders.SetSuppressionState(true);
lastNode.AssetsList.Assets.SetSuppressionState(true);
parentNode.Add(lastNode);
}
parentNode = lastNode.Folders;
}
lastNode?.AssetsList.Assets.Add(item);
}
}
Folders.AddRange(treeItems);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
Folders.AddRange(treeItems);
ApplicationService.ApplicationView.CUE4Parse.SearchVm.SearchResults.AddRange(items);
foreach (var folder in Folders)
InvokeOnCollectionChanged(folder);
foreach (var folder in Folders)
InvokeOnCollectionChanged(folder);
static void InvokeOnCollectionChanged(TreeItem item)
static void InvokeOnCollectionChanged(TreeItem item)
{
item.Folders.SetSuppressionState(false);
item.AssetsList.Assets.SetSuppressionState(false);
if (item.Folders.Count != 0)
{
item.Folders.SetSuppressionState(false);
item.AssetsList.Assets.SetSuppressionState(false);
item.Folders.InvokeOnCollectionChanged();
if (item.Folders.Count != 0)
{
item.Folders.SetSuppressionState(false);
item.Folders.InvokeOnCollectionChanged();
foreach (var folderItem in item.Folders)
InvokeOnCollectionChanged(folderItem);
}
if (item.AssetsList.Assets.Count != 0)
item.AssetsList.Assets.InvokeOnCollectionChanged();
foreach (var folderItem in item.Folders)
InvokeOnCollectionChanged(folderItem);
}
});
}
if (item.AssetsList.Assets.Count != 0)
item.AssetsList.Assets.InvokeOnCollectionChanged();
}
});
}
}
}

View File

@@ -3,77 +3,76 @@ using System.Windows.Data;
using CUE4Parse.Compression;
using FModel.Framework;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class AssetItem : ViewModel
{
public class AssetItem : ViewModel
private string _fullPath;
public string FullPath
{
private string _fullPath;
public string FullPath
{
get => _fullPath;
private set => SetProperty(ref _fullPath, value);
}
private bool _isEncrypted;
public bool IsEncrypted
{
get => _isEncrypted;
private set => SetProperty(ref _isEncrypted, value);
}
private long _offset;
public long Offset
{
get => _offset;
private set => SetProperty(ref _offset, value);
}
private long _size;
public long Size
{
get => _size;
private set => SetProperty(ref _size, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private CompressionMethod _compression;
public CompressionMethod Compression
{
get => _compression;
private set => SetProperty(ref _compression, value);
}
public AssetItem(string fullPath, bool isEncrypted, long offset, long size, string package, CompressionMethod compression)
{
FullPath = fullPath;
IsEncrypted = isEncrypted;
Offset = offset;
Size = size;
Package = package;
Compression = compression;
}
public override string ToString() => FullPath;
get => _fullPath;
private set => SetProperty(ref _fullPath, value);
}
public class AssetsListViewModel
private bool _isEncrypted;
public bool IsEncrypted
{
public RangeObservableCollection<AssetItem> Assets { get; }
public ICollectionView AssetsView { get; }
get => _isEncrypted;
private set => SetProperty(ref _isEncrypted, value);
}
public AssetsListViewModel()
private long _offset;
public long Offset
{
get => _offset;
private set => SetProperty(ref _offset, value);
}
private long _size;
public long Size
{
get => _size;
private set => SetProperty(ref _size, value);
}
private string _package;
public string Package
{
get => _package;
private set => SetProperty(ref _package, value);
}
private CompressionMethod _compression;
public CompressionMethod Compression
{
get => _compression;
private set => SetProperty(ref _compression, value);
}
public AssetItem(string fullPath, bool isEncrypted, long offset, long size, string package, CompressionMethod compression)
{
FullPath = fullPath;
IsEncrypted = isEncrypted;
Offset = offset;
Size = size;
Package = package;
Compression = compression;
}
public override string ToString() => FullPath;
}
public class AssetsListViewModel
{
public RangeObservableCollection<AssetItem> Assets { get; }
public ICollectionView AssetsView { get; }
public AssetsListViewModel()
{
Assets = new RangeObservableCollection<AssetItem>();
AssetsView = new ListCollectionView(Assets)
{
Assets = new RangeObservableCollection<AssetItem>();
AssetsView = new ListCollectionView(Assets)
{
SortDescriptions = {new SortDescription("FullPath", ListSortDirection.Ascending)}
};
}
SortDescriptions = { new SortDescription("FullPath", ListSortDirection.Ascending) }
};
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,102 +16,101 @@ using K4os.Compression.LZ4;
using K4os.Compression.LZ4.Streams;
using Serilog;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class BackupManagerViewModel : ViewModel
{
public class BackupManagerViewModel : ViewModel
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly string _gameName;
private Backup _selectedBackup;
public Backup SelectedBackup
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly string _gameName;
get => _selectedBackup;
set => SetProperty(ref _selectedBackup, value);
}
private Backup _selectedBackup;
public Backup SelectedBackup
public ObservableCollection<Backup> Backups { get; }
public ICollectionView BackupsView { get; }
public BackupManagerViewModel(string gameName)
{
_gameName = gameName;
Backups = new ObservableCollection<Backup>();
BackupsView = new ListCollectionView(Backups) { SortDescriptions = { new SortDescription("FileName", ListSortDirection.Ascending) } };
}
public async Task Initialize()
{
await _threadWorkerView.Begin(cancellationToken =>
{
get => _selectedBackup;
set => SetProperty(ref _selectedBackup, value);
}
var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName);
if (backups == null) return;
public ObservableCollection<Backup> Backups { get; }
public ICollectionView BackupsView { get; }
public BackupManagerViewModel(string gameName)
{
_gameName = gameName;
Backups = new ObservableCollection<Backup>();
BackupsView = new ListCollectionView(Backups) {SortDescriptions = {new SortDescription("FileName", ListSortDirection.Ascending)}};
}
public async Task Initialize()
{
await _threadWorkerView.Begin(cancellationToken =>
Application.Current.Dispatcher.Invoke(() =>
{
var backups = _apiEndpointView.FModelApi.GetBackups(cancellationToken, _gameName);
if (backups == null) return;
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var backup in backups) Backups.Add(backup);
SelectedBackup = Backups.LastOrDefault();
});
foreach (var backup in backups) Backups.Add(backup);
SelectedBackup = Backups.LastOrDefault();
});
}
});
}
public async Task CreateBackup()
public async Task CreateBackup()
{
await _threadWorkerView.Begin(_ =>
{
await _threadWorkerView.Begin(_ =>
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
var fullPath = Path.Combine(backupFolder, fileName);
using var fileStream = new FileStream(fullPath, FileMode.Create);
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
using var writer = new BinaryWriter(compressedStream);
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
var fullPath = Path.Combine(backupFolder, fileName);
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
using var fileStream = new FileStream(fullPath, FileMode.Create);
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
using var writer = new BinaryWriter(compressedStream);
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
writer.Write((long) 0);
writer.Write((long) 0);
writer.Write(entry.Size);
writer.Write(entry.IsEncrypted);
writer.Write(0);
writer.Write($"/{entry.Path.ToLower()}");
writer.Write(0);
}
SaveCheck(fullPath, fileName, "created", "create");
});
}
public async Task Download()
{
if (SelectedBackup == null) return;
await _threadWorkerView.Begin(_ =>
{
var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName);
_apiEndpointView.BenbotApi.DownloadFile(SelectedBackup.DownloadUrl, fullPath);
SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download");
});
}
private void SaveCheck(string fullPath, string fileName, string type1, string type2)
{
if (new FileInfo(fullPath).Length > 0)
{
Log.Information("{FileName} successfully {Type}", fileName, type1);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be {Type}", fileName, type1);
FLogger.AppendError();
FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true);
writer.Write((long) 0);
writer.Write((long) 0);
writer.Write(entry.Size);
writer.Write(entry.IsEncrypted);
writer.Write(0);
writer.Write($"/{entry.Path.ToLower()}");
writer.Write(0);
}
SaveCheck(fullPath, fileName, "created", "create");
});
}
public async Task Download()
{
if (SelectedBackup == null) return;
await _threadWorkerView.Begin(_ =>
{
var fullPath = Path.Combine(Path.Combine(UserSettings.Default.OutputDirectory, "Backups"), SelectedBackup.FileName);
_apiEndpointView.BenbotApi.DownloadFile(SelectedBackup.DownloadUrl, fullPath);
SaveCheck(fullPath, SelectedBackup.FileName, "downloaded", "download");
});
}
private void SaveCheck(string fullPath, string fileName, string type1, string type2)
{
if (new FileInfo(fullPath).Length > 0)
{
Log.Information("{FileName} successfully {Type}", fileName, type1);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be {Type}", fileName, type1);
FLogger.AppendError();
FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,34 +2,33 @@
using FModel.Framework;
using FModel.Views;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
{
public class AddEditDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
public AddEditDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
public AddEditDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir)
customDir = new CustomDirectory();
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir)
customDir = new CustomDirectory();
Helper.OpenWindow<AdonisWindow>("Custom Directory", () =>
Helper.OpenWindow<AdonisWindow>("Custom Directory", () =>
{
var index = contextViewModel.GetIndex(customDir);
var input = new CustomDir(customDir);
var result = input.ShowDialog();
if (!result.HasValue || !result.Value || string.IsNullOrEmpty(customDir.Header) && string.IsNullOrEmpty(customDir.DirectoryPath))
return;
if (index > 1)
{
var index = contextViewModel.GetIndex(customDir);
var input = new CustomDir(customDir);
var result = input.ShowDialog();
if (!result.HasValue || !result.Value || string.IsNullOrEmpty(customDir.Header) && string.IsNullOrEmpty(customDir.DirectoryPath))
return;
if (index > 1)
{
contextViewModel.Edit(index, customDir);
}
else
contextViewModel.Add(customDir);
});
}
contextViewModel.Edit(index, customDir);
}
else
contextViewModel.Add(customDir);
});
}
}

View File

@@ -1,16 +1,15 @@
using FModel.Framework;
namespace FModel.ViewModels.Commands
{
public class AddTabCommand : ViewModelCommand<TabControlViewModel>
{
public AddTabCommand(TabControlViewModel contextViewModel) : base(contextViewModel)
{
}
namespace FModel.ViewModels.Commands;
public override void Execute(TabControlViewModel contextViewModel, object parameter)
{
contextViewModel.AddTab();
}
public class AddTabCommand : ViewModelCommand<TabControlViewModel>
{
public AddTabCommand(TabControlViewModel contextViewModel) : base(contextViewModel)
{
}
public override void Execute(TabControlViewModel contextViewModel, object parameter)
{
contextViewModel.AddTab();
}
}

View File

@@ -1,45 +1,44 @@
using FModel.Framework;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class AudioCommand : ViewModelCommand<AudioPlayerViewModel>
{
public class AudioCommand : ViewModelCommand<AudioPlayerViewModel>
public AudioCommand(AudioPlayerViewModel contextViewModel) : base(contextViewModel)
{
public AudioCommand(AudioPlayerViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(AudioPlayerViewModel contextViewModel, object parameter)
{
if (parameter is not string s)
return;
public override void Execute(AudioPlayerViewModel contextViewModel, object parameter)
{
if (parameter is not string s)
return;
switch (s)
{
case "Previous":
contextViewModel.Previous();
break;
case "PlayPause":
contextViewModel.PlayPauseOnStart();
break;
case "ForcePlayPause":
contextViewModel.PlayPauseOnForce();
break;
case "Stop":
contextViewModel.Stop();
break;
case "Next":
contextViewModel.Next();
break;
case "Remove":
contextViewModel.Remove();
break;
case "Save":
contextViewModel.Save();
break;
case "Save_Playlist":
contextViewModel.SavePlaylist();
break;
}
switch (s)
{
case "Previous":
contextViewModel.Previous();
break;
case "PlayPause":
contextViewModel.PlayPauseOnStart();
break;
case "ForcePlayPause":
contextViewModel.PlayPauseOnForce();
break;
case "Stop":
contextViewModel.Stop();
break;
case "Next":
contextViewModel.Next();
break;
case "Remove":
contextViewModel.Remove();
break;
case "Save":
contextViewModel.Save();
break;
case "Save_Playlist":
contextViewModel.SavePlaylist();
break;
}
}
}

View File

@@ -5,43 +5,42 @@ using System.Windows;
using FModel.Extensions;
using FModel.Framework;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class CopyCommand : ViewModelCommand<ApplicationViewModel>
{
public class CopyCommand : ViewModelCommand<ApplicationViewModel>
public CopyCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
public CopyCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
}
public override void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
var sb = new StringBuilder();
switch (trigger)
{
case "File_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
break;
case "File_Name":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
break;
case "Directory_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
break;
case "File_Path_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
break;
case "File_Name_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
break;
}
public override void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
var sb = new StringBuilder();
switch (trigger)
{
case "File_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath);
break;
case "File_Name":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/'));
break;
case "Directory_Path":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('/'));
break;
case "File_Path_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringBeforeLast('.'));
break;
case "File_Name_No_Extension":
foreach (var asset in assetItems) sb.AppendLine(asset.FullPath.SubstringAfterLast('/').SubstringBeforeLast('.'));
break;
}
Clipboard.SetText(sb.ToString().TrimEnd());
}
Clipboard.SetText(sb.ToString().TrimEnd());
}
}

View File

@@ -1,21 +1,20 @@
using FModel.Framework;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
{
public class DeleteDirectoryCommand : ViewModelCommand<CustomDirectoriesViewModel>
public DeleteDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
public DeleteDirectoryCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir) return;
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not CustomDirectory customDir) return;
var index = contextViewModel.GetIndex(customDir);
if (index < 2) return;
var index = contextViewModel.GetIndex(customDir);
if (index < 2) return;
contextViewModel.Delete(index);
}
contextViewModel.Delete(index);
}
}

View File

@@ -2,57 +2,56 @@
using FModel.Framework;
using FModel.Services;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
{
public class GoToCommand : ViewModelCommand<CustomDirectoriesViewModel>
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
}
public GoToCommand(CustomDirectoriesViewModel contextViewModel) : base(contextViewModel)
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not string s || string.IsNullOrEmpty(s)) return;
JumpTo(s);
}
public TreeItem JumpTo(string directory)
{
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
if (root is not { Count: > 0 }) return null;
var i = 0;
var done = false;
var folders = directory.Split('/');
while (!done)
{
}
public override void Execute(CustomDirectoriesViewModel contextViewModel, object parameter)
{
if (parameter is not string s || string.IsNullOrEmpty(s)) return;
JumpTo(s);
}
public TreeItem JumpTo(string directory)
{
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
var root = _applicationView.CUE4Parse.AssetsFolder.Folders;
if (root is not {Count: > 0}) return null;
var i = 0;
var done = false;
var folders = directory.Split('/');
while (!done)
foreach (var folder in root)
{
foreach (var folder in root)
if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
continue;
folder.IsExpanded = true; // folder found = expand
// is this the last folder aka the one we want to jump in
if (i >= folders.Length - 1)
{
if (!folder.Header.Equals(folders[i], i == 0 ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal))
continue;
folder.IsExpanded = true; // folder found = expand
// is this the last folder aka the one we want to jump in
if (i >= folders.Length - 1)
{
folder.IsSelected = true; // select it
return folder;
}
root = folder.Folders; // grab his subfolders
break;
folder.IsSelected = true; // select it
return folder;
}
i++;
done = i == folders.Length || root.Count == 0;
root = folder.Folders; // grab his subfolders
break;
}
return null;
i++;
done = i == folders.Length || root.Count == 0;
}
return null;
}
}

View File

@@ -2,49 +2,47 @@
using FModel.Extensions;
using FModel.Framework;
using FModel.Views.Resources.Controls;
using System.IO;
using System.Windows;
using System.Windows.Media;
using FModel.Views.Resources.Converters;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class ImageCommand : ViewModelCommand<TabItem>
{
public class ImageCommand : ViewModelCommand<TabItem>
public ImageCommand(TabItem contextViewModel) : base(contextViewModel)
{
public ImageCommand(TabItem contextViewModel) : base(contextViewModel)
{
}
}
public override void Execute(TabItem contextViewModel, object parameter)
{
if (parameter == null || !contextViewModel.HasImage) return;
public override void Execute(TabItem contextViewModel, object parameter)
{
if (parameter == null || !contextViewModel.HasImage) return;
switch (parameter)
switch (parameter)
{
case "Open":
{
case "Open":
Helper.OpenWindow<AdonisWindow>(contextViewModel.SelectedImage.ExportName + " (Image)", () =>
{
Helper.OpenWindow<AdonisWindow>(contextViewModel.SelectedImage.ExportName + " (Image)", () =>
var popout = new ImagePopout
{
var popout = new ImagePopout
{
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
Width = contextViewModel.SelectedImage.Image.Width,
Height = contextViewModel.SelectedImage.Image.Height,
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
ImageCtrl = {Source = contextViewModel.SelectedImage.Image}
};
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
popout.Show();
});
break;
}
case "Copy":
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
break;
case "Save":
contextViewModel.SaveImage(false);
break;
Title = contextViewModel.SelectedImage.ExportName + " (Image)",
Width = contextViewModel.SelectedImage.Image.Width,
Height = contextViewModel.SelectedImage.Image.Height,
WindowState = contextViewModel.SelectedImage.Image.Height > 1000 ? WindowState.Maximized : WindowState.Normal,
ImageCtrl = { Source = contextViewModel.SelectedImage.Image }
};
RenderOptions.SetBitmapScalingMode(popout.ImageCtrl, BoolToRenderModeConverter.Instance.Convert(contextViewModel.SelectedImage.RenderNearestNeighbor));
popout.Show();
});
break;
}
case "Copy":
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
break;
case "Save":
contextViewModel.SaveImage(false);
break;
}
}
}
}

View File

@@ -18,210 +18,209 @@ using FModel.Views.Resources.Controls;
using K4os.Compression.LZ4.Streams;
using Microsoft.Win32;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
/// <summary>
/// this will always load all files no matter the loading mode
/// however what this does is filtering what to show to the user
/// </summary>
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
{
/// <summary>
/// this will always load all files no matter the loading mode
/// however what this does is filtering what to show to the user
/// </summary>
public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
private const uint _IS_LZ4 = 0x184D2204u;
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
{
private const uint _IS_LZ4 = 0x184D2204u;
}
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
{
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
{
FLogger.AppendError();
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
return;
}
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
{
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
{
FLogger.AppendError();
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
return;
}
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
{
FLogger.AppendError();
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
}
FLogger.AppendError();
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, press F12 or please restart.", Constants.WHITE, true);
}
#if DEBUG
var loadingTime = Stopwatch.StartNew();
var loadingTime = Stopwatch.StartNew();
#endif
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
await _threadWorkerView.Begin(cancellationToken =>
{
// filter what to show
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.Single:
case ELoadingMode.Multiple:
{
var l = (IList) parameter;
if (l.Count < 1) return;
var directoryFilesToShow = l.Cast<FileItem>();
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
break;
}
case ELoadingMode.All:
{
FilterDirectoryFilesToDisplay(cancellationToken, null);
break;
}
case ELoadingMode.AllButNew:
case ELoadingMode.AllButModified:
{
FilterNewOrModifiedFilesToDisplay(cancellationToken);
break;
}
default: throw new ArgumentOutOfRangeException();
}
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
});
#if DEBUG
loadingTime.Stop();
FLogger.AppendDebug();
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
#endif
}
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
await _threadWorkerView.Begin(cancellationToken =>
{
HashSet<string> filter;
if (directoryFiles == null)
filter = null;
else
{
filter = new HashSet<string>();
foreach (var directoryFile in directoryFiles)
{
if (!directoryFile.IsEnabled)
continue;
filter.Add(directoryFile.Name);
}
}
var hasFilter = filter != null && filter.Count != 0;
var entries = new List<VfsEntry>();
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
if (hasFilter)
{
if (filter.Contains(entry.Vfs.Name))
entries.Add(entry);
}
else
entries.Add(entry);
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a backup file older than your current game version",
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
Multiselect = false
};
if (!(bool) openFileDialog.ShowDialog()) return;
FLogger.AppendInformation();
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
using var memoryStream = new MemoryStream();
if (fileStream.ReadUInt32() == _IS_LZ4)
{
fileStream.Position -= 4;
using var compressionStream = LZ4Stream.Decode(fileStream);
compressionStream.CopyTo(memoryStream);
}
else fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
var entries = new List<VfsEntry>();
// filter what to show
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.AllButNew:
case ELoadingMode.Single:
case ELoadingMode.Multiple:
{
var paths = new Dictionary<string, int>();
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 29;
paths[archive.ReadString().ToLower()[1..]] = 0;
archive.Position += 4;
}
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
{
cancellationToken.ThrowIfCancellationRequested();
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
entries.Add(entry);
}
var l = (IList) parameter;
if (l.Count < 1) return;
var directoryFilesToShow = l.Cast<FileItem>();
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
break;
}
case ELoadingMode.All:
{
FilterDirectoryFilesToDisplay(cancellationToken, null);
break;
}
case ELoadingMode.AllButNew:
case ELoadingMode.AllButModified:
{
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 16;
var uncompressedSize = archive.Read<long>();
var isEncrypted = archive.ReadFlag();
archive.Position += 4;
var fullPath = archive.ReadString().ToLower()[1..];
archive.Position += 4;
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
continue;
entries.Add(entry);
}
FilterNewOrModifiedFilesToDisplay(cancellationToken);
break;
}
default: throw new ArgumentOutOfRangeException();
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
});
#if DEBUG
loadingTime.Stop();
FLogger.AppendDebug();
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
#endif
}
}
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
{
HashSet<string> filter;
if (directoryFiles == null)
filter = null;
else
{
filter = new HashSet<string>();
foreach (var directoryFile in directoryFiles)
{
if (!directoryFile.IsEnabled)
continue;
filter.Add(directoryFile.Name);
}
}
var hasFilter = filter != null && filter.Count != 0;
var entries = new List<VfsEntry>();
foreach (var asset in _applicationView.CUE4Parse.Provider.Files.Values)
{
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
if (asset is not VfsEntry entry || entry.Path.EndsWith(".uexp") || entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl"))
continue;
if (hasFilter)
{
if (filter.Contains(entry.Vfs.Name))
entries.Add(entry);
}
else
entries.Add(entry);
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
private void FilterNewOrModifiedFilesToDisplay(CancellationToken cancellationToken)
{
var openFileDialog = new OpenFileDialog
{
Title = "Select a backup file older than your current game version",
InitialDirectory = Path.Combine(UserSettings.Default.OutputDirectory, "Backups"),
Filter = "FBKP Files (*.fbkp)|*.fbkp|All Files (*.*)|*.*",
Multiselect = false
};
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
FLogger.AppendInformation();
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
using var memoryStream = new MemoryStream();
if (fileStream.ReadUInt32() == _IS_LZ4)
{
fileStream.Position -= 4;
using var compressionStream = LZ4Stream.Decode(fileStream);
compressionStream.CopyTo(memoryStream);
}
else fileStream.CopyTo(memoryStream);
memoryStream.Position = 0;
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
var entries = new List<VfsEntry>();
switch (UserSettings.Default.LoadingMode)
{
case ELoadingMode.AllButNew:
{
var paths = new Dictionary<string, int>();
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 29;
paths[archive.ReadString().ToLower()[1..]] = 0;
archive.Position += 4;
}
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
{
cancellationToken.ThrowIfCancellationRequested();
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
entries.Add(entry);
}
break;
}
case ELoadingMode.AllButModified:
{
while (archive.Position < archive.Length)
{
cancellationToken.ThrowIfCancellationRequested();
archive.Position += 16;
var uncompressedSize = archive.Read<long>();
var isEncrypted = archive.ReadFlag();
archive.Position += 4;
var fullPath = archive.ReadString().ToLower()[1..];
archive.Position += 4;
if (fullPath.EndsWith(".uexp") || fullPath.EndsWith(".ubulk") || fullPath.EndsWith(".uptnl") ||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(fullPath, out var asset) || asset is not VfsEntry entry ||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
continue;
entries.Add(entry);
}
break;
}
}
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
}

View File

@@ -9,105 +9,104 @@ using FModel.Views;
using FModel.Views.Resources.Controls;
using Newtonsoft.Json;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class MenuCommand : ViewModelCommand<ApplicationViewModel>
{
public class MenuCommand : ViewModelCommand<ApplicationViewModel>
public MenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
public MenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
}
}
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
{
switch (parameter)
{
switch (parameter)
{
case "Directory_Selector":
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true);
break;
case "Directory_AES":
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
break;
case "Directory_Backup":
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show());
break;
case "Directory_ArchivesInfo":
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;
case "Views_MapViewer":
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
break;
case "Views_ImageMerger":
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
break;
case "Settings":
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "ModelSettings":
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "Help_About":
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
break;
case "Help_Donate":
Process.Start(new ProcessStartInfo {FileName = Constants.DONATE_LINK, UseShellExecute = true});
break;
case "Help_Changelog":
UserSettings.Default.ShowChangelog = true;
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
break;
case "Help_BugsReport":
Process.Start(new ProcessStartInfo {FileName = Constants.ISSUE_LINK, UseShellExecute = true});
break;
case "Help_Discord":
Process.Start(new ProcessStartInfo {FileName = Constants.DISCORD_LINK, UseShellExecute = true});
break;
case "ToolBox_Clear_Logs":
FLogger.Logger.Text = string.Empty;
break;
case "ToolBox_Open_Output_Directory":
Process.Start(new ProcessStartInfo {FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true});
break;
case "ToolBox_Expand_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
case "Directory_Selector":
contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true);
break;
case "Directory_AES":
Helper.OpenWindow<AdonisWindow>("AES Manager", () => new AesManager().Show());
break;
case "Directory_Backup":
Helper.OpenWindow<AdonisWindow>("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show());
break;
case "Directory_ArchivesInfo":
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;
case "Views_MapViewer":
Helper.OpenWindow<AdonisWindow>("Map Viewer", () => new MapViewer().Show());
break;
case "Views_ImageMerger":
Helper.OpenWindow<AdonisWindow>("Image Merger", () => new ImageMerger().Show());
break;
case "Settings":
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "ModelSettings":
UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1;
Helper.OpenWindow<AdonisWindow>("Settings", () => new SettingsView().Show());
break;
case "Help_About":
Helper.OpenWindow<AdonisWindow>("About", () => new About().Show());
break;
case "Help_Donate":
Process.Start(new ProcessStartInfo { FileName = Constants.DONATE_LINK, UseShellExecute = true });
break;
case "Help_Changelog":
UserSettings.Default.ShowChangelog = true;
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
break;
case "Help_BugsReport":
Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true });
break;
case "Help_Discord":
Process.Start(new ProcessStartInfo { FileName = Constants.DISCORD_LINK, UseShellExecute = true });
break;
case "ToolBox_Clear_Logs":
FLogger.Logger.Text = string.Empty;
break;
case "ToolBox_Open_Output_Directory":
Process.Start(new ProcessStartInfo { FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true });
break;
case "ToolBox_Expand_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
LoopFolders(cancellationToken, folder, true);
}
});
break;
case "ToolBox_Collapse_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
LoopFolders(cancellationToken, folder, true);
}
});
break;
case "ToolBox_Collapse_All":
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
{
LoopFolders(cancellationToken, folder, false);
}
});
break;
case TreeItem selectedFolder:
selectedFolder.IsSelected = false;
selectedFolder.IsSelected = true;
break;
}
}
private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded)
{
if (parent.IsExpanded != isExpanded)
{
parent.IsExpanded = isExpanded;
Thread.Sleep(10);
}
cancellationToken.ThrowIfCancellationRequested();
foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded);
LoopFolders(cancellationToken, folder, false);
}
});
break;
case TreeItem selectedFolder:
selectedFolder.IsSelected = false;
selectedFolder.IsSelected = true;
break;
}
}
}
private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded)
{
if (parent.IsExpanded != isExpanded)
{
parent.IsExpanded = isExpanded;
Thread.Sleep(10);
}
cancellationToken.ThrowIfCancellationRequested();
foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded);
}
}

View File

@@ -3,60 +3,63 @@ using System.Linq;
using FModel.Framework;
using FModel.Services;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
{
public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
public RightClickMenuCommand(ApplicationViewModel contextViewModel) : base(contextViewModel)
{
}
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
await _threadWorkerView.Begin(cancellationToken =>
{
switch (trigger)
{
case "Assets_Extract_New_Tab":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
}
break;
case "Assets_Export_Data":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
}
break;
case "Assets_Save_Properties":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
}
break;
case "Assets_Save_Texture":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
}
break;
}
});
}
}
}
public override async void Execute(ApplicationViewModel contextViewModel, object parameter)
{
if (parameter is not object[] parameters || parameters[0] is not string trigger)
return;
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
if (!assetItems.Any()) return;
await _threadWorkerView.Begin(cancellationToken =>
{
switch (trigger)
{
case "Assets_Extract_New_Tab":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
}
break;
case "Assets_Export_Data":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
}
break;
case "Assets_Save_Properties":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
}
break;
case "Assets_Save_Texture":
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
}
break;
}
});
}
}

View File

@@ -4,46 +4,45 @@ using FModel.Framework;
using FModel.Services;
using FModel.Views.Resources.Controls;
namespace FModel.ViewModels.Commands
namespace FModel.ViewModels.Commands;
public class TabCommand : ViewModelCommand<TabItem>
{
public class TabCommand : ViewModelCommand<TabItem>
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public TabCommand(TabItem contextViewModel) : base(contextViewModel)
{
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
}
public TabCommand(TabItem contextViewModel) : base(contextViewModel)
public override void Execute(TabItem contextViewModel, object parameter)
{
switch (parameter)
{
}
public override void Execute(TabItem contextViewModel, object parameter)
{
switch (parameter)
{
case TabItem mdlClick:
_applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick);
break;
case "Close_Tab":
_applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel);
break;
case "Close_All_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveAllTabs();
break;
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
break;
case "Open_Properties":
if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return;
Helper.OpenWindow<AdonisWindow>(contextViewModel.Header + " (Properties)", () =>
case TabItem mdlClick:
_applicationView.CUE4Parse.TabControl.RemoveTab(mdlClick);
break;
case "Close_Tab":
_applicationView.CUE4Parse.TabControl.RemoveTab(contextViewModel);
break;
case "Close_All_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveAllTabs();
break;
case "Close_Other_Tabs":
_applicationView.CUE4Parse.TabControl.RemoveOtherTabs(contextViewModel);
break;
case "Open_Properties":
if (contextViewModel.Header == "New Tab" || contextViewModel.Document == null) return;
Helper.OpenWindow<AdonisWindow>(contextViewModel.Header + " (Properties)", () =>
{
new PropertiesPopout(contextViewModel)
{
new PropertiesPopout(contextViewModel)
{
Title = contextViewModel.Header + " (Properties)"
}.Show();
});
break;
case "Copy_Asset_Name":
Clipboard.SetText(contextViewModel.Header);
break;
}
Title = contextViewModel.Header + " (Properties)"
}.Show();
});
break;
case "Copy_Asset_Name":
Clipboard.SetText(contextViewModel.Header);
break;
}
}
}

View File

@@ -9,163 +9,162 @@ using FModel.Framework;
using FModel.Settings;
using FModel.ViewModels.Commands;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class CustomDirectory : ViewModel
{
public class CustomDirectory : ViewModel
private string _header;
public string Header
{
private string _header;
public string Header
{
get => _header;
set => SetProperty(ref _header, value);
}
private string _directoryPath;
public string DirectoryPath
{
get => _directoryPath;
set => SetProperty(ref _directoryPath, value);
}
public CustomDirectory()
{
Header = string.Empty;
DirectoryPath = string.Empty;
}
public CustomDirectory(string header, string path)
{
Header = header;
DirectoryPath = path;
}
public override string ToString() => Header;
get => _header;
set => SetProperty(ref _header, value);
}
public class CustomDirectoriesViewModel : ViewModel
private string _directoryPath;
public string DirectoryPath
{
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this);
private AddEditDirectoryCommand _addEditDirectoryCommand;
public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this);
private DeleteDirectoryCommand _deleteDirectoryCommand;
public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this);
private readonly ObservableCollection<Control> _directories;
public ReadOnlyObservableCollection<Control> Directories { get; }
private readonly FGame _game;
private readonly string _gameDirectoryAtLaunch;
public CustomDirectoriesViewModel(FGame game, string directory)
{
_game = game;
_gameDirectoryAtLaunch = directory;
_directories = new ObservableCollection<Control>(EnumerateDirectories());
Directories = new ReadOnlyObservableCollection<Control>(_directories);
}
public int GetIndex(CustomDirectory dir)
{
return _directories.IndexOf(_directories.FirstOrDefault(x =>
x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath));
}
public void Add(CustomDirectory dir)
{
_directories.Add(new MenuItem {Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir)});
}
public void Edit(int index, CustomDirectory newDir)
{
if (_directories.ElementAt(index) is not MenuItem dir) return;
dir.Header = newDir.Header;
dir.Tag = newDir.DirectoryPath;
}
public void Delete(int index)
{
_directories.RemoveAt(index);
}
public void Save()
{
var cd = new List<CustomDirectory>();
for (var i = 2; i < _directories.Count; i++)
{
if (_directories[i] is not MenuItem m) continue;
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
}
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
else UserSettings.Default.CustomDirectories[_game] = cd;
}
private IEnumerable<Control> EnumerateDirectories()
{
yield return new MenuItem
{
Header = "Add Directory",
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand
};
yield return new Separator();
IList<CustomDirectory> cd;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
cd = settings.CustomDirectories;
else cd = UserSettings.Default.CustomDirectories[_game];
foreach (var setting in cd)
{
if (setting.DirectoryPath.EndsWith('/'))
setting.DirectoryPath = setting.DirectoryPath[..^1];
yield return new MenuItem
{
Header = setting.Header,
Tag = setting.DirectoryPath,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
ItemsSource = EnumerateCommands(setting)
};
}
}
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
{
yield return new MenuItem
{
Header = "Go To",
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = GoToCommand,
CommandParameter = dir.DirectoryPath
};
yield return new MenuItem
{
Header = "Edit Directory",
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand,
CommandParameter = dir
};
yield return new MenuItem
{
Header = "Delete Directory",
StaysOpenOnClick = true,
Icon = new Image {Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative))},
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = DeleteDirectoryCommand,
CommandParameter = dir
};
}
get => _directoryPath;
set => SetProperty(ref _directoryPath, value);
}
public CustomDirectory()
{
Header = string.Empty;
DirectoryPath = string.Empty;
}
public CustomDirectory(string header, string path)
{
Header = header;
DirectoryPath = path;
}
public override string ToString() => Header;
}
public class CustomDirectoriesViewModel : ViewModel
{
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(this);
private AddEditDirectoryCommand _addEditDirectoryCommand;
public AddEditDirectoryCommand AddEditDirectoryCommand => _addEditDirectoryCommand ??= new AddEditDirectoryCommand(this);
private DeleteDirectoryCommand _deleteDirectoryCommand;
public DeleteDirectoryCommand DeleteDirectoryCommand => _deleteDirectoryCommand ??= new DeleteDirectoryCommand(this);
private readonly ObservableCollection<Control> _directories;
public ReadOnlyObservableCollection<Control> Directories { get; }
private readonly FGame _game;
private readonly string _gameDirectoryAtLaunch;
public CustomDirectoriesViewModel(FGame game, string directory)
{
_game = game;
_gameDirectoryAtLaunch = directory;
_directories = new ObservableCollection<Control>(EnumerateDirectories());
Directories = new ReadOnlyObservableCollection<Control>(_directories);
}
public int GetIndex(CustomDirectory dir)
{
return _directories.IndexOf(_directories.FirstOrDefault(x =>
x is MenuItem m && m.Header.ToString() == dir.Header && m.Tag.ToString() == dir.DirectoryPath));
}
public void Add(CustomDirectory dir)
{
_directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) });
}
public void Edit(int index, CustomDirectory newDir)
{
if (_directories.ElementAt(index) is not MenuItem dir) return;
dir.Header = newDir.Header;
dir.Tag = newDir.DirectoryPath;
}
public void Delete(int index)
{
_directories.RemoveAt(index);
}
public void Save()
{
var cd = new List<CustomDirectory>();
for (var i = 2; i < _directories.Count; i++)
{
if (_directories[i] is not MenuItem m) continue;
cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString()));
}
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch))
UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd;
else UserSettings.Default.CustomDirectories[_game] = cd;
}
private IEnumerable<Control> EnumerateDirectories()
{
yield return new MenuItem
{
Header = "Add Directory",
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/add_directory.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand
};
yield return new Separator();
IList<CustomDirectory> cd;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings))
cd = settings.CustomDirectories;
else cd = UserSettings.Default.CustomDirectories[_game];
foreach (var setting in cd)
{
if (setting.DirectoryPath.EndsWith('/'))
setting.DirectoryPath = setting.DirectoryPath[..^1];
yield return new MenuItem
{
Header = setting.Header,
Tag = setting.DirectoryPath,
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
ItemsSource = EnumerateCommands(setting)
};
}
}
private IEnumerable<MenuItem> EnumerateCommands(CustomDirectory dir)
{
yield return new MenuItem
{
Header = "Go To",
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/go_to_directory.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = GoToCommand,
CommandParameter = dir.DirectoryPath
};
yield return new MenuItem
{
Header = "Edit Directory",
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/edit.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = AddEditDirectoryCommand,
CommandParameter = dir
};
yield return new MenuItem
{
Header = "Delete Directory",
StaysOpenOnClick = true,
Icon = new Image { Source = new BitmapImage(new Uri("/FModel;component/Resources/delete.png", UriKind.Relative)) },
HorizontalContentAlignment = HorizontalAlignment.Left,
VerticalContentAlignment = VerticalAlignment.Center,
Command = DeleteDirectoryCommand,
CommandParameter = dir
};
}
}

View File

@@ -6,110 +6,109 @@ using System.Windows.Data;
using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.UE4.Vfs;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class FileItem : ViewModel
{
public class FileItem : ViewModel
private string _name;
public string Name
{
private string _name;
public string Name
{
get => _name;
private set => SetProperty(ref _name, value);
}
get => _name;
private set => SetProperty(ref _name, value);
}
private long _length;
public long Length
{
get => _length;
private set => SetProperty(ref _length, value);
}
private long _length;
public long Length
{
get => _length;
private set => SetProperty(ref _length, value);
}
private int _fileCount;
public int FileCount
{
get => _fileCount;
set => SetProperty(ref _fileCount, value);
}
private int _fileCount;
public int FileCount
{
get => _fileCount;
set => SetProperty(ref _fileCount, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
set => SetProperty(ref _mountPoint, value);
}
private string _mountPoint;
public string MountPoint
{
get => _mountPoint;
set => SetProperty(ref _mountPoint, value);
}
private bool _isEncrypted;
public bool IsEncrypted
{
get => _isEncrypted;
set => SetProperty(ref _isEncrypted, value);
}
private bool _isEncrypted;
public bool IsEncrypted
{
get => _isEncrypted;
set => SetProperty(ref _isEncrypted, value);
}
private bool _isEnabled;
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private bool _isEnabled;
public bool IsEnabled
{
get => _isEnabled;
set => SetProperty(ref _isEnabled, value);
}
private string _key;
public string Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private string _key;
public string Key
{
get => _key;
set => SetProperty(ref _key, value);
}
private FGuid _guid;
public FGuid Guid
{
get => _guid;
set => SetProperty(ref _guid, value);
}
private FGuid _guid;
public FGuid Guid
{
get => _guid;
set => SetProperty(ref _guid, value);
}
public FileItem(string name, long length)
{
Name = name;
Length = length;
}
public FileItem(string name, long length)
{
Name = name;
Length = length;
}
public override string ToString()
public override string ToString()
{
return $"{Name} | {Key}";
}
}
public class GameDirectoryViewModel : ViewModel
{
public bool HasNoFile => DirectoryFiles.Count < 1;
public readonly ObservableCollection<FileItem> DirectoryFiles;
public ICollectionView DirectoryFilesView { get; }
public GameDirectoryViewModel()
{
DirectoryFiles = new ObservableCollection<FileItem>();
DirectoryFilesView = new ListCollectionView(DirectoryFiles) { SortDescriptions = { new SortDescription("Name", ListSortDirection.Ascending) } };
}
public void DeactivateAll()
{
foreach (var file in DirectoryFiles)
{
return $"{Name} | {Key}";
file.IsEnabled = false;
}
}
public class GameDirectoryViewModel : ViewModel
public void Add(IAesVfsReader reader)
{
public bool HasNoFile => DirectoryFiles.Count < 1;
public readonly ObservableCollection<FileItem> DirectoryFiles;
public ICollectionView DirectoryFilesView { get; }
public GameDirectoryViewModel()
Application.Current.Dispatcher.Invoke(() =>
{
DirectoryFiles = new ObservableCollection<FileItem>();
DirectoryFilesView = new ListCollectionView(DirectoryFiles) {SortDescriptions = {new SortDescription("Name", ListSortDirection.Ascending)}};
}
public void DeactivateAll()
{
foreach (var file in DirectoryFiles)
DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
{
file.IsEnabled = false;
}
}
public void Add(IAesVfsReader reader)
{
Application.Current.Dispatcher.Invoke(() =>
{
DirectoryFiles.Add(new FileItem(reader.Name, reader.Length)
{
Guid = reader.EncryptionKeyGuid,
IsEncrypted = reader.IsEncrypted,
IsEnabled = false,
Key = string.Empty
});
Guid = reader.EncryptionKeyGuid,
IsEncrypted = reader.IsEncrypted,
IsEnabled = false,
Key = string.Empty
});
}
});
}
}

View File

@@ -14,356 +14,355 @@ using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
using Microsoft.Win32;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class GameSelectorViewModel : ViewModel
{
public class GameSelectorViewModel : ViewModel
public class DetectedGame
{
public class DetectedGame
public string GameName { get; set; }
public string GameDirectory { get; set; }
public bool IsManual { get; set; }
// the followings are only used when game is manually added
public AesResponse AesKeys { get; set; }
public EGame OverridedGame { get; set; }
public List<FCustomVersion> OverridedCustomVersions { get; set; }
public Dictionary<string, bool> OverridedOptions { get; set; }
public IList<CustomDirectory> CustomDirectories { get; set; }
}
private DetectedGame _selectedDetectedGame;
public DetectedGame SelectedDetectedGame
{
get => _selectedDetectedGame;
set => SetProperty(ref _selectedDetectedGame, value);
}
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
public GameSelectorViewModel(string gameDirectory)
{
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
foreach (var game in UserSettings.Default.ManualGames.Values)
{
public string GameName { get; set; }
public string GameDirectory { get; set; }
public bool IsManual { get; set; }
// the followings are only used when game is manually added
public AesResponse AesKeys { get; set; }
public EGame OverridedGame { get; set; }
public List<FCustomVersion> OverridedCustomVersions { get; set; }
public Dictionary<string, bool> OverridedOptions { get; set; }
public IList<CustomDirectory> CustomDirectories { get; set; }
}
private DetectedGame _selectedDetectedGame;
public DetectedGame SelectedDetectedGame
{
get => _selectedDetectedGame;
set => SetProperty(ref _selectedDetectedGame, value);
}
private readonly ObservableCollection<DetectedGame> _autoDetectedGames;
public ReadOnlyObservableCollection<DetectedGame> AutoDetectedGames { get; }
public GameSelectorViewModel(string gameDirectory)
{
_autoDetectedGames = new ObservableCollection<DetectedGame>(EnumerateDetectedGames().Where(x => x != null));
foreach (var game in UserSettings.Default.ManualGames.Values)
{
_autoDetectedGames.Add(game);
}
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
SelectedDetectedGame = detectedGame;
else if (!string.IsNullOrEmpty(gameDirectory))
AddUnknownGame(gameDirectory);
else
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
}
/// <summary>
/// dedicated to manual games
/// </summary>
public void AddUnknownGame(string gameName, string gameDirectory)
{
var game = new DetectedGame
{
GameName = gameName,
GameDirectory = gameDirectory,
IsManual = true,
AesKeys = null,
OverridedGame = EGame.GAME_UE4_LATEST,
OverridedCustomVersions = null,
OverridedOptions = null,
CustomDirectories = new List<CustomDirectory>()
};
UserSettings.Default.ManualGames[gameDirectory] = game;
_autoDetectedGames.Add(game);
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void AddUnknownGame(string gameDirectory)
{
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
SelectedDetectedGame = AutoDetectedGames.Last();
}
AutoDetectedGames = new ReadOnlyObservableCollection<DetectedGame>(_autoDetectedGames);
public void DeleteSelectedGame()
{
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
_autoDetectedGames.Remove(SelectedDetectedGame);
SelectedDetectedGame = AutoDetectedGames.Last();
}
if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame)
SelectedDetectedGame = detectedGame;
else if (!string.IsNullOrEmpty(gameDirectory))
AddUnknownGame(gameDirectory);
else
SelectedDetectedGame = AutoDetectedGames.FirstOrDefault();
}
private IEnumerable<DetectedGame> EnumerateDetectedGames()
/// <summary>
/// dedicated to manual games
/// </summary>
public void AddUnknownGame(string gameName, string gameDirectory)
{
var game = new DetectedGame
{
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content");
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
}
GameName = gameName,
GameDirectory = gameDirectory,
IsManual = true,
AesKeys = null,
OverridedGame = EGame.GAME_UE4_LATEST,
OverridedCustomVersions = null,
OverridedOptions = null,
CustomDirectories = new List<CustomDirectory>()
};
private LauncherInstalled _launcherInstalled;
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
UserSettings.Default.ManualGames[gameDirectory] = game;
_autoDetectedGames.Add(game);
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void AddUnknownGame(string gameDirectory)
{
_autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory });
SelectedDetectedGame = AutoDetectedGames.Last();
}
public void DeleteSelectedGame()
{
UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem
_autoDetectedGames.Remove(SelectedDetectedGame);
SelectedDetectedGame = AutoDetectedGames.Last();
}
private IEnumerable<DetectedGame> EnumerateDetectedGames()
{
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER };
yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks");
yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks");
yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks");
yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks");
yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content");
yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks");
yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks");
yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks");
yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks");
yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content");
yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks");
yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER };
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
}
private LauncherInstalled _launcherInstalled;
private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory)
{
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
if (_launcherInstalled?.InstallationList != null)
{
_launcherInstalled ??= GetDriveLauncherInstalls<LauncherInstalled>("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat");
if (_launcherInstalled?.InstallationList != null)
foreach (var installationList in _launcherInstalled.InstallationList)
{
foreach (var installationList in _launcherInstalled.InstallationList)
{
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
}
if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = installationList.AppName, GameDirectory = $"{installationList.InstallLocation}{pakDirectory}" };
}
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
return null;
}
private RiotClientInstalls _riotClientInstalls;
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
Log.Warning("Could not find {GameName} in LauncherInstalled.dat", gameName);
return null;
}
private RiotClientInstalls _riotClientInstalls;
private DetectedGame GetRiotGame(string gameName, string pakDirectory)
{
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is { AssociatedClient: { } })
{
_riotClientInstalls ??= GetDriveLauncherInstalls<RiotClientInstalls>("ProgramData\\Riot Games\\RiotClientInstalls.json");
if (_riotClientInstalls is { AssociatedClient: { } })
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
{
foreach (var (key, _) in _riotClientInstalls.AssociatedClient)
{
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
}
if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase))
return new DetectedGame { GameName = gameName, GameDirectory = $"{key.Replace('/', '\\')}{pakDirectory}" };
}
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
return null;
}
private LauncherSettings _launcherSettings;
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
Log.Warning("Could not find {GameName} in RiotClientInstalls.json", gameName);
return null;
}
private LauncherSettings _launcherSettings;
private DetectedGame GetMojangGame(string gameName, string pakDirectory)
{
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
if (_launcherSettings is { ProductLibraryDir: { } })
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
return null;
}
private DetectedGame GetSteamGame(int id, string pakDirectory)
{
var steamInfo = SteamDetection.GetSteamGameById(id);
if (steamInfo is not null)
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
Log.Warning("Could not find {GameId} in steam manifests", id);
return null;
}
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
{
var installLocation = string.Empty;
try
{
_launcherSettings ??= GetDataLauncherInstalls<LauncherSettings>("\\.minecraft\\launcher_settings.json");
if (_launcherSettings is { ProductLibraryDir: { } })
return new DetectedGame { GameName = gameName, GameDirectory = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}" };
Log.Warning("Could not find {GameName} in launcher_settings.json", gameName);
return null;
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
}
private DetectedGame GetSteamGame(int id, string pakDirectory)
catch
{
var steamInfo = SteamDetection.GetSteamGameById(id);
if (steamInfo is not null)
return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" };
Log.Warning("Could not find {GameId} in steam manifests", id);
return null;
// ignored
}
private DetectedGame GetRockstarGamesGame(string key, string pakDirectory)
if (!string.IsNullOrEmpty(installLocation))
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
Log.Warning("Could not find {GameName} in the registry", key);
return null;
}
private T GetDriveLauncherInstalls<T>(string jsonFile)
{
foreach (var drive in DriveInfo.GetDrives())
{
var installLocation = string.Empty;
try
{
installLocation = App.GetRegistryValue(@$"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{key}", "InstallLocation", RegistryHive.LocalMachine);
}
catch
{
// ignored
}
var launcher = $"{drive.Name}{jsonFile}";
if (!File.Exists(launcher)) continue;
if (!string.IsNullOrEmpty(installLocation))
return new DetectedGame { GameName = key, GameDirectory = $"{installLocation}{pakDirectory}" };
Log.Warning("Could not find {GameName} in the registry", key);
return null;
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
private T GetDriveLauncherInstalls<T>(string jsonFile)
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
return default;
}
private T GetDataLauncherInstalls<T>(string jsonFile)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var launcher = $"{appData}{jsonFile}";
if (File.Exists(launcher))
{
foreach (var drive in DriveInfo.GetDrives())
{
var launcher = $"{drive.Name}{jsonFile}";
if (!File.Exists(launcher)) continue;
Log.Information("\"{Launcher}\" found in drive \"{DriveName}\"", launcher, drive.Name);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
Log.Warning("\"{JsonFile}\" not found in any drives", jsonFile);
return default;
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
private T GetDataLauncherInstalls<T>(string jsonFile)
{
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var launcher = $"{appData}{jsonFile}";
if (File.Exists(launcher))
{
Log.Information("\"{Launcher}\" found in \"{AppData}\"", launcher, appData);
return JsonConvert.DeserializeObject<T>(File.ReadAllText(launcher));
}
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
return default;
}
Log.Warning("\"{Json}\" not found anywhere", jsonFile);
return default;
}
#pragma warning disable 649
private class LauncherInstalled
{
public Installation[] InstallationList;
}
private class LauncherInstalled
{
public Installation[] InstallationList;
}
private class Installation
{
public string InstallLocation;
public string AppName;
public string AppVersion;
}
private class Installation
{
public string InstallLocation;
public string AppName;
public string AppVersion;
}
private class RiotClientInstalls
{
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> AssociatedClient;
private class RiotClientInstalls
{
[JsonProperty("associated_client", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> AssociatedClient;
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> Patchlines;
[JsonProperty("patchlines", NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, string> Patchlines;
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
public string RcDefault;
[JsonProperty("rc_default", NullValueHandling = NullValueHandling.Ignore)]
public string RcDefault;
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
public string RcLive;
}
[JsonProperty("rc_live", NullValueHandling = NullValueHandling.Ignore)]
public string RcLive;
}
private class LauncherSettings
{
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
public string Channel;
private class LauncherSettings
{
[JsonProperty("channel", NullValueHandling = NullValueHandling.Ignore)]
public string Channel;
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
public object[] CustomChannels;
[JsonProperty("customChannels", NullValueHandling = NullValueHandling.Ignore)]
public object[] CustomChannels;
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceId;
[JsonProperty("deviceId", NullValueHandling = NullValueHandling.Ignore)]
public string DeviceId;
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
public int FormatVersion;
[JsonProperty("formatVersion", NullValueHandling = NullValueHandling.Ignore)]
public int FormatVersion;
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale;
[JsonProperty("locale", NullValueHandling = NullValueHandling.Ignore)]
public string Locale;
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
public string ProductLibraryDir;
}
[JsonProperty("productLibraryDir", NullValueHandling = NullValueHandling.Ignore)]
public string ProductLibraryDir;
}
#pragma warning restore 649
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
public static class SteamDetection
// https://stackoverflow.com/questions/54767662/finding-game-launcher-executables-in-directory-c-sharp/67679123#67679123
public static class SteamDetection
{
private static readonly List<AppInfo> _steamApps;
static SteamDetection()
{
private static readonly List<AppInfo> _steamApps;
_steamApps = GetSteamApps(GetSteamLibs());
}
static SteamDetection()
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
{
var apps = new List<AppInfo>();
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
{
_steamApps = GetSteamApps(GetSteamLibs());
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
}
public static AppInfo GetSteamGameById(int id) => _steamApps.FirstOrDefault(app => app.Id == id.ToString());
return apps;
}
private static List<AppInfo> GetSteamApps(IEnumerable<string> steamLibs)
private static AppInfo GetAppInfo(string appMetaFile)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
{
var apps = new List<AppInfo>();
foreach (var files in steamLibs.Select(lib => Path.Combine(lib, "SteamApps")).Select(appMetaDataPath => Directory.GetFiles(appMetaDataPath, "*.acf")))
{
apps.AddRange(files.Select(GetAppInfo).Where(appInfo => appInfo != null));
}
return apps;
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (!match.Success) continue;
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
}
private static AppInfo GetAppInfo(string appMetaFile)
if (dic.Keys.Count <= 0) return null;
AppInfo appInfo = new();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.GameRoot = libGameRoot;
return appInfo;
}
private static List<string> GetSteamLibs()
{
var steamPath = GetSteamPath();
if (steamPath == null) return new List<string>();
var libraries = new List<string> { steamPath };
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
if (!File.Exists(listFile)) return new List<string>();
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var fileDataLines = File.ReadAllLines(appMetaFile);
var dic = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var line in fileDataLines)
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
if (!match.Success) continue;
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
if (Directory.Exists(path))
{
var match = Regex.Match(line, @"\s*""(?<key>\w+)""\s+""(?<val>.*)""");
if (!match.Success) continue;
var key = match.Groups["key"].Value;
var val = match.Groups["val"].Value;
dic[key] = val;
libraries.Add(path);
}
if (dic.Keys.Count <= 0) return null;
AppInfo appInfo = new();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.GameRoot = libGameRoot;
return appInfo;
}
private static List<string> GetSteamLibs()
return libraries;
}
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
public class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string GameRoot { get; internal set; }
public override string ToString()
{
var steamPath = GetSteamPath();
if (steamPath == null) return new List<string>();
var libraries = new List<string> { steamPath };
var listFile = Path.Combine(steamPath, @"steamapps\libraryfolders.vdf");
if (!File.Exists(listFile)) return new List<string>();
var lines = File.ReadAllLines(listFile);
foreach (var line in lines)
{
var match = Regex.Match(line, @"""(?<path>\w:\\\\.*)""");
if (!match.Success) continue;
var path = match.Groups["path"].Value.Replace(@"\\", @"\");
if (Directory.Exists(path))
{
libraries.Add(path);
}
}
return libraries;
}
private static string GetSteamPath() => (string) Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam", "InstallPath", ""); // Win64, we don't support Win32
public class AppInfo
{
public string Id { get; internal set; }
public string Name { get; internal set; }
public string GameRoot { get; internal set; }
public override string ToString()
{
return $"{Name} ({Id})";
}
return $"{Name} ({Id})";
}
}
}
}
}

View File

@@ -4,20 +4,19 @@ using System.Collections.ObjectModel;
using FModel.Framework;
using FModel.ViewModels.Commands;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class LoadingModesViewModel : ViewModel
{
public class LoadingModesViewModel : ViewModel
private LoadCommand _loadCommand;
public LoadCommand LoadCommand => _loadCommand ??= new LoadCommand(this);
public ReadOnlyObservableCollection<ELoadingMode> Modes { get; }
public LoadingModesViewModel()
{
private LoadCommand _loadCommand;
public LoadCommand LoadCommand => _loadCommand ??= new LoadCommand(this);
public ReadOnlyObservableCollection<ELoadingMode> Modes { get; }
public LoadingModesViewModel()
{
Modes = new ReadOnlyObservableCollection<ELoadingMode>(new ObservableCollection<ELoadingMode>(EnumerateLoadingModes()));
}
private IEnumerable<ELoadingMode> EnumerateLoadingModes() => Enum.GetValues<ELoadingMode>();
Modes = new ReadOnlyObservableCollection<ELoadingMode>(new ObservableCollection<ELoadingMode>(EnumerateLoadingModes()));
}
}
private IEnumerable<ELoadingMode> EnumerateLoadingModes() => Enum.GetValues<ELoadingMode>();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -6,61 +6,59 @@ using System.Text.RegularExpressions;
using System.Windows.Data;
using FModel.Framework;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class SearchViewModel : ViewModel
{
public class SearchViewModel : ViewModel
private string _filterText;
public string FilterText
{
private string _filterText;
public string FilterText
{
get => _filterText;
set => SetProperty(ref _filterText, value);
}
get => _filterText;
set => SetProperty(ref _filterText, value);
}
private bool _hasRegexEnabled;
public bool HasRegexEnabled
{
get => _hasRegexEnabled;
set => SetProperty(ref _hasRegexEnabled, value);
}
private bool _hasRegexEnabled;
public bool HasRegexEnabled
{
get => _hasRegexEnabled;
set => SetProperty(ref _hasRegexEnabled, value);
}
private bool _hasMatchCaseEnabled;
public bool HasMatchCaseEnabled
{
get => _hasMatchCaseEnabled;
set => SetProperty(ref _hasMatchCaseEnabled, value);
}
private bool _hasMatchCaseEnabled;
public bool HasMatchCaseEnabled
{
get => _hasMatchCaseEnabled;
set => SetProperty(ref _hasMatchCaseEnabled, value);
}
public int ResultsCount => SearchResults?.Count ?? 0;
public RangeObservableCollection<AssetItem> SearchResults { get; }
public ICollectionView SearchResultsView { get; }
public int ResultsCount => SearchResults?.Count ?? 0;
public RangeObservableCollection<AssetItem> SearchResults { get; }
public ICollectionView SearchResultsView { get; }
public SearchViewModel()
{
SearchResults = new RangeObservableCollection<AssetItem>();
SearchResultsView = new ListCollectionView(SearchResults);
}
public SearchViewModel()
{
SearchResults = new RangeObservableCollection<AssetItem>();
SearchResultsView = new ListCollectionView(SearchResults);
}
public void RefreshFilter()
{
if (SearchResultsView.Filter == null)
SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' '));
else
SearchResultsView.Refresh();
}
public void RefreshFilter()
{
if (SearchResultsView.Filter == null)
SearchResultsView.Filter = e => ItemFilter(e, FilterText.Trim().Split(' '));
else
SearchResultsView.Refresh();
}
private bool ItemFilter(object item, IEnumerable<string> filters)
{
if (item is not AssetItem assetItem)
return true;
private bool ItemFilter(object item, IEnumerable<string> filters)
{
if (item is not AssetItem assetItem)
return true;
if (!HasRegexEnabled)
return filters.All(x => assetItem.FullPath.IndexOf(x,
HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase) >= 0);
if (!HasRegexEnabled)
return filters.All(x => assetItem.FullPath.Contains(x, HasMatchCaseEnabled ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase));
var o = RegexOptions.None;
if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
}
var o = RegexOptions.None;
if (HasMatchCaseEnabled) o |= RegexOptions.IgnoreCase;
return new Regex(FilterText, o).Match(assetItem.FullPath).Success;
}
}

View File

@@ -14,323 +14,324 @@ using FModel.Services;
using FModel.Settings;
using FModel.ViewModels.ApiEndpoints.Models;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class SettingsViewModel : ViewModel
{
public class SettingsViewModel : ViewModel
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
private EUpdateMode _selectedUpdateMode;
public EUpdateMode SelectedUpdateMode
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler;
private EUpdateMode _selectedUpdateMode;
public EUpdateMode SelectedUpdateMode
{
get => _selectedUpdateMode;
set => SetProperty(ref _selectedUpdateMode, value);
}
private string _selectedPreset;
public string SelectedPreset
{
get => _selectedPreset;
set
{
SetProperty(ref _selectedPreset, value);
RaisePropertyChanged("EnableElements");
}
}
private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform
{
get => _selectedUePlatform;
set => SetProperty(ref _selectedUePlatform, value);
}
private EGame _selectedUeGame;
public EGame SelectedUeGame
{
get => _selectedUeGame;
set => SetProperty(ref _selectedUeGame, value);
}
private List<FCustomVersion> _selectedCustomVersions;
public List<FCustomVersion> SelectedCustomVersions
{
get => _selectedCustomVersions;
set => SetProperty(ref _selectedCustomVersions, value);
}
private Dictionary<string, bool> _selectedOptions;
public Dictionary<string, bool> SelectedOptions
{
get => _selectedOptions;
set => SetProperty(ref _selectedOptions, value);
}
private ELanguage _selectedAssetLanguage;
public ELanguage SelectedAssetLanguage
{
get => _selectedAssetLanguage;
set => SetProperty(ref _selectedAssetLanguage, value);
}
private EAesReload _selectedAesReload;
public EAesReload SelectedAesReload
{
get => _selectedAesReload;
set => SetProperty(ref _selectedAesReload, value);
}
private EDiscordRpc _selectedDiscordRpc;
public EDiscordRpc SelectedDiscordRpc
{
get => _selectedDiscordRpc;
set => SetProperty(ref _selectedDiscordRpc, value);
}
private ECompressedAudio _selectedCompressedAudio;
public ECompressedAudio SelectedCompressedAudio
{
get => _selectedCompressedAudio;
set => SetProperty(ref _selectedCompressedAudio, value);
}
private EIconStyle _selectedCosmeticStyle;
public EIconStyle SelectedCosmeticStyle
{
get => _selectedCosmeticStyle;
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
get => _selectedMeshExportFormat;
set => SetProperty(ref _selectedMeshExportFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
get => _selectedTextureExportFormat;
set => SetProperty(ref _selectedTextureExportFormat, value);
}
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
public ObservableCollection<string> Presets { get; private set; }
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
private readonly FGame _game;
private Game _gamePreset;
private string _outputSnapshot;
private string _rawDataSnapshot;
private string _propertiesSnapshot;
private string _textureSnapshot;
private string _audioSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private EUpdateMode _updateModeSnapshot;
private string _presetSnapshot;
private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot;
private List<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot;
private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
public SettingsViewModel(FGame game)
{
_game = game;
}
public void Initialize()
{
_outputSnapshot = UserSettings.Default.OutputDirectory;
_rawDataSnapshot = UserSettings.Default.RawDataDirectory;
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
_textureSnapshot = UserSettings.Default.TextureDirectory;
_audioSnapshot = UserSettings.Default.AudioDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_updateModeSnapshot = UserSettings.Default.UpdateMode;
_presetSnapshot = UserSettings.Default.Presets[_game];
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
{
_ueGameSnapshot = settings.OverridedGame;
_customVersionsSnapshot = settings.OverridedCustomVersions;
_optionsSnapshot = settings.OverridedOptions;
}
else
{
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
}
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
SelectedUpdateMode = _updateModeSnapshot;
SelectedPreset = _presetSnapshot;
SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
SelectedAesReload = UserSettings.Default.AesReload;
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
Presets = new ObservableCollection<string>(EnumeratePresets());
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
}
public async Task InitPresets(string gameName)
{
await _threadWorkerView.Begin(cancellationToken =>
{
if (string.IsNullOrEmpty(gameName)) return;
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
});
if (_gamePreset?.Versions == null) return;
foreach (var version in _gamePreset.Versions.Keys)
{
Presets.Add(version);
}
}
public void SwitchPreset(string key)
{
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
SelectedCustomVersions = new List<FCustomVersion>();
foreach (var (guid, v) in version.CustomVersions)
{
SelectedCustomVersions.Add(new FCustomVersion {Key = new FGuid(guid), Version = v});
}
SelectedOptions = new Dictionary<string, bool>();
foreach (var (k, v) in version.Options)
{
SelectedOptions[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
}
public SettingsOut Save()
{
var ret = SettingsOut.Nothing;
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
ret = SettingsOut.Restart;
if (_assetLanguageSnapshot != SelectedAssetLanguage)
ret = SettingsOut.ReloadLocres;
if (_updateModeSnapshot != SelectedUpdateMode)
ret = SettingsOut.CheckForUpdates;
UserSettings.Default.UpdateMode = SelectedUpdateMode;
UserSettings.Default.Presets[_game] = SelectedPreset;
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
{
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
}
else
{
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
}
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
UserSettings.Default.AesReload = SelectedAesReload;
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
if (SelectedDiscordRpc == EDiscordRpc.Never)
_discordHandler.Shutdown();
return ret;
}
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
private IEnumerable<string> EnumeratePresets()
{
yield return Constants._NO_PRESET_TRIGGER;
}
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
get => _selectedUpdateMode;
set => SetProperty(ref _selectedUpdateMode, value);
}
}
private string _selectedPreset;
public string SelectedPreset
{
get => _selectedPreset;
set
{
SetProperty(ref _selectedPreset, value);
RaisePropertyChanged("EnableElements");
}
}
private ETexturePlatform _selectedUePlatform;
public ETexturePlatform SelectedUePlatform
{
get => _selectedUePlatform;
set => SetProperty(ref _selectedUePlatform, value);
}
private EGame _selectedUeGame;
public EGame SelectedUeGame
{
get => _selectedUeGame;
set => SetProperty(ref _selectedUeGame, value);
}
private List<FCustomVersion> _selectedCustomVersions;
public List<FCustomVersion> SelectedCustomVersions
{
get => _selectedCustomVersions;
set => SetProperty(ref _selectedCustomVersions, value);
}
private Dictionary<string, bool> _selectedOptions;
public Dictionary<string, bool> SelectedOptions
{
get => _selectedOptions;
set => SetProperty(ref _selectedOptions, value);
}
private ELanguage _selectedAssetLanguage;
public ELanguage SelectedAssetLanguage
{
get => _selectedAssetLanguage;
set => SetProperty(ref _selectedAssetLanguage, value);
}
private EAesReload _selectedAesReload;
public EAesReload SelectedAesReload
{
get => _selectedAesReload;
set => SetProperty(ref _selectedAesReload, value);
}
private EDiscordRpc _selectedDiscordRpc;
public EDiscordRpc SelectedDiscordRpc
{
get => _selectedDiscordRpc;
set => SetProperty(ref _selectedDiscordRpc, value);
}
private ECompressedAudio _selectedCompressedAudio;
public ECompressedAudio SelectedCompressedAudio
{
get => _selectedCompressedAudio;
set => SetProperty(ref _selectedCompressedAudio, value);
}
private EIconStyle _selectedCosmeticStyle;
public EIconStyle SelectedCosmeticStyle
{
get => _selectedCosmeticStyle;
set => SetProperty(ref _selectedCosmeticStyle, value);
}
private EMeshFormat _selectedMeshExportFormat;
public EMeshFormat SelectedMeshExportFormat
{
get => _selectedMeshExportFormat;
set => SetProperty(ref _selectedMeshExportFormat, value);
}
private ELodFormat _selectedLodExportFormat;
public ELodFormat SelectedLodExportFormat
{
get => _selectedLodExportFormat;
set => SetProperty(ref _selectedLodExportFormat, value);
}
private ETextureFormat _selectedTextureExportFormat;
public ETextureFormat SelectedTextureExportFormat
{
get => _selectedTextureExportFormat;
set => SetProperty(ref _selectedTextureExportFormat, value);
}
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
public ObservableCollection<string> Presets { get; private set; }
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
public ReadOnlyObservableCollection<ECompressedAudio> CompressedAudios { get; private set; }
public ReadOnlyObservableCollection<EIconStyle> CosmeticStyles { get; private set; }
public ReadOnlyObservableCollection<EMeshFormat> MeshExportFormats { get; private set; }
public ReadOnlyObservableCollection<ELodFormat> LodExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETextureFormat> TextureExportFormats { get; private set; }
public ReadOnlyObservableCollection<ETexturePlatform> Platforms { get; private set; }
public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER;
private readonly FGame _game;
private Game _gamePreset;
private string _outputSnapshot;
private string _rawDataSnapshot;
private string _propertiesSnapshot;
private string _textureSnapshot;
private string _audioSnapshot;
private string _modelSnapshot;
private string _gameSnapshot;
private EUpdateMode _updateModeSnapshot;
private string _presetSnapshot;
private ETexturePlatform _uePlatformSnapshot;
private EGame _ueGameSnapshot;
private List<FCustomVersion> _customVersionsSnapshot;
private Dictionary<string, bool> _optionsSnapshot;
private ELanguage _assetLanguageSnapshot;
private ECompressedAudio _compressedAudioSnapshot;
private EIconStyle _cosmeticStyleSnapshot;
private EMeshFormat _meshExportFormatSnapshot;
private ELodFormat _lodExportFormatSnapshot;
private ETextureFormat _textureExportFormatSnapshot;
public SettingsViewModel(FGame game)
{
_game = game;
}
public void Initialize()
{
_outputSnapshot = UserSettings.Default.OutputDirectory;
_rawDataSnapshot = UserSettings.Default.RawDataDirectory;
_propertiesSnapshot = UserSettings.Default.PropertiesDirectory;
_textureSnapshot = UserSettings.Default.TextureDirectory;
_audioSnapshot = UserSettings.Default.AudioDirectory;
_modelSnapshot = UserSettings.Default.ModelDirectory;
_gameSnapshot = UserSettings.Default.GameDirectory;
_updateModeSnapshot = UserSettings.Default.UpdateMode;
_presetSnapshot = UserSettings.Default.Presets[_game];
_uePlatformSnapshot = UserSettings.Default.OverridedPlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings))
{
_ueGameSnapshot = settings.OverridedGame;
_customVersionsSnapshot = settings.OverridedCustomVersions;
_optionsSnapshot = settings.OverridedOptions;
}
else
{
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
}
_assetLanguageSnapshot = UserSettings.Default.AssetLanguage;
_compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode;
_cosmeticStyleSnapshot = UserSettings.Default.CosmeticStyle;
_meshExportFormatSnapshot = UserSettings.Default.MeshExportFormat;
_lodExportFormatSnapshot = UserSettings.Default.LodExportFormat;
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
SelectedUpdateMode = _updateModeSnapshot;
SelectedPreset = _presetSnapshot;
SelectedUePlatform = _uePlatformSnapshot;
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
SelectedAssetLanguage = _assetLanguageSnapshot;
SelectedCompressedAudio = _compressedAudioSnapshot;
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
SelectedMeshExportFormat = _meshExportFormatSnapshot;
SelectedLodExportFormat = _lodExportFormatSnapshot;
SelectedTextureExportFormat = _textureExportFormatSnapshot;
SelectedAesReload = UserSettings.Default.AesReload;
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
Presets = new ObservableCollection<string>(EnumeratePresets());
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
AssetLanguages = new ReadOnlyObservableCollection<ELanguage>(new ObservableCollection<ELanguage>(EnumerateAssetLanguages()));
AesReloads = new ReadOnlyObservableCollection<EAesReload>(new ObservableCollection<EAesReload>(EnumerateAesReloads()));
DiscordRpcs = new ReadOnlyObservableCollection<EDiscordRpc>(new ObservableCollection<EDiscordRpc>(EnumerateDiscordRpcs()));
CompressedAudios = new ReadOnlyObservableCollection<ECompressedAudio>(new ObservableCollection<ECompressedAudio>(EnumerateCompressedAudios()));
CosmeticStyles = new ReadOnlyObservableCollection<EIconStyle>(new ObservableCollection<EIconStyle>(EnumerateCosmeticStyles()));
MeshExportFormats = new ReadOnlyObservableCollection<EMeshFormat>(new ObservableCollection<EMeshFormat>(EnumerateMeshExportFormat()));
LodExportFormats = new ReadOnlyObservableCollection<ELodFormat>(new ObservableCollection<ELodFormat>(EnumerateLodExportFormat()));
TextureExportFormats = new ReadOnlyObservableCollection<ETextureFormat>(new ObservableCollection<ETextureFormat>(EnumerateTextureExportFormat()));
Platforms = new ReadOnlyObservableCollection<ETexturePlatform>(new ObservableCollection<ETexturePlatform>(EnumerateUePlatforms()));
}
public async Task InitPresets(string gameName)
{
await _threadWorkerView.Begin(cancellationToken =>
{
if (string.IsNullOrEmpty(gameName)) return;
_gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName);
});
if (_gamePreset?.Versions == null) return;
foreach (var version in _gamePreset.Versions.Keys)
{
Presets.Add(version);
}
}
public void SwitchPreset(string key)
{
if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return;
SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST);
SelectedCustomVersions = new List<FCustomVersion>();
foreach (var (guid, v) in version.CustomVersions)
{
SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v });
}
SelectedOptions = new Dictionary<string, bool>();
foreach (var (k, v) in version.Options)
{
SelectedOptions[k] = v;
}
}
public void ResetPreset()
{
SelectedUeGame = _ueGameSnapshot;
SelectedCustomVersions = _customVersionsSnapshot;
SelectedOptions = _optionsSnapshot;
}
public SettingsOut Save()
{
var ret = SettingsOut.Nothing;
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
_textureSnapshot != UserSettings.Default.TextureDirectory || // textbox
_audioSnapshot != UserSettings.Default.AudioDirectory || // textbox
_modelSnapshot != UserSettings.Default.ModelDirectory || // textbox
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
ret = SettingsOut.Restart;
if (_assetLanguageSnapshot != SelectedAssetLanguage)
ret = SettingsOut.ReloadLocres;
if (_updateModeSnapshot != SelectedUpdateMode)
ret = SettingsOut.CheckForUpdates;
UserSettings.Default.UpdateMode = SelectedUpdateMode;
UserSettings.Default.Presets[_game] = SelectedPreset;
UserSettings.Default.OverridedPlatform = SelectedUePlatform;
if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
{
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
}
else
{
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
}
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio;
UserSettings.Default.CosmeticStyle = SelectedCosmeticStyle;
UserSettings.Default.MeshExportFormat = SelectedMeshExportFormat;
UserSettings.Default.LodExportFormat = SelectedLodExportFormat;
UserSettings.Default.TextureExportFormat = SelectedTextureExportFormat;
UserSettings.Default.AesReload = SelectedAesReload;
UserSettings.Default.DiscordRpc = SelectedDiscordRpc;
if (SelectedDiscordRpc == EDiscordRpc.Never)
_discordHandler.Shutdown();
return ret;
}
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
private IEnumerable<string> EnumeratePresets()
{
yield return Constants._NO_PRESET_TRIGGER;
}
private IEnumerable<EGame> EnumerateUeGames() => Enum.GetValues<EGame>();
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
private IEnumerable<ECompressedAudio> EnumerateCompressedAudios() => Enum.GetValues<ECompressedAudio>();
private IEnumerable<EIconStyle> EnumerateCosmeticStyles() => Enum.GetValues<EIconStyle>();
private IEnumerable<EMeshFormat> EnumerateMeshExportFormat() => Enum.GetValues<EMeshFormat>();
private IEnumerable<ELodFormat> EnumerateLodExportFormat() => Enum.GetValues<ELodFormat>();
private IEnumerable<ETextureFormat> EnumerateTextureExportFormat() => Enum.GetValues<ETextureFormat>();
private IEnumerable<ETexturePlatform> EnumerateUePlatforms() => Enum.GetValues<ETexturePlatform>();
}

View File

@@ -18,430 +18,437 @@ using System.Windows.Media.Imaging;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse_Conversion.Textures;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class TabImage : ViewModel
{
public class TabImage : ViewModel
public string ExportName { get; }
public byte[] ImageBuffer { get; set; }
public TabImage(string name, bool rnn, SKBitmap img)
{
public string ExportName { get; }
public byte[] ImageBuffer { get; set; }
public TabImage(string name, bool rnn, SKBitmap img)
{
ExportName = name;
RenderNearestNeighbor = rnn;
SetImage(img);
}
private BitmapImage _image;
public BitmapImage Image
{
get => _image;
set
{
if (_image == value) return;
SetProperty(ref _image, value);
}
}
private bool _renderNearestNeighbor;
public bool RenderNearestNeighbor
{
get => _renderNearestNeighbor;
set => SetProperty(ref _renderNearestNeighbor, value);
}
private bool _noAlpha;
public bool NoAlpha
{
get => _noAlpha;
set
{
SetProperty(ref _noAlpha, value);
ResetImage();
}
}
private void SetImage(SKBitmap bitmap)
{
_bmp = bitmap;
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
Image = image;
}
private SKBitmap _bmp;
private void ResetImage() => SetImage(_bmp);
ExportName = name;
RenderNearestNeighbor = rnn;
SetImage(img);
}
public class TabItem : ViewModel
private BitmapImage _image;
public BitmapImage Image
{
private string _header;
public string Header
get => _image;
set
{
get => _header;
set => SetProperty(ref _header, value);
}
private string _directory;
public string Directory
{
get => _directory;
set => SetProperty(ref _directory, value);
}
private bool _hasSearchOpen;
public bool HasSearchOpen
{
get => _hasSearchOpen;
set => SetProperty(ref _hasSearchOpen, value);
}
private string _textToFind;
public string TextToFind
{
get => _textToFind;
set => SetProperty(ref _textToFind, value);
}
private bool _searchUp;
public bool SearchUp
{
get => _searchUp;
set => SetProperty(ref _searchUp, value);
}
private bool _caseSensitive;
public bool CaseSensitive
{
get => _caseSensitive;
set => SetProperty(ref _caseSensitive, value);
}
private bool _useRegEx;
public bool UseRegEx
{
get => _useRegEx;
set => SetProperty(ref _useRegEx, value);
}
private bool _wholeWord;
public bool WholeWord
{
get => _wholeWord;
set => SetProperty(ref _wholeWord, value);
}
private TextDocument _document;
public TextDocument Document
{
get => _document;
set => SetProperty(ref _document, value);
}
private double _fontSize = 11.0;
public double FontSize
{
get => _fontSize;
set => SetProperty(ref _fontSize, value);
}
private double _scrollPosition;
public double ScrollPosition
{
get => _scrollPosition;
set => SetProperty(ref _scrollPosition, value);
}
private string _scrollTrigger;
public string ScrollTrigger
{
get => _scrollTrigger;
set => SetProperty(ref _scrollTrigger, value);
}
private IHighlightingDefinition _highlighter;
public IHighlightingDefinition Highlighter
{
get => _highlighter;
set
{
if (_highlighter == value) return;
SetProperty(ref _highlighter, value);
}
}
private TabImage _selectedImage;
public TabImage SelectedImage
{
get => _selectedImage;
set
{
if (_selectedImage == value) return;
SetProperty(ref _selectedImage, value);
RaisePropertyChanged("HasImage");
RaisePropertyChanged("Page");
}
}
public bool HasImage => SelectedImage != null;
public bool HasMultipleImages => _images.Count > 1;
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
private readonly ObservableCollection<TabImage> _images;
public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger);
private TabCommand _tabCommand;
public TabCommand TabCommand => _tabCommand ??= new TabCommand(this);
private ImageCommand _imageCommand;
public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this);
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
public TabItem(string header, string directory)
{
Header = header;
Directory = directory;
_images = new ObservableCollection<TabImage>();
}
public void ClearImages()
{
Application.Current.Dispatcher.Invoke(() =>
{
_images.Clear();
SelectedImage = null;
RaisePropertyChanged("HasMultipleImages");
});
}
public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform));
public void AddImage(string name, bool rnn, SKBitmap[] img)
{
foreach (var i in img) AddImage(name, rnn, i);
}
public void AddImage(string name, bool rnn, SKBitmap img)
{
Application.Current.Dispatcher.Invoke(() =>
{
var t = new TabImage(name, rnn, img);
if (UserSettings.Default.IsAutoSaveTextures)
SaveImage(t, true);
_images.Add(t);
SelectedImage ??= t;
RaisePropertyChanged("Page");
RaisePropertyChanged("HasMultipleImages");
});
}
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
public void SetDocumentText(string text, bool bulkSave)
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = text;
if (UserSettings.Default.IsAutoSaveProps || bulkSave)
SaveProperty(true);
});
}
public void ResetDocumentText()
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = string.Empty;
});
}
public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave);
private void SaveImage(TabImage image, bool autoSave)
{
if (image == null) return;
var fileName = $"{image.ExportName}.png";
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
Title = "Save Texture",
FileName = fileName,
InitialDirectory = UserSettings.Default.TextureDirectory,
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
}
SaveCheck(directory, fileName);
}
public void SaveProperty(bool autoSave)
{
var fileName = Path.ChangeExtension(Header, ".json");
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
Title = "Save Property",
FileName = fileName,
InitialDirectory = UserSettings.Default.PropertiesDirectory,
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
SaveCheck(directory, fileName);
}
private void SaveCheck(string path, string fileName)
{
if (File.Exists(path))
{
Log.Information("{FileName} successfully saved", fileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", fileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
}
if (_image == value) return;
SetProperty(ref _image, value);
}
}
public class TabControlViewModel : ViewModel
private bool _renderNearestNeighbor;
public bool RenderNearestNeighbor
{
private TabItem _selectedTab;
public TabItem SelectedTab
get => _renderNearestNeighbor;
set => SetProperty(ref _renderNearestNeighbor, value);
}
private bool _noAlpha;
public bool NoAlpha
{
get => _noAlpha;
set
{
get => _selectedTab;
set => SetProperty(ref _selectedTab, value);
SetProperty(ref _noAlpha, value);
ResetImage();
}
}
private AddTabCommand _addTabCommand;
public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this);
private void SetImage(SKBitmap bitmap)
{
_bmp = bitmap;
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
Image = image;
}
private readonly ObservableCollection<TabItem> _tabItems;
public ReadOnlyObservableCollection<TabItem> TabsItems { get; }
private SKBitmap _bmp;
private void ResetImage() => SetImage(_bmp);
}
public bool HasNoTabs => _tabItems.Count == 0;
public bool CanAddTabs => _tabItems.Count < 25;
public class TabItem : ViewModel
{
private string _header;
public string Header
{
get => _header;
set => SetProperty(ref _header, value);
}
public TabControlViewModel()
private string _directory;
public string Directory
{
get => _directory;
set => SetProperty(ref _directory, value);
}
private bool _hasSearchOpen;
public bool HasSearchOpen
{
get => _hasSearchOpen;
set => SetProperty(ref _hasSearchOpen, value);
}
private string _textToFind;
public string TextToFind
{
get => _textToFind;
set => SetProperty(ref _textToFind, value);
}
private bool _searchUp;
public bool SearchUp
{
get => _searchUp;
set => SetProperty(ref _searchUp, value);
}
private bool _caseSensitive;
public bool CaseSensitive
{
get => _caseSensitive;
set => SetProperty(ref _caseSensitive, value);
}
private bool _useRegEx;
public bool UseRegEx
{
get => _useRegEx;
set => SetProperty(ref _useRegEx, value);
}
private bool _wholeWord;
public bool WholeWord
{
get => _wholeWord;
set => SetProperty(ref _wholeWord, value);
}
private TextDocument _document;
public TextDocument Document
{
get => _document;
set => SetProperty(ref _document, value);
}
private double _fontSize = 11.0;
public double FontSize
{
get => _fontSize;
set => SetProperty(ref _fontSize, value);
}
private double _scrollPosition;
public double ScrollPosition
{
get => _scrollPosition;
set => SetProperty(ref _scrollPosition, value);
}
private string _scrollTrigger;
public string ScrollTrigger
{
get => _scrollTrigger;
set => SetProperty(ref _scrollTrigger, value);
}
private IHighlightingDefinition _highlighter;
public IHighlightingDefinition Highlighter
{
get => _highlighter;
set
{
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
SelectedTab = TabsItems.FirstOrDefault();
if (_highlighter == value) return;
SetProperty(ref _highlighter, value);
}
}
public void AddTab(string header = null, string directory = null)
private TabImage _selectedImage;
public TabImage SelectedImage
{
get => _selectedImage;
set
{
if (!CanAddTabs) return;
if (_selectedImage == value) return;
SetProperty(ref _selectedImage, value);
RaisePropertyChanged("HasImage");
RaisePropertyChanged("Page");
}
}
var h = header ?? "New Tab";
var d = directory ?? string.Empty;
if (SelectedTab is { Header : "New Tab" })
public bool HasImage => SelectedImage != null;
public bool HasMultipleImages => _images.Count > 1;
public string Page => $"{_images.IndexOf(_selectedImage) + 1} / {_images.Count}";
private readonly ObservableCollection<TabImage> _images;
public bool ShouldScroll => !string.IsNullOrEmpty(ScrollTrigger);
private TabCommand _tabCommand;
public TabCommand TabCommand => _tabCommand ??= new TabCommand(this);
private ImageCommand _imageCommand;
public ImageCommand ImageCommand => _imageCommand ??= new ImageCommand(this);
private GoToCommand _goToCommand;
public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null);
public TabItem(string header, string directory)
{
Header = header;
Directory = directory;
_images = new ObservableCollection<TabImage>();
}
public void ClearImages()
{
Application.Current.Dispatcher.Invoke(() =>
{
_images.Clear();
SelectedImage = null;
RaisePropertyChanged("HasMultipleImages");
});
}
public void AddImage(UTexture2D texture) => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform));
public void AddImage(string name, bool rnn, SKBitmap[] img)
{
foreach (var i in img) AddImage(name, rnn, i);
}
public void AddImage(string name, bool rnn, SKBitmap img)
{
Application.Current.Dispatcher.Invoke(() =>
{
var t = new TabImage(name, rnn, img);
if (UserSettings.Default.IsAutoSaveTextures)
SaveImage(t, true);
_images.Add(t);
SelectedImage ??= t;
RaisePropertyChanged("Page");
RaisePropertyChanged("HasMultipleImages");
});
}
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
public void SetDocumentText(string text, bool bulkSave)
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = text;
if (UserSettings.Default.IsAutoSaveProps || bulkSave)
SaveProperty(true);
});
}
public void ResetDocumentText()
{
Application.Current.Dispatcher.Invoke(() =>
{
Document ??= new TextDocument();
Document.Text = string.Empty;
});
}
public void SaveImage(bool autoSave) => SaveImage(SelectedImage, autoSave);
private void SaveImage(TabImage image, bool autoSave)
{
if (image == null) return;
var fileName = $"{image.ExportName}.png";
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
SelectedTab.Header = h;
SelectedTab.Directory = d;
return;
}
Title = "Save Texture",
FileName = fileName,
InitialDirectory = UserSettings.Default.TextureDirectory,
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
Application.Current.Dispatcher.Invoke(() =>
using (var fs = new FileStream(directory, FileMode.Create, FileAccess.Write, FileShare.Read))
{
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
}
SaveCheck(directory, fileName);
}
public void SaveProperty(bool autoSave)
{
var fileName = Path.ChangeExtension(Header, ".json");
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
if (!autoSave)
{
var saveFileDialog = new SaveFileDialog
{
_tabItems.Add(new TabItem(h, d));
SelectedTab = _tabItems.Last();
});
Title = "Save Property",
FileName = fileName,
InitialDirectory = UserSettings.Default.PropertiesDirectory,
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
};
var result = saveFileDialog.ShowDialog();
if (!result.HasValue || !result.Value) return;
directory = saveFileDialog.FileName;
}
else
{
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
}
public void RemoveTab(TabItem tab = null)
{
Application.Current.Dispatcher.Invoke(() =>
{
var tabCount = _tabItems.Count;
var tabToDelete = tab ?? SelectedTab;
switch (tabCount)
{
case <= 0:
return;
// select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete
case > 1:
SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0
break;
}
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
SaveCheck(directory, fileName);
}
_tabItems.Remove(tabToDelete);
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
});
private void SaveCheck(string path, string fileName)
{
if (File.Exists(path))
{
Log.Information("{FileName} successfully saved", fileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
}
public class TabEventArgs : EventArgs
else
{
public TabItem TabToRemove { get; set; }
public TabEventArgs(TabItem tab) { TabToRemove = tab; }
}
public event EventHandler OnTabRemove;
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
public void RemoveOtherTabs(TabItem tab)
{
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var t in _tabItems.Where(t => t != tab).ToList())
{
_tabItems.Remove(t);
}
});
}
public void RemoveAllTabs()
{
Application.Current.Dispatcher.Invoke(() =>
{
SelectedTab = null;
_tabItems.Clear();
});
}
private static IEnumerable<TabItem> EnumerateTabs()
{
yield return new TabItem("New Tab", string.Empty);
Log.Error("{FileName} could not be saved", fileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
}
}
}
public class TabControlViewModel : ViewModel
{
private TabItem _selectedTab;
public TabItem SelectedTab
{
get => _selectedTab;
set => SetProperty(ref _selectedTab, value);
}
private AddTabCommand _addTabCommand;
public AddTabCommand AddTabCommand => _addTabCommand ??= new AddTabCommand(this);
private readonly ObservableCollection<TabItem> _tabItems;
public ReadOnlyObservableCollection<TabItem> TabsItems { get; }
public bool HasNoTabs => _tabItems.Count == 0;
public bool CanAddTabs => _tabItems.Count < 25;
public TabControlViewModel()
{
_tabItems = new ObservableCollection<TabItem>(EnumerateTabs());
TabsItems = new ReadOnlyObservableCollection<TabItem>(_tabItems);
SelectedTab = TabsItems.FirstOrDefault();
}
public void AddTab(string header = null, string directory = null)
{
if (!CanAddTabs) return;
var h = header ?? "New Tab";
var d = directory ?? string.Empty;
if (SelectedTab is { Header : "New Tab" })
{
SelectedTab.Header = h;
SelectedTab.Directory = d;
return;
}
Application.Current.Dispatcher.Invoke(() =>
{
_tabItems.Add(new TabItem(h, d));
SelectedTab = _tabItems.Last();
});
}
public void RemoveTab(TabItem tab = null)
{
Application.Current.Dispatcher.Invoke(() =>
{
var tabCount = _tabItems.Count;
var tabToDelete = tab ?? SelectedTab;
switch (tabCount)
{
case <= 0:
return;
// select previous tab before deleting current to avoid "ScrollToZero" issue on tab delete
case > 1:
SelectedTab = _tabItems.Previous(tabToDelete); // will select last if previous is -1 but who cares anyway, still better than having +1 to scroll 0
break;
}
_tabItems.Remove(tabToDelete);
OnTabRemove?.Invoke(this, new TabEventArgs(tabToDelete));
});
}
public class TabEventArgs : EventArgs
{
public TabItem TabToRemove { get; set; }
public TabEventArgs(TabItem tab)
{
TabToRemove = tab;
}
}
public event EventHandler OnTabRemove;
public void GoLeftTab() => SelectedTab = _tabItems.Previous(SelectedTab);
public void GoRightTab() => SelectedTab = _tabItems.Next(SelectedTab);
public void RemoveOtherTabs(TabItem tab)
{
Application.Current.Dispatcher.Invoke(() =>
{
foreach (var t in _tabItems.Where(t => t != tab).ToList())
{
_tabItems.Remove(t);
}
});
}
public void RemoveAllTabs()
{
Application.Current.Dispatcher.Invoke(() =>
{
SelectedTab = null;
_tabItems.Clear();
});
}
private static IEnumerable<TabItem> EnumerateTabs()
{
yield return new TabItem("New Tab", string.Empty);
}
}

View File

@@ -7,113 +7,112 @@ using FModel.Services;
using FModel.Views.Resources.Controls;
using Serilog;
namespace FModel.ViewModels
namespace FModel.ViewModels;
public class ThreadWorkerViewModel : ViewModel
{
public class ThreadWorkerViewModel : ViewModel
private bool _statusChangeAttempted;
public bool StatusChangeAttempted
{
private bool _statusChangeAttempted;
public bool StatusChangeAttempted
get => _statusChangeAttempted;
private set => SetProperty(ref _statusChangeAttempted, value);
}
private bool _operationCancelled;
public bool OperationCancelled
{
get => _operationCancelled;
private set => SetProperty(ref _operationCancelled, value);
}
private CancellationTokenSource _currentCancellationTokenSource;
public CancellationTokenSource CurrentCancellationTokenSource
{
get => _currentCancellationTokenSource;
set
{
get => _statusChangeAttempted;
private set => SetProperty(ref _statusChangeAttempted, value);
}
private bool _operationCancelled;
public bool OperationCancelled
{
get => _operationCancelled;
private set => SetProperty(ref _operationCancelled, value);
}
private CancellationTokenSource _currentCancellationTokenSource;
public CancellationTokenSource CurrentCancellationTokenSource
{
get => _currentCancellationTokenSource;
set
{
if (_currentCancellationTokenSource == value) return;
SetProperty(ref _currentCancellationTokenSource, value);
RaisePropertyChanged("CanBeCanceled");
}
}
public bool CanBeCanceled => CurrentCancellationTokenSource != null;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
public ThreadWorkerViewModel()
{
_jobs = new AsyncQueue<Action<CancellationToken>>();
}
public async Task Begin(Action<CancellationToken> action)
{
if (!_applicationView.IsReady)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource ??= new CancellationTokenSource();
_jobs.Enqueue(action);
await ProcessQueues();
}
public void Cancel()
{
if (!CanBeCanceled)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource.Cancel();
}
private async Task ProcessQueues()
{
if (_jobs.Count > 0)
{
_applicationView.Status = EStatusKind.Loading;
await foreach (var job in _jobs)
{
try
{
// will end in "catch" if canceled
await Task.Run(() => job(CurrentCancellationTokenSource.Token));
}
catch (OperationCanceledException)
{
_applicationView.Status = EStatusKind.Stopped;
CurrentCancellationTokenSource = null; // kill token
OperationCancelled = true;
OperationCancelled = false;
return;
}
catch (Exception e)
{
_applicationView.Status = EStatusKind.Failed;
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
FLogger.AppendError();
FLogger.AppendText(e.Message, Constants.WHITE, true);
FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true);
return;
}
}
_applicationView.Status = EStatusKind.Completed;
CurrentCancellationTokenSource = null; // kill token
}
}
public void SignalOperationInProgress()
{
StatusChangeAttempted = true;
StatusChangeAttempted = false;
if (_currentCancellationTokenSource == value) return;
SetProperty(ref _currentCancellationTokenSource, value);
RaisePropertyChanged("CanBeCanceled");
}
}
}
public bool CanBeCanceled => CurrentCancellationTokenSource != null;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
private readonly AsyncQueue<Action<CancellationToken>> _jobs;
public ThreadWorkerViewModel()
{
_jobs = new AsyncQueue<Action<CancellationToken>>();
}
public async Task Begin(Action<CancellationToken> action)
{
if (!_applicationView.IsReady)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource ??= new CancellationTokenSource();
_jobs.Enqueue(action);
await ProcessQueues();
}
public void Cancel()
{
if (!CanBeCanceled)
{
SignalOperationInProgress();
return;
}
CurrentCancellationTokenSource.Cancel();
}
private async Task ProcessQueues()
{
if (_jobs.Count > 0)
{
_applicationView.Status = EStatusKind.Loading;
await foreach (var job in _jobs)
{
try
{
// will end in "catch" if canceled
await Task.Run(() => job(CurrentCancellationTokenSource.Token));
}
catch (OperationCanceledException)
{
_applicationView.Status = EStatusKind.Stopped;
CurrentCancellationTokenSource = null; // kill token
OperationCancelled = true;
OperationCancelled = false;
return;
}
catch (Exception e)
{
_applicationView.Status = EStatusKind.Failed;
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
FLogger.AppendError();
FLogger.AppendText(e.Message, Constants.WHITE, true);
FLogger.AppendText(" " + e.StackTrace.SubstringBefore('\n').Trim(), Constants.WHITE, true);
return;
}
}
_applicationView.Status = EStatusKind.Completed;
CurrentCancellationTokenSource = null; // kill token
}
}
public void SignalOperationInProgress()
{
StatusChangeAttempted = true;
StatusChangeAttempted = false;
}
}