mirror of
https://github.com/4sval/FModel.git
synced 2026-09-26 11:07:39 -05:00
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user