mirror of
https://github.com/4sval/FModel.git
synced 2026-03-21 17:24:26 -05:00
commit
d132bc46e2
|
|
@ -1 +1 @@
|
|||
Subproject commit 4e955153559be8dc156d15fc93ff8c1016d3ebfe
|
||||
Subproject commit 455b72e5e38bfe9476b5823bc642fc8ef488347f
|
||||
|
|
@ -97,14 +97,16 @@ public partial class App
|
|||
Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"));
|
||||
|
||||
#if DEBUG
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).CreateLogger();
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Debug-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
||||
#else
|
||||
Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File(
|
||||
Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"),
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger();
|
||||
#endif
|
||||
|
||||
Log.Information("Version {Version}", Constants.APP_VERSION);
|
||||
Log.Information("Version {Version} ({CommitId})", Constants.APP_VERSION, Constants.APP_COMMIT_ID);
|
||||
Log.Information("{OS}", GetOperatingSystemProductName());
|
||||
Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription);
|
||||
Log.Information("Culture {SysLang}", CultureInfo.CurrentCulture);
|
||||
|
|
@ -140,7 +142,7 @@ public partial class App
|
|||
if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore)
|
||||
{
|
||||
if ((EErrorKind) messageBox.ButtonPressed.Id == EErrorKind.ResetSettings)
|
||||
UserSettings.Default = new UserSettings();
|
||||
UserSettings.Delete();
|
||||
|
||||
ApplicationService.ApplicationView.Restart();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,20 @@
|
|||
using System.Numerics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Extensions;
|
||||
|
||||
namespace FModel;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
|
||||
public static readonly string APP_PATH = Path.GetFullPath(Environment.GetCommandLineArgs()[0]);
|
||||
public static readonly string APP_VERSION = FileVersionInfo.GetVersionInfo(APP_PATH).FileVersion;
|
||||
public static readonly string APP_COMMIT_ID = FileVersionInfo.GetVersionInfo(APP_PATH).ProductVersion.SubstringAfter('+');
|
||||
public static readonly string APP_SHORT_COMMIT_ID = APP_COMMIT_ID[..7];
|
||||
|
||||
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
public static readonly FGuid ZERO_GUID = new(0U);
|
||||
|
||||
|
|
@ -21,6 +29,9 @@ public static class Constants
|
|||
public const string BLUE = "#528BCC";
|
||||
|
||||
public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a";
|
||||
public const string GH_REPO = "https://api.github.com/repos/4sval/FModel";
|
||||
public const string GH_COMMITS_HISTORY = GH_REPO + "/commits";
|
||||
public const string GH_RELEASES = GH_REPO + "/releases";
|
||||
public const string DONATE_LINK = "https://fmodel.app/donate";
|
||||
public const string DISCORD_LINK = "https://fmodel.app/discord";
|
||||
|
||||
|
|
|
|||
|
|
@ -31,25 +31,33 @@ public class BaseIcon : UCreator
|
|||
{
|
||||
// rarity
|
||||
if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series);
|
||||
else if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList")) GetSeries(dataList);
|
||||
else if (Object.TryGetValue(out FStructFallback componentContainer, "ComponentContainer")) GetSeries(componentContainer);
|
||||
else GetRarity(Object.GetOrDefault("Rarity", EFortRarity.Uncommon)); // default is uncommon
|
||||
|
||||
if (Object.TryGetValue(out FInstancedStruct[] dataList, "DataList"))
|
||||
{
|
||||
GetSeries(dataList);
|
||||
Preview = Utils.GetBitmap(dataList);
|
||||
}
|
||||
|
||||
// preview
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
|
||||
Preview = Utils.GetBitmap(materialInstancePreview);
|
||||
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
|
||||
Preview = Utils.GetBitmap(res);
|
||||
if (Preview is null)
|
||||
{
|
||||
if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview))
|
||||
Preview = preview;
|
||||
else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition"))
|
||||
Preview = Utils.GetBitmap(itemDefinition);
|
||||
else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "EntryListIcon", "SmallPreviewImage", "BundleImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon"))
|
||||
Preview = Utils.GetBitmap(largePreview);
|
||||
else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s))
|
||||
Preview = Utils.GetBitmap(s);
|
||||
else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item"))
|
||||
Preview = Utils.GetBitmap(otherPreview);
|
||||
else if (Object.TryGetValue(out UMaterialInstanceConstant materialInstancePreview, "EventCalloutImage"))
|
||||
Preview = Utils.GetBitmap(materialInstancePreview);
|
||||
else if (Object.TryGetValue(out FStructFallback brush, "IconBrush") && brush.TryGetValue(out UTexture2D res, "ResourceObject"))
|
||||
Preview = Utils.GetBitmap(res);
|
||||
}
|
||||
|
||||
// text
|
||||
if (Object.TryGetValue(out FText displayName, "DisplayName", "ItemName", "BundleName", "DefaultHeaderText", "UIDisplayName", "EntryName", "EventCalloutTitle"))
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public class BaseMaterialInstance : BaseIcon
|
|||
case "TextureA":
|
||||
case "TextureB":
|
||||
case "OfferImage":
|
||||
case "CarTexture":
|
||||
Preview = Utils.GetBitmap(texture);
|
||||
break;
|
||||
}
|
||||
|
|
@ -88,4 +89,4 @@ public class BaseMaterialInstance : BaseIcon
|
|||
|
||||
return new[] { ret };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
|
|
@ -8,10 +9,11 @@ namespace FModel.Creator.Bases.FN;
|
|||
|
||||
public class BaseOfferDisplayData : UCreator
|
||||
{
|
||||
private BaseMaterialInstance[] _offerImages;
|
||||
private readonly List<BaseMaterialInstance> _offerImages;
|
||||
|
||||
public BaseOfferDisplayData(UObject uObject, EIconStyle style) : base(uObject, style)
|
||||
{
|
||||
_offerImages = new List<BaseMaterialInstance>();
|
||||
}
|
||||
|
||||
public override void ParseForInfo()
|
||||
|
|
@ -19,24 +21,23 @@ public class BaseOfferDisplayData : UCreator
|
|||
if (!Object.TryGetValue(out FStructFallback[] contextualPresentations, "ContextualPresentations"))
|
||||
return;
|
||||
|
||||
_offerImages = new BaseMaterialInstance[contextualPresentations.Length];
|
||||
for (var i = 0; i < _offerImages.Length; i++)
|
||||
for (var i = 0; i < contextualPresentations.Length; i++)
|
||||
{
|
||||
if (!contextualPresentations[i].TryGetValue(out FSoftObjectPath material, "Material") ||
|
||||
!material.TryLoad(out UMaterialInterface presentation)) continue;
|
||||
|
||||
var offerImage = new BaseMaterialInstance(presentation, Style);
|
||||
offerImage.ParseForInfo();
|
||||
_offerImages[i] = offerImage;
|
||||
_offerImages.Add(offerImage);
|
||||
}
|
||||
}
|
||||
|
||||
public override SKBitmap[] Draw()
|
||||
{
|
||||
var ret = new SKBitmap[_offerImages.Length];
|
||||
var ret = new SKBitmap[_offerImages.Count];
|
||||
for (var i = 0; i < ret.Length; i++)
|
||||
{
|
||||
ret[i] = _offerImages[i].Draw()[0];
|
||||
ret[i] = _offerImages[i]?.Draw()[0];
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class BasePlaylist : UCreator
|
|||
return;
|
||||
|
||||
var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text);
|
||||
if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase ||
|
||||
if (!playlist.IsSuccess || playlist.Data.Images is not { HasShowcase: true } ||
|
||||
!_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image))
|
||||
return;
|
||||
|
||||
|
|
@ -74,4 +74,4 @@ public class BasePlaylist : UCreator
|
|||
if (_missionIcon == null) return;
|
||||
c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ public class CreatorPackage : IDisposable
|
|||
case "FortBadgeItemDefinition":
|
||||
case "SparksMicItemDefinition":
|
||||
case "FortAwardItemDefinition":
|
||||
case "FortStackItemDefinition":
|
||||
case "FortWorldItemDefinition":
|
||||
case "SparksAuraItemDefinition":
|
||||
case "SparksDrumItemDefinition":
|
||||
case "SparksBassItemDefinition":
|
||||
|
|
@ -76,6 +78,8 @@ public class CreatorPackage : IDisposable
|
|||
case "FortGiftBoxItemDefinition":
|
||||
case "FortOutpostItemDefinition":
|
||||
case "FortVehicleItemDefinition":
|
||||
case "FortMissionItemDefinition":
|
||||
case "FortAccountItemDefinition":
|
||||
case "SparksGuitarItemDefinition":
|
||||
case "FortCardPackItemDefinition":
|
||||
case "FortDefenderItemDefinition":
|
||||
|
|
@ -83,28 +87,34 @@ public class CreatorPackage : IDisposable
|
|||
case "FortResourceItemDefinition":
|
||||
case "FortBackpackItemDefinition":
|
||||
case "FortEventQuestMapDataAsset":
|
||||
case "FortBuildingItemDefinition":
|
||||
case "FortWeaponModItemDefinition":
|
||||
case "FortCodeTokenItemDefinition":
|
||||
case "FortSchematicItemDefinition":
|
||||
case "FortAlterableItemDefinition":
|
||||
case "SparksKeyboardItemDefinition":
|
||||
case "FortWorldMultiItemDefinition":
|
||||
case "FortAlterationItemDefinition":
|
||||
case "FortExpeditionItemDefinition":
|
||||
case "FortIngredientItemDefinition":
|
||||
case "FortConsumableItemDefinition":
|
||||
case "StWFortAccoladeItemDefinition":
|
||||
case "FortAccountBuffItemDefinition":
|
||||
case "FortWeaponMeleeItemDefinition":
|
||||
case "FortPlayerPerksItemDefinition":
|
||||
case "FortPlaysetPropItemDefinition":
|
||||
case "FortPrerollDataItemDefinition":
|
||||
case "JunoRecipeBundleItemDefinition":
|
||||
case "FortHomebaseNodeItemDefinition":
|
||||
case "FortNeverPersistItemDefinition":
|
||||
case "FortPlayerAugmentItemDefinition":
|
||||
case "FortSmartBuildingItemDefinition":
|
||||
case "FortGiftBoxUnlockItemDefinition":
|
||||
case "FortWeaponModItemDefinitionOptic":
|
||||
case "RadioContentSourceItemDefinition":
|
||||
case "FortPlaysetGrenadeItemDefinition":
|
||||
case "JunoWeaponCreatureItemDefinition":
|
||||
case "FortEventDependentItemDefinition":
|
||||
case "FortPersonalVehicleItemDefinition":
|
||||
case "FortGameplayModifierItemDefinition":
|
||||
case "FortHardcoreModifierItemDefinition":
|
||||
|
|
@ -113,11 +123,13 @@ public class CreatorPackage : IDisposable
|
|||
case "FortConversionControlItemDefinition":
|
||||
case "FortAccountBuffCreditItemDefinition":
|
||||
case "JunoBuildInstructionsItemDefinition":
|
||||
case "FortCharacterCosmeticItemDefinition":
|
||||
case "JunoBuildingSetAccountItemDefinition":
|
||||
case "FortEventCurrencyItemDefinitionRedir":
|
||||
case "FortPersistentResourceItemDefinition":
|
||||
case "FortWeaponMeleeOffhandItemDefinition":
|
||||
case "FortHomebaseBannerIconItemDefinition":
|
||||
case "FortVehicleCosmeticsVariantTokenType":
|
||||
case "JunoBuildingPropAccountItemDefinition":
|
||||
case "FortCampaignHeroLoadoutItemDefinition":
|
||||
case "FortConditionalResourceItemDefinition":
|
||||
|
|
@ -129,6 +141,7 @@ public class CreatorPackage : IDisposable
|
|||
case "FortVehicleCosmeticsItemDefinition_Skin":
|
||||
case "FortVehicleCosmeticsItemDefinition_Wheel":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "FortDeployableBaseCloudSaveItemDefinition":
|
||||
case "FortVehicleCosmeticsItemDefinition_Booster":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
case "FortVehicleCosmeticsItemDefinition_DriftSmoke":
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
|
|||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Objects;
|
||||
using FModel.Framework;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
|
|
@ -71,6 +72,7 @@ public static class Utils
|
|||
return GetBitmap(material);
|
||||
default:
|
||||
{
|
||||
if (export.TryGetValue(out FInstancedStruct[] dataList, "DataList")) return GetBitmap(dataList);
|
||||
if (export.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage);
|
||||
if (export.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview);
|
||||
if (export.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage"))
|
||||
|
|
@ -85,6 +87,21 @@ public static class Utils
|
|||
}
|
||||
}
|
||||
|
||||
public static SKBitmap GetBitmap(FInstancedStruct[] structs)
|
||||
{
|
||||
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "LargeIcon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isl)
|
||||
{
|
||||
return GetBitmap(isl.NonConstStruct.Get<FSoftObjectPath>("LargeIcon"));
|
||||
}
|
||||
|
||||
if (structs.FirstOrDefault(d => d.NonConstStruct?.TryGetValue(out FSoftObjectPath p, "Icon") == true && !p.AssetPathName.IsNone) is { NonConstStruct: not null } isi)
|
||||
{
|
||||
return GetBitmap(isi.NonConstStruct.Get<FSoftObjectPath>("Icon"));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static SKBitmap GetBitmap(UMaterialInstanceConstant material)
|
||||
{
|
||||
if (material == null) return null;
|
||||
|
|
@ -400,4 +417,4 @@ public static class Utils
|
|||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,7 @@ public enum EErrorKind
|
|||
public enum SettingsOut
|
||||
{
|
||||
ReloadLocres,
|
||||
ReloadMappings,
|
||||
CheckForUpdates
|
||||
ReloadMappings
|
||||
}
|
||||
|
||||
public enum EStatusKind
|
||||
|
|
@ -64,15 +63,15 @@ public enum ELoadingMode
|
|||
AllButModified
|
||||
}
|
||||
|
||||
public enum EUpdateMode
|
||||
{
|
||||
[Description("Stable")]
|
||||
Stable,
|
||||
[Description("Beta")]
|
||||
Beta,
|
||||
[Description("QA Testing")]
|
||||
Qa
|
||||
}
|
||||
// public enum EUpdateMode
|
||||
// {
|
||||
// [Description("Stable")]
|
||||
// Stable,
|
||||
// [Description("Beta")]
|
||||
// Beta,
|
||||
// [Description("QA Testing")]
|
||||
// Qa
|
||||
// }
|
||||
|
||||
public enum ECompressedAudio
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.4.3.6</Version>
|
||||
<AssemblyVersion>4.4.3.6</AssemblyVersion>
|
||||
<FileVersion>4.4.3.6</FileVersion>
|
||||
<Version>4.4.4.0</Version>
|
||||
<AssemblyVersion>4.4.4.0</AssemblyVersion>
|
||||
<FileVersion>4.4.4.0</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
|
@ -148,21 +148,21 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AdonisUI" Version="1.17.1" />
|
||||
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.5" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.3.0.90" />
|
||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="EpicManifestParser" Version="2.2.1" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.90.1.1" />
|
||||
<PackageReference Include="EpicManifestParser" Version="2.3.3" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.5" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
<PackageReference Include="OpenTK" Version="4.8.2" />
|
||||
<PackageReference Include="RestSharp" Version="110.2.0" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||
<PackageReference Include="RestSharp" Version="112.1.0" />
|
||||
<PackageReference Include="Serilog" Version="4.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
|
||||
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
using System;
|
||||
using System;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
public class FRestRequest : RestRequest
|
||||
{
|
||||
private const int _timeout = 3 * 1000;
|
||||
private const int TimeoutSeconds = 5;
|
||||
|
||||
public FRestRequest(string url, Method method = Method.Get) : base(url, method)
|
||||
{
|
||||
Timeout = _timeout;
|
||||
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
|
||||
}
|
||||
|
||||
public FRestRequest(Uri uri, Method method = Method.Get) : base(uri, method)
|
||||
{
|
||||
Timeout = _timeout;
|
||||
Timeout = TimeSpan.FromSeconds(TimeoutSeconds);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,11 +147,11 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Changelog" Command="{Binding MenuCommand}" CommandParameter="Help_Changelog">
|
||||
<MenuItem Header="Releases" Command="{Binding MenuCommand}" CommandParameter="Help_Releases">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource NoteIcon}" />
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource GitHubIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
|
|
@ -349,7 +349,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Folder's Packages Textures (.png)" Click="OnFolderTextureClick">
|
||||
<MenuItem Header="Save Folder's Packages Textures" Click="OnFolderTextureClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -517,7 +517,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture (.png)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
|
|
@ -797,13 +797,17 @@
|
|||
<Style TargetType="StatusBarItem">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
|
||||
<Setter Property="Visibility" Value="Hidden" />
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</StatusBarItem.Style>
|
||||
<TextBlock HorizontalAlignment="Center" FontWeight="SemiBold" Text="SND" />
|
||||
</StatusBarItem>
|
||||
|
||||
<StatusBarItem Margin="10 0 0 0">
|
||||
<TextBlock Text="{Binding LastUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, Converter={x:Static converters:RelativeDateTimeConverter.Instance}, StringFormat=Last Refresh: {0}}" />
|
||||
</StatusBarItem>
|
||||
</StackPanel>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ public partial class MainWindow
|
|||
{
|
||||
var newOrUpdated = UserSettings.Default.ShowChangelog;
|
||||
#if !DEBUG
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode, true);
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(true);
|
||||
#endif
|
||||
|
||||
switch (UserSettings.Default.AesReload)
|
||||
|
|
@ -85,7 +85,7 @@ public partial class MainWindow
|
|||
#if DEBUG
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "fortnitegame/Content/Characters/Player/Female/Large/Bodies/F_LRG_BunnyBR/Meshes/F_LRG_BunnyBR.uasset"));
|
||||
// "FortniteGame/Content/Athena/Apollo/Maps/UI/Apollo_Terrain_Minimap.uasset"));
|
||||
// await _threadWorkerView.Begin(cancellationToken =>
|
||||
// _applicationView.CUE4Parse.Extract(cancellationToken,
|
||||
// "FortniteGame/Content/Environments/Helios/Props/GlacierHotel/GlacierHotel_Globe_A/Meshes/SM_GlacierHotel_Globe_A.uasset"));
|
||||
|
|
|
|||
|
|
@ -27,10 +27,18 @@ vec2 unpackBoneIDsAndWeights(int packedData)
|
|||
return vec2(float((packedData >> 16) & 0xFFFF), float(packedData & 0xFFFF));
|
||||
}
|
||||
|
||||
vec4 calculateScale(vec4 bindPos, vec4 bindNormal)
|
||||
{
|
||||
vec4 worldPos = vInstanceMatrix * bindPos;
|
||||
float scaleFactor = length(uViewPos - worldPos.xyz) * 0.0035;
|
||||
return transpose(inverse(vInstanceMatrix)) * bindNormal * scaleFactor;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
vec4 bindNormal = vec4(vNormal, 1.0);
|
||||
bindPos.xyz += calculateScale(bindPos, bindNormal).xyz;
|
||||
|
||||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
|
|
@ -53,8 +61,6 @@ void main()
|
|||
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
|
||||
}
|
||||
}
|
||||
finalPos = normalize(finalPos);
|
||||
finalNormal = normalize(finalNormal);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -62,10 +68,5 @@ void main()
|
|||
finalNormal = bindNormal;
|
||||
}
|
||||
|
||||
vec4 worldPos = vInstanceMatrix * finalPos;
|
||||
float scaleFactor = length(uViewPos - worldPos.xyz) * 0.0035;
|
||||
vec4 nor = transpose(inverse(vInstanceMatrix)) * finalNormal * scaleFactor;
|
||||
finalPos.xyz += nor.xyz;
|
||||
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,11 @@ public class DirectorySettings : ViewModel, ICloneable
|
|||
return HashCode.Combine(GameDirectory, (int) UeVersion);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return GameName;
|
||||
}
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
return this.MemberwiseClone();
|
||||
|
|
|
|||
|
|
@ -32,16 +32,21 @@ namespace FModel.Settings
|
|||
Default = new UserSettings();
|
||||
}
|
||||
|
||||
private static bool _bSave = true;
|
||||
public static void Save()
|
||||
{
|
||||
if (Default == null) return;
|
||||
if (!_bSave || Default == null) return;
|
||||
Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir;
|
||||
File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented));
|
||||
}
|
||||
|
||||
public static void Delete()
|
||||
{
|
||||
if (File.Exists(FilePath)) File.Delete(FilePath);
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
_bSave = false;
|
||||
File.Delete(FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint)
|
||||
|
|
@ -174,18 +179,18 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _loadingMode, value);
|
||||
}
|
||||
|
||||
private EUpdateMode _updateMode = EUpdateMode.Beta;
|
||||
public EUpdateMode UpdateMode
|
||||
private DateTime _lastUpdateCheck = DateTime.MinValue;
|
||||
public DateTime LastUpdateCheck
|
||||
{
|
||||
get => _updateMode;
|
||||
set => SetProperty(ref _updateMode, value);
|
||||
get => _lastUpdateCheck;
|
||||
set => SetProperty(ref _lastUpdateCheck, value);
|
||||
}
|
||||
|
||||
private string _commitHash = Constants.APP_VERSION;
|
||||
public string CommitHash
|
||||
private DateTime _nextUpdateCheck = DateTime.Now;
|
||||
public DateTime NextUpdateCheck
|
||||
{
|
||||
get => _commitHash;
|
||||
set => SetProperty(ref _commitHash, value);
|
||||
get => _nextUpdateCheck;
|
||||
set => SetProperty(ref _nextUpdateCheck, value);
|
||||
}
|
||||
|
||||
private bool _keepDirectoryStructure = true;
|
||||
|
|
@ -260,8 +265,6 @@ namespace FModel.Settings
|
|||
|
||||
[JsonIgnore]
|
||||
public DirectorySettings CurrentDir { get; set; }
|
||||
[JsonIgnore]
|
||||
public string ShortCommitHash => CommitHash[..7];
|
||||
|
||||
/// <summary>
|
||||
/// TO DELETEEEEEEEEEEEEE
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using FModel.Framework;
|
||||
|
|
@ -12,7 +12,7 @@ public class ApiEndpointViewModel
|
|||
private readonly RestClient _client = new (new RestClientOptions
|
||||
{
|
||||
UserAgent = $"FModel/{Constants.APP_VERSION}",
|
||||
MaxTimeout = 3 * 1000
|
||||
Timeout = TimeSpan.FromSeconds(5)
|
||||
}, configureSerialization: s => s.UseSerializer<JsonNetSerializer>());
|
||||
|
||||
public FortniteApiEndpoint FortniteApi { get; }
|
||||
|
|
@ -20,6 +20,7 @@ public class ApiEndpointViewModel
|
|||
public FortniteCentralApiEndpoint CentralApi { get; }
|
||||
public EpicApiEndpoint EpicApi { get; }
|
||||
public FModelApiEndpoint FModelApi { get; }
|
||||
public GitHubApiEndpoint GitHubApi { get; }
|
||||
public DynamicApiEndpoint DynamicApi { get; }
|
||||
|
||||
public ApiEndpointViewModel()
|
||||
|
|
@ -29,6 +30,7 @@ public class ApiEndpointViewModel
|
|||
CentralApi = new FortniteCentralApiEndpoint(_client);
|
||||
EpicApi = new EpicApiEndpoint(_client);
|
||||
FModelApi = new FModelApiEndpoint(_client);
|
||||
GitHubApi = new GitHubApiEndpoint(_client);
|
||||
DynamicApi = new DynamicApiEndpoint(_client);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ using FModel.Framework;
|
|||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using FModel.Views;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
using Serilog;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
|
|
@ -46,19 +46,6 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
return _news ??= GetNewsAsync(token, game).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<Info> GetInfosAsync(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
var request = new FRestRequest($"https://api.fmodel.app/v1/infos/{updateMode}");
|
||||
var response = await _client.ExecuteAsync<Info>(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public Info GetInfos(CancellationToken token, EUpdateMode updateMode)
|
||||
{
|
||||
return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task<Donator[]> GetDonatorsAsync()
|
||||
{
|
||||
var request = new FRestRequest($"https://api.fmodel.app/v1/donations/donators");
|
||||
|
|
@ -116,14 +103,16 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
return communityDesign;
|
||||
}
|
||||
|
||||
public void CheckForUpdates(EUpdateMode updateMode, bool launch = false)
|
||||
public void CheckForUpdates(bool launch = false)
|
||||
{
|
||||
if (DateTime.Now < UserSettings.Default.NextUpdateCheck) return;
|
||||
|
||||
if (launch)
|
||||
{
|
||||
AutoUpdater.ParseUpdateInfoEvent += ParseUpdateInfoEvent;
|
||||
AutoUpdater.CheckForUpdateEvent += CheckForUpdateEvent;
|
||||
}
|
||||
AutoUpdater.Start($"https://api.fmodel.app/v1/infos/{updateMode}");
|
||||
AutoUpdater.Start("https://api.fmodel.app/v1/infos/Qa");
|
||||
}
|
||||
|
||||
private void ParseUpdateInfoEvent(ParseUpdateInfoEventArgs args)
|
||||
|
|
@ -138,7 +127,6 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
DownloadURL = _infos.DownloadUrl,
|
||||
Mandatory = new CustomMandatory
|
||||
{
|
||||
Value = UserSettings.Default.UpdateMode == EUpdateMode.Qa,
|
||||
CommitHash = _infos.Version.SubstringAfter('+')
|
||||
}
|
||||
};
|
||||
|
|
@ -149,43 +137,21 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
if (args is { CurrentVersion: { } })
|
||||
{
|
||||
var qa = (CustomMandatory) args.Mandatory;
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
if ((qa.Value && qa.CommitHash == UserSettings.Default.CommitHash) || // qa branch : same commit id
|
||||
(!qa.Value && currentVersion == args.InstalledVersion && args.CurrentVersion == UserSettings.Default.CommitHash)) // stable - beta branch : same version + commit id = version
|
||||
UserSettings.Default.LastUpdateCheck = DateTime.Now;
|
||||
|
||||
if (((CustomMandatory)args.Mandatory).CommitHash == Constants.APP_COMMIT_ID)
|
||||
{
|
||||
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.GetDescription()} is {(qa.Value ? qa.ShortCommitHash : args.CurrentVersion)}. You are using version {(qa.Value ? UserSettings.Default.ShortCommitHash : 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
|
||||
};
|
||||
var currentVersion = new System.Version(args.CurrentVersion);
|
||||
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (AutoUpdater.DownloadUpdate(args))
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
|
||||
UserSettings.Default.CommitHash = qa.CommitHash;
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
const string message = "A new update is available!";
|
||||
Helper.OpenWindow<AdonisWindow>(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -199,7 +165,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
var request = new FRestRequest(args.ChangelogURL);
|
||||
var response = _client.Execute(request);
|
||||
if (string.IsNullOrEmpty(response.Content)) return;
|
||||
if (!response.IsSuccessful || string.IsNullOrEmpty(response.Content)) return;
|
||||
|
||||
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
|
||||
|
|
|
|||
28
FModel/ViewModels/ApiEndpoints/GitHubApiEndpoint.cs
Normal file
28
FModel/ViewModels/ApiEndpoints/GitHubApiEndpoint.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using System.Threading.Tasks;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
||||
public class GitHubApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public GitHubApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<GitHubCommit[]> GetCommitHistoryAsync(string branch = "dev", int page = 1, int limit = 20)
|
||||
{
|
||||
var request = new FRestRequest(Constants.GH_COMMITS_HISTORY);
|
||||
request.AddParameter("sha", branch);
|
||||
request.AddParameter("page", page);
|
||||
request.AddParameter("per_page", limit);
|
||||
var response = await _client.ExecuteAsync<GitHubCommit[]>(request).ConfigureAwait(false);
|
||||
return response.Data;
|
||||
}
|
||||
|
||||
public async Task<GitHubRelease> GetReleaseAsync(string tag)
|
||||
{
|
||||
var request = new FRestRequest($"{Constants.GH_RELEASES}/tags/{tag}");
|
||||
var response = await _client.ExecuteAsync<GitHubRelease>(request).ConfigureAwait(false);
|
||||
return response.Data;
|
||||
}
|
||||
}
|
||||
125
FModel/ViewModels/ApiEndpoints/Models/GitHubResponse.cs
Normal file
125
FModel/ViewModels/ApiEndpoints/Models/GitHubResponse.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
using System.Windows;
|
||||
using AdonisUI.Controls;
|
||||
using AutoUpdaterDotNET;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using MessageBox = AdonisUI.Controls.MessageBox;
|
||||
using MessageBoxButton = AdonisUI.Controls.MessageBoxButton;
|
||||
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
|
||||
using MessageBoxResult = AdonisUI.Controls.MessageBoxResult;
|
||||
using J = Newtonsoft.Json.JsonPropertyAttribute;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
public class GitHubRelease
|
||||
{
|
||||
[J("assets")] public GitHubAsset[] Assets { get; private set; }
|
||||
}
|
||||
|
||||
public class GitHubAsset : ViewModel
|
||||
{
|
||||
[J("name")] public string Name { get; private set; }
|
||||
[J("size")] public int Size { get; private set; }
|
||||
[J("download_count")] public int DownloadCount { get; private set; }
|
||||
[J("browser_download_url")] public string BrowserDownloadUrl { get; private set; }
|
||||
[J("created_at")] public DateTime CreatedAt { get; private set; }
|
||||
[J("uploader")] public Author Uploader { get; private set; }
|
||||
|
||||
private bool _isLatest;
|
||||
public bool IsLatest
|
||||
{
|
||||
get => _isLatest;
|
||||
set => SetProperty(ref _isLatest, value);
|
||||
}
|
||||
}
|
||||
|
||||
public class GitHubCommit : ViewModel
|
||||
{
|
||||
private string _sha;
|
||||
[J("sha")]
|
||||
public string Sha
|
||||
{
|
||||
get => _sha;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _sha, value);
|
||||
RaisePropertyChanged(nameof(IsCurrent));
|
||||
RaisePropertyChanged(nameof(ShortSha));
|
||||
}
|
||||
}
|
||||
|
||||
[J("commit")] public Commit Commit { get; set; }
|
||||
[J("author")] public Author Author { get; set; }
|
||||
|
||||
private GitHubAsset _asset;
|
||||
public GitHubAsset Asset
|
||||
{
|
||||
get => _asset;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _asset, value);
|
||||
RaisePropertyChanged(nameof(IsDownloadable));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCurrent => Sha == Constants.APP_COMMIT_ID;
|
||||
public string ShortSha => Sha[..7];
|
||||
public bool IsDownloadable => Asset != null;
|
||||
|
||||
public void Download()
|
||||
{
|
||||
if (IsCurrent)
|
||||
{
|
||||
MessageBox.Show(new MessageBoxModel
|
||||
{
|
||||
Text = "You are already on the latest version.",
|
||||
Caption = "Update FModel",
|
||||
Icon = MessageBoxImage.Information,
|
||||
Buttons = [MessageBoxButtons.Ok()],
|
||||
IsSoundEnabled = false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var messageBox = new MessageBoxModel
|
||||
{
|
||||
Text = $"Are you sure you want to update to version '{ShortSha}'?{(!Asset.IsLatest ? "\nThis is not the latest version." : "")}",
|
||||
Caption = "Update FModel",
|
||||
Icon = MessageBoxImage.Question,
|
||||
Buttons = MessageBoxButtons.YesNo(),
|
||||
IsSoundEnabled = false
|
||||
};
|
||||
|
||||
MessageBox.Show(messageBox);
|
||||
if (messageBox.Result != MessageBoxResult.Yes) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (AutoUpdater.DownloadUpdate(new UpdateInfoEventArgs { DownloadURL = Asset.BrowserDownloadUrl }))
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
MessageBox.Show(exception.Message, exception.GetType().ToString(), MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Commit
|
||||
{
|
||||
[J("author")] public Author Author { get; set; }
|
||||
[J("message")] public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class Author
|
||||
{
|
||||
[J("name")] public string Name { get; set; }
|
||||
[J("login")] public string Login { get; set; }
|
||||
[J("date")] public DateTime Date { get; set; }
|
||||
[J("avatar_url")] public string AvatarUrl { get; set; }
|
||||
[J("html_url")] public string HtmlUrl { get; set; }
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ using CUE4Parse.UE4.Readers;
|
|||
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
|
||||
using OffiUtils;
|
||||
using RestSharp;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
|
@ -117,7 +117,7 @@ public class VManifest
|
|||
return chunkBytes;
|
||||
}
|
||||
|
||||
public Stream GetPakStream(int index) => new VPakStream(this, index);
|
||||
public VPakStream GetPakStream(int index) => new VPakStream(this, index);
|
||||
}
|
||||
|
||||
public readonly struct VHeader
|
||||
|
|
@ -179,7 +179,7 @@ public readonly struct VChunk
|
|||
public string GetUrl() => $"https://fmodel.fortnite-api.com/valorant/v2/chunks/{Id}";
|
||||
}
|
||||
|
||||
public class VPakStream : Stream, ICloneable
|
||||
public class VPakStream : Stream, IRandomAccessStream, ICloneable
|
||||
{
|
||||
private readonly VManifest _manifest;
|
||||
private readonly int _pakIndex;
|
||||
|
|
@ -203,11 +203,22 @@ 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 int Read(byte[] buffer, int offset, int count) =>
|
||||
ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
|
||||
|
||||
public int ReadAt(long position, byte[] buffer, int offset, int count) =>
|
||||
ReadAtAsync(position, 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);
|
||||
var bytesRead = await ReadAtAsync(_position, buffer, offset, count, cancellationToken);
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public async Task<int> ReadAtAsync(long position, 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);
|
||||
|
|
@ -234,10 +245,14 @@ public class VPakStream : Stream, ICloneable
|
|||
if (++i == _chunks.Length) break;
|
||||
}
|
||||
|
||||
_position += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public Task<int> ReadAtAsync(long position, Memory<byte> memory, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
private async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4)
|
||||
{
|
||||
var tasks = new List<Task>();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using CUE4Parse.Compression;
|
|||
using CUE4Parse.Encryption.Aes;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -50,7 +49,7 @@ public class ApplicationViewModel : ViewModel
|
|||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||
private CopyCommand _copyCommand;
|
||||
|
||||
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode.GetDescription()}";
|
||||
public string InitialWindowTitle => $"FModel ({Constants.APP_SHORT_COMMIT_ID})";
|
||||
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
||||
public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
|
|
@ -144,7 +143,7 @@ public class ApplicationViewModel : ViewModel
|
|||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "dotnet",
|
||||
Arguments = $"\"{Path.GetFullPath(Environment.GetCommandLineArgs()[0])}\"",
|
||||
Arguments = $"\"{path}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = false,
|
||||
RedirectStandardError = false,
|
||||
|
|
@ -208,7 +207,7 @@ public class ApplicationViewModel : ViewModel
|
|||
foreach (var entry in zip.Entries)
|
||||
{
|
||||
var entryPath = Path.Combine(zipDir, entry.FullName);
|
||||
await using var entryFs = File.OpenRead(entryPath);
|
||||
await using var entryFs = File.Create(entryPath);
|
||||
await using var entryStream = entry.Open();
|
||||
await entryStream.CopyToAsync(entryFs);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
|
@ -20,6 +21,8 @@ namespace FModel.ViewModels;
|
|||
|
||||
public class BackupManagerViewModel : ViewModel
|
||||
{
|
||||
public const uint FBKP_MAGIC = 0x504B4246;
|
||||
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
|
@ -64,23 +67,21 @@ public class BackupManagerViewModel : ViewModel
|
|||
var backupFolder = Path.Combine(UserSettings.Default.OutputDirectory, "Backups");
|
||||
var fileName = $"{_gameName}_{DateTime.Now:MM'_'dd'_'yyyy}.fbkp";
|
||||
var fullPath = Path.Combine(backupFolder, fileName);
|
||||
var func = new Func<GameFile, bool>(x => !x.Path.EndsWith(".uexp") && !x.Path.EndsWith(".ubulk") && !x.Path.EndsWith(".uptnl"));
|
||||
|
||||
using var fileStream = new FileStream(fullPath, FileMode.Create);
|
||||
using var compressedStream = LZ4Stream.Encode(fileStream, LZ4Level.L00_FAST);
|
||||
using var writer = new BinaryWriter(compressedStream);
|
||||
writer.Write(FBKP_MAGIC);
|
||||
writer.Write((byte) EBackupVersion.Latest);
|
||||
writer.Write(_applicationView.CUE4Parse.Provider.Files.Values.Count(func));
|
||||
|
||||
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);
|
||||
if (!func(asset)) continue;
|
||||
writer.Write(asset.Size);
|
||||
writer.Write(asset.IsEncrypted);
|
||||
writer.Write($"/{asset.Path.ToLower()}");
|
||||
}
|
||||
|
||||
SaveCheck(fullPath, fileName, "created", "create");
|
||||
|
|
@ -116,3 +117,12 @@ public class BackupManagerViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum EBackupVersion : byte
|
||||
{
|
||||
BeforeVersionWasAdded = 0,
|
||||
Initial,
|
||||
|
||||
LatestPlusOne,
|
||||
Latest = LatestPlusOne - 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,26 @@ using System.Text.RegularExpressions;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
|
||||
using AdonisUI.Controls;
|
||||
|
||||
using CUE4Parse.Compression;
|
||||
using CUE4Parse.Encryption.Aes;
|
||||
using CUE4Parse.FileProvider;
|
||||
using CUE4Parse.FileProvider.Vfs;
|
||||
using CUE4Parse.GameTypes.ApexMobile.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.DBD.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.DeltaForce.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.DreamStar.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.FSR.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.FunkoFusion.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.MJS.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.NetEase.MAR.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.PAXDEI.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.Rennsport.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.Snowbreak.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.UDWN.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.THPS.Encryption.Aes;
|
||||
using CUE4Parse.MappingsProvider;
|
||||
using CUE4Parse.UE4.AssetRegistry;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
|
|
@ -26,6 +41,7 @@ using CUE4Parse.UE4.Assets.Exports.Verse;
|
|||
using CUE4Parse.UE4.Assets.Exports.Wwise;
|
||||
using CUE4Parse.UE4.IO;
|
||||
using CUE4Parse.UE4.Localization;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Oodle.Objects;
|
||||
|
|
@ -33,16 +49,11 @@ using CUE4Parse.UE4.Readers;
|
|||
using CUE4Parse.UE4.Shaders;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.UE4.Wwise;
|
||||
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Sounds;
|
||||
using CUE4Parse.GameTypes.UDWN.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.DBD.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.DreamStar.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.PAXDEI.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.NetEase.MAR.Encryption.Aes;
|
||||
using CUE4Parse.GameTypes.FSR.Encryption.Aes;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using EpicManifestParser;
|
||||
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
|
|
@ -51,13 +62,18 @@ using FModel.Settings;
|
|||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using FModel.Views.Snooper;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using OffiUtils;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using SkiaSharp;
|
||||
|
||||
using UE4Config.Parsing;
|
||||
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
|
@ -66,7 +82,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)",
|
||||
private readonly Regex _fnLive = new(@"^FortniteGame[/\\]Content[/\\]Paks[/\\]",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
private string _internalGameName;
|
||||
|
|
@ -177,12 +193,19 @@ public class CUE4ParseViewModel : ViewModel
|
|||
Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
|
||||
Provider.CustomEncryption = Provider.Versions.Game switch
|
||||
{
|
||||
EGame.GAME_ApexLegendsMobile => ApexLegendsMobileAes.DecryptApexMobile,
|
||||
EGame.GAME_Snowbreak => SnowbreakAes.SnowbreakDecrypt,
|
||||
EGame.GAME_MarvelRivals => MarvelAes.MarvelDecrypt,
|
||||
EGame.GAME_Undawn => ToaaAes.ToaaDecrypt,
|
||||
EGame.GAME_DeadByDaylight => DBDAes.DbDDecrypt,
|
||||
EGame.GAME_PaxDei => PaxDeiAes.PaxDeiDecrypt,
|
||||
EGame.GAME_3on3FreeStyleRebound => FreeStyleReboundAes.FSRDecrypt,
|
||||
EGame.GAME_DreamStar => DreamStarAes.DreamStarDecrypt,
|
||||
EGame.GAME_DeltaForceHawkOps => DeltaForceAes.DeltaForceDecrypt,
|
||||
EGame.GAME_MonsterJamShowdown => MonsterJamShowdownAes.MonsterJamShowdownDecrypt,
|
||||
EGame.GAME_Rennsport => RennsportAes.RennsportDecrypt,
|
||||
EGame.GAME_FunkoFusion => FunkoFusionAes.FunkoFusionDecrypt,
|
||||
EGame.GAME_TonyHawkProSkater12 => THPS12Aes.THPS12Decrypt,
|
||||
_ => Provider.CustomEncryption
|
||||
};
|
||||
|
||||
|
|
@ -216,26 +239,32 @@ public class CUE4ParseViewModel : ViewModel
|
|||
ChunkCacheDirectory = cacheDir,
|
||||
ManifestCacheDirectory = cacheDir,
|
||||
ChunkBaseUrl = "http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/",
|
||||
Zlibng = ZlibHelper.Instance
|
||||
Zlibng = ZlibHelper.Instance,
|
||||
CacheChunksAsIs = false
|
||||
};
|
||||
|
||||
var startTs = Stopwatch.GetTimestamp();
|
||||
var (manifest, _) = manifestInfo.DownloadAndParseAsync(manifestOptions,
|
||||
cancellationToken: cancellationToken).GetAwaiter().GetResult();
|
||||
cancellationToken: cancellationToken,
|
||||
elementManifestPredicate: x => x.Uri.Host is ("epicgames-download1.akamaized.net" or "download.epicgames.com")
|
||||
).GetAwaiter().GetResult();
|
||||
var parseTime = Stopwatch.GetElapsedTime(startTs);
|
||||
const bool cacheChunksAsIs = false;
|
||||
|
||||
foreach (var fileManifest in manifest.FileManifestList)
|
||||
{
|
||||
if (fileManifest.FileName.Equals("Cloud/IoStoreOnDemand.ini", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
IoStoreOnDemand.Read(new StreamReader(fileManifest.GetStream(cacheChunksAsIs)));
|
||||
IoStoreOnDemand.Read(new StreamReader(fileManifest.GetStream()));
|
||||
continue;
|
||||
}
|
||||
if (!_fnLive.IsMatch(fileManifest.FileName)) continue;
|
||||
|
||||
p.RegisterVfs(fileManifest.FileName, [fileManifest.GetStream(cacheChunksAsIs)]
|
||||
, it => new FStreamArchive(it, manifest.FileManifestList.First(x => x.FileName.Equals(it)).GetStream(cacheChunksAsIs), p.Versions));
|
||||
if (!_fnLive.IsMatch(fileManifest.FileName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
p.RegisterVfs(fileManifest.FileName, [(IRandomAccessStream)fileManifest.GetStream()]
|
||||
, it => new FRandomAccessStreamArchive(it, manifest.FileManifestList.First(x => x.FileName.Equals(it)).GetStream(), p.Versions));
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
|
|
@ -252,7 +281,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
for (var i = 0; i < manifestInfo.Paks.Length; i++)
|
||||
{
|
||||
p.RegisterVfs(manifestInfo.Paks[i].GetFullName(), [manifestInfo.GetPakStream(i)]);
|
||||
p.RegisterVfs(manifestInfo.Paks[i].GetFullName(), [(IRandomAccessStream)manifestInfo.GetPakStream(i)]);
|
||||
}
|
||||
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
|
|
@ -661,6 +690,16 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
break;
|
||||
}
|
||||
case "bin" when fileName.Contains("GlobalShaderCache", StringComparison.OrdinalIgnoreCase):
|
||||
{
|
||||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var registry = new FGlobalShaderCache(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "bnk":
|
||||
case "pck":
|
||||
{
|
||||
|
|
@ -812,6 +851,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
"JunoBuildingPropAccountItemDefinition" => true,
|
||||
_ => false
|
||||
}:
|
||||
case UPaperSprite when isNone && UserSettings.Default.PreviewMaterials:
|
||||
case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes:
|
||||
case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes:
|
||||
case USkeleton when isNone && UserSettings.Default.SaveSkeletonAsMesh:
|
||||
|
|
@ -847,7 +887,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case UAnimMontage when HasFlag(bulk, EBulkType.Animations):
|
||||
case UAnimComposite when HasFlag(bulk, EBulkType.Animations):
|
||||
{
|
||||
SaveExport(export, HasFlag(bulk, EBulkType.Auto));
|
||||
SaveExport(export, updateUi);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
|
|
@ -870,7 +910,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
if (fullPath.StartsWith("/")) fullPath = fullPath[1..];
|
||||
var savedAudioPath = Path.Combine(UserSettings.Default.AudioDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLower()}";
|
||||
UserSettings.Default.KeepDirectoryStructure ? fullPath : fullPath.SubstringAfterLast('/')).Replace('\\', '/') + $".{ext.ToLowerInvariant()}";
|
||||
|
||||
if (!UserSettings.Default.IsAutoOpenSounds)
|
||||
{
|
||||
|
|
@ -893,29 +933,21 @@ public class CUE4ParseViewModel : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
private void SaveExport(UObject export, bool auto)
|
||||
private void SaveExport(UObject export, bool updateUi = true)
|
||||
{
|
||||
var toSave = new Exporter(export, UserSettings.Default.ExportOptions);
|
||||
|
||||
string dir;
|
||||
if (!auto)
|
||||
{
|
||||
var folderBrowser = new VistaFolderBrowserDialog();
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
dir = folderBrowser.SelectedPath;
|
||||
else return;
|
||||
}
|
||||
else dir = UserSettings.Default.ModelDirectory;
|
||||
|
||||
var toSaveDirectory = new DirectoryInfo(dir);
|
||||
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
|
||||
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
|
||||
{
|
||||
Log.Information("Successfully saved {FilePath}", savedFilePath);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(label, savedFilePath, true);
|
||||
});
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(label, savedFilePath, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -154,7 +154,16 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true));
|
||||
|
||||
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
||||
var mode = UserSettings.Default.LoadingMode;
|
||||
var entries = ParseBackup(openFileDialog.FileName, mode, cancellationToken);
|
||||
|
||||
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
}
|
||||
|
||||
private List<VfsEntry> ParseBackup(string path, ELoadingMode mode, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var fileStream = new FileStream(path, FileMode.Open);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
||||
if (fileStream.ReadUInt32() == _IS_LZ4)
|
||||
|
|
@ -169,25 +178,41 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
|
||||
var entries = new List<VfsEntry>();
|
||||
|
||||
var mode = UserSettings.Default.LoadingMode;
|
||||
switch (mode)
|
||||
{
|
||||
case ELoadingMode.AllButNew:
|
||||
{
|
||||
var paths = new Dictionary<string, int>();
|
||||
while (archive.Position < archive.Length)
|
||||
var paths = new HashSet<string>();
|
||||
var magic = archive.Read<uint>();
|
||||
if (magic != BackupManagerViewModel.FBKP_MAGIC)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
archive.Position -= sizeof(uint);
|
||||
while (archive.Position < archive.Length)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += 29;
|
||||
paths[archive.ReadString().ToLower()[1..]] = 0;
|
||||
archive.Position += 4;
|
||||
archive.Position += 29;
|
||||
paths.Add(archive.ReadString().ToLower()[1..]);
|
||||
archive.Position += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var version = archive.Read<EBackupVersion>();
|
||||
var count = archive.Read<int>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
archive.Position += sizeof(long) + sizeof(byte);
|
||||
paths.Add(archive.ReadString().ToLower()[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var (key, value) in _applicationView.CUE4Parse.Provider.Files)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (value is not VfsEntry entry || paths.ContainsKey(key) || entry.Path.EndsWith(".uexp") ||
|
||||
if (value is not VfsEntry entry || paths.Contains(key) || entry.Path.EndsWith(".uexp") ||
|
||||
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
|
||||
|
||||
entries.Add(entry);
|
||||
|
|
@ -198,31 +223,54 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
}
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
while (archive.Position < archive.Length)
|
||||
var magic = archive.Read<uint>();
|
||||
if (magic != BackupManagerViewModel.FBKP_MAGIC)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
archive.Position -= sizeof(uint);
|
||||
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;
|
||||
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);
|
||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
||||
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var version = archive.Read<EBackupVersion>();
|
||||
var count = archive.Read<int>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var uncompressedSize = archive.Read<long>();
|
||||
var isEncrypted = archive.ReadFlag();
|
||||
var fullPath = archive.ReadString().ToLower()[1..];
|
||||
|
||||
AddEntry(fullPath, uncompressedSize, isEncrypted, entries);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
|
||||
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
|
||||
return entries;
|
||||
}
|
||||
|
||||
private void AddEntry(string path, long uncompressedSize, bool isEncrypted, List<VfsEntry> entries)
|
||||
{
|
||||
if (path.EndsWith(".uexp") || path.EndsWith(".ubulk") || path.EndsWith(".uptnl") ||
|
||||
!_applicationView.CUE4Parse.Provider.Files.TryGetValue(path, out var asset) || asset is not VfsEntry entry ||
|
||||
entry.Size == uncompressedSize && entry.IsEncrypted == isEncrypted)
|
||||
return;
|
||||
|
||||
entries.Add(entry);
|
||||
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,9 +54,8 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
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);
|
||||
case "Help_Releases":
|
||||
Helper.OpenWindow<AdonisWindow>("Releases", () => new UpdateView().Show());
|
||||
break;
|
||||
case "Help_BugsReport":
|
||||
Process.Start(new ProcessStartInfo { FileName = Constants.ISSUE_LINK, UseShellExecute = true });
|
||||
|
|
|
|||
40
FModel/ViewModels/Commands/RemindMeCommand.cs
Normal file
40
FModel/ViewModels/Commands/RemindMeCommand.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
|
||||
namespace FModel.ViewModels.Commands;
|
||||
|
||||
public class RemindMeCommand : ViewModelCommand<UpdateViewModel>
|
||||
{
|
||||
public RemindMeCommand(UpdateViewModel contextViewModel) : base(contextViewModel)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Execute(UpdateViewModel contextViewModel, object parameter)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case "Days":
|
||||
// check for update in 3 days
|
||||
UserSettings.Default.NextUpdateCheck = DateTime.Now.AddDays(3);
|
||||
break;
|
||||
case "Week":
|
||||
// check for update next week (a week starts on Monday)
|
||||
var delay = (DayOfWeek.Monday - DateTime.Now.DayOfWeek + 7) % 7;
|
||||
UserSettings.Default.NextUpdateCheck = DateTime.Now.AddDays(delay == 0 ? 7 : delay);
|
||||
break;
|
||||
case "Month":
|
||||
// check for update next month (if today is 31st, it will be 1st of next month)
|
||||
UserSettings.Default.NextUpdateCheck = DateTime.Now.AddDays(1 - DateTime.Now.Day).AddMonths(1);
|
||||
break;
|
||||
case "Never":
|
||||
// never check for updates
|
||||
UserSettings.Default.NextUpdateCheck = DateTime.MaxValue;
|
||||
break;
|
||||
default:
|
||||
// reset
|
||||
UserSettings.Default.NextUpdateCheck = DateTime.Now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
var assetItems = ((IList) parameters[1]).Cast<AssetItem>().ToArray();
|
||||
if (!assetItems.Any()) return;
|
||||
|
||||
var updateUi = assetItems.Length > 1 ? EBulkType.Auto : EBulkType.None;
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
switch (trigger)
|
||||
|
|
@ -47,7 +48,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Textures":
|
||||
|
|
@ -55,7 +56,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Models":
|
||||
|
|
@ -63,7 +64,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | updateUi);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Animations":
|
||||
|
|
@ -71,7 +72,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | updateUi);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,28 +33,16 @@ public class GameSelectorViewModel : ViewModel
|
|||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
private bool _useCustomEGames;
|
||||
public bool UseCustomEGames
|
||||
{
|
||||
get => _useCustomEGames;
|
||||
set => SetProperty(ref _useCustomEGames, value);
|
||||
}
|
||||
|
||||
private DirectorySettings _selectedDirectory;
|
||||
public DirectorySettings SelectedDirectory
|
||||
{
|
||||
get => _selectedDirectory;
|
||||
set
|
||||
{
|
||||
SetProperty(ref _selectedDirectory, value);
|
||||
if (_selectedDirectory != null) UseCustomEGames = EnumerateUeGames().ElementAt(1).Contains(_selectedDirectory.UeVersion);
|
||||
}
|
||||
set => SetProperty(ref _selectedDirectory, value);
|
||||
}
|
||||
|
||||
private readonly ObservableCollection<DirectorySettings> _detectedDirectories;
|
||||
public ReadOnlyObservableCollection<DirectorySettings> DetectedDirectories { get; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; }
|
||||
public ReadOnlyObservableCollection<EGame> CustomUeGames { get; }
|
||||
|
||||
public GameSelectorViewModel(string gameDirectory)
|
||||
{
|
||||
|
|
@ -73,9 +61,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
else
|
||||
SelectedDirectory = DetectedDirectories.FirstOrDefault();
|
||||
|
||||
var ueGames = EnumerateUeGames().ToArray();
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[0]));
|
||||
CustomUeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[1]));
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(EnumerateUeGames()));
|
||||
}
|
||||
|
||||
public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory);
|
||||
|
|
@ -94,11 +80,11 @@ public class GameSelectorViewModel : ViewModel
|
|||
SelectedDirectory = DetectedDirectories.Last();
|
||||
}
|
||||
|
||||
private IEnumerable<IGrouping<bool, EGame>> EnumerateUeGames()
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.GroupBy(value => (int)value == ((int)value & ~0xF));
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<DirectorySettings> EnumerateDetectedGames()
|
||||
{
|
||||
yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_5);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Serialization;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
|
|
@ -27,20 +26,6 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _useCustomOutputFolders, value);
|
||||
}
|
||||
|
||||
private bool _useCustomEGames;
|
||||
public bool UseCustomEGames
|
||||
{
|
||||
get => _useCustomEGames;
|
||||
set => SetProperty(ref _useCustomEGames, value);
|
||||
}
|
||||
|
||||
private EUpdateMode _selectedUpdateMode;
|
||||
public EUpdateMode SelectedUpdateMode
|
||||
{
|
||||
get => _selectedUpdateMode;
|
||||
set => SetProperty(ref _selectedUpdateMode, value);
|
||||
}
|
||||
|
||||
private ETexturePlatform _selectedUePlatform;
|
||||
public ETexturePlatform SelectedUePlatform
|
||||
{
|
||||
|
|
@ -175,9 +160,7 @@ public class SettingsViewModel : ViewModel
|
|||
public bool SocketSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.ActorX;
|
||||
public bool CompressionSettingsEnabled => SelectedMeshExportFormat == EMeshFormat.UEFormat;
|
||||
|
||||
public ReadOnlyObservableCollection<EUpdateMode> UpdateModes { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> UeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<EGame> CustomUeGames { get; private set; }
|
||||
public ReadOnlyObservableCollection<ELanguage> AssetLanguages { get; private set; }
|
||||
public ReadOnlyObservableCollection<EAesReload> AesReloads { get; private set; }
|
||||
public ReadOnlyObservableCollection<EDiscordRpc> DiscordRpcs { get; private set; }
|
||||
|
|
@ -198,7 +181,6 @@ public class SettingsViewModel : ViewModel
|
|||
private string _audioSnapshot;
|
||||
private string _modelSnapshot;
|
||||
private string _gameSnapshot;
|
||||
private EUpdateMode _updateModeSnapshot;
|
||||
private ETexturePlatform _uePlatformSnapshot;
|
||||
private EGame _ueGameSnapshot;
|
||||
private IList<FCustomVersion> _customVersionsSnapshot;
|
||||
|
|
@ -230,7 +212,6 @@ public class SettingsViewModel : ViewModel
|
|||
_audioSnapshot = UserSettings.Default.AudioDirectory;
|
||||
_modelSnapshot = UserSettings.Default.ModelDirectory;
|
||||
_gameSnapshot = UserSettings.Default.GameDirectory;
|
||||
_updateModeSnapshot = UserSettings.Default.UpdateMode;
|
||||
_uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform;
|
||||
_ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion;
|
||||
_customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions;
|
||||
|
|
@ -255,7 +236,6 @@ public class SettingsViewModel : ViewModel
|
|||
_materialExportFormatSnapshot = UserSettings.Default.MaterialExportFormat;
|
||||
_textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat;
|
||||
|
||||
SelectedUpdateMode = _updateModeSnapshot;
|
||||
SelectedUePlatform = _uePlatformSnapshot;
|
||||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
|
|
@ -273,12 +253,7 @@ public class SettingsViewModel : ViewModel
|
|||
SelectedAesReload = UserSettings.Default.AesReload;
|
||||
SelectedDiscordRpc = UserSettings.Default.DiscordRpc;
|
||||
|
||||
var ueGames = EnumerateUeGames().ToArray();
|
||||
UseCustomEGames = ueGames[1].Contains(SelectedUeGame);
|
||||
|
||||
UpdateModes = new ReadOnlyObservableCollection<EUpdateMode>(new ObservableCollection<EUpdateMode>(EnumerateUpdateModes()));
|
||||
UeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[0]));
|
||||
CustomUeGames = new ReadOnlyObservableCollection<EGame>(new ObservableCollection<EGame>(ueGames[1]));
|
||||
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()));
|
||||
|
|
@ -302,8 +277,6 @@ public class SettingsViewModel : ViewModel
|
|||
whatShouldIDo.Add(SettingsOut.ReloadLocres);
|
||||
if (_mappingsUpdate)
|
||||
whatShouldIDo.Add(SettingsOut.ReloadMappings);
|
||||
if (_updateModeSnapshot != SelectedUpdateMode)
|
||||
whatShouldIDo.Add(SettingsOut.CheckForUpdates);
|
||||
|
||||
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
|
||||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
|
||||
|
|
@ -317,7 +290,6 @@ public class SettingsViewModel : ViewModel
|
|||
_gameSnapshot != UserSettings.Default.GameDirectory) // textbox
|
||||
restart = true;
|
||||
|
||||
UserSettings.Default.UpdateMode = SelectedUpdateMode;
|
||||
UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame;
|
||||
UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform;
|
||||
UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions;
|
||||
|
|
@ -342,12 +314,11 @@ public class SettingsViewModel : ViewModel
|
|||
return restart;
|
||||
}
|
||||
|
||||
private IEnumerable<EUpdateMode> EnumerateUpdateModes() => Enum.GetValues<EUpdateMode>();
|
||||
private IEnumerable<IGrouping<bool, EGame>> EnumerateUeGames()
|
||||
private IEnumerable<EGame> EnumerateUeGames()
|
||||
=> Enum.GetValues<EGame>()
|
||||
.GroupBy(value => (int)value)
|
||||
.Select(group => group.First())
|
||||
.GroupBy(value => (int)value == ((int)value & ~0xF));
|
||||
.OrderBy(value => (int)value == ((int)value & ~0xF));
|
||||
private IEnumerable<ELanguage> EnumerateAssetLanguages() => Enum.GetValues<ELanguage>();
|
||||
private IEnumerable<EAesReload> EnumerateAesReloads() => Enum.GetValues<EAesReload>();
|
||||
private IEnumerable<EDiscordRpc> EnumerateDiscordRpcs() => Enum.GetValues<EDiscordRpc>();
|
||||
|
|
|
|||
|
|
@ -68,9 +68,13 @@ public class TabImage : ViewModel
|
|||
Image = null;
|
||||
return;
|
||||
}
|
||||
|
||||
_bmp = bitmap;
|
||||
using var data = _bmp.Encode(NoAlpha ? SKEncodedImageFormat.Jpeg : SKEncodedImageFormat.Png, 100);
|
||||
using var data = _bmp.Encode(NoAlpha ? ETextureFormat.Jpeg : UserSettings.Default.TextureExportFormat, 100);
|
||||
using var stream = new MemoryStream(ImageBuffer = data.ToArray(), false);
|
||||
if (UserSettings.Default.TextureExportFormat == ETextureFormat.Tga)
|
||||
return;
|
||||
|
||||
var image = new BitmapImage();
|
||||
image.BeginInit();
|
||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
||||
|
|
@ -240,18 +244,31 @@ public class TabItem : ViewModel
|
|||
|
||||
public void AddImage(UTexture texture, bool save, bool updateUi)
|
||||
{
|
||||
var img = texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||
if (texture is UTextureCube)
|
||||
var appendLayerNumber = false;
|
||||
var img = new SKBitmap[1];
|
||||
if (texture is UTexture2DArray textureArray)
|
||||
{
|
||||
img = img?.ToPanorama();
|
||||
img = textureArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||
appendLayerNumber = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
img[0] = texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform);
|
||||
if (texture is UTextureCube)
|
||||
{
|
||||
img[0] = img[0]?.ToPanorama();
|
||||
}
|
||||
}
|
||||
|
||||
AddImage(texture.Name, texture.RenderNearestNeighbor, img, save, updateUi);
|
||||
AddImage(texture.Name, texture.RenderNearestNeighbor, img, save, updateUi, appendLayerNumber);
|
||||
}
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi)
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi, bool appendLayerNumber = false)
|
||||
{
|
||||
foreach (var i in img) AddImage(name, rnn, i, save, updateUi);
|
||||
for (var i = 0; i < img.Length; i++)
|
||||
{
|
||||
AddImage($"{name}{(appendLayerNumber ? $"_{i}" : "")}", rnn, img[i], save, updateUi);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap img, bool save, bool updateUi)
|
||||
|
|
@ -288,7 +305,16 @@ public class TabItem : ViewModel
|
|||
private void SaveImage(TabImage image, bool updateUi)
|
||||
{
|
||||
if (image == null) return;
|
||||
var fileName = $"{image.ExportName}.png";
|
||||
|
||||
var ext = UserSettings.Default.TextureExportFormat switch
|
||||
{
|
||||
ETextureFormat.Png => ".png",
|
||||
ETextureFormat.Jpeg => ".jpg",
|
||||
ETextureFormat.Tga => ".tga",
|
||||
_ => ".png"
|
||||
};
|
||||
|
||||
var fileName = image.ExportName + ext;
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
|
|
|
|||
74
FModel/ViewModels/UpdateViewModel.cs
Normal file
74
FModel/ViewModels/UpdateViewModel.cs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Data;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using FModel.ViewModels.Commands;
|
||||
using FModel.Views.Resources.Converters;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class UpdateViewModel : ViewModel
|
||||
{
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
|
||||
private RemindMeCommand _remindMeCommand;
|
||||
public RemindMeCommand RemindMeCommand => _remindMeCommand ??= new RemindMeCommand(this);
|
||||
|
||||
public RangeObservableCollection<GitHubCommit> Commits { get; }
|
||||
public ICollectionView CommitsView { get; }
|
||||
|
||||
public UpdateViewModel()
|
||||
{
|
||||
Commits = new RangeObservableCollection<GitHubCommit>();
|
||||
CommitsView = new ListCollectionView(Commits)
|
||||
{
|
||||
GroupDescriptions = { new PropertyGroupDescription("Commit.Author.Date", new DateTimeToDateConverter()) }
|
||||
};
|
||||
|
||||
if (UserSettings.Default.NextUpdateCheck < DateTime.Now)
|
||||
RemindMeCommand.Execute(this, null);
|
||||
}
|
||||
|
||||
public async Task Load()
|
||||
{
|
||||
Commits.AddRange(await _apiEndpointView.GitHubApi.GetCommitHistoryAsync());
|
||||
|
||||
var qa = await _apiEndpointView.GitHubApi.GetReleaseAsync("qa");
|
||||
qa.Assets.OrderByDescending(x => x.CreatedAt).First().IsLatest = true;
|
||||
|
||||
foreach (var asset in qa.Assets)
|
||||
{
|
||||
var commitSha = asset.Name.SubstringBeforeLast(".zip");
|
||||
var commit = Commits.FirstOrDefault(x => x.Sha == commitSha);
|
||||
if (commit != null)
|
||||
{
|
||||
commit.Asset = asset;
|
||||
}
|
||||
else
|
||||
{
|
||||
Commits.Add(new GitHubCommit
|
||||
{
|
||||
Sha = commitSha,
|
||||
Commit = new Commit
|
||||
{
|
||||
Message = $"FModel ({commitSha[..7]})",
|
||||
Author = new Author { Name = asset.Uploader.Login, Date = asset.CreatedAt }
|
||||
},
|
||||
Author = asset.Uploader,
|
||||
Asset = asset
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadLatest()
|
||||
{
|
||||
Commits.FirstOrDefault(x => x.Asset.IsLatest)?.Download();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.DirectorySelector"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
|
|
@ -51,37 +52,29 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Detected Game" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
|
||||
<controls:FilterableComboBox Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding DetectedDirectories}" Margin="0 0 0 5"
|
||||
Style="{StaticResource UComboBox}"
|
||||
adonisExtensions:WatermarkExtension.Watermark="Search for a game..."
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding GameName, Converter={x:Static converters:StringToGameConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</controls:FilterableComboBox>
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="UE Versions" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="1" Grid.Column="2" Margin="0 0 0 5"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ItemsSource" Value="{Binding UeGames}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding UseCustomEGames}" Value="True">
|
||||
<Setter Property="ItemsSource" Value="{Binding CustomUeGames}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<controls:FilterableComboBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5"
|
||||
ItemsSource="{Binding UeGames}"
|
||||
Style="{StaticResource UComboBox}"
|
||||
VerticalAlignment="Center" SelectedItem="{Binding SelectedDirectory.UeVersion, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Row="1" Grid.Column="4" Margin="5 0 0 5" ToolTip="Enable custom UE versions"
|
||||
IsChecked="{Binding UseCustomEGames, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
BorderBrush="White" Style="{StaticResource HighlightedCheckBox}" />
|
||||
</controls:FilterableComboBox>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Directory" VerticalAlignment="Center" />
|
||||
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding SelectedDirectory.GameDirectory, Mode=TwoWay}" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
using FModel.ViewModels;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FModel.ViewModels;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
using System.Windows;
|
||||
using CUE4Parse.Utils;
|
||||
|
|
@ -39,8 +42,29 @@ public partial class DirectorySelector
|
|||
var folderBrowser = new VistaFolderBrowserDialog {ShowNewFolderButton = false};
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
{
|
||||
HelloMyNameIsGame.Text = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
HelloGameMyNameIsDirectory.Text = folderBrowser.SelectedPath;
|
||||
|
||||
// install_folder/
|
||||
// ├─ Engine/
|
||||
// ├─ GameName/
|
||||
// │ ├─ Binaries/
|
||||
// │ ├─ Content/
|
||||
// │ │ ├─ Paks/
|
||||
// our goal is to get the GameName folder
|
||||
var currentFolder = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
if (currentFolder.Equals("Paks", StringComparison.InvariantCulture))
|
||||
{
|
||||
var dir = new DirectoryInfo(folderBrowser.SelectedPath);
|
||||
if (dir.Parent is { Parent: not null } &&
|
||||
dir.Parent.Name.Equals("Content", StringComparison.InvariantCulture) &&
|
||||
dir.Parent.Parent.GetDirectories().Any(x => x.Name == "Binaries"))
|
||||
{
|
||||
HelloMyNameIsGame.Text = dir.Parent.Parent.Name;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
HelloMyNameIsGame.Text = folderBrowser.SelectedPath.SubstringAfterLast('\\');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
131
FModel/Views/Resources/Controls/CommitControl.xaml
Normal file
131
FModel/Views/Resources/Controls/CommitControl.xaml
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<UserControl x:Class="FModel.Views.Resources.Controls.CommitControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Resources.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Border BorderThickness="1" CornerRadius="0.5"
|
||||
BorderBrush="{DynamicResource {x:Static adonisUi:Brushes.Layer4BackgroundBrush}}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0" Margin="5">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="5" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="{Binding Commit.Message, Converter={x:Static converters:CommitMessageConverter.Instance}, ConverterParameter=Title}" FontWeight="Bold" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Row="1" Text="{Binding Commit.Message, Converter={x:Static converters:CommitMessageConverter.Instance}, ConverterParameter=Description}" TextWrapping="Wrap">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Commit.Message, Converter={x:Static converters:CommitMessageConverter.Instance}, ConverterParameter=Description}" Value="">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<Grid Grid.Row="3">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="5"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="5"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Ellipse Grid.Column="0">
|
||||
<Ellipse.Fill>
|
||||
<ImageBrush ImageSource="{Binding Author.AvatarUrl}" />
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
<TextBlock Grid.Column="2" FontSize="11">
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} committed {1}">
|
||||
<Binding Path="Author.Login" />
|
||||
<Binding Path="Commit.Author.Date" Converter="{x:Static converters:RelativeDateTimeConverter.Instance}" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="15" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Border Grid.Column="0"
|
||||
BorderThickness="1"
|
||||
CornerRadius="2.5"
|
||||
Padding="5,2"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock FontSize="9" Foreground="{Binding BorderBrush, RelativeSource={RelativeSource AncestorType=Border}}">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Asset.IsLatest}" Value="True">
|
||||
<Setter Property="Text" Value="Latest" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
|
||||
<Setter Property="Text" Value="Current" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
|
||||
<Border.Style>
|
||||
<Style TargetType="Border">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding Asset.IsLatest}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
<Setter Property="BorderBrush" Value="#3fb950" />
|
||||
<Setter Property="Background" Value="#0f3fb950" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible" />
|
||||
<Setter Property="BorderBrush" Value="#3f92b9" />
|
||||
<Setter Property="Background" Value="#0f3f92b9" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Border.Style>
|
||||
</Border>
|
||||
|
||||
<controls:CommitDownloaderControl Grid.Column="2" Commit="{Binding}">
|
||||
<controls:CommitDownloaderControl.Style>
|
||||
<Style TargetType="controls:CommitDownloaderControl">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsDownloadable}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</controls:CommitDownloaderControl.Style>
|
||||
</controls:CommitDownloaderControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Border>
|
||||
</UserControl>
|
||||
|
||||
12
FModel/Views/Resources/Controls/CommitControl.xaml.cs
Normal file
12
FModel/Views/Resources/Controls/CommitControl.xaml.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using System.Windows.Controls;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public partial class CommitControl : UserControl
|
||||
{
|
||||
public CommitControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
55
FModel/Views/Resources/Controls/CommitDownloaderControl.xaml
Normal file
55
FModel/Views/Resources/Controls/CommitDownloaderControl.xaml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<UserControl x:Class="FModel.Views.Resources.Controls.CommitDownloaderControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="../Resources.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="15" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Viewbox Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Canvas Width="16" Height="16">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
|
||||
Data="{StaticResource ArchiveIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
<StackPanel Grid.Column="2">
|
||||
<TextBlock Text="Size" FontSize="10" />
|
||||
<TextBlock FontSize="10" Text="{Binding Asset.Size, Converter={x:Static converters:SizeToStringConverter.Instance}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Button Grid.Column="2" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Download"
|
||||
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType=Grid}}"
|
||||
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
|
||||
IsEnabled="{Binding IsCurrent, Converter={x:Static converters:InvertBooleanConverter.Instance}}"
|
||||
Click="OnDownload">
|
||||
<Viewbox Width="16" Height="16" VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Canvas Width="16" Height="16">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
|
||||
Data="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z" />
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.DisabledForegroundBrush}}"
|
||||
Data="M11.78 4.72a.749.749 0 1 1-1.06 1.06L8.75 3.811V9.5a.75.75 0 0 1-1.5 0V3.811L5.28 5.78a.749.749 0 1 1-1.06-1.06l3.25-3.25a.749.749 0 0 1 1.06 0l3.25 3.25Z" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
public partial class CommitDownloaderControl : UserControl
|
||||
{
|
||||
public CommitDownloaderControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CommitProperty =
|
||||
DependencyProperty.Register(nameof(Commit), typeof(GitHubCommit), typeof(CommitDownloaderControl), new PropertyMetadata(null));
|
||||
|
||||
public GitHubCommit Commit
|
||||
{
|
||||
get { return (GitHubCommit)GetValue(CommitProperty); }
|
||||
set { SetValue(CommitProperty, value); }
|
||||
}
|
||||
|
||||
private void OnDownload(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Commit.Download();
|
||||
}
|
||||
}
|
||||
|
||||
273
FModel/Views/Resources/Controls/FilterableComboBox.cs
Normal file
273
FModel/Views/Resources/Controls/FilterableComboBox.cs
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// https://stackoverflow.com/a/58066259/13389331
|
||||
/// </summary>
|
||||
public class FilterableComboBox : ComboBox
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, on lost focus or enter key pressed, checks the text in the combobox. If the text is not present
|
||||
/// in the list, it leaves it blank.
|
||||
/// </summary>
|
||||
public bool OnlyValuesInList {
|
||||
get => (bool)GetValue(OnlyValuesInListProperty);
|
||||
set => SetValue(OnlyValuesInListProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty OnlyValuesInListProperty =
|
||||
DependencyProperty.Register(nameof(OnlyValuesInList), typeof(bool), typeof(FilterableComboBox));
|
||||
|
||||
/// <summary>
|
||||
/// Selected item, changes only on lost focus or enter key pressed
|
||||
/// </summary>
|
||||
public object EffectivelySelectedItem {
|
||||
get => (bool)GetValue(EffectivelySelectedItemProperty);
|
||||
set => SetValue(EffectivelySelectedItemProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty EffectivelySelectedItemProperty =
|
||||
DependencyProperty.Register(nameof(EffectivelySelectedItem), typeof(object), typeof(FilterableComboBox));
|
||||
|
||||
private string CurrentFilter = string.Empty;
|
||||
private bool TextBoxFreezed;
|
||||
protected TextBox EditableTextBox => GetTemplateChild("PART_EditableTextBox") as TextBox;
|
||||
private UserChange<bool> IsDropDownOpenUC;
|
||||
|
||||
/// <summary>
|
||||
/// Triggers on lost focus or enter key pressed, if the selected item changed since the last time focus was lost or enter was pressed.
|
||||
/// </summary>
|
||||
public event Action<FilterableComboBox, object> SelectionEffectivelyChanged;
|
||||
|
||||
public FilterableComboBox()
|
||||
{
|
||||
IsDropDownOpenUC = new UserChange<bool>(v => IsDropDownOpen = v);
|
||||
DropDownOpened += FilteredComboBox_DropDownOpened;
|
||||
|
||||
Focusable = true;
|
||||
IsEditable = true;
|
||||
IsTextSearchEnabled = true;
|
||||
StaysOpenOnEdit = true;
|
||||
IsReadOnly = false;
|
||||
|
||||
Loaded += (s, e) => {
|
||||
if (EditableTextBox != null)
|
||||
new TextBoxBaseUserChangeTracker(EditableTextBox).UserTextChanged += FilteredComboBox_UserTextChange;
|
||||
};
|
||||
|
||||
SelectionChanged += (_, __) => shouldTriggerSelectedItemChanged = true;
|
||||
|
||||
SelectionEffectivelyChanged += (_, o) => EffectivelySelectedItem = o;
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnPreviewKeyDown(e);
|
||||
if (e.Key == Key.Down && !IsDropDownOpen) {
|
||||
IsDropDownOpen = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Escape) {
|
||||
ClearFilter();
|
||||
Text = "";
|
||||
IsDropDownOpen = true;
|
||||
}
|
||||
else if (e.Key == Key.Enter || e.Key == Key.Tab) {
|
||||
CheckSelectedItem();
|
||||
TriggerSelectedItemChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
|
||||
{
|
||||
base.OnPreviewLostKeyboardFocus(e);
|
||||
CheckSelectedItem();
|
||||
if ((e.OldFocus == this || e.OldFocus == EditableTextBox) && e.NewFocus != this && e.NewFocus != EditableTextBox)
|
||||
TriggerSelectedItemChanged();
|
||||
}
|
||||
|
||||
private void CheckSelectedItem()
|
||||
{
|
||||
if (OnlyValuesInList)
|
||||
Text = SelectedItem?.ToString() ?? "";
|
||||
}
|
||||
|
||||
private bool shouldTriggerSelectedItemChanged = false;
|
||||
private void TriggerSelectedItemChanged()
|
||||
{
|
||||
if (shouldTriggerSelectedItemChanged) {
|
||||
SelectionEffectivelyChanged?.Invoke(this, SelectedItem);
|
||||
shouldTriggerSelectedItemChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearFilter()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurrentFilter)) return;
|
||||
CurrentFilter = "";
|
||||
CollectionViewSource.GetDefaultView(ItemsSource).Refresh();
|
||||
}
|
||||
|
||||
private void FilteredComboBox_DropDownOpened(object sender, EventArgs e)
|
||||
{
|
||||
if (IsDropDownOpenUC.IsUserChange)
|
||||
ClearFilter();
|
||||
}
|
||||
|
||||
private void FilteredComboBox_UserTextChange(object sender, EventArgs e)
|
||||
{
|
||||
if (TextBoxFreezed) return;
|
||||
var tb = EditableTextBox;
|
||||
if (tb.SelectionStart + tb.SelectionLength == tb.Text.Length)
|
||||
CurrentFilter = tb.Text.Substring(0, tb.SelectionStart).ToLower();
|
||||
else
|
||||
CurrentFilter = tb.Text.ToLower();
|
||||
RefreshFilter();
|
||||
}
|
||||
|
||||
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
||||
{
|
||||
if (newValue != null) {
|
||||
var view = CollectionViewSource.GetDefaultView(newValue);
|
||||
view.Filter += FilterItem;
|
||||
}
|
||||
|
||||
if (oldValue != null) {
|
||||
var view = CollectionViewSource.GetDefaultView(oldValue);
|
||||
if (view != null) view.Filter -= FilterItem;
|
||||
}
|
||||
|
||||
base.OnItemsSourceChanged(oldValue, newValue);
|
||||
}
|
||||
|
||||
private void RefreshFilter()
|
||||
{
|
||||
if (ItemsSource == null) return;
|
||||
|
||||
var view = CollectionViewSource.GetDefaultView(ItemsSource);
|
||||
FreezTextBoxState(() => {
|
||||
var isDropDownOpen = IsDropDownOpen;
|
||||
//always hide because showing it enables the user to pick with up and down keys, otherwise it's not working because of the glitch in view.Refresh()
|
||||
IsDropDownOpenUC.Set(false);
|
||||
view.Refresh();
|
||||
|
||||
if (!string.IsNullOrEmpty(CurrentFilter) || isDropDownOpen)
|
||||
IsDropDownOpenUC.Set(true);
|
||||
|
||||
if (SelectedItem == null) {
|
||||
foreach (var itm in ItemsSource)
|
||||
if (itm.ToString() == Text) {
|
||||
SelectedItem = itm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void FreezTextBoxState(Action action)
|
||||
{
|
||||
TextBoxFreezed = true;
|
||||
var tb = EditableTextBox;
|
||||
var text = Text;
|
||||
var selStart = tb.SelectionStart;
|
||||
var selLen = tb.SelectionLength;
|
||||
action();
|
||||
Text = text;
|
||||
tb.SelectionStart = selStart;
|
||||
tb.SelectionLength = selLen;
|
||||
TextBoxFreezed = false;
|
||||
}
|
||||
|
||||
private bool FilterItem(object value)
|
||||
{
|
||||
if (value == null) return false;
|
||||
if (CurrentFilter.Length == 0) return true;
|
||||
|
||||
return value.ToString().ToLower().Contains(CurrentFilter);
|
||||
}
|
||||
|
||||
private class TextBoxBaseUserChangeTracker
|
||||
{
|
||||
private bool IsTextInput { get; set; }
|
||||
|
||||
public TextBox TextBoxBase { get; set; }
|
||||
private List<Key> PressedKeys = new List<Key>();
|
||||
public event EventHandler UserTextChanged;
|
||||
private string LastText;
|
||||
|
||||
public TextBoxBaseUserChangeTracker(TextBox textBoxBase)
|
||||
{
|
||||
TextBoxBase = textBoxBase;
|
||||
LastText = TextBoxBase.ToString();
|
||||
|
||||
textBoxBase.PreviewTextInput += (s, e) => {
|
||||
IsTextInput = true;
|
||||
};
|
||||
|
||||
textBoxBase.TextChanged += (s, e) => {
|
||||
var isUserChange = PressedKeys.Count > 0 || IsTextInput || LastText == TextBoxBase.ToString();
|
||||
IsTextInput = false;
|
||||
LastText = TextBoxBase.ToString();
|
||||
if (isUserChange)
|
||||
UserTextChanged?.Invoke(this, e);
|
||||
};
|
||||
|
||||
textBoxBase.PreviewKeyDown += (s, e) => {
|
||||
switch (e.Key) {
|
||||
case Key.Back:
|
||||
case Key.Space:
|
||||
if (!PressedKeys.Contains(e.Key))
|
||||
PressedKeys.Add(e.Key);
|
||||
break;
|
||||
}
|
||||
if (e.Key == Key.Back) {
|
||||
var textBox = textBoxBase as TextBox;
|
||||
if (textBox.SelectionStart > 0 && textBox.SelectionLength > 0 && (textBox.SelectionStart + textBox.SelectionLength) == textBox.Text.Length) {
|
||||
textBox.SelectionStart--;
|
||||
textBox.SelectionLength++;
|
||||
e.Handled = true;
|
||||
UserTextChanged?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
textBoxBase.PreviewKeyUp += (s, e) => {
|
||||
if (PressedKeys.Contains(e.Key))
|
||||
PressedKeys.Remove(e.Key);
|
||||
};
|
||||
|
||||
textBoxBase.LostFocus += (s, e) => {
|
||||
PressedKeys.Clear();
|
||||
IsTextInput = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class UserChange<T>
|
||||
{
|
||||
private Action<T> action;
|
||||
|
||||
public bool IsUserChange { get; private set; } = true;
|
||||
|
||||
public UserChange(Action<T> action)
|
||||
{
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public void Set(T val)
|
||||
{
|
||||
try {
|
||||
IsUserChange = false;
|
||||
action(val);
|
||||
}
|
||||
finally {
|
||||
IsUserChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
FModel/Views/Resources/Converters/CommitMessageConverter.cs
Normal file
25
FModel/Views/Resources/Converters/CommitMessageConverter.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class CommitMessageConverter : IValueConverter
|
||||
{
|
||||
public static readonly CommitMessageConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string commitMessage)
|
||||
{
|
||||
var parts = commitMessage.Split("\n\n");
|
||||
return parameter?.ToString() == "Title" ? parts[0] : parts.Length > 1 ? parts[1] : string.Empty;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
24
FModel/Views/Resources/Converters/DateTimeToDateConverter.cs
Normal file
24
FModel/Views/Resources/Converters/DateTimeToDateConverter.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class DateTimeToDateConverter : IValueConverter
|
||||
{
|
||||
public static readonly DateTimeToDateConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is DateTime dateTime)
|
||||
{
|
||||
return DateOnly.FromDateTime(dateTime);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
24
FModel/Views/Resources/Converters/InvertBooleanConverter.cs
Normal file
24
FModel/Views/Resources/Converters/InvertBooleanConverter.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class InvertBooleanConverter : IValueConverter
|
||||
{
|
||||
public static readonly InvertBooleanConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool boolean)
|
||||
{
|
||||
return !boolean;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace FModel.Views.Resources.Converters;
|
||||
|
||||
public class RelativeDateTimeConverter : IValueConverter
|
||||
{
|
||||
public static readonly RelativeDateTimeConverter Instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is DateTime dateTime)
|
||||
{
|
||||
var timeSpan = DateTime.Now - dateTime.ToLocalTime();
|
||||
|
||||
int time;
|
||||
string unit;
|
||||
if (timeSpan.TotalSeconds < 30)
|
||||
return "Just now";
|
||||
|
||||
if (timeSpan.TotalMinutes < 1)
|
||||
{
|
||||
time = timeSpan.Seconds;
|
||||
unit = "second";
|
||||
}
|
||||
else if (timeSpan.TotalHours < 1)
|
||||
{
|
||||
time = timeSpan.Minutes;
|
||||
unit = "minute";
|
||||
}
|
||||
else switch (timeSpan.TotalDays)
|
||||
{
|
||||
case < 1:
|
||||
time = timeSpan.Hours;
|
||||
unit = "hour";
|
||||
break;
|
||||
case < 7:
|
||||
time = timeSpan.Days;
|
||||
unit = "day";
|
||||
break;
|
||||
case < 30:
|
||||
time = timeSpan.Days / 7;
|
||||
unit = "week";
|
||||
break;
|
||||
case < 365:
|
||||
time = timeSpan.Days / 30;
|
||||
unit = "month";
|
||||
break;
|
||||
default:
|
||||
time = timeSpan.Days / 365;
|
||||
unit = "year";
|
||||
break;
|
||||
}
|
||||
|
||||
return $"{time} {unit}{(time > 1 ? "s" : string.Empty)} ago";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -68,6 +68,8 @@
|
|||
<Geometry x:Key="UnfoldIcon">M12 5.83l2.46 2.46c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L12.7 3.7c-.39-.39-1.02-.39-1.41 0L8.12 6.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 5.83zm0 12.34l-2.46-2.46c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l3.17 3.18c.39.39 1.02.39 1.41 0l3.17-3.17c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L12 18.17z</Geometry>
|
||||
<Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry>
|
||||
<Geometry x:Key="MeshIcon">M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z</Geometry>
|
||||
<Geometry x:Key="ArchiveIcon">M3.5 1.75v11.5c0 .09.048.173.126.217a.75.75 0 0 1-.752 1.298A1.748 1.748 0 0 1 2 13.25V1.75C2 .784 2.784 0 3.75 0h5.586c.464 0 .909.185 1.237.513l2.914 2.914c.329.328.513.773.513 1.237v8.586A1.75 1.75 0 0 1 12.25 15h-.5a.75.75 0 0 1 0-1.5h.5a.25.25 0 0 0 .25-.25V4.664a.25.25 0 0 0-.073-.177L9.513 1.573a.25.25 0 0 0-.177-.073H7.25a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5h-3a.25.25 0 0 0-.25.25Zm3.75 8.75h.5c.966 0 1.75.784 1.75 1.75v3a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1-.75-.75v-3c0-.966.784-1.75 1.75-1.75ZM6 5.25a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 6 5.25Zm.75 2.25h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 6.75A.75.75 0 0 1 8.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 6.75ZM8.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM8 9.75A.75.75 0 0 1 8.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 8 9.75Zm-1 2.5v2.25h1v-2.25a.25.25 0 0 0-.25-.25h-.5a.25.25 0 0 0-.25.25Z</Geometry>
|
||||
<Geometry x:Key="GitHubIcon">M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z</Geometry>
|
||||
|
||||
<Style x:Key="TabItemFillSpace" TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
|
||||
<Setter Property="Width">
|
||||
|
|
@ -872,7 +874,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture (.png)" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Textures">
|
||||
<MenuItem Header="Save Texture" Command="{Binding TabCommand}" CommandParameter="Asset_Save_Textures">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -1471,6 +1473,222 @@
|
|||
<Setter Property="Focusable" Value="False" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="UComboBox" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="adonisExtensions:WatermarkExtension.Watermark" Value="UE5 / UE4 / GameName..." />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
<Grid>
|
||||
<Border x:Name="Border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"/>
|
||||
|
||||
<Border x:Name="SpotlightLayer"
|
||||
Background="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BackgroundBrush}"
|
||||
BorderBrush="{TemplateBinding adonisExtensions:CursorSpotlightExtension.BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
|
||||
adonisExtensions:CursorSpotlightExtension.MouseEventSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}}"
|
||||
SnapsToDevicePixels="False"/>
|
||||
|
||||
<ToggleButton x:Name="ToggleButton"
|
||||
ClickMode="Press"
|
||||
Focusable="False"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
adonisExtensions:CornerRadiusExtension.CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
|
||||
IsChecked="{Binding Path=IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
Template="{StaticResource ComboBoxToggleButtonTemplate}"/>
|
||||
|
||||
<Border Margin="0, 0, 11, 0">
|
||||
|
||||
<DockPanel Margin="{TemplateBinding Padding}">
|
||||
|
||||
<adonisControls:ValidationErrorIndicator x:Name="ErrorAlertHost"
|
||||
ValidatedElement="{Binding ., RelativeSource={RelativeSource TemplatedParent}}"
|
||||
IsValidatedElementFocused="False"
|
||||
IsErrorMessageDisplayOnFocusEnabled="{TemplateBinding adonisExtensions:ValidationExtension.IsErrorMessageVisibleOnFocus}"
|
||||
IsErrorMessageDisplayOnMouseOverEnabled="{TemplateBinding adonisExtensions:ValidationExtension.IsErrorMessageVisibleOnMouseOver}"
|
||||
ErrorMessagePlacement="{TemplateBinding adonisExtensions:ValidationExtension.ErrorMessagePlacement}"
|
||||
Visibility="Collapsed"
|
||||
DockPanel.Dock="Left"
|
||||
Margin="0, 0, 4, 0"/>
|
||||
|
||||
<ContentPresenter x:Name="ContentSite"
|
||||
IsHitTestVisible="False"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding ComboBox.SelectionBoxItem}"
|
||||
ContentTemplate="{TemplateBinding ComboBox.SelectionBoxItemTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>
|
||||
|
||||
|
||||
<TextBox x:Name="PART_EditableTextBox"
|
||||
IsReadOnly="{TemplateBinding IsReadOnly}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Visibility="Hidden"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Focusable="True">
|
||||
<TextBox.Template>
|
||||
<ControlTemplate TargetType="TextBox" >
|
||||
<Grid>
|
||||
<ContentPresenter x:Name="PlaceholderHost"
|
||||
Content="{Binding Path=(adonisExtensions:WatermarkExtension.Watermark), RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Opacity="0.5"
|
||||
IsHitTestVisible="False"
|
||||
Visibility="Collapsed"/>
|
||||
|
||||
<ScrollViewer Name="PART_ContentHost"
|
||||
Focusable="False"
|
||||
HorizontalScrollBarVisibility="Hidden"
|
||||
VerticalScrollBarVisibility="Hidden"
|
||||
Template="{StaticResource TextBoxScrollViewerTemplate}"/>
|
||||
</Grid>
|
||||
|
||||
<ControlTemplate.Triggers>
|
||||
<DataTrigger Binding="{Binding Path=(adonisExtensions:WatermarkExtension.IsWatermarkVisible), RelativeSource={RelativeSource FindAncestor, AncestorType=ComboBox}}" Value="True">
|
||||
<Setter Property="Visibility" TargetName="PlaceholderHost" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</TextBox.Template>
|
||||
</TextBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Popup showing items -->
|
||||
<Popup x:Name="PART_Popup"
|
||||
Placement="Bottom"
|
||||
Focusable="False"
|
||||
AllowsTransparency="True"
|
||||
IsOpen="{TemplateBinding ComboBox.IsDropDownOpen}"
|
||||
PopupAnimation="Slide"
|
||||
adonisExtensions:LayerExtension.IncreaseLayer="True">
|
||||
|
||||
<Grid x:Name="DropDown"
|
||||
SnapsToDevicePixels="True"
|
||||
MinWidth="{TemplateBinding FrameworkElement.ActualWidth}"
|
||||
MaxHeight="{TemplateBinding ComboBox.MaxDropDownHeight}">
|
||||
|
||||
<Border x:Name="DropDownBorder"
|
||||
Background="{TemplateBinding Background}"
|
||||
Margin="0, 1, 0, 0"
|
||||
CornerRadius="{TemplateBinding adonisExtensions:CornerRadiusExtension.CornerRadius}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}">
|
||||
|
||||
<ScrollViewer x:Name="DropDownScroller"
|
||||
SnapsToDevicePixels="True"
|
||||
adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode="{Binding Path=(adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
adonisExtensions:ScrollViewerExtension.HorizontalScrollBarExpansionMode="{Binding Path=(adonisExtensions:ScrollViewerExtension.HorizontalScrollBarExpansionMode), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement="{Binding Path=(adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
adonisExtensions:ScrollViewerExtension.HorizontalScrollBarPlacement="{Binding Path=(adonisExtensions:ScrollViewerExtension.HorizontalScrollBarPlacement), RelativeSource={RelativeSource TemplatedParent}}"
|
||||
adonisExtensions:ScrollViewerExtension.HideScrollBarsUntilMouseOver="{Binding Path=(adonisExtensions:ScrollViewerExtension.HideScrollBarsUntilMouseOver), RelativeSource={RelativeSource TemplatedParent}}">
|
||||
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<StackPanel x:Name="BlindIndicator"
|
||||
Grid.ZIndex="1"
|
||||
Focusable="False"
|
||||
Margin="0 7 0 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="Visible"
|
||||
MinHeight="15">
|
||||
<ContentControl ContentTemplate="{DynamicResource {x:Static adonisUi:Templates.Expander}}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
RenderTransformOrigin="0.5 0.5">
|
||||
<ContentControl.RenderTransform>
|
||||
<TransformGroup>
|
||||
<RotateTransform Angle="180"/>
|
||||
<TranslateTransform Y="0"/>
|
||||
</TransformGroup>
|
||||
</ContentControl.RenderTransform>
|
||||
<ContentControl.Triggers>
|
||||
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||
<BeginStoryboard>
|
||||
<BeginStoryboard.Storyboard>
|
||||
<Storyboard RepeatBehavior="Forever">
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetProperty="RenderTransform.Children[0].Angle"
|
||||
From="190" To="170" Duration="0:0:0.3"
|
||||
AutoReverse="True" />
|
||||
<DoubleAnimation
|
||||
Storyboard.TargetProperty="RenderTransform.Children[1].Y"
|
||||
From="-1" To="1" Duration="0:0:0.2"
|
||||
AutoReverse="True" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard.Storyboard>
|
||||
</BeginStoryboard>
|
||||
</EventTrigger>
|
||||
</ContentControl.Triggers>
|
||||
</ContentControl>
|
||||
<!-- <TextBlock Text="Game-Specific Versions" -->
|
||||
<!-- Foreground="{TemplateBinding Foreground}" -->
|
||||
<!-- FontSize="9" -->
|
||||
<!-- TextAlignment="Center"> -->
|
||||
<!-- </TextBlock> -->
|
||||
|
||||
<!-- <StackPanel.Background> -->
|
||||
<!-- <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> -->
|
||||
<!-- <GradientStop Color="#35000000" Offset="0" /> -->
|
||||
<!-- <GradientStop Color="#00000000" Offset="1" /> -->
|
||||
<!-- </LinearGradientBrush> -->
|
||||
<!-- </StackPanel.Background> -->
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Popup>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
|
||||
<Trigger Property="HasItems" Value="False">
|
||||
<Setter Property="FrameworkElement.MinHeight" TargetName="DropDownBorder" Value="95"/>
|
||||
<Setter Property="Visibility" TargetName="BlindIndicator" Value="Collapsed" />
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsGrouping" Value="True">
|
||||
<Setter Property="ScrollViewer.CanContentScroll" Value="False"/>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsEditable" Value="True">
|
||||
<Setter Property="KeyboardNavigation.IsTabStop" Value="False"/>
|
||||
<Setter Property="UIElement.Visibility" TargetName="PART_EditableTextBox" Value="Visible"/>
|
||||
<Setter Property="UIElement.Visibility" TargetName="ContentSite" Value="Collapsed"/>
|
||||
<Setter Property="HorizontalAlignment" TargetName="ToggleButton" Value="Right"/>
|
||||
<Setter Property="HorizontalAlignment" TargetName="ToggleButton" Value="Right"/>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="Validation.HasError" Value="True">
|
||||
<Setter Property="Visibility" TargetName="ErrorAlertHost" Value="Visible"/>
|
||||
<Setter Property="BorderBrush" TargetName="Border" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}"/>
|
||||
<Setter Property="BorderBrush" TargetName="SpotlightLayer" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}"/>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsFocused" SourceName="PART_EditableTextBox" Value="True">
|
||||
<Setter Property="IsValidatedElementFocused" TargetName="ErrorAlertHost" Value="True"/>
|
||||
</Trigger>
|
||||
|
||||
<Trigger Property="IsDropDownOpen" Value="True">
|
||||
<Setter Property="IsValidatedElementFocused" TargetName="ErrorAlertHost" Value="True"/>
|
||||
</Trigger>
|
||||
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="{x:Type Button}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Focusable" Value="False" />
|
||||
</Style>
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture (.png)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem Header="Save Texture" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -55,7 +54,7 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Output Directory *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Directory where log files, backups and other do-not-delete files will be put in." />
|
||||
<TextBox x:Name="ImJackedBro" Grid.Row="0" Grid.Column="2" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<TextBox x:Name="ImJackedBro" Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding OutputDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseOutput" Margin="0 0 0 5" />
|
||||
|
||||
<CheckBox Grid.Row="0" Grid.Column="6" Margin="5 0 0 5" ToolTip="Customize the directory of more output folders"
|
||||
|
|
@ -79,34 +78,24 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Export Raw Data Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="0" Grid.Column="2" Text="{Binding RawDataDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding RawDataDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseRawData" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Save Properties Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding PropertiesDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="1" Grid.Column="2" IsReadOnly="True" Text="{Binding PropertiesDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="1" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseProperties" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Save Texture Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="2" Grid.Column="2" Text="{Binding TextureDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="2" Grid.Column="2" IsReadOnly="True" Text="{Binding TextureDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="2" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseTexture" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Save Audio Directory *" VerticalAlignment="Center" HorizontalAlignment="Right" FontSize="11" FontStyle="Italic" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="3" Grid.Column="2" Text="{Binding AudioDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="3" Grid.Column="2" IsReadOnly="True" Text="{Binding AudioDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="3" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseAudio" Margin="0 0 0 5" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Update Mode" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Receive updates each time a new release is pushed to GitHub Receive updates each time a new commit is pushed to GitHub" />
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.UpdateModes}" SelectedItem="{Binding SettingsView.SelectedUpdateMode, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="3" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Discord Rich Presence" VerticalAlignment="Center" />
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.DiscordRpcs}" SelectedItem="{Binding SettingsView.SelectedDiscordRpc, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -115,39 +104,27 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<Separator Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="GAME"></Separator>
|
||||
<Separator Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="GAME"></Separator>
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="5" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
|
||||
<TextBlock Grid.Row="4" Grid.Column="0" Text="Archive Directory *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="4" Grid.Column="2" Grid.ColumnSpan="3" IsReadOnly="True" Text="{Binding GameDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="4" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseDirectories" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" />
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Margin="0 0 0 5">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ItemsSource" Value="{Binding SettingsView.UeGames}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding SettingsView.UseCustomEGames}" Value="True">
|
||||
<Setter Property="ItemsSource" Value="{Binding SettingsView.CustomUeGames}" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="UE Versions *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the UE version to use when parsing packages" />
|
||||
<controls:FilterableComboBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="5" SelectedItem="{Binding SettingsView.SelectedUeGame, Mode=TwoWay}"
|
||||
ItemsSource="{Binding SettingsView.UeGames}"
|
||||
Style="{StaticResource UComboBox}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CheckBox Grid.Row="6" Grid.Column="6" Margin="5 0 0 5" ToolTip="Enable custom UE versions"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
IsChecked="{Binding SettingsView.UseCustomEGames, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
BorderBrush="White" Style="{StaticResource HighlightedCheckBox}" />
|
||||
</controls:FilterableComboBox>
|
||||
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
|
||||
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
|
||||
<ComboBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.Platforms}" SelectedItem="{Binding SettingsView.SelectedUePlatform, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
|
|
@ -157,8 +134,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
|
||||
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Text="Compressed Audio" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="What to do when encountering a compressed audio file" />
|
||||
<ComboBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.CompressedAudios}" SelectedItem="{Binding SettingsView.SelectedCompressedAudio, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -167,8 +144,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" />
|
||||
<ComboBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="8" Grid.Column="0" Text="Packages Language" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Language used and shown when parsing packages" />
|
||||
<ComboBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="5" ItemsSource="{Binding SettingsView.AssetLanguages}" SelectedItem="{Binding SettingsView.SelectedAssetLanguage, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -177,14 +154,14 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Auto-save packages following their game directory" />
|
||||
<CheckBox Grid.Row="10" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 5 0 5" ToolTip="Auto-save packages following their game directory" />
|
||||
<CheckBox Grid.Row="9" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 0"/>
|
||||
|
||||
<Separator Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
|
||||
<Separator Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="7" Style="{StaticResource CustomSeparator}" Tag="ADVANCED"></Separator>
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Text="Versioning Configuration *" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
|
|
@ -198,10 +175,10 @@
|
|||
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="AES Reload at Launch" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
<ComboBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
ItemsSource="{Binding SettingsView.AesReloads}" SelectedItem="{Binding SettingsView.SelectedAesReload, Mode=TwoWay}"
|
||||
Visibility="{Binding SettingsView.AesEndpoint.IsValid, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
|
|
@ -212,8 +189,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="Endpoint Configuration" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Grid Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="5" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
|
|
@ -225,24 +202,24 @@
|
|||
<Button Grid.Column="2" Content="Mapping" Click="OpenMappingEndpoint" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="15" Grid.Column="2" Margin="0 5 0 10"
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="14" Grid.Column="2" Margin="0 5 0 10"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Grid.Row="16" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
|
||||
<TextBox Grid.Row="15" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<Button Grid.Row="16" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
|
||||
<Button Grid.Row="15" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<TextBlock Grid.Row="17" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="17" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="16" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="16" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
|
@ -342,7 +319,7 @@
|
|||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Model Export Directory *" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
ToolTip="This will be the directory where Meshes, Materials and Animations will be exported" />
|
||||
<TextBox Grid.Row="0" Grid.Column="2" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<TextBox Grid.Row="0" Grid.Column="2" IsReadOnly="True" Text="{Binding ModelDirectory, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0 0 0 5" />
|
||||
<Button Grid.Row="0" Grid.Column="4" Content="..." HorizontalAlignment="Right" Click="OnBrowseModels" Margin="0 0 0 5" />
|
||||
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Mesh Format" VerticalAlignment="Center" Margin="0 0 0 5"/>
|
||||
|
|
|
|||
|
|
@ -51,9 +51,6 @@ public partial class SettingsView
|
|||
case SettingsOut.ReloadMappings:
|
||||
await _applicationView.CUE4Parse.InitMappings();
|
||||
break;
|
||||
case SettingsOut.CheckForUpdates:
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
|
@ -44,9 +45,9 @@ public class Morph : IDisposable
|
|||
Vertices[baseIndex + count++] = vertices[i + 1] + positionDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 2] + positionDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 3] + positionDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 7] + tangentDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 8] + tangentDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 9] + tangentDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 7] + tangentDelta.X;
|
||||
Vertices[baseIndex + count++] = vertices[i + 8] + tangentDelta.Z;
|
||||
Vertices[baseIndex + count++] = vertices[i + 9] + tangentDelta.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -60,6 +61,27 @@ public class Morph : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget)
|
||||
{
|
||||
Name = morphTarget.Name;
|
||||
Vertices = new float[vertices.Length];
|
||||
Array.Copy(vertices, Vertices, vertices.Length);
|
||||
|
||||
foreach (var vert in morphTarget.MorphLODModels[0].Vertices)
|
||||
{
|
||||
var count = 0;
|
||||
if (dict.TryGetValue(vert.SourceIdx, out var baseIndex))
|
||||
{
|
||||
Vertices[baseIndex + count++] += vert.PositionDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] += vert.PositionDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] += vert.PositionDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] += vert.TangentZDelta.X;
|
||||
Vertices[baseIndex + count++] += vert.TangentZDelta.Z;
|
||||
Vertices[baseIndex + count++] += vert.TangentZDelta.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
|
|
@ -74,23 +74,45 @@ public class SkeletalModel : UModel
|
|||
}
|
||||
}
|
||||
|
||||
Morphs = new List<Morph>();
|
||||
for (var i = 0; i < export.MorphTargets.Length; i++)
|
||||
Morphs = [];
|
||||
if (export.MorphTargets.Length == 0) return;
|
||||
|
||||
export.PopulateMorphTargetVerticesData();
|
||||
|
||||
var verticesCount = Vertices.Length / VertexSize;
|
||||
var cachedVertices = new float[verticesCount * Morph.VertexSize];
|
||||
var vertexLookup = new Dictionary<uint, int>(verticesCount);
|
||||
for (int i = 0; i < Vertices.Length; i += VertexSize)
|
||||
{
|
||||
if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) ||
|
||||
morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1)
|
||||
var count = 0;
|
||||
var baseIndex = i / VertexSize * Morph.VertexSize;
|
||||
vertexLookup[(uint) Vertices[i]] = baseIndex;
|
||||
{
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 1];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 2];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 3];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 7];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 8];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 9];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var morph in export.MorphTargets)
|
||||
{
|
||||
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length < 1 ||
|
||||
morphTarget.MorphLODModels[0].Vertices.Length < 1)
|
||||
continue;
|
||||
|
||||
Morphs.Add(new Morph(Vertices, VertexSize, morphTarget));
|
||||
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget));
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletalModel(USkeleton export, FBox box) : base(export)
|
||||
{
|
||||
Indices = Array.Empty<uint>();
|
||||
Materials = Array.Empty<Material>();
|
||||
Vertices = Array.Empty<float>();
|
||||
Sections = Array.Empty<Section>();
|
||||
Indices = [];
|
||||
Materials = [];
|
||||
Vertices = [];
|
||||
Sections = [];
|
||||
AddInstance(Transform.Identity);
|
||||
|
||||
Box = box * Constants.SCALE_DOWN_RATIO;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
|
@ -52,6 +55,59 @@ public class StaticModel : UModel
|
|||
Box = staticMesh.BoundingBox * 1.5f * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
|
||||
public StaticModel(UPaperSprite paperSprite, UTexture2D texture) : base(paperSprite)
|
||||
{
|
||||
Indices = new uint[paperSprite.BakedRenderData.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
Indices[i] = (uint) i;
|
||||
}
|
||||
|
||||
Vertices = new float[paperSprite.BakedRenderData.Length * VertexSize];
|
||||
for (int i = 0; i < paperSprite.BakedRenderData.Length; i++)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i * VertexSize;
|
||||
var vert = paperSprite.BakedRenderData[i];
|
||||
var u = vert.Z;
|
||||
var v = vert.W;
|
||||
|
||||
Vertices[baseIndex + count++] = i;
|
||||
Vertices[baseIndex + count++] = vert.X * paperSprite.PixelsPerUnrealUnit * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vert.Y * paperSprite.PixelsPerUnrealUnit * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = u;
|
||||
Vertices[baseIndex + count++] = v;
|
||||
Vertices[baseIndex + count++] = .5f;
|
||||
}
|
||||
|
||||
Materials = new Material[1];
|
||||
if (paperSprite.DefaultMaterial?.TryLoad(out UMaterialInstance unrealMaterial) ?? false)
|
||||
{
|
||||
Materials[0] = new Material(unrealMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
Materials[0] = new Material();
|
||||
}
|
||||
Materials[0].Parameters.Textures[CMaterialParams2.FallbackDiffuse] = texture;
|
||||
Materials[0].IsUsed = true;
|
||||
|
||||
Sections = new Section[1];
|
||||
Sections[0] = new Section(0, Indices.Length, 0);
|
||||
|
||||
AddInstance(Transform.Identity);
|
||||
|
||||
var backward = new FVector(0, Math.Max(paperSprite.BakedSourceDimension.X, paperSprite.BakedSourceDimension.Y) / 2, 0);
|
||||
Box = new FBox(-backward, backward) * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
|
||||
public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null)
|
||||
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -103,6 +103,9 @@ public class Renderer : IDisposable
|
|||
LoadJunoWorld(cancellationToken, bp, Transform.Identity);
|
||||
Color = VertexColor.Colors;
|
||||
break;
|
||||
case UPaperSprite ps:
|
||||
LoadPaperSprite(ps);
|
||||
break;
|
||||
}
|
||||
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
|
||||
SetupCamera();
|
||||
|
|
@ -397,6 +400,23 @@ public class Renderer : IDisposable
|
|||
Options.SelectModel(guid);
|
||||
}
|
||||
|
||||
private void LoadPaperSprite(UPaperSprite original)
|
||||
{
|
||||
if (!(original.BakedSourceTexture?.TryLoad(out UTexture2D texture) ?? false))
|
||||
return;
|
||||
|
||||
var guid = texture.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.AddInstance(Transform.Identity);
|
||||
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
|
||||
return;
|
||||
}
|
||||
|
||||
Options.Models[guid] = new StaticModel(original, texture);
|
||||
Options.SelectModel(guid);
|
||||
}
|
||||
|
||||
private void SetupCamera()
|
||||
{
|
||||
if (Options.TryGetModel(out var model))
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ public class Material : IDisposable
|
|||
}
|
||||
|
||||
{ // ambient occlusion + color boost
|
||||
if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO", "Mask") &&
|
||||
if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") &&
|
||||
!original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
|
||||
{
|
||||
HasAo = true;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,19 @@ public class Texture : IDisposable
|
|||
Height = bitmap.Height;
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
GL.TexImage2D(_target, 0, texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, bitmap.Bytes);
|
||||
var internalFormat = Format switch
|
||||
{
|
||||
EPixelFormat.PF_G8 => PixelInternalFormat.R8,
|
||||
_ => texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb
|
||||
};
|
||||
|
||||
var pixelFormat = Format switch
|
||||
{
|
||||
EPixelFormat.PF_G8 => PixelFormat.Red,
|
||||
_ => PixelFormat.Rgba
|
||||
};
|
||||
|
||||
GL.TexImage2D(_target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, bitmap.Bytes);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ public static class TextureHelper
|
|||
case "COSMICSHAKE":
|
||||
case "PHOENIX":
|
||||
case "ATOMICHEART":
|
||||
case "MULTIVERSUS":
|
||||
case "BODYCAM":
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
|
|
|
|||
|
|
@ -1013,9 +1013,9 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
style.Colors[(int) ImGuiCol.ResizeGripActive] = new Vector4(0.12f, 0.41f, 0.81f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.Tab] = new Vector4(0.15f, 0.15f, 0.19f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.TabHovered] = new Vector4(0.35f, 0.35f, 0.41f, 0.80f);
|
||||
style.Colors[(int) ImGuiCol.TabActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.TabUnfocused] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.TabUnfocusedActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.TabSelected] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.TabDimmed] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.TabDimmedSelected] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.DockingPreview] = new Vector4(0.26f, 0.59f, 0.98f, 0.70f);
|
||||
style.Colors[(int) ImGuiCol.DockingEmptyBg] = new Vector4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
style.Colors[(int) ImGuiCol.PlotLines] = new Vector4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
|
|
|
|||
114
FModel/Views/UpdateView.xaml
Normal file
114
FModel/Views/UpdateView.xaml
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<adonisControls:AdonisWindow x:Class="FModel.Views.UpdateView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:FModel"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
WindowStartupLocation="CenterScreen" IconVisibility="Collapsed" ResizeMode="CanMinimize" Loaded="OnLoaded"
|
||||
MinHeight="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.40'}"
|
||||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.35'}">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="Releases" />
|
||||
</Style>
|
||||
</adonisControls:AdonisWindow.Style>
|
||||
<adonisControls:AdonisWindow.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Resources/Resources.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</adonisControls:AdonisWindow.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" TextWrapping="Wrap" HorizontalAlignment="Center" TextAlignment="Center" Margin="10">
|
||||
All releases listed below are available for download. They are sorted by date, with the latest release at the top.
|
||||
We regularly remove old ones to keep the list clean and up to date with the latest UE releases.
|
||||
If you wish to manually check for updates, this window is accessible via the Help > Releases menu.
|
||||
</TextBlock>
|
||||
|
||||
<Grid Grid.Row="1" HorizontalAlignment="Center">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<!-- <Grid.Style> -->
|
||||
<!-- <Style TargetType="Grid"> -->
|
||||
<!-- <Setter Property="Visibility" Value="Visible" /> -->
|
||||
<!-- <Style.Triggers> -->
|
||||
<!-- <DataTrigger Binding="{Binding Title, RelativeSource={RelativeSource AncestorType=adonisControls:AdonisWindow}}" Value="Releases"> -->
|
||||
<!-- <Setter Property="Visibility" Value="Collapsed" /> -->
|
||||
<!-- </DataTrigger> -->
|
||||
<!-- </Style.Triggers> -->
|
||||
<!-- </Style> -->
|
||||
<!-- </Grid.Style> -->
|
||||
|
||||
<Button Grid.Column="0" Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}"
|
||||
VerticalAlignment="Top" Height="{Binding ActualHeight, ElementName=RemindButton}"
|
||||
Click="OnDownloadLatest">
|
||||
Download Latest Release
|
||||
</Button>
|
||||
<StackPanel Grid.Column="2">
|
||||
<adonisControls:SplitButton x:Name="RemindButton" Content="Remind Me Now ..." Command="{Binding RemindMeCommand}">
|
||||
<adonisControls:SplitButton.SplitMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="In 3 Days" Command="{Binding RemindMeCommand}" CommandParameter="Days" />
|
||||
<MenuItem Header="Next Week" Command="{Binding RemindMeCommand}" CommandParameter="Week" />
|
||||
<MenuItem Header="Next Month" Command="{Binding RemindMeCommand}" CommandParameter="Month" />
|
||||
<MenuItem Header="Never" Command="{Binding RemindMeCommand}" CommandParameter="Never" />
|
||||
</ContextMenu>
|
||||
</adonisControls:SplitButton.SplitMenu>
|
||||
</adonisControls:SplitButton>
|
||||
<TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" FontSize="10" Margin="0 2.5 0 0"
|
||||
Text="{Binding NextUpdateCheck, Source={x:Static local:Settings.UserSettings.Default}, StringFormat=Next Refresh: {0:MMM d, yyyy}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Separator Grid.Row="2" Style="{StaticResource CustomSeparator}" Tag="History" />
|
||||
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding CommitsView}">
|
||||
<ItemsControl.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.ContainerStyle>
|
||||
<Style TargetType="GroupItem">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="GroupItem">
|
||||
<GroupBox adonisExtensions:LayerExtension.Layer="3"
|
||||
Header="{Binding Name}"
|
||||
HeaderStringFormat="Commits on {0:MMM d, yyyy}"
|
||||
Margin="0 0 0 5">
|
||||
<ItemsPresenter />
|
||||
</GroupBox>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</GroupStyle.ContainerStyle>
|
||||
<GroupStyle.Panel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical" />
|
||||
</ItemsPanelTemplate>
|
||||
</GroupStyle.Panel>
|
||||
</GroupStyle>
|
||||
</ItemsControl.GroupStyle>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<controls:CommitControl Margin="0 0 0 1" />
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
||||
27
FModel/Views/UpdateView.xaml.cs
Normal file
27
FModel/Views/UpdateView.xaml.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System.Windows;
|
||||
using FModel.ViewModels;
|
||||
using FModel.Views.Resources.Controls;
|
||||
|
||||
namespace FModel.Views;
|
||||
|
||||
public partial class UpdateView
|
||||
{
|
||||
public UpdateView()
|
||||
{
|
||||
DataContext = new UpdateViewModel();
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not UpdateViewModel viewModel) return;
|
||||
await viewModel.Load();
|
||||
}
|
||||
|
||||
private void OnDownloadLatest(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is not UpdateViewModel viewModel) return;
|
||||
viewModel.DownloadLatest();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user