diff --git a/CUE4Parse b/CUE4Parse index 2cdf9d61..d816fe61 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 2cdf9d61e66253f746f6a6e5b23b57979fbaab69 +Subproject commit d816fe61ac8e5798d1584ea2f9871acfca0ca429 diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index f5de48d8..8491c467 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -41,6 +41,8 @@ public partial class App { UserSettings.Default = JsonConvert.DeserializeObject( File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings); + + /*if (UserSettings.Default.ShowChangelog) */MigrateV1Games(); } catch { @@ -141,6 +143,17 @@ public partial class App e.Handled = true; } + private void MigrateV1Games() + { + foreach ((var gameDir, var setting) in UserSettings.Default.ManualGames) + { + if (!Directory.Exists(gameDir)) continue; + UserSettings.Default.PerDirectory[gameDir] = + DirectorySettings.Default(setting.GameName, setting.GameDirectory, true, setting.OverridedGame, setting.AesKeys?.MainKey); + } + UserSettings.Default.ManualGames.Clear(); + } + private string GetOperatingSystemProductName() { var productName = string.Empty; diff --git a/FModel/Constants.cs b/FModel/Constants.cs index 2f53fc8a..a0bf4597 100644 --- a/FModel/Constants.cs +++ b/FModel/Constants.cs @@ -20,7 +20,7 @@ public static class Constants public const string YELLOW = "#E5C07B"; public const string BLUE = "#528BCC"; - public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new/choose"; + public const string ISSUE_LINK = "https://github.com/4sval/FModel/discussions/categories/q-a"; public const string DONATE_LINK = "https://fmodel.app/donate"; public const string DISCORD_LINK = "https://fmodel.app/discord"; diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs index 4ee9a578..e0fbc220 100644 --- a/FModel/Creator/Bases/FN/BaseIconStats.cs +++ b/FModel/Creator/Bases/FN/BaseIconStats.cs @@ -52,7 +52,7 @@ public class BaseIconStats : BaseIcon foreach (var poi in challengeMapPoiData) { if (!poi.TryGetValue(out FStructFallback locationTag, "LocationTag") || !locationTag.TryGetValue(out FName tagName, "TagName") || - !tagName.Text.Equals(location.Text, StringComparison.OrdinalIgnoreCase) || !poi.TryGetValue(out FText text, "Text")) continue; + tagName != location.TagName || !poi.TryGetValue(out FText text, "Text")) continue; locationName = text.Text; break; @@ -92,7 +92,11 @@ public class BaseIconStats : BaseIcon _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "35D04D1B45737BEA25B69686D9E085B9", "Damage"), dmgPb * multiplier, 200)); } - if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical")) + if (weaponRowValue.TryGetValue(out float mdpc, "MaxDamagePerCartridge") && mdpc >= 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), mdpc, 200)); + } + else if (weaponRowValue.TryGetValue(out float dmgCritical, "DamageZone_Critical")) { _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "0DEF2455463B008C4499FEA03D149EDF", "Headshot Damage"), dmgPb * dmgCritical * multiplier, 200)); } diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index 7d44f685..1ff20af0 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -160,9 +160,9 @@ public class CreatorPackage : IDisposable case "FortChallengeBundleItemDefinition": creator = new BaseBundle(_object, _style); return true; - case "AthenaSeasonItemDefinition": - creator = new BaseSeason(_object, _style); - return true; + // case "AthenaSeasonItemDefinition": + // creator = new BaseSeason(_object, _style); + // return true; case "FortItemAccessTokenType": creator = new BaseItemAccessToken(_object, _style); return true; @@ -203,69 +203,6 @@ public class CreatorPackage : IDisposable case "QuestData": creator = new Bases.MV.BaseQuest(_object, _style); return true; - // Battle Breakers - case "WExpGenericAccountItemDefinition": - case "WExpGearAccountItemDefinition": - case "WExpHQWorkerLodgesDefinition": - case "WExpPersonalEventDefinition": - case "WExpUpgradePotionDefinition": - case "WExpAccountRewardDefinition": - case "WExpHQBlacksmithDefinition": - case "WExpHQSecretShopDefinition": - case "WExpHQMonsterPitDefinition": - case "WExpHQHeroTowerDefinition": - case "WExpVoucherItemDefinition": - case "WExpTreasureMapDefinition": - case "WExpHammerChestDefinition": - case "WExpHQWorkshopDefinition": - case "WExpUnlockableDefinition": - case "WExpHQSmelterDefinition": - case "WExpContainerDefinition": - case "WExpCharacterDefinition": - case "WExpHQMarketDefinition": - case "WExpGiftboxDefinition": - case "WExpStandInDefinition": - case "WExpRegionDefinition": - case "WExpHQMineDefinition": - case "WExpXpBookDefinition": - case "WExpTokenDefinition": - case "WExpItemDefinition": - case "WExpZoneDefinition": - creator = new BaseBreakersIcon(_object, EIconStyle.Default); - return true; - // Spellbreak - case "GTargetedTeleportActiveSkill": - case "GChronomasterV2ActiveSkill": - case "GShadowstepActiveSkill": - case "GGatewayActiveSkill": - case "GStealthActiveSkill": - case "GFeatherActiveSkill": - case "GCosmeticDropTrail": - case "GFlightActiveSkill": - case "GCosmeticRunTrail": - case "GCosmeticArtifact": - case "GCosmeticTriumph": - case "GWolfsbloodSkill": - case "GDashActiveSkill": - case "GCharacterPerk": - case "GCosmeticTitle": - case "GCosmeticBadge": - case "GRMTStoreOffer": - case "GCosmeticEmote": - case "GCosmeticCard": - case "GCosmeticSkin": - case "GStoreOffer": - case "GAccolade": - case "GRuneItem": - case "GQuest": - creator = new BaseSpellIcon(_object, EIconStyle.Default); - return true; - case "GLeagueTier": - creator = new BaseLeague(_object, EIconStyle.Default); - return true; - case "GLeagueDivision": - creator = new BaseDivision(_object, EIconStyle.Default); - return true; default: creator = null; return false; diff --git a/FModel/Creator/Typefaces.cs b/FModel/Creator/Typefaces.cs index 38a9a90f..e8336690 100644 --- a/FModel/Creator/Typefaces.cs +++ b/FModel/Creator/Typefaces.cs @@ -21,7 +21,6 @@ public class Typefaces private const string _BURBANK_BIG_REGULAR_BOLD = "BurbankBigRegular-Bold"; // official fortnite ig private const string _BURBANK_SMALL_MEDIUM = "BurbankSmall-Medium"; private const string _DROID_SANS_FORTNITE_SUBSET = "DroidSans-Fortnite-Subset"; - private const string _NIS_JYAU = "NIS_JYAU"; // japanese fortnite private const string _NOTO_COLOR_EMOJI = "NotoColorEmoji"; private const string _NOTO_SANS_BOLD = "NotoSans-Bold"; private const string _NOTO_SANS_FORTNITE_BOLD = "NotoSans-Fortnite-Bold"; @@ -32,7 +31,7 @@ public class Typefaces private const string _NOTO_SANS_ARABIC_BLACK = "NotoSansArabic-Black"; // arabic fortnite private const string _NOTO_SANS_ARABIC_BOLD = "NotoSansArabic-Bold"; private const string _NOTO_SANS_ARABIC_REGULAR = "NotoSansArabic-Regular"; - private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; + private const string _NOTO_SANS_JP_BOLD = "NotoSansJP-Bold"; // japanese fortnite private const string _NOTO_SANS_KR_REGULAR = "NotoSansKR-Regular"; private const string _NOTO_SANS_SC_BLACK = "NotoSansSC-Black"; // simplified chinese fortnite private const string _NOTO_SANS_SC_REGULAR = "NotoSansSC-Regular"; @@ -49,23 +48,6 @@ public class Typefaces private const string _XIANGHEHEI_SC_PRO_BLACK = "XiangHeHei_SC/MXiangHeHeiSCPro-Black"; private const string _XIANGHEHEI_SC_PRO_HEAVY = "XiangHeHei_SC/MXiangHeHeiSCPro-Heavy"; - // Spellbreak - private const string _SPELLBREAK_BASE_PATH = "/Game/UI/Fonts/"; - private const string _MONTSERRAT_SEMIBOLD = "Montserrat-Semibold"; - private const string _MONTSERRAT_SEMIBOLD_ITALIC = "Montserrat-SemiBoldItalic"; - private const string _NANUM_GOTHIC = "NanumGothic"; - private const string _QUADRAT_BOLD = "Quadrat_Bold"; - private const string _SEGOE_BOLD_ITALIC = "Segoe_Bold_Italic"; - - // WorldExplorers - private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/"; - private const string _HEMIHEAD426 = "HemiHead426"; - private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular"; - private const string _LATO_BLACK = "Lato-Black"; - private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic"; - private const string _LATO_LIGHT = "Lato-Light"; - private const string _LATO_MEDIUM = "Lato-Medium"; - private readonly CUE4ParseViewModel _viewModel; public readonly SKTypeface Default; // used as a fallback font for all untranslated strings (item source, ...) @@ -85,16 +67,16 @@ public class Typefaces Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream); - switch (viewModel.Game) + switch (viewModel.Provider.InternalGameName.ToUpperInvariant()) { - case FGame.FortniteGame: + case "FORTNITEGAME": { DisplayName = OnTheFly(_FORTNITE_BASE_PATH + language switch { ELanguage.Korean => _ASIA_ERINM, ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, - ELanguage.Japanese => _NIS_JYAU, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK, @@ -130,7 +112,7 @@ public class Typefaces { ELanguage.Korean => _ASIA_ERINM, ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, - ELanguage.Japanese => _NIS_JYAU, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK, @@ -142,7 +124,7 @@ public class Typefaces { ELanguage.Korean => _ASIA_ERINM, ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, - ELanguage.Japanese => _NIS_JYAU, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK, @@ -153,7 +135,7 @@ public class Typefaces language switch { ELanguage.Korean => _ASIA_ERINM, - ELanguage.Japanese => _NIS_JYAU, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK, @@ -164,7 +146,7 @@ public class Typefaces language switch { ELanguage.Korean => _ASIA_ERINM, - ELanguage.Japanese => _NIS_JYAU, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, ELanguage.Chinese => _NOTO_SANS_SC_BLACK, @@ -172,36 +154,7 @@ public class Typefaces } + _EXT); break; } - case FGame.WorldExplorers: - { - DisplayName = OnTheFly(_BATTLE_BREAKERS_BASE_PATH + - language switch - { - ELanguage.Korean => _NOTO_SANS_KR_REGULAR, - ELanguage.Russian => _LATO_BLACK, - ELanguage.Japanese => _NOTO_SANS_JP_REGULAR, - ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, - _ => _HEMIHEAD426 - } + _EXT); - - Description = OnTheFly(_BATTLE_BREAKERS_BASE_PATH + - language switch - { - ELanguage.Korean => _NOTO_SANS_KR_REGULAR, - ELanguage.Russian => _LATO_BLACK, - ELanguage.Japanese => _NOTO_SANS_JP_REGULAR, - ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, - _ => _HEMIHEAD426 - } + _EXT); - break; - } - case FGame.g3: - { - DisplayName = OnTheFly(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD + _EXT); - Description = OnTheFly(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT); - break; - } - case FGame.MultiVersus: + case "MULTIVERSUS": { DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch { diff --git a/FModel/Creator/Utils.cs b/FModel/Creator/Utils.cs index 410b3ac9..57bc1213 100644 --- a/FModel/Creator/Utils.cs +++ b/FModel/Creator/Utils.cs @@ -112,7 +112,7 @@ public static class Utils public static SKBitmap GetB64Bitmap(string b64) => SKBitmap.Decode(new MemoryStream(Convert.FromBase64String(b64)) { Position = 0 }); public static SKBitmap GetBitmap(FSoftObjectPath softObjectPath) => GetBitmap(softObjectPath.AssetPathName.Text); public static SKBitmap GetBitmap(string fullPath) => TryLoadObject(fullPath, out UTexture2D texture) ? GetBitmap(texture) : null; - public static SKBitmap GetBitmap(UTexture2D texture) => texture.IsVirtual ? null : texture.Decode(UserSettings.Default.OverridedPlatform); + public static SKBitmap GetBitmap(UTexture2D texture) => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform); public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data); public static SKBitmap ResizeWithRatio(this SKBitmap me, double width, double height) diff --git a/FModel/Enums.cs b/FModel/Enums.cs index eaa7cd24..77c81c0e 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -52,56 +52,6 @@ public enum EDiscordRpc Never } -public enum FGame -{ - [Description("Unknown")] - Unknown, - [Description("Fortnite")] - FortniteGame, - [Description("Valorant")] - ShooterGame, - [Description("Dead By Daylight")] - DeadByDaylight, - [Description("Borderlands 3")] - OakGame, - [Description("Minecraft Dungeons")] - Dungeons, - [Description("Battle Breakers")] - WorldExplorers, - [Description("Spellbreak")] - g3, - [Description("State Of Decay 2")] - StateOfDecay2, - [Description("The Cycle")] - Prospect, - [Description("The Outer Worlds")] - Indiana, - [Description("Rogue Company")] - RogueCompany, - [Description("Star Wars: Jedi Fallen Order")] - SwGame, - [Description("Core")] - Platform, - [Description("Days Gone")] - BendGame, - [Description("PLAYERUNKNOWN'S BATTLEGROUNDS")] - TslGame, - [Description("Splitgate")] - PortalWars, - [Description("GTA: The Trilogy - Definitive Edition")] - Gameface, - [Description("Sea of Thieves")] - Athena, - [Description("DEPRECATED")] - PandaGame, - [Description("MultiVersus")] - MultiVersus, - [Description("Tower of Fantasy")] - Hotta, - [Description("eFootball 2023")] - eFootball -} - public enum ELoadingMode { [Description("Single")] diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index ded384a9..e8971df2 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -5,9 +5,9 @@ net6.0-windows true FModel.ico - 4.4.3.1 - 4.4.3.1 - 4.4.3.1 + 4.4.3.4 + 4.4.3.4 + 4.4.3.4 false true win-x64 @@ -77,6 +77,7 @@ + @@ -107,6 +108,8 @@ + + @@ -130,24 +133,26 @@ + + - + - - + + - + - + @@ -203,6 +208,7 @@ + diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index 3db666be..b9952ffd 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -94,7 +94,7 @@ - + @@ -126,25 +126,6 @@ - - - - - - - - - - - - @@ -396,7 +377,7 @@ - + diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 41850e26..449f04a0 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -40,7 +40,6 @@ public partial class MainWindow private void OnClosing(object sender, CancelEventArgs e) { - _applicationView.CustomDirectories.Save(); _discordHandler.Dispose(); } @@ -56,8 +55,8 @@ public partial class MainWindow case EAesReload.Always: await _applicationView.CUE4Parse.RefreshAes(); break; - case EAesReload.OncePerDay when UserSettings.Default.LastAesReload != DateTime.Today: - UserSettings.Default.LastAesReload = DateTime.Today; + case EAesReload.OncePerDay when UserSettings.Default.CurrentDir.LastAesReload != DateTime.Today: + UserSettings.Default.CurrentDir.LastAesReload = DateTime.Today; await _applicationView.CUE4Parse.RefreshAes(); break; } @@ -84,9 +83,12 @@ public partial class MainWindow ).ConfigureAwait(false); #if DEBUG - // await _threadWorkerView.Begin(cancellationToken => - // _applicationView.CUE4Parse.Extract(cancellationToken, - // "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_MED_Ballerina/Meshes/F_MED_Ballerina.uasset")); + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.Extract(cancellationToken, + "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_Med_Soldier_01/Meshes/F_Med_Soldier_01.uasset")); + await _threadWorkerView.Begin(cancellationToken => + _applicationView.CUE4Parse.Extract(cancellationToken, + "fortnitegame/Content/Animation/Game/MainPlayer/Emotes/Cowbell/Cowbell_CMM_Loop_M.uasset")); #endif } @@ -140,7 +142,7 @@ public partial class MainWindow private async void OnMappingsReload(object sender, ExecutedRoutedEventArgs e) { - await _applicationView.CUE4Parse.InitMappings(); + await _applicationView.CUE4Parse.InitMappings(true); } private void OnOpenAvalonFinder() @@ -228,13 +230,13 @@ public partial class MainWindow } } - private void OnSaveDirectoryClick(object sender, RoutedEventArgs e) + private void OnFavoriteDirectoryClick(object sender, RoutedEventArgs e) { if (AssetsFolderName.SelectedItem is not TreeItem folder) return; _applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint)); FLogger.Append(ELog.Information, () => - FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true)); + FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new favorite directory", Constants.WHITE, true)); } private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e) diff --git a/FModel/Resources/bone.frag b/FModel/Resources/bone.frag new file mode 100644 index 00000000..cdfebf9d --- /dev/null +++ b/FModel/Resources/bone.frag @@ -0,0 +1,11 @@ +#version 460 core + +in vec3 fPos; +in vec3 fColor; + +out vec4 FragColor; + +void main() +{ + FragColor = vec4(fColor, 1.0); +} diff --git a/FModel/Resources/bone.vert b/FModel/Resources/bone.vert new file mode 100644 index 00000000..a1eda419 --- /dev/null +++ b/FModel/Resources/bone.vert @@ -0,0 +1,19 @@ +#version 460 core + +layout (location = 0) in vec3 vPos; +layout (location = 1) in vec3 vColor; + +uniform mat4 uView; +uniform mat4 uProjection; +uniform mat4 uInstanceMatrix; + +out vec3 fPos; +out vec3 fColor; + +void main() +{ + gl_PointSize = 7.5f; + gl_Position = uProjection * uView * uInstanceMatrix * vec4(vPos, 1.0); + fPos = vec3(uInstanceMatrix * vec4(vPos, 1.0)); + fColor = vColor; +} diff --git a/FModel/Resources/checker.png b/FModel/Resources/checker.png new file mode 100644 index 00000000..8e1daf31 Binary files /dev/null and b/FModel/Resources/checker.png differ diff --git a/FModel/Resources/default.frag b/FModel/Resources/default.frag index 7e6aac04..29b9f84d 100644 --- a/FModel/Resources/default.frag +++ b/FModel/Resources/default.frag @@ -228,7 +228,7 @@ void main() } else if (bVertexColors[4]) { - FragColor = vec4(fTexCoords, 0.0, 1.0); + FragColor = SamplerToVector(uParameters.Diffuse[0].Sampler); } else { diff --git a/FModel/Resources/default.vert b/FModel/Resources/default.vert index 1cae7fda..3d530a5b 100644 --- a/FModel/Resources/default.vert +++ b/FModel/Resources/default.vert @@ -16,10 +16,15 @@ layout(std430, binding = 1) buffer BoneMatrices { mat4 uFinalBonesMatrix[]; }; +layout(std430, binding = 2) buffer RestBoneMatrices +{ + mat4 uRestBonesMatrix[]; +}; uniform mat4 uView; uniform mat4 uProjection; uniform float uMorphTime; +uniform bool uIsAnimated; out vec3 fPos; out vec3 fNormal; @@ -37,14 +42,14 @@ void main() vec4 finalPos = vec4(0.0); vec4 finalNormal = vec4(0.0); vec4 finalTangent = vec4(0.0); - if (vBoneIds != vBoneWeights) + if (uIsAnimated) { for(int i = 0 ; i < 4; i++) { int boneIndex = int(vBoneIds[i]); if(boneIndex < 0) break; - mat4 boneMatrix = uFinalBonesMatrix[boneIndex]; + mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]); mat4 inverseBoneMatrix = transpose(inverse(boneMatrix)); float weight = vBoneWeights[i]; diff --git a/FModel/Resources/outline.vert b/FModel/Resources/outline.vert index 56de336d..ffb85c6c 100644 --- a/FModel/Resources/outline.vert +++ b/FModel/Resources/outline.vert @@ -11,11 +11,16 @@ layout(std430, binding = 1) buffer BoneMatrices { mat4 uFinalBonesMatrix[]; }; +layout(std430, binding = 2) buffer RestBoneMatrices +{ + mat4 uRestBonesMatrix[]; +}; uniform mat4 uView; uniform vec3 uViewPos; uniform mat4 uProjection; uniform float uMorphTime; +uniform bool uIsAnimated; void main() { @@ -24,14 +29,14 @@ void main() vec4 finalPos = vec4(0.0); vec4 finalNormal = vec4(0.0); - if (vBoneIds != vBoneWeights) + if (uIsAnimated) { for(int i = 0 ; i < 4; i++) { int boneIndex = int(vBoneIds[i]); if(boneIndex < 0) break; - mat4 boneMatrix = uFinalBonesMatrix[boneIndex]; + mat4 boneMatrix = uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]); float weight = vBoneWeights[i]; finalPos += boneMatrix * bindPos * weight; diff --git a/FModel/Resources/picking.vert b/FModel/Resources/picking.vert index 9f354e9f..7d267c0c 100644 --- a/FModel/Resources/picking.vert +++ b/FModel/Resources/picking.vert @@ -10,24 +10,29 @@ layout(std430, binding = 1) buffer BoneMatrices { mat4 uFinalBonesMatrix[]; }; +layout(std430, binding = 2) buffer RestBoneMatrices +{ + mat4 uRestBonesMatrix[]; +}; uniform mat4 uView; uniform mat4 uProjection; uniform float uMorphTime; +uniform bool uIsAnimated; void main() { vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0); vec4 finalPos = vec4(0.0); - if (vBoneIds != vBoneWeights) + if (uIsAnimated) { for(int i = 0 ; i < 4; i++) { int boneIndex = int(vBoneIds[i]); if(boneIndex < 0) break; - finalPos += uFinalBonesMatrix[boneIndex] * bindPos * vBoneWeights[i]; + finalPos += uFinalBonesMatrix[boneIndex] * inverse(uRestBonesMatrix[boneIndex]) * bindPos * vBoneWeights[i]; } } else finalPos = bindPos; diff --git a/FModel/Services/DiscordService.cs b/FModel/Services/DiscordService.cs index 6735e42e..c5b36d28 100644 --- a/FModel/Services/DiscordService.cs +++ b/FModel/Services/DiscordService.cs @@ -41,14 +41,14 @@ namespace FModel.Services Details = $"{gameName} - Idling" }; - _client.OnReady += (_, args) => Log.Information("{Username}#{Discriminator} ({UserId}) is now ready", args.User.Username, args.User.Discriminator, args.User.ID); + _client.OnReady += (_, args) => Log.Information("@{Username} ({UserId}) is now ready", args.User.Username, args.User.ID); _client.SetPresence(_currentPresence); _client.Initialize(); } public void UpdatePresence(CUE4ParseViewModel viewModel) => UpdatePresence( - $"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.GameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages", + $"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.InternalGameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages", $"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim()); public void UpdatePresence(string details, string state) diff --git a/FModel/Settings/CustomDirectory.cs b/FModel/Settings/CustomDirectory.cs new file mode 100644 index 00000000..471cb93f --- /dev/null +++ b/FModel/Settings/CustomDirectory.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using FModel.Framework; + +namespace FModel.Settings; + +public class CustomDirectory : ViewModel +{ + public static IList Default(string gameName) + { + switch (gameName) + { + case "Fortnite": + case "Fortnite [LIVE]": + return new List + { + new("Cosmetics", "FortniteGame/Content/Athena/Items/Cosmetics/"), + new("Emotes [AUDIO]", "FortniteGame/Content/Athena/Sounds/Emotes/"), + new("Music Packs [AUDIO]", "FortniteGame/Content/Athena/Sounds/MusicPacks/"), + new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"), + new("Strings", "FortniteGame/Content/Localization/") + }; + case "VALORANT": + case "VALORANT [LIVE]": + return new List + { + new("Audio", "ShooterGame/Content/WwiseAudio/Media/"), + new("Characters", "ShooterGame/Content/Characters/"), + new("Gun Buddies", "ShooterGame/Content/Equippables/Buddies/"), + new("Cards and Sprays", "ShooterGame/Content/Personalization/"), + new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"), + new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/") + }; + default: + return new List(); + } + } + + private string _header; + public string Header + { + get => _header; + set => SetProperty(ref _header, value); + } + + private string _directoryPath; + public string DirectoryPath + { + get => _directoryPath; + set => SetProperty(ref _directoryPath, value); + } + + public CustomDirectory() + { + Header = string.Empty; + DirectoryPath = string.Empty; + } + + public CustomDirectory(string header, string path) + { + Header = header; + DirectoryPath = path; + } + + public override string ToString() => Header; +} diff --git a/FModel/Settings/DirectorySettings.cs b/FModel/Settings/DirectorySettings.cs new file mode 100644 index 00000000..d96a735b --- /dev/null +++ b/FModel/Settings/DirectorySettings.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Versions; +using FModel.Framework; +using FModel.ViewModels.ApiEndpoints.Models; + +namespace FModel.Settings; + +public class DirectorySettings : ViewModel, ICloneable +{ + public static DirectorySettings Default( + string gameName, string gameDir, bool manual = false, EGame ue = EGame.GAME_UE4_LATEST, string aes = "") + { + UserSettings.Default.PerDirectory.TryGetValue(gameDir, out var old); + return new DirectorySettings + { + GameName = gameName, + GameDirectory = gameDir, + IsManual = manual, + UeVersion = old?.UeVersion ?? ue, + TexturePlatform = old?.TexturePlatform ?? ETexturePlatform.DesktopMobile, + Versioning = old?.Versioning ?? new VersioningSettings(), + Endpoints = old?.Endpoints ?? EndpointSettings.Default(gameName), + Directories = old?.Directories ?? CustomDirectory.Default(gameName), + AesKeys = old?.AesKeys ?? new AesResponse { MainKey = aes, DynamicKeys = null }, + LastAesReload = old?.LastAesReload ?? DateTime.Today.AddDays(-1) + }; + } + + private string _gameName; + public string GameName + { + get => _gameName; + set => SetProperty(ref _gameName, value); + } + + private string _gameDirectory; + public string GameDirectory + { + get => _gameDirectory; + set => SetProperty(ref _gameDirectory, value); + } + + private bool _isManual; + public bool IsManual + { + get => _isManual; + set => SetProperty(ref _isManual, value); + } + + private EGame _ueVersion; + public EGame UeVersion + { + get => _ueVersion; + set => SetProperty(ref _ueVersion, value); + } + + private ETexturePlatform _texturePlatform; + public ETexturePlatform TexturePlatform + { + get => _texturePlatform; + set => SetProperty(ref _texturePlatform, value); + } + + private VersioningSettings _versioning; + public VersioningSettings Versioning + { + get => _versioning; + set => SetProperty(ref _versioning, value); + } + + private EndpointSettings[] _endpoints; + public EndpointSettings[] Endpoints + { + get => _endpoints; + set => SetProperty(ref _endpoints, value); + } + + private IList _directories; + public IList Directories + { + get => _directories; + set => SetProperty(ref _directories, value); + } + + private AesResponse _aesKeys; + public AesResponse AesKeys + { + get => _aesKeys; + set => SetProperty(ref _aesKeys, value); + } + + private DateTime _lastAesReload; + public DateTime LastAesReload + { + get => _lastAesReload; + set => SetProperty(ref _lastAesReload, value); + } + + private bool Equals(DirectorySettings other) + { + return GameDirectory == other.GameDirectory && UeVersion == other.UeVersion; + } + + public override bool Equals(object obj) + { + return obj is DirectorySettings other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(GameDirectory, (int) UeVersion); + } + + public object Clone() + { + return this.MemberwiseClone(); + } +} diff --git a/FModel/Framework/FEndpoint.cs b/FModel/Settings/EndpointSettings.cs similarity index 73% rename from FModel/Framework/FEndpoint.cs rename to FModel/Settings/EndpointSettings.cs index cd80f109..9f0bd641 100644 --- a/FModel/Framework/FEndpoint.cs +++ b/FModel/Settings/EndpointSettings.cs @@ -1,12 +1,29 @@ using System.Linq; +using FModel.Framework; using FModel.ViewModels.ApiEndpoints; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace FModel.Framework; +namespace FModel.Settings; -public class FEndpoint : ViewModel +public class EndpointSettings : ViewModel { + public static EndpointSettings[] Default(string gameName) + { + switch (gameName) + { + case "Fortnite": + case "Fortnite [LIVE]": + return new EndpointSettings[] + { + new("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"), + new("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") + }; + default: + return new EndpointSettings[] { new(), new() }; + } + } + private string _url; public string Url { @@ -51,8 +68,8 @@ public class FEndpoint : ViewModel "Your endpoint configuration is valid! Please, avoid any unnecessary modifications!" : "Your endpoint configuration DOES NOT seem to be valid yet! Please, test it out!"; - public FEndpoint() {} - public FEndpoint(string url, string path) + public EndpointSettings() {} + public EndpointSettings(string url, string path) { Url = url; Path = path; diff --git a/FModel/Settings/UserSettings.cs b/FModel/Settings/UserSettings.cs index bfe43db7..5dc4eb85 100644 --- a/FModel/Settings/UserSettings.cs +++ b/FModel/Settings/UserSettings.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Windows; using System.Windows.Input; -using CUE4Parse.UE4.Assets.Exports.Texture; -using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Versions; using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Textures; @@ -33,6 +31,8 @@ namespace FModel.Settings public static void Save() { + if (Default == null) return; + Default.PerDirectory[Default.CurrentDir.GameDirectory] = Default.CurrentDir; File.WriteAllText(FilePath, JsonConvert.SerializeObject(Default, Formatting.Indented)); } @@ -41,13 +41,9 @@ namespace FModel.Settings if (File.Exists(FilePath)) File.Delete(FilePath); } - public static bool IsEndpointValid(FGame game, EEndpointType type, out FEndpoint endpoint) + public static bool IsEndpointValid(EEndpointType type, out EndpointSettings endpoint) { - endpoint = null; - if (!Default.CustomEndpoints.TryGetValue(game, out var endpoints)) - return false; - - endpoint = endpoints[(int) type]; + endpoint = Default.CurrentDir.Endpoints[(int) type]; return endpoint.Overwrite || endpoint.IsValid; } @@ -100,7 +96,7 @@ namespace FModel.Settings set => SetProperty(ref _modelDirectory, value); } - private string _gameDirectory; + private string _gameDirectory = string.Empty; public string GameDirectory { get => _gameDirectory; @@ -135,13 +131,6 @@ namespace FModel.Settings set => SetProperty(ref _avalonImageSize, value); } - private IDictionary _aesKeys = new Dictionary(); - public IDictionary AesKeys - { - get => _aesKeys; - set => SetProperty(ref _aesKeys, value); - } - private string _audioDeviceId; public string AudioDeviceId { @@ -156,7 +145,7 @@ namespace FModel.Settings set => SetProperty(ref _audioPlayerVolume, value); } - private ELoadingMode _loadingMode = ELoadingMode.Multiple; + private ELoadingMode _loadingMode = ELoadingMode.All; public ELoadingMode LoadingMode { get => _loadingMode; @@ -233,9 +222,19 @@ namespace FModel.Settings set => SetProperty(ref _readScriptData, value); } - // - // can't refactor to use this data layout for everything - // because it will wipe old user settings that relies on FGame + private IDictionary _perDirectory = new Dictionary(); + public IDictionary PerDirectory + { + get => _perDirectory; + set => SetProperty(ref _perDirectory, value); + } + + [JsonIgnore] + public DirectorySettings CurrentDir { get; set; } + + /// + /// TO DELETEEEEEEEEEEEEE + /// private IDictionary _manualGames = new Dictionary(); public IDictionary ManualGames { @@ -243,291 +242,6 @@ namespace FModel.Settings set => SetProperty(ref _manualGames, value); } - private ETexturePlatform _overridedPlatform = ETexturePlatform.DesktopMobile; - public ETexturePlatform OverridedPlatform - { - get => _overridedPlatform; - set => SetProperty(ref _overridedPlatform, value); - } - - private IDictionary _presets = new Dictionary - { - {FGame.Unknown, Constants._NO_PRESET_TRIGGER}, - {FGame.FortniteGame, Constants._NO_PRESET_TRIGGER}, - {FGame.ShooterGame, Constants._NO_PRESET_TRIGGER}, - {FGame.DeadByDaylight, Constants._NO_PRESET_TRIGGER}, - {FGame.OakGame, Constants._NO_PRESET_TRIGGER}, - {FGame.Dungeons, Constants._NO_PRESET_TRIGGER}, - {FGame.WorldExplorers, Constants._NO_PRESET_TRIGGER}, - {FGame.g3, Constants._NO_PRESET_TRIGGER}, - {FGame.StateOfDecay2, Constants._NO_PRESET_TRIGGER}, - {FGame.Prospect, Constants._NO_PRESET_TRIGGER}, - {FGame.Indiana, Constants._NO_PRESET_TRIGGER}, - {FGame.RogueCompany, Constants._NO_PRESET_TRIGGER}, - {FGame.SwGame, Constants._NO_PRESET_TRIGGER}, - {FGame.Platform, Constants._NO_PRESET_TRIGGER}, - {FGame.BendGame, Constants._NO_PRESET_TRIGGER}, - {FGame.TslGame, Constants._NO_PRESET_TRIGGER}, - {FGame.PortalWars, Constants._NO_PRESET_TRIGGER}, - {FGame.Gameface, Constants._NO_PRESET_TRIGGER}, - {FGame.Athena, Constants._NO_PRESET_TRIGGER}, - {FGame.MultiVersus, Constants._NO_PRESET_TRIGGER}, - {FGame.Hotta, Constants._NO_PRESET_TRIGGER}, - {FGame.eFootball, Constants._NO_PRESET_TRIGGER} - }; - public IDictionary Presets - { - get => _presets; - set => SetProperty(ref _presets, value); - } - - private IDictionary _overridedGame = new Dictionary - { - {FGame.Unknown, EGame.GAME_UE4_LATEST}, - {FGame.FortniteGame, EGame.GAME_UE5_2}, - {FGame.ShooterGame, EGame.GAME_Valorant}, - {FGame.DeadByDaylight, EGame.GAME_UE4_27}, - {FGame.OakGame, EGame.GAME_Borderlands3}, - {FGame.Dungeons, EGame.GAME_UE4_22}, - {FGame.WorldExplorers, EGame.GAME_UE4_24}, - {FGame.g3, EGame.GAME_UE4_22}, - {FGame.StateOfDecay2, EGame.GAME_StateOfDecay2}, - {FGame.Prospect, EGame.GAME_Splitgate}, - {FGame.Indiana, EGame.GAME_UE4_21}, - {FGame.RogueCompany, EGame.GAME_RogueCompany}, - {FGame.SwGame, EGame.GAME_UE4_LATEST}, - {FGame.Platform, EGame.GAME_UE4_26}, - {FGame.BendGame, EGame.GAME_UE4_11}, - {FGame.TslGame, EGame.GAME_PlayerUnknownsBattlegrounds}, - {FGame.PortalWars, EGame.GAME_UE4_27}, - {FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition}, - {FGame.Athena, EGame.GAME_SeaOfThieves}, - {FGame.MultiVersus, EGame.GAME_UE4_26}, - {FGame.Hotta, EGame.GAME_TowerOfFantasy}, - {FGame.eFootball, EGame.GAME_UE4_26} - }; - public IDictionary OverridedGame - { - get => _overridedGame; - set => SetProperty(ref _overridedGame, value); - } - - private IDictionary> _overridedCustomVersions = new Dictionary> - { - {FGame.Unknown, null}, - {FGame.FortniteGame, null}, - {FGame.ShooterGame, null}, - {FGame.DeadByDaylight, null}, - {FGame.OakGame, null}, - {FGame.Dungeons, null}, - {FGame.WorldExplorers, null}, - {FGame.g3, null}, - {FGame.StateOfDecay2, null}, - {FGame.Prospect, null}, - {FGame.Indiana, null}, - {FGame.RogueCompany, null}, - {FGame.SwGame, null}, - {FGame.Platform, null}, - {FGame.BendGame, null}, - {FGame.TslGame, null}, - {FGame.PortalWars, null}, - {FGame.Gameface, null}, - {FGame.Athena, null}, - {FGame.MultiVersus, null}, - {FGame.Hotta, null}, - {FGame.eFootball, null} - }; - public IDictionary> OverridedCustomVersions - { - get => _overridedCustomVersions; - set => SetProperty(ref _overridedCustomVersions, value); - } - - private IDictionary> _overridedOptions = new Dictionary> - { - {FGame.Unknown, null}, - {FGame.FortniteGame, null}, - {FGame.ShooterGame, null}, - {FGame.DeadByDaylight, null}, - {FGame.OakGame, null}, - {FGame.Dungeons, null}, - {FGame.WorldExplorers, null}, - {FGame.g3, null}, - {FGame.StateOfDecay2, null}, - {FGame.Prospect, null}, - {FGame.Indiana, null}, - {FGame.RogueCompany, null}, - {FGame.SwGame, null}, - {FGame.Platform, null}, - {FGame.BendGame, null}, - {FGame.TslGame, null}, - {FGame.PortalWars, null}, - {FGame.Gameface, null}, - {FGame.Athena, null}, - {FGame.MultiVersus, null}, - {FGame.Hotta, null}, - {FGame.eFootball, null} - }; - - private IDictionary>> _overridedMapStructTypes = new Dictionary>> - { - {FGame.Unknown, null}, - {FGame.FortniteGame, null}, - {FGame.ShooterGame, null}, - {FGame.DeadByDaylight, null}, - {FGame.OakGame, null}, - {FGame.Dungeons, null}, - {FGame.WorldExplorers, null}, - {FGame.g3, null}, - {FGame.StateOfDecay2, null}, - {FGame.Prospect, null}, - {FGame.Indiana, null}, - {FGame.RogueCompany, null}, - {FGame.SwGame, null}, - {FGame.Platform, null}, - {FGame.BendGame, null}, - {FGame.TslGame, null}, - {FGame.PortalWars, null}, - {FGame.Gameface, null}, - {FGame.Athena, null}, - {FGame.MultiVersus, null}, - {FGame.Hotta, null}, - {FGame.eFootball, null} - }; - public IDictionary> OverridedOptions - { - get => _overridedOptions; - set => SetProperty(ref _overridedOptions, value); - } - - public IDictionary>> OverridedMapStructTypes - { - get => _overridedMapStructTypes; - set => SetProperty(ref _overridedMapStructTypes, value); - } - - private IDictionary _customEndpoints = new Dictionary - { - {FGame.Unknown, new FEndpoint[]{new (), new ()}}, - { - FGame.FortniteGame, new [] - { - new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"), - new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") // && @.meta.platform=='Windows' - } - }, - {FGame.ShooterGame, new FEndpoint[]{new (), new ()}}, - {FGame.DeadByDaylight, new FEndpoint[]{new (), new ()}}, - {FGame.OakGame, new FEndpoint[]{new (), new ()}}, - {FGame.Dungeons, new FEndpoint[]{new (), new ()}}, - {FGame.WorldExplorers, new FEndpoint[]{new (), new ()}}, - {FGame.g3, new FEndpoint[]{new (), new ()}}, - {FGame.StateOfDecay2, new FEndpoint[]{new (), new ()}}, - {FGame.Prospect, new FEndpoint[]{new (), new ()}}, - {FGame.Indiana, new FEndpoint[]{new (), new ()}}, - {FGame.RogueCompany, new FEndpoint[]{new (), new ()}}, - {FGame.SwGame, new FEndpoint[]{new (), new ()}}, - {FGame.Platform, new FEndpoint[]{new (), new ()}}, - {FGame.BendGame, new FEndpoint[]{new (), new ()}}, - {FGame.TslGame, new FEndpoint[]{new (), new ()}}, - {FGame.PortalWars, new FEndpoint[]{new (), new ()}}, - {FGame.Gameface, new FEndpoint[]{new (), new ()}}, - {FGame.Athena, new FEndpoint[]{new (), new ()}}, - {FGame.MultiVersus, new FEndpoint[]{new (), new ()}}, - {FGame.Hotta, new FEndpoint[]{new (), new ()}}, - {FGame.eFootball, new FEndpoint[]{new (), new ()}} - }; - public IDictionary CustomEndpoints - { - get => _customEndpoints; - set => SetProperty(ref _customEndpoints, value); - } - - private IDictionary> _customDirectories = new Dictionary> - { - {FGame.Unknown, new List()}, - { - FGame.FortniteGame, new List - { - new("Cosmetics", "FortniteGame/Content/Athena/Items/Cosmetics/"), - new("Emotes [AUDIO]", "FortniteGame/Content/Athena/Sounds/Emotes/"), - new("Music Packs [AUDIO]", "FortniteGame/Content/Athena/Sounds/MusicPacks/"), - new("Weapons", "FortniteGame/Content/Athena/Items/Weapons/"), - new("Strings", "FortniteGame/Content/Localization/") - } - }, - { - FGame.ShooterGame, new List - { - new("Audio", "ShooterGame/Content/WwiseAudio/Media/"), - new("Characters", "ShooterGame/Content/Characters/"), - new("Gun Buddies", "ShooterGame/Content/Equippables/Buddies/"), - new("Cards and Sprays", "ShooterGame/Content/Personalization/"), - new("Shop Backgrounds", "ShooterGame/Content/UI/OutOfGame/MainMenu/Store/Shared/Textures/"), - new("Weapon Renders", "ShooterGame/Content/UI/Screens/OutOfGame/MainMenu/Collection/Assets/Large/") - } - }, - { - FGame.DeadByDaylight, new List - { - new("Audio", "DeadByDaylight/Content/WwiseAudio/Windows/"), - new("Characters", "DeadByDaylight/Content/Characters/"), - new("Icons", "DeadByDaylight/Content/UI/UMGAssets/Icons/"), - new("Strings", "DeadByDaylight/Content/Localization/") - } - }, - {FGame.OakGame, new List()}, - { - FGame.Dungeons, new List - { - new("Levels", "Dungeons/Content/data/Lovika/Levels"), - new("Friendlies", "Dungeons/Content/Actor/Characters/Friendlies"), - new("Skins", "Dungeons/Content/Actor/Characters/Player/Master/Skins"), - new("Strings", "Dungeons/Content/Localization/") - } - }, - { - FGame.WorldExplorers, new List - { - new("Loot", "WorldExplorers/Content/Loot/"), - new("Strings", "WorldExplorers/Content/Localization/") - } - }, - { - FGame.g3, new List - { - new("Cosmetics", "g3/Content/Blueprints/Cosmetics/"), - new("Strings", "g3/Content/Localization/") - } - }, - {FGame.StateOfDecay2, new List()}, - {FGame.Prospect, new List()}, - {FGame.Indiana, new List()}, - {FGame.RogueCompany, new List()}, - {FGame.SwGame, new List()}, - {FGame.Platform, new List()}, - {FGame.BendGame, new List()}, - {FGame.TslGame, new List()}, - {FGame.PortalWars, new List()}, - {FGame.Gameface, new List()}, - {FGame.Athena, new List()}, - {FGame.MultiVersus, new List()}, - {FGame.Hotta, new List()}, - {FGame.eFootball, new List()} - }; - public IDictionary> CustomDirectories - { - get => _customDirectories; - set => SetProperty(ref _customDirectories, value); - } - - private DateTime _lastAesReload = DateTime.Today.AddDays(-1); - public DateTime LastAesReload - { - get => _lastAesReload; - set => SetProperty(ref _lastAesReload, value); - } - private AuthResponse _lastAuthResponse = new() {AccessToken = "", ExpiresAt = DateTime.Now}; public AuthResponse LastAuthResponse { @@ -710,6 +424,13 @@ namespace FModel.Settings set => SetProperty(ref _saveMorphTargets, value); } + private bool _saveEmbeddedMaterials = true; + public bool SaveEmbeddedMaterials + { + get => _saveEmbeddedMaterials; + set => SetProperty(ref _saveEmbeddedMaterials, value); + } + private bool _saveSkeletonAsMesh; public bool SaveSkeletonAsMesh { diff --git a/FModel/Settings/VersioningSettings.cs b/FModel/Settings/VersioningSettings.cs new file mode 100644 index 00000000..e6ecec91 --- /dev/null +++ b/FModel/Settings/VersioningSettings.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using CUE4Parse.UE4.Objects.Core.Serialization; +using FModel.Framework; + +namespace FModel.Settings; + +public class VersioningSettings : ViewModel +{ + private IList _customVersions; + public IList CustomVersions + { + get => _customVersions; + set => SetProperty(ref _customVersions, value); + } + + private IDictionary _options; + public IDictionary Options + { + get => _options; + set => SetProperty(ref _options, value); + } + + private IDictionary> _mapStructTypes; + public IDictionary> MapStructTypes + { + get => _mapStructTypes; + set => SetProperty(ref _mapStructTypes, value); + } + + public VersioningSettings() {} +} diff --git a/FModel/ViewModels/AboutViewModel.cs b/FModel/ViewModels/AboutViewModel.cs new file mode 100644 index 00000000..6c0478d3 --- /dev/null +++ b/FModel/ViewModels/AboutViewModel.cs @@ -0,0 +1,70 @@ +using System.Text; +using System.Threading.Tasks; +using FModel.Framework; +using FModel.Services; +using FModel.ViewModels.ApiEndpoints.Models; + +namespace FModel.ViewModels; + +public class AboutViewModel : ViewModel +{ + private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + + private string _descriptionLabel; + public string DescriptionLabel + { + get => _descriptionLabel; + set => SetProperty(ref _descriptionLabel, value); + } + + private string _contributorsLabel; + public string ContributorsLabel + { + get => _contributorsLabel; + set => SetProperty(ref _contributorsLabel, value); + } + + private string _donatorsLabel; + public string DonatorsLabel + { + get => _donatorsLabel; + set => SetProperty(ref _donatorsLabel, value); + } + + private string _referencesLabel; + public string ReferencesLabel + { + get => _referencesLabel; + set => SetProperty(ref _referencesLabel, value); + } + + public AboutViewModel() + { + + } + + public async Task Initialize() + { + await Task.WhenAll( + Task.Run(() => + { + DescriptionLabel = "FModel is an archive explorer for Unreal Engine games that uses CUE4Parse as its core parsing library, providing robust support for the latest UE4 and UE5 archive formats. It aims to deliver a modern and intuitive user interface, powerful features, and a comprehensive set of tools for previewing and converting game packages, empowering YOU to understand games' inner workings with ease."; + ContributorsLabel = $"FModel owes its continued existence to the passionate individuals who have generously contributed their time and expertise. Contributions from individuals such as {string.Join(", ", "GMatrixGames", "amr", "LongerWarrior", "MinshuG", "InTheShade", "Officer")}, and countless others, both in the past and those yet to come, ensure the continuous development and success of this project. If you are benefiting from FModel and would like to support its continued improvements, please consider making a donation."; + ReferencesLabel = string.Join(", ", + "Adonis UI", "AutoUpdater.NET", "AvalonEdit", "CSCore", "CUE4Parse", "DiscordRichPresence", + "EpicManifestParser", "ImGui.NET", "K4os.Compression.LZ4", "Newtonsoft.Json", "NVorbis", "Oodle.NET", + "Ookii.Dialogs.Wpf", "OpenTK", "RestSharp", "Serilog", "SixLabors.ImageSharp", "SkiaSharp"); + }), + Task.Run(() => + { + var donators = _apiEndpointView.FModelApi.GetDonators(); + if (donators == null) return; + + var sb = new StringBuilder(); + sb.AppendJoin(", ", donators); + sb.Append('.'); + DonatorsLabel = sb.ToString(); + }) + ).ConfigureAwait(false); + } +} diff --git a/FModel/ViewModels/AesManagerViewModel.cs b/FModel/ViewModels/AesManagerViewModel.cs index c1134cb2..a3fa522d 100644 --- a/FModel/ViewModels/AesManagerViewModel.cs +++ b/FModel/ViewModels/AesManagerViewModel.cs @@ -35,22 +35,7 @@ public class AesManagerViewModel : ViewModel { await _threadWorkerView.Begin(_ => { - if (_cue4Parse.Game == FGame.Unknown && - UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings)) - { - _keysFromSettings = settings.AesKeys; - } - else - { - UserSettings.Default.AesKeys.TryGetValue(_cue4Parse.Game, out _keysFromSettings); - } - - _keysFromSettings ??= new AesResponse - { - MainKey = string.Empty, - DynamicKeys = null - }; - + _keysFromSettings = UserSettings.Default.CurrentDir.AesKeys; _mainKey.Key = Helper.FixKey(_keysFromSettings.MainKey); AesKeys = new FullyObservableCollection(EnumerateAesKeys()); AesKeys.ItemPropertyChanged += AesKeysOnItemPropertyChanged; @@ -105,9 +90,7 @@ public class AesManagerViewModel : ViewModel public void SetAesKeys() { - if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings; - else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings; + UserSettings.Default.CurrentDir.AesKeys = _keysFromSettings; // Log.Information("{@Json}", UserSettings.Default); } diff --git a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs index 8a835a30..0e1b1f85 100644 --- a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs +++ b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs @@ -24,6 +24,7 @@ public class FModelApiEndpoint : AbstractApiProvider { private News _news; private Info _infos; + private Donator[] _donators; private Backup[] _backups; private Game _game; private readonly IDictionary _communityDesigns = new Dictionary(); @@ -58,6 +59,19 @@ public class FModelApiEndpoint : AbstractApiProvider return _infos ?? GetInfosAsync(token, updateMode).GetAwaiter().GetResult(); } + public async Task GetDonatorsAsync() + { + var request = new FRestRequest($"https://api.fmodel.app/v1/donations/donators"); + var response = await _client.ExecuteAsync(request).ConfigureAwait(false); + Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString); + return response.Data; + } + + public Donator[] GetDonators() + { + return _donators ??= GetDonatorsAsync().GetAwaiter().GetResult(); + } + public async Task GetBackupsAsync(CancellationToken token, string gameName) { var request = new FRestRequest($"https://api.fmodel.app/v1/backups/{gameName}"); diff --git a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs index 3f389861..6c9b8dc4 100644 --- a/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs +++ b/FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs @@ -25,6 +25,14 @@ public class Backup [J] public long FileSize { get; private set; } } +public class Donator +{ + [J] public string Username { get; private set; } + [J] public int Count { get; private set; } + + public override string ToString() => $"{Username}{(Count > 5 ? " ❤️" : "")}"; +} + [DebuggerDisplay("{" + nameof(DisplayName) + "}")] public class Game { diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs index c908d0be..dd044e67 100644 --- a/FModel/ViewModels/ApplicationViewModel.cs +++ b/FModel/ViewModels/ApplicationViewModel.cs @@ -48,9 +48,7 @@ public class ApplicationViewModel : ViewModel public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}"; public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown"; - public string TitleExtra => - $"({(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" + - $"{(Build != EBuildKind.Release ? $" ({Build})" : "")}"; + public string TitleExtra => $"({UserSettings.Default.CurrentDir.UeVersion}){(Build != EBuildKind.Release ? $" ({Build})" : "")}"; public LoadingModesViewModel LoadingModes { get; } public CustomDirectoriesViewModel CustomDirectories { get; } @@ -58,7 +56,6 @@ public class ApplicationViewModel : ViewModel public SettingsViewModel SettingsView { get; } public AesManagerViewModel AesManager { get; } public AudioPlayerViewModel AudioPlayer { get; } - public MapViewerViewModel MapViewer { get; } private OodleCompressor _oodle; public ApplicationViewModel() @@ -73,50 +70,41 @@ public class ApplicationViewModel : ViewModel #endif LoadingModes = new LoadingModesViewModel(); - AvoidEmptyGameDirectoryAndSetEGame(false); - if (UserSettings.Default.GameDirectory is null) + UserSettings.Default.CurrentDir = AvoidEmptyGameDirectory(false); + if (UserSettings.Default.CurrentDir is null) { //If no game is selected, many things will break before a shutdown request is processed in the normal way. //A hard exit is preferable to an unhandled expection in this case Environment.Exit(0); } - CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory); - CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory); - SettingsView = new SettingsViewModel(CUE4Parse.Game); + CUE4Parse = new CUE4ParseViewModel(); + CustomDirectories = new CustomDirectoriesViewModel(); + SettingsView = new SettingsViewModel(); AesManager = new AesManagerViewModel(CUE4Parse); - MapViewer = new MapViewerViewModel(CUE4Parse); AudioPlayer = new AudioPlayerViewModel(); + Status.SetStatus(EStatusKind.Ready); } - public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched) + public DirectorySettings AvoidEmptyGameDirectory(bool bAlreadyLaunched) { var gameDirectory = UserSettings.Default.GameDirectory; - if (!string.IsNullOrEmpty(gameDirectory) && !bAlreadyLaunched) return; + if (!bAlreadyLaunched && UserSettings.Default.PerDirectory.TryGetValue(gameDirectory, out var currentDir)) + return currentDir; var gameLauncherViewModel = new GameSelectorViewModel(gameDirectory); var result = new DirectorySelector(gameLauncherViewModel).ShowDialog(); - if (!result.HasValue || !result.Value) return; + if (!result.HasValue || !result.Value) return null; - UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDetectedGame.GameDirectory; - if (!bAlreadyLaunched || gameDirectory == gameLauncherViewModel.SelectedDetectedGame.GameDirectory) return; + UserSettings.Default.GameDirectory = gameLauncherViewModel.SelectedDirectory.GameDirectory; + if (!bAlreadyLaunched || UserSettings.Default.CurrentDir.Equals(gameLauncherViewModel.SelectedDirectory)) + return gameLauncherViewModel.SelectedDirectory; + // UserSettings.Save(); // ??? change key then change game, key saved correctly what? + UserSettings.Default.CurrentDir = gameLauncherViewModel.SelectedDirectory; RestartWithWarning(); - } - - public async Task UpdateProvider(bool isLaunch) - { - if (!isLaunch && !AesManager.HasChange) return; - - CUE4Parse.ClearProvider(); - await ApplicationService.ThreadWorkerView.Begin(cancellationToken => - { - CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys); - CUE4Parse.Provider.LoadIniConfigs(); - AesManager.SetAesKeys(); - }); - RaisePropertyChanged(nameof(GameDisplayName)); + return null; } public void RestartWithWarning() @@ -161,6 +149,20 @@ public class ApplicationViewModel : ViewModel Application.Current.Shutdown(); } + public async Task UpdateProvider(bool isLaunch) + { + if (!isLaunch && !AesManager.HasChange) return; + + CUE4Parse.ClearProvider(); + await ApplicationService.ThreadWorkerView.Begin(cancellationToken => + { + CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys); + CUE4Parse.Provider.LoadIniConfigs(); + AesManager.SetAesKeys(); + }); + RaisePropertyChanged(nameof(GameDisplayName)); + } + public async Task InitVgmStream() { var vgmZipFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-win.zip"); diff --git a/FModel/ViewModels/AudioPlayerViewModel.cs b/FModel/ViewModels/AudioPlayerViewModel.cs index 87418e1c..4aedde73 100644 --- a/FModel/ViewModels/AudioPlayerViewModel.cs +++ b/FModel/ViewModels/AudioPlayerViewModel.cs @@ -551,6 +551,7 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable case "adpcm": case "opus": case "wem": + case "at9": { if (TryConvert(out var wavFilePath) && !string.IsNullOrEmpty(wavFilePath)) { diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 16f2c2b8..473ad1fb 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -62,11 +62,11 @@ public class CUE4ParseViewModel : ViewModel private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - private FGame _game; - public FGame Game + private string _internalGameName; + public string InternalGameName { - get => _game; - set => SetProperty(ref _game, value); + get => _internalGameName; + set => SetProperty(ref _internalGameName, value); } private bool _modelIsOverwritingMaterial; @@ -96,7 +96,7 @@ public class CUE4ParseViewModel : ViewModel var scale = ImGuiController.GetDpiScale(); var htz = Snooper.GetMaxRefreshFrequency(); return _snooper = new Snooper( - new GameWindowSettings { RenderFrequency = htz, UpdateFrequency = htz }, + new GameWindowSettings { UpdateFrequency = htz }, new NativeWindowSettings { Size = new OpenTK.Mathematics.Vector2i( @@ -123,80 +123,48 @@ public class CUE4ParseViewModel : ViewModel public TabControlViewModel TabControl { get; } public ConfigIni BuildInfo { get; } - public CUE4ParseViewModel(string gameDirectory) + public CUE4ParseViewModel() { + var currentDir = UserSettings.Default.CurrentDir; + var gameDirectory = currentDir.GameDirectory; + var versionContainer = new VersionContainer( + game: currentDir.UeVersion, platform: currentDir.TexturePlatform, + customVersions: new FCustomVersionContainer(currentDir.Versioning.CustomVersions), + optionOverrides: currentDir.Versioning.Options, + mapStructTypesOverrides: currentDir.Versioning.MapStructTypes); + switch (gameDirectory) { case Constants._FN_LIVE_TRIGGER: { - Game = FGame.FortniteGame; - Provider = new StreamedFileProvider("FortniteLive", true, - new VersionContainer( - UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, - customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]), - optionOverrides: UserSettings.Default.OverridedOptions[Game])); + InternalGameName = "FortniteGame"; + Provider = new StreamedFileProvider("FortniteLive", true, versionContainer); break; } case Constants._VAL_LIVE_TRIGGER: { - Game = FGame.ShooterGame; - Provider = new StreamedFileProvider("ValorantLive", true, - new VersionContainer( - UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, - customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]), - optionOverrides: UserSettings.Default.OverridedOptions[Game])); + InternalGameName = "ShooterGame"; + Provider = new StreamedFileProvider("ValorantLive", true, versionContainer); break; } default: { - var parent = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\"); - if (gameDirectory.Contains("eFootball")) parent = gameDirectory.SubstringBeforeLast("\\pak").SubstringAfterLast("\\"); - Game = parent.ToEnum(FGame.Unknown); - var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform, - customVersions: new FCustomVersionContainer(UserSettings.Default.OverridedCustomVersions[Game]), - optionOverrides: UserSettings.Default.OverridedOptions[Game], - mapStructTypesOverrides: UserSettings.Default.OverridedMapStructTypes[Game]); - - switch (Game) + InternalGameName = gameDirectory.SubstringBeforeLast(gameDirectory.Contains("eFootball") ? "\\pak" : "\\Content").SubstringAfterLast("\\"); + Provider = InternalGameName switch { - case FGame.StateOfDecay2: - { - Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List - { - new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), - new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") - }, - SearchOption.AllDirectories, true, versions); - break; - } - case FGame.FortniteGame: - Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List - { - new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\FortniteGame\\Saved\\PersistentDownloadDir\\InstalledBundles"), - }, - SearchOption.AllDirectories, true, versions); - break; - case FGame.eFootball: - Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List - { - new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download") - }, - SearchOption.AllDirectories, true, versions); - break; - case FGame.Unknown when UserSettings.Default.ManualGames.TryGetValue(gameDirectory, out var settings): - { - versions = new VersionContainer(settings.OverridedGame, UserSettings.Default.OverridedPlatform, - customVersions: new FCustomVersionContainer(settings.OverridedCustomVersions), - optionOverrides: settings.OverridedOptions, - mapStructTypesOverrides: settings.OverridedMapStructTypes); - goto default; - } - default: - { - Provider = new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versions); - break; - } - } + "StateOfDecay2" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), + new DirectoryInfo[] + { + new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\Paks"), + new(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\StateOfDecay2\\Saved\\DisabledPaks") + }, SearchOption.AllDirectories, true, versionContainer), + "eFootball" => new DefaultFileProvider(new DirectoryInfo(gameDirectory), + new DirectoryInfo[] + { + new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download") + }, SearchOption.AllDirectories, true, versionContainer), + _ => new DefaultFileProvider(gameDirectory, SearchOption.AllDirectories, true, versionContainer) + }; break; } @@ -255,7 +223,7 @@ public class CUE4ParseViewModel : ViewModel } if (!_fnLive.IsMatch(fileManifest.Name)) continue; - p.Initialize(fileManifest.Name, new Stream[] { fileManifest.GetStream() } + p.RegisterVfs(fileManifest.Name, new Stream[] { fileManifest.GetStream() } , it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions)); } @@ -273,7 +241,7 @@ public class CUE4ParseViewModel : ViewModel for (var i = 0; i < manifestInfo.Paks.Length; i++) { - p.Initialize(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) }); + p.RegisterVfs(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) }); } FLogger.Append(ELog.Information, () => @@ -283,18 +251,18 @@ public class CUE4ParseViewModel : ViewModel } break; - case DefaultFileProvider d: - d.Initialize(); - + case DefaultFileProvider: var buildInfoPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\BuildInfo.ini"); if (File.Exists(buildInfoPath)) BuildInfo.Read(new StringReader(File.ReadAllText(buildInfoPath))); break; } + Provider.Initialize(); + foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui { cancellationToken.ThrowIfCancellationRequested(); - if (vfs.Length <= 365 || !_hiddenArchives.IsMatch(vfs.Name)) continue; + if (!_hiddenArchives.IsMatch(vfs.Name)) continue; GameDirectory.Add(vfs); } @@ -336,7 +304,7 @@ public class CUE4ParseViewModel : ViewModel file.FileCount = vfs.FileCount; } - Game = Provider.GameName.ToEnum(Game); + InternalGameName = Provider.InternalGameName; } public void ClearProvider() @@ -354,7 +322,7 @@ public class CUE4ParseViewModel : ViewModel { // game directory dependent, we don't have the provider game name yet since we don't have aes keys // except when this comes from the AES Manager - if (!UserSettings.IsEndpointValid(Game, EEndpointType.Aes, out var endpoint)) + if (!UserSettings.IsEndpointValid(EEndpointType.Aes, out var endpoint)) return; await _threadWorkerView.Begin(cancellationToken => @@ -362,7 +330,7 @@ public class CUE4ParseViewModel : ViewModel var aes = _apiEndpointView.DynamicApi.GetAesKeys(cancellationToken, endpoint.Url, endpoint.Path); if (aes is not { IsValid: true }) return; - UserSettings.Default.AesKeys[Game] = aes; + UserSettings.Default.CurrentDir.AesKeys = aes; }); } @@ -370,7 +338,7 @@ public class CUE4ParseViewModel : ViewModel { await _threadWorkerView.Begin(cancellationToken => { - var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.GameName); + var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.InternalGameName); if (info == null) return; FLogger.Append(ELog.None, () => @@ -383,9 +351,9 @@ public class CUE4ParseViewModel : ViewModel }); } - public Task InitMappings() + public Task InitMappings(bool force = false) { - if (!UserSettings.IsEndpointValid(Game, EEndpointType.Mapping, out var endpoint)) + if (!UserSettings.IsEndpointValid(EEndpointType.Mapping, out var endpoint)) { Provider.MappingsContainer = null; return Task.CompletedTask; @@ -393,11 +361,10 @@ public class CUE4ParseViewModel : ViewModel return Task.Run(() => { + var l = ELog.Information; if (endpoint.Overwrite && File.Exists(endpoint.FilePath)) { Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath); - FLogger.Append(ELog.Information, () => - FLogger.Text($"Mappings pulled from '{endpoint.FilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true)); } else if (endpoint.IsValid) { @@ -410,14 +377,12 @@ public class CUE4ParseViewModel : ViewModel if (!mapping.IsValid) continue; var mappingPath = Path.Combine(mappingsFolder, mapping.FileName); - if (!File.Exists(mappingPath)) + if (force || !File.Exists(mappingPath)) { _apiEndpointView.DownloadFile(mapping.Url, mappingPath); } Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath); - FLogger.Append(ELog.Information, () => - FLogger.Text($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true)); break; } } @@ -429,10 +394,15 @@ public class CUE4ParseViewModel : ViewModel var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName); - FLogger.Append(ELog.Warning, () => - FLogger.Text($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true)); + l = ELog.Warning; } } + + if (Provider.MappingsContainer is FileUsmapTypeMappingsProvider m) + { + Log.Information($"Mappings pulled from '{m.FileName}'"); + FLogger.Append(l, () => FLogger.Text($"Mappings pulled from '{m.FileName}'", Constants.WHITE, true)); + } }); } @@ -444,13 +414,24 @@ public class CUE4ParseViewModel : ViewModel return Task.Run(() => { - var inst = new List(); - Provider.DefaultEngine.FindPropertyInstructions("ConsoleVariables", "a.StripAdditiveRefPose", inst); - if (inst.Count > 0 && inst[0].Value.Equals("1")) + foreach (var token in Provider.DefaultEngine.Sections.FirstOrDefault(s => s.Name == "ConsoleVariables")?.Tokens ?? new List()) { - FLogger.Append(ELog.Warning, () => - FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true)); + if (token is not InstructionToken it) continue; + var boolValue = it.Value.Equals("1"); + + switch (it.Key) + { + case "a.StripAdditiveRefPose" when boolValue: + FLogger.Append(ELog.Warning, () => + FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true)); + continue; + case "r.StaticMesh.KeepMobileMinLODSettingOnDesktop": + case "r.SkeletalMesh.KeepMobileMinLODSettingOnDesktop": + Provider.Versions[it.Key[2..]] = boolValue; + continue; + } } + _cvaVerifDone = true; }); } @@ -472,7 +453,7 @@ public class CUE4ParseViewModel : ViewModel public Task VerifyContentBuildManifest() { - if (Provider is not DefaultFileProvider || !Provider.GameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) + if (Provider is not DefaultFileProvider || !Provider.InternalGameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) return Task.CompletedTask; var persistentDownloadDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FortniteGame/Saved/PersistentDownloadDir"); @@ -564,7 +545,7 @@ public class CUE4ParseViewModel : ViewModel } private Task LoadHotfixedLocalizedResources() { - if (!Provider.GameName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; + if (!Provider.InternalGameName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask; return Task.Run(() => { var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(default, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage)); @@ -591,7 +572,7 @@ public class CUE4ParseViewModel : ViewModel if (_virtualPathCount > 0) return Task.CompletedTask; return Task.Run(() => { - _virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion()); + _virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.CurrentDir.UeVersion.GetVersion()); if (_virtualPathCount > 0) { FLogger.Append(ELog.Information, () => @@ -609,7 +590,7 @@ public class CUE4ParseViewModel : ViewModel { foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs); } @@ -619,7 +600,7 @@ public class CUE4ParseViewModel : ViewModel { foreach (var asset in folder.AssetsList.Assets) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); try { @@ -759,7 +740,7 @@ public class CUE4ParseViewModel : ViewModel break; } - case "bin" when fileName.Contains("AssetRegistry"): + case "bin" when fileName.Contains("AssetRegistry", StringComparison.OrdinalIgnoreCase): { if (Provider.TryCreateReader(fullPath, out var archive)) { @@ -892,7 +873,7 @@ public class CUE4ParseViewModel : ViewModel TabControl.SelectedTab.SetDocumentText(verseDigest.ReadableCode, false, false); return true; } - case UTexture2D { IsVirtual: false } texture when isNone: + case UTexture texture when isNone || saveTextures: { TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi); return false; @@ -902,8 +883,12 @@ public class CUE4ParseViewModel : ViewModel { var shouldDecompress = UserSettings.Default.CompressedAudioMode == ECompressedAudio.PlayDecompressed; export.Decode(shouldDecompress, out var audioFormat, out var data); - if (data == null || string.IsNullOrEmpty(audioFormat) || export.Owner == null) + var hasAf = !string.IsNullOrEmpty(audioFormat); + if (data == null || !hasAf || export.Owner == null) + { + if (hasAf) FLogger.Append(ELog.Warning, () => FLogger.Text($"Unsupported audio format '{audioFormat}'", Constants.WHITE, true)); return false; + } SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data); return false; @@ -911,8 +896,10 @@ public class CUE4ParseViewModel : ViewModel case UWorld when isNone && UserSettings.Default.PreviewWorlds: case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes: case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes: + case USkeleton when isNone && UserSettings.Default.SaveSkeletonAsMesh: case UMaterialInstance when isNone && UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial && - !(Game == FGame.FortniteGame && export.Owner != null && (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) || + !(Provider.InternalGameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase) && export.Owner != null && + (export.Owner.Name.EndsWith($"/MI_OfferImages/{export.Name}", StringComparison.OrdinalIgnoreCase) || export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) || export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))): { @@ -997,8 +984,9 @@ public class CUE4ParseViewModel : ViewModel MaterialFormat = UserSettings.Default.MaterialExportFormat, TextureFormat = UserSettings.Default.TextureExportFormat, SocketFormat = UserSettings.Default.SocketExportFormat, - Platform = UserSettings.Default.OverridedPlatform, - ExportMorphTargets = UserSettings.Default.SaveMorphTargets + Platform = UserSettings.Default.CurrentDir.TexturePlatform, + ExportMorphTargets = UserSettings.Default.SaveMorphTargets, + ExportMaterials = UserSettings.Default.SaveEmbeddedMaterials }; var toSave = new Exporter(export, exportOptions); diff --git a/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs b/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs index 8258bfba..4ed647d4 100644 --- a/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs +++ b/FModel/ViewModels/Commands/AddEditDirectoryCommand.cs @@ -1,5 +1,6 @@ using AdonisUI.Controls; using FModel.Framework; +using FModel.Settings; using FModel.Views; namespace FModel.ViewModels.Commands; @@ -31,4 +32,4 @@ public class AddEditDirectoryCommand : ViewModelCommand switch (parameter) { case "Directory_Selector": - contextViewModel.AvoidEmptyGameDirectoryAndSetEGame(true); + contextViewModel.AvoidEmptyGameDirectory(true); break; case "Directory_AES": Helper.OpenWindow("AES Manager", () => new AesManager().Show()); break; case "Directory_Backup": - Helper.OpenWindow("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.GameName).Show()); + Helper.OpenWindow("Backup Manager", () => new BackupManager(contextViewModel.CUE4Parse.Provider.InternalGameName).Show()); break; case "Directory_ArchivesInfo": contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info"); @@ -42,19 +42,12 @@ public class MenuCommand : ViewModelCommand case "Views_AudioPlayer": Helper.OpenWindow("Audio Player", () => new AudioPlayer().Show()); break; - case "Views_MapViewer": - Helper.OpenWindow("Map Viewer", () => new MapViewer().Show()); - break; case "Views_ImageMerger": Helper.OpenWindow("Image Merger", () => new ImageMerger().Show()); break; case "Settings": Helper.OpenWindow("Settings", () => new SettingsView().Show()); break; - case "ModelSettings": - UserSettings.Default.LastOpenedSettingTab = contextViewModel.CUE4Parse.Game == FGame.FortniteGame ? 2 : 1; - Helper.OpenWindow("Settings", () => new SettingsView().Show()); - break; case "Help_About": Helper.OpenWindow("About", () => new About().Show()); break; @@ -111,7 +104,7 @@ public class MenuCommand : ViewModelCommand if (!expand && folder.IsExpanded) { folder.IsExpanded = false; - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); } @@ -129,7 +122,7 @@ public class MenuCommand : ViewModelCommand for (var node = nodes.Last; node != null; node = node.Previous) { node.Value.IsExpanded = true; - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); } } diff --git a/FModel/ViewModels/Commands/RightClickMenuCommand.cs b/FModel/ViewModels/Commands/RightClickMenuCommand.cs index 1da8395e..ab009a4b 100644 --- a/FModel/ViewModels/Commands/RightClickMenuCommand.cs +++ b/FModel/ViewModels/Commands/RightClickMenuCommand.cs @@ -29,7 +29,7 @@ public class RightClickMenuCommand : ViewModelCommand case "Assets_Extract_New_Tab": foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true); } @@ -37,7 +37,7 @@ public class RightClickMenuCommand : ViewModelCommand case "Assets_Export_Data": foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); contextViewModel.CUE4Parse.ExportData(asset.FullPath); } @@ -45,7 +45,7 @@ public class RightClickMenuCommand : ViewModelCommand case "Assets_Save_Properties": foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties); } @@ -53,7 +53,7 @@ public class RightClickMenuCommand : ViewModelCommand case "Assets_Save_Textures": foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures); } @@ -61,7 +61,7 @@ public class RightClickMenuCommand : ViewModelCommand case "Assets_Save_Models": foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto); } @@ -69,7 +69,7 @@ public class RightClickMenuCommand : ViewModelCommand case "Assets_Save_Animations": foreach (var asset in assetItems) { - Thread.Sleep(10); + Thread.Yield(); cancellationToken.ThrowIfCancellationRequested(); contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto); } diff --git a/FModel/ViewModels/CustomDirectoriesViewModel.cs b/FModel/ViewModels/CustomDirectoriesViewModel.cs index 84efe771..d5394edf 100644 --- a/FModel/ViewModels/CustomDirectoriesViewModel.cs +++ b/FModel/ViewModels/CustomDirectoriesViewModel.cs @@ -11,37 +11,6 @@ using FModel.ViewModels.Commands; namespace FModel.ViewModels; -public class CustomDirectory : ViewModel -{ - private string _header; - public string Header - { - get => _header; - set => SetProperty(ref _header, value); - } - - private string _directoryPath; - public string DirectoryPath - { - get => _directoryPath; - set => SetProperty(ref _directoryPath, value); - } - - public CustomDirectory() - { - Header = string.Empty; - DirectoryPath = string.Empty; - } - - public CustomDirectory(string header, string path) - { - Header = header; - DirectoryPath = path; - } - - public override string ToString() => Header; -} - public class CustomDirectoriesViewModel : ViewModel { private GoToCommand _goToCommand; @@ -54,13 +23,8 @@ public class CustomDirectoriesViewModel : ViewModel private readonly ObservableCollection _directories; public ReadOnlyObservableCollection Directories { get; } - private readonly FGame _game; - private readonly string _gameDirectoryAtLaunch; - - public CustomDirectoriesViewModel(FGame game, string directory) + public CustomDirectoriesViewModel() { - _game = game; - _gameDirectoryAtLaunch = directory; _directories = new ObservableCollection(EnumerateDirectories()); Directories = new ReadOnlyObservableCollection(_directories); } @@ -74,6 +38,7 @@ public class CustomDirectoriesViewModel : ViewModel public void Add(CustomDirectory dir) { _directories.Add(new MenuItem { Header = dir.Header, Tag = dir.DirectoryPath, ItemsSource = EnumerateCommands(dir) }); + Save(); } public void Edit(int index, CustomDirectory newDir) @@ -82,25 +47,25 @@ public class CustomDirectoriesViewModel : ViewModel dir.Header = newDir.Header; dir.Tag = newDir.DirectoryPath; + Save(); } public void Delete(int index) { _directories.RemoveAt(index); + Save(); } public void Save() { - var cd = new List(); + var directories = new List(); for (var i = 2; i < _directories.Count; i++) { if (_directories[i] is not MenuItem m) continue; - cd.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString())); + directories.Add(new CustomDirectory(m.Header.ToString(), m.Tag.ToString())); } - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(_gameDirectoryAtLaunch)) - UserSettings.Default.ManualGames[_gameDirectoryAtLaunch].CustomDirectories = cd; - else UserSettings.Default.CustomDirectories[_game] = cd; + UserSettings.Default.CurrentDir.Directories = directories; } private IEnumerable EnumerateDirectories() @@ -115,12 +80,7 @@ public class CustomDirectoriesViewModel : ViewModel }; yield return new Separator(); - IList cd; - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameDirectoryAtLaunch, out var settings)) - cd = settings.CustomDirectories; - else cd = UserSettings.Default.CustomDirectories[_game]; - - foreach (var setting in cd) + foreach (var setting in UserSettings.Default.CurrentDir.Directories) { if (setting.DirectoryPath.EndsWith('/')) setting.DirectoryPath = setting.DirectoryPath[..^1]; @@ -167,4 +127,4 @@ public class CustomDirectoriesViewModel : ViewModel CommandParameter = dir }; } -} \ No newline at end of file +} diff --git a/FModel/ViewModels/GameSelectorViewModel.cs b/FModel/ViewModels/GameSelectorViewModel.cs index 1ffa43bf..f1d40228 100644 --- a/FModel/ViewModels/GameSelectorViewModel.cs +++ b/FModel/ViewModels/GameSelectorViewModel.cs @@ -22,114 +22,97 @@ public class GameSelectorViewModel : ViewModel { public string GameName { get; set; } public string GameDirectory { get; set; } + public EGame OverridedGame { get; set; } public bool IsManual { get; set; } // the followings are only used when game is manually added public AesResponse AesKeys { get; set; } - public EGame OverridedGame { get; set; } public List OverridedCustomVersions { get; set; } public Dictionary OverridedOptions { get; set; } public Dictionary> OverridedMapStructTypes { get; set; } public IList CustomDirectories { get; set; } } - private DetectedGame _selectedDetectedGame; - public DetectedGame SelectedDetectedGame + private DirectorySettings _selectedDirectory; + public DirectorySettings SelectedDirectory { - get => _selectedDetectedGame; - set => SetProperty(ref _selectedDetectedGame, value); + get => _selectedDirectory; + set => SetProperty(ref _selectedDirectory, value); } - private readonly ObservableCollection _autoDetectedGames; - public ReadOnlyObservableCollection AutoDetectedGames { get; } + private readonly ObservableCollection _detectedDirectories; + public ReadOnlyObservableCollection DetectedDirectories { get; } + public ReadOnlyObservableCollection UeVersions { get; } public GameSelectorViewModel(string gameDirectory) { - _autoDetectedGames = new ObservableCollection(EnumerateDetectedGames().Where(x => x != null)); - foreach (var game in UserSettings.Default.ManualGames.Values) + _detectedDirectories = new ObservableCollection(EnumerateDetectedGames().Where(x => x != null)); + foreach (var dir in UserSettings.Default.PerDirectory.Values.Where(x => x.IsManual)) { - _autoDetectedGames.Add(game); + _detectedDirectories.Add((DirectorySettings) dir.Clone()); } - AutoDetectedGames = new ReadOnlyObservableCollection(_autoDetectedGames); + DetectedDirectories = new ReadOnlyObservableCollection(_detectedDirectories); - if (AutoDetectedGames.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame) - SelectedDetectedGame = detectedGame; + if (DetectedDirectories.FirstOrDefault(x => x.GameDirectory == gameDirectory) is { } detectedGame) + SelectedDirectory = detectedGame; else if (!string.IsNullOrEmpty(gameDirectory)) - AddUnknownGame(gameDirectory); + AddUndetectedDir(gameDirectory); else - SelectedDetectedGame = AutoDetectedGames.FirstOrDefault(); + SelectedDirectory = DetectedDirectories.FirstOrDefault(); + + UeVersions = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUeGames())); } - /// - /// dedicated to manual games - /// - public void AddUnknownGame(string gameName, string gameDirectory) + public void AddUndetectedDir(string gameDirectory) => AddUndetectedDir(gameDirectory.SubstringAfterLast('\\'), gameDirectory); + public void AddUndetectedDir(string gameName, string gameDirectory) { - var game = new DetectedGame - { - GameName = gameName, - GameDirectory = gameDirectory, - IsManual = true, - AesKeys = null, - OverridedGame = EGame.GAME_UE4_LATEST, - OverridedCustomVersions = null, - OverridedOptions = null, - OverridedMapStructTypes = null, - CustomDirectories = new List() - }; - - UserSettings.Default.ManualGames[gameDirectory] = game; - _autoDetectedGames.Add(game); - SelectedDetectedGame = AutoDetectedGames.Last(); - } - - public void AddUnknownGame(string gameDirectory) - { - _autoDetectedGames.Add(new DetectedGame { GameName = gameDirectory.SubstringAfterLast('\\'), GameDirectory = gameDirectory }); - SelectedDetectedGame = AutoDetectedGames.Last(); + var setting = DirectorySettings.Default(gameName, gameDirectory, true); + UserSettings.Default.PerDirectory[gameDirectory] = setting; + _detectedDirectories.Add(setting); + SelectedDirectory = DetectedDirectories.Last(); } public void DeleteSelectedGame() { - UserSettings.Default.ManualGames.Remove(SelectedDetectedGame.GameDirectory); // should not be a problem - _autoDetectedGames.Remove(SelectedDetectedGame); - SelectedDetectedGame = AutoDetectedGames.Last(); + UserSettings.Default.PerDirectory.Remove(SelectedDirectory.GameDirectory); // should not be a problem + _detectedDirectories.Remove(SelectedDirectory); + SelectedDirectory = DetectedDirectories.Last(); } - private IEnumerable EnumerateDetectedGames() + private IEnumerable EnumerateUeGames() + => Enum.GetValues() + .GroupBy(value => (int)value) + .Select(group => group.First()) + .OrderBy(value => (int)value == ((int)value & ~0xF)); + private IEnumerable EnumerateDetectedGames() { - yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks"); - yield return new DetectedGame { GameName = "Fortnite [LIVE]", GameDirectory = Constants._FN_LIVE_TRIGGER }; - yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks"); - yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks"); - yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks"); - yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks"); - yield return GetUnrealEngineGame("WorldExplorersLive", "\\WorldExplorers\\Content\\Paks"); - yield return GetUnrealEngineGame("Newt", "\\g3\\Content\\Paks"); - yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks"); - yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks"); - yield return GetUnrealEngineGame("a99769d95d8f400baad1f67ab5dfe508", "\\Core\\Platform\\Content\\Paks"); - yield return GetUnrealEngineGame("Nebula", "\\BendGame\\Content"); - yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks"); - yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks"); - yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks"); - yield return new DetectedGame { GameName = "Valorant [LIVE]", GameDirectory = Constants._VAL_LIVE_TRIGGER }; - yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks"); - yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight - yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG - yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks"); // STAR WARS Jedi: Fallen Order™ - yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate - yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves - yield return GetSteamGame(1665460, "\\pak"); // eFootball 2023 - yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks"); - yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks"); - yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks"); - yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks"); + yield return GetUnrealEngineGame("Fortnite", "\\FortniteGame\\Content\\Paks", EGame.GAME_UE5_3); + yield return DirectorySettings.Default("Fortnite [LIVE]", Constants._FN_LIVE_TRIGGER, ue: EGame.GAME_UE5_3); + yield return GetUnrealEngineGame("Pewee", "\\RogueCompany\\Content\\Paks", EGame.GAME_RogueCompany); + yield return GetUnrealEngineGame("Rosemallow", "\\Indiana\\Content\\Paks", EGame.GAME_UE4_21); + yield return GetUnrealEngineGame("Catnip", "\\OakGame\\Content\\Paks", EGame.GAME_Borderlands3); + yield return GetUnrealEngineGame("AzaleaAlpha", "\\Prospect\\Content\\Paks", EGame.GAME_UE4_27); + yield return GetUnrealEngineGame("shoebill", "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); + yield return GetUnrealEngineGame("Snoek", "\\StateOfDecay2\\Content\\Paks", EGame.GAME_StateOfDecay2); + yield return GetUnrealEngineGame("711c5e95dc094ca58e5f16bd48e751d6", "\\MultiVersus\\Content\\Paks", EGame.GAME_UE4_26); + yield return GetUnrealEngineGame("9361c8c6d2f34b42b5f2f61093eedf48", "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); + yield return GetRiotGame("VALORANT", "ShooterGame\\Content\\Paks", EGame.GAME_Valorant); + yield return DirectorySettings.Default("VALORANT [LIVE]", Constants._VAL_LIVE_TRIGGER, ue: EGame.GAME_Valorant); + yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks", EGame.GAME_UE4_27); // Dead By Daylight + yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks", EGame.GAME_PlayerUnknownsBattlegrounds); // PUBG + yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks", EGame.GAME_StarWarsJediFallenOrder); // STAR WARS Jedi: Fallen Order™ + yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks", EGame.GAME_Splitgate); // Splitgate + yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks", EGame.GAME_SeaOfThieves); // Sea of Thieves + yield return GetSteamGame(1665460, "\\pak", EGame.GAME_UE4_26); // eFootball 2023 + yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition); + yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition); + yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks", EGame.GAME_GTATheTrilogyDefinitiveEdition); + yield return GetLevelInfiniteGame("tof_launcher", "\\Hotta\\Content\\Paks", EGame.GAME_TowerOfFantasy); } private LauncherInstalled _launcherInstalled; - private DetectedGame GetUnrealEngineGame(string gameName, string pakDirectory) + private DirectorySettings GetUnrealEngineGame(string gameName, string pakDirectory, EGame ueVersion) { _launcherInstalled ??= GetDriveLauncherInstalls("ProgramData\\Epic\\UnrealEngineLauncher\\LauncherInstalled.dat"); if (_launcherInstalled?.InstallationList != null) @@ -140,7 +123,7 @@ public class GameSelectorViewModel : ViewModel if (installationList.AppName.Equals(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir)) { Log.Debug("Found {GameName} in LauncherInstalled.dat", gameName); - return new DetectedGame { GameName = installationList.AppName, GameDirectory = gameDir }; + return DirectorySettings.Default(installationList.AppName, gameDir, ue: ueVersion); } } } @@ -149,7 +132,7 @@ public class GameSelectorViewModel : ViewModel } private RiotClientInstalls _riotClientInstalls; - private DetectedGame GetRiotGame(string gameName, string pakDirectory) + private DirectorySettings GetRiotGame(string gameName, string pakDirectory, EGame ueVersion) { _riotClientInstalls ??= GetDriveLauncherInstalls("ProgramData\\Riot Games\\RiotClientInstalls.json"); if (_riotClientInstalls is { AssociatedClient: { } }) @@ -160,7 +143,7 @@ public class GameSelectorViewModel : ViewModel if (key.Contains(gameName, StringComparison.OrdinalIgnoreCase) && Directory.Exists(gameDir)) { Log.Debug("Found {GameName} in RiotClientInstalls.json", gameName); - return new DetectedGame { GameName = gameName, GameDirectory = gameDir }; + return DirectorySettings.Default(gameName, gameDir, ue: ueVersion); } } } @@ -168,36 +151,19 @@ public class GameSelectorViewModel : ViewModel return null; } - private LauncherSettings _launcherSettings; - private DetectedGame GetMojangGame(string gameName, string pakDirectory) - { - _launcherSettings ??= GetDataLauncherInstalls("\\.minecraft\\launcher_settings.json"); - if (_launcherSettings is { ProductLibraryDir: { } }) - { - var gameDir = $"{_launcherSettings.ProductLibraryDir}{pakDirectory}"; - if (Directory.Exists(gameDir)) - { - Log.Debug("Found {GameName} in launcher_settings.json", gameName); - return new DetectedGame { GameName = gameName, GameDirectory = gameDir }; - } - } - - return null; - } - - private DetectedGame GetSteamGame(int id, string pakDirectory) + private DirectorySettings GetSteamGame(int id, string pakDirectory, EGame ueVersion) { var steamInfo = SteamDetection.GetSteamGameById(id); if (steamInfo is not null) { Log.Debug("Found {GameName} in steam manifests", steamInfo.Name); - return new DetectedGame { GameName = steamInfo.Name, GameDirectory = $"{steamInfo.GameRoot}{pakDirectory}" }; + return DirectorySettings.Default(steamInfo.Name, $"{steamInfo.GameRoot}{pakDirectory}", ue: ueVersion); } return null; } - private DetectedGame GetRockstarGamesGame(string key, string pakDirectory) + private DirectorySettings GetRockstarGamesGame(string key, string pakDirectory, EGame ueVersion) { var installLocation = string.Empty; try @@ -213,13 +179,13 @@ public class GameSelectorViewModel : ViewModel if (Directory.Exists(gameDir)) { Log.Debug("Found {GameName} in the registry", key); - return new DetectedGame { GameName = key, GameDirectory = gameDir }; + return DirectorySettings.Default(key, gameDir, ue: ueVersion); } return null; } - private DetectedGame GetLevelInfiniteGame(string key, string pakDirectory) + private DirectorySettings GetLevelInfiniteGame(string key, string pakDirectory, EGame ueVersion) { var installLocation = string.Empty; var displayName = string.Empty; @@ -238,7 +204,7 @@ public class GameSelectorViewModel : ViewModel if (Directory.Exists(gameDir)) { Log.Debug("Found {GameName} in the registry", key); - return new DetectedGame { GameName = displayName, GameDirectory = gameDir }; + return DirectorySettings.Default(displayName, gameDir, ue: ueVersion); } return null; @@ -258,19 +224,6 @@ public class GameSelectorViewModel : ViewModel return default; } - private T GetDataLauncherInstalls(string jsonFile) - { - var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - var launcher = $"{appData}{jsonFile}"; - if (File.Exists(launcher)) - { - Log.Debug("\"{Launcher}\" found in \"{AppData}\"", launcher, appData); - return JsonConvert.DeserializeObject(File.ReadAllText(launcher)); - } - - return default; - } - #pragma warning disable 649 private class LauncherInstalled { diff --git a/FModel/ViewModels/MapViewerViewModel.cs b/FModel/ViewModels/MapViewerViewModel.cs deleted file mode 100644 index b9a36dcf..00000000 --- a/FModel/ViewModels/MapViewerViewModel.cs +++ /dev/null @@ -1,876 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Media.Imaging; -using CUE4Parse.UE4.Assets.Exports; -using CUE4Parse.UE4.Assets.Exports.Engine; -using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Assets.Exports.Texture; -using CUE4Parse.UE4.Assets.Objects; -using CUE4Parse.UE4.Objects.Core.i18N; -using CUE4Parse.UE4.Objects.Core.Math; -using CUE4Parse.UE4.Objects.GameplayTags; -using CUE4Parse.UE4.Objects.UObject; -using FModel.Creator; -using FModel.Extensions; -using FModel.Framework; -using FModel.Services; -using SkiaSharp; -using SkiaSharp.HarfBuzz; - -namespace FModel.ViewModels; - -public class MapLayer -{ - public SKBitmap Layer; - public bool IsEnabled; -} - -public enum EWaypointType -{ - Parkour, - TimeTrials -} - -public class MapViewerViewModel : ViewModel -{ - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private DiscordHandler _discordHandler => DiscordService.DiscordHandler; - - #region BINDINGS - - private bool _brPois; - public bool BrPois - { - get => _brPois; - set => SetProperty(ref _brPois, value, "ApolloGameplay_MapPois"); - } - - private bool _brLandmarks; - public bool BrLandmarks - { - get => _brLandmarks; - set => SetProperty(ref _brLandmarks, value, "ApolloGameplay_MapLandmarks"); - } - - private bool _brTagsLocation; - public bool BrTagsLocation - { - get => _brTagsLocation; - set => SetProperty(ref _brTagsLocation, value, "ApolloGameplay_TagsLocation"); - } - - private bool _brPatrolsPath; - public bool BrPatrolsPath - { - get => _brPatrolsPath; - set => SetProperty(ref _brPatrolsPath, value, "ApolloGameplay_PatrolsPath"); - } - - private bool _brUpgradeBenches; - public bool BrUpgradeBenches - { - get => _brUpgradeBenches; - set => SetProperty(ref _brUpgradeBenches, value, "ApolloGameplay_UpgradeBenches"); - } - - private bool _brPhonebooths; - public bool BrPhonebooths - { - get => _brPhonebooths; - set => SetProperty(ref _brPhonebooths, value, "ApolloGameplay_Phonebooths"); - } - - private bool _brVendingMachines; - public bool BrVendingMachines - { - get => _brVendingMachines; - set => SetProperty(ref _brVendingMachines, value, "ApolloGameplay_VendingMachines"); - } - - private bool _brBountyBoards; - public bool BrBountyBoards - { - get => _brBountyBoards; - set => SetProperty(ref _brBountyBoards, value, "ApolloGameplay_BountyBoards"); - } - - private bool _prLandmarks; - public bool PrLandmarks - { - get => _prLandmarks; - set => SetProperty(ref _prLandmarks, value, "PapayaGameplay_MapLandmarks"); - } - - private bool _prCannonball; - public bool PrCannonball - { - get => _prCannonball; - set => SetProperty(ref _prCannonball, value, "PapayaGameplay_CannonballGame"); - } - - private bool _prSkydive; - public bool PrSkydive - { - get => _prSkydive; - set => SetProperty(ref _prSkydive, value, "PapayaGameplay_SkydiveGame"); - } - - private bool _prShootingTargets; - public bool PrShootingTargets - { - get => _prShootingTargets; - set => SetProperty(ref _prShootingTargets, value, "PapayaGameplay_ShootingTargets"); - } - - private bool _prParkour; - public bool PrParkour - { - get => _prParkour; - set => SetProperty(ref _prParkour, value, "PapayaGameplay_ParkourGame"); - } - - private bool _prTimeTrials; - public bool PrTimeTrials - { - get => _prTimeTrials; - set => SetProperty(ref _prTimeTrials, value, "PapayaGameplay_TimeTrials"); - } - - private bool _prVendingMachines; - public bool PrVendingMachines - { - get => _prVendingMachines; - set => SetProperty(ref _prVendingMachines, value, "PapayaGameplay_VendingMachines"); - } - - private bool _prMusicBlocks; - public bool PrMusicBlocks - { - get => _prMusicBlocks; - set => SetProperty(ref _prMusicBlocks, value, "PapayaGameplay_MusicBlocks"); - } - - #endregion - - #region BITMAP IMAGES - - private BitmapImage _brMiniMapImage; - private BitmapImage _prMiniMapImage; - private BitmapImage _mapImage; - public BitmapImage MapImage - { - get => _mapImage; - set => SetProperty(ref _mapImage, value); - } - - private BitmapImage _brLayerImage; - private BitmapImage _prLayerImage; - private BitmapImage _layerImage; - public BitmapImage LayerImage - { - get => _layerImage; - set => SetProperty(ref _layerImage, value); - } - - private const int _widthHeight = 2048; - private const int _brRadius = 141000; - private const int _prRadius = 51000; - private int _mapIndex; - public int MapIndex // 0 is BR, 1 is PR - { - get => _mapIndex; - set - { - SetProperty(ref _mapIndex, value); - TriggerChange(); - } - } - - #endregion - - private const string _FIRST_BITMAP = "MapCheck"; - private readonly Dictionary[] _bitmaps; // first bitmap is the displayed map, others are overlays of the map - private readonly CUE4ParseViewModel _cue4Parse; - - public MapViewerViewModel(CUE4ParseViewModel cue4Parse) - { - _bitmaps = new[] - { - new Dictionary(), - new Dictionary() - }; - _cue4Parse = cue4Parse; - } - - public async void Initialize() - { - Utils.Typefaces ??= new Typefaces(_cue4Parse); - _textPaint.Typeface = _fillPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; - await LoadBrMiniMap(); - await LoadPrMiniMap(); - TriggerChange(); - } - - public BitmapImage GetImageToSave() => GetImageSource(GetLayerBitmap(true)); - - private SKBitmap GetLayerBitmap(bool withMap) - { - var ret = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(ret); - - foreach (var (key, value) in _bitmaps[MapIndex]) - { - if (!value.IsEnabled || !withMap && key == _FIRST_BITMAP) - continue; - - c.DrawBitmap(value.Layer, new SKRect(0, 0, _widthHeight, _widthHeight)); - } - - return ret; - } - - protected override bool SetProperty(ref T storage, T value, string propertyName = null) // don't delete, else nothing will update for some reason - { - var ret = base.SetProperty(ref storage, value, propertyName); - if (bool.TryParse(value.ToString(), out var b)) GenericToggle(propertyName, b); - return ret; - } - - private async void GenericToggle(string key, bool enabled) - { - if (_bitmaps[MapIndex].TryGetValue(key, out var layer) && layer.Layer != null) - { - layer.IsEnabled = enabled; - } - else if (enabled) // load layer - { - switch (key) - { - case "ApolloGameplay_MapPois": - case "ApolloGameplay_MapLandmarks": - case "PapayaGameplay_MapLandmarks": - await LoadQuestIndicatorData(); - break; - case "ApolloGameplay_TagsLocation": - await LoadTagsLocation(); - break; - case "ApolloGameplay_PatrolsPath": - await LoadPatrolsPath(); - break; - case "ApolloGameplay_UpgradeBenches": - await LoadUpgradeBenches(); - break; - case "ApolloGameplay_Phonebooths": - await LoadPhonebooths(); - break; - case "ApolloGameplay_VendingMachines": - await LoadBrVendingMachines(); - break; - case "ApolloGameplay_BountyBoards": - await LoadBountyBoards(); - break; - case "PapayaGameplay_CannonballGame": - await LoadCannonballGame(); - break; - case "PapayaGameplay_SkydiveGame": - await LoadSkydiveGame(); - break; - case "PapayaGameplay_ShootingTargets": - await LoadShootingTargets(); - break; - case "PapayaGameplay_ParkourGame": - await LoadWaypoint(EWaypointType.Parkour); - break; - case "PapayaGameplay_TimeTrials": - await LoadWaypoint(EWaypointType.TimeTrials); - break; - case "PapayaGameplay_VendingMachines": - await LoadPrVendingMachines(); - break; - case "PapayaGameplay_MusicBlocks": - await LoadMusicBlocks(); - break; - } - - _bitmaps[MapIndex][key].IsEnabled = true; - } - - switch (MapIndex) - { - case 0: - _brLayerImage = GetImageSource(GetLayerBitmap(false)); - break; - case 1: - _prLayerImage = GetImageSource(GetLayerBitmap(false)); - break; - } - - TriggerChange(); - } - - private BitmapImage GetImageSource(SKBitmap bitmap) - { - if (bitmap == null) return null; - using var data = bitmap.Encode(SKEncodedImageFormat.Png, 100); - using var stream = data.AsStream(); - var image = new BitmapImage(); - image.BeginInit(); - image.CacheOption = BitmapCacheOption.OnLoad; - image.StreamSource = stream; - image.EndInit(); - image.Freeze(); - return image; - } - - private void TriggerChange() - { - var layerCount = _bitmaps[_mapIndex].Count(x => x.Value.IsEnabled); - var layerString = $"{layerCount} Layer{(layerCount > 1 ? "s" : "")}"; - switch (_mapIndex) - { - case 0: - _discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Battle Royale ({layerString})"); - _mapImage = _brMiniMapImage; - _layerImage = _brLayerImage; - break; - case 1: - _discordHandler.UpdateButDontSavePresence(null, $"Map Viewer: Party Royale ({layerString})"); - _mapImage = _prMiniMapImage; - _layerImage = _prLayerImage; - break; - } - - RaisePropertyChanged(nameof(MapImage)); - RaisePropertyChanged(nameof(LayerImage)); - } - - private readonly SKPaint _textPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 26 - }; - - private readonly SKPaint _fillPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - IsStroke = true, Color = SKColors.Black, TextSize = 26, - TextAlign = SKTextAlign.Center - }; - - private readonly SKPaint _pathPaint = new() - { - IsAntialias = true, FilterQuality = SKFilterQuality.High, IsStroke = true, - Style = SKPaintStyle.Stroke, StrokeWidth = 5, Color = SKColors.Red, - ImageFilter = SKImageFilter.CreateDropShadow(4, 4, 8, 8, SKColors.Black) - }; - - private FVector2D GetMapPosition(FVector vector, int mapRadius) - { - const int wh = 2048 + 128 + 32; - var nx = (vector.Y + mapRadius) / (mapRadius * 2) * wh; - var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * wh; - return new FVector2D(nx, ny); - } - - private async Task LoadBrMiniMap() - { - if (_bitmaps[0].TryGetValue(_FIRST_BITMAP, out var brMap) && brMap.Layer != null) - return; // if map already loaded - - await _threadWorkerView.Begin(_ => - { - if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerBR.Default__UIMapManagerBR_C", out UObject mapManager) || - !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial")) return; - var midTex = mapMaterial.GetFirstTexture(); - if ((midTex?.Name ?? string.Empty).Contains("Mask")) - midTex = mapMaterial.GetTextureAtIndex(1); - - if (midTex is not UTexture2D tex) return; - _bitmaps[0][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true }; - _brMiniMapImage = GetImageSource(_bitmaps[0][_FIRST_BITMAP].Layer); - }); - } - - private async Task LoadPrMiniMap() - { - if (_bitmaps[1].TryGetValue(_FIRST_BITMAP, out var prMap) && prMap.Layer != null) - return; // if map already loaded - - await _threadWorkerView.Begin(_ => - { - if (!Utils.TryLoadObject("FortniteGame/Content/UI/IngameMap/UIMapManagerPapaya.Default__UIMapManagerPapaya_C", out UObject mapManager) || - !mapManager.TryGetValue(out UMaterial mapMaterial, "MapMaterial") || mapMaterial.GetFirstTexture() is not UTexture2D tex) return; - - _bitmaps[1][_FIRST_BITMAP] = new MapLayer { Layer = Utils.GetBitmap(tex), IsEnabled = true }; - _prMiniMapImage = GetImageSource(_bitmaps[1][_FIRST_BITMAP].Layer); - }); - } - - private async Task LoadQuestIndicatorData() - { - await _threadWorkerView.Begin(_ => - { - var poisBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - var brLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - var prLandmarksBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var pois = new SKCanvas(poisBitmap); - using var brLandmarks = new SKCanvas(brLandmarksBitmap); - using var prLandmarks = new SKCanvas(prLandmarksBitmap); - - if (Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData", out UObject indicatorData) && - indicatorData.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) - { - foreach (var poiData in challengeMapPoiData) - { - if (!poiData.TryGetValue(out FSoftObjectPath discoveryQuest, "DiscoveryQuest") || - !poiData.TryGetValue(out FText text, "Text") || string.IsNullOrEmpty(text.Text) || - !poiData.TryGetValue(out FVector worldLocation, "WorldLocation") || - !poiData.TryGetValue(out FName discoverBackend, "DiscoverObjectiveBackendName")) continue; - - var shaper = new CustomSKShaper(_textPaint.Typeface); - if (discoverBackend.Text.Contains("papaya", StringComparison.OrdinalIgnoreCase)) - { - _fillPaint.StrokeWidth = 5; - var vector = GetMapPosition(worldLocation, _prRadius); - prLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); - prLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _fillPaint); - prLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _textPaint); - } - else if (discoveryQuest.AssetPathName.Text.Contains("landmarks", StringComparison.OrdinalIgnoreCase)) - { - _fillPaint.StrokeWidth = 5; - var vector = GetMapPosition(worldLocation, _brRadius); - brLandmarks.DrawPoint(vector.X, vector.Y, _pathPaint); - brLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _fillPaint); - brLandmarks.DrawShapedText(shaper, text.Text, vector.X, vector.Y - 12.5F, _textPaint); - } - else - { - _fillPaint.StrokeWidth = 10; - var vector = GetMapPosition(worldLocation, _brRadius); - pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X, vector.Y, _fillPaint); - pois.DrawShapedText(shaper, text.Text.ToUpperInvariant(), vector.X, vector.Y, _textPaint); - } - } - } - - _bitmaps[0]["ApolloGameplay_MapPois"] = new MapLayer { Layer = poisBitmap, IsEnabled = false }; - _bitmaps[0]["ApolloGameplay_MapLandmarks"] = new MapLayer { Layer = brLandmarksBitmap, IsEnabled = false }; - _bitmaps[1]["PapayaGameplay_MapLandmarks"] = new MapLayer { Layer = prLandmarksBitmap, IsEnabled = false }; - }); - } - - private async Task LoadPatrolsPath() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var patrolsPathBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(patrolsPathBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S22_NPCLibrary"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("FortAthenaPatrolPath", StringComparison.OrdinalIgnoreCase) || - !export.TryGetValue(out FPackageIndex[] patrolPoints, "PatrolPoints")) continue; - - var displayName = export.Name["FortAthenaPatrolPath_".Length..]; - if (export.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags") && gameplayTags.GameplayTags.Length > 0) - displayName = gameplayTags.GameplayTags[0].Text["Athena.AI.SpawnLocation.Tandem.".Length..]; - - if (!Utils.TryGetPackageIndexExport(patrolPoints[0], out UObject uObject) || - !uObject.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var path = new SKPath(); - var vector = GetMapPosition(relativeLocation, _brRadius); - path.MoveTo(vector.X, vector.Y); - - for (var i = 1; i < patrolPoints.Length; i++) - { - if (!Utils.TryGetPackageIndexExport(patrolPoints[i], out uObject) || - !uObject.TryGetValue(out rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out uObject) || - !uObject.TryGetValue(out relativeLocation, "RelativeLocation")) continue; - - vector = GetMapPosition(relativeLocation, _brRadius); - path.LineTo(vector.X, vector.Y); - } - - c.DrawPath(path, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_PatrolsPath"] = new MapLayer { Layer = patrolsPathBitmap, IsEnabled = false }; - }); - } - - private async Task LoadCannonballGame() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var cannonballBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(cannonballBitmap); - - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_CannonballGame"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("BP_CannonballGame_Target_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("CannonballGame_VehicleSpawner_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var displayName = Utils.GetLocalizedResource("", "D998BEF44F051E0885C6C58565934BEA", "Cannonball"); - var vector = GetMapPosition(relativeLocation, _prRadius); - - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_CannonballGame"] = new MapLayer { Layer = cannonballBitmap, IsEnabled = false }; - }); - } - - private async Task LoadSkydiveGame() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var skydiveBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(skydiveBitmap); - - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_SkydiveGame"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("BP_Waypoint_Papaya_Skydive_Start_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !export.TryGetValue(out FText minigameActivityName, "MinigameActivityName") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(minigameActivityName.Text, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_SkydiveGame"] = new MapLayer { Layer = skydiveBitmap, IsEnabled = false }; - }); - } - - private async Task LoadShootingTargets() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(shootingTargetsBitmap); - - var bDone = false; - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_ShootingTargets"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("PapayaShootingTarget_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - if (bDone) continue; - - bDone = true; - c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText("Shooting Target", vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_ShootingTargets"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false }; - }); - } - - private async Task LoadWaypoint(EWaypointType type) - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var waypointBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(waypointBitmap); - - string file; - string name; - switch (type) - { - case EWaypointType.Parkour: - file = "PapayaGameplay_ParkourGame"; - name = "Parkour"; - break; - case EWaypointType.TimeTrials: - file = "PapayaGameplay_TimeTrials"; - name = "Time Trials"; - break; - default: - throw new ArgumentOutOfRangeException(nameof(type), type, null); - } - - var path = new SKPath(); - var exports = Utils.LoadExports($"/PapayaGameplay/LevelOverlays/{file}"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("BP_Waypoint_Parent_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject root) || - !root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - if (path.IsEmpty || export.TryGetValue(out bool startsTrial, "StartsTrial") && startsTrial) - { - path.MoveTo(vector.X, vector.Y); - c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint); - } - else if (export.TryGetValue(out bool endsTrial, "EndsTrial") && endsTrial) - { - path.LineTo(vector.X, vector.Y); - c.DrawPath(path, _pathPaint); - path = new SKPath(); - } - else path.LineTo(vector.X, vector.Y); - } - - _bitmaps[1][file] = new MapLayer { Layer = waypointBitmap, IsEnabled = false }; - }); - } - - private async Task LoadPrVendingMachines() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var set = new HashSet(); - var timeTrialsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(timeTrialsBitmap); - - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_VendingMachines"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Papaya_VendingMachine_Boat_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_BoogieBomb_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Burger_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_CrashPad_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_FishingPole_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Grappler_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Jetpack_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Blue_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintGrenade_Red_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Blue_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PaintLauncher_Red_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_PlungerBow_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Quad_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Papaya_VendingMachine_Tomato_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject root) || - !root.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var name = export.ExportType.SubstringAfter("B_Papaya_VendingMachine_").SubstringBeforeLast("_C"); - var vector = GetMapPosition(relativeLocation, _prRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - if (!set.Add(name)) continue; - - c.DrawText(name, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(name, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_VendingMachines"] = new MapLayer { Layer = timeTrialsBitmap, IsEnabled = false }; - }); - } - - private async Task LoadMusicBlocks() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var shootingTargetsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(shootingTargetsBitmap); - - var bDone = false; - var exports = Utils.LoadExports("/PapayaGameplay/LevelOverlays/PapayaGameplay_MusicBlocks"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("MusicBlock_Piano3_Papaya_C", StringComparison.OrdinalIgnoreCase)) continue; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _prRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - if (bDone) continue; - - bDone = true; - c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText("Music Blocks", vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[1]["PapayaGameplay_MusicBlocks"] = new MapLayer { Layer = shootingTargetsBitmap, IsEnabled = false }; - }); - } - - private async Task LoadUpgradeBenches() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var upgradeBenchesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(upgradeBenchesBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_UpgradeBenches"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Athena_Spawner_UpgradeStation_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = export.Name["B_Athena_Spawner_".Length..]; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_UpgradeBenches"] = new MapLayer { Layer = upgradeBenchesBitmap, IsEnabled = false }; - }); - } - - private async Task LoadPhonebooths() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var phoneboothsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(phoneboothsBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Apollo_Terrain_NPCLibrary_Stations_Phonebooths"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Athena_Spawner_Payphone_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = export.Name["B_Athena_Spawner_".Length..]; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_Phonebooths"] = new MapLayer { Layer = phoneboothsBitmap, IsEnabled = false }; - }); - } - - private async Task LoadBrVendingMachines() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var vendingMachinesBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(vendingMachinesBitmap); - - var exports = Utils.LoadExports("/NPCLibrary/LevelOverlays/Artemis_Overlay_S19_ServiceStations"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Athena_Spawner_VendingMachine_MendingOnly_C", StringComparison.OrdinalIgnoreCase) && - !export.ExportType.Equals("B_Athena_Spawner_VendingMachine_Random_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = $"{(export.ExportType.Contains("Mending") ? "MM" : "WOM")}_{export.Name["B_Athena_Spawner_VendingMachine_Random".Length..]}"; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_VendingMachines"] = new MapLayer { Layer = vendingMachinesBitmap, IsEnabled = false }; - }); - } - - private async Task LoadBountyBoards() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - var bountyBoardsBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(bountyBoardsBitmap); - - var exports = Utils.LoadExports("/Bounties/Maps/BB_Overlay_ServiceStations"); - foreach (var export in exports) - { - if (!export.ExportType.Equals("B_Bounties_Spawner_BountyBoard_C", StringComparison.OrdinalIgnoreCase)) continue; - var displayName = $"BountyBoard_{export.Name["BP_BountyBoard_C_".Length..]}"; - - if (!export.TryGetValue(out FPackageIndex rootComponent, "RootComponent") || - !Utils.TryGetPackageIndexExport(rootComponent, out UObject uObject) || - !uObject.TryGetValue(out FVector relativeLocation, "RelativeLocation")) continue; - - var vector = GetMapPosition(relativeLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_BountyBoards"] = new MapLayer { Layer = bountyBoardsBitmap, IsEnabled = false }; - }); - } - - private async Task LoadTagsLocation() - { - await _threadWorkerView.Begin(_ => - { - _fillPaint.StrokeWidth = 5; - if (!Utils.TryLoadObject("FortniteGame/Content/Quests/QuestTagToLocationDataRows.QuestTagToLocationDataRows", out UDataTable locationData)) - return; - - var tagsLocationBitmap = new SKBitmap(_widthHeight, _widthHeight, SKColorType.Rgba8888, SKAlphaType.Premul); - using var c = new SKCanvas(tagsLocationBitmap); - - foreach (var (key, uObject) in locationData.RowMap) - { - if (key.Text.StartsWith("Athena.Location.POI", StringComparison.OrdinalIgnoreCase) || - key.Text.StartsWith("Athena.Location.Unnamed", StringComparison.OrdinalIgnoreCase) || - key.Text.Contains(".Tandem.", StringComparison.OrdinalIgnoreCase) || - !uObject.TryGetValue(out FVector worldLocation, "WorldLocation")) continue; - - var parts = key.Text.Split('.'); - var displayName = parts[^2]; - if (!int.TryParse(parts[^1], out var _)) - displayName += " " + parts[^1]; - - var vector = GetMapPosition(worldLocation, _brRadius); - c.DrawPoint(vector.X, vector.Y, _pathPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _fillPaint); - c.DrawText(displayName, vector.X, vector.Y - 12.5F, _textPaint); - } - - _bitmaps[0]["ApolloGameplay_TagsLocation"] = new MapLayer { Layer = tagsLocationBitmap, IsEnabled = false }; - }); - } -} diff --git a/FModel/ViewModels/SettingsViewModel.cs b/FModel/ViewModels/SettingsViewModel.cs index e74a96fb..4d5f2aa4 100644 --- a/FModel/ViewModels/SettingsViewModel.cs +++ b/FModel/ViewModels/SettingsViewModel.cs @@ -1,26 +1,21 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Threading.Tasks; +using System.Linq; using CUE4Parse.UE4.Assets.Exports.Texture; -using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.UE4.Objects.Core.Serialization; using CUE4Parse.UE4.Versions; using CUE4Parse_Conversion.Meshes; using CUE4Parse_Conversion.Textures; using CUE4Parse.UE4.Assets.Exports.Material; -using FModel.Extensions; using FModel.Framework; using FModel.Services; using FModel.Settings; -using FModel.ViewModels.ApiEndpoints.Models; namespace FModel.ViewModels; public class SettingsViewModel : ViewModel { - private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; - private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; private readonly DiscordHandler _discordHandler = DiscordService.DiscordHandler; private bool _useCustomOutputFolders; @@ -37,17 +32,6 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _selectedUpdateMode, value); } - private string _selectedPreset; - public string SelectedPreset - { - get => _selectedPreset; - set - { - SetProperty(ref _selectedPreset, value); - RaisePropertyChanged("EnableElements"); - } - } - private ETexturePlatform _selectedUePlatform; public ETexturePlatform SelectedUePlatform { @@ -62,36 +46,36 @@ public class SettingsViewModel : ViewModel set => SetProperty(ref _selectedUeGame, value); } - private List _selectedCustomVersions; - public List SelectedCustomVersions + private IList _selectedCustomVersions; + public IList SelectedCustomVersions { get => _selectedCustomVersions; set => SetProperty(ref _selectedCustomVersions, value); } - private Dictionary _selectedOptions; - public Dictionary SelectedOptions + private IDictionary _selectedOptions; + public IDictionary SelectedOptions { get => _selectedOptions; set => SetProperty(ref _selectedOptions, value); } - private Dictionary> _selectedMapStructTypes; - public Dictionary> SelectedMapStructTypes + private IDictionary> _selectedMapStructTypes; + public IDictionary> SelectedMapStructTypes { get => _selectedMapStructTypes; set => SetProperty(ref _selectedMapStructTypes, value); } - private FEndpoint _aesEndpoint; - public FEndpoint AesEndpoint + private EndpointSettings _aesEndpoint; + public EndpointSettings AesEndpoint { get => _aesEndpoint; set => SetProperty(ref _aesEndpoint, value); } - private FEndpoint _mappingEndpoint; - public FEndpoint MappingEndpoint + private EndpointSettings _mappingEndpoint; + public EndpointSettings MappingEndpoint { get => _mappingEndpoint; set => SetProperty(ref _mappingEndpoint, value); @@ -168,7 +152,6 @@ public class SettingsViewModel : ViewModel } public ReadOnlyObservableCollection UpdateModes { get; private set; } - public ObservableCollection Presets { get; private set; } public ReadOnlyObservableCollection UeGames { get; private set; } public ReadOnlyObservableCollection AssetLanguages { get; private set; } public ReadOnlyObservableCollection AesReloads { get; private set; } @@ -182,10 +165,6 @@ public class SettingsViewModel : ViewModel public ReadOnlyObservableCollection TextureExportFormats { get; private set; } public ReadOnlyObservableCollection Platforms { get; private set; } - public bool EnableElements => SelectedPreset == Constants._NO_PRESET_TRIGGER; - - private readonly FGame _game; - private Game _gamePreset; private string _outputSnapshot; private string _rawDataSnapshot; private string _propertiesSnapshot; @@ -194,12 +173,11 @@ public class SettingsViewModel : ViewModel private string _modelSnapshot; private string _gameSnapshot; private EUpdateMode _updateModeSnapshot; - private string _presetSnapshot; private ETexturePlatform _uePlatformSnapshot; private EGame _ueGameSnapshot; - private List _customVersionsSnapshot; - private Dictionary _optionsSnapshot; - private Dictionary> _mapStructTypesSnapshot; + private IList _customVersionsSnapshot; + private IDictionary _optionsSnapshot; + private IDictionary> _mapStructTypesSnapshot; private ELanguage _assetLanguageSnapshot; private ECompressedAudio _compressedAudioSnapshot; private EIconStyle _cosmeticStyleSnapshot; @@ -211,9 +189,9 @@ public class SettingsViewModel : ViewModel private bool _mappingsUpdate = false; - public SettingsViewModel(FGame game) + public SettingsViewModel() { - _game = game; + } public void Initialize() @@ -226,33 +204,19 @@ public class SettingsViewModel : ViewModel _modelSnapshot = UserSettings.Default.ModelDirectory; _gameSnapshot = UserSettings.Default.GameDirectory; _updateModeSnapshot = UserSettings.Default.UpdateMode; - _presetSnapshot = UserSettings.Default.Presets[_game]; - _uePlatformSnapshot = UserSettings.Default.OverridedPlatform; - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(_gameSnapshot, out var settings)) - { - _ueGameSnapshot = settings.OverridedGame; - _customVersionsSnapshot = settings.OverridedCustomVersions; - _optionsSnapshot = settings.OverridedOptions; - _mapStructTypesSnapshot = settings.OverridedMapStructTypes; - } - else - { - _ueGameSnapshot = UserSettings.Default.OverridedGame[_game]; - _customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game]; - _optionsSnapshot = UserSettings.Default.OverridedOptions[_game]; - _mapStructTypesSnapshot = UserSettings.Default.OverridedMapStructTypes[_game]; - } + _uePlatformSnapshot = UserSettings.Default.CurrentDir.TexturePlatform; + _ueGameSnapshot = UserSettings.Default.CurrentDir.UeVersion; + _customVersionsSnapshot = UserSettings.Default.CurrentDir.Versioning.CustomVersions; + _optionsSnapshot = UserSettings.Default.CurrentDir.Versioning.Options; + _mapStructTypesSnapshot = UserSettings.Default.CurrentDir.Versioning.MapStructTypes; - if (UserSettings.Default.CustomEndpoints.TryGetValue(_game, out var endpoints)) + AesEndpoint = UserSettings.Default.CurrentDir.Endpoints[0]; + MappingEndpoint = UserSettings.Default.CurrentDir.Endpoints[1]; + MappingEndpoint.PropertyChanged += (_, args) => { - AesEndpoint = endpoints[0]; - MappingEndpoint = endpoints[1]; - MappingEndpoint.PropertyChanged += (_, args) => - { - if (!_mappingsUpdate) - _mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath"; - }; - } + if (!_mappingsUpdate) + _mappingsUpdate = args.PropertyName is "Overwrite" or "FilePath"; + }; _assetLanguageSnapshot = UserSettings.Default.AssetLanguage; _compressedAudioSnapshot = UserSettings.Default.CompressedAudioMode; @@ -264,7 +228,6 @@ public class SettingsViewModel : ViewModel _textureExportFormatSnapshot = UserSettings.Default.TextureExportFormat; SelectedUpdateMode = _updateModeSnapshot; - SelectedPreset = _presetSnapshot; SelectedUePlatform = _uePlatformSnapshot; SelectedUeGame = _ueGameSnapshot; SelectedCustomVersions = _customVersionsSnapshot; @@ -282,7 +245,6 @@ public class SettingsViewModel : ViewModel SelectedDiscordRpc = UserSettings.Default.DiscordRpc; UpdateModes = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUpdateModes())); - Presets = new ObservableCollection(EnumeratePresets()); UeGames = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUeGames())); AssetLanguages = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAssetLanguages())); AesReloads = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateAesReloads())); @@ -297,53 +259,6 @@ public class SettingsViewModel : ViewModel Platforms = new ReadOnlyObservableCollection(new ObservableCollection(EnumerateUePlatforms())); } - public async Task InitPresets(string gameName) - { - await _threadWorkerView.Begin(cancellationToken => - { - if (string.IsNullOrEmpty(gameName)) return; - _gamePreset = _apiEndpointView.FModelApi.GetGames(cancellationToken, gameName); - }); - - if (_gamePreset?.Versions == null) return; - foreach (var version in _gamePreset.Versions.Keys) - { - Presets.Add(version); - } - } - - public void SwitchPreset(string key) - { - if (_gamePreset?.Versions == null || !_gamePreset.Versions.TryGetValue(key, out var version)) return; - SelectedUeGame = version.GameEnum.ToEnum(EGame.GAME_UE4_LATEST); - - SelectedCustomVersions = new List(); - foreach (var (guid, v) in version.CustomVersions) - { - SelectedCustomVersions.Add(new FCustomVersion { Key = new FGuid(guid), Version = v }); - } - - SelectedOptions = new Dictionary(); - foreach (var (k, v) in version.Options) - { - SelectedOptions[k] = v; - } - - SelectedMapStructTypes = new Dictionary>(); - foreach (var (k, v) in version.MapStructTypes) - { - SelectedMapStructTypes[k] = v; - } - } - - public void ResetPreset() - { - SelectedUeGame = _ueGameSnapshot; - SelectedCustomVersions = _customVersionsSnapshot; - SelectedOptions = _optionsSnapshot; - SelectedMapStructTypes = _mapStructTypesSnapshot; - } - public bool Save(out List whatShouldIDo) { var restart = false; @@ -369,22 +284,11 @@ public class SettingsViewModel : ViewModel restart = true; UserSettings.Default.UpdateMode = SelectedUpdateMode; - UserSettings.Default.Presets[_game] = SelectedPreset; - UserSettings.Default.OverridedPlatform = SelectedUePlatform; - if (_game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory)) - { - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame; - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions; - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions; - UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedMapStructTypes = SelectedMapStructTypes; - } - else - { - UserSettings.Default.OverridedGame[_game] = SelectedUeGame; - UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions; - UserSettings.Default.OverridedOptions[_game] = SelectedOptions; - UserSettings.Default.OverridedMapStructTypes[_game] = SelectedMapStructTypes; - } + UserSettings.Default.CurrentDir.UeVersion = SelectedUeGame; + UserSettings.Default.CurrentDir.TexturePlatform = SelectedUePlatform; + UserSettings.Default.CurrentDir.Versioning.CustomVersions = SelectedCustomVersions; + UserSettings.Default.CurrentDir.Versioning.Options = SelectedOptions; + UserSettings.Default.CurrentDir.Versioning.MapStructTypes = SelectedMapStructTypes; UserSettings.Default.AssetLanguage = SelectedAssetLanguage; UserSettings.Default.CompressedAudioMode = SelectedCompressedAudio; @@ -404,11 +308,11 @@ public class SettingsViewModel : ViewModel } private IEnumerable EnumerateUpdateModes() => Enum.GetValues(); - private IEnumerable EnumeratePresets() - { - yield return Constants._NO_PRESET_TRIGGER; - } - private IEnumerable EnumerateUeGames() => Enum.GetValues(); + private IEnumerable EnumerateUeGames() + => Enum.GetValues() + .GroupBy(value => (int)value) + .Select(group => group.First()) + .OrderBy(value => (int)value == ((int)value & ~0xF)); private IEnumerable EnumerateAssetLanguages() => Enum.GetValues(); private IEnumerable EnumerateAesReloads() => Enum.GetValues(); private IEnumerable EnumerateDiscordRpcs() => Enum.GetValues(); diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 064ed0cc..461dd34e 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -228,8 +228,8 @@ public class TabItem : ViewModel }); } - public void AddImage(UTexture2D texture, bool save, bool updateUi) - => AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform), save, updateUi); + public void AddImage(UTexture texture, bool save, bool updateUi) + => AddImage(texture.Name, texture.RenderNearestNeighbor, texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform), save, updateUi); public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi) { diff --git a/FModel/Views/About.xaml b/FModel/Views/About.xaml index af33b59a..ca498671 100644 --- a/FModel/Views/About.xaml +++ b/FModel/Views/About.xaml @@ -4,7 +4,8 @@ xmlns:local="clr-namespace:FModel" xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" WindowStartupLocation="CenterScreen" ResizeMode="NoResize" - IconVisibility="Collapsed" Width="500" SizeToContent="Height"> + IconVisibility="Collapsed" Width="500" SizeToContent="Height" + Loaded="OnLoaded"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -