From c403453cc30b8cc30d86cc25f7c6d10a8a02c82a Mon Sep 17 00:00:00 2001 From: Valentin Date: Sat, 22 May 2021 22:10:08 +0200 Subject: [PATCH] FModel v4.0 --- .github/ISSUE_TEMPLATE/bug-report.md | 21 - .github/ISSUE_TEMPLATE/feature_request.md | 17 - .github/workflows/main.yml | 32 + .gitignore | 54 +- .gitmodules | 3 + CUE4Parse | 1 + FModel.sln | 25 - FModel/App.xaml | 11 +- FModel/App.xaml.cs | 144 +- FModel/Constants.cs | 63 + FModel/Creator/Bases/BB/BaseBreakersIcon.cs | 43 + FModel/Creator/Bases/BaseBBDefinition.cs | 60 - FModel/Creator/Bases/BaseBundle.cs | 102 - FModel/Creator/Bases/BaseGCosmetic.cs | 106 - FModel/Creator/Bases/BaseIcon.cs | 164 - FModel/Creator/Bases/BaseItemAccess.cs | 138 - FModel/Creator/Bases/BaseMapUIData.cs | 96 - FModel/Creator/Bases/BaseOffer.cs | 93 - FModel/Creator/Bases/BaseOfferMaterial.cs | 121 - FModel/Creator/Bases/BasePlaylist.cs | 60 - FModel/Creator/Bases/BaseSeason.cs | 318 -- FModel/Creator/Bases/BaseUIData.cs | 177 - FModel/Creator/Bases/BaseUserOption.cs | 201 - FModel/Creator/Bases/FN/BaseBundle.cs | 141 + FModel/Creator/Bases/FN/BaseCommunity.cs | 305 ++ FModel/Creator/Bases/FN/BaseIcon.cs | 297 ++ FModel/Creator/Bases/FN/BaseIconStats.cs | 319 ++ .../Creator/Bases/FN/BaseItemAccessToken.cs | 100 + .../Creator/Bases/FN/BaseMaterialInstance.cs | 83 + FModel/Creator/Bases/FN/BaseMtxOffer.cs | 85 + FModel/Creator/Bases/FN/BasePlaylist.cs | 76 + FModel/Creator/Bases/FN/BaseQuest.cs | 259 ++ FModel/Creator/Bases/FN/BaseSeason.cs | 174 + FModel/Creator/Bases/FN/BaseSeries.cs | 27 + FModel/Creator/Bases/FN/BaseUserControl.cs | 193 + FModel/Creator/Bases/FN/Reward.cs | 153 + FModel/Creator/Bases/IBase.cs | 17 - FModel/Creator/Bases/SB/BaseDivision.cs | 48 + FModel/Creator/Bases/SB/BaseLeague.cs | 55 + FModel/Creator/Bases/SB/BaseSpellIcon.cs | 98 + FModel/Creator/Bases/UCreator.cs | 230 ++ FModel/Creator/Bundles/CompletionReward.cs | 60 - FModel/Creator/Bundles/Header.cs | 106 - FModel/Creator/Bundles/HeaderStyle.cs | 139 - FModel/Creator/Bundles/Quest.cs | 94 - FModel/Creator/Bundles/QuestStyle.cs | 241 -- FModel/Creator/Bundles/Reward.cs | 155 - FModel/Creator/Creator.cs | 496 --- FModel/Creator/CreatorPackage.cs | 202 + FModel/Creator/Icons/DisplayAssetImage.cs | 104 - FModel/Creator/Icons/LargeSmallImage.cs | 69 - FModel/Creator/Icons/UserFacingFlag.cs | 73 - FModel/Creator/Icons/Watermark.cs | 31 - FModel/Creator/Rarities/EFortRarity.cs | 15 - FModel/Creator/Rarities/Rarity.cs | 204 - FModel/Creator/Rarities/Serie.cs | 43 - FModel/Creator/Stats/Statistic.cs | 12 - FModel/Creator/Stats/Statistics.cs | 231 -- FModel/Creator/Texts/ETextSide.cs | 9 - FModel/Creator/Texts/GameplayTag.cs | 73 - FModel/Creator/Texts/Helper.cs | 161 - FModel/Creator/Texts/Text.cs | 300 -- FModel/Creator/Texts/Typefaces.cs | 243 -- FModel/Creator/Typefaces.cs | 224 ++ FModel/Creator/Utils.cs | 378 +- FModel/Discord/DiscordIntegration.cs | 70 - FModel/Enums.cs | 179 +- FModel/Extensions/AvalonExtensions.cs | 47 + FModel/Extensions/CollectionExtensions.cs | 37 + FModel/Extensions/EnumExtensions.cs | 84 + FModel/Extensions/StreamExtensions.cs | 31 + .../StringExtensions.cs} | 132 +- FModel/FModel.csproj | 309 +- FModel/FModel.sln | 43 + FModel/Framework/AsyncQueue.cs | 33 + FModel/Framework/Command.cs | 19 + FModel/Framework/CustomSKShaper.cs | 96 + FModel/Framework/FullyObservableCollection.cs | 117 + FModel/Framework/Hotkey.cs | 50 + FModel/Framework/JsonNetSerializer.cs | 43 + FModel/Framework/RangeObservableCollection.cs | 42 + FModel/Framework/ViewModel.cs | 76 + FModel/Framework/ViewModelCommand.cs | 50 + FModel/Globals.cs | 92 - FModel/Grabber/Aes/AesData.cs | 37 - FModel/Grabber/Aes/AesGrabber.cs | 85 - FModel/Grabber/Cdn/CdnData.cs | 53 - FModel/Grabber/Cdn/CdnDataGrabber.cs | 102 - FModel/Grabber/Manifests/ManifestGrabber.cs | 38 - .../Grabber/Manifests/ValorantAPIManifest.cs | 780 ---- FModel/Grabber/Mappings/MappingsData.cs | 50 - FModel/Grabber/Mappings/MappingsGrabber.cs | 57 - FModel/Grabber/Paks/InstallsJson.cs | 11 - FModel/Grabber/Paks/LauncherDat.cs | 14 - FModel/Grabber/Paks/LauncherSettings.cs | 7 - FModel/Grabber/Paks/PaksGrabber.cs | 289 -- FModel/Helper.cs | 88 + FModel/Logger/DebugHelper.cs | 41 - FModel/Logger/Logger.cs | 178 - FModel/MainWindow.xaml | 1169 +++--- FModel/MainWindow.xaml.cs | 728 +--- FModel/PakReader/AESDecryptor.cs | 61 - FModel/PakReader/BinaryHelper.cs | 63 - FModel/PakReader/IO/EIoChunkType.cs | 20 - FModel/PakReader/IO/EIoContainerFlags.cs | 11 - FModel/PakReader/IO/FArc.cs | 16 - FModel/PakReader/IO/FContainerHeader.cs | 37 - FModel/PakReader/IO/FExportBundleEntry.cs | 25 - FModel/PakReader/IO/FExportDesc.cs | 16 - FModel/PakReader/IO/FExportMapEntry.cs | 37 - .../PakReader/IO/FFileIoStoreContainerFile.cs | 19 - FModel/PakReader/IO/FFileIoStoreReader.cs | 404 -- FModel/PakReader/IO/FFragment.cs | 26 - FModel/PakReader/IO/FImportDesc.cs | 11 - FModel/PakReader/IO/FIoChunkHash.cs | 14 - FModel/PakReader/IO/FIoChunkId.cs | 64 - FModel/PakReader/IO/FIoContainerId.cs | 14 - FModel/PakReader/IO/FIoDirectoryIndexEntry.cs | 20 - .../PakReader/IO/FIoDirectoryIndexHandle.cs | 38 - .../PakReader/IO/FIoDirectoryIndexResource.cs | 34 - FModel/PakReader/IO/FIoFileIndexEntry.cs | 18 - FModel/PakReader/IO/FIoGlobalData.cs | 133 - FModel/PakReader/IO/FIoOffsetAndLength.cs | 26 - FModel/PakReader/IO/FIoStoreEntry.cs | 59 - .../IO/FIoStoreTocCompressedBlockEntry.cs | 69 - FModel/PakReader/IO/FIoStoreTocEntryMeta.cs | 18 - .../PakReader/IO/FIoStoreTocEntryMetaFlags.cs | 9 - FModel/PakReader/IO/FIoStoreTocHeader.cs | 71 - FModel/PakReader/IO/FIoStoreTocResource.cs | 114 - FModel/PakReader/IO/FIterator.cs | 74 - FModel/PakReader/IO/FMappedName.cs | 71 - FModel/PakReader/IO/FMinimalName.cs | 22 - FModel/PakReader/IO/FNameEntryId.cs | 45 - FModel/PakReader/IO/FPackageDesc.cs | 18 - FModel/PakReader/IO/FPackageId.cs | 21 - FModel/PakReader/IO/FPackageLocation.cs | 10 - FModel/PakReader/IO/FPackageObjectIndex.cs | 71 - FModel/PakReader/IO/FPackageStoreEntry.cs | 36 - FModel/PakReader/IO/FPackageSummary.cs | 39 - FModel/PakReader/IO/FScriptObjectDesc.cs | 20 - FModel/PakReader/IO/FScriptObjectEntry.cs | 20 - FModel/PakReader/IO/FSerializedNameHeader.cs | 17 - FModel/PakReader/IO/FUnversionedHeader.cs | 65 - FModel/PakReader/IO/FUnversionedProperty.cs | 29 - FModel/PakReader/IO/IoPackage.cs | 72 - FModel/PakReader/LICENSE | 21 - FModel/PakReader/LocMetaReader.cs | 48 - FModel/PakReader/LocResReader.cs | 160 - FModel/PakReader/Package.cs | 64 - FModel/PakReader/Pak/DefaultPakFilter.cs | 7 - FModel/PakReader/Pak/IPakFilter.cs | 7 - FModel/PakReader/Pak/PakFileReader.cs | 516 --- FModel/PakReader/Pak/PakFilter.cs | 63 - FModel/PakReader/Pak/PakIndex.cs | 190 - FModel/PakReader/Pak/PakPackage.cs | 96 - FModel/PakReader/Parsers/Class/IUExport.cs | 70 - .../Parsers/Class/UAkMediaAssetData.cs | 40 - FModel/PakReader/Parsers/Class/UCurveTable.cs | 80 - FModel/PakReader/Parsers/Class/UDataTable.cs | 113 - FModel/PakReader/Parsers/Class/UFontFace.cs | 45 - FModel/PakReader/Parsers/Class/UObject.cs | 138 - FModel/PakReader/Parsers/Class/USoundWave.cs | 103 - .../PakReader/Parsers/Class/UStringTable.cs | 37 - FModel/PakReader/Parsers/Class/UTexture2D.cs | 86 - FModel/PakReader/Parsers/IoPackageReader.cs | 300 -- .../PakReader/Parsers/LegacyPackageReader.cs | 123 - .../Objects/EAnimationCompressionFormat.cs | 14 - .../Parsers/Objects/EAnimationKeyFormat.cs | 10 - .../Objects/EAssetRegistryDependencyType.cs | 20 - .../Parsers/Objects/EBulkDataFlags.cs | 54 - .../Parsers/Objects/ECompressionFlags.cs | 33 - .../Parsers/Objects/ECurveTableMode.cs | 12 - .../Parsers/Objects/EDateTimeStyle.cs | 12 - .../Parsers/Objects/EDecompressionType.cs | 15 - .../Parsers/Objects/EExportFilterFlags.cs | 9 - .../Parsers/Objects/EExpressionType.cs | 12 - .../Parsers/Objects/EFormatArgumentType.cs | 14 - .../Parsers/Objects/EInitializationMode.cs | 12 - .../Parsers/Objects/ELightingBuildQuality.cs | 11 - .../PakReader/Parsers/Objects/EObjectFlags.cs | 56 - .../Parsers/Objects/EPackageFlags.cs | 38 - .../PakReader/Parsers/Objects/EPakVersion.cs | 24 - .../PakReader/Parsers/Objects/EPixelFormat.cs | 76 - .../Parsers/Objects/ERangeBoundType.cs | 9 - .../Objects/ERichCurveCompressionFormat.cs | 17 - .../Objects/ERichCurveExtrapolation.cs | 18 - .../Parsers/Objects/ERichCurveInterpMode.cs | 15 - .../ERichCurveKeyTimeCompressionFormat.cs | 11 - .../Parsers/Objects/ERichCurveTangentMode.cs | 15 - .../Objects/ERichCurveTangentWeightMode.cs | 15 - .../PakReader/Parsers/Objects/ESoundGroup.cs | 32 - .../Objects/ESoundWaveLoadingBehavior.cs | 18 - .../Objects/ESoundWavePrecacheState.cs | 10 - .../Objects/ESoundwaveSampleRateSettings.cs | 13 - .../Objects/EStringTableLoadingPhase.cs | 12 - FModel/PakReader/Parsers/Objects/ETextFlag.cs | 11 - .../PakReader/Parsers/Objects/ETextGender.cs | 10 - .../Parsers/Objects/ETextHistoryType.cs | 22 - .../Parsers/Objects/ETransformType.cs | 11 - .../EUnrealEngineObjectLicenseeUE4Version.cs | 10 - .../Objects/EUnrealEngineObjectUE4Version.cs | 629 --- .../Parsers/Objects/EVirtualTextureCodec.cs | 14 - .../Parsers/Objects/FAkMediaDataChunk.cs | 16 - .../PakReader/Parsers/Objects/FAssetData.cs | 53 - .../Objects/FAssetDataTagMapSharedView.cs | 19 - .../Parsers/Objects/FAssetIdentifier.cs | 53 - .../Parsers/Objects/FAssetPackageData.cs | 37 - .../Parsers/Objects/FAssetRegistryState.cs | 121 - .../Parsers/Objects/FAssetRegistryVersion.cs | 32 - .../Parsers/Objects/FBodyInstance.cs | 12 - FModel/PakReader/Parsers/Objects/FBox.cs | 19 - FModel/PakReader/Parsers/Objects/FBox2D.cs | 19 - .../Parsers/Objects/FByteBulkData.cs | 52 - .../Parsers/Objects/FByteBulkDataHeader.cs | 38 - .../PakReader/Parsers/Objects/FChunkHeader.cs | 16 - FModel/PakReader/Parsers/Objects/FColor.cs | 23 - .../Parsers/Objects/FColorMaterialInput.cs | 18 - .../Parsers/Objects/FCompressedChunk.cs | 20 - .../Parsers/Objects/FCompressedOffsetData.cs | 14 - .../Parsers/Objects/FCompressedSegment.cs | 22 - .../Parsers/Objects/FCustomVersion.cs | 17 - .../Objects/FCustomVersionContainer.cs | 14 - FModel/PakReader/Parsers/Objects/FDateTime.cs | 20 - .../PakReader/Parsers/Objects/FDependsNode.cs | 68 - .../Parsers/Objects/FDictionaryHeader.cs | 30 - .../Parsers/Objects/FEngineVersion.cs | 34 - FModel/PakReader/Parsers/Objects/FEntry.cs | 19 - .../Objects/FEvaluationTreeEntryHandle.cs | 12 - .../PakReader/Parsers/Objects/FFactChunk.cs | 18 - .../PakReader/Parsers/Objects/FFieldClass.cs | 88 - .../Parsers/Objects/FFormatArgumentData.cs | 35 - .../Parsers/Objects/FFormatArgumentValue.cs | 37 - .../Parsers/Objects/FFormatContainer.cs | 16 - .../PakReader/Parsers/Objects/FFrameNumber.cs | 12 - .../Parsers/Objects/FGameplayTagContainer.cs | 54 - .../Parsers/Objects/FGenerationInfo.cs | 16 - FModel/PakReader/Parsers/Objects/FGuid.cs | 65 - FModel/PakReader/Parsers/Objects/FIntPoint.cs | 16 - .../FLevelSequenceLegacyObjectReference.cs | 16 - .../FLevelSequenceObjectReferenceMap.cs | 18 - .../PakReader/Parsers/Objects/FLinearColor.cs | 24 - FModel/PakReader/Parsers/Objects/FMD5Hash.cs | 14 - .../Parsers/Objects/FMaterialInput.cs | 37 - .../Objects/FMovieSceneEvaluationKey.cs | 16 - .../Objects/FMovieSceneEvaluationTemplate.cs | 13 - .../Objects/FMovieSceneEvaluationTree.cs | 14 - .../Objects/FMovieSceneEvaluationTreeNode.cs | 18 - .../FMovieSceneEvaluationTreeNodeHandle.cs | 14 - .../Objects/FMovieSceneFloatChannel.cs | 16 - .../Parsers/Objects/FMovieSceneFrameRange.cs | 12 - .../Parsers/Objects/FMovieSceneSegment.cs | 25 - FModel/PakReader/Parsers/Objects/FName.cs | 43 - .../Parsers/Objects/FNameEntrySerialized.cs | 61 - .../Objects/FNameTableArchiveReader.cs | 40 - .../Objects/FNavAgentSelectorCustomization.cs | 12 - .../Parsers/Objects/FObjectExport.cs | 104 - .../Parsers/Objects/FObjectImport.cs | 23 - .../Parsers/Objects/FObjectResource.cs | 19 - .../Parsers/Objects/FOodleCompressedData.cs | 21 - .../Objects/FOodleDictionaryArchive.cs | 16 - .../Parsers/Objects/FPackageFileSummary.cs | 197 - .../Parsers/Objects/FPackageIndex.cs | 92 - .../Parsers/Objects/FPakCompressedBlock.cs | 25 - .../Parsers/Objects/FPakDirectoryEntry.cs | 17 - FModel/PakReader/Parsers/Objects/FPakEntry.cs | 239 -- FModel/PakReader/Parsers/Objects/FPakInfo.cs | 97 - .../Parsers/Objects/FPathHashIndexEntry.cs | 17 - .../Parsers/Objects/FPerPlatformFloat.cs | 14 - .../Parsers/Objects/FPerPlatformInt.cs | 14 - .../Parsers/Objects/FPrimaryAssetId.cs | 14 - .../Parsers/Objects/FPrimaryAssetType.cs | 12 - .../PakReader/Parsers/Objects/FPropertyTag.cs | 120 - FModel/PakReader/Parsers/Objects/FQuat.cs | 20 - .../Parsers/Objects/FRichCurveKey.cs | 37 - .../Parsers/Objects/FRiffWaveHeader.cs | 18 - FModel/PakReader/Parsers/Objects/FRotator.cs | 18 - FModel/PakReader/Parsers/Objects/FSHAHash.cs | 14 - .../PakReader/Parsers/Objects/FSampleChunk.cs | 36 - .../PakReader/Parsers/Objects/FSampleLoop.cs | 24 - .../Objects/FSectionEvaluationDataTree.cs | 12 - .../Parsers/Objects/FSimpleCurveKey.cs | 16 - ...SkeletalMeshAreaWeightedTriangleSampler.cs | 20 - .../FSkeletalMeshSamplingLODBuiltData.cs | 13 - .../PakReader/Parsers/Objects/FSmartName.cs | 13 - .../Parsers/Objects/FSoftObjectPath.cs | 27 - .../Parsers/Objects/FStreamedAudioChunk.cs | 29 - .../PakReader/Parsers/Objects/FStringTable.cs | 29 - .../Parsers/Objects/FStripDataFlags.cs | 36 - FModel/PakReader/Parsers/Objects/FText.cs | 91 - .../PakReader/Parsers/Objects/FTextHistory.cs | 8 - .../Objects/FTextHistoryArgumentDataFormat.cs | 36 - .../Parsers/Objects/FTextHistoryAsDate.cs | 37 - .../Parsers/Objects/FTextHistoryAsTime.cs | 34 - .../Parsers/Objects/FTextHistoryBase.cs | 22 - .../Parsers/Objects/FTextHistoryDateTime.cs | 39 - .../Objects/FTextHistoryFormatNumber.cs | 33 - .../Parsers/Objects/FTextHistoryNone.cs | 24 - .../Objects/FTextHistoryOrderedFormat.cs | 40 - .../Objects/FTextHistoryStringTableEntry.cs | 30 - .../Parsers/Objects/FTextHistoryTransform.cs | 30 - FModel/PakReader/Parsers/Objects/FTextKey.cs | 22 - .../Parsers/Objects/FTexture2DMipMap.cs | 21 - .../Parsers/Objects/FTexturePlatformData.cs | 40 - .../Parsers/Objects/FUniqueObjectGuid.cs | 13 - FModel/PakReader/Parsers/Objects/FVector.cs | 18 - FModel/PakReader/Parsers/Objects/FVector2D.cs | 16 - FModel/PakReader/Parsers/Objects/FVector4.cs | 24 - .../Parsers/Objects/FVectorMaterialInput.cs | 18 - .../Objects/FVirtualTextureBuiltData.cs | 73 - .../Objects/FVirtualTextureDataChunk.cs | 30 - FModel/PakReader/Parsers/Objects/IUStruct.cs | 5 - .../Objects/TEvaluationTreeEntryContainer.cs | 14 - .../Objects/TMovieSceneEvaluationTree.cs | 14 - FModel/PakReader/Parsers/Objects/TRange.cs | 14 - .../PakReader/Parsers/Objects/TRangeBound.cs | 14 - .../Parsers/Objects/UScriptStruct.cs | 127 - FModel/PakReader/Parsers/OodleStream.cs | 81 - FModel/PakReader/Parsers/PackageReader.cs | 69 - .../Parsers/PropertyTagData/ArrayProperty.cs | 67 - .../PropertyTagData/AssetObjectProperty.cs | 17 - .../Parsers/PropertyTagData/BaseProperty.cs | 127 - .../Parsers/PropertyTagData/BoolProperty.cs | 33 - .../Parsers/PropertyTagData/ByteProperty.cs | 27 - .../PropertyTagData/DelegateProperty.cs | 25 - .../Parsers/PropertyTagData/DoubleProperty.cs | 17 - .../Parsers/PropertyTagData/EnumProperty.cs | 40 - .../PropertyTagData/FieldPathProperty.cs | 19 - .../Parsers/PropertyTagData/FloatProperty.cs | 17 - .../Parsers/PropertyTagData/Int16Property.cs | 17 - .../Parsers/PropertyTagData/Int64Property.cs | 17 - .../Parsers/PropertyTagData/Int8Property.cs | 17 - .../Parsers/PropertyTagData/IntProperty.cs | 17 - .../PropertyTagData/InterfaceProperty.cs | 18 - .../PropertyTagData/LazyObjectProperty.cs | 13 - .../Parsers/PropertyTagData/MapProperty.cs | 73 - .../MulticastDelegateProperty.cs | 13 - .../Parsers/PropertyTagData/NameProperty.cs | 19 - .../Parsers/PropertyTagData/ObjectProperty.cs | 20 - .../Parsers/PropertyTagData/SetProperty.cs | 65 - .../PropertyTagData/SoftObjectProperty.cs | 22 - .../Parsers/PropertyTagData/StrProperty.cs | 17 - .../Parsers/PropertyTagData/StructProperty.cs | 35 - .../Parsers/PropertyTagData/TextProperty.cs | 19 - .../Parsers/PropertyTagData/UInt16Property.cs | 17 - .../Parsers/PropertyTagData/UInt32Property.cs | 17 - .../Parsers/PropertyTagData/UInt64Property.cs | 17 - FModel/PakReader/ReaderEntry.cs | 64 - FModel/PakReader/ReaderExtensions.cs | 60 - FModel/PakReader/Textures/ASTC/ASTCDecoder.cs | 1338 ------- FModel/PakReader/Textures/ASTC/ASTCPixel.cs | 133 - .../PakReader/Textures/ASTC/BitArrayStream.cs | 120 - .../PakReader/Textures/ASTC/IntegerEncoded.cs | 268 -- FModel/PakReader/Textures/BC/BCDecoder.cs | 117 - FModel/PakReader/Textures/BC/Detex.cs | 106 - .../BC/DetexCompressedTextureFormatIndex.cs | 39 - .../PakReader/Textures/BC/DetexPixelFormat.cs | 361 -- .../Textures/BC/DetexTextureFormat.cs | 119 - FModel/PakReader/Textures/DXT/DXTDecoder.cs | 263 -- FModel/PakReader/Textures/TextureDecoder.cs | 99 - .../PakReader/Textures/TextureFormatHelper.cs | 159 - FModel/PakReader/WwiseReader.cs | 518 --- FModel/Properties/Resources.Designer.cs | 2794 +------------- FModel/Properties/Resources.ar.resx | 850 ----- FModel/Properties/Resources.de-DE.resx | 830 ---- FModel/Properties/Resources.es.resx | 869 ----- FModel/Properties/Resources.fr-FR.resx | 840 ---- FModel/Properties/Resources.it-IT.resx | 796 ---- FModel/Properties/Resources.ja-JP.resx | 790 ---- FModel/Properties/Resources.pt-PT.resx | 884 ----- FModel/Properties/Resources.resx | 976 +---- FModel/Properties/Resources.ru-RU.resx | 789 ---- FModel/Properties/Resources.zh-CN.resx | 835 ---- FModel/Properties/Settings.Designer.cs | 554 --- FModel/Properties/Settings.cs | 77 - FModel/Properties/Settings.settings | 138 - FModel/Resources/Cataba.png | Bin 0 -> 151456 bytes FModel/Resources/ColorPickerOne.png | Bin 60205 -> 0 bytes FModel/Resources/ColorPickerTwo.png | Bin 4373 -> 0 bytes FModel/Resources/Default.png | Bin 0 -> 234732 bytes FModel/Resources/Detex.dll | Bin 50176 -> 0 bytes FModel/Resources/EIconDesign_Default.png | Bin 26647 -> 0 bytes FModel/Resources/EIconDesign_Flat.png | Bin 17732 -> 0 bytes FModel/Resources/EIconDesign_Mini.png | Bin 24716 -> 0 bytes FModel/Resources/EIconDesign_NoBackground.png | Bin 7955 -> 0 bytes FModel/Resources/EIconDesign_NoText.png | Bin 20943 -> 0 bytes FModel/Resources/FModel.ico | Bin 0 -> 48051 bytes FModel/Resources/Flat.png | Bin 0 -> 220651 bytes FModel/Resources/Ini.xshd | 74 +- FModel/Resources/Json.xshd | 33 +- FModel/Resources/NoBackground.png | Bin 0 -> 177314 bytes FModel/Resources/NoText.png | Bin 0 -> 224392 bytes .../T_Placeholder_Challenge_Image.png | Bin 14259 -> 0 bytes FModel/Resources/Xml.xshd | 4 +- FModel/Resources/add_directory.png | Bin 0 -> 428 bytes FModel/Resources/alert.ico | Bin 9662 -> 0 bytes FModel/Resources/android.png | Bin 0 -> 810 bytes FModel/Resources/api-off.ico | Bin 9662 -> 0 bytes FModel/Resources/api.ico | Bin 9662 -> 0 bytes FModel/Resources/apple.png | Bin 0 -> 599 bytes FModel/Resources/archive.png | Bin 0 -> 645 bytes FModel/Resources/archive_disabled.png | Bin 0 -> 995 bytes FModel/Resources/archive_enabled.png | Bin 0 -> 1005 bytes FModel/Resources/asset.png | Bin 0 -> 718 bytes FModel/Resources/asset_ini.png | Bin 0 -> 805 bytes FModel/Resources/asset_png.png | Bin 0 -> 666 bytes FModel/Resources/asset_psd.png | Bin 0 -> 641 bytes FModel/Resources/athena.png | Bin 0 -> 1180 bytes FModel/Resources/backup-restore.png | Bin 574 -> 0 bytes .../battle-breakers-item-background.png | Bin 16069 -> 0 bytes FModel/Resources/battlebreakers.ico | Bin 67646 -> 0 bytes FModel/Resources/battlebreakers.png | Bin 0 -> 1122 bytes FModel/Resources/blueprint.png | Bin 0 -> 319 bytes FModel/Resources/borderlands.png | Bin 0 -> 1029 bytes FModel/Resources/borderlands3.ico | Bin 67646 -> 0 bytes FModel/Resources/check-circle.ico | Bin 9662 -> 0 bytes FModel/Resources/cinematics.png | Bin 0 -> 932 bytes FModel/Resources/city_pin.png | Bin 0 -> 5567 bytes FModel/Resources/content-copy.png | Bin 454 -> 0 bytes FModel/Resources/creative.png | Bin 0 -> 2479 bytes FModel/Resources/delete-forever.png | Bin 568 -> 0 bytes FModel/Resources/delete.png | Bin 0 -> 283 bytes FModel/Resources/edit.png | Bin 0 -> 267 bytes FModel/Resources/egl2.ico | Bin 39453 -> 0 bytes FModel/Resources/empty_folder.png | Bin 0 -> 774 bytes FModel/Resources/engine.png | Bin 0 -> 1145 bytes FModel/Resources/fallenorder.png | Bin 0 -> 1202 bytes FModel/Resources/file-image.ico | Bin 9662 -> 0 bytes FModel/Resources/file-multiple.png | Bin 416 -> 0 bytes FModel/Resources/file.png | Bin 406 -> 0 bytes FModel/Resources/fmodel.png | Bin 212913 -> 0 bytes FModel/Resources/folder-download.png | Bin 420 -> 0 bytes FModel/Resources/folder.png | Bin 0 -> 697 bytes FModel/Resources/fortnite.ico | Bin 41662 -> 0 bytes FModel/Resources/fortnite.png | Bin 0 -> 1014 bytes FModel/Resources/fortnitebr.png | Bin 0 -> 686 bytes FModel/Resources/gear.png | Bin 0 -> 1070 bytes FModel/Resources/go_to_directory.png | Bin 0 -> 596 bytes FModel/Resources/image-plus.png | Bin 602 -> 0 bytes FModel/Resources/image-remove.png | Bin 710 -> 0 bytes FModel/Resources/linux.png | Bin 0 -> 8183 bytes FModel/Resources/localization.png | Bin 0 -> 985 bytes FModel/Resources/lock-open-variant.ico | Bin 9662 -> 0 bytes FModel/Resources/materialicon.png | Bin 0 -> 1416 bytes FModel/Resources/minecraftdungeons.ico | Bin 67646 -> 0 bytes FModel/Resources/open-in-new.png | Bin 548 -> 0 bytes FModel/Resources/pause.png | Bin 366 -> 0 bytes FModel/Resources/pc.png | Bin 0 -> 709 bytes FModel/Resources/pencil.png | Bin 432 -> 0 bytes FModel/Resources/pin.png | Bin 0 -> 5310 bytes FModel/Resources/play.png | Bin 516 -> 0 bytes FModel/Resources/playlist-plus.png | Bin 383 -> 0 bytes FModel/Resources/power.png | Bin 537 -> 0 bytes FModel/Resources/progress-download.png | Bin 651 -> 0 bytes FModel/Resources/puzzle.png | Bin 0 -> 1120 bytes FModel/Resources/refresh.png | Bin 529 -> 0 bytes FModel/Resources/roguecompany.png | Bin 4630 -> 1253 bytes FModel/Resources/share-all.png | Bin 650 -> 0 bytes FModel/Resources/share.png | Bin 468 -> 0 bytes FModel/Resources/sign-direction-plus.png | Bin 444 -> 0 bytes FModel/Resources/sign-direction-remove.png | Bin 423 -> 0 bytes FModel/Resources/sod2.ico | Bin 67646 -> 0 bytes FModel/Resources/sound.png | Bin 0 -> 685 bytes FModel/Resources/spellbreak.ico | Bin 59582 -> 0 bytes FModel/Resources/spellbreak.png | Bin 0 -> 547 bytes FModel/Resources/stop.png | Bin 362 -> 0 bytes FModel/Resources/texture.png | Bin 0 -> 703 bytes FModel/Resources/thecycle.ico | Bin 152126 -> 0 bytes FModel/Resources/thecycle.png | Bin 0 -> 11440 bytes FModel/Resources/theouterworlds.png | Bin 25865 -> 0 bytes FModel/Resources/ui.png | Bin 0 -> 738 bytes FModel/Resources/unix.png | Bin 0 -> 2857 bytes FModel/Resources/unknown_asset.png | Bin 0 -> 630 bytes FModel/Resources/valorant.live.ico | Bin 92398 -> 0 bytes FModel/Resources/valorant.png | Bin 0 -> 6019 bytes FModel/Resources/volume-minus.png | Bin 439 -> 0 bytes FModel/Resources/volume-mute.png | Bin 560 -> 0 bytes FModel/Resources/volume-plus.png | Bin 440 -> 0 bytes FModel/Resources/weapon.png | Bin 0 -> 646 bytes FModel/Resources/wifi-strength-off.ico | Bin 9662 -> 0 bytes FModel/Resources/windows.png | Bin 0 -> 1371 bytes FModel/Services/ApplicationService.cs | 11 + FModel/Services/DiscordService.cs | 96 + FModel/Settings/UserSettings.cs | 424 +++ FModel/Theme/LeftMarginMultiplierConverter.cs | 27 - FModel/Theme/Style.xaml | 3376 ----------------- FModel/Theme/TreeViewItemExtensions.cs | 30 - FModel/Utils/Assets.cs | 548 --- FModel/Utils/BitArrays.cs | 19 - FModel/Utils/ByteOrderSwap.cs | 12 - FModel/Utils/Commands.cs | 17 - FModel/Utils/EGL2.cs | 105 - FModel/Utils/Endpoints.cs | 135 - FModel/Utils/Enums.cs | 14 - FModel/Utils/FConsole.cs | 42 - FModel/Utils/FWindows.cs | 21 - FModel/Utils/Folders.cs | 178 - .../Utils/HttpClientDownloadWithProgress.cs | 109 - FModel/Utils/Keys.cs | 128 - FModel/Utils/Localizations.cs | 278 -- FModel/Utils/MathUtils.cs | 10 - FModel/Utils/ObservableSortedList.cs | 203 - FModel/Utils/Oodle.cs | 74 - FModel/Utils/Paks.cs | 273 -- FModel/Utils/Screens.cs | 31 - FModel/Utils/SevenZipHelper.cs | 119 - FModel/Utils/Streams.cs | 35 - FModel/Utils/Tasks.cs | 23 - FModel/Utils/Updater.cs | 80 - FModel/Utils/ValoloWwiseDict.cs | 1080 ------ FModel/ViewModels/AesManagerViewModel.cs | 135 + FModel/ViewModels/ApiEndpointViewModel.cs | 30 + .../ApiEndpoints/AbstractApiProvider.cs | 14 + .../ApiEndpoints/BenbotApiEndpoint.cs | 61 + .../ApiEndpoints/EpicApiEndpoint.cs | 61 + FModel/ViewModels/ApiEndpoints/FModelApi.cs | 145 + .../ApiEndpoints/FortniteApiEndpoint.cs | 33 + .../ApiEndpoints/Models/AesResponse.cs | 23 + .../ApiEndpoints/Models/EpicResponse.cs | 13 + .../ApiEndpoints/Models/FModelResponse.cs | 182 + .../ApiEndpoints/Models/MappingsResponse.cs | 23 + .../ApiEndpoints/Models/PlaylistResponse.cs | 36 + .../ApiEndpoints/ValorantApiEndpoint.cs | 314 ++ FModel/ViewModels/ApplicationViewModel.cs | 167 + FModel/ViewModels/AssetsFolderViewModel.cs | 171 + FModel/ViewModels/AssetsListViewModel.cs | 79 + FModel/ViewModels/AudioPlayerViewModel.cs | 580 +++ .../AvalonEdit/AvalonEditViewModel.cs | 155 - FModel/ViewModels/BackupManagerViewModel.cs | 118 + .../Buttons/ExtractStopViewModel.cs | 35 - FModel/ViewModels/CUE4ParseViewModel.cs | 643 ++++ .../ViewModels/ComboBox/ComboBoxViewModel.cs | 124 - .../Commands/AddEditDirectoryCommand.cs | 34 + FModel/ViewModels/Commands/AddTabCommand.cs | 16 + FModel/ViewModels/Commands/AudioCommand.cs | 45 + FModel/ViewModels/Commands/CopyCommand.cs | 47 + .../Commands/DeleteDirectoryCommand.cs | 21 + .../ViewModels/Commands/ExportDataCommand.cs | 32 + .../Commands/ExtractNewTabCommand.cs | 33 + FModel/ViewModels/Commands/GoToCommand.cs | 57 + FModel/ViewModels/Commands/ImageCommand.cs | 44 + FModel/ViewModels/Commands/LoadCommand.cs | 212 ++ FModel/ViewModels/Commands/MenuCommand.cs | 65 + .../Commands/SavePropertyCommand.cs | 33 + .../ViewModels/Commands/SaveTextureCommand.cs | 33 + FModel/ViewModels/Commands/TabCommand.cs | 49 + .../ViewModels/CustomDirectoriesViewModel.cs | 161 + .../ViewModels/DataGrid/DataGridViewModel.cs | 50 - FModel/ViewModels/GameDirectoryViewModel.cs | 114 + FModel/ViewModels/GameSelectorViewModel.cs | 185 + .../ViewModels/ImageBox/ImageBoxViewModel.cs | 175 - FModel/ViewModels/ListBox/ListBoxViewModel.cs | 80 - FModel/ViewModels/LoadingModesViewModel.cs | 25 + FModel/ViewModels/MapViewerViewModel.cs | 254 ++ .../MenuItem/BackupMenuItemViewModel.cs | 275 -- FModel/ViewModels/MenuItem/CommandHandler.cs | 49 - .../MenuItem/GotoMenuItemViewModel.cs | 140 - FModel/ViewModels/MenuItem/MenuItems.cs | 128 - .../MenuItem/PakMenuItemViewModel.cs | 510 --- .../ViewModels/Notifier/NotifierViewModel.cs | 75 - FModel/ViewModels/PropertyChangedBase.cs | 19 - FModel/ViewModels/SearchViewModel.cs | 66 + FModel/ViewModels/SettingsViewModel.cs | 189 + .../SoundPlayer/InputFileViewModel.cs | 80 - .../StatusBar/StatusBarViewModel.cs | 69 - .../TabControl/AssetPropertiesViewModel.cs | 116 - .../TabControl/PakPropertiesViewModel.cs | 96 - FModel/ViewModels/TabControlViewModel.cs | 325 ++ FModel/ViewModels/ThreadWorkerViewModel.cs | 116 + .../Treeview/SortedTreeviewViewModel.cs | 167 - .../ViewModels/Treeview/TreeviewViewModel.cs | 65 - FModel/Views/About.xaml | 53 + FModel/Views/About.xaml.cs | 10 + FModel/Views/AesManager.xaml | 98 + FModel/Views/AesManager.xaml.cs | 35 + FModel/Views/AudioPlayer.xaml | 155 + FModel/Views/AudioPlayer.xaml.cs | 80 + FModel/Views/BackupManager.xaml | 86 + FModel/Views/BackupManager.xaml.cs | 33 + FModel/Views/CustomDir.xaml | 67 + FModel/Views/CustomDir.xaml.cs | 20 + FModel/Views/DirectorySelector.xaml | 86 + FModel/Views/DirectorySelector.xaml.cs | 36 + FModel/Views/ImageMerger.xaml | 109 + FModel/Views/ImageMerger.xaml.cs | 271 ++ FModel/Views/MapViewer.xaml | 50 + FModel/Views/MapViewer.xaml.cs | 61 + .../Controls/Aed/GamePathElementGenerator.cs | 39 + .../Controls/Aed/GamePathVisualLineText.cs | 85 + .../Controls/Aed/HexColorElementGenerator.cs | 39 + .../Controls/Aed/HexColorVisualLineText.cs | 31 + .../Controls/Aup/CustomCodecFactory.cs | 91 + .../Views/Resources/Controls/Aup/ISource.cs | 22 + .../Resources/Controls/Aup}/NVorbisSource.cs | 50 +- .../Resources/Controls/Aup/SourceEventArgs.cs | 19 + .../Aup/SourcePropertyChangedEventArgs.cs | 26 + .../Controls/Aup/SpectrumAnalyzer.cs | 426 +++ .../Controls/Aup/SpectrumProvider.cs} | 24 +- .../Views/Resources/Controls/Aup/Timeclock.cs | 328 ++ .../Views/Resources/Controls/Aup/Timeline.cs | 350 ++ .../Resources/Controls/AvalonEditor.xaml | 118 + .../Resources/Controls/AvalonEditor.xaml.cs | 184 + .../Views/Resources/Controls/HotkeyTextBox.cs | 92 + .../Views/Resources/Controls/ImagePopout.xaml | 21 + .../Resources/Controls/ImagePopout.xaml.cs | 10 + .../Resources/Controls/Mgn/EFrameType.cs | 8 + .../Views/Resources/Controls/Mgn/Magnifier.cs | 177 + .../Controls/Mgn/MagnifierAdorner.cs | 86 + .../Controls/Mgn/MagnifierManager.cs | 103 + .../Controls/OnTagDataTemplateSelector.cs | 14 + .../Resources/Controls/PropertiesPopout.xaml | 22 + .../Controls/PropertiesPopout.xaml.cs | 81 + .../Controls/Rtb/CustomRichTextBox.cs | 172 + .../Controls/Rtb/CustomScrollViewer.cs | 56 + .../Controls}/TreeViewItemBehavior.cs | 24 +- ...rderThicknessToStrokeThicknessConverter.cs | 23 + .../CaseInsensitiveStringEqualsConverter.cs | 21 + .../Converters/DateTimeToStringConverter.cs | 21 + .../Converters/EnumToStringConverter.cs | 32 + .../FileExtensionEqualsConverter.cs | 21 + .../FolderToSeparatorTagConverter.cs | 21 + .../Converters/FullPathToFileConverter.cs | 22 + .../IsNullToBoolReversedConverter.cs | 21 + .../Converters/MultiParameterConverter.cs | 21 + .../Resources/Converters/RatioConverter.cs | 28 + .../Converters/SizeToStringConverter.cs | 22 + .../Converters/StringToGameConverter.cs | 35 + .../Resources/Converters/TabSizeConverter.cs | 27 + .../Converters/TrimRightToLeftConverter.cs | 33 + FModel/Views/Resources/Resources.xaml | 1633 ++++++++ FModel/Views/SearchView.xaml | 204 + FModel/Views/SearchView.xaml.cs | 70 + FModel/Views/SettingsView.xaml | 399 ++ FModel/Views/SettingsView.xaml.cs | 86 + FModel/Windows/AESManager/AESManager.xaml | 79 - FModel/Windows/AESManager/AESManager.xaml.cs | 207 - FModel/Windows/About/FAbout.xaml | 102 - FModel/Windows/About/FAbout.xaml.cs | 15 - .../AvalonEditFindReplace.xaml | 70 - .../AvalonEditFindReplace.xaml.cs | 40 - .../AvalonEditFindReplaceHelper.cs | 465 --- FModel/Windows/ColorPicker/ColorPalette.cs | 108 - FModel/Windows/ColorPicker/ColorPickRow.xaml | 32 - .../Windows/ColorPicker/ColorPickRow.xaml.cs | 38 - .../ColorPicker/ColorPickerControl.xaml | 100 - .../ColorPicker/ColorPickerControl.xaml.cs | 398 -- .../ColorPicker/ColorPickerDialogOptions.cs | 12 - .../ColorPicker/ColorPickerSettings.cs | 14 - .../ColorPicker/ColorPickerSwatch.xaml | 25 - .../ColorPicker/ColorPickerSwatch.xaml.cs | 69 - .../ColorPicker/ColorPickerWindow.xaml | 27 - .../ColorPicker/ColorPickerWindow.xaml.cs | 93 - FModel/Windows/ColorPicker/ColorSwatchItem.cs | 10 - FModel/Windows/ColorPicker/SliderRow.xaml | 20 - FModel/Windows/ColorPicker/SliderRow.xaml.cs | 53 - FModel/Windows/ColorPicker/Util.cs | 249 -- .../CustomNotifier/CustomNotifier.xaml | 35 - .../CustomNotifier/CustomNotifier.xaml.cs | 32 - .../CustomNotifier/CustomNotifierHelper.cs | 14 - .../DarkMessageBox/DarkMessageBox.xaml | 67 - .../DarkMessageBox/DarkMessageBox.xaml.cs | 235 -- .../DarkMessageBox/DarkMessageBoxHelper.cs | 302 -- FModel/Windows/DependencyObjects.cs | 49 - FModel/Windows/ImagesMerger/ImagesMerger.xaml | 81 - .../Windows/ImagesMerger/ImagesMerger.xaml.cs | 288 -- FModel/Windows/Launcher/FLauncher.xaml | 75 - FModel/Windows/Launcher/FLauncher.xaml.cs | 160 - FModel/Windows/Search/Search.xaml | 67 - FModel/Windows/Search/Search.xaml.cs | 148 - .../Settings/ChallengeBundlesCreator.xaml | 137 - .../Settings/ChallengeBundlesCreator.xaml.cs | 130 - FModel/Windows/Settings/General.xaml | 166 - FModel/Windows/Settings/General.xaml.cs | 133 - FModel/Windows/Settings/IconCreator.xaml | 224 -- FModel/Windows/Settings/IconCreator.xaml.cs | 156 - FModel/Windows/SoundPlayer/AudioPlayer.xaml | 147 - .../Windows/SoundPlayer/AudioPlayer.xaml.cs | 339 -- FModel/Windows/SoundPlayer/ISample.cs | 7 - .../UserControls/SpectrumAnalyzer.xaml | 11 - .../UserControls/SpectrumAnalyzer.xaml.cs | 22 - .../SoundPlayer/UserControls/Timeclock.xaml | 11 - .../UserControls/Timeclock.xaml.cs | 17 - .../SoundPlayer/UserControls/Timeline.xaml | 11 - .../SoundPlayer/UserControls/Timeline.xaml.cs | 17 - .../SoundPlayer/Visualization/Device.cs | 126 - .../SoundPlayer/Visualization/EClockType.cs | 8 - .../SoundPlayer/Visualization/EDeviceType.cs | 8 - .../Visualization/EScalingStrategy.cs | 9 - .../Visualization/ESourceEventType.cs | 8 - .../Visualization/ESourceProperty.cs | 11 - .../SoundPlayer/Visualization/ISource.cs | 26 - .../Visualization/ISpectrumProvider.cs | 8 - .../SoundPlayer/Visualization/OutputSource.cs | 545 --- .../Visualization/SourceEventArgs.cs | 9 - .../SourcePropertyChangedEventArgs.cs | 10 - .../Visualization/SpectrumAnalyzer.cs | 725 ---- .../SoundPlayer/Visualization/Timeclock.cs | 624 --- .../SoundPlayer/Visualization/Timeline.cs | 528 --- FModel/Windows/UserInput/GoToUserInput.xaml | 45 - .../Windows/UserInput/GoToUserInput.xaml.cs | 54 - LICENSE | 674 ---- README.md | 182 +- appveyor.yml | 10 - 702 files changed, 17887 insertions(+), 52663 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/main.yml create mode 100644 .gitmodules create mode 160000 CUE4Parse delete mode 100644 FModel.sln create mode 100644 FModel/Constants.cs create mode 100644 FModel/Creator/Bases/BB/BaseBreakersIcon.cs delete mode 100644 FModel/Creator/Bases/BaseBBDefinition.cs delete mode 100644 FModel/Creator/Bases/BaseBundle.cs delete mode 100644 FModel/Creator/Bases/BaseGCosmetic.cs delete mode 100644 FModel/Creator/Bases/BaseIcon.cs delete mode 100644 FModel/Creator/Bases/BaseItemAccess.cs delete mode 100644 FModel/Creator/Bases/BaseMapUIData.cs delete mode 100644 FModel/Creator/Bases/BaseOffer.cs delete mode 100644 FModel/Creator/Bases/BaseOfferMaterial.cs delete mode 100644 FModel/Creator/Bases/BasePlaylist.cs delete mode 100644 FModel/Creator/Bases/BaseSeason.cs delete mode 100644 FModel/Creator/Bases/BaseUIData.cs delete mode 100644 FModel/Creator/Bases/BaseUserOption.cs create mode 100644 FModel/Creator/Bases/FN/BaseBundle.cs create mode 100644 FModel/Creator/Bases/FN/BaseCommunity.cs create mode 100644 FModel/Creator/Bases/FN/BaseIcon.cs create mode 100644 FModel/Creator/Bases/FN/BaseIconStats.cs create mode 100644 FModel/Creator/Bases/FN/BaseItemAccessToken.cs create mode 100644 FModel/Creator/Bases/FN/BaseMaterialInstance.cs create mode 100644 FModel/Creator/Bases/FN/BaseMtxOffer.cs create mode 100644 FModel/Creator/Bases/FN/BasePlaylist.cs create mode 100644 FModel/Creator/Bases/FN/BaseQuest.cs create mode 100644 FModel/Creator/Bases/FN/BaseSeason.cs create mode 100644 FModel/Creator/Bases/FN/BaseSeries.cs create mode 100644 FModel/Creator/Bases/FN/BaseUserControl.cs create mode 100644 FModel/Creator/Bases/FN/Reward.cs delete mode 100644 FModel/Creator/Bases/IBase.cs create mode 100644 FModel/Creator/Bases/SB/BaseDivision.cs create mode 100644 FModel/Creator/Bases/SB/BaseLeague.cs create mode 100644 FModel/Creator/Bases/SB/BaseSpellIcon.cs create mode 100644 FModel/Creator/Bases/UCreator.cs delete mode 100644 FModel/Creator/Bundles/CompletionReward.cs delete mode 100644 FModel/Creator/Bundles/Header.cs delete mode 100644 FModel/Creator/Bundles/HeaderStyle.cs delete mode 100644 FModel/Creator/Bundles/Quest.cs delete mode 100644 FModel/Creator/Bundles/QuestStyle.cs delete mode 100644 FModel/Creator/Bundles/Reward.cs delete mode 100644 FModel/Creator/Creator.cs create mode 100644 FModel/Creator/CreatorPackage.cs delete mode 100644 FModel/Creator/Icons/DisplayAssetImage.cs delete mode 100644 FModel/Creator/Icons/LargeSmallImage.cs delete mode 100644 FModel/Creator/Icons/UserFacingFlag.cs delete mode 100644 FModel/Creator/Icons/Watermark.cs delete mode 100644 FModel/Creator/Rarities/EFortRarity.cs delete mode 100644 FModel/Creator/Rarities/Rarity.cs delete mode 100644 FModel/Creator/Rarities/Serie.cs delete mode 100644 FModel/Creator/Stats/Statistic.cs delete mode 100644 FModel/Creator/Stats/Statistics.cs delete mode 100644 FModel/Creator/Texts/ETextSide.cs delete mode 100644 FModel/Creator/Texts/GameplayTag.cs delete mode 100644 FModel/Creator/Texts/Helper.cs delete mode 100644 FModel/Creator/Texts/Text.cs delete mode 100644 FModel/Creator/Texts/Typefaces.cs create mode 100644 FModel/Creator/Typefaces.cs delete mode 100644 FModel/Discord/DiscordIntegration.cs create mode 100644 FModel/Extensions/AvalonExtensions.cs create mode 100644 FModel/Extensions/CollectionExtensions.cs create mode 100644 FModel/Extensions/EnumExtensions.cs create mode 100644 FModel/Extensions/StreamExtensions.cs rename FModel/{Utils/Strings.cs => Extensions/StringExtensions.cs} (59%) create mode 100644 FModel/FModel.sln create mode 100644 FModel/Framework/AsyncQueue.cs create mode 100644 FModel/Framework/Command.cs create mode 100644 FModel/Framework/CustomSKShaper.cs create mode 100644 FModel/Framework/FullyObservableCollection.cs create mode 100644 FModel/Framework/Hotkey.cs create mode 100644 FModel/Framework/JsonNetSerializer.cs create mode 100644 FModel/Framework/RangeObservableCollection.cs create mode 100644 FModel/Framework/ViewModel.cs create mode 100644 FModel/Framework/ViewModelCommand.cs delete mode 100644 FModel/Globals.cs delete mode 100644 FModel/Grabber/Aes/AesData.cs delete mode 100644 FModel/Grabber/Aes/AesGrabber.cs delete mode 100644 FModel/Grabber/Cdn/CdnData.cs delete mode 100644 FModel/Grabber/Cdn/CdnDataGrabber.cs delete mode 100644 FModel/Grabber/Manifests/ManifestGrabber.cs delete mode 100644 FModel/Grabber/Manifests/ValorantAPIManifest.cs delete mode 100644 FModel/Grabber/Mappings/MappingsData.cs delete mode 100644 FModel/Grabber/Mappings/MappingsGrabber.cs delete mode 100644 FModel/Grabber/Paks/InstallsJson.cs delete mode 100644 FModel/Grabber/Paks/LauncherDat.cs delete mode 100644 FModel/Grabber/Paks/LauncherSettings.cs delete mode 100644 FModel/Grabber/Paks/PaksGrabber.cs create mode 100644 FModel/Helper.cs delete mode 100644 FModel/Logger/DebugHelper.cs delete mode 100644 FModel/Logger/Logger.cs delete mode 100644 FModel/PakReader/AESDecryptor.cs delete mode 100644 FModel/PakReader/BinaryHelper.cs delete mode 100644 FModel/PakReader/IO/EIoChunkType.cs delete mode 100644 FModel/PakReader/IO/EIoContainerFlags.cs delete mode 100644 FModel/PakReader/IO/FArc.cs delete mode 100644 FModel/PakReader/IO/FContainerHeader.cs delete mode 100644 FModel/PakReader/IO/FExportBundleEntry.cs delete mode 100644 FModel/PakReader/IO/FExportDesc.cs delete mode 100644 FModel/PakReader/IO/FExportMapEntry.cs delete mode 100644 FModel/PakReader/IO/FFileIoStoreContainerFile.cs delete mode 100644 FModel/PakReader/IO/FFileIoStoreReader.cs delete mode 100644 FModel/PakReader/IO/FFragment.cs delete mode 100644 FModel/PakReader/IO/FImportDesc.cs delete mode 100644 FModel/PakReader/IO/FIoChunkHash.cs delete mode 100644 FModel/PakReader/IO/FIoChunkId.cs delete mode 100644 FModel/PakReader/IO/FIoContainerId.cs delete mode 100644 FModel/PakReader/IO/FIoDirectoryIndexEntry.cs delete mode 100644 FModel/PakReader/IO/FIoDirectoryIndexHandle.cs delete mode 100644 FModel/PakReader/IO/FIoDirectoryIndexResource.cs delete mode 100644 FModel/PakReader/IO/FIoFileIndexEntry.cs delete mode 100644 FModel/PakReader/IO/FIoGlobalData.cs delete mode 100644 FModel/PakReader/IO/FIoOffsetAndLength.cs delete mode 100644 FModel/PakReader/IO/FIoStoreEntry.cs delete mode 100644 FModel/PakReader/IO/FIoStoreTocCompressedBlockEntry.cs delete mode 100644 FModel/PakReader/IO/FIoStoreTocEntryMeta.cs delete mode 100644 FModel/PakReader/IO/FIoStoreTocEntryMetaFlags.cs delete mode 100644 FModel/PakReader/IO/FIoStoreTocHeader.cs delete mode 100644 FModel/PakReader/IO/FIoStoreTocResource.cs delete mode 100644 FModel/PakReader/IO/FIterator.cs delete mode 100644 FModel/PakReader/IO/FMappedName.cs delete mode 100644 FModel/PakReader/IO/FMinimalName.cs delete mode 100644 FModel/PakReader/IO/FNameEntryId.cs delete mode 100644 FModel/PakReader/IO/FPackageDesc.cs delete mode 100644 FModel/PakReader/IO/FPackageId.cs delete mode 100644 FModel/PakReader/IO/FPackageLocation.cs delete mode 100644 FModel/PakReader/IO/FPackageObjectIndex.cs delete mode 100644 FModel/PakReader/IO/FPackageStoreEntry.cs delete mode 100644 FModel/PakReader/IO/FPackageSummary.cs delete mode 100644 FModel/PakReader/IO/FScriptObjectDesc.cs delete mode 100644 FModel/PakReader/IO/FScriptObjectEntry.cs delete mode 100644 FModel/PakReader/IO/FSerializedNameHeader.cs delete mode 100644 FModel/PakReader/IO/FUnversionedHeader.cs delete mode 100644 FModel/PakReader/IO/FUnversionedProperty.cs delete mode 100644 FModel/PakReader/IO/IoPackage.cs delete mode 100644 FModel/PakReader/LICENSE delete mode 100644 FModel/PakReader/LocMetaReader.cs delete mode 100644 FModel/PakReader/LocResReader.cs delete mode 100644 FModel/PakReader/Package.cs delete mode 100644 FModel/PakReader/Pak/DefaultPakFilter.cs delete mode 100644 FModel/PakReader/Pak/IPakFilter.cs delete mode 100644 FModel/PakReader/Pak/PakFileReader.cs delete mode 100644 FModel/PakReader/Pak/PakFilter.cs delete mode 100644 FModel/PakReader/Pak/PakIndex.cs delete mode 100644 FModel/PakReader/Pak/PakPackage.cs delete mode 100644 FModel/PakReader/Parsers/Class/IUExport.cs delete mode 100644 FModel/PakReader/Parsers/Class/UAkMediaAssetData.cs delete mode 100644 FModel/PakReader/Parsers/Class/UCurveTable.cs delete mode 100644 FModel/PakReader/Parsers/Class/UDataTable.cs delete mode 100644 FModel/PakReader/Parsers/Class/UFontFace.cs delete mode 100644 FModel/PakReader/Parsers/Class/UObject.cs delete mode 100644 FModel/PakReader/Parsers/Class/USoundWave.cs delete mode 100644 FModel/PakReader/Parsers/Class/UStringTable.cs delete mode 100644 FModel/PakReader/Parsers/Class/UTexture2D.cs delete mode 100644 FModel/PakReader/Parsers/IoPackageReader.cs delete mode 100644 FModel/PakReader/Parsers/LegacyPackageReader.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EAnimationCompressionFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EAnimationKeyFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EAssetRegistryDependencyType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EBulkDataFlags.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ECompressionFlags.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ECurveTableMode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EDateTimeStyle.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EDecompressionType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EExportFilterFlags.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EExpressionType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EFormatArgumentType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EInitializationMode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ELightingBuildQuality.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EObjectFlags.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EPackageFlags.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EPakVersion.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EPixelFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERangeBoundType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERichCurveCompressionFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERichCurveExtrapolation.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERichCurveInterpMode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERichCurveKeyTimeCompressionFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERichCurveTangentMode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ERichCurveTangentWeightMode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ESoundGroup.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ESoundWaveLoadingBehavior.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ESoundWavePrecacheState.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ESoundwaveSampleRateSettings.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EStringTableLoadingPhase.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ETextFlag.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ETextGender.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ETextHistoryType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/ETransformType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EUnrealEngineObjectLicenseeUE4Version.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EUnrealEngineObjectUE4Version.cs delete mode 100644 FModel/PakReader/Parsers/Objects/EVirtualTextureCodec.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAkMediaDataChunk.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAssetData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAssetDataTagMapSharedView.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAssetIdentifier.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAssetPackageData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAssetRegistryState.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FAssetRegistryVersion.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FBodyInstance.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FBox.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FBox2D.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FByteBulkData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FByteBulkDataHeader.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FChunkHeader.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FColor.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FColorMaterialInput.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FCompressedChunk.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FCompressedOffsetData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FCompressedSegment.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FCustomVersion.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FCustomVersionContainer.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FDateTime.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FDependsNode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FDictionaryHeader.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FEngineVersion.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FEntry.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FEvaluationTreeEntryHandle.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FFactChunk.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FFieldClass.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FFormatArgumentData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FFormatArgumentValue.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FFormatContainer.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FFrameNumber.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FGameplayTagContainer.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FGenerationInfo.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FGuid.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FIntPoint.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FLevelSequenceLegacyObjectReference.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FLevelSequenceObjectReferenceMap.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FLinearColor.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMD5Hash.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMaterialInput.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneEvaluationKey.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneEvaluationTemplate.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneEvaluationTree.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneEvaluationTreeNode.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneEvaluationTreeNodeHandle.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneFloatChannel.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneFrameRange.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FMovieSceneSegment.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FName.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FNameEntrySerialized.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FNameTableArchiveReader.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FNavAgentSelectorCustomization.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FObjectExport.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FObjectImport.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FObjectResource.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FOodleCompressedData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FOodleDictionaryArchive.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPackageFileSummary.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPackageIndex.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPakCompressedBlock.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPakDirectoryEntry.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPakEntry.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPakInfo.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPathHashIndexEntry.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPerPlatformFloat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPerPlatformInt.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPrimaryAssetId.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPrimaryAssetType.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FPropertyTag.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FQuat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FRichCurveKey.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FRiffWaveHeader.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FRotator.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSHAHash.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSampleChunk.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSampleLoop.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSectionEvaluationDataTree.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSimpleCurveKey.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSkeletalMeshAreaWeightedTriangleSampler.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSkeletalMeshSamplingLODBuiltData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSmartName.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FSoftObjectPath.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FStreamedAudioChunk.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FStringTable.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FStripDataFlags.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FText.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistory.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryArgumentDataFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryAsDate.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryAsTime.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryBase.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryDateTime.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryFormatNumber.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryNone.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryOrderedFormat.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryStringTableEntry.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextHistoryTransform.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTextKey.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTexture2DMipMap.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FTexturePlatformData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FUniqueObjectGuid.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FVector.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FVector2D.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FVector4.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FVectorMaterialInput.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FVirtualTextureBuiltData.cs delete mode 100644 FModel/PakReader/Parsers/Objects/FVirtualTextureDataChunk.cs delete mode 100644 FModel/PakReader/Parsers/Objects/IUStruct.cs delete mode 100644 FModel/PakReader/Parsers/Objects/TEvaluationTreeEntryContainer.cs delete mode 100644 FModel/PakReader/Parsers/Objects/TMovieSceneEvaluationTree.cs delete mode 100644 FModel/PakReader/Parsers/Objects/TRange.cs delete mode 100644 FModel/PakReader/Parsers/Objects/TRangeBound.cs delete mode 100644 FModel/PakReader/Parsers/Objects/UScriptStruct.cs delete mode 100644 FModel/PakReader/Parsers/OodleStream.cs delete mode 100644 FModel/PakReader/Parsers/PackageReader.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/ArrayProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/AssetObjectProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/BaseProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/BoolProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/ByteProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/DelegateProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/DoubleProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/EnumProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/FieldPathProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/FloatProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/Int16Property.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/Int64Property.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/Int8Property.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/IntProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/InterfaceProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/LazyObjectProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/MapProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/MulticastDelegateProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/NameProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/ObjectProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/SetProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/SoftObjectProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/StrProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/StructProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/TextProperty.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/UInt16Property.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/UInt32Property.cs delete mode 100644 FModel/PakReader/Parsers/PropertyTagData/UInt64Property.cs delete mode 100644 FModel/PakReader/ReaderEntry.cs delete mode 100644 FModel/PakReader/ReaderExtensions.cs delete mode 100644 FModel/PakReader/Textures/ASTC/ASTCDecoder.cs delete mode 100644 FModel/PakReader/Textures/ASTC/ASTCPixel.cs delete mode 100644 FModel/PakReader/Textures/ASTC/BitArrayStream.cs delete mode 100644 FModel/PakReader/Textures/ASTC/IntegerEncoded.cs delete mode 100644 FModel/PakReader/Textures/BC/BCDecoder.cs delete mode 100644 FModel/PakReader/Textures/BC/Detex.cs delete mode 100644 FModel/PakReader/Textures/BC/DetexCompressedTextureFormatIndex.cs delete mode 100644 FModel/PakReader/Textures/BC/DetexPixelFormat.cs delete mode 100644 FModel/PakReader/Textures/BC/DetexTextureFormat.cs delete mode 100644 FModel/PakReader/Textures/DXT/DXTDecoder.cs delete mode 100644 FModel/PakReader/Textures/TextureDecoder.cs delete mode 100644 FModel/PakReader/Textures/TextureFormatHelper.cs delete mode 100644 FModel/PakReader/WwiseReader.cs delete mode 100644 FModel/Properties/Resources.ar.resx delete mode 100644 FModel/Properties/Resources.de-DE.resx delete mode 100644 FModel/Properties/Resources.es.resx delete mode 100644 FModel/Properties/Resources.fr-FR.resx delete mode 100644 FModel/Properties/Resources.it-IT.resx delete mode 100644 FModel/Properties/Resources.ja-JP.resx delete mode 100644 FModel/Properties/Resources.pt-PT.resx delete mode 100644 FModel/Properties/Resources.ru-RU.resx delete mode 100644 FModel/Properties/Resources.zh-CN.resx delete mode 100644 FModel/Properties/Settings.Designer.cs delete mode 100644 FModel/Properties/Settings.cs delete mode 100644 FModel/Properties/Settings.settings create mode 100644 FModel/Resources/Cataba.png delete mode 100644 FModel/Resources/ColorPickerOne.png delete mode 100644 FModel/Resources/ColorPickerTwo.png create mode 100644 FModel/Resources/Default.png delete mode 100644 FModel/Resources/Detex.dll delete mode 100644 FModel/Resources/EIconDesign_Default.png delete mode 100644 FModel/Resources/EIconDesign_Flat.png delete mode 100644 FModel/Resources/EIconDesign_Mini.png delete mode 100644 FModel/Resources/EIconDesign_NoBackground.png delete mode 100644 FModel/Resources/EIconDesign_NoText.png create mode 100644 FModel/Resources/FModel.ico create mode 100644 FModel/Resources/Flat.png create mode 100644 FModel/Resources/NoBackground.png create mode 100644 FModel/Resources/NoText.png delete mode 100644 FModel/Resources/T_Placeholder_Challenge_Image.png create mode 100644 FModel/Resources/add_directory.png delete mode 100644 FModel/Resources/alert.ico create mode 100644 FModel/Resources/android.png delete mode 100644 FModel/Resources/api-off.ico delete mode 100644 FModel/Resources/api.ico create mode 100644 FModel/Resources/apple.png create mode 100644 FModel/Resources/archive.png create mode 100644 FModel/Resources/archive_disabled.png create mode 100644 FModel/Resources/archive_enabled.png create mode 100644 FModel/Resources/asset.png create mode 100644 FModel/Resources/asset_ini.png create mode 100644 FModel/Resources/asset_png.png create mode 100644 FModel/Resources/asset_psd.png create mode 100644 FModel/Resources/athena.png delete mode 100644 FModel/Resources/backup-restore.png delete mode 100644 FModel/Resources/battle-breakers-item-background.png delete mode 100644 FModel/Resources/battlebreakers.ico create mode 100644 FModel/Resources/battlebreakers.png create mode 100644 FModel/Resources/blueprint.png create mode 100644 FModel/Resources/borderlands.png delete mode 100644 FModel/Resources/borderlands3.ico delete mode 100644 FModel/Resources/check-circle.ico create mode 100644 FModel/Resources/cinematics.png create mode 100644 FModel/Resources/city_pin.png delete mode 100644 FModel/Resources/content-copy.png create mode 100644 FModel/Resources/creative.png delete mode 100644 FModel/Resources/delete-forever.png create mode 100644 FModel/Resources/delete.png create mode 100644 FModel/Resources/edit.png delete mode 100644 FModel/Resources/egl2.ico create mode 100644 FModel/Resources/empty_folder.png create mode 100644 FModel/Resources/engine.png create mode 100644 FModel/Resources/fallenorder.png delete mode 100644 FModel/Resources/file-image.ico delete mode 100644 FModel/Resources/file-multiple.png delete mode 100644 FModel/Resources/file.png delete mode 100644 FModel/Resources/fmodel.png delete mode 100644 FModel/Resources/folder-download.png create mode 100644 FModel/Resources/folder.png delete mode 100644 FModel/Resources/fortnite.ico create mode 100644 FModel/Resources/fortnite.png create mode 100644 FModel/Resources/fortnitebr.png create mode 100644 FModel/Resources/gear.png create mode 100644 FModel/Resources/go_to_directory.png delete mode 100644 FModel/Resources/image-plus.png delete mode 100644 FModel/Resources/image-remove.png create mode 100644 FModel/Resources/linux.png create mode 100644 FModel/Resources/localization.png delete mode 100644 FModel/Resources/lock-open-variant.ico create mode 100644 FModel/Resources/materialicon.png delete mode 100644 FModel/Resources/minecraftdungeons.ico delete mode 100644 FModel/Resources/open-in-new.png delete mode 100644 FModel/Resources/pause.png create mode 100644 FModel/Resources/pc.png delete mode 100644 FModel/Resources/pencil.png create mode 100644 FModel/Resources/pin.png delete mode 100644 FModel/Resources/play.png delete mode 100644 FModel/Resources/playlist-plus.png delete mode 100644 FModel/Resources/power.png delete mode 100644 FModel/Resources/progress-download.png create mode 100644 FModel/Resources/puzzle.png delete mode 100644 FModel/Resources/refresh.png delete mode 100644 FModel/Resources/share-all.png delete mode 100644 FModel/Resources/share.png delete mode 100644 FModel/Resources/sign-direction-plus.png delete mode 100644 FModel/Resources/sign-direction-remove.png delete mode 100644 FModel/Resources/sod2.ico create mode 100644 FModel/Resources/sound.png delete mode 100644 FModel/Resources/spellbreak.ico create mode 100644 FModel/Resources/spellbreak.png delete mode 100644 FModel/Resources/stop.png create mode 100644 FModel/Resources/texture.png delete mode 100644 FModel/Resources/thecycle.ico create mode 100644 FModel/Resources/thecycle.png delete mode 100644 FModel/Resources/theouterworlds.png create mode 100644 FModel/Resources/ui.png create mode 100644 FModel/Resources/unix.png create mode 100644 FModel/Resources/unknown_asset.png delete mode 100644 FModel/Resources/valorant.live.ico create mode 100644 FModel/Resources/valorant.png delete mode 100644 FModel/Resources/volume-minus.png delete mode 100644 FModel/Resources/volume-mute.png delete mode 100644 FModel/Resources/volume-plus.png create mode 100644 FModel/Resources/weapon.png delete mode 100644 FModel/Resources/wifi-strength-off.ico create mode 100644 FModel/Resources/windows.png create mode 100644 FModel/Services/ApplicationService.cs create mode 100644 FModel/Services/DiscordService.cs create mode 100644 FModel/Settings/UserSettings.cs delete mode 100644 FModel/Theme/LeftMarginMultiplierConverter.cs delete mode 100644 FModel/Theme/Style.xaml delete mode 100644 FModel/Theme/TreeViewItemExtensions.cs delete mode 100644 FModel/Utils/Assets.cs delete mode 100644 FModel/Utils/BitArrays.cs delete mode 100644 FModel/Utils/ByteOrderSwap.cs delete mode 100644 FModel/Utils/Commands.cs delete mode 100644 FModel/Utils/EGL2.cs delete mode 100644 FModel/Utils/Endpoints.cs delete mode 100644 FModel/Utils/Enums.cs delete mode 100644 FModel/Utils/FConsole.cs delete mode 100644 FModel/Utils/FWindows.cs delete mode 100644 FModel/Utils/Folders.cs delete mode 100644 FModel/Utils/HttpClientDownloadWithProgress.cs delete mode 100644 FModel/Utils/Keys.cs delete mode 100644 FModel/Utils/Localizations.cs delete mode 100644 FModel/Utils/MathUtils.cs delete mode 100644 FModel/Utils/ObservableSortedList.cs delete mode 100644 FModel/Utils/Oodle.cs delete mode 100644 FModel/Utils/Paks.cs delete mode 100644 FModel/Utils/Screens.cs delete mode 100644 FModel/Utils/SevenZipHelper.cs delete mode 100644 FModel/Utils/Streams.cs delete mode 100644 FModel/Utils/Tasks.cs delete mode 100644 FModel/Utils/Updater.cs delete mode 100644 FModel/Utils/ValoloWwiseDict.cs create mode 100644 FModel/ViewModels/AesManagerViewModel.cs create mode 100644 FModel/ViewModels/ApiEndpointViewModel.cs create mode 100644 FModel/ViewModels/ApiEndpoints/AbstractApiProvider.cs create mode 100644 FModel/ViewModels/ApiEndpoints/BenbotApiEndpoint.cs create mode 100644 FModel/ViewModels/ApiEndpoints/EpicApiEndpoint.cs create mode 100644 FModel/ViewModels/ApiEndpoints/FModelApi.cs create mode 100644 FModel/ViewModels/ApiEndpoints/FortniteApiEndpoint.cs create mode 100644 FModel/ViewModels/ApiEndpoints/Models/AesResponse.cs create mode 100644 FModel/ViewModels/ApiEndpoints/Models/EpicResponse.cs create mode 100644 FModel/ViewModels/ApiEndpoints/Models/FModelResponse.cs create mode 100644 FModel/ViewModels/ApiEndpoints/Models/MappingsResponse.cs create mode 100644 FModel/ViewModels/ApiEndpoints/Models/PlaylistResponse.cs create mode 100644 FModel/ViewModels/ApiEndpoints/ValorantApiEndpoint.cs create mode 100644 FModel/ViewModels/ApplicationViewModel.cs create mode 100644 FModel/ViewModels/AssetsFolderViewModel.cs create mode 100644 FModel/ViewModels/AssetsListViewModel.cs create mode 100644 FModel/ViewModels/AudioPlayerViewModel.cs delete mode 100644 FModel/ViewModels/AvalonEdit/AvalonEditViewModel.cs create mode 100644 FModel/ViewModels/BackupManagerViewModel.cs delete mode 100644 FModel/ViewModels/Buttons/ExtractStopViewModel.cs create mode 100644 FModel/ViewModels/CUE4ParseViewModel.cs delete mode 100644 FModel/ViewModels/ComboBox/ComboBoxViewModel.cs create mode 100644 FModel/ViewModels/Commands/AddEditDirectoryCommand.cs create mode 100644 FModel/ViewModels/Commands/AddTabCommand.cs create mode 100644 FModel/ViewModels/Commands/AudioCommand.cs create mode 100644 FModel/ViewModels/Commands/CopyCommand.cs create mode 100644 FModel/ViewModels/Commands/DeleteDirectoryCommand.cs create mode 100644 FModel/ViewModels/Commands/ExportDataCommand.cs create mode 100644 FModel/ViewModels/Commands/ExtractNewTabCommand.cs create mode 100644 FModel/ViewModels/Commands/GoToCommand.cs create mode 100644 FModel/ViewModels/Commands/ImageCommand.cs create mode 100644 FModel/ViewModels/Commands/LoadCommand.cs create mode 100644 FModel/ViewModels/Commands/MenuCommand.cs create mode 100644 FModel/ViewModels/Commands/SavePropertyCommand.cs create mode 100644 FModel/ViewModels/Commands/SaveTextureCommand.cs create mode 100644 FModel/ViewModels/Commands/TabCommand.cs create mode 100644 FModel/ViewModels/CustomDirectoriesViewModel.cs delete mode 100644 FModel/ViewModels/DataGrid/DataGridViewModel.cs create mode 100644 FModel/ViewModels/GameDirectoryViewModel.cs create mode 100644 FModel/ViewModels/GameSelectorViewModel.cs delete mode 100644 FModel/ViewModels/ImageBox/ImageBoxViewModel.cs delete mode 100644 FModel/ViewModels/ListBox/ListBoxViewModel.cs create mode 100644 FModel/ViewModels/LoadingModesViewModel.cs create mode 100644 FModel/ViewModels/MapViewerViewModel.cs delete mode 100644 FModel/ViewModels/MenuItem/BackupMenuItemViewModel.cs delete mode 100644 FModel/ViewModels/MenuItem/CommandHandler.cs delete mode 100644 FModel/ViewModels/MenuItem/GotoMenuItemViewModel.cs delete mode 100644 FModel/ViewModels/MenuItem/MenuItems.cs delete mode 100644 FModel/ViewModels/MenuItem/PakMenuItemViewModel.cs delete mode 100644 FModel/ViewModels/Notifier/NotifierViewModel.cs delete mode 100644 FModel/ViewModels/PropertyChangedBase.cs create mode 100644 FModel/ViewModels/SearchViewModel.cs create mode 100644 FModel/ViewModels/SettingsViewModel.cs delete mode 100644 FModel/ViewModels/SoundPlayer/InputFileViewModel.cs delete mode 100644 FModel/ViewModels/StatusBar/StatusBarViewModel.cs delete mode 100644 FModel/ViewModels/TabControl/AssetPropertiesViewModel.cs delete mode 100644 FModel/ViewModels/TabControl/PakPropertiesViewModel.cs create mode 100644 FModel/ViewModels/TabControlViewModel.cs create mode 100644 FModel/ViewModels/ThreadWorkerViewModel.cs delete mode 100644 FModel/ViewModels/Treeview/SortedTreeviewViewModel.cs delete mode 100644 FModel/ViewModels/Treeview/TreeviewViewModel.cs create mode 100644 FModel/Views/About.xaml create mode 100644 FModel/Views/About.xaml.cs create mode 100644 FModel/Views/AesManager.xaml create mode 100644 FModel/Views/AesManager.xaml.cs create mode 100644 FModel/Views/AudioPlayer.xaml create mode 100644 FModel/Views/AudioPlayer.xaml.cs create mode 100644 FModel/Views/BackupManager.xaml create mode 100644 FModel/Views/BackupManager.xaml.cs create mode 100644 FModel/Views/CustomDir.xaml create mode 100644 FModel/Views/CustomDir.xaml.cs create mode 100644 FModel/Views/DirectorySelector.xaml create mode 100644 FModel/Views/DirectorySelector.xaml.cs create mode 100644 FModel/Views/ImageMerger.xaml create mode 100644 FModel/Views/ImageMerger.xaml.cs create mode 100644 FModel/Views/MapViewer.xaml create mode 100644 FModel/Views/MapViewer.xaml.cs create mode 100644 FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs create mode 100644 FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs create mode 100644 FModel/Views/Resources/Controls/Aed/HexColorElementGenerator.cs create mode 100644 FModel/Views/Resources/Controls/Aed/HexColorVisualLineText.cs create mode 100644 FModel/Views/Resources/Controls/Aup/CustomCodecFactory.cs create mode 100644 FModel/Views/Resources/Controls/Aup/ISource.cs rename FModel/{Windows/SoundPlayer => Views/Resources/Controls/Aup}/NVorbisSource.cs (51%) create mode 100644 FModel/Views/Resources/Controls/Aup/SourceEventArgs.cs create mode 100644 FModel/Views/Resources/Controls/Aup/SourcePropertyChangedEventArgs.cs create mode 100644 FModel/Views/Resources/Controls/Aup/SpectrumAnalyzer.cs rename FModel/{Windows/SoundPlayer/Visualization/BasicSpectrumProvider.cs => Views/Resources/Controls/Aup/SpectrumProvider.cs} (57%) create mode 100644 FModel/Views/Resources/Controls/Aup/Timeclock.cs create mode 100644 FModel/Views/Resources/Controls/Aup/Timeline.cs create mode 100644 FModel/Views/Resources/Controls/AvalonEditor.xaml create mode 100644 FModel/Views/Resources/Controls/AvalonEditor.xaml.cs create mode 100644 FModel/Views/Resources/Controls/HotkeyTextBox.cs create mode 100644 FModel/Views/Resources/Controls/ImagePopout.xaml create mode 100644 FModel/Views/Resources/Controls/ImagePopout.xaml.cs create mode 100644 FModel/Views/Resources/Controls/Mgn/EFrameType.cs create mode 100644 FModel/Views/Resources/Controls/Mgn/Magnifier.cs create mode 100644 FModel/Views/Resources/Controls/Mgn/MagnifierAdorner.cs create mode 100644 FModel/Views/Resources/Controls/Mgn/MagnifierManager.cs create mode 100644 FModel/Views/Resources/Controls/OnTagDataTemplateSelector.cs create mode 100644 FModel/Views/Resources/Controls/PropertiesPopout.xaml create mode 100644 FModel/Views/Resources/Controls/PropertiesPopout.xaml.cs create mode 100644 FModel/Views/Resources/Controls/Rtb/CustomRichTextBox.cs create mode 100644 FModel/Views/Resources/Controls/Rtb/CustomScrollViewer.cs rename FModel/{ViewModels/Treeview => Views/Resources/Controls}/TreeViewItemBehavior.cs (54%) create mode 100644 FModel/Views/Resources/Converters/BorderThicknessToStrokeThicknessConverter.cs create mode 100644 FModel/Views/Resources/Converters/CaseInsensitiveStringEqualsConverter.cs create mode 100644 FModel/Views/Resources/Converters/DateTimeToStringConverter.cs create mode 100644 FModel/Views/Resources/Converters/EnumToStringConverter.cs create mode 100644 FModel/Views/Resources/Converters/FileExtensionEqualsConverter.cs create mode 100644 FModel/Views/Resources/Converters/FolderToSeparatorTagConverter.cs create mode 100644 FModel/Views/Resources/Converters/FullPathToFileConverter.cs create mode 100644 FModel/Views/Resources/Converters/IsNullToBoolReversedConverter.cs create mode 100644 FModel/Views/Resources/Converters/MultiParameterConverter.cs create mode 100644 FModel/Views/Resources/Converters/RatioConverter.cs create mode 100644 FModel/Views/Resources/Converters/SizeToStringConverter.cs create mode 100644 FModel/Views/Resources/Converters/StringToGameConverter.cs create mode 100644 FModel/Views/Resources/Converters/TabSizeConverter.cs create mode 100644 FModel/Views/Resources/Converters/TrimRightToLeftConverter.cs create mode 100644 FModel/Views/Resources/Resources.xaml create mode 100644 FModel/Views/SearchView.xaml create mode 100644 FModel/Views/SearchView.xaml.cs create mode 100644 FModel/Views/SettingsView.xaml create mode 100644 FModel/Views/SettingsView.xaml.cs delete mode 100644 FModel/Windows/AESManager/AESManager.xaml delete mode 100644 FModel/Windows/AESManager/AESManager.xaml.cs delete mode 100644 FModel/Windows/About/FAbout.xaml delete mode 100644 FModel/Windows/About/FAbout.xaml.cs delete mode 100644 FModel/Windows/AvalonEditFindReplace/AvalonEditFindReplace.xaml delete mode 100644 FModel/Windows/AvalonEditFindReplace/AvalonEditFindReplace.xaml.cs delete mode 100644 FModel/Windows/AvalonEditFindReplace/AvalonEditFindReplaceHelper.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPalette.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPickRow.xaml delete mode 100644 FModel/Windows/ColorPicker/ColorPickRow.xaml.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPickerControl.xaml delete mode 100644 FModel/Windows/ColorPicker/ColorPickerControl.xaml.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPickerDialogOptions.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPickerSettings.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPickerSwatch.xaml delete mode 100644 FModel/Windows/ColorPicker/ColorPickerSwatch.xaml.cs delete mode 100644 FModel/Windows/ColorPicker/ColorPickerWindow.xaml delete mode 100644 FModel/Windows/ColorPicker/ColorPickerWindow.xaml.cs delete mode 100644 FModel/Windows/ColorPicker/ColorSwatchItem.cs delete mode 100644 FModel/Windows/ColorPicker/SliderRow.xaml delete mode 100644 FModel/Windows/ColorPicker/SliderRow.xaml.cs delete mode 100644 FModel/Windows/ColorPicker/Util.cs delete mode 100644 FModel/Windows/CustomNotifier/CustomNotifier.xaml delete mode 100644 FModel/Windows/CustomNotifier/CustomNotifier.xaml.cs delete mode 100644 FModel/Windows/CustomNotifier/CustomNotifierHelper.cs delete mode 100644 FModel/Windows/DarkMessageBox/DarkMessageBox.xaml delete mode 100644 FModel/Windows/DarkMessageBox/DarkMessageBox.xaml.cs delete mode 100644 FModel/Windows/DarkMessageBox/DarkMessageBoxHelper.cs delete mode 100644 FModel/Windows/DependencyObjects.cs delete mode 100644 FModel/Windows/ImagesMerger/ImagesMerger.xaml delete mode 100644 FModel/Windows/ImagesMerger/ImagesMerger.xaml.cs delete mode 100644 FModel/Windows/Launcher/FLauncher.xaml delete mode 100644 FModel/Windows/Launcher/FLauncher.xaml.cs delete mode 100644 FModel/Windows/Search/Search.xaml delete mode 100644 FModel/Windows/Search/Search.xaml.cs delete mode 100644 FModel/Windows/Settings/ChallengeBundlesCreator.xaml delete mode 100644 FModel/Windows/Settings/ChallengeBundlesCreator.xaml.cs delete mode 100644 FModel/Windows/Settings/General.xaml delete mode 100644 FModel/Windows/Settings/General.xaml.cs delete mode 100644 FModel/Windows/Settings/IconCreator.xaml delete mode 100644 FModel/Windows/Settings/IconCreator.xaml.cs delete mode 100644 FModel/Windows/SoundPlayer/AudioPlayer.xaml delete mode 100644 FModel/Windows/SoundPlayer/AudioPlayer.xaml.cs delete mode 100644 FModel/Windows/SoundPlayer/ISample.cs delete mode 100644 FModel/Windows/SoundPlayer/UserControls/SpectrumAnalyzer.xaml delete mode 100644 FModel/Windows/SoundPlayer/UserControls/SpectrumAnalyzer.xaml.cs delete mode 100644 FModel/Windows/SoundPlayer/UserControls/Timeclock.xaml delete mode 100644 FModel/Windows/SoundPlayer/UserControls/Timeclock.xaml.cs delete mode 100644 FModel/Windows/SoundPlayer/UserControls/Timeline.xaml delete mode 100644 FModel/Windows/SoundPlayer/UserControls/Timeline.xaml.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/Device.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/EClockType.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/EDeviceType.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/EScalingStrategy.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/ESourceEventType.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/ESourceProperty.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/ISource.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/ISpectrumProvider.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/OutputSource.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/SourceEventArgs.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/SourcePropertyChangedEventArgs.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/SpectrumAnalyzer.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/Timeclock.cs delete mode 100644 FModel/Windows/SoundPlayer/Visualization/Timeline.cs delete mode 100644 FModel/Windows/UserInput/GoToUserInput.xaml delete mode 100644 FModel/Windows/UserInput/GoToUserInput.xaml.cs delete mode 100644 LICENSE delete mode 100644 appveyor.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 8c9714aa..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -1. Describe the bug - *A clear and concise description of what the bug is.* -2. Steps to reproduce the behavior -3. Send logs - *The latest log file is ABSOLUTELY MANDATORY* -4. Add screenshots - -**DO NOT** report issues to ask where is ``. -**DO NOT** report issues if you have AES errors. -**DO NOT** report issues if your game is not up to date. - -Without following the rules above, your issue will be closed without explanation. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 0075f9ca..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement, suggestion -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c3ac4b71 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,32 @@ +name: Artifact Generator + +on: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + + steps: + - name: GIT Checkout + uses: actions/checkout@v2 + with: + submodules: 'true' + token: ${{ secrets.PAT_TOKEN }} + + - name: .NET 5 Setup + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 5.0.x + + - name: .NET Restore + run: dotnet restore FModel + + - name: .NET Publish + run: dotnet publish FModel -c Release -f net5.0-windows -o "./FModel/bin/Publish/" -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:DebugType=None -p:GenerateDocumentationFile=false -p:DebugSymbols=false --no-self-contained -r win-x64 + + - name: EXE Upload + uses: actions/upload-artifact@v2 + with: + name: FModel + path: D:\a\FModel4\FModel4\FModel\bin\Publish\ diff --git a/.gitignore b/.gitignore index da2d41ee..c95cf427 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files +*.rsuser *.suo *.user *.userosscache @@ -12,6 +13,9 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# Mono auto generated files +mono_crash.* + # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -19,13 +23,17 @@ [Rr]eleases/ x64/ x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +[Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ +.idea/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ @@ -36,9 +44,10 @@ Generated\ Files/ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -# NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ @@ -52,7 +61,6 @@ BenchmarkDotNet.Artifacts/ project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json # StyleCop StyleCopReport.xml @@ -60,7 +68,7 @@ StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c -*_i.h +*_h.h *.ilk *.meta *.obj @@ -77,6 +85,7 @@ StyleCopReport.xml *.tlh *.tmp *.tmp_proj +*_wpftmp.csproj *.log *.vspscc *.vssscc @@ -119,9 +128,6 @@ _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user -# JustCode is a .NET coding add-in -.JustCode - # TeamCity is a build add-in _TeamCity* @@ -179,6 +185,8 @@ PublishScripts/ # NuGet Packages *.nupkg +# NuGet Symbol Packages +*.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. @@ -203,12 +211,14 @@ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx +*.appxbundle +*.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache -!*.[Cc]ache/ +!?*.[Cc]ache/ # Others ClientBin/ @@ -221,7 +231,7 @@ ClientBin/ *.publishsettings orleans.codegen.cs -# Including strong name files can present a security risk +# Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk @@ -236,7 +246,7 @@ Generated_Code/ # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ -#Backup*/ +Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ @@ -252,6 +262,9 @@ ServiceFabricBackup/ *.bim.layout *.bim_*.settings *.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ @@ -287,12 +300,8 @@ paket-files/ # FAKE - F# Make .fake/ -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ +# CodeRush personal settings +.cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ @@ -317,7 +326,7 @@ __pycache__/ # OpenCover UI analysis results OpenCover/ -# Azure Stream Analytics local run output +# Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log @@ -326,8 +335,17 @@ ASALocalRun/ # NVidia Nsight GPU debugger configuration file *.nvuser -# MFractors (Xamarin productivity tool) working folder +# MFractors (Xamarin productivity tool) working folder .mfractor/ +# Local History for Visual Studio +.localhistory/ -.github \ No newline at end of file +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..2221fc41 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "CUE4Parse"] + path = CUE4Parse + url = https://github.com/FabianFG/CUE4Parse diff --git a/CUE4Parse b/CUE4Parse new file mode 160000 index 00000000..09a98cda --- /dev/null +++ b/CUE4Parse @@ -0,0 +1 @@ +Subproject commit 09a98cdafd273db1cdee58ded37ed4f5b3a2ff22 diff --git a/FModel.sln b/FModel.sln deleted file mode 100644 index f0d77284..00000000 --- a/FModel.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30804.86 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel\FModel.csproj", "{A42F8737-D056-4FA5-BEB5-AA96E1639F8A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.ActiveCfg = Debug|x64 - {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Debug|x64.Build.0 = Debug|x64 - {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.ActiveCfg = Release|x64 - {A42F8737-D056-4FA5-BEB5-AA96E1639F8A}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {688BAB53-42F7-45F2-AFDF-79A07771213F} - EndGlobalSection -EndGlobal diff --git a/FModel/App.xaml b/FModel/App.xaml index a43c45c3..f35316c1 100644 --- a/FModel/App.xaml +++ b/FModel/App.xaml @@ -1,14 +1,19 @@  + Exit="AppExit" DispatcherUnhandledException="OnUnhandledException"> - - + + + + #206BD4 + #D49220 + #C22B2B diff --git a/FModel/App.xaml.cs b/FModel/App.xaml.cs index 62ebf361..de79dd94 100644 --- a/FModel/App.xaml.cs +++ b/FModel/App.xaml.cs @@ -1,80 +1,124 @@ -using FModel.Logger; -using FModel.Utils; -using FModel.ViewModels.ComboBox; -using FModel.ViewModels.StatusBar; -using FModel.Windows.DarkMessageBox; +using AdonisUI.Controls; +using Microsoft.Win32; +using Serilog; using System; -using System.Diagnostics; using System.IO; -using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Threading; +using FModel.Framework; +using FModel.Services; +using FModel.Settings; +using Newtonsoft.Json; +using Serilog.Sinks.SystemConsole.Themes; +using MessageBox = AdonisUI.Controls.MessageBox; +using MessageBoxImage = AdonisUI.Controls.MessageBoxImage; +using MessageBoxResult = AdonisUI.Controls.MessageBoxResult; namespace FModel { /// /// Interaction logic for App.xaml /// - public partial class App : Application + public partial class App { - internal static Stopwatch StartTimer { get; private set; } - static bool framerateSet = false; - protected override void OnStartup(StartupEventArgs e) { - StartTimer = Stopwatch.StartNew(); - - DebugHelper.Init(LogsFilePath); // get old settings too - - Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(ProgramLang.GetProgramLang()); - - DebugHelper.WriteLine("{0} {1}", "[FModel]", "––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––"); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Version]", Assembly.GetExecutingAssembly().GetName().Version.ToString()); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Build]", Globals.Build); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[OS]", Logger.Logger.GetOperatingSystemProductName(true)); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Runtime]", RuntimeInformation.FrameworkDescription); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Culture]", Thread.CurrentThread.CurrentUICulture); - - StatusBarVm.statusBarViewModel.Set(FModel.Properties.Resources.Initializing, FModel.Properties.Resources.Loading); - base.OnStartup(e); - } - public static string LogsFilePath - { - get + try { - string filename = string.Format("FModel-Log-{0:yyyy-MM-dd}.txt", DateTime.Now); - - // Copy user settings from previous application version if necessary - if (FModel.Properties.Settings.Default.UpdateSettings) - FModel.Properties.Settings.Default.Upgrade(); - - Folders.LoadFolders(); - - return Path.Combine(FModel.Properties.Settings.Default.OutputPath + "\\Logs", filename); + UserSettings.Default = JsonConvert.DeserializeObject( + File.ReadAllText(UserSettings.FilePath), JsonNetSerializer.SerializerSettings); } + catch + { + UserSettings.Default = new UserSettings(); + } + + if (!Directory.Exists(UserSettings.Default.OutputDirectory)) + { + UserSettings.Default.OutputDirectory = Path.Combine(Directory.GetCurrentDirectory(), "Output"); + } + + Directory.CreateDirectory(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "FModel")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Backups")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Exports")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Saves")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Textures")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Sounds")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, "Logs")); + Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data")); + + Log.Logger = new LoggerConfiguration().WriteTo.Console(theme: AnsiConsoleTheme.Literate).WriteTo.File( + path: Path.Combine(UserSettings.Default.OutputDirectory, "Logs", $"FModel-Log-{DateTime.Now:yyyy-MM-dd}.txt"), + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [FModel] [{Level:u3}] {Message:lj}{NewLine}{Exception}").CreateLogger(); + + Log.Information("Version {Version}", Constants.APP_VERSION); + Log.Information("{OS}", GetOperatingSystemProductName()); + Log.Information("{RuntimeVer}", RuntimeInformation.FrameworkDescription); + Log.Information("Culture {SysLang}", Thread.CurrentThread.CurrentUICulture); } - private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + private void AppExit(object sender, ExitEventArgs e) { - string errorMessage = string.Format(FModel.Properties.Resources.UnhandledExceptionOccured, e.Exception.Message); - DebugHelper.WriteException(e.Exception, "thrown in App.xaml.cs by OnDispatcherUnhandledException"); - DarkMessageBoxHelper.Show(errorMessage, FModel.Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error, true); + Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––"); + Log.CloseAndFlush(); + UserSettings.Save(); + Environment.Exit(0); + } + + private void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + Log.Error("{Exception}", e.Exception); + + var messageBox = new MessageBoxModel + { + Text = $"An unhandled exception occurred: {e.Exception.Message}", + Caption = "Fatal Error", + Icon = MessageBoxImage.Error, + Buttons = new[] + { + MessageBoxButtons.Custom("Restart", EErrorKind.Restart), + MessageBoxButtons.Custom("OK", EErrorKind.Ignore) + }, + IsSoundEnabled = false + }; + + MessageBox.Show(messageBox); + if (messageBox.Result == MessageBoxResult.Custom && (EErrorKind) messageBox.ButtonPressed.Id != EErrorKind.Ignore) + { + ApplicationService.ApplicationView.Restart(); + } + e.Handled = true; } - internal static void SetFramerate() + private string GetOperatingSystemProductName() { - if (!framerateSet) + var productName = string.Empty; + try { - System.Windows.Media.Animation.Timeline.DesiredFrameRateProperty.OverrideMetadata( - typeof(System.Windows.Media.Animation.Timeline), - new FrameworkPropertyMetadata { DefaultValue = 10 }); - framerateSet = true; + productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine); } + catch + { + // ignored + } + + if (string.IsNullOrEmpty(productName)) + productName = Environment.OSVersion.VersionString; + + return $"{productName} ({(Environment.Is64BitOperatingSystem ? "64" : "32")}-bit)"; + } + + private string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser) + { + using var rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path); + if (rk != null) + return rk.GetValue(name, null) as string; + return string.Empty; } } -} +} \ No newline at end of file diff --git a/FModel/Constants.cs b/FModel/Constants.cs new file mode 100644 index 00000000..2a4603da --- /dev/null +++ b/FModel/Constants.cs @@ -0,0 +1,63 @@ +using System; +using System.Reflection; +using CUE4Parse.UE4.Objects.Core.Misc; + +namespace FModel +{ + public static class Constants + { + public static readonly string APP_VERSION = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); + public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000"; + public static readonly FGuid ZERO_GUID = new(0U); + + public const string WHITE = "#DAE5F2"; + public const string RED = "#E06C75"; + public const string GREEN = "#98C379"; + public const string YELLOW = "#E5C07B"; + public const string BLUE = "#528BCC"; + + public const string DONATE_LINK = "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EP9SSWG8MW4UC&source=url"; + public const string CHANGELOG_LINK = "https://github.com/iAmAsval/FModel/releases/latest"; + public const string ISSUE_LINK = "https://github.com/iAmAsval/FModel/issues/new"; + public const string DISCORD_LINK = "https://discord.gg/fdkNYYQ"; + + public const string _FN_LIVE_TRIGGER = "fortnite-live.manifest"; + public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest"; + + public static string GetRandomColor() + { + return _randomColors[_random.Next(0, 255)]; + } + + private static readonly Random _random = new(Environment.TickCount); + private static readonly string[] _randomColors = + { + "F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C", + "FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63", + "D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7", + "CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB", + "D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8", + "4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB", + "5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE", + "E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1", + "82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4", + "039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2", + "80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF", + "00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B", + "00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784", + "66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853", + "F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E", + "CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39", + "C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4", + "FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00", + "FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000", + "FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D", + "FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00", + "FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C", + "FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548", + "6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E", + "757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B", + "546E7A", "455A64", "37474F", "263238", "000000", + }; + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/BB/BaseBreakersIcon.cs b/FModel/Creator/Bases/BB/BaseBreakersIcon.cs new file mode 100644 index 00000000..c5f514d6 --- /dev/null +++ b/FModel/Creator/Bases/BB/BaseBreakersIcon.cs @@ -0,0 +1,43 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Creator.Bases.FN; +using SkiaSharp; + +namespace FModel.Creator.Bases.BB +{ + public class BaseBreakersIcon : BaseIcon + { + public BaseBreakersIcon(UObject uObject, EIconStyle style) : base(uObject, style) + { + SeriesBackground = Utils.GetBitmap("WorldExplorers/Content/UMG/Materials/t_TextGradient.t_TextGradient"); + Background = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("636363")}; + Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")}; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FSoftObjectPath iconTextureAssetData, "IconTextureAssetData")) + Preview = Utils.GetBitmap(iconTextureAssetData); + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description")) + Description = description.Text; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + + return SKImage.FromBitmap(ret); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/BaseBBDefinition.cs b/FModel/Creator/Bases/BaseBBDefinition.cs deleted file mode 100644 index 3d5e38d9..00000000 --- a/FModel/Creator/Bases/BaseBBDefinition.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Windows; -using FModel.Creator.Rarities; -using FModel.Creator.Texts; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; -using SkiaSharp; - -namespace FModel.Creator.Bases -{ - public class BaseBBDefinition : IBase - { - public SKBitmap FallbackImage; - public SKBitmap IconImage; - public SKBitmap RarityBackgroundImage; - public SKColor[] RarityBackgroundColors; - public SKColor[] RarityBorderColor; - public string DisplayName; - public string Description; - public int Width = 512; - public int Height = 512; - public int Margin = 2; - - public BaseBBDefinition(string exportType) - { - FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream); - RarityBackgroundImage = null; - IconImage = FallbackImage; - RarityBackgroundColors = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("636363") }; - RarityBorderColor = new[] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") }; - DisplayName = ""; - Description = ""; - } - - public BaseBBDefinition(IUExport export, string exportType) : this(exportType) - { - if (export.GetExport("IconTextureAssetData") is {} previewImage) - IconImage = Utils.GetSoftObjectTexture(previewImage); - else if (export.GetExport("IconTextureAssetData") is {} iconTexture) - IconImage = Utils.GetObjectTexture(iconTexture); - - if (export.GetExport("DisplayName") is {} displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - if (export.GetExport("Description") is {} description) - Description = Text.GetTextPropertyBase(description); - - RarityBackgroundImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/battle-breakers-item-background.png"))?.Stream); - } - - SKBitmap IBase.FallbackImage => FallbackImage; - SKBitmap IBase.IconImage => IconImage; - SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors; - SKColor[] IBase.RarityBorderColor => RarityBorderColor; - string IBase.DisplayName => DisplayName; - string IBase.Description => Description; - int IBase.Width => Width; - int IBase.Height => Height; - int IBase.Margin => Margin; - } -} \ No newline at end of file diff --git a/FModel/Creator/Bases/BaseBundle.cs b/FModel/Creator/Bases/BaseBundle.cs deleted file mode 100644 index b6d49b61..00000000 --- a/FModel/Creator/Bases/BaseBundle.cs +++ /dev/null @@ -1,102 +0,0 @@ -using FModel.Creator.Bundles; -using FModel.Creator.Texts; -using System.Collections.Generic; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class BaseBundle - { - public Header DisplayStyle; - public string DisplayName; - public string FolderName; - public string Watermark; - public int Width = 1024; - public int HeaderHeight = 261; // height is the header basically - public int AdditionalSize = 50; // must be increased depending on the number of quests to draw - public bool IsDisplayNameShifted; - public List Quests; - public List CompletionRewards; - - public BaseBundle() - { - DisplayStyle = new Header(); - DisplayName = ""; - FolderName = ""; - Watermark = Properties.Settings.Default.ChallengeBannerWatermark; - Quests = new List(); - CompletionRewards = new List(); - } - - /// - /// used for the settings - /// - public BaseBundle(string watermark) : this() - { - DisplayName = "{DisplayName}"; - FolderName = "{FolderName}"; - Watermark = watermark; - Quests.Add(new Quest { Description = "", Count = 999, Reward = null }); - AdditionalSize += 89; - } - - public BaseBundle(IUExport export, string assetFolder) : this() - { - if (export.GetExport("DisplayStyle") is StructProperty displayStyle) - DisplayStyle = new Header(displayStyle, assetFolder); - if (export.GetExport("DisplayName") is TextProperty displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - - if (export.GetExport("CareerQuestBitShifts") is ArrayProperty careerQuestBitShifts) - { - foreach (SoftObjectProperty questPath in careerQuestBitShifts.Value) - { - Package p = Utils.GetPropertyPakPackage(questPath.Value.AssetPathName.String); - if (p.HasExport() && !p.Equals(default)) - { - var obj = p.GetExport(); - if (obj != null) - Quests.Add(new Quest(obj)); - } - } - } - - if (export.GetExport("BundleCompletionRewards") is ArrayProperty bundleCompletionRewards) - { - foreach (StructProperty completionReward in bundleCompletionRewards.Value) - { - if (completionReward.Value is UObject reward && - reward.TryGetValue("CompletionCount", out var c) && c is IntProperty completionCount && - reward.TryGetValue("Rewards", out var r) && r is ArrayProperty rewards) - { - foreach (StructProperty rew in rewards.Value) - { - if (rew.Value is UObject re && - re.TryGetValue("Quantity", out var q) && q is IntProperty quantity && - re.TryGetValue("TemplateId", out var t) && t is StrProperty templateId && - re.TryGetValue("ItemDefinition", out var d) && d is SoftObjectProperty itemDefinition) - { - if (!itemDefinition.Value.AssetPathName.IsNone && - !itemDefinition.Value.AssetPathName.String.Contains("/Items/Tokens/") && - !itemDefinition.Value.AssetPathName.String.Contains("/Items/Quests")) - { - CompletionRewards.Add(new CompletionReward(completionCount, quantity, itemDefinition)); - } - else if (!string.IsNullOrEmpty(templateId.Value)) - { - CompletionRewards.Add(new CompletionReward(completionCount, quantity, templateId.Value)); - } - } - } - } - } - } - - FolderName = assetFolder; - AdditionalSize += 95 * Quests.Count; - if (CompletionRewards.Count > 0) AdditionalSize += 50 + (95 * CompletionRewards.Count); - } - } -} diff --git a/FModel/Creator/Bases/BaseGCosmetic.cs b/FModel/Creator/Bases/BaseGCosmetic.cs deleted file mode 100644 index c6c04fd7..00000000 --- a/FModel/Creator/Bases/BaseGCosmetic.cs +++ /dev/null @@ -1,106 +0,0 @@ -using FModel.Creator.Rarities; -using FModel.Creator.Texts; -using SkiaSharp; -using System; -using System.Windows; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class BaseGCosmetic : IBase - { - public SKBitmap FallbackImage; - public SKBitmap IconImage; - public SKColor[] RarityBackgroundColors; - public SKColor[] RarityBorderColor; - public SKBitmap RarityBackgroundImage1; - public SKBitmap RarityBackgroundImage2; - public string RarityDisplayName; - public string DisplayName; - public string Description; - public int Width = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons - public int Height = 512; - public int Margin = 2; - - public BaseGCosmetic(string exportType) - { - FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream); - IconImage = FallbackImage; - RarityBackgroundColors = new SKColor[2] { SKColor.Parse("FFFFFF"), SKColor.Parse("636363") }; - RarityBorderColor = new SKColor[2] { SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF") }; - RarityBackgroundImage1 = null; - RarityBackgroundImage2 = null; - RarityDisplayName = ""; - DisplayName = ""; - Description = ""; - Width = exportType switch - { - "GCosmeticCard" => 1536, - _ => 512 - }; - Height = exportType switch - { - "GCosmeticCard" => 450, // Not perfect, causes images to get stretched a bit, but actually allows text to show up so it works for now. - FireMonkey - _ => 512 - }; - } - - public BaseGCosmetic(IUExport export, string exportType) : this(exportType) - { - // rarity - EnumProperty r = export.GetExport("Rarity"); - Rarity.GetInGameRarity(this, r); - this.RarityDisplayName = r != null ? r?.Value.String["EXRarity::".Length..] : "Common"; - - // image - if (export.GetExport("IconTexture") is SoftObjectProperty previewImage) - this.IconImage = Utils.GetSoftObjectTexture(previewImage); - else if (export.GetExport("IconTexture") is ObjectProperty iconTexture) - this.IconImage = Utils.GetObjectTexture(iconTexture); - - // text - if (export.GetExport("DisplayName", "Title") is TextProperty displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - if (export.GetExport("Description") is TextProperty description) - Description = Text.GetTextPropertyBase(description); - - RarityBackgroundImage1 = Utils.GetTexture("/Game/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox"); - RarityBackgroundImage2 = Utils.GetTexture("/Game/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT"); - } - - public void Draw(SKCanvas c) - { - if (this.RarityBackgroundImage1 != null) - c.DrawBitmap(this.RarityBackgroundImage1, new SKRect(0, 0, Width, Height), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - if (this.RarityBackgroundImage2 != null) - c.DrawBitmap(this.RarityBackgroundImage2, new SKRect(0, 0, Width, Height), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColors.Transparent.WithAlpha(75) }); - - int x = this.Margin * (int)2.5; - int radi = 15; - c.DrawCircle(x + radi, x + radi, radi, new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient( - new SKPoint(radi, radi), - (radi * 2) / 5 * 4, - this.RarityBackgroundColors, - SKShaderTileMode.Clamp) - }); - } - - SKBitmap IBase.FallbackImage => FallbackImage; - SKBitmap IBase.IconImage => IconImage; - SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors; - SKColor[] IBase.RarityBorderColor => RarityBorderColor; - string IBase.DisplayName => DisplayName; - string IBase.Description => Description; - int IBase.Width => Width; - int IBase.Height => Height; - int IBase.Margin => Margin; - } -} diff --git a/FModel/Creator/Bases/BaseIcon.cs b/FModel/Creator/Bases/BaseIcon.cs deleted file mode 100644 index 14bb107c..00000000 --- a/FModel/Creator/Bases/BaseIcon.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using FModel.Creator.Icons; -using FModel.Creator.Rarities; -using FModel.Creator.Stats; -using FModel.Creator.Texts; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; -using FModel.Properties; -using FModel.Utils; -using SkiaSharp; - -namespace FModel.Creator.Bases -{ - public class BaseIcon : IBase - { - public SKBitmap FallbackImage; - public SKBitmap IconImage; - public SKBitmap RarityBackgroundImage; - public SKBitmap[] UserFacingFlags; - public SKColor[] RarityBackgroundColors; - public SKColor[] RarityBorderColor; - public string DisplayName; - public string Description; - public string ShortDescription; - public string CosmeticSource; - public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons - public int AdditionalSize; // must be increased if there are weapon stats, hero abilities or more to draw/show - public int Margin = 2; - public List Stats; - - public BaseIcon() - { - FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream); - IconImage = FallbackImage; - RarityBackgroundImage = null; - UserFacingFlags = null; - RarityBackgroundColors = new[] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") }; - RarityBorderColor = new[] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") }; - DisplayName = ""; - Description = ""; - ShortDescription = ""; - CosmeticSource = ""; - Stats = new List(); - } - - public BaseIcon(IUExport export, string assetName, bool forceHR) : this() - { - if (export.GetExport("Series") is { } series) - Serie.GetRarity(this, series); - else if (Settings.Default.UseGameColors) // override default green - Rarity.GetInGameRarity(this, export.GetExport("Rarity")); // uncommon will be triggered by Rarity being null - else if (export.GetExport("Rarity") is { } rarity) - Rarity.GetHardCodedRarity(this, rarity); - - if (export.GetExport("HeroDefinition", "WeaponDefinition") is { } itemDef) - LargeSmallImage.GetPreviewImage(this, itemDef, assetName, forceHR); - else if (export.GetExport(forceHR ? "LargePreviewImage" : "SmallPreviewImage", forceHR ? "ItemDisplayAsset" : "SmallImage", forceHR ? "SidePanelIcon" : "ToastIcon") is { } previewImage) - LargeSmallImage.GetPreviewImage(this, previewImage); - else if (export.GetExport("access_item") is { } accessItem) - { - Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var d = p.GetExport(); - if (d != null) - { - IconImage = new BaseIcon(d, accessItem.Value.Resource.ObjectName.String + ".uasset", false).IconImage; - } - } - } - - if (export.GetExport("DisplayName", "DefaultHeaderText", "UIDisplayName") is { } displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - } - - /// - /// Order: - /// 1. Rarity - /// 2. Image - /// 3. Text - /// 1. DisplayName - /// 2. Description - /// 3. Misc - /// 4. GameplayTags - /// 1. order doesn't matter - /// 2. the importance here is to get the description before gameplay tags - /// - public BaseIcon(IUExport export, string exportType, ref string assetName) : this() - { - // rarity - if (export.GetExport("Series") is { } series) - Serie.GetRarity(this, series); - else if (Settings.Default.UseGameColors) // override default green - Rarity.GetInGameRarity(this, export.GetExport("Rarity")); // uncommon will be triggered by Rarity being null - else if (export.GetExport("Rarity") is { } rarity) - Rarity.GetHardCodedRarity(this, rarity); - - // image - if (Settings.Default.UseItemShopIcon && - DisplayAssetImage.GetDisplayAssetImage(this, export, ref assetName)) - { } // ^^^^ will return false if image not found, if so, we try to get the normal icon - else if (export.GetExport("HeroDefinition", "WeaponDefinition") is { } itemDef) - LargeSmallImage.GetPreviewImage(this, itemDef, assetName); - else if (export.GetExport("LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset") is { } previewImage) - LargeSmallImage.GetPreviewImage(this, previewImage); - else if (export.GetExport("SmallPreviewImage", "ToastIcon") is { } smallPreviewImage) - IconImage = Utils.GetObjectTexture(smallPreviewImage); - else if (export.GetExport("IconBrush") is { } iconBrush) // abilities - LargeSmallImage.GetPreviewImage(this, iconBrush); - - // text - if (export.GetExport("DisplayName", "DefaultHeaderText", "UIDisplayName") is { } displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - if (export.GetExport("Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription") is { } description) - Description = Text.GetTextPropertyBase(description); - else if (export.GetExport("Description") is { } arrayDescription) // abilities - Description = Text.GetTextPropertyBase(arrayDescription); - if (export.GetExport("MaxStackSize") is { } maxStackSize) - ShortDescription = Text.GetMaxStackSize(maxStackSize); - else if (export.GetExport("XpRewardAmount") is { } xpRewardAmount) - ShortDescription = Text.GetXpRewardAmount(xpRewardAmount); - else if (export.GetExport("ShortDescription", "UIDisplaySubName") is { } shortDescription) - ShortDescription = Text.GetTextPropertyBase(shortDescription); - else if (exportType.Equals("AthenaItemWrapDefinition")) // if no ShortDescription it's most likely a wrap - ShortDescription = Localizations.GetLocalization("Fort.Cosmetics", "ItemWrapShortDescription", "Wrap"); - - // gameplaytags - if (export.GetExport("GameplayTags") is { } gameplayTags) - GameplayTag.GetGameplayTags(this, gameplayTags, exportType); - else if (export.GetExport("cosmetic_item") is { } cosmeticItem) // variants - CosmeticSource = cosmeticItem.Value.Resource.ObjectName.String; - - if (Settings.Default.DrawStats && export.GetExport("AmmoData") is { } ammoData) - Statistics.GetAmmoData(this, ammoData); - if (Settings.Default.DrawStats && export.GetExport("WeaponStatHandle") is { } weaponStatHandle && - (exportType.Equals("FortWeaponMeleeItemDefinition") || - (export.GetExport("StatList") is { } statList && - !statList.Value.AssetPathName.String.StartsWith("/Game/UI/Tooltips/NoTooltipStats")))) - { - Statistics.GetWeaponStats(this, weaponStatHandle); - } - if (Settings.Default.DrawStats && export.GetExport("HeroGameplayDefinition") is { } heroGameplayDefinition) - Statistics.GetHeroStats(this, heroGameplayDefinition); - - /* Please do not add Schematics support because it takes way too much memory */ - /* Thank the STW Dev Team for using a 5,69Mb file to get... Oh nvm, they all left */ - - AdditionalSize = 48 * Stats.Count; - } - - SKBitmap IBase.FallbackImage => FallbackImage; - SKBitmap IBase.IconImage => IconImage; - SKColor[] IBase.RarityBackgroundColors => RarityBackgroundColors; - SKColor[] IBase.RarityBorderColor => RarityBorderColor; - string IBase.DisplayName => DisplayName; - string IBase.Description => Description; - int IBase.Width => Size; - int IBase.Height => Size; - int IBase.Margin => Margin; - } -} diff --git a/FModel/Creator/Bases/BaseItemAccess.cs b/FModel/Creator/Bases/BaseItemAccess.cs deleted file mode 100644 index 8800e6df..00000000 --- a/FModel/Creator/Bases/BaseItemAccess.cs +++ /dev/null @@ -1,138 +0,0 @@ -using FModel.Creator.Rarities; -using FModel.Creator.Texts; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; -using SkiaSharp; -using SkiaSharp.HarfBuzz; - -namespace FModel.Creator.Bases -{ - public class BaseItemAccess - { - private readonly SKPaint descriptionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DescriptionTypeface, - TextSize = 13, - Color = SKColors.White, - }; - - public BaseIcon Item; - public string SItem; - public SKBitmap Lock; - public SKBitmap Unlock; - public string DisplayName; - public string Description; - public string UnlockDescription; - public int Size = 512; // keep it 512 (or a multiple of 512) if you don't want blurry icons - - public BaseItemAccess() - { - Item = new BaseIcon(); - SItem = ""; - Lock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128").Resize(24, 24); - Unlock = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128").Resize(24, 24); - DisplayName = ""; - Description = ""; - UnlockDescription = ""; - } - - public BaseItemAccess(IUExport export) : this() - { - if (export.GetExport("access_item") is ObjectProperty accessItem) - { - SItem = accessItem.Value.Resource.ObjectName.String; - Package p = Utils.GetPropertyPakPackage(accessItem.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var d = p.GetExport(); - if (d != null) - { - Item = new BaseIcon(d, SItem + ".uasset", true); - } - } - } - - if (export.GetExport("DisplayName") is TextProperty displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - if (export.GetExport("Description") is TextProperty description) - Description = Text.GetTextPropertyBase(description); - if (export.GetExport("UnlockDescription") is TextProperty unlockDescription) - UnlockDescription = Text.GetTextPropertyBase(unlockDescription); - } - - public void Draw(SKCanvas c) - { - Rarity.DrawRarity(c, Item); - - int size = 45; - int left = Size / 2; - SKPaint namePaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = size, - Color = SKColors.White, - TextAlign = SKTextAlign.Center, - }; - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(namePaint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(DisplayName, namePaint); - shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f; - - if (shapedTextWidth > (Size - (Item.Margin * 2))) - { - namePaint.TextSize -= 2; - } - else - { - break; - } - } - - c.DrawShapedText(shaper, DisplayName, (Size - shapedTextWidth) / 2f, Item.Margin * 8 + size, namePaint); - } - else - { - while (namePaint.MeasureText(DisplayName) > (Size - (Item.Margin * 2))) - { - namePaint.TextSize = size -= 2; - } - c.DrawText(DisplayName, left, Item.Margin * 8 + size, namePaint); - } - - int topBase = Item.Margin + size * 2; - c.DrawBitmap(Lock, new SKRect(50, topBase, 50 + Lock.Width, topBase + Lock.Height), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - Helper.DrawMultilineText(c, UnlockDescription, Size, Item.Margin, ETextSide.Left, - new SKRect(70 + Lock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase); - - c.DrawBitmap(Unlock, new SKRect(50, topBase, 50 + Unlock.Width, topBase + Unlock.Height), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - Helper.DrawMultilineText(c, Description, Size, Item.Margin, ETextSide.Left, - new SKRect(70 + Unlock.Width, topBase + 10, Size - 50, 256), descriptionPaint, out topBase); - - int h = Size - Item.Margin - topBase; - c.DrawBitmap(Item.IconImage ?? Item.FallbackImage, new SKRect(left - h / 2, topBase, left + h / 2, Size - Item.Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - c.DrawText(SItem, Size - (Item.Margin * 2.5f), Size - (Item.Margin * 2.5f), new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.BottomDefaultTypeface ?? Text.TypeFaces.DefaultTypeface, - TextSize = 15, - Color = SKColors.White, - TextAlign = SKTextAlign.Right, - }); - } - } -} diff --git a/FModel/Creator/Bases/BaseMapUIData.cs b/FModel/Creator/Bases/BaseMapUIData.cs deleted file mode 100644 index fe959efb..00000000 --- a/FModel/Creator/Bases/BaseMapUIData.cs +++ /dev/null @@ -1,96 +0,0 @@ -using FModel.Creator.Texts; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; -using SkiaSharp; - -namespace FModel.Creator.Bases -{ - public class BaseMapUIData - { - public SKBitmap Splash; - public SKBitmap VLogo; - public string DisplayName; - public string Description; - public string Coordinates; - public int Width = 1920; - public int Height = 1080; - - public BaseMapUIData() - { - Splash = null; - VLogo = null; - DisplayName = ""; - Description = ""; - Coordinates = ""; - } - - public BaseMapUIData(IUExport export) : this() - { - if (export.GetExport("DisplayName") is TextProperty displayName) - DisplayName = Text.GetTextPropertyBase(displayName) ?? ""; - if (export.GetExport("Description") is TextProperty description) - Description = Text.GetTextPropertyBase(description) ?? ""; - if (export.GetExport("Coordinates") is TextProperty coordinates) - Coordinates = Text.GetTextPropertyBase(coordinates) ?? ""; - - if (export.GetExport("Splash") is ObjectProperty icon) - Splash = Utils.GetObjectTexture(icon); - - VLogo = Utils.GetTexture("/Game/UI/Shared/Icons/Valorant_logo_cutout").Resize(48, 48); - - if (Splash != null) - { - Width = Splash.Width; - Height = Splash.Height; - } - } - - public void Draw(SKCanvas c) - { - int paddingLR = 80; - int paddingTB = 35; - int nameSize = 200; - int descriptionSize = 30; - using var namePaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = nameSize, - TextAlign = SKTextAlign.Left, - Color = SKColor.Parse("FFFBFA") - }; - while (namePaint.MeasureText(DisplayName) > Width - (paddingLR * 2)) - { - namePaint.TextSize = nameSize -= 2; - } - using var descriptionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DescriptionTypeface, - TextSize = descriptionSize, - TextAlign = SKTextAlign.Left, - Color = SKColor.Parse("FFFBFA") - }; - while (descriptionPaint.MeasureText(Description) > Width - (paddingLR * 2)) - { - descriptionPaint.TextSize = descriptionSize -= 2; - } - - c.DrawBitmap(Splash, new SKRect(0, 0, Width, Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - c.DrawText(DisplayName.ToUpper(), paddingLR, paddingTB + namePaint.TextSize, namePaint); - c.DrawRect(new SKRect(paddingLR + 2.5f, paddingTB + 25 + namePaint.TextSize, paddingLR + 202.5f, paddingTB + 27.5f + namePaint.TextSize), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("5AFFFBFA") }); - c.DrawText(Description, paddingLR + 2.5f, paddingTB + 40 + namePaint.TextSize + descriptionPaint.TextSize, descriptionPaint); - - descriptionPaint.Typeface = Text.TypeFaces.BundleDefaultTypeface; - c.DrawText(Coordinates.ToUpper(), paddingLR, Height - paddingTB - descriptionPaint.TextSize, descriptionPaint); - - if (VLogo != null) - { - c.DrawBitmap(VLogo, new SKRect(Width - VLogo.Width - paddingLR, paddingLR, Width - paddingLR, paddingLR + VLogo.Height), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - c.DrawRect(new SKRect(Width - VLogo.Width - paddingLR, paddingLR + VLogo.Height + 5, Width - paddingLR, paddingLR + VLogo.Height + 7.5f), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true, Color = SKColor.Parse("FFFBFA") }); - } - } - } -} diff --git a/FModel/Creator/Bases/BaseOffer.cs b/FModel/Creator/Bases/BaseOffer.cs deleted file mode 100644 index e8b2abcf..00000000 --- a/FModel/Creator/Bases/BaseOffer.cs +++ /dev/null @@ -1,93 +0,0 @@ -using SkiaSharp; -using System; -using System.Windows; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class BaseOffer - { - public SKBitmap FallbackImage; - public SKBitmap IconImage; - public SKColor[] RarityBackgroundColors; - public SKColor RarityBorderColor; - public int Size = 512; - public int Margin = 2; - - public BaseOffer() - { - FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream); - IconImage = FallbackImage; - RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") }; - RarityBorderColor = SKColor.Parse("9092AB"); - } - - public BaseOffer(IUExport export) : this() - { - if (export.GetExport("DetailsImage", "TileImage") is StructProperty typeImage) - { - if (typeImage.Value is UObject t && t.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject) - { - IconImage = Utils.GetObjectTexture(resourceObject); - } - } - - if (export.GetExport("Gradient") is StructProperty gradient) - { - if (gradient.Value is UObject g && - g.TryGetValue("Start", out var s1) && s1 is StructProperty t1 && t1.Value is FLinearColor start && - g.TryGetValue("Stop", out var s2) && s2 is StructProperty t2 && t2.Value is FLinearColor stop) - { - RarityBackgroundColors = new SKColor[2] { SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex) }; - } - } - - if (export.GetExport("Background") is StructProperty background) - { - if (background.Value is FLinearColor b) - { - RarityBorderColor = SKColor.Parse(b.Hex); - } - } - } - - public void DrawBackground(SKCanvas c) - { - if (RarityBackgroundColors[0] == RarityBackgroundColors[1]) - RarityBackgroundColors[0] = RarityBorderColor; - - RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1); - RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2); - bool reverse = l1 > l2; - - // border - c.DrawRect(new SKRect(0, 0, Size, Size), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = RarityBorderColor - }); - - c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient( - new SKPoint(Size / 2, Size / 2), - Size / 5 * 4, - new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] }, - SKShaderTileMode.Clamp) - }); - } - - public void DrawImage(SKCanvas c) - { - c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - } - } -} diff --git a/FModel/Creator/Bases/BaseOfferMaterial.cs b/FModel/Creator/Bases/BaseOfferMaterial.cs deleted file mode 100644 index b14bf728..00000000 --- a/FModel/Creator/Bases/BaseOfferMaterial.cs +++ /dev/null @@ -1,121 +0,0 @@ -using SkiaSharp; -using System; -using System.Windows; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class BaseOfferMaterial - { - public SKBitmap FallbackImage; - public SKBitmap IconImage; - public SKBitmap RarityBackgroundImage; - public SKColor[] RarityBackgroundColors; - public SKColor RarityBorderColor; - public int Size = 512; - public int Margin = 2; - - public BaseOfferMaterial() - { - FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png")).Stream); - IconImage = null; - RarityBackgroundImage = null; - RarityBackgroundColors = new SKColor[2] { SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69") }; - RarityBorderColor = SKColor.Parse("9092AB"); - } - - public BaseOfferMaterial(IUExport export) : this() - { - if (export.GetExport("VectorParameterValues") is ArrayProperty vectorParameterValues) - { - foreach (StructProperty vectorParameter in vectorParameterValues.Value) - { - if (vectorParameter.Value is UObject parameter && - parameter.TryGetValue("ParameterValue", out var i) && i is StructProperty v && v.Value is FLinearColor value && - parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info && - info.TryGetValue("Name", out var j1) && j1 is NameProperty name) - { - if (name.Value.String.Equals("Background_Color_A")) - { - RarityBackgroundColors[0] = SKColor.Parse(value.Hex); - RarityBorderColor = RarityBackgroundColors[0]; - } - else if (name.Value.String.Equals("Background_Color_B")) - { - RarityBackgroundColors[1] = SKColor.Parse(value.Hex); - } - } - } - } - - if (export.GetExport("TextureParameterValues") is ArrayProperty textureParameterValues) - { - foreach (StructProperty textureParameter in textureParameterValues.Value) - { - if (textureParameter.Value is UObject parameter && - parameter.TryGetValue("ParameterValue", out var i) && i is ObjectProperty value && - parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info && - info.TryGetValue("Name", out var j1) && j1 is NameProperty name) - { - if (name.Value.String.Equals("SeriesTexture")) - { - RarityBackgroundImage = Utils.GetObjectTexture(value); - } - else if (IconImage == null && value.Value.Resource.OuterIndex.Resource != null && (name.Value.String.Equals("OfferImage") || name.Value.String.Contains("Texture"))) - { - IconImage = Utils.GetObjectTexture(value); - if (IconImage == null) IconImage = Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_1"); - if (IconImage == null) IconImage = Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_01"); - } - } - } - } - - if (IconImage == null) - IconImage = FallbackImage; - } - - public void DrawBackground(SKCanvas c) - { - if (RarityBackgroundColors[0] == RarityBackgroundColors[1]) - RarityBackgroundColors[0] = RarityBorderColor; - - RarityBackgroundColors[0].ToHsl(out var _, out var _, out var l1); - RarityBackgroundColors[1].ToHsl(out var _, out var _, out var l2); - bool reverse = l1 > l2; - - // border - c.DrawRect(new SKRect(0, 0, Size, Size), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = RarityBorderColor - }); - - c.DrawRect(new SKRect(Margin, Margin, Size - Margin, Size - Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient( - new SKPoint(Size / 2, Size / 2), - Size / 5 * 4, - new SKColor[2] { reverse ? RarityBackgroundColors[0] : RarityBackgroundColors[1], reverse ? RarityBackgroundColors[1] : RarityBackgroundColors[0] }, - SKShaderTileMode.Clamp) - }); - } - - public void DrawImage(SKCanvas c) - { - if (RarityBackgroundImage != null) - c.DrawBitmap(RarityBackgroundImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - c.DrawBitmap(IconImage ?? FallbackImage, new SKRect(Margin, Margin, Size - Margin, Size - Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - } - } -} diff --git a/FModel/Creator/Bases/BasePlaylist.cs b/FModel/Creator/Bases/BasePlaylist.cs deleted file mode 100644 index 75d71839..00000000 --- a/FModel/Creator/Bases/BasePlaylist.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Windows; - -using FModel.Creator.Texts; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; -using FModel.Utils; - -using Fortnite_API.Objects; -using Fortnite_API.Objects.V1; -using SkiaSharp; - -namespace FModel.Creator.Bases -{ - public class BasePlaylist : IBase - { - public SKBitmap FallbackImage { get; } - public SKBitmap IconImage { get; } - public SKColor[] RarityBackgroundColors { get; } - public SKColor[] RarityBorderColor { get; } - public string DisplayName { get; } - public string Description { get; } - public int Width { get; } - public int Height { get; } - public int Margin { get; } = 2; - - public BasePlaylist(IUExport export) - { - FallbackImage = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream); - IconImage = FallbackImage; - RarityBackgroundColors = new[] { SKColor.Parse("5EBC36"), SKColor.Parse("305C15") }; - RarityBorderColor = new[] { SKColor.Parse("74EF52"), SKColor.Parse("74EF52") }; - - if (export.GetExport("UIDisplayName", "DisplayName") is { } displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - if (export.GetExport("UIDescription", "Description") is { } description) - Description = Text.GetTextPropertyBase(description); - - Width = 1024; - Height = 512; - - if (export.GetExport("PlaylistName") is { } playlistName && !playlistName.Value.IsNone) - { - ApiResponse playlist = Endpoints.FortniteAPIClient.V1.Playlists.Get(playlistName.Value.String); - - if (playlist.IsSuccess && playlist.Data.Images.HasShowcase) - { - byte[] imageBytes = Endpoints.GetRawData(playlist.Data.Images.Showcase); - - if (imageBytes != null) - { - IconImage = SKBitmap.Decode(imageBytes); - Width = IconImage.Width; - Height = IconImage.Height; - } - } - } - } - } -} diff --git a/FModel/Creator/Bases/BaseSeason.cs b/FModel/Creator/Bases/BaseSeason.cs deleted file mode 100644 index 3768f6cb..00000000 --- a/FModel/Creator/Bases/BaseSeason.cs +++ /dev/null @@ -1,318 +0,0 @@ -using FModel.Creator.Bundles; -using FModel.Creator.Texts; -using SkiaSharp; -using SkiaSharp.HarfBuzz; -using System; -using System.Collections.Generic; -using System.Linq; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class BaseSeason - { - public string DisplayName; - public string FolderName; - public string Watermark; - public Reward FirstWinReward; - public Dictionary> BookXpSchedule; - public Header DisplayStyle; - public int Width = 1024; - public int HeaderHeight = 261; // height is the header basically - public int AdditionalSize = 50; // must be increased depending on the number of quests to draw - - public BaseSeason() - { - DisplayName = ""; - FolderName = ""; - Watermark = Properties.Settings.Default.ChallengeBannerWatermark; - FirstWinReward = null; - BookXpSchedule = new Dictionary>(); - DisplayStyle = new Header(); - } - - public BaseSeason(IUExport export, string assetFolder) : this() - { - if (export.GetExport("DisplayName") is TextProperty displayName) - DisplayName = Text.GetTextPropertyBase(displayName); - - if (export.GetExport("SeasonFirstWinRewards") is StructProperty s && s.Value is UObject seasonFirstWinRewards && - seasonFirstWinRewards.GetExport("Rewards") is ArrayProperty rewards) - { - foreach (StructProperty reward in rewards.Value) - { - if (reward.Value is UObject o && - o.GetExport("ItemDefinition") is SoftObjectProperty itemDefinition && - o.GetExport("Quantity") is IntProperty quantity) - { - FirstWinReward = new Reward(quantity, itemDefinition.Value); - } - } - } - - if (export.GetExport("BookXpScheduleFree") is StructProperty r2 && r2.Value is UObject bookXpScheduleFree && - bookXpScheduleFree.GetExport("Levels") is ArrayProperty levels2) - { - for (int i = 0; i < levels2.Value.Length; i++) - { - BookXpSchedule[i] = new List(); // init list for all reward index and once - if (levels2.Value[i] is StructProperty level && level.Value is UObject l && - l.GetExport("Rewards") is ArrayProperty elRewards && elRewards.Value.Length > 0) - { - foreach (StructProperty reward in elRewards.Value) - { - if (reward.Value is UObject o && - o.GetExport("ItemDefinition") is SoftObjectProperty itemDefinition && - !itemDefinition.Value.AssetPathName.String.Contains("/Items/Tokens/") && - o.GetExport("Quantity") is IntProperty quantity) - { - BookXpSchedule[i].Add(new Reward(quantity, itemDefinition.Value)); - } - } - } - } - } - - if (export.GetExport("BookXpSchedulePaid") is StructProperty r1 && r1.Value is UObject bookXpSchedulePaid && - bookXpSchedulePaid.GetExport("Levels") is ArrayProperty levels1) - { - for (int i = 0; i < levels1.Value.Length; i++) - { - if (levels1.Value[i] is StructProperty level && level.Value is UObject l && - l.GetExport("Rewards") is ArrayProperty elRewards && elRewards.Value.Length > 0) - { - foreach (StructProperty reward in elRewards.Value) - { - if (reward.Value is UObject o && - o.GetExport("ItemDefinition") is SoftObjectProperty itemDefinition && - o.GetExport("Quantity") is IntProperty quantity) - { - BookXpSchedule[i].Add(new Reward(quantity, itemDefinition.Value)); - } - } - } - } - } - - FolderName = assetFolder; - AdditionalSize += 100 * (BookXpSchedule.Count / 10); - } - - public void Draw(SKCanvas c) - { - DrawHeaderPaint(c); - DrawHeaderText(c); - - using SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - TextSize = 15, - Color = SKColors.White, - TextAlign = SKTextAlign.Center, - Typeface = Text.TypeFaces.BottomDefaultTypeface ?? Text.TypeFaces.DisplayNameTypeface - }; - using SKPaint bg = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = SKColor.Parse("#0F5CAF"), - }; - using SKPaint rarity = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = SKColors.White, - }; - using SKPaint icon = new SKPaint - { - FilterQuality = SKFilterQuality.High, - IsAntialias = true - }; - - int y = HeaderHeight + 50; - int defaultSize = 80; - int x = 20; - foreach (var (index, reward) in BookXpSchedule) - { - if (index == 0 || reward.Count == 0) - continue; - - c.DrawText(index.ToString(), new SKPoint(x + (defaultSize / 2), y - 5), paint); - - var theReward = reward[0].TheReward; - if (theReward == null) - continue; - - rarity.Color = theReward.RarityBackgroundColors[0]; - c.DrawRect(new SKRect(x, y, x + defaultSize, y + defaultSize), bg); - c.DrawBitmap(reward[0].RewardIcon, new SKPoint(x, y), icon); - var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; - pathBottom.MoveTo(x, y + defaultSize); - pathBottom.LineTo(x, y + defaultSize - (defaultSize / 25 * 2.5f)); - pathBottom.LineTo(x + defaultSize, y + defaultSize - (defaultSize / 25 * 4.5f)); - pathBottom.LineTo(x + defaultSize, y + defaultSize); - pathBottom.Close(); - c.DrawPath(pathBottom, rarity); - - if (index != 1 && index % 10 == 0) - { - y += defaultSize + 20; - x = 20; - } - else - { - x += defaultSize + 20; - } - } - } - - private void DrawHeaderText(SKCanvas c) - { - using SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.BundleDisplayNameTypeface, - TextSize = 50, - Color = SKColors.White, - TextAlign = SKTextAlign.Left, - }; - - string text = DisplayName.ToUpper(); - int x = 300; - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - - SKShaper shaper = new SKShaper(paint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(text, paint); - shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; - - if (shapedTextWidth > Width) - { - paint.TextSize -= 2; - } - else - { - break; - } - } - //only trigger the fix if The Last char is a digit - if (char.IsDigit(text[text.Length - 1])) - { - int s = text.Count(k => Char.IsDigit(k)); - float numberwidth = paint.MeasureText(text.Substring(text.Length - s)); - - //Draw Number Separately - c.DrawShapedText(shaper, text.Substring(text.Length - s), x, 155, paint); - - c.DrawShapedText(shaper, text.Substring(0, text.Length - s), x + numberwidth, 155, paint); - } - else - { - //feels bad man - c.DrawShapedText(shaper, text, x, 155, paint); - - } - - } - else - { - while (paint.MeasureText(text) > (Width - x)) - { - paint.TextSize -= 2; - } - c.DrawText(text, x, 155, paint); - } - - paint.Color = SKColors.White.WithAlpha(150); - paint.TextAlign = SKTextAlign.Right; - paint.TextSize = 23; - paint.Typeface = Text.TypeFaces.DefaultTypeface; - c.DrawText(Watermark - .Replace("{BundleName}", text) - .Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")), - Width - 25, HeaderHeight - 40, paint); - - paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; - paint.Color = DisplayStyle.SecondaryColor; - paint.TextAlign = SKTextAlign.Left; - paint.TextSize = 30; - c.DrawText(FolderName.ToUpper(), x, 95, paint); - } - - private void DrawHeaderPaint(SKCanvas c) - { - c.DrawRect(new SKRect(0, 0, Width, HeaderHeight), new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = DisplayStyle.PrimaryColor - }); - - if (DisplayStyle.CustomBackground != null && DisplayStyle.CustomBackground.Height != DisplayStyle.CustomBackground.Width) - { - var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen }; - if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity); - c.DrawBitmap(DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint); - } - else if (DisplayStyle.DisplayImage != null) - { - if (DisplayStyle.CustomBackground != null && DisplayStyle.CustomBackground.Height == DisplayStyle.CustomBackground.Width) - c.DrawBitmap(DisplayStyle.CustomBackground, new SKRect(0, 0, HeaderHeight, HeaderHeight), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - BlendMode = SKBlendMode.Screen, - ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, DisplayStyle.SecondaryColor.WithAlpha(25)) - }); - - c.DrawBitmap(DisplayStyle.DisplayImage, new SKRect(0, 0, HeaderHeight, HeaderHeight), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, DisplayStyle.SecondaryColor.WithAlpha(50)) - }); - } - - if (FirstWinReward != null) - { - c.DrawBitmap(FirstWinReward.TheReward.IconImage.Resize(HeaderHeight, HeaderHeight), new SKPoint(0, 0), new SKPaint - { - FilterQuality = SKFilterQuality.High, - IsAntialias = true - }); - } - - SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd }; - pathTop.MoveTo(0, HeaderHeight); - pathTop.LineTo(Width, HeaderHeight); - pathTop.LineTo(Width, HeaderHeight - 19); - pathTop.LineTo(Width / 2 + 7, HeaderHeight - 23); - pathTop.LineTo(Width / 2 + 13, HeaderHeight - 7); - pathTop.LineTo(0, HeaderHeight - 19); - pathTop.Close(); - c.DrawPath(pathTop, new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = DisplayStyle.SecondaryColor, - ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, DisplayStyle.AccentColor.WithAlpha(75)) - }); - - c.DrawRect(new SKRect(0, HeaderHeight, Width, HeaderHeight + AdditionalSize), new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want - }); - } - } -} diff --git a/FModel/Creator/Bases/BaseUIData.cs b/FModel/Creator/Bases/BaseUIData.cs deleted file mode 100644 index 1be7f5d5..00000000 --- a/FModel/Creator/Bases/BaseUIData.cs +++ /dev/null @@ -1,177 +0,0 @@ -using FModel.Creator.Stats; -using FModel.Creator.Texts; -using SkiaSharp; -using System.Collections.Generic; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class BaseUIData - { - private readonly SKPaint descriptionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DescriptionTypeface, - TextSize = 19.5f, - Color = SKColor.Parse("939498"), - }; - - public SKBitmap IconImage; - public string DisplayName; - public string Description; - public List Abilities; - public int Width = 768; // keep it 512 (or a multiple of 512) if you don't want blurry icons - public int AdditionalWidth = 0; - public int Height = 96; - public int Margin = 3; - - public BaseUIData() - { - IconImage = null; - DisplayName = ""; - Description = ""; - Abilities = new List(); - } - - public BaseUIData(IUExport[] exports, int baseIndex) : this() - { - if (exports[baseIndex].GetExport("DisplayName") is TextProperty displayName) - DisplayName = Text.GetTextPropertyBase(displayName) ?? ""; - if (exports[baseIndex].GetExport("Description") is TextProperty description) - { - Description = Text.GetTextPropertyBase(description) ?? ""; - if (Description.Equals(DisplayName)) Description = string.Empty; - if (!string.IsNullOrEmpty(Description)) - { - Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Count; - Height += (int)descriptionPaint.TextSize; - } - } - - if (exports[baseIndex].GetExport("StoreFeaturedImage", "FullRender", "VerticalPromoImage", "LargeIcon", "DisplayIcon2", "DisplayIcon") is ObjectProperty icon) - { - SKBitmap raw = Utils.GetObjectTexture(icon); - if (raw != null) - { - float coef = (float)Width / (float)raw.Width; - int sizeX = (int)(raw.Width * coef); - int sizeY = (int)(raw.Height * coef); - Height += sizeY; - IconImage = raw.Resize(sizeX, sizeY); - } - } - - if (exports[baseIndex].GetExport("Abilities") is MapProperty abilities) - { - AdditionalWidth = 768; - foreach (var (_, value) in abilities.Value) - { - if (value is ObjectProperty o && o.Value.Resource == null && o.Value.Index > 0) - { - Statistic s = new Statistic(); - if (exports[o.Value.Index - 1].GetExport("DisplayName") is TextProperty aDisplayName) - s.DisplayName = Text.GetTextPropertyBase(aDisplayName) ?? ""; - if (exports[o.Value.Index - 1].GetExport("Description") is TextProperty aDescription) - { - s.Description = Text.GetTextPropertyBase(aDescription) ?? ""; - if (!string.IsNullOrEmpty(Description)) - { - s.Height += (int)descriptionPaint.TextSize * Helper.SplitLines(s.Description, descriptionPaint, Width - Margin).Count; - s.Height += (int)descriptionPaint.TextSize * 3; - } - } - if (exports[o.Value.Index - 1].GetExport("DisplayIcon") is ObjectProperty displayIcon) - { - SKBitmap raw = Utils.GetObjectTexture(displayIcon); - if (raw != null) s.Icon = raw.Resize(128, 128); - } - Abilities.Add(s); - } - } - } - } - - public void Draw(SKCanvas c) - { - DrawCenteredTitle(c, DisplayName, 67.5f, out var textSize); - - Helper.DrawMultilineText(c, Description, Width, Margin, ETextSide.Center, - new SKRect(Margin, textSize + 56.25f, Width - Margin, Height - 37.5f), descriptionPaint, out var yPos); - - if (IconImage != null) - c.DrawBitmap(IconImage, new SKRect(0, yPos, Width, Height), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - int yaPos = 0; - foreach (Statistic ability in Abilities) - { - int xToAdd = ability.Icon != null ? ability.Icon.Width : 0; - textSize = 42.5f; - var namePaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = textSize, - Color = SKColors.White, - TextAlign = SKTextAlign.Left, - }; - - // resize if too long - while (namePaint.MeasureText(ability.DisplayName) > Width - 128) - { - namePaint.TextSize = textSize -= 2; - } - - c.DrawText(ability.DisplayName, Width + Margin + xToAdd + 10, yaPos + Margin + textSize, namePaint); - - Helper.DrawMultilineText(c, ability.Description, Width, Width + Margin + xToAdd + 10, ETextSide.Left, - new SKRect(Width + Margin + xToAdd + 10, textSize + yaPos + 27.5f, Width + AdditionalWidth - Margin, Height - 27.5f), descriptionPaint, out var _); - - if (ability.Icon != null) - c.DrawBitmap(ability.Icon, new SKRect(Width + Margin, yaPos, Width + Margin + ability.Icon.Width, yaPos + ability.Icon.Height), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - yaPos += ability.Height + 48 + (Margin * 2); - } - } - - private void DrawCenteredTitle(SKCanvas c, string title, float textSize, out float outTextSize) - { - SKPaint namePaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = textSize, - Color = SKColors.White, - TextAlign = SKTextAlign.Center, - }; - float textWidth = namePaint.MeasureText(title); - while (textWidth > Width) // resize if too long - { - namePaint.TextSize = textSize -= 2; - textWidth = namePaint.MeasureText(title); - } - outTextSize = textSize; - - float x1 = (Width / 2 - (textWidth / 2)) - 20; - float x2 = (x1 + textWidth) + 40; - float y1 = Margin + 5; - float y2 = Margin + namePaint.TextSize + 10; - - c.DrawLine(new SKPoint(30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(x1 - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") }); - c.DrawLine(new SKPoint(x2 + 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPoint(Width - 30, y1 + 5 + (namePaint.TextSize / 2)), new SKPaint { Color = SKColor.Parse("E2E8E6") }); - - c.DrawLine(new SKPoint(x1, y1), new SKPoint(x2, y1), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // top - c.DrawLine(new SKPoint(x1, y2 + 5), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // bottom - c.DrawLine(new SKPoint(x1, y1), new SKPoint(x1, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // left - c.DrawLine(new SKPoint(x2, y1), new SKPoint(x2, y2 + 5), new SKPaint { Color = SKColor.Parse("E2E8E6") }); // right - c.DrawRect(new SKRect(x1 + 5, y1 + 5, x2 - 5, y2), new SKPaint { Color = SKColor.Parse("949598") }); - - c.DrawText(title, Width / 2, Margin + namePaint.TextSize, namePaint); - } - } -} diff --git a/FModel/Creator/Bases/BaseUserOption.cs b/FModel/Creator/Bases/BaseUserOption.cs deleted file mode 100644 index 7beaed75..00000000 --- a/FModel/Creator/Bases/BaseUserOption.cs +++ /dev/null @@ -1,201 +0,0 @@ -using FModel.Creator.Texts; -using SkiaSharp; -using SkiaSharp.HarfBuzz; -using System.Collections.Generic; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bases -{ - public class Options - { - public string Option; - public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150); - } - - public class BaseUserOption - { - private readonly SKPaint descriptionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = 25, - Color = SKColor.Parse("88DBFF"), - }; - - public string OptionDisplayName; - public string OptionDescription; - public List OptionValues = new List(); - public int Width = 512; - public int Height = 128; - public int Margin = 32; - - public BaseUserOption(IUExport export) - { - if (export.GetExport("OptionDisplayName") is TextProperty optionDisplayName) - OptionDisplayName = Text.GetTextPropertyBase(optionDisplayName).ToUpperInvariant(); - if (export.GetExport("OptionDescription") is TextProperty optionDescription) - { - OptionDescription = Text.GetTextPropertyBase(optionDescription); - if (!string.IsNullOrEmpty(OptionDescription)) - { - Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Count; - Height += (int)descriptionPaint.TextSize; - } - } - - if (export.GetExport("OptionValues") is ArrayProperty optionValues) - { - OptionValues = new List(optionValues.Value.Length); - for (int i = 0; i < OptionValues.Capacity; i++) - { - if (optionValues.Value[i] is StructProperty s && s.Value is UObject option) - { - if (option.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName) - { - var opt = new Options { Option = Text.GetTextPropertyBase(displayName).ToUpperInvariant() }; - if (option.TryGetValue("Value", out var v) && v is StructProperty value && value.Value is FLinearColor color) - opt.Color = SKColor.Parse(color.Hex).WithAlpha(150); - OptionValues.Add(opt); - } - else if (option.TryGetValue("PrimaryAssetName", out var v2) && v2 is NameProperty primaryAssetName) - OptionValues.Add(new Options { Option = primaryAssetName.Value.String }); - } - } - } - - if (export.GetExport("OptionOnText") is TextProperty optionOnText) - OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOnText).ToUpperInvariant() }); - if (export.GetExport("OptionOffText") is TextProperty optionOffText) - OptionValues.Add(new Options { Option = Text.GetTextPropertyBase(optionOffText).ToUpperInvariant() }); - - if (export.GetExport("Min", "DefaultValue") is IntProperty iMin && - export.GetExport("Max") is IntProperty iMax) - { - int increment = iMin.Value; - if (export.GetExport("IncrementValue") is IntProperty incrementValue) - increment = incrementValue.Value; - - for (int i = iMin.Value; i <= iMax.Value; i += increment) - { - OptionValues.Add(new Options { Option = i.ToString() }); - } - } - - if (export.GetExport("Min") is FloatProperty fMin && - export.GetExport("Max") is FloatProperty fMax) - { - float increment = fMin.Value; - if (export.GetExport("IncrementValue") is FloatProperty incrementValue) - increment = incrementValue.Value; - - for (float i = fMin.Value; i <= fMax.Value; i += increment) - { - OptionValues.Add(new Options { Option = i.ToString() }); - } - } - - Height += Margin; - Height += 35 * OptionValues.Count; - } - - public void Draw(SKCanvas c) - { - c.DrawRect(new SKRect(0, 0, Width, Height), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateLinearGradient( - new SKPoint(Width / 2, Height), - new SKPoint(Width, Height / 4), - new SKColor[2] { SKColor.Parse("01369C"), SKColor.Parse("1273C8") }, - SKShaderTileMode.Clamp) - }); - - int textSize = 45; - SKPaint namePaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = textSize, - Color = SKColors.White, - TextAlign = SKTextAlign.Left - }; - SKPaint optionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = 20, - Color = SKColor.Parse("EEFFFF"), - TextAlign = SKTextAlign.Left - }; - - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(namePaint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(OptionDisplayName, namePaint); - shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f; - - if (shapedTextWidth > (Width - (Margin * 2))) - { - namePaint.TextSize -= 2; - } - else - { - break; - } - } - - c.DrawShapedText(shaper, OptionDisplayName, Margin, Margin + textSize, namePaint); - } - else - { - while (namePaint.MeasureText(OptionDisplayName) > (Width - (Margin * 2))) - { - namePaint.TextSize = textSize -= 2; - } - c.DrawText(OptionDisplayName, Margin, Margin + textSize, namePaint); - } - - int y = (Margin + textSize) + ((int)descriptionPaint.TextSize + (Margin / 2)); - Helper.DrawMultilineText(c, OptionDescription, Width, Margin, ETextSide.Left, - new SKRect(Margin, y, Width - Margin, 256), descriptionPaint, out int top); - - int height = 30; - int space = 5; - foreach (Options option in OptionValues) - { - c.DrawRect(new SKRect(Margin, top, Width - Margin, top + height), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = option.Color - }); - - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(optionPaint.Typeface); - SKShaper.Result shapedText = shaper.Shape(option.Option, optionPaint); - float shapedTextWidth = shapedText.Points[^1].X + optionPaint.TextSize / 2f; - c.DrawShapedText(shaper, option.Option, Margin + (space * 2), top + (20 * 1.1f), optionPaint); - } - else - { - c.DrawText(option.Option, Margin + (space * 2), top + (20 * 1.1f), optionPaint); - } - - top += height + space; - } - } - } -} diff --git a/FModel/Creator/Bases/FN/BaseBundle.cs b/FModel/Creator/Bases/FN/BaseBundle.cs new file mode 100644 index 00000000..7437640b --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseBundle.cs @@ -0,0 +1,141 @@ +using System.Collections.Generic; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Extensions; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseBundle : UCreator + { + private IList _quests; + private const int _headerHeight = 100; + + public BaseBundle(UObject uObject, EIconStyle style) : base(uObject, style) + { + Width = 1024; + Height = _headerHeight; + Margin = 0; + } + + public override void ParseForInfo() + { + _quests = new List(); + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text.ToUpperInvariant(); + + if (Object.TryGetValue(out FStructFallback[] quests, "QuestInfos")) // prout :) + { + foreach (var quest in quests) + { + if (!quest.TryGetValue(out FSoftObjectPath questDefinition, "QuestDefinition")) continue; + + BaseQuest q; + var path = questDefinition.AssetPathName.Text; + do + { + if (!Utils.TryLoadObject(path, out UObject uObject)) break; + + q = new BaseQuest(uObject, Style); + q.ParseForInfo(); + _quests.Add(q); + path = path.SubstringBeforeWithLast('/') + q.NextQuestName + "." + q.NextQuestName; + } while (!string.IsNullOrEmpty(q.NextQuestName)); + } + } + + if (Object.TryGetValue(out FStructFallback[] completionRewards, "BundleCompletionRewards")) + { + foreach (var completionReward in completionRewards) + { + if (!completionReward.TryGetValue(out int completionCount, "CompletionCount") || + !completionReward.TryGetValue(out FStructFallback[] rewards, "Rewards")) continue; + + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out int quantity, "Quantity") || + !reward.TryGetValue(out string templateId, "TemplateId") || + !reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue; + + if (!itemDefinition.AssetPathName.IsNone && + !itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") && + !itemDefinition.AssetPathName.Text.Contains("/Items/Quests")) + { + _quests.Add(new BaseQuest(completionCount, itemDefinition, Style)); + } + else if (!string.IsNullOrWhiteSpace(templateId)) + { + _quests.Add(new BaseQuest(completionCount, quantity, templateId, Style)); + } + } + } + } + + Height += 256 * _quests.Count; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawHeader(c); + DrawDisplayName(c); + DrawQuests(c); + + return SKImage.FromBitmap(ret); + } + + private readonly SKPaint _headerPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Bundle, TextSize = 50, + TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630") + }; + + private void DrawHeader(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint); + + var background = _quests.Count > 0 ? _quests[0].Background : Background; + _headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] {background[0].WithAlpha(50), background[1].WithAlpha(50)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 0, Width, _headerHeight), _headerPaint); + + _headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), + new[] {SKColors.Black.WithAlpha(25), background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint); + } + + private new void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName)) return; + + _headerPaint.Shader = null; + _headerPaint.Color = SKColors.White; + while (_headerPaint.MeasureText(DisplayName) > Width) + { + _headerPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_headerPaint.Typeface); + var shapedText = shaper.Shape(DisplayName, _headerPaint); + c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint); + } + + private void DrawQuests(SKCanvas c) + { + var y = _headerHeight; + foreach (var quest in _quests) + { + quest.DrawQuest(c, y); + y += quest.Height; + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseCommunity.cs b/FModel/Creator/Bases/FN/BaseCommunity.cs new file mode 100644 index 00000000..2f139665 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseCommunity.cs @@ -0,0 +1,305 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.GameplayTags; +using CUE4Parse.UE4.Objects.UObject; +using CUE4Parse.UE4.Versions; +using CUE4Parse_Fortnite.Enums; +using FModel.Extensions; +using FModel.Framework; +using FModel.Services; +using FModel.Settings; +using FModel.ViewModels.ApiEndpoints.Models; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseCommunity : BaseIcon + { + private readonly CommunityDesign _design; + private string _rarityName; + private string _source; + private string _season; + private bool _lowerDrawn; + + public BaseCommunity(UObject uObject, EIconStyle style, string designName) : base(uObject, style) + { + Margin = 0; + _lowerDrawn = false; + _design = ApplicationService.ApiEndpointView.FModelApi.GetDesign(designName); + } + + public override void ParseForInfo() + { + ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled); + + if (Object.TryGetValue(out FPackageIndex series, "Series") && Utils.TryGetPackageIndexExport(series, out UObject export)) + _rarityName = export.Name; + else + _rarityName = GetRarityName(Object.GetOrDefault("Rarity")); + + if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) + CheckGameplayTags(gameplayTags); + if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) + CosmeticSource = cosmeticItem.Name.ToUpper(); + + DisplayName = DisplayName.ToUpper(); + Description = Description.ToUpper(); + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + if (_design == null) + { + base.Draw(c); + } + else + { + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + if (_design.DrawSeason && _design.Fonts.TryGetValue("Season", out var font)) + DrawToBottom(c, font, _season); + if (_design.DrawSource && _design.Fonts.TryGetValue("Source", out font)) + DrawToBottom(c, font, _source); + DrawUserFacingFlags(c, _design.GameplayTags.DrawCustomOnly); + } + + return SKImage.FromBitmap(ret); + } + + private void CheckGameplayTags(FGameplayTagContainer gameplayTags) + { + if (_design == null) return; + if (_design.DrawSource) + { + if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) + _source = source.Text["Cosmetics.Source.".Length..].ToUpper(); + else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) + _source = action.Text["Athena.ItemAction.".Length..].ToUpper(); + } + + if (_design.DrawSet && gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) + Description += GetCosmeticSet(set.Text, _design.DrawSetShort); + if (_design.DrawSeason && gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) + _season = GetCosmeticSeason(season.Text, _design.DrawSeasonShort); + + GetUserFacingFlags(gameplayTags.GetAllGameplayTags( + "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender.")); + } + + private string GetCosmeticSet(string setName, bool bShort) + { + return bShort ? setName["Cosmetics.Set.".Length..] : base.GetCosmeticSet(setName); + } + + private string GetCosmeticSeason(string seasonNumber, bool bShort) + { + if (!bShort) return base.GetCosmeticSeason(seasonNumber); + var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; + var number = int.Parse(s); + if (number == 10) + s = "X"; + + return number > 10 ? $"C{number / 10 + 1} S{s[^1..]}" : $"C1 S{s}"; + } + + private string GetRarityName(FName r) + { + var rarity = EFortRarity.Uncommon; + switch (r.Text) + { + case "EFortRarity::Common": + case "EFortRarity::Handmade": + rarity = EFortRarity.Common; + break; + case "EFortRarity::Rare": + case "EFortRarity::Sturdy": + rarity = EFortRarity.Rare; + break; + case "EFortRarity::Epic": + case "EFortRarity::Quality": + rarity = EFortRarity.Epic; + break; + case "EFortRarity::Legendary": + case "EFortRarity::Fine": + rarity = EFortRarity.Legendary; + break; + case "EFortRarity::Mythic": + case "EFortRarity::Elegant": + rarity = EFortRarity.Mythic; + break; + case "EFortRarity::Transcendent": + case "EFortRarity::Masterwork": + rarity = EFortRarity.Transcendent; + break; + case "EFortRarity::Unattainable": + case "EFortRarity::Badass": + rarity = EFortRarity.Unattainable; + break; + } + + return rarity.GetDescription(); + } + + private new void DrawBackground(SKCanvas c) + { + if (_design.Rarities.TryGetValue(_rarityName, out var rarity)) + { + c.DrawBitmap(rarity.Background, 0, 0, ImagePaint); + c.DrawBitmap(rarity.Upper, 0, 0, ImagePaint); + } + else + { + base.DrawBackground(c); + } + } + + private new void DrawTextBackground(SKCanvas c) + { + if (!_lowerDrawn && string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + + _lowerDrawn = true; + if (_design.Rarities.TryGetValue(_rarityName, out var rarity)) + { + c.DrawBitmap(rarity.Lower, 0, 0, ImagePaint); + } + else + { + base.DrawTextBackground(c); + } + } + + private new void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName)) return; + if (_design.Fonts.TryGetValue(nameof(DisplayName), out var font)) + { + DisplayNamePaint.TextSize = font.FontSize; + DisplayNamePaint.TextScaleX = font.FontScale; + DisplayNamePaint.Color = font.FontColor; + DisplayNamePaint.TextSkewX = font.SkewValue; + DisplayNamePaint.TextAlign = font.Alignment; + if (font.ShadowValue > 0) + DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); + if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || + font.Typeface.TryGetValue(ELanguage.English, out path)) + DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path); + + while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) + { + DisplayNamePaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); + var x = font.Alignment switch + { + SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, + _ => font.X + }; + + c.DrawShapedText(shaper, DisplayName, x, font.Y, DisplayNamePaint); + } + else + { + base.DrawDisplayName(c); + } + } + + private new void DrawDescription(SKCanvas c) + { + if (string.IsNullOrEmpty(Description)) return; + if (_design.Fonts.TryGetValue(nameof(Description), out var font)) + { + DescriptionPaint.TextSize = font.FontSize; + DescriptionPaint.TextScaleX = font.FontScale; + DescriptionPaint.Color = font.FontColor; + DescriptionPaint.TextSkewX = font.SkewValue; + DescriptionPaint.TextAlign = font.Alignment; + if (font.ShadowValue > 0) + DescriptionPaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); + if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || + font.Typeface.TryGetValue(ELanguage.English, out path)) + DescriptionPaint.Typeface = Utils.Typefaces.OnTheFly(path); + + while (DescriptionPaint.MeasureText(Description) > Width - Margin * 2) + { + DescriptionPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(DescriptionPaint.Typeface); + var shapedText = shaper.Shape(Description, DescriptionPaint); + var x = font.Alignment switch + { + SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, + _ => font.X + }; + + if (font.MaxLineCount < 2) + { + c.DrawShapedText(shaper, Description, x, font.Y, DescriptionPaint); + } + else + { + Utils.DrawMultilineText(c, Description, Width - Margin * 2, Margin, DescriptionPaint.TextAlign, + new SKRect(Margin, font.Y, Width - Margin, Height), DescriptionPaint, out _); + } + } + else + { + base.DrawDescription(c); + } + } + + private void DrawToBottom(SKCanvas c, FontDesign font, string text) + { + if (string.IsNullOrEmpty(text)) return; + if (!_lowerDrawn) + { + _lowerDrawn = true; + DrawTextBackground(c); + } + + DisplayNamePaint.TextSize = font.FontSize; + DisplayNamePaint.TextScaleX = font.FontScale; + DisplayNamePaint.Color = font.FontColor; + DisplayNamePaint.TextSkewX = font.SkewValue; + DisplayNamePaint.TextAlign = font.Alignment; + if (font.ShadowValue > 0) + DisplayNamePaint.ImageFilter = SKImageFilter.CreateDropShadow(2, 2, 4, 4, new SKColor(0, 0, 0, font.ShadowValue)); + if (font.Typeface.TryGetValue(UserSettings.Default.AssetLanguage, out var path) || + font.Typeface.TryGetValue(ELanguage.English, out path)) + DisplayNamePaint.Typeface = Utils.Typefaces.OnTheFly(path); + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(text, DisplayNamePaint); + var x = font.Alignment switch + { + SKTextAlign.Center => (Width - shapedText.Points[^1].X) / 2f, + SKTextAlign.Right => font.X - DisplayNamePaint.MeasureText(text), + _ => font.X + }; + + c.DrawShapedText(shaper, text, x, font.Y, DisplayNamePaint); + } + + private void DrawUserFacingFlags(SKCanvas c, bool customOnly) + { + if (UserFacingFlags == null || UserFacingFlags.Length < 1) return; + if (customOnly) + { + c.DrawBitmap(_design.GameplayTags.Custom, 0, 0, ImagePaint); + } + else + { + // add size to api + // draw + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseIcon.cs b/FModel/Creator/Bases/FN/BaseIcon.cs new file mode 100644 index 00000000..b388c4c9 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseIcon.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +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 CUE4Parse_Conversion.Textures; +using CUE4Parse_Fortnite.Enums; +using FModel.Settings; +using SkiaSharp; + +namespace FModel.Creator.Bases.FN +{ + public class BaseIcon : UCreator + { + public SKBitmap SeriesBackground { get; protected set; } + protected string ShortDescription { get; set; } + protected string CosmeticSource { get; set; } + protected SKBitmap[] UserFacingFlags { get; set; } + + public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) + { + } + + public void ParseForReward(bool isUsingDisplayAsset) + { + // rarity + if (Object.TryGetValue(out FPackageIndex series, "Series")) GetSeries(series); + else GetRarity(Object.GetOrDefault("Rarity")); // default is uncommon + + // preview + if (isUsingDisplayAsset && Utils.TryGetDisplayAsset(Object, out var preview)) + Preview = preview; + else if (Object.TryGetValue(out FPackageIndex itemDefinition, "HeroDefinition", "WeaponDefinition")) + Preview = Utils.GetBitmap(itemDefinition); + else if (Object.TryGetValue(out FSoftObjectPath largePreview, "LargePreviewImage", "SidePanelIcon", "EntryListIcon", "SmallPreviewImage", "ItemDisplayAsset", "LargeIcon", "ToastIcon", "SmallIcon")) + Preview = Utils.GetBitmap(largePreview); + else if (Object.TryGetValue(out string s, "LargePreviewImage") && !string.IsNullOrEmpty(s)) + Preview = Utils.GetBitmap(s); + else if (Object.TryGetValue(out FPackageIndex otherPreview, "SmallPreviewImage", "ToastIcon", "access_item")) + Preview = Utils.GetBitmap(otherPreview); + + // text + if (Object.TryGetValue(out FText displayName, "DisplayName", "DefaultHeaderText", "UIDisplayName", "EntryName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description", "GeneralDescription", "DefaultBodyText", "UIDescription", "UIDisplayDescription", "EntryDescription")) + Description = description.Text; + else if (Object.TryGetValue(out FText[] descriptions, "Description")) + Description = string.Join('\n', descriptions.Select(x => x.Text)); + if (Object.TryGetValue(out FText shortDescription, "ShortDescription", "UIDisplaySubName")) + ShortDescription = shortDescription.Text; + else if (Object.ExportType.Equals("AthenaItemWrapDefinition", StringComparison.OrdinalIgnoreCase)) + ShortDescription = "Wrap"; + + Description = Utils.RemoveHtmlTags(Description); + } + + public override void ParseForInfo() + { + ParseForReward(UserSettings.Default.CosmeticDisplayAsset == EEnabledDisabled.Enabled); + + if (Object.TryGetValue(out FGameplayTagContainer gameplayTags, "GameplayTags")) + CheckGameplayTags(gameplayTags); + if (Object.TryGetValue(out FPackageIndex cosmeticItem, "cosmetic_item")) + CosmeticSource = cosmeticItem.Name; + } + + protected void Draw(SKCanvas c) + { + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + case EIconStyle.NoText: + DrawBackground(c); + DrawPreview(c); + DrawUserFacingFlags(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + DrawToBottom(c, SKTextAlign.Right, CosmeticSource); + if (Description != ShortDescription) + DrawToBottom(c, SKTextAlign.Left, ShortDescription); + DrawUserFacingFlags(c); + break; + } + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + Draw(c); + + return SKImage.FromBitmap(ret); + } + + private void GetSeries(FPackageIndex s) + { + if (!Utils.TryGetPackageIndexExport(s, out UObject export)) return; + + GetSeries(export); + } + + protected void GetSeries(UObject uObject) + { + if (uObject is UTexture2D texture2D) + { + SeriesBackground = SKBitmap.Decode(texture2D.Decode()?.Encode()); + return; + } + + if (uObject.TryGetValue(out FSoftObjectPath backgroundTexture, "BackgroundTexture")) + { + SeriesBackground = Utils.GetBitmap(backgroundTexture); + } + + if (uObject.TryGetValue(out FStructFallback colors, "Colors") && + colors.TryGetValue(out FLinearColor color1, "Color1") && + colors.TryGetValue(out FLinearColor color2, "Color2") && + colors.TryGetValue(out FLinearColor color3, "Color3")) + { + Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)}; + Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)}; + } + + if (uObject.Name.Equals("PlatformSeries") && + uObject.TryGetValue(out FSoftObjectPath itemCardMaterial, "ItemCardMaterial") && + Utils.TryLoadObject(itemCardMaterial.AssetPathName.Text, out UMaterialInstanceConstant material)) + { + foreach (var vectorParameter in material.VectorParameterValues) + { + if (vectorParameter.ParameterValue == null || !vectorParameter.ParameterInfo.Name.Text.Equals("ColorCircuitBackground")) + continue; + + Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); + } + } + } + + private void GetRarity(FName r) + { + if (!Utils.TryLoadObject("FortniteGame/Content/Balance/RarityData.RarityData", out UObject export)) return; + + var rarity = EFortRarity.Uncommon; + switch (r.Text) + { + case "EFortRarity::Common": + case "EFortRarity::Handmade": + rarity = EFortRarity.Common; + break; + case "EFortRarity::Rare": + case "EFortRarity::Sturdy": + rarity = EFortRarity.Rare; + break; + case "EFortRarity::Epic": + case "EFortRarity::Quality": + rarity = EFortRarity.Epic; + break; + case "EFortRarity::Legendary": + case "EFortRarity::Fine": + rarity = EFortRarity.Legendary; + break; + case "EFortRarity::Mythic": + case "EFortRarity::Elegant": + rarity = EFortRarity.Mythic; + break; + case "EFortRarity::Transcendent": + case "EFortRarity::Masterwork": + rarity = EFortRarity.Transcendent; + break; + case "EFortRarity::Unattainable": + case "EFortRarity::Badass": + rarity = EFortRarity.Unattainable; + break; + } + + if (export.GetByIndex((int) rarity) is { } data && + data.TryGetValue(out FLinearColor color1, "Color1") && + data.TryGetValue(out FLinearColor color2, "Color2") && + data.TryGetValue(out FLinearColor color3, "Color3")) + { + Background = new[] {SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex)}; + Border = new[] {SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex)}; + } + } + + protected string GetCosmeticSet(string setName) + { + if (!Utils.TryLoadObject("FortniteGame/Content/Athena/Items/Cosmetics/Metadata/CosmeticSets.CosmeticSets", out UDataTable cosmeticSets)) + return string.Empty; + + if (!cosmeticSets.TryGetDataTableRow(setName, StringComparison.OrdinalIgnoreCase, out var uObject)) + return string.Empty; + + var name = string.Empty; + if (uObject.TryGetValue(out FText displayName, "DisplayName")) + name = displayName.Text; + + var format = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set."); + return string.Format(format, name); + } + + protected string GetCosmeticSeason(string seasonNumber) + { + var s = seasonNumber["Cosmetics.Filter.Season.".Length..]; + var number = int.Parse(s); + if (number == 10) + s = "X"; + + var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); + var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}."); + if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s))); + + var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}"); + var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}"); + var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..])); + return Utils.RemoveHtmlTags(string.Format(introduced, d)); + } + + private void CheckGameplayTags(FGameplayTagContainer gameplayTags) + { + if (gameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) + CosmeticSource = source.Text["Cosmetics.Source.".Length..]; + else if (gameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) + CosmeticSource = action.Text["Athena.ItemAction.".Length..]; + + if (gameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) + Description += GetCosmeticSet(set.Text); + if (gameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) + Description += GetCosmeticSeason(season.Text); + + GetUserFacingFlags(gameplayTags.GetAllGameplayTags( + "Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender.")); + } + + protected void GetUserFacingFlags(IList userFacingFlags) + { + if (userFacingFlags.Count < 1 || !Utils.TryLoadObject("FortniteGame/Content/Items/ItemCategories.ItemCategories", out UObject itemCategories)) + return; + + if (!itemCategories.TryGetValue(out FStructFallback[] tertiaryCategories, "TertiaryCategories")) + return; + + UserFacingFlags = new SKBitmap[userFacingFlags.Count]; + for (var i = 0; i < UserFacingFlags.Length; i++) + { + if (userFacingFlags[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests", StringComparison.OrdinalIgnoreCase)) + { + if (Object.ExportType.Equals("AthenaPetCarrierItemDefinition", StringComparison.OrdinalIgnoreCase)) + UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png"))?.Stream); + else UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png"))?.Stream); + } + else + { + foreach (var category in tertiaryCategories) + { + if (category.TryGetValue(out FGameplayTagContainer tagContainer, "TagContainer") && tagContainer.TryGetGameplayTag(userFacingFlags[i], out _) && + category.TryGetValue(out FStructFallback categoryBrush, "CategoryBrush") && categoryBrush.TryGetValue(out FStructFallback brushXxs, "Brush_XXS") && + brushXxs.TryGetValue(out FPackageIndex resourceObject, "ResourceObject") && Utils.TryGetPackageIndexExport(resourceObject, out UTexture2D texture)) + { + UserFacingFlags[i] = Utils.GetBitmap(texture); + } + } + } + } + } + + private void DrawUserFacingFlags(SKCanvas c) + { + if (UserFacingFlags == null) return; + + const int size = 25; + var x = Margin * (int) 2.5; + foreach (var flag in UserFacingFlags) + { + if (flag == null) continue; + + c.DrawBitmap(flag.Resize(size), new SKPoint(x, Margin * (int) 2.5), ImagePaint); + x += size; + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseIconStats.cs b/FModel/Creator/Bases/FN/BaseIconStats.cs new file mode 100644 index 00000000..a43450a0 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseIconStats.cs @@ -0,0 +1,319 @@ +using System; +using System.Collections.Generic; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.Engine.Curves; +using CUE4Parse.UE4.Objects.GameplayTags; +using CUE4Parse.UE4.Objects.UObject; +using CUE4Parse_Fortnite.Enums; +using FModel.Extensions; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseIconStats : BaseIcon + { + private readonly IList _statistics; + private const int _headerHeight = 128; + private bool _screenLayer; + + public BaseIconStats(UObject uObject, EIconStyle style) : base(uObject, style) + { + Width = 1024; + Height = _headerHeight; + Margin = 0; + _statistics = new List(); + _screenLayer = uObject.ExportType.Equals("FortAccoladeItemDefinition", StringComparison.OrdinalIgnoreCase); + DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default"); + } + + public override void ParseForInfo() + { + base.ParseForInfo(); + DisplayName = DisplayName.ToUpperInvariant(); + + if (Object.TryGetValue(out FName accoladeType, "AccoladeType") && + accoladeType.Text.Equals("EFortAccoladeType::Medal", StringComparison.OrdinalIgnoreCase)) + { + _screenLayer = false; + } + + if (Object.TryGetValue(out FGameplayTagContainer poiLocations, "POILocations") && + Utils.TryLoadObject("FortniteGame/Content/Quests/QuestIndicatorData.QuestIndicatorData", out UObject uObject) && + uObject.TryGetValue(out FStructFallback[] challengeMapPoiData, "ChallengeMapPoiData")) + { + foreach (var location in poiLocations) + { + var locationName = "Unknown"; + 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; + + locationName = text.Text; + break; + } + + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "B0C091D7409B1657423C5F97E9CF4C77", "LOCATION NAME"), locationName.ToUpper())); + } + } + + if (Object.TryGetValue(out FStructFallback maxStackSize, "MaxStackSize")) + { + if (maxStackSize.TryGetValue(out float v, "Value") && v > -1) + { + _statistics.Add(new IconStat("Max Stack", v)); + } + else if (TryGetCurveTableStat(maxStackSize, out var s)) + { + _statistics.Add(new IconStat("Max Stack", s)); + } + } + + if (Object.TryGetValue(out FStructFallback xpRewardAmount, "XpRewardAmount") && TryGetCurveTableStat(xpRewardAmount, out var x)) + { + _statistics.Add(new IconStat("XP Amount", x)); + } + + if (Object.TryGetValue(out FStructFallback weaponStatHandle, "WeaponStatHandle") && + weaponStatHandle.TryGetValue(out FName weaponRowName, "RowName") && + weaponStatHandle.TryGetValue(out UDataTable dataTable, "DataTable") && + dataTable.TryGetDataTableRow(weaponRowName.Text, StringComparison.OrdinalIgnoreCase, out var weaponRowValue)) + { + if (weaponRowValue.TryGetValue(out float dmgPb, "DmgPB") && dmgPb != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player"), dmgPb, 200)); + } + + if (weaponRowValue.TryGetValue(out int clipSize, "ClipSize") && clipSize != 0) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "068239DD4327B36124498C9C5F61C038", "Magazine Size"), clipSize, 50)); + } + + if (weaponRowValue.TryGetValue(out float firingRate, "FiringRate") && firingRate != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "27B80BA44805ABD5A2D2BAB2902B250C", "Fire Rate"), firingRate, 15)); + } + + if (weaponRowValue.TryGetValue(out float armTime, "ArmTime") && armTime != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "3BFEB8BD41A677CC5F45B9A90D6EAD6F", "Arming Delay"), armTime, 125)); + } + + if (weaponRowValue.TryGetValue(out float reloadTime, "ReloadTime") && reloadTime != 0f) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time"), reloadTime, 15)); + } + + if ((Object.ExportType.Equals("FortContextTrapItemDefinition", StringComparison.OrdinalIgnoreCase) || + Object.ExportType.Equals("FortTrapItemDefinition", StringComparison.OrdinalIgnoreCase)) && + weaponRowValue.TryGetValue(out UDataTable durabilityTable, "Durability") && + weaponRowValue.TryGetValue(out FName durabilityRowName, "DurabilityRowName") && + durabilityTable.TryGetDataTableRow(durabilityRowName.Text, StringComparison.OrdinalIgnoreCase, out var durability) && + durability.TryGetValue(out int duraByRarity, GetRarityName(Object.GetOrDefault("Rarity")))) + { + _statistics.Add(new IconStat(Utils.GetLocalizedResource("", "6FA2882140CB69DE32FD73A392F0585B", "Durability"), duraByRarity, 20)); + } + } + + if (!string.IsNullOrEmpty(Description)) + Height += 40 + (int) _informationPaint.TextSize * Utils.SplitLines(Description, _informationPaint, Width - 20).Count; + Height += 50 * _statistics.Count; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawHeader(c); + DrawDisplayName(c); + DrawStatistics(c); + + return SKImage.FromBitmap(ret); + } + + private bool TryGetCurveTableStat(FStructFallback property, out float statValue) + { + if (property.TryGetValue(out FStructFallback curve, "Curve") && + curve.TryGetValue(out FName rowName, "RowName") && + curve.TryGetValue(out UCurveTable curveTable, "CurveTable") && + curveTable.TryGetCurveTableRow(rowName.Text, StringComparison.OrdinalIgnoreCase, out var rowValue) && + rowValue.TryGetValue(out FSimpleCurveKey[] keys, "Keys") && keys.Length > 0) + { + statValue = keys[0].KeyValue; + return true; + } + + statValue = 0F; + return false; + } + + private string GetRarityName(FName r) + { + var rarity = EFortRarity.Uncommon; + switch (r.Text) + { + case "EFortRarity::Common": + case "EFortRarity::Handmade": + rarity = EFortRarity.Common; + break; + case "EFortRarity::Rare": + case "EFortRarity::Sturdy": + rarity = EFortRarity.Rare; + break; + case "EFortRarity::Epic": + case "EFortRarity::Quality": + rarity = EFortRarity.Epic; + break; + case "EFortRarity::Legendary": + case "EFortRarity::Fine": + rarity = EFortRarity.Legendary; + break; + case "EFortRarity::Mythic": + case "EFortRarity::Elegant": + rarity = EFortRarity.Mythic; + break; + case "EFortRarity::Transcendent": + case "EFortRarity::Masterwork": + rarity = EFortRarity.Transcendent; + break; + case "EFortRarity::Unattainable": + case "EFortRarity::Badass": + rarity = EFortRarity.Unattainable; + break; + } + + return rarity.GetDescription(); + } + + private readonly SKPaint _informationPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColor.Parse("#262630"), TextSize = 16, + Typeface = Utils.Typefaces.Description + }; + + private void DrawHeader(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] {Background[0].WithAlpha(180), Background[1].WithAlpha(220)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(_headerHeight, 0, Width, _headerHeight), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] {SKColor.Parse("#262630"), SKColor.Parse("#1f1f26")}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, _headerHeight, Width, Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), + new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp); + using var rect = new SKPath {FillType = SKPathFillType.EvenOdd}; + rect.MoveTo(0, 0); + rect.LineTo(_headerHeight + _headerHeight / 3, 0); + rect.LineTo(_headerHeight, _headerHeight); + rect.LineTo(0, _headerHeight); + rect.Close(); + c.DrawPath(rect, _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(_headerHeight / 2, _headerHeight / 2), new SKPoint(_headerHeight / 2 + 100, _headerHeight / 2), + new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); + c.DrawPath(rect, _informationPaint); + + _informationPaint.Shader = null; + + ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver; + c.DrawBitmap((Preview ?? DefaultPreview).Resize(_headerHeight), 0, 0, ImagePaint); + } + + private new void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName)) return; + + _informationPaint.TextSize = 50; + _informationPaint.Color = SKColors.White; + _informationPaint.Typeface = Utils.Typefaces.Bundle; + while (_informationPaint.MeasureText(DisplayName) > Width - _headerHeight * 2) + { + _informationPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_informationPaint.Typeface); + shaper.Shape(DisplayName, _informationPaint); + c.DrawShapedText(shaper, DisplayName, _headerHeight + _headerHeight / 3 + 10, _headerHeight / 2 + _informationPaint.TextSize / 3, _informationPaint); + } + + private void DrawStatistics(SKCanvas c) + { + var outY = _headerHeight + 25f; + if (!string.IsNullOrEmpty(Description)) + { + _informationPaint.TextSize = 16; + _informationPaint.Color = SKColors.White.WithAlpha(175); + _informationPaint.Typeface = Utils.Typefaces.Description; + Utils.DrawMultilineText(c, Description, Width - 40, 0, SKTextAlign.Center, + new SKRect(20, outY, Width - 20, Height), _informationPaint, out outY); + outY += 25; + } + + foreach (var stat in _statistics) + { + stat.Draw(c, Border[0].WithAlpha(100), Width, _headerHeight, ref outY); + outY += 50; + } + } + } + + public class IconStat + { + private readonly string _statName; + private readonly object _value; + private readonly float _maxValue; + + public IconStat(string statName, object value, float maxValue = 0) + { + _statName = statName.ToUpperInvariant(); + _value = value; + _maxValue = maxValue; + } + + private readonly SKPaint _statPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + TextSize = 25, Typeface = Utils.Typefaces.DisplayName, + Color = SKColors.White + }; + + public void Draw(SKCanvas c, SKColor sliderColor, int width, int height, ref float y) + { + while (_statPaint.MeasureText(_statName) > height * 2) + { + _statPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_statPaint.Typeface); + shaper.Shape(_statName, _statPaint); + c.DrawShapedText(shaper, _statName, 50, y + 10, _statPaint); + + _statPaint.TextAlign = SKTextAlign.Right; + _statPaint.Typeface = Utils.Typefaces.BundleNumber; + _statPaint.Color = sliderColor; + var sliderRight = width - 100 - _statPaint.MeasureText(_value.ToString()); + c.DrawRect(new SKRect(height * 2, y, Math.Min(width - height, sliderRight), y + 5), _statPaint); + + _statPaint.Color = SKColors.White; + c.DrawText(_value.ToString(), new SKPoint(width - 50, y + 10), _statPaint); + + if (_maxValue < 1 || !float.TryParse(_value.ToString(), out var floatValue)) return; + var sliderWidth = (sliderRight - height * 2) * (floatValue / _maxValue); + c.DrawRect(new SKRect(height * 2, y, Math.Min(height * 2 + sliderWidth, sliderRight), y + 5), _statPaint); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseItemAccessToken.cs b/FModel/Creator/Bases/FN/BaseItemAccessToken.cs new file mode 100644 index 00000000..deb47ba0 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseItemAccessToken.cs @@ -0,0 +1,100 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseItemAccessToken : UCreator + { + private readonly SKBitmap _locked, _unlocked; + private string _unlockedDescription, _exportName; + private BaseIcon _icon; + + public BaseItemAccessToken(UObject uObject, EIconStyle style) : base(uObject, style) + { + _unlocked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Unlocked-128.T-Icon-Unlocked-128").Resize(24); + _locked = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Locks/T-Icon-Lock-128.T-Icon-Lock-128").Resize(24); + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FPackageIndex accessItem, "access_item") && + Utils.TryGetPackageIndexExport(accessItem, out UObject uObject)) + { + _exportName = uObject.Name; + _icon = new BaseIcon(uObject, EIconStyle.Default); + _icon.ParseForReward(false); + } + + if (Object.TryGetValue(out FText displayName, "DisplayName") && displayName.Text != "TBD") + DisplayName = displayName.Text; + else + DisplayName = _icon?.DisplayName; + + Description = Object.TryGetValue(out FText description, "Description") ? description.Text : _icon?.Description; + if (Object.TryGetValue(out FText unlockDescription, "UnlockDescription")) _unlockedDescription = unlockDescription.Text; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + Preview = _icon.Preview; + DrawPreview(c); + break; + case EIconStyle.NoText: + Preview = _icon.Preview; + _icon.DrawBackground(c); + DrawPreview(c); + break; + default: + _icon.DrawBackground(c); + DrawInformation(c); + DrawToBottom(c, SKTextAlign.Right, _exportName); + break; + } + + return SKImage.FromBitmap(ret); + } + + private void DrawInformation(SKCanvas c) + { + var size = 45; + var left = Width / 2; + + while (DisplayNamePaint.MeasureText(DisplayName) > Width - _icon.Margin * 2) + { + DisplayNamePaint.TextSize = size -= 2; + } + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); + c.DrawShapedText(shaper, DisplayName, left - shapedText.Points[^1].X / 2, _icon.Margin * 8 + size, DisplayNamePaint); + + float topBase = _icon.Margin + size * 2; + if (!string.IsNullOrEmpty(_unlockedDescription)) + { + c.DrawBitmap(_locked, new SKRect(50, topBase, 50 + _locked.Width, topBase + _locked.Height), ImagePaint); + Utils.DrawMultilineText(c, _unlockedDescription, Width, _icon.Margin, SKTextAlign.Left, + new SKRect(70 + _locked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase); + } + + if (!string.IsNullOrEmpty(Description)) + { + c.DrawBitmap(_unlocked, new SKRect(50, topBase, 50 + _unlocked.Width, topBase + _unlocked.Height), ImagePaint); + Utils.DrawMultilineText(c, Description, Width, _icon.Margin, SKTextAlign.Left, + new SKRect(70 + _unlocked.Width, topBase + 10, Width - 50, 256), DescriptionPaint, out topBase); + } + + var h = Width - _icon.Margin - topBase; + c.DrawBitmap(_icon.Preview ?? _icon.DefaultPreview, new SKRect(left - h / 2, topBase, left + h / 2, Width - _icon.Margin), ImagePaint); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseMaterialInstance.cs b/FModel/Creator/Bases/FN/BaseMaterialInstance.cs new file mode 100644 index 00000000..566a5e87 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseMaterialInstance.cs @@ -0,0 +1,83 @@ +using System.Linq; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Objects.UObject; +using SkiaSharp; + +namespace FModel.Creator.Bases.FN +{ + public class BaseMaterialInstance : BaseIcon + { + public BaseMaterialInstance(UObject uObject, EIconStyle style) : base(uObject, style) + { + Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")}; + Border = new[] {SKColor.Parse("9092AB")}; + } + + public override void ParseForInfo() + { + if (!(Object is UMaterialInstanceConstant material)) return; + + foreach (var textureParameter in material.TextureParameterValues) // get texture from base material + { + if (!(textureParameter.ParameterValue is UTexture2D texture) || Preview != null) continue; + switch (textureParameter.ParameterInfo.Name.Text) + { + case "SeriesTexture": + GetSeries(texture); + break; + case "TextureA": + case "TextureB": + case "OfferImage": + Preview = Utils.GetBitmap(texture); + break; + } + } + + while (material.VectorParameterValues.Length == 0 || // try to get color from parent if not found here + material.VectorParameterValues.All(x => x.ParameterInfo.Name.Text.Equals("FallOff_Color"))) // use parent if it only contains FallOff_Color + { + if (material.TryGetValue(out FPackageIndex parent, "Parent")) + Utils.TryGetPackageIndexExport(parent, out material); + else return; + + if (material == null) return; + } + + foreach (var vectorParameter in material.VectorParameterValues) + { + if (vectorParameter.ParameterValue == null) continue; + switch (vectorParameter.ParameterInfo.Name.Text) + { + case "Background_Color_A": + Background[0] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); + Border[0] = Background[0]; + break; + case "Background_Color_B": // Border color can be defaulted here in some case where Background_Color_A should be taken from parent but Background_Color_B from base + Background[1] = SKColor.Parse(vectorParameter.ParameterValue.Value.Hex); + break; + } + } + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + break; + } + + return SKImage.FromBitmap(ret); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseMtxOffer.cs b/FModel/Creator/Bases/FN/BaseMtxOffer.cs new file mode 100644 index 00000000..248d48c5 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseMtxOffer.cs @@ -0,0 +1,85 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.UObject; +using SkiaSharp; + +namespace FModel.Creator.Bases.FN +{ + public class BaseMtxOffer : UCreator + { + public BaseMtxOffer(UObject uObject, EIconStyle style) : base(uObject, style) + { + Background = new[] {SKColor.Parse("4F4F69"), SKColor.Parse("4F4F69")}; + Border = new[] {SKColor.Parse("9092AB")}; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FStructFallback typeImage, "DetailsImage", "TileImage") && + typeImage.TryGetValue(out FPackageIndex resource, "ResourceObject")) + { + Preview = Utils.GetBitmap(resource); + } + + if (Object.TryGetValue(out FStructFallback gradient, "Gradient") && + gradient.TryGetValue(out FLinearColor start, "Start") && + gradient.TryGetValue(out FLinearColor stop, "Stop")) + { + Background = new[] {SKColor.Parse(start.Hex), SKColor.Parse(stop.Hex)}; + } + + if (Object.TryGetValue(out FLinearColor background, "Background")) + Border = new[] {SKColor.Parse(background.Hex)}; + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText shortDescription, "ShortDescription")) + Description = shortDescription.Text; + + if (Object.TryGetValue(out FStructFallback[] details, "DetailsAttributes")) + { + foreach (var detail in details) + { + if (detail.TryGetValue(out FText detailName, "Name")) + { + Description += $"\n- {detailName.Text.TrimEnd()}"; + } + + if (detail.TryGetValue(out FText detailValue, "Value") && !string.IsNullOrEmpty(detailValue.Text)) + { + Description += $" ({detailValue.Text})"; + } + } + } + + Description = Utils.RemoveHtmlTags(Description); + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + case EIconStyle.NoText: + DrawBackground(c); + DrawPreview(c); + break; + default: + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + break; + } + + return SKImage.FromBitmap(ret); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BasePlaylist.cs b/FModel/Creator/Bases/FN/BasePlaylist.cs new file mode 100644 index 00000000..b33a2761 --- /dev/null +++ b/FModel/Creator/Bases/FN/BasePlaylist.cs @@ -0,0 +1,76 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Services; +using FModel.ViewModels; +using SkiaSharp; + +namespace FModel.Creator.Bases.FN +{ + public class BasePlaylist : UCreator + { + private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView; + private SKBitmap _missionIcon; + + public BasePlaylist(UObject uObject, EIconStyle style) : base(uObject, style) + { + Margin = 0; + Width = 1024; + Height = 512; + Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Tiles/T_Athena_Tile_Matchmaking_Default.T_Athena_Tile_Matchmaking_Default"); + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FText displayName, "UIDisplayName", "DisplayName")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "UIDescription", "Description")) + Description = description.Text; + if (Object.TryGetValue(out UTexture2D missionIcon, "MissionIcon")) + _missionIcon = Utils.GetBitmap(missionIcon).Resize(25); + + if (!Object.TryGetValue(out FName playlistName, "PlaylistName") || string.IsNullOrWhiteSpace(playlistName.Text)) + return; + + var playlist = _apiEndpointView.FortniteApi.GetPlaylist(playlistName.Text); + if (!playlist.IsSuccess || !playlist.Data.Images.HasShowcase || + !_apiEndpointView.FortniteApi.TryGetBytes(playlist.Data.Images.Showcase, out var image)) + return; + + Preview = Utils.GetBitmap(image).Resize(1024, 512); // Force size to 1024x512 to prevent huge previews. + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + switch (Style) + { + case EIconStyle.NoBackground: + DrawPreview(c); + break; + case EIconStyle.NoText: + DrawPreview(c); + DrawMissionIcon(c); + break; + default: + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + DrawMissionIcon(c); + break; + } + + return SKImage.FromBitmap(ret); + } + + private void DrawMissionIcon(SKCanvas c) + { + if (_missionIcon == null) return; + c.DrawBitmap(_missionIcon, new SKPoint(5, 5), ImagePaint); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseQuest.cs b/FModel/Creator/Bases/FN/BaseQuest.cs new file mode 100644 index 00000000..1baafaeb --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseQuest.cs @@ -0,0 +1,259 @@ +using System; +using System.Linq; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Extensions; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseQuest : BaseIcon + { + private int _count; + private Reward _reward; + private readonly bool _screenLayer; + private readonly string[] _unauthorizedReward = {"Token", "ChallengeBundle", "GiftBox"}; + + public string NextQuestName { get; private set; } + + public BaseQuest(UObject uObject, EIconStyle style) : base(uObject, style) + { + Margin = 0; + Width = 1024; + Height = 256; + DefaultPreview = Utils.GetBitmap("FortniteGame/Content/Athena/HUD/Quests/Art/T_NPC_Default.T_NPC_Default"); + if (uObject != null) + { + _screenLayer = uObject.ExportType.Equals("FortFeatItemDefinition", StringComparison.OrdinalIgnoreCase); + } + } + + private BaseQuest(int completionCount, EIconStyle style) : this(null, style) // completion + { + var description = completionCount < 0 ? + Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete all {0} challenges to earn the reward item") : + Utils.GetLocalizedResource("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete any {0} challenges to earn the reward item"); + + DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0); + } + + public BaseQuest(int completionCount, FSoftObjectPath itemDefinition, EIconStyle style) : this(completionCount, style) // completion + { + _reward = Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject) ? new Reward(uObject) : new Reward(); + } + + public BaseQuest(int completionCount, int quantity, string reward, EIconStyle style) : this(completionCount, style) // completion + { + _reward = new Reward(quantity, reward); + } + + public override void ParseForInfo() + { + ParseForReward(false); + + if (Object.TryGetValue(out FStructFallback urgentQuestData, "UrgentQuestData")) + { + if (urgentQuestData.TryGetValue(out FText eventTitle, "EventTitle")) + DisplayName = eventTitle.Text; + if (urgentQuestData.TryGetValue(out FText eventDescription, "EventDescription")) + Description = eventDescription.Text; + if (urgentQuestData.TryGetValue(out FPackageIndex alertIcon, "AlertIcon", "BountyPriceImage")) + Preview = Utils.GetBitmap(alertIcon); + } + else + { + Description = ShortDescription; + if (Object.TryGetValue(out FText completionText, "CompletionText")) + Description += "\n" + completionText.Text; + if (Object.TryGetValue(out FSoftObjectPath tandemCharacterData, "TandemCharacterData") && + Utils.TryLoadObject(tandemCharacterData.AssetPathName.Text, out UObject uObject) && + uObject.TryGetValue(out FSoftObjectPath tandemIcon, "SidePanelIcon", "EntryListIcon", "ToastIcon")) + { + Preview = Utils.GetBitmap(tandemIcon); + } + } + + if (Object.TryGetValue(out int objectiveCompletionCount, "ObjectiveCompletionCount")) + _count = objectiveCompletionCount; + + if (Object.TryGetValue(out FStructFallback[] objectives, "Objectives") && objectives.Length > 0) + { + // actual description doesn't exist + if (string.IsNullOrEmpty(Description) && objectives[0].TryGetValue(out FText description, "Description")) + Description = description.Text; + + // ObjectiveCompletionCount doesn't exist + if (_count == 0) + { + if (objectives[0].TryGetValue(out int count, "Count") && count > 1) + _count = count; + else + _count = objectives.Length; + } + } + + if (Object.TryGetValue(out FStructFallback[] rewards, "Rewards")) + { + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out FStructFallback itemPrimaryAssetId, "ItemPrimaryAssetId") || + !reward.TryGetValue(out int quantity, "Quantity")) continue; + + if (!itemPrimaryAssetId.TryGetValue(out FStructFallback primaryAssetType, "PrimaryAssetType") || + !itemPrimaryAssetId.TryGetValue(out FName primaryAssetName, "PrimaryAssetName") || + !primaryAssetType.TryGetValue(out FName name, "Name")) continue; + + if (name.Text.Equals("Quest", StringComparison.OrdinalIgnoreCase)) + { + NextQuestName = primaryAssetName.Text; + } + else if (!_unauthorizedReward.Contains(name.Text)) + { + _reward = new Reward(quantity, primaryAssetName); + } + } + } + + if (_reward == null && Object.TryGetValue(out UDataTable rewardsTable, "RewardsTable")) + { + if (rewardsTable.TryGetDataTableRow("Default", StringComparison.InvariantCulture, out var row)) + { + if (row.TryGetValue(out FName templateId, "TemplateId") && + row.TryGetValue(out int quantity, "Quantity")) + { + _reward = new Reward(quantity, templateId); + } + } + } + + if (_reward == null && Object.TryGetValue(out FStructFallback[] hiddenRewards, "HiddenRewards")) + { + foreach (var hiddenReward in hiddenRewards) + { + if (!hiddenReward.TryGetValue(out FName templateId, "TemplateId") || + !hiddenReward.TryGetValue(out int quantity, "Quantity")) continue; + + _reward = new Reward(quantity, templateId); + break; + } + } + + _reward ??= new Reward(); + } + + public void DrawQuest(SKCanvas c, int y) + { + DrawBackground(c, y); + DrawPreview(c, y); + DrawTexts(c, y); + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawQuest(c, 0); + + return SKImage.FromBitmap(ret); + } + + private string ReformatString(string s, string completionCount, bool isAll) + { + s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "{0}"); + var index = s.IndexOf("{0}|plural(", StringComparison.OrdinalIgnoreCase); + if (index > -1) + { + var p = s.Substring(index, s[index..].IndexOf(')') + 1); + s = s.Replace(p, string.Empty); + s = s.Insert(s.IndexOf("", StringComparison.OrdinalIgnoreCase), p.SubstringAfter("(").SubstringAfter("=").SubstringBefore(",")); + } + + var upper = s.SubstringAfter(">").SubstringBefore(""); + return string.Format(Utils.RemoveHtmlTags(s.Replace(upper, upper.ToUpper())), isAll ? string.Empty : completionCount).Replace(" ", " "); + } + + private readonly SKPaint _informationPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColor.Parse("#262630") + }; + + private void DrawBackground(SKCanvas c, int y) + { + c.DrawRect(new SKRect(Margin, y, Width, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, + new[] {Background[0].WithAlpha(50), Background[1].WithAlpha(50)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(Height / 2, y, Width, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, y + Height), new SKPoint(Width / 2, 75), + new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, y + 75, Width, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, y + Height / 2), Width / 5 * 4, Background, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(Margin, y, Height / 2, y + Height), _informationPaint); + + _informationPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Height / 2, y + Height / 2), new SKPoint(Height / 2 + 100, y + Height / 2), + new[] {SKColors.Black.WithAlpha(25), Background[1].WithAlpha(0)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(Height / 2, y, Height / 2 + 100, y + Height), _informationPaint); + } + + private void DrawPreview(SKCanvas c, int y) + { + ImagePaint.BlendMode = _screenLayer ? SKBlendMode.Screen : Preview == null ? SKBlendMode.ColorBurn : SKBlendMode.SrcOver; + c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, y, Height - Margin, y + Height), ImagePaint); + } + + private void DrawTexts(SKCanvas c, int y) + { + _informationPaint.Shader = null; + + if (!string.IsNullOrWhiteSpace(DisplayName)) + { + _informationPaint.TextSize = 40; + _informationPaint.Color = SKColors.White; + _informationPaint.Typeface = Utils.Typefaces.Bundle; + while (_informationPaint.MeasureText(DisplayName) > Width - Height - 10) + { + _informationPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_informationPaint.Typeface); + shaper.Shape(DisplayName, _informationPaint); + c.DrawShapedText(shaper, DisplayName, Height, y + 50, _informationPaint); + } + + var outY = y + 75f; + if (!string.IsNullOrWhiteSpace(Description)) + { + _informationPaint.TextSize = 16; + _informationPaint.Color = SKColors.White.WithAlpha(175); + _informationPaint.Typeface = Utils.Typefaces.Description; + Utils.DrawMultilineText(c, Description, Width - Height, y, SKTextAlign.Left, + new SKRect(Height, outY, Width - 10, y + Height), _informationPaint, out outY); + } + + _informationPaint.Color = Border[0].WithAlpha(100); + c.DrawRect(new SKRect(Height, outY, Width - 150, outY + 5), _informationPaint); + + if (_count > 0) + { + _informationPaint.TextSize = 25; + _informationPaint.Color = SKColors.White; + _informationPaint.Typeface = Utils.Typefaces.BundleNumber; + c.DrawText("0 / ", new SKPoint(Width - 130, outY + 10), _informationPaint); + + _informationPaint.Color = Border[0]; + c.DrawText(_count.ToString(), new SKPoint(Width - 95, outY + 10), _informationPaint); + } + + _reward.DrawQuest(c, new SKRect(Height, outY + 25, Width - 20, y + Height - 25)); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseSeason.cs b/FModel/Creator/Bases/FN/BaseSeason.cs new file mode 100644 index 00000000..c39be25c --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseSeason.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseSeason : UCreator + { + private Reward _firstWinReward; + private Dictionary> _bookXpSchedule; + private const int _headerHeight = 150; + // keep the list because rewards are ordered by least to most important + // we only care about the most but we also have filters so we can't just take the last reward + + public BaseSeason(UObject uObject, EIconStyle style) : base(uObject, style) + { + Width = 1024; + Height = _headerHeight + 50; + Margin = 0; + } + + public override void ParseForInfo() + { + _bookXpSchedule = new Dictionary>(); + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text.ToUpperInvariant(); + + if (Object.TryGetValue(out FStructFallback seasonFirstWinRewards, "SeasonFirstWinRewards") && + seasonFirstWinRewards.TryGetValue(out FStructFallback[] rewards, "Rewards")) + { + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || + !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; + + _firstWinReward = new Reward(uObject); + break; + } + } + + var freeLevels = Array.Empty(); + var paidLevels = Array.Empty(); + if (Object.TryGetValue(out FPackageIndex[] additionalSeasonData, "AdditionalSeasonData") && + additionalSeasonData.Length > 0 && Utils.TryGetPackageIndexExport(additionalSeasonData[0], out UObject data) && + data.TryGetValue(out FStructFallback battlePassXpScheduleFree, "BattlePassXpScheduleFree") && + battlePassXpScheduleFree.TryGetValue(out freeLevels, "Levels") && + data.TryGetValue(out FStructFallback battlePassXpSchedulePaid, "BattlePassXpSchedulePaid") && + battlePassXpSchedulePaid.TryGetValue(out paidLevels, "Levels")) + { + // we got them boys + } + else if (Object.TryGetValue(out FStructFallback bookXpScheduleFree, "BookXpScheduleFree") && + bookXpScheduleFree.TryGetValue(out freeLevels, "Levels") && + Object.TryGetValue(out FStructFallback bookXpSchedulePaid, "BookXpSchedulePaid") && + bookXpSchedulePaid.TryGetValue(out paidLevels, "Levels")) + { + // we got them boys + } + + for (var i = 0; i < freeLevels.Length; i++) + { + _bookXpSchedule[i] = new List(); + if (!freeLevels[i].TryGetValue(out rewards, "Rewards")) continue; + + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || + itemDefinition.AssetPathName.Text.Contains("/Items/Tokens/") || + !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; + + _bookXpSchedule[i].Add(new Reward(uObject)); + break; + } + } + + for (var i = 0; i < paidLevels.Length; i++) + { + if (!paidLevels[i].TryGetValue(out rewards, "Rewards")) continue; + + foreach (var reward in rewards) + { + if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition") || + !Utils.TryLoadObject(itemDefinition.AssetPathName.Text, out UObject uObject)) continue; + + _bookXpSchedule[i].Add(new Reward(uObject)); + break; + } + } + + Height += 100 * _bookXpSchedule.Count / 10; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawHeader(c); + _firstWinReward?.DrawSeasonWin(c, _headerHeight); + DrawBookSchedule(c); + + return SKImage.FromBitmap(ret); + } + + private const int _DEFAULT_AREA_SIZE = 80; + private readonly SKPaint _headerPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Bundle, TextSize = 50, + TextAlign = SKTextAlign.Center, Color = SKColor.Parse("#262630") + }; + private readonly SKPaint _bookPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.BundleNumber, + Color = SKColors.White, TextAlign = SKTextAlign.Center, TextSize = 15 + }; + + public void DrawHeader(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint); + + _headerPaint.Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, _headerHeight / 2), Width / 5 * 4, + new[] {SKColors.SkyBlue.WithAlpha(50), SKColors.Blue.WithAlpha(50)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 0, Width, Height), _headerPaint); + + _headerPaint.Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, _headerHeight), new SKPoint(Width / 2, 75), + new[] {SKColors.Black.WithAlpha(25), SKColors.Blue.WithAlpha(0)}, SKShaderTileMode.Clamp); + c.DrawRect(new SKRect(0, 75, Width, _headerHeight), _headerPaint); + + _headerPaint.Shader = null; + _headerPaint.Color = SKColors.White; + while (_headerPaint.MeasureText(DisplayName) > Width) + { + _headerPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_headerPaint.Typeface); + var shapedText = shaper.Shape(DisplayName, _headerPaint); + c.DrawShapedText(shaper, DisplayName, (Width - shapedText.Points[^1].X) / 2, _headerHeight / 2 + _headerPaint.TextSize / 2 - 10, _headerPaint); + } + + private void DrawBookSchedule(SKCanvas c) + { + var x = 20; + var y = _headerHeight + 50; + foreach (var (index, reward) in _bookXpSchedule) + { + if (index == 0 || reward.Count == 0 || !reward[0].HasReward()) + continue; + + c.DrawText(index.ToString(), new SKPoint(x + _DEFAULT_AREA_SIZE / 2, y - 5), _bookPaint); + reward[0].DrawSeason(c, x, y, _DEFAULT_AREA_SIZE); + + if (index != 1 && index % 10 == 0) + { + y += _DEFAULT_AREA_SIZE + 20; + x = 20; + } + else + { + x += _DEFAULT_AREA_SIZE + 20; + } + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseSeries.cs b/FModel/Creator/Bases/FN/BaseSeries.cs new file mode 100644 index 00000000..6f1fd619 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseSeries.cs @@ -0,0 +1,27 @@ +using CUE4Parse.UE4.Assets.Exports; +using SkiaSharp; + +namespace FModel.Creator.Bases.FN +{ + public class BaseSeries : BaseIcon + { + public BaseSeries(UObject uObject, EIconStyle style) : base(uObject, style) + { + } + + public override void ParseForInfo() + { + GetSeries(Object); + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + + return SKImage.FromBitmap(ret); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/BaseUserControl.cs b/FModel/Creator/Bases/FN/BaseUserControl.cs new file mode 100644 index 00000000..c4f12217 --- /dev/null +++ b/FModel/Creator/Bases/FN/BaseUserControl.cs @@ -0,0 +1,193 @@ +using System.Collections.Generic; +using System.Globalization; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class BaseUserControl : UCreator + { + private List _optionValues = new(); + + private readonly SKPaint _displayNamePaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = 45, + Color = SKColors.White, TextAlign = SKTextAlign.Left + }; + private readonly SKPaint _descriptionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = 25, + Color = SKColor.Parse("88DBFF"), TextAlign = SKTextAlign.Left + }; + + public BaseUserControl(UObject uObject, EIconStyle style) : base(uObject, style) + { + Width = 512; + Height = 128; + Margin = 32; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName")) + DisplayName = optionDisplayName.Text.ToUpperInvariant(); + if (Object.TryGetValue(out FText optionDescription, "OptionDescription")) + { + Description = optionDescription.Text; + + if (string.IsNullOrWhiteSpace(Description)) return; + Height += (int) _descriptionPaint.TextSize * Utils.SplitLines(Description, _descriptionPaint, Width - Margin).Count; + Height += (int) _descriptionPaint.TextSize; + } + + if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues")) + { + _optionValues = new List(); + foreach (var option in optionValues) + { + if (option.TryGetValue(out FText displayName, "DisplayName")) + { + var opt = new Options {Option = displayName.Text.ToUpperInvariant()}; + if (option.TryGetValue(out FLinearColor color, "Value")) + opt.Color = SKColor.Parse(color.Hex).WithAlpha(150); + + _optionValues.Add(opt); + } + else if (option.TryGetValue(out FName primaryAssetName, "PrimaryAssetName")) + { + _optionValues.Add(new Options {Option = primaryAssetName.Text}); + } + } + } + + if (Object.TryGetValue(out FText optionOnText, "OptionOnText")) + _optionValues.Add(new Options {Option = optionOnText.Text}); + if (Object.TryGetValue(out FText optionOffText, "OptionOffText")) + _optionValues.Add(new Options {Option = optionOffText.Text}); + + if (Object.TryGetValue(out int iMin, "Min", "DefaultValue") && + Object.TryGetValue(out int iMax, "Max")) + { + var increment = iMin; + if (Object.TryGetValue(out int incrementValue, "IncrementValue")) + increment = incrementValue; + + var format = "{0}"; + if (Object.TryGetValue(out FText unitName, "UnitName")) + format = unitName.Text; + + for (var i = iMin; i <= iMax; i += increment) + { + _optionValues.Add(new Options {Option = string.Format(format, i)}); + } + } + + if (Object.TryGetValue(out float fMin, "Min", "DefaultValue") && + Object.TryGetValue(out float fMax, "Max")) + { + var increment = fMin; + if (Object.TryGetValue(out float incrementValue, "IncrementValue")) + increment = incrementValue; + + var format = "{0}"; + if (Object.TryGetValue(out FText unitName, "UnitName")) + format = unitName.Text; + + for (var i = fMin; i <= fMax; i += increment) + { + _optionValues.Add(new Options {Option = string.Format(format, i)}); + } + } + + Height += Margin; + Height += 35 * _optionValues.Count; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Opaque); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawInformation(c); + + return SKImage.FromBitmap(ret); + } + + private new void DrawBackground(SKCanvas c) + { + c.DrawRect(new SKRect(0, 0, Width, Height), + new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(Width / 2, Height), + new SKPoint(Width, Height / 4), + new[] {SKColor.Parse("01369C"), SKColor.Parse("1273C8")}, + SKShaderTileMode.Clamp) + }); + } + + private void DrawInformation(SKCanvas c) + { + // display name + while (_displayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) + { + _displayNamePaint.TextSize -= 2; + } + + var shaper = new CustomSKShaper(_displayNamePaint.Typeface); + shaper.Shape(DisplayName, _displayNamePaint); + c.DrawShapedText(shaper, DisplayName, Margin, Margin + _displayNamePaint.TextSize, _displayNamePaint); +#if DEBUG + c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Margin + _displayNamePaint.TextSize), new SKPaint {Color = SKColors.Blue, IsStroke = true}); +#endif + + // description + float y = Margin; + if (!string.IsNullOrEmpty(DisplayName)) y += _displayNamePaint.TextSize; + if (!string.IsNullOrEmpty(Description)) y += _descriptionPaint.TextSize + Margin / 2F; + + Utils.DrawMultilineText(c, Description, Width, Margin, SKTextAlign.Left, + new SKRect(Margin, y, Width - Margin, 256), _descriptionPaint, out var top); + + // options + foreach (var option in _optionValues) + { + option.Draw(c, Margin, Width, ref top); + } + } + } + + public class Options + { + private const int _SPACE = 5; + private const int _HEIGHT = 30; + + private readonly SKPaint _optionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = 20, + Color = SKColor.Parse("EEFFFF"), TextAlign = SKTextAlign.Left + }; + + public string Option; + public SKColor Color = SKColor.Parse("55C5FC").WithAlpha(150); + + public void Draw(SKCanvas c, int margin, int width, ref float top) + { + c.DrawRect(new SKRect(margin, top, width - margin, top + _HEIGHT), new SKPaint {IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = Color}); + c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint); + top += _HEIGHT + _SPACE; + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/FN/Reward.cs b/FModel/Creator/Bases/FN/Reward.cs new file mode 100644 index 00000000..cef0b920 --- /dev/null +++ b/FModel/Creator/Bases/FN/Reward.cs @@ -0,0 +1,153 @@ +using System; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases.FN +{ + public class Reward + { + private string _rewardQuantity; + private BaseIcon _theReward; + + public bool HasReward() => _theReward != null; + + public Reward() + { + _rewardQuantity = "x0"; + } + + public Reward(int quantity, FName primaryAssetName) : this(quantity, primaryAssetName.Text) + { + } + + public Reward(int quantity, string assetName) : this() + { + _rewardQuantity = $"x{quantity:###,###,###}".Trim(); + + if (assetName.Contains(':')) + { + var parts = assetName.Split(':'); + + if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase)) + { + if (!Utils.TryLoadObject($"FortniteGame/Content/Items/BannerIcons/{parts[1]}.{parts[1]}", out UObject p)) + return; + + _theReward = new BaseIcon(p, EIconStyle.Default); + _theReward.ParseForReward(false); + _theReward.Border[0] = SKColors.White; + _rewardQuantity = _theReward.DisplayName; + } + else GetReward(parts[1]); + } + else GetReward(assetName); + } + + public Reward(UObject uObject) + { + _theReward = new BaseIcon(uObject, EIconStyle.Default); + _theReward.ParseForReward(false); + _theReward.Border[0] = SKColors.White; + _rewardQuantity = _theReward.DisplayName; + } + + private readonly SKPaint _rewardPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High + }; + + public void DrawQuest(SKCanvas c, SKRect rect) + { + _rewardPaint.TextSize = 50; + if (HasReward()) + { + c.DrawBitmap(_theReward.Preview.Resize((int) rect.Height), new SKPoint(rect.Left, rect.Top), _rewardPaint); + + _rewardPaint.Color = _theReward.Border[0]; + _rewardPaint.Typeface = _rewardQuantity.StartsWith("x") ? Utils.Typefaces.BundleNumber : Utils.Typefaces.Bundle; + _rewardPaint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, _theReward.Background[0].WithAlpha(150)); + while (_rewardPaint.MeasureText(_rewardQuantity) > rect.Width) + { + _rewardPaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(_rewardPaint.Typeface); + shaper.Shape(_rewardQuantity, _rewardPaint); + c.DrawShapedText(shaper, _rewardQuantity, rect.Left + rect.Height + 25, rect.MidY + 20, _rewardPaint); + } + else + { + _rewardPaint.Color = SKColors.White; + _rewardPaint.Typeface = Utils.Typefaces.BundleNumber; + c.DrawText("No Reward", new SKPoint(rect.Left, rect.MidY + 20), _rewardPaint); + } + } + + public void DrawSeasonWin(SKCanvas c, int size) + { + if (!HasReward()) return; + c.DrawBitmap(_theReward.Preview.Resize(size), new SKPoint(0, 0), _rewardPaint); + } + + public void DrawSeason(SKCanvas c, int x, int y, int areaSize) + { + if (!HasReward()) return; + + // area + icon + _rewardPaint.Color = SKColor.Parse("#0F5CAF"); + c.DrawRect(new SKRect(x, y, x + areaSize, y + areaSize), _rewardPaint); + c.DrawBitmap(_theReward.Preview.Resize(areaSize), new SKPoint(x, y), _rewardPaint); + + // rarity color + _rewardPaint.Color = _theReward.Background[0]; + var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd}; + pathBottom.MoveTo(x, y + areaSize); + pathBottom.LineTo(x, y + areaSize - areaSize / 25 * 2.5f); + pathBottom.LineTo(x + areaSize, y + areaSize - areaSize / 25 * 4.5f); + pathBottom.LineTo(x + areaSize, y + areaSize); + pathBottom.Close(); + c.DrawPath(pathBottom, _rewardPaint); + } + + private void GetReward(string trigger) + { + switch (trigger.ToLower()) + { + case "athenabattlestar": + _theReward = new BaseIcon(null, EIconStyle.Default); + _theReward.Border[0] = SKColor.Parse("FFDB67"); + _theReward.Background[0] = SKColor.Parse("8F4A20"); + _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints.T-FNBR-BattlePoints"); + break; + case "athenaseasonalxp": + _theReward = new BaseIcon(null, EIconStyle.Default); + _theReward.Border[0] = SKColor.Parse("E6FDB1"); + _theReward.Background[0] = SKColor.Parse("51830F"); + _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium.T-FNBR-XPMedium"); + break; + case "mtxgiveaway": + _theReward = new BaseIcon(null, EIconStyle.Default); + _theReward.Border[0] = SKColor.Parse("DCE6FF"); + _theReward.Background[0] = SKColor.Parse("64A0AF"); + _theReward.Preview = Utils.GetBitmap("FortniteGame/Content/UI/Foundation/Textures/Icons/Items/T-Items-MTX.T-Items-MTX"); + break; + default: + { + var path = Utils.GetFullPath($"FortniteGame/Content/Athena/.*?/{trigger}.*"); // path has no objectname and its needed so we push the trigger again as the objectname + if (!string.IsNullOrWhiteSpace(path) && Utils.TryLoadObject(path.Replace("uasset", trigger), out UObject d)) + { + _theReward = new BaseIcon(d, EIconStyle.Default); + _theReward.ParseForReward(false); + _theReward.Border[0] = SKColors.White; + _rewardQuantity = _theReward.DisplayName; + } + + break; + } + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/IBase.cs b/FModel/Creator/Bases/IBase.cs deleted file mode 100644 index 367d6faf..00000000 --- a/FModel/Creator/Bases/IBase.cs +++ /dev/null @@ -1,17 +0,0 @@ -using SkiaSharp; - -namespace FModel.Creator.Bases -{ - interface IBase - { - SKBitmap FallbackImage { get; } - SKBitmap IconImage { get; } - SKColor[] RarityBackgroundColors { get; } - SKColor[] RarityBorderColor { get; } - string DisplayName { get; } - string Description { get; } - int Width { get; } - int Height { get; } - int Margin { get; } - } -} diff --git a/FModel/Creator/Bases/SB/BaseDivision.cs b/FModel/Creator/Bases/SB/BaseDivision.cs new file mode 100644 index 00000000..105e0204 --- /dev/null +++ b/FModel/Creator/Bases/SB/BaseDivision.cs @@ -0,0 +1,48 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.UObject; +using SkiaSharp; + +namespace FModel.Creator.Bases.SB +{ + public class BaseDivision : UCreator + { + public BaseDivision(UObject uObject, EIconStyle style) : base(uObject, style) + { + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FPackageIndex icon, "Icon", "IconNoTier")) + { + Preview = Utils.GetBitmap(icon); + } + + if (Object.TryGetValue(out FLinearColor lightColor, "UILightColor") && + Object.TryGetValue(out FLinearColor mediumColor, "UIMediumColor") && + Object.TryGetValue(out FLinearColor darkColor, "UIDarkColor") && + Object.TryGetValue(out FLinearColor cardColor, "UICardColor")) + { + Background = new[] {SKColor.Parse(lightColor.Hex), SKColor.Parse(cardColor.Hex)}; + Border = new[] {SKColor.Parse(mediumColor.Hex), SKColor.Parse(darkColor.Hex)}; + } + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + + return SKImage.FromBitmap(ret); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/SB/BaseLeague.cs b/FModel/Creator/Bases/SB/BaseLeague.cs new file mode 100644 index 00000000..b10811e8 --- /dev/null +++ b/FModel/Creator/Bases/SB/BaseLeague.cs @@ -0,0 +1,55 @@ +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.UObject; +using SkiaSharp; + +namespace FModel.Creator.Bases.SB +{ + public class BaseLeague : UCreator + { + private int _promotionXp, _xpLostPerMatch; + + public BaseLeague(UObject uObject, EIconStyle style) : base(uObject, style) + { + _promotionXp = 0; + _xpLostPerMatch = 0; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out int promotionXp, "PromotionXP")) + _promotionXp = promotionXp; + if (Object.TryGetValue(out int xpLostPerMatch, "XPLostPerMatch")) + _xpLostPerMatch = xpLostPerMatch; + + if (Object.TryGetValue(out FPackageIndex division, "Division") && + Utils.TryGetPackageIndexExport(division, out UObject div)) + { + var d = new BaseDivision(div, Style); + d.ParseForInfo(); + Preview = d.Preview; + Background = d.Background; + Border = d.Border; + } + + if (Object.TryGetValue(out FText displayName, "DisplayName")) + DisplayName = displayName.Text; + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + + DrawToBottom(c, SKTextAlign.Left, $"PromotionXP: {_promotionXp}"); + DrawToBottom(c, SKTextAlign.Right, $"XPLostPerMatch: {_xpLostPerMatch}"); + + return SKImage.FromBitmap(ret); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/SB/BaseSpellIcon.cs b/FModel/Creator/Bases/SB/BaseSpellIcon.cs new file mode 100644 index 00000000..770e6b81 --- /dev/null +++ b/FModel/Creator/Bases/SB/BaseSpellIcon.cs @@ -0,0 +1,98 @@ +using System; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Engine; +using CUE4Parse.UE4.Objects.Core.i18N; +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Creator.Bases.FN; +using SkiaSharp; + +namespace FModel.Creator.Bases.SB +{ + public class BaseSpellIcon : BaseIcon + { + private SKBitmap _seriesBackground2; + + private readonly SKPaint _overlayPaint = new() + { + FilterQuality = SKFilterQuality.High, + IsAntialias = true, + Color = SKColors.Transparent.WithAlpha(75) + }; + + public BaseSpellIcon(UObject uObject, EIconStyle style) : base(uObject, style) + { + Background = new[] {SKColor.Parse("FFFFFF"), SKColor.Parse("636363")}; + Border = new[] {SKColor.Parse("D0D0D0"), SKColor.Parse("FFFFFF")}; + Width = Object.ExportType.StartsWith("BP_Cosmetic_Card") ? 1536 : 512; + Height = Object.ExportType.StartsWith("BP_Cosmetic_Card") ? 450 : 512; + } + + public override void ParseForInfo() + { + if (Object.TryGetValue(out FName rarity, "Rarity")) + GetRarity(rarity); + + if (Object.TryGetValue(out FSoftObjectPath preview, "IconTexture")) + Preview = Utils.GetBitmap(preview); + else if (Object.TryGetValue(out FPackageIndex icon, "IconTexture")) + Preview = Utils.GetBitmap(icon); + + if (Object.TryGetValue(out FText displayName, "DisplayName", "Title")) + DisplayName = displayName.Text; + if (Object.TryGetValue(out FText description, "Description")) + Description = description.Text; + + SeriesBackground = Utils.GetBitmap("g3/Content/UI/Textures/assets/HUDAccentFillBox.HUDAccentFillBox"); + _seriesBackground2 = Utils.GetBitmap("g3/Content/UI/Textures/assets/store/ItemBGStatic_UIT.ItemBGStatic_UIT"); + } + + public override SKImage Draw() + { + using var ret = new SKBitmap(Width, Height, SKColorType.Rgba8888, SKAlphaType.Premul); + using var c = new SKCanvas(ret); + + DrawBackgrounds(c); + DrawBackground(c); + DrawPreview(c); + DrawTextBackground(c); + DrawDisplayName(c); + DrawDescription(c); + + return SKImage.FromBitmap(ret); + } + + private void DrawBackgrounds(SKCanvas c) + { + if (SeriesBackground != null) + c.DrawBitmap(SeriesBackground, new SKRect(0, 0, Width, Height), ImagePaint); + if (_seriesBackground2 != null) + c.DrawBitmap(_seriesBackground2, new SKRect(0, 0, Width, Height), _overlayPaint); + + var x = Margin * (int) 2.5; + const int radi = 15; + c.DrawCircle(x + radi, x + radi, radi, new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateRadialGradient( + new SKPoint(radi, radi), radi * 2 / 5 * 4, + Background, SKShaderTileMode.Clamp) + }); + } + + private void GetRarity(FName n) + { + if (!Utils.TryLoadObject("g3/Content/UI/UIKit/DT_RarityColors.DT_RarityColors", out UDataTable rarity)) return; + + if (rarity.TryGetDataTableRow(n.Text["EXRarity::".Length..], StringComparison.Ordinal, out var row)) + { + if (row.TryGetValue(out FLinearColor[] colors, "Colors")) + { + Background = new[] {SKColor.Parse(colors[0].Hex), SKColor.Parse(colors[2].Hex)}; + Border = new[] {SKColor.Parse(colors[1].Hex), SKColor.Parse(colors[0].Hex)}; + } + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bases/UCreator.cs b/FModel/Creator/Bases/UCreator.cs new file mode 100644 index 00000000..deedc79a --- /dev/null +++ b/FModel/Creator/Bases/UCreator.cs @@ -0,0 +1,230 @@ +using System; +using System.Windows; +using CUE4Parse.UE4.Assets.Exports; +using FModel.Creator.Bases.FN; +using FModel.Framework; +using SkiaSharp; +using SkiaSharp.HarfBuzz; + +namespace FModel.Creator.Bases +{ + public abstract class UCreator + { + protected UObject Object { get; } + protected EIconStyle Style { get; } + public SKBitmap DefaultPreview { get; set; } + public SKBitmap Preview { get; set; } + public SKColor[] Background { get; protected set; } + public SKColor[] Border { get; protected set; } + public string DisplayName { get; protected set; } + public string Description { get; protected set; } + public int Margin { get; protected set; } + public int Width { get; protected set; } + public int Height { get; protected set; } + + public abstract void ParseForInfo(); + public abstract SKImage Draw(); + + protected UCreator(UObject uObject, EIconStyle style) + { + DefaultPreview = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_Placeholder_Item_Image.png"))?.Stream); + Background = new[] {SKColor.Parse("5BFD00"), SKColor.Parse("003700")}; + Border = new[] {SKColor.Parse("1E8500"), SKColor.Parse("5BFD00")}; + DisplayName = string.Empty; + Description = string.Empty; + Width = 512; + Height = 512; + Margin = 2; + Object = uObject; + Style = style; + } + + private const int _STARTER_TEXT_POSITION = 380, _NAME_TEXT_SIZE = 45, _BOTTOM_TEXT_SIZE = 15; + protected readonly SKPaint DisplayNamePaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.DisplayName, TextSize = _NAME_TEXT_SIZE, + Color = SKColors.White, TextAlign = SKTextAlign.Center + }; + protected readonly SKPaint DescriptionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Typeface = Utils.Typefaces.Description, TextSize = 13, + Color = SKColors.White + }; + protected readonly SKPaint ImagePaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High + }; + private readonly SKPaint _textBackgroundPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = new SKColor(0, 0, 50, 75) + }; + private readonly SKPaint _shortDescriptionPaint = new() + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Color = SKColors.White + }; + + public void DrawBackground(SKCanvas c) + { + // reverse doesn't affect basic rarities + if (Background[0] == Background[1]) Background[0] = Border[0]; + Background[0].ToHsl(out _, out _, out var l1); + Background[1].ToHsl(out _, out _, out var l2); + var reverse = l1 > l2; + + // border + c.DrawRect(new SKRect(0, 0, Width, Height), + new SKPaint + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient( + new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), Border, SKShaderTileMode.Clamp) + }); + + if (this is BaseIcon {SeriesBackground: { }} baseIcon) + c.DrawBitmap(baseIcon.SeriesBackground, new SKRect(baseIcon.Margin, baseIcon.Margin, baseIcon.Width - baseIcon.Margin, + baseIcon.Height - baseIcon.Margin), ImagePaint); + else + { + switch (Style) + { + case EIconStyle.Flat: + { + c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), + new SKPaint + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateLinearGradient(new SKPoint(Width / 2, Height), new SKPoint(Width, Height / 4), + new[] {Background[reverse ? 0 : 1].WithAlpha(150), Border[0]}, SKShaderTileMode.Clamp) + }); + if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + + var pathTop = new SKPath {FillType = SKPathFillType.EvenOdd}; + pathTop.MoveTo(Margin, Margin); + pathTop.LineTo(Margin + Width / 17 * 10, Margin); + pathTop.LineTo(Margin, Margin + Height / 17); + pathTop.Close(); + c.DrawPath(pathTop, new SKPaint + { + IsAntialias = true, + FilterQuality = SKFilterQuality.High, + Color = Background[1].WithAlpha(75) + }); + break; + } + default: + { + c.DrawRect(new SKRect(Margin, Margin, Width - Margin, Height - Margin), + new SKPaint + { + IsAntialias = true, FilterQuality = SKFilterQuality.High, + Shader = SKShader.CreateRadialGradient(new SKPoint(Width / 2, Height / 2), Width / 5 * 4, + new[] {Background[reverse ? 0 : 1], Background[reverse ? 1 : 0]}, + SKShaderTileMode.Clamp) + }); + break; + } + } + } + } + + protected void DrawPreview(SKCanvas c) + => c.DrawBitmap(Preview ?? DefaultPreview, new SKRect(Margin, Margin, Width - Margin, Height - Margin), ImagePaint); + + protected void DrawTextBackground(SKCanvas c) + { + if (string.IsNullOrEmpty(DisplayName) && string.IsNullOrEmpty(Description)) return; + switch (Style) + { + case EIconStyle.Flat: + { + var pathBottom = new SKPath {FillType = SKPathFillType.EvenOdd}; + pathBottom.MoveTo(Margin, Height - Margin); + pathBottom.LineTo(Margin, Height - Margin - Height / 17 * 2.5f); + pathBottom.LineTo(Width - Margin, Height - Margin - Height / 17 * 4.5f); + pathBottom.LineTo(Width - Margin, Height - Margin); + pathBottom.Close(); + c.DrawPath(pathBottom, _textBackgroundPaint); + break; + } + default: + c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, Height - Margin), _textBackgroundPaint); + break; + } + } + + protected void DrawDisplayName(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(DisplayName)) return; + + while (DisplayNamePaint.MeasureText(DisplayName) > Width - Margin * 2) + { + DisplayNamePaint.TextSize -= 1; + } + + var shaper = new CustomSKShaper(DisplayNamePaint.Typeface); + var shapedText = shaper.Shape(DisplayName, DisplayNamePaint); + var x = (Width - shapedText.Points[^1].X) / 2; + var y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE; + + switch (Style) + { + case EIconStyle.Flat: + { + DisplayNamePaint.TextAlign = SKTextAlign.Right; + x = Width - Margin * 2 - shapedText.Points[^1].X; + break; + } + } + +#if DEBUG + c.DrawLine(x, 0, x, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true}); + c.DrawLine(x + shapedText.Points[^1].X, 0, x + shapedText.Points[^1].X, Width, new SKPaint {Color = SKColors.Blue, IsStroke = true}); + c.DrawRect(new SKRect(Margin, _STARTER_TEXT_POSITION, Width - Margin, y), new SKPaint {Color = SKColors.Blue, IsStroke = true}); +#endif + + c.DrawShapedText(shaper, DisplayName, x, y, DisplayNamePaint); + } + + protected void DrawDescription(SKCanvas c) + { + if (string.IsNullOrWhiteSpace(Description)) return; + + var maxLine = string.IsNullOrEmpty(DisplayName) ? 8 : 4; + var side = SKTextAlign.Center; + switch (Style) + { + case EIconStyle.Flat: + side = SKTextAlign.Right; + break; + } + + Utils.DrawCenteredMultilineText(c, Description, maxLine, Width, Margin, side, + new SKRect(Margin, string.IsNullOrEmpty(DisplayName) ? _STARTER_TEXT_POSITION : _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, Width - Margin, Height - _BOTTOM_TEXT_SIZE), DescriptionPaint); + } + + protected void DrawToBottom(SKCanvas c, SKTextAlign side, string text) + { + if (string.IsNullOrEmpty(text)) return; + + _shortDescriptionPaint.TextAlign = side; + _shortDescriptionPaint.TextSize = Utils.Typefaces.Bottom == null ? 15 : 13; + switch (side) + { + case SKTextAlign.Left: + _shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.DisplayName; + var shaper = new CustomSKShaper(_shortDescriptionPaint.Typeface); + shaper.Shape(text, _shortDescriptionPaint); + + c.DrawShapedText(shaper, text, Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint); + break; + case SKTextAlign.Right: + _shortDescriptionPaint.Typeface = Utils.Typefaces.Bottom ?? Utils.Typefaces.Default; + c.DrawText(text, Width - Margin * 2.5f, Width - Margin * 2.5f, _shortDescriptionPaint); + break; + } + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Bundles/CompletionReward.cs b/FModel/Creator/Bundles/CompletionReward.cs deleted file mode 100644 index b86364ab..00000000 --- a/FModel/Creator/Bundles/CompletionReward.cs +++ /dev/null @@ -1,60 +0,0 @@ -using FModel.Utils; -using System; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bundles -{ - public class CompletionReward - { - private const string _TRIGGER1 = ""; - private const string _TRIGGER2 = ""; - public string CompletionText; - public Reward Reward; - - public CompletionReward(IntProperty completionCount) - { - string all = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat_All", "Complete all {0} challenges to earn the reward item"); - string allFormated = ReformatString(all, completionCount.Value.ToString(), true); - string any = Localizations.GetLocalization("AthenaChallengeDetailsEntry", "CompletionRewardFormat", "Complete any {0} challenges to earn the reward item"); - string anyFormated = ReformatString(any, completionCount.Value.ToString(), false); - CompletionText = completionCount.Value >= 0 ? anyFormated : allFormated; - - Reward = null; - } - - public CompletionReward(IntProperty completionCount, IntProperty quantity, SoftObjectProperty itemDefinition) : this(completionCount) - { - Reward = new Reward(quantity, itemDefinition); - } - - public CompletionReward(IntProperty completionCount, IntProperty quantity, string reward) : this(completionCount) - { - Reward = new Reward(quantity, reward); - } - - private string ReformatString(string s, string completionCount, bool isAll) - { - s = s.Replace("({0})", "{0}").Replace("{QuestNumber}", "{0}"); - - int index = s.IndexOf("|plural(", StringComparison.CurrentCultureIgnoreCase); - if (index > -1) - { - int i = s.Substring(index).IndexOf(')', StringComparison.CurrentCultureIgnoreCase); - s = s.Replace(s.Substring(index, i + 1), string.Empty).Replace("{0} {0}", "{0}"); - } - - int index1 = s.IndexOf(_TRIGGER1, StringComparison.CurrentCultureIgnoreCase); - if (index1 < 0) index1 = 0; - string partOne = s.Substring(0, index1); - - string partTemp = s.Substring(index1 + _TRIGGER1.Length); - int index2 = partTemp.IndexOf(_TRIGGER2, StringComparison.CurrentCultureIgnoreCase); - if (index2 < 0) index2 = 0; - string partUpper = partTemp.Substring(0, index2).ToUpper().Replace("{0}", isAll ? string.Empty : completionCount); - - string partTwo = partTemp.Substring(index2 + _TRIGGER2.Length); - - return string.Format("{0}{1}{2}", partOne, partUpper, partTwo).Replace(" ", " ").Replace(" ,", ","); - } - } -} diff --git a/FModel/Creator/Bundles/Header.cs b/FModel/Creator/Bundles/Header.cs deleted file mode 100644 index 0f727455..00000000 --- a/FModel/Creator/Bundles/Header.cs +++ /dev/null @@ -1,106 +0,0 @@ -using SkiaSharp; -using System; -using System.IO; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bundles -{ - public class Header - { - public SKColor PrimaryColor; - public SKColor SecondaryColor; - public SKColor AccentColor; - public SKBitmap DisplayImage; // 256x256 - public SKBitmap CustomBackground; // 1024x256 - - private readonly Random _random = new Random(Environment.TickCount); - private readonly string[] _randomColors = new string[255] - { - "F44336", "FFEBEE", "FFCDD2", "EF9A9A", "E57373", "EF5350", "E53935", "D32F2F", "C62828", "B71C1C", - "FF8A80", "FF5252", "FF1744", "D50000", "FCE4EC", "F8BBD0", "F48FB1", "F06292", "EC407A", "E91E63", - "D81B60", "C2185B", "AD1457", "880E4F", "FF80AB", "FF4081", "F50057", "C51162", "F3E5F5", "E1BEE7", - "CE93D8", "BA68C8", "AB47BC", "9C27B0", "8E24AA", "7B1FA2", "6A1B9A", "4A148C", "EA80FC", "E040FB", - "D500F9", "AA00FF", "EDE7F6", "D1C4E9", "B39DDB", "9575CD", "7E57C2", "673AB7", "5E35B1", "512DA8", - "4527A0", "311B92", "B388FF", "7C4DFF", "651FFF", "6200EA", "E8EAF6", "C5CAE9", "9FA8DA", "7986CB", - "5C6BC0", "3F51B5", "3949AB", "303F9F", "283593", "1A237E", "8C9EFF", "536DFE", "3D5AFE", "304FFE", - "E3F2FD", "BBDEFB", "90CAF9", "64B5F6", "42A5F5", "2196F3", "1E88E5", "1976D2", "1565C0", "0D47A1", - "82B1FF", "448AFF", "2979FF", "2962FF", "E1F5FE", "B3E5FC", "81D4FA", "4FC3F7", "29B6F6", "03A9F4", - "039BE5", "0288D1", "0277BD", "01579B", "80D8FF", "40C4FF", "00B0FF", "0091EA", "E0F7FA", "B2EBF2", - "80DEEA", "4DD0E1", "26C6DA", "00BCD4", "00ACC1", "0097A7", "00838F", "006064", "84FFFF", "18FFFF", - "00E5FF", "00B8D4", "E0F2F1", "B2DFDB", "80CBC4", "4DB6AC", "26A69A", "009688", "00897B", "00796B", - "00695C", "004D40", "A7FFEB", "64FFDA", "1DE9B6", "00BFA5", "E8F5E9", "C8E6C9", "A5D6A7", "81C784", - "66BB6A", "4CAF50", "43A047", "388E3C", "2E7D32", "1B5E20", "B9F6CA", "69F0AE", "00E676", "00C853", - "F1F8E9", "DCEDC8", "C5E1A5", "AED581", "9CCC65", "8BC34A", "7CB342", "689F38", "558B2F", "33691E", - "CCFF90", "B2FF59", "76FF03", "64DD17", "F9FBE7", "F0F4C3", "E6EE9C", "DCE775", "D4E157", "CDDC39", - "C0CA33", "AFB42B", "9E9D24", "827717", "F4FF81", "EEFF41", "C6FF00", "AEEA00", "FFFDE7", "FFF9C4", - "FFF59D", "FFF176", "FFEE58", "FFEB3B", "FDD835", "FBC02D", "F9A825", "F57F17", "FFFF8D", "FFFF00", - "FFEA00", "FFD600", "FFF8E1", "FFECB3", "FFE082", "FFD54F", "FFCA28", "FFC107", "FFB300", "FFA000", - "FF8F00", "FF6F00", "FFE57F", "FFD740", "FFC400", "FFAB00", "FFF3E0", "FFE0B2", "FFCC80", "FFB74D", - "FFA726", "FF9800", "FB8C00", "F57C00", "EF6C00", "E65100", "FFD180", "FFAB40", "FF9100", "FF6D00", - "FBE9E7", "FFCCBC", "FFAB91", "FF8A65", "FF7043", "FF5722", "F4511E", "E64A19", "D84315", "BF360C", - "FF9E80", "FF6E40", "FF3D00", "DD2C00", "EFEBE9", "D7CCC8", "BCAAA4", "A1887F", "8D6E63", "795548", - "6D4C41", "5D4037", "4E342E", "3E2723", "FAFAFA", "F5F5F5", "EEEEEE", "E0E0E0", "BDBDBD", "9E9E9E", - "757575", "616161", "424242", "212121", "ECEFF1", "CFD8DC", "B0BEC5", "90A4AE", "78909C", "607D8B", - "546E7A", "455A64", "37474F", "263238", "000000", - }; - - public Header() - { - if (Properties.Settings.Default.UseChallengeBanner) - { - SKColor mainColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerPrimaryColor); - mainColor.ToHsl(out float h, out float s, out float l); - float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F; - - PrimaryColor = mainColor; - SecondaryColor = SKColor.Parse(Properties.Settings.Default.ChallengeBannerSecondaryColor); - AccentColor = SKColor.FromHsl(h += i, s, l); - DisplayImage = null; - if (!string.IsNullOrEmpty(Properties.Settings.Default.ChallengeBannerPath)) - CustomBackground = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.ChallengeBannerPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - else CustomBackground = null; - } - else - { - SKColor mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]); - mainColor.ToHsl(out float h, out float s, out float l); - while (l > 75 || l < 10) - { - mainColor = SKColor.Parse(_randomColors[_random.Next(0, 255)]); - mainColor.ToHsl(out float _, out float _, out l); - } - float i = l + 20.0F > 100.0F ? 100.0F - l : 20.0F; - - PrimaryColor = mainColor; - SecondaryColor = SKColor.FromHsl(h, s, l += i); - AccentColor = SKColor.FromHsl(h += i, s, l); - DisplayImage = null; - CustomBackground = null; - } - } - - public Header(StructProperty displayStyle, string assetFolder) : this() - { - if (displayStyle.Value is UObject o) - { - if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c1, "PrimaryColor") && c1 is StructProperty s1 && s1.Value is FLinearColor primaryColor) - PrimaryColor = SKColor.Parse(primaryColor.Hex); - if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue(out var c2, "SecondaryColor") && c2 is StructProperty s2 && s2.Value is FLinearColor secondaryColor) - SecondaryColor = SKColor.Parse(secondaryColor.Hex); - if (!Properties.Settings.Default.UseChallengeBanner && o.TryGetValue("AccentColor", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor accentColor) - { - AccentColor = SKColor.Parse(accentColor.Hex); - if (SecondaryColor.Red + SecondaryColor.Green + SecondaryColor.Blue <= 75 || assetFolder.Equals("LTM", StringComparison.CurrentCultureIgnoreCase)) // if secondary is too dark - SecondaryColor = AccentColor; // use accent and pray for accent to be ligher - } - - if (o.TryGetValue("DisplayImage", out var i) && i is SoftObjectProperty displayImage) - DisplayImage = Utils.GetSoftObjectTexture(displayImage); - if (CustomBackground == null && o.TryGetValue("CustomBackground", out var b) && b is SoftObjectProperty customBackground) - CustomBackground = Utils.GetSoftObjectTexture(customBackground); - } - } - } -} diff --git a/FModel/Creator/Bundles/HeaderStyle.cs b/FModel/Creator/Bundles/HeaderStyle.cs deleted file mode 100644 index 8d7c50f9..00000000 --- a/FModel/Creator/Bundles/HeaderStyle.cs +++ /dev/null @@ -1,139 +0,0 @@ -using FModel.Creator.Bases; -using FModel.Creator.Texts; -using SkiaSharp; -using SkiaSharp.HarfBuzz; -using System; -using System.Linq; - -namespace FModel.Creator.Bundles -{ - static class HeaderStyle - { - public static void DrawHeaderPaint(SKCanvas c, BaseBundle icon) - { - c.DrawRect(new SKRect(0, 0, icon.Width, icon.HeaderHeight), new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = icon.DisplayStyle.PrimaryColor - }); - - if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height != icon.DisplayStyle.CustomBackground.Width) - { - icon.IsDisplayNameShifted = false; - var bgPaint = new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen }; - if (Properties.Settings.Default.UseChallengeBanner) bgPaint.Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.ChallengeBannerOpacity); - c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, 1024, 256), bgPaint); - } - else if (icon.DisplayStyle.DisplayImage != null) - { - icon.IsDisplayNameShifted = true; - if (icon.DisplayStyle.CustomBackground != null && icon.DisplayStyle.CustomBackground.Height == icon.DisplayStyle.CustomBackground.Width) - c.DrawBitmap(icon.DisplayStyle.CustomBackground, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight), - new SKPaint { - IsAntialias = true, FilterQuality = SKFilterQuality.High, BlendMode = SKBlendMode.Screen, - ImageFilter = SKImageFilter.CreateDropShadow(2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(25)) - }); - - c.DrawBitmap(icon.DisplayStyle.DisplayImage, new SKRect(0, 0, icon.HeaderHeight, icon.HeaderHeight), - new SKPaint { - IsAntialias = true, FilterQuality = SKFilterQuality.High, - ImageFilter = SKImageFilter.CreateDropShadow(-2.5F, 0, 20, 0, icon.DisplayStyle.SecondaryColor.WithAlpha(50)) - }); - } - - SKPath pathTop = new SKPath { FillType = SKPathFillType.EvenOdd }; - pathTop.MoveTo(0, icon.HeaderHeight); - pathTop.LineTo(icon.Width, icon.HeaderHeight); - pathTop.LineTo(icon.Width, icon.HeaderHeight - 19); - pathTop.LineTo(icon.Width / 2 + 7, icon.HeaderHeight - 23); - pathTop.LineTo(icon.Width / 2 + 13, icon.HeaderHeight - 7); - pathTop.LineTo(0, icon.HeaderHeight - 19); - pathTop.Close(); - c.DrawPath(pathTop, new SKPaint { - IsAntialias = true, FilterQuality = SKFilterQuality.High, Color = icon.DisplayStyle.SecondaryColor, - ImageFilter = SKImageFilter.CreateDropShadow(-5, -5, 0, 0, icon.DisplayStyle.AccentColor.WithAlpha(75)) - }); - - c.DrawRect(new SKRect(0, icon.HeaderHeight, icon.Width, icon.HeaderHeight + icon.AdditionalSize), new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = icon.DisplayStyle.PrimaryColor.WithAlpha(200) // default background is black, so i'm kinda lowering the brightness here and that's what i want - }); - } - - public static void DrawHeaderText(SKCanvas c, BaseBundle icon) - { - using SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.BundleDisplayNameTypeface, - TextSize = 50, - Color = SKColors.White, - TextAlign = SKTextAlign.Left, - }; - - string text = icon.DisplayName.ToUpper(); - int x = icon.IsDisplayNameShifted ? 300 : 50; - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(paint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(text, paint); - shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; - - if (shapedTextWidth > (icon.Width - x)) - { - paint.TextSize -= 1; - } - else - { - break; - } - } - - if (char.IsDigit(text[text.Length - 1])) - { - int s = text.Count(k => Char.IsDigit(k)); - float numberwidth = paint.MeasureText(text.Substring(text.Length - s)); - c.DrawShapedText(shaper, text.Substring(text.Length - s), x, 155, paint); - - c.DrawShapedText(shaper, text.Substring(0, text.Length - s), x + numberwidth, 155, paint); - } - else - { - //feels bad man - c.DrawShapedText(shaper, text, x, 155, paint); - } - } - else - { - while (paint.MeasureText(text) > (icon.Width - x)) - { - paint.TextSize -= 2; - } - c.DrawText(text, x, 155, paint); - } - - paint.Color = SKColors.White.WithAlpha(150); - paint.TextAlign = SKTextAlign.Right; - paint.TextSize = 23; - paint.Typeface = Text.TypeFaces.DefaultTypeface; - c.DrawText(icon.Watermark - .Replace("{BundleName}", text) - .Replace("{Date}", DateTime.Now.ToString("dd/MM/yyyy")), - icon.Width - 25, icon.HeaderHeight - 40, paint); - - paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; - paint.Color = icon.DisplayStyle.SecondaryColor; - paint.TextAlign = SKTextAlign.Left; - paint.TextSize = 30; - c.DrawText(icon.FolderName.ToUpper(), x, 95, paint); - } - } -} diff --git a/FModel/Creator/Bundles/Quest.cs b/FModel/Creator/Bundles/Quest.cs deleted file mode 100644 index a64bad4b..00000000 --- a/FModel/Creator/Bundles/Quest.cs +++ /dev/null @@ -1,94 +0,0 @@ -using FModel.Creator.Texts; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bundles -{ - public class Quest - { - public string Description; - public int Count; - public Reward Reward; - - public Quest() - { - Description = ""; - Count = 0; - Reward = null; - } - - public Quest(UObject obj) : this() - { - if (obj.TryGetValue("Description", out var d) && d is TextProperty description) - Description = Text.GetTextPropertyBase(description); - if (obj.TryGetValue("ObjectiveCompletionCount", out var o) && o is IntProperty objectiveCompletionCount) - Count = objectiveCompletionCount.Value; - - if (obj.TryGetValue("Objectives", out var v1) && v1 is ArrayProperty a1 && - a1.Value.Length > 0 && a1.Value[0] is StructProperty s && s.Value is UObject objectives) - { - if (string.IsNullOrEmpty(Description) && objectives.TryGetValue("Description", out var od) && od is TextProperty objectivesDescription) - Description = Text.GetTextPropertyBase(objectivesDescription); - - if (Count == 0 && objectives.TryGetValue("Count", out var c) && c is IntProperty count) - Count = count.Value; - } - - if (obj.TryGetValue("Rewards", out var v2) && v2 is ArrayProperty rewards) - { - foreach (StructProperty reward in rewards.Value) - { - if (reward.Value is UObject r1 && - r1.TryGetValue("ItemPrimaryAssetId", out var i1) && i1 is StructProperty itemPrimaryAssetId && - r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity) - { - if (itemPrimaryAssetId.Value is UObject r2 && - r2.TryGetValue("PrimaryAssetType", out var t1) && t1 is StructProperty primaryAssetType && - r2.TryGetValue("PrimaryAssetName", out var t2) && t2 is NameProperty primaryAssetName) - { - if (primaryAssetType.Value is UObject r3 && r3.TryGetValue("Name", out var k) && k is NameProperty name) - { - if (!name.Value.String.Equals("Quest") && !name.Value.String.Equals("Token") && - !name.Value.String.Equals("ChallengeBundle") && !name.Value.String.Equals("GiftBox")) - { - Reward = new Reward(quantity, primaryAssetName); - break; - } - } - } - } - } - } - - if (Reward == null && obj.TryGetValue("RewardsTable", out var v4) && v4 is ObjectProperty rewardsTable && rewardsTable.Value.Resource.OuterIndex.Resource != null) - { - Package p = Utils.GetPropertyPakPackage(rewardsTable.Value.Resource.OuterIndex.Resource?.ObjectName.String); - if (p != null && p.HasExport() && !p.Equals(default)) - { - var u = p.GetExport(); - if (u != null && u.TryGetValue("Default", out var i) && i is UObject r && - r.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId && - r.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity) - { - Reward = new Reward(quantity, templateId); - } - } - } - - if (Reward == null && obj.TryGetValue("HiddenRewards", out var v3) && v3 is ArrayProperty hiddenRewards) - { - foreach (StructProperty reward in hiddenRewards.Value) - { - if (reward.Value is UObject r1 && - r1.TryGetValue("TemplateId", out var i1) && i1 is NameProperty templateId && - r1.TryGetValue("Quantity", out var i2) && i2 is IntProperty quantity) - { - Reward = new Reward(quantity, templateId); - break; - } - } - } - } - } -} diff --git a/FModel/Creator/Bundles/QuestStyle.cs b/FModel/Creator/Bundles/QuestStyle.cs deleted file mode 100644 index ebf4fb3b..00000000 --- a/FModel/Creator/Bundles/QuestStyle.cs +++ /dev/null @@ -1,241 +0,0 @@ -using FModel.Creator.Bases; -using FModel.Creator.Texts; -using SkiaSharp; -using SkiaSharp.HarfBuzz; -using System; -using System.Linq; - -namespace FModel.Creator.Bundles -{ - static class QuestStyle - { - public static void DrawQuests(SKCanvas c, BaseBundle icon) - { - using SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - TextSize = 27, - Color = SKColors.White, - TextAlign = SKTextAlign.Left, - Typeface = Text.TypeFaces.BundleDisplayNameTypeface - }; - - int y = icon.HeaderHeight + 50; - foreach (Quest q in icon.Quests) - { - DrawQuestBackground(c, icon, y, true); - - paint.TextSize = 27; - paint.ImageFilter = null; - paint.Color = SKColors.White; - paint.TextAlign = SKTextAlign.Left; - paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface; - if (!string.IsNullOrEmpty(q.Description)) - { - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(paint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(q.Description, paint); - shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; - - if (shapedTextWidth > icon.Width - 65 - 165) - { - paint.TextSize -= 1; - } - else - { - break; - } - } - if (char.IsDigit(q.Description[q.Description.Length - 1])) - { - int s = q.Description.Count(k => Char.IsDigit(k)); - c.DrawShapedText(shaper, q.Description.Substring(q.Description.Length - s), 65, y + paint.TextSize + 11, paint); - - c.DrawShapedText(shaper, q.Description.Substring(0, q.Description.Length - s), 115, y + paint.TextSize + 11, paint); - } - else - { - c.DrawShapedText(shaper, q.Description, 65, y + paint.TextSize + 11, paint); - - } - } - else - { - while (paint.MeasureText(q.Description) > icon.Width - 65 - 165) - { - paint.TextSize -= 1; - } - c.DrawText(q.Description, new SKPoint(65, y + paint.TextSize + 11), paint); - } - } - - paint.TextSize = 16; - paint.Color = SKColors.White.WithAlpha(200); - paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; - c.DrawText(q.Count.ToString(), new SKPoint(93 + icon.Width - (175 * 3), y + 60), paint); - - if (q.Reward?.RewardIcon != null) - { - if (q.Reward.IsCountShifted) - { - int l = q.Reward.RewardQuantity.ToString().Length; - paint.TextSize = l >= 5 ? 30 : 35; - paint.TextAlign = SKTextAlign.Right; - paint.Color = SKColor.Parse(q.Reward.RewardFillColor); - paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(q.Reward.RewardBorderColor).WithAlpha(200)); - c.DrawText(q.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint); - c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 30 - q.Reward.RewardIcon.Width, y + 12.5F), - new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); - } - else - c.DrawBitmap(q.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5), - new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); - } - - y += 95; - } - } - - public static void DrawCompletionRewards(SKCanvas c, BaseBundle icon) - { - using SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - TextSize = 35, - Color = SKColors.White, - TextAlign = SKTextAlign.Left, - Typeface = Text.TypeFaces.BundleDisplayNameTypeface - }; - - int y = icon.HeaderHeight + (50 * 2) + (95 * icon.Quests.Count); - foreach (CompletionReward r in icon.CompletionRewards) - { - DrawQuestBackground(c, icon, y, false); - - paint.TextSize = 35; - paint.ImageFilter = null; - paint.Color = SKColors.White; - paint.TextAlign = SKTextAlign.Left; - paint.Typeface = Text.TypeFaces.BundleDisplayNameTypeface; - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(paint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(r.CompletionText, paint); - shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; - - if (shapedTextWidth > icon.Width - 65 - 165) - { - paint.TextSize -= 1; - } - else - { - break; - } - } - - c.DrawShapedText(shaper, r.CompletionText, 65, y + paint.TextSize + 15, paint); - } - else - { - while (paint.MeasureText(r.CompletionText) > icon.Width - 65 - 165) - { - paint.TextSize -= 1; - } - c.DrawText(r.CompletionText, new SKPoint(65, y + paint.TextSize + 15), paint); - } - - if (r.Reward?.RewardIcon != null) - { - if (r.Reward.IsCountShifted) - { - int l = r.Reward.RewardQuantity.ToString().Length; - paint.TextSize = l >= 5 ? 30 : 35; - paint.TextAlign = SKTextAlign.Right; - paint.Color = SKColor.Parse(r.Reward.RewardFillColor); - paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; - paint.ImageFilter = SKImageFilter.CreateDropShadow(0, 0, 5, 5, SKColor.Parse(r.Reward.RewardBorderColor).WithAlpha(200)); - c.DrawText(r.Reward.RewardQuantity.ToString(), new SKPoint(icon.Width - 85, y + 47.5F), paint); - c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 30 - r.Reward.RewardIcon.Width, y + 12.5F), - new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); - } - else - c.DrawBitmap(r.Reward.RewardIcon, new SKPoint(icon.Width - 125, y + 5), - new SKPaint { IsAntialias = true, FilterQuality = SKFilterQuality.High }); - } - - y += 95; - } - } - - private static void DrawQuestBackground(SKCanvas c, BaseBundle icon, int y, bool hasSlider) - { - SKColor baseColor = icon.DisplayStyle.PrimaryColor; - using SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = baseColor - }; - using SKPath secondaryRect = new SKPath - { - FillType = SKPathFillType.EvenOdd - }; - using SKPath selector = new SKPath - { - FillType = SKPathFillType.EvenOdd - }; - using SKPath slider = new SKPath - { - FillType = SKPathFillType.EvenOdd - }; - - c.DrawRect(new SKRect(25, y, icon.Width - 25, y + 75), paint); - - baseColor.ToHsl(out float h, out float s, out float l); - baseColor = SKColor.FromHsl(h, s, l + 5); - paint.Color = baseColor; - - secondaryRect.MoveTo(32, y + 5); - secondaryRect.LineTo(icon.Width - 155, y + 4); - secondaryRect.LineTo(icon.Width - 175, y + 68); - secondaryRect.LineTo(29, y + 71); - secondaryRect.Close(); - c.DrawPath(secondaryRect, paint); - - paint.Color = icon.DisplayStyle.SecondaryColor; - selector.MoveTo(41, y + 38); - selector.LineTo(48, y + 34); - selector.LineTo(52, y + 39); - selector.LineTo(46, y + 44); - selector.Close(); - c.DrawPath(selector, paint); - - if (hasSlider) - { - slider.MoveTo(65, y + 53); - slider.LineTo(65 + icon.Width - (175 * 3), y + 53); - slider.LineTo(65 + icon.Width - (175 * 3), y + 58); - slider.LineTo(65, y + 58); - slider.Close(); - c.DrawPath(slider, paint); - - paint.TextSize = 14; - paint.Color = SKColors.White; - paint.TextAlign = SKTextAlign.Left; - paint.Typeface = Text.TypeFaces.BundleDefaultTypeface; - c.DrawText("0 / ", new SKPoint(75 + icon.Width - (175 * 3), y + 59), paint); - } - } - } -} diff --git a/FModel/Creator/Bundles/Reward.cs b/FModel/Creator/Bundles/Reward.cs deleted file mode 100644 index 2218a190..00000000 --- a/FModel/Creator/Bundles/Reward.cs +++ /dev/null @@ -1,155 +0,0 @@ -using FModel.Creator.Bases; -using SkiaSharp; -using System; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Bundles -{ - public class Reward - { - public int RewardQuantity; - public SKBitmap RewardIcon; - public BaseIcon TheReward; - public string RewardFillColor; - public string RewardBorderColor; - public bool IsCountShifted; - - public Reward() - { - RewardQuantity = 0; - RewardIcon = null; - TheReward = null; - RewardFillColor = ""; - RewardBorderColor = ""; - IsCountShifted = false; - } - - public Reward(IntProperty quantity, NameProperty primaryAssetName) : this(quantity, primaryAssetName.Value.String) { } - public Reward(IntProperty quantity, string assetName) : this() - { - RewardQuantity = quantity.Value; - - if (assetName.Contains(':')) - { - string[] parts = assetName.Split(':'); - if (parts[0].Equals("HomebaseBannerIcon", StringComparison.CurrentCultureIgnoreCase)) - { - Package p = Utils.GetPropertyPakPackage($"/Game/Items/BannerIcons/{parts[1]}.{parts[1]}"); - if (p.HasExport() && !p.Equals(default)) - { - if (p.GetExport() is UObject banner) - { - TheReward = new BaseIcon(banner, $"{parts[1]}.uasset", false); - RewardIcon = TheReward.IconImage.Resize(64, 64); - } - } - } - else GetReward(parts[1]); - } - else GetReward(assetName); - } - public Reward(IntProperty quantity, FSoftObjectPath itemFullPath) - { - RewardQuantity = quantity.Value; - Package p = Utils.GetPropertyPakPackage(itemFullPath.AssetPathName.String); - if (p.HasExport() && !p.Equals(default)) - { - var d = p.GetExport(); - if (d != null) - { - int i = itemFullPath.AssetPathName.String.LastIndexOf('/'); - TheReward = new BaseIcon(d, itemFullPath.AssetPathName.String[(i > 0 ? i : 0)..] + ".uasset", false); - RewardIcon = TheReward.IconImage.Resize(80, 80); - } - } - } - - public Reward(IntProperty quantity, SoftObjectProperty itemDefinition) : this() - { - RewardQuantity = quantity.Value; - - Package p = Utils.GetPropertyPakPackage(itemDefinition.Value.AssetPathName.String); - if (p != null && p.HasExport() && !p.Equals(default)) - { - var d = p.GetExport(); - if (d != null) - { - int s1 = itemDefinition.Value.AssetPathName.String.LastIndexOf('/'); - if (s1 < 0) s1 = 0; - int s2 = itemDefinition.Value.AssetPathName.String.LastIndexOf('.') - s1; - switch (itemDefinition.Value.AssetPathName.String) - { - case "/Game/Items/PersistentResources/AthenaBattleStar.AthenaBattleStar": - IsCountShifted = true; - RewardFillColor = "FFDB67"; - RewardBorderColor = "8F4A20"; - RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48); - break; - case "/Game/Items/PersistentResources/AthenaSeasonalXP.AthenaSeasonalXP": - IsCountShifted = true; - RewardFillColor = "E6FDB1"; - RewardBorderColor = "51830F"; - RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48); - break; - case "/Game/Items/Currency/MtxGiveaway.MtxGiveaway": - IsCountShifted = true; - RewardFillColor = "DCE6FF"; - RewardBorderColor = "64A0AF"; - RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48); - break; - default: - IsCountShifted = false; - TheReward = new BaseIcon(d, itemDefinition.Value.AssetPathName.String.Substring(s1, s2) + ".uasset", false); - RewardIcon = TheReward.IconImage.Resize(64, 64); - break; - } - } - } - } - - private void GetReward(string trigger) - { - switch (trigger.ToLower()) - { - case "athenabattlestar": - IsCountShifted = true; - RewardFillColor = "FFDB67"; - RewardBorderColor = "8F4A20"; - RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-BattlePoints").Resize(48, 48); - break; - case "athenaseasonalxp": - IsCountShifted = true; - RewardFillColor = "E6FDB1"; - RewardBorderColor = "51830F"; - RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-FNBR-XPMedium").Resize(48, 48); - break; - case "mtxgiveaway": - IsCountShifted = true; - RewardFillColor = "DCE6FF"; - RewardBorderColor = "64A0AF"; - RewardIcon = Utils.GetTexture("/Game/UI/Foundation/Textures/Icons/Items/T-Items-MTX").Resize(48, 48); - break; - default: - { - string path = Utils.GetFullPath($"/FortniteGame/Content/Athena/.*?/{trigger}.*").Replace("FortniteGame/Content", "Game"); - Package p = Utils.GetPropertyPakPackage(path); - if (p!= null && p.HasExport() && !p.Equals(default)) - { - var d = p.GetExport(); - if (d != null) - { - int i = path.LastIndexOf('/'); - IsCountShifted = false; - TheReward = new BaseIcon(d, path[(i > 0 ? i : 0)..] + ".uasset", false); - RewardIcon = TheReward.IconImage.Resize(64, 64); - } - } - break; - } - } - } - } -} diff --git a/FModel/Creator/Creator.cs b/FModel/Creator/Creator.cs deleted file mode 100644 index e79ff8fc..00000000 --- a/FModel/Creator/Creator.cs +++ /dev/null @@ -1,496 +0,0 @@ -using FModel.Creator.Bases; -using FModel.Creator.Bundles; -using FModel.Creator.Icons; -using FModel.Creator.Rarities; -using FModel.Creator.Stats; -using FModel.Creator.Texts; -using FModel.ViewModels.ImageBox; -using SkiaSharp; -using System.IO; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; - -namespace FModel.Creator -{ - static class Creator - { - /// - /// i don't cache images because i don't wanna store a lot of SKCanvas in the memory - /// - /// true if an icon has been drawn - public static bool TryDrawIcon(string assetPath, FName[] exportTypes, IUExport[] exports) - { - var d = new DirectoryInfo(assetPath); - string assetName = d.Name; - string assetFolder = d.Parent.Name; - if (Text.TypeFaces.NeedReload(false)) - Text.TypeFaces = new Typefaces(); // when opening bundle creator settings without loading paks first - - int index; - { - if (Globals.Game.ActualGame == EGame.Valorant || Globals.Game.ActualGame == EGame.Spellbreak) - index = 1; - else - index = 0; - } - string exportType; - { - if (exportTypes.Length > index && (exportTypes[index].String == "BlueprintGeneratedClass" || exportTypes[index].String == "FortWeaponAdditionalData_AudioVisualizerData" || exportTypes[index].String == "FortWeaponAdditionalData_SingleWieldState")) - index++; - - exportType = exportTypes.Length > index ? exportTypes[index].String : string.Empty; - } - - switch (exportType) - { - case "AthenaConsumableEmoteItemDefinition": - case "AthenaSkyDiveContrailItemDefinition": - case "AthenaLoadingScreenItemDefinition": - case "AthenaVictoryPoseItemDefinition": - case "AthenaPetCarrierItemDefinition": - case "AthenaMusicPackItemDefinition": - case "AthenaBattleBusItemDefinition": - case "AthenaCharacterItemDefinition": - case "FortAlterationItemDefinition": - case "AthenaBackpackItemDefinition": - case "AthenaPickaxeItemDefinition": - case "AthenaGadgetItemDefinition": - case "AthenaGliderItemDefinition": - case "AthenaDailyQuestDefinition": - case "FortBackpackItemDefinition": - case "AthenaSprayItemDefinition": - case "AthenaDanceItemDefinition": - case "AthenaEmojiItemDefinition": - case "AthenaItemWrapDefinition": - case "AthenaToyItemDefinition": - case "FortHeroType": - case "FortTokenType": - case "FortAbilityKit": - case "FortWorkerType": - case "RewardGraphToken": - case "FortBannerTokenType": - case "FortVariantTokenType": - case "FortDecoItemDefinition": - case "FortFeatItemDefinition": - case "FortStatItemDefinition": - case "FortTrapItemDefinition": - case "FortAmmoItemDefinition": - case "FortTandemCharacterData": - case "FortQuestItemDefinition": - case "FortBadgeItemDefinition": - case "FortAwardItemDefinition": - case "FortGadgetItemDefinition": - case "FortPlaysetItemDefinition": - case "FortGiftBoxItemDefinition": - case "FortSpyTechItemDefinition": - case "FortOutpostItemDefinition": - case "FortAccoladeItemDefinition": - case "FortCardPackItemDefinition": - case "FortDefenderItemDefinition": - case "FortCurrencyItemDefinition": - case "FortResourceItemDefinition": - case "FortCodeTokenItemDefinition": - case "FortSchematicItemDefinition": - case "FortExpeditionItemDefinition": - case "FortIngredientItemDefinition": - case "FortAccountBuffItemDefinition": - case "FortWeaponMeleeItemDefinition": - case "FortContextTrapItemDefinition": - case "FortPlayerPerksItemDefinition": - case "FortPlaysetPropItemDefinition": - case "FortHomebaseNodeItemDefinition": - case "FortWeaponRangedItemDefinition": - case "FortNeverPersistItemDefinition": - case "RadioContentSourceItemDefinition": - case "FortPlaysetGrenadeItemDefinition": - case "FortPersonalVehicleItemDefinition": - case "FortGameplayModifierItemDefinition": - case "FortHardcoreModifierItemDefinition": - case "FortConsumableAccountItemDefinition": - case "FortConversionControlItemDefinition": - case "FortAccountBuffCreditItemDefinition": - case "FortEventCurrencyItemDefinitionRedir": - case "FortPersistentResourceItemDefinition": - case "FortHomebaseBannerIconItemDefinition": - case "FortCampaignHeroLoadoutItemDefinition": - case "FortConditionalResourceItemDefinition": - case "FortChallengeBundleScheduleDefinition": - case "FortWeaponMeleeDualWieldItemDefinition": - case "FortDailyRewardScheduleTokenDefinition": - case "FortCreativeRealEstatePlotItemDefinition": - { - BaseIcon icon = new BaseIcon(exports[index], exportType, ref assetName); - int height = icon.Size + icon.AdditionalSize; - using (var ret = new SKBitmap(icon.Size, height, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - Rarity.DrawRarity(c, icon); - } - - LargeSmallImage.DrawPreviewImage(c, icon); - - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText) - { - Text.DrawBackground(c, icon); - Text.DrawDisplayName(c, icon); - Text.DrawDescription(c, icon); - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.Mini) - { - if (!icon.ShortDescription.Equals(icon.DisplayName) && - !icon.ShortDescription.Equals(icon.Description)) - Text.DrawToBottom(c, icon, ETextSide.Left, icon.ShortDescription); - Text.DrawToBottom(c, icon, ETextSide.Right, icon.CosmeticSource); - } - } - - UserFacingFlag.DrawUserFacingFlags(c, icon); - - // has more things to show - if (height > icon.Size) - { - Statistics.DrawStats(c, icon); - } - } - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "FortPlaylistAthena": - { - BasePlaylist icon = new BasePlaylist(exports[index]); - using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - Rarity.DrawRarity(c, icon); - } - - LargeSmallImage.DrawNotStretchedPreviewImage(c, icon); - - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText) - { - Text.DrawBackground(c, icon); - Text.DrawDisplayName(c, icon); - Text.DrawDescription(c, icon); - } - } - - // Watermark.DrawWatermark(c); // boi why would you watermark something you don't own ¯\_(ツ)_/¯ - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "AthenaSeasonItemDefinition": - { - BaseSeason icon = new BaseSeason(exports[index], assetFolder); - using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize, - SKColorType.Rgba8888, SKAlphaType.Opaque)) - using (var c = new SKCanvas(ret)) - { - icon.Draw(c); - - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "FortMtxOfferData": - { - BaseOffer icon = new BaseOffer(exports[index]); - using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - icon.DrawBackground(c); - } - - icon.DrawImage(c); - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "MaterialInstanceConstant": - { - if (assetFolder.Equals("MI_OfferImages") || assetFolder.Equals("RenderSwitch_Materials")) - { - BaseOfferMaterial icon = new BaseOfferMaterial(exports[index]); - using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - icon.DrawBackground(c); - } - - icon.DrawImage(c); - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - - return false; - } - case "FortItemSeriesDefinition": - { - BaseIcon icon = new BaseIcon(); - using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque)) - using (var c = new SKCanvas(ret)) - { - Serie.GetRarity(icon, exports[index]); - Rarity.DrawRarity(c, icon); - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "PlaylistUserOptionEnum": - case "PlaylistUserOptionBool": - case "PlaylistUserOptionString": - case "PlaylistUserOptionIntEnum": - case "PlaylistUserOptionIntRange": - case "PlaylistUserOptionColorEnum": - case "PlaylistUserOptionFloatEnum": - case "PlaylistUserOptionFloatRange": - case "PlaylistUserOptionPrimaryAsset": - case "PlaylistUserOptionCollisionProfileEnum": - { - BaseUserOption icon = new BaseUserOption(exports[index]); - using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Opaque)) - using (var c = new SKCanvas(ret)) - { - icon.Draw(c); - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "FortChallengeBundleItemDefinition": - { - BaseBundle icon = new BaseBundle(exports[index], assetFolder); - using (var ret = new SKBitmap(icon.Width, icon.HeaderHeight + icon.AdditionalSize, - SKColorType.Rgba8888, SKAlphaType.Opaque)) - using (var c = new SKCanvas(ret)) - { - HeaderStyle.DrawHeaderPaint(c, icon); - HeaderStyle.DrawHeaderText(c, icon); - QuestStyle.DrawQuests(c, icon); - QuestStyle.DrawCompletionRewards(c, icon); - - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "FortItemAccessTokenType": - { - BaseItemAccess icon = new BaseItemAccess(exports[index]); - using (var ret = new SKBitmap(icon.Size, icon.Size, SKColorType.Rgba8888, SKAlphaType.Opaque)) - using (var c = new SKCanvas(ret)) - { - icon.Draw(c); - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "MapUIData": - { - BaseMapUIData icon = new BaseMapUIData(exports[index]); - using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - icon.Draw(c); - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "ArmorUIData": - case "SprayUIData": - case "ThemeUIData": - case "ContractUIData": - case "CurrencyUIData": - case "GameModeUIData": - case "CharacterUIData": - case "SprayLevelUIData": - case "EquippableUIData": - case "PlayerCardUIData": - case "Gun_UIData_Base_C": - case "CharacterRoleUIData": - case "EquippableSkinUIData": - case "EquippableCharmUIData": - case "EquippableSkinLevelUIData": - case "EquippableSkinChromaUIData": - case "EquippableCharmLevelUIData": - { - BaseUIData icon = new BaseUIData(exports, index); - using (var ret = new SKBitmap(icon.Width + icon.AdditionalWidth, icon.Height, SKColorType.Rgba8888, - SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - icon.Draw(c); - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - //case "StreamedVideoDataAsset": // must find a way to automatically gets the right version in the url - // { - // if (Globals.Game.ActualGame == EGame.Valorant && exports[index].GetExport("Uuid") is StructProperty s && s.Value is FGuid uuid) - // { - // Process.Start(new ProcessStartInfo - // { - // FileName = string.Format( - // "http://valorant.dyn.riotcdn.net/x/videos/release-01.05/{0}_default_universal.mp4", - // $"{uuid.A:x8}-{uuid.B >> 16:x4}-{uuid.B & 0xFFFF:x4}-{uuid.C >> 16:x4}-{uuid.C & 0xFFFF:x4}{uuid.D:x8}"), - // UseShellExecute = true - // }); - // } - // return false; - // } - case "GQuest": - case "GAccolade": - case "GCosmeticSkin": - case "GCharacterPerk": - case "GCosmeticTitle": - case "GCosmeticBadge": - case "GCosmeticEmote": - case "GCosmeticTriumph": - case "GCosmeticRunTrail": - case "GCosmeticArtifact": - case "GCosmeticDropTrail": - { - BaseGCosmetic icon = new BaseGCosmetic(exports[index], exportType); - using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat) - { - icon.Draw(c); - } - else if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - Rarity.DrawRarity(c, icon); - } - - LargeSmallImage.DrawPreviewImage(c, icon); - - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground && - (EIconDesign)Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText) - { - Text.DrawBackground(c, icon); - Text.DrawDisplayName(c, icon); - Text.DrawDescription(c, icon); - } - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - case "GCosmeticCard": - { - BaseGCosmetic icon = new BaseGCosmetic(exports[index], exportType); - using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat) - { - icon.Draw(c); - } - else - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - Rarity.DrawRarity(c, icon); - } - } - - LargeSmallImage.DrawPreviewImage(c, icon); - - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText) - { - Text.DrawBackground(c, icon); - Text.DrawDisplayName(c, icon); - Text.DrawDescription(c, icon); - } - } - - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - // Battle Breakers - case "WExpGenericAccountItemDefinition": - { - BaseBBDefinition icon = new BaseBBDefinition(exports[index], exportType); - using (var ret = new SKBitmap(icon.Width, icon.Height, SKColorType.Rgba8888, SKAlphaType.Premul)) - using (var c = new SKCanvas(ret)) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - if (icon.RarityBackgroundImage != null) - { - c.DrawBitmap(icon.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - } - else - { - Rarity.DrawRarity(c, icon); - } - } - - LargeSmallImage.DrawPreviewImage(c, icon); - - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoBackground) - { - if ((EIconDesign) Properties.Settings.Default.AssetsIconDesign != EIconDesign.NoText) - { - Text.DrawBackground(c, icon); - Text.DrawDisplayName(c, icon); - Text.DrawDescription(c, icon); - } - } - - Watermark.DrawWatermark(c); // watermark should only be applied on icons with width = 512 - ImageBoxVm.imageBoxViewModel.Set(ret, assetName); - } - - return true; - } - } - - return false; - } - } -} diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs new file mode 100644 index 00000000..8a9c50cd --- /dev/null +++ b/FModel/Creator/CreatorPackage.cs @@ -0,0 +1,202 @@ +using System; +using System.Runtime.CompilerServices; +using CUE4Parse.UE4.Assets.Exports; +using FModel.Creator.Bases; +using FModel.Creator.Bases.BB; +using FModel.Creator.Bases.FN; +using FModel.Creator.Bases.SB; + +namespace FModel.Creator +{ + public class CreatorPackage : IDisposable + { + private UObject _object; + private EIconStyle _style; + + public CreatorPackage(UObject uObject, EIconStyle style) + { + _object = uObject; + _style = style; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UCreator ConstructCreator() + { + TryConstructCreator(out var creator); + return creator; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryConstructCreator(out UCreator creator) + { + switch (_object.ExportType) + { + // Fortnite + case "AthenaConsumableEmoteItemDefinition": + case "AthenaSkyDiveContrailItemDefinition": + case "AthenaLoadingScreenItemDefinition": + case "AthenaVictoryPoseItemDefinition": + case "AthenaPetCarrierItemDefinition": + case "AthenaMusicPackItemDefinition": + case "AthenaBattleBusItemDefinition": + case "AthenaCharacterItemDefinition": + case "AthenaMapMarkerItemDefinition": + case "AthenaBackpackItemDefinition": + case "AthenaPickaxeItemDefinition": + case "AthenaGadgetItemDefinition": + case "AthenaGliderItemDefinition": + case "AthenaSprayItemDefinition": + case "AthenaDanceItemDefinition": + case "AthenaEmojiItemDefinition": + case "AthenaItemWrapDefinition": + case "AthenaToyItemDefinition": + case "FortHeroType": + case "FortTokenType": + case "FortAbilityKit": + case "FortWorkerType": + case "RewardGraphToken": + case "FortBannerTokenType": + case "FortVariantTokenType": + case "FortDecoItemDefinition": + case "FortStatItemDefinition": + case "FortAmmoItemDefinition": + case "FortEmoteItemDefinition": + case "FortBadgeItemDefinition": + case "FortAwardItemDefinition": + case "FortGadgetItemDefinition": + case "FortPlaysetItemDefinition": + case "FortGiftBoxItemDefinition": + case "FortOutpostItemDefinition": + case "FortVehicleItemDefinition": + case "FortCardPackItemDefinition": + case "FortDefenderItemDefinition": + case "FortCurrencyItemDefinition": + case "FortResourceItemDefinition": + case "FortBackpackItemDefinition": + case "FortCodeTokenItemDefinition": + case "FortSchematicItemDefinition": + case "FortWorldMultiItemDefinition": + case "FortAlterationItemDefinition": + case "FortExpeditionItemDefinition": + case "FortIngredientItemDefinition": + case "FortAccountBuffItemDefinition": + case "FortWeaponMeleeItemDefinition": + case "FortPlayerPerksItemDefinition": + case "FortPlaysetPropItemDefinition": + case "FortHomebaseNodeItemDefinition": + case "FortNeverPersistItemDefinition": + case "RadioContentSourceItemDefinition": + case "FortPlaysetGrenadeItemDefinition": + case "FortPersonalVehicleItemDefinition": + case "FortGameplayModifierItemDefinition": + case "FortHardcoreModifierItemDefinition": + case "FortConsumableAccountItemDefinition": + case "FortConversionControlItemDefinition": + case "FortAccountBuffCreditItemDefinition": + case "FortEventCurrencyItemDefinitionRedir": + case "FortPersistentResourceItemDefinition": + case "FortHomebaseBannerIconItemDefinition": + case "FortCampaignHeroLoadoutItemDefinition": + case "FortConditionalResourceItemDefinition": + case "FortChallengeBundleScheduleDefinition": + case "FortWeaponMeleeDualWieldItemDefinition": + case "FortDailyRewardScheduleTokenDefinition": + case "FortCreativeRealEstatePlotItemDefinition": + case "AthenaDanceItemDefinition_AdHocSquadsJoin_C": + creator = _style switch + { + EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"), + _ => new BaseIcon(_object, _style) + }; + return true; + case "FortTrapItemDefinition": + case "FortTandemCharacterData": + case "FortSpyTechItemDefinition": + case "FortAccoladeItemDefinition": + case "FortContextTrapItemDefinition": + case "FortWeaponRangedItemDefinition": + case "Daybreak_LevelExitVehicle_PartItemDefinition_C": + creator = new BaseIconStats(_object, _style); + return true; + case "FortItemSeriesDefinition": + creator = new BaseSeries(_object, _style); + return true; + case "MaterialInstanceConstant" + when _object.Owner != null && + (_object.Owner.Name.EndsWith($"/MI_OfferImages/{_object.Name}") || + _object.Owner.Name.EndsWith($"/RenderSwitch_Materials/{_object.Name}")): + creator = new BaseMaterialInstance(_object, _style); + return true; + case "FortMtxOfferData": + creator = new BaseMtxOffer(_object, _style); + return true; + case "FortPlaylistAthena": + creator = new BasePlaylist(_object, _style); + return true; + case "FortFeatItemDefinition": + case "FortQuestItemDefinition": + case "AthenaDailyQuestDefinition": + case "FortUrgentQuestItemDefinition": + creator = new BaseQuest(_object, _style); + return true; + case "FortCompendiumItemDefinition": + case "FortChallengeBundleItemDefinition": + creator = new BaseBundle(_object, _style); + return true; + case "AthenaSeasonItemDefinition": + creator = new BaseSeason(_object, _style); + return true; + case "FortItemAccessTokenType": + creator = new BaseItemAccessToken(_object, _style); + return true; + case "PlaylistUserOptionEnum": + case "PlaylistUserOptionBool": + case "PlaylistUserOptionString": + case "PlaylistUserOptionIntEnum": + case "PlaylistUserOptionIntRange": + case "PlaylistUserOptionColorEnum": + case "PlaylistUserOptionFloatEnum": + case "PlaylistUserOptionFloatRange": + case "PlaylistUserOptionPrimaryAsset": + case "PlaylistUserOptionCollisionProfileEnum": + creator = new BaseUserControl(_object, _style); + return true; + // Battle Breakers + case "WExpGenericAccountItemDefinition": + creator = new BaseBreakersIcon(_object, EIconStyle.Default); + return true; + // Spellbreak + case "GQuest": + case "GAccolade": + case "GCosmeticCard": + case "GCosmeticSkin": + case "GCharacterPerk": + case "GCosmeticTitle": + case "GCosmeticBadge": + case "GCosmeticEmote": + case "GCosmeticTriumph": + case "GCosmeticRunTrail": + case "GCosmeticArtifact": + case "GCosmeticDropTrail": + 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; + } + } + + public override string ToString() => $"{_object.ExportType} | {_style}"; + + public void Dispose() + { + _object = null; + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Icons/DisplayAssetImage.cs b/FModel/Creator/Icons/DisplayAssetImage.cs deleted file mode 100644 index 5aa2dc1b..00000000 --- a/FModel/Creator/Icons/DisplayAssetImage.cs +++ /dev/null @@ -1,104 +0,0 @@ -using FModel.Creator.Bases; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Icons -{ - static class DisplayAssetImage - { - public static bool GetDisplayAssetImage(BaseIcon icon, IUExport o, ref string assetName) - { - string path = string.Empty; - bool displayExists = true; - if (o.TryGetValue("DisplayAssetPath", out var d) && d is StructProperty da && da.Value is FSoftObjectPath daOut) - path = daOut.AssetPathName.String; - else - { - displayExists = false; - path = "/Game/Catalog/MI_OfferImages/MI_" + assetName.Substring(0, assetName.LastIndexOf(".")).Replace("Athena_Commando_", ""); - } - - Package p = Utils.GetPropertyPakPackage(path); - if (!displayExists && p == default) - p = Utils.GetPropertyPakPackage(path[..path.LastIndexOf("_")]); - - if (p != null && p.HasExport()) - { - var obj = p.GetExport(); - if (obj != null) - { - if (obj.GetExport("TextureParameterValues") is ArrayProperty textureParameterValues) - { - foreach (StructProperty textureParameter in textureParameterValues.Value) - { - if (textureParameter.Value is UObject parameter && - parameter.TryGetValue("ParameterValue", out var i) && i is ObjectProperty value && - parameter.TryGetValue("ParameterInfo", out var i1) && i1 is StructProperty i2 && i2.Value is UObject info && - info.TryGetValue("Name", out var j1) && j1 is NameProperty name) - { - if (name.Value.String.Equals("OfferImage") || name.Value.String.Equals("Texture")) - { - icon.IconImage = Utils.GetObjectTexture(value) ?? Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_1") ?? Utils.GetTexture($"{value.Value.Resource.OuterIndex.Resource.ObjectName.String}_01"); - assetName = "MI_" + assetName; - return true; - } - } - } - } - return GenerateOldFormat(icon, obj, ref assetName); - } - } - - return GenerateOldFormat(icon, ref assetName); - } - - private static bool GenerateOldFormat(BaseIcon icon, ref string assetName) - { - var p = Utils.GetPropertyPakPackage("/Game/Catalog/DisplayAssets/DA_Featured_" + assetName.Substring(0, assetName.LastIndexOf("."))); - if (p != null && p.HasExport()) - { - var obj = p.GetExport(); - if (obj != null) - { - return GenerateOldFormat(icon, obj, ref assetName); - } - } - return false; - } - private static bool GenerateOldFormat(BaseIcon icon, UObject obj, ref string assetName) - { - string imageType = "DetailsImage"; - switch ("DA_Featured_" + assetName) - { - case "DA_Featured_Glider_ID_141_AshtonBoardwalk.uasset": - case "DA_Featured_Glider_ID_150_TechOpsBlue.uasset": - case "DA_Featured_Glider_ID_131_SpeedyMidnight.uasset": - case "DA_Featured_Pickaxe_ID_178_SpeedyMidnight.uasset": - case "DA_Featured_Glider_ID_015_Brite.uasset": - case "DA_Featured_Glider_ID_016_Tactical.uasset": - case "DA_Featured_Glider_ID_017_Assassin.uasset": - case "DA_Featured_Pickaxe_ID_027_Scavenger.uasset": - case "DA_Featured_Pickaxe_ID_028_Space.uasset": - case "DA_Featured_Pickaxe_ID_029_Assassin.uasset": - return false; - case "DA_Featured_Glider_ID_070_DarkViking.uasset": - case "DA_Featured_CID_319_Athena_Commando_F_Nautilus.uasset": - imageType = "TileImage"; - break; - } - - if (obj.TryGetValue(imageType, out var v1) && v1 is StructProperty s && s.Value is UObject type && - type.TryGetValue("ResourceObject", out var v2) && v2 is ObjectProperty resourceObject && - resourceObject.Value.Resource.OuterIndex.Resource != null && - !resourceObject.Value.Resource.OuterIndex.Resource.ObjectName.String.Contains("/Game/Athena/Prototype/Textures/")) - { - icon.IconImage = Utils.GetObjectTexture(resourceObject); - assetName = "DA_Featured_" + assetName; - return true; - } - return false; - } - } -} diff --git a/FModel/Creator/Icons/LargeSmallImage.cs b/FModel/Creator/Icons/LargeSmallImage.cs deleted file mode 100644 index aff5a550..00000000 --- a/FModel/Creator/Icons/LargeSmallImage.cs +++ /dev/null @@ -1,69 +0,0 @@ -using FModel.Creator.Bases; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; -using SkiaSharp; - -namespace FModel.Creator.Icons -{ - static class LargeSmallImage - { - public static void GetPreviewImage(BaseIcon icon, StructProperty u) - { - if (u.Value is UObject o && o.TryGetValue("ResourceObject", out var v) && v is ObjectProperty resourceObject) - icon.IconImage = Utils.GetObjectTexture(resourceObject); - } - public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName) => GetPreviewImage(icon, o, assetName, true); - public static void GetPreviewImage(BaseIcon icon, ObjectProperty o, string assetName, bool hightRes) - { - string path = o.Value.Resource?.OuterIndex.Resource?.ObjectName.String; - if (path?.Equals("/Game/Athena/Items/Weapons/WID_Harvest_Pickaxe_STWCosmetic_Tier") == true) - path += "_" + assetName.Substring(assetName.LastIndexOf(".") - 1, 1); - - Package p = Utils.GetPropertyPakPackage(path); - if (p != null && p.HasExport() && !p.Equals(default)) - { - if (GetPreviewImage(icon, p.GetIndexedExport(0), hightRes)) - return; - else if (GetPreviewImage(icon, p.GetIndexedExport(1), hightRes)) // FortniteGame/Content/Athena/Items/Cosmetics/Pickaxes/Pickaxe_ID_402_BlackKnightFemale1H.uasset - return; - } - } - public static void GetPreviewImage(BaseIcon icon, SoftObjectProperty s) => icon.IconImage = Utils.GetSoftObjectTexture(s); - - private static bool GetPreviewImage(BaseIcon icon, UObject obj, bool hightRes) - { - if (obj != null) - { - if (hightRes && obj.TryGetValue("LargePreviewImage", out var sLarge) && sLarge is SoftObjectProperty largePreviewImage && !string.IsNullOrEmpty(largePreviewImage.Value.AssetPathName.String)) - { - GetPreviewImage(icon, largePreviewImage); - return true; - } - else if (obj.TryGetValue("SmallPreviewImage", out var sSmall1) && sSmall1 is SoftObjectProperty smallPreviewImage1 && !string.IsNullOrEmpty(smallPreviewImage1.Value.AssetPathName.String)) - { - GetPreviewImage(icon, smallPreviewImage1); - return true; - } - else if (obj.TryGetValue("SmallPreviewImage", out var sSmall2) && sSmall2 is ObjectProperty smallPreviewImage2 && !string.IsNullOrEmpty(smallPreviewImage2.Value.Resource.OuterIndex.Resource.ObjectName.String)) - { - icon.IconImage = Utils.GetObjectTexture(smallPreviewImage2); - return true; - } - } - return false; - } - - public static void DrawPreviewImage(SKCanvas c, IBase icon) => - c.DrawBitmap(icon.IconImage ?? icon.FallbackImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - public static void DrawNotStretchedPreviewImage(SKCanvas c, IBase icon) - { - SKBitmap i = icon.IconImage ?? icon.FallbackImage; - int x = i.Width < icon.Width ? ((icon.Width / 2) - (i.Width / 2)) : icon.Margin; - c.DrawBitmap(i, new SKRect(x, icon.Margin, (x + i.Width) - (icon.Margin * 2), i.Height - icon.Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - } - } -} diff --git a/FModel/Creator/Icons/UserFacingFlag.cs b/FModel/Creator/Icons/UserFacingFlag.cs deleted file mode 100644 index 08826700..00000000 --- a/FModel/Creator/Icons/UserFacingFlag.cs +++ /dev/null @@ -1,73 +0,0 @@ -using FModel.Creator.Bases; -using SkiaSharp; -using System; -using System.Collections.Generic; -using System.Windows; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Icons -{ - static class UserFacingFlag - { - public static void GetUserFacingFlags(List uffs, BaseIcon icon, string exportType) - { - if (uffs.Count > 0) - { - Package p = Utils.GetPropertyPakPackage("/Game/Items/ItemCategories"); //PrimaryCategories - SecondaryCategories - TertiaryCategories - if (p.HasExport() && !p.Equals(default)) - { - var o = p.GetExport(); - if (o != null && o.TryGetValue("TertiaryCategories", out var tertiaryCategories) && tertiaryCategories is ArrayProperty tertiaryArray) - { - icon.UserFacingFlags = new SKBitmap[uffs.Count]; - for (int i = 0; i < uffs.Count; i++) - { - if (uffs[i].Equals("Cosmetics.UserFacingFlags.HasUpgradeQuests")) - { - if (exportType.Equals("AthenaPetCarrierItemDefinition")) - icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Pets-64.png")).Stream); - else - icon.UserFacingFlags[i] = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T-Icon-Quests-64.png")).Stream); - } - else - { - foreach (StructProperty structProp in tertiaryArray.Value) - { - if (structProp.Value is UObject mainUObject && - mainUObject.TryGetValue("TagContainer", out var struc1) && struc1 is StructProperty tagContainer && tagContainer.Value is FGameplayTagContainer f && f.GameplayTags.TryGetGameplayTag(uffs[i], out var _) && - mainUObject.TryGetValue("CategoryBrush", out var struc2) && struc2 is StructProperty categoryBrush && categoryBrush.Value is UObject categoryUObject && - categoryUObject.TryGetValue("Brush_XXS", out var struc3) && struc3 is StructProperty brushXXS && brushXXS.Value is UObject brushUObject && - brushUObject.TryGetValue("ResourceObject", out var object1) && object1 is ObjectProperty resourceObject) - { - icon.UserFacingFlags[i] = Utils.GetObjectTexture(resourceObject); - break; - } - } - } - } - } - } - } - } - - public static void DrawUserFacingFlags(SKCanvas c, BaseIcon icon) - { - if (icon.UserFacingFlags != null) - { - int size = 25; - int x = icon.Margin * (int)2.5; - foreach (SKBitmap b in icon.UserFacingFlags) - { - if (b == null) - continue; - - c.DrawBitmap(b.Resize(size, size), new SKPoint(x, icon.Margin * (int)2.5), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - x += size; - } - } - } - } -} diff --git a/FModel/Creator/Icons/Watermark.cs b/FModel/Creator/Icons/Watermark.cs deleted file mode 100644 index c6b952c3..00000000 --- a/FModel/Creator/Icons/Watermark.cs +++ /dev/null @@ -1,31 +0,0 @@ -using SkiaSharp; -using System.IO; - -namespace FModel.Creator.Icons -{ - static class Watermark - { - public static void DrawWatermark(SKCanvas c) - { - if (Properties.Settings.Default.UseIconWatermark && !string.IsNullOrWhiteSpace(Properties.Settings.Default.IconWatermarkPath)) - { - using SKBitmap watermarkBase = SKBitmap.Decode(new FileInfo(Properties.Settings.Default.IconWatermarkPath).Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - int sizeX = watermarkBase.Width * (int)Properties.Settings.Default.IconWatermarkScale / 512; - int sizeY = watermarkBase.Height * (int)Properties.Settings.Default.IconWatermarkScale / 512; - SKBitmap watermark = watermarkBase.Resize(sizeX, sizeY); - - float left = Properties.Settings.Default.IconWatermarkX; - float top = Properties.Settings.Default.IconWatermarkY; - float right = left + watermark.Width; - float bottom = top + watermark.Height; - c.DrawBitmap(watermark, new SKRect(left, top, right, bottom), - new SKPaint - { - FilterQuality = SKFilterQuality.High, - IsAntialias = true, - Color = SKColors.Transparent.WithAlpha((byte)Properties.Settings.Default.IconWatermarkOpacity) - }); - } - } - } -} diff --git a/FModel/Creator/Rarities/EFortRarity.cs b/FModel/Creator/Rarities/EFortRarity.cs deleted file mode 100644 index 412e7988..00000000 --- a/FModel/Creator/Rarities/EFortRarity.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace FModel.Creator.Rarities -{ - public enum EFortRarity : int - { - Common, - Uncommon, - Rare, - Epic, - Legendary, - Mythic, - Transcendent, - Unattainable, - //Impossible - } -} diff --git a/FModel/Creator/Rarities/Rarity.cs b/FModel/Creator/Rarities/Rarity.cs deleted file mode 100644 index e720ec02..00000000 --- a/FModel/Creator/Rarities/Rarity.cs +++ /dev/null @@ -1,204 +0,0 @@ -using FModel.Creator.Bases; -using SkiaSharp; -using System.Linq; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Rarities -{ - static class Rarity - { - public static void GetInGameRarity(BaseIcon icon, EnumProperty e) - { - Package p = Utils.GetPropertyPakPackage("/Game/Balance/RarityData"); - if (p != null && p.HasExport()) - { - var d = p.GetExport(); - if (d != null) - { - EFortRarity rarity = EFortRarity.Uncommon; - switch (e?.Value.String) - { - case "EFortRarity::Common": - case "EFortRarity::Handmade": - rarity = EFortRarity.Common; - break; - case "EFortRarity::Rare": - case "EFortRarity::Sturdy": - rarity = EFortRarity.Rare; - break; - case "EFortRarity::Epic": - case "EFortRarity::Quality": - rarity = EFortRarity.Epic; - break; - case "EFortRarity::Legendary": - case "EFortRarity::Fine": - rarity = EFortRarity.Legendary; - break; - case "EFortRarity::Mythic": - case "EFortRarity::Elegant": - rarity = EFortRarity.Mythic; - break; - case "EFortRarity::Transcendent": - case "EFortRarity::Masterwork": - rarity = EFortRarity.Transcendent; - break; - case "EFortRarity::Unattainable": - case "EFortRarity::Badass": - rarity = EFortRarity.Unattainable; - break; - } - - if (d.Values.ElementAt((int)rarity) is StructProperty s && s.Value is UObject colors) - { - if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 && - colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 && - colors.TryGetValue("Color3", out var c3) && c3 is StructProperty s3 && s3.Value is FLinearColor color3) - { - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; - } - } - } - } - else GetHardCodedRarity(icon, e); - } - - public static void GetInGameRarity(BaseGCosmetic icon, EnumProperty e) - { - Package p = Utils.GetPropertyPakPackage("/Game/UI/UIKit/DT_RarityColors"); - if (p != null || p.HasExport()) - { - var d = p.GetExport(); - if (d != null) - { - if (e != null && d.TryGetValue(e.Value.String["EXRarity::".Length..], out object r) && r is UObject rarity && - rarity.GetExport("Colors") is ArrayProperty colors && - colors.Value[0] is StructProperty s1 && s1.Value is FLinearColor color1 && - colors.Value[1] is StructProperty s2 && s2.Value is FLinearColor color2 && - colors.Value[2] is StructProperty s3 && s3.Value is FLinearColor color3) - { - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color3.Hex) }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; - } - } - } - } - - public static void GetHardCodedRarity(BaseIcon icon, EnumProperty e) - { - switch (e?.Value.String) - { - case "EFortRarity::Common": - case "EFortRarity::Handmade": - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("6D6D6D"), SKColor.Parse("333333") }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("9E9E9E"), SKColor.Parse("9E9E9E") }; - break; - case "EFortRarity::Rare": - case "EFortRarity::Sturdy": - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("3669BB"), SKColor.Parse("133254") }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("5180EE"), SKColor.Parse("5180EE") }; - break; - case "EFortRarity::Epic": - case "EFortRarity::Quality": - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("8138C2"), SKColor.Parse("35155C") }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("B251ED"), SKColor.Parse("B251ED") }; - break; - case "EFortRarity::Legendary": - case "EFortRarity::Fine": - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("C06A38"), SKColor.Parse("5C2814") }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EC9650"), SKColor.Parse("EC9650") }; - break; - case "EFortRarity::Mythic": - case "EFortRarity::Elegant": - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("BA9C36"), SKColor.Parse("594415") }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("EED951"), SKColor.Parse("EED951") }; - break; - case "EFortRarity::Transcendent": - case "EFortRarity::Masterwork": - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse("5CDCE2"), SKColor.Parse("72C5F8") }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse("28DAFB"), SKColor.Parse("28DAFB") }; - break; - } - } - - public static void DrawRarity(SKCanvas c, IBase icon) - { - // border - c.DrawRect(new SKRect(0, 0, icon.Width, icon.Height), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateLinearGradient( - new SKPoint(icon.Width / 2, icon.Height), - new SKPoint(icon.Width, icon.Height / 4), - icon.RarityBorderColor, - SKShaderTileMode.Clamp) - }); - - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) - { - case EIconDesign.Flat: - { - if (icon is BaseIcon i && i.RarityBackgroundImage != null) - c.DrawBitmap(i.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - else - { - c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = icon.RarityBackgroundColors[0] - }); - - var paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = icon.RarityBackgroundColors[1].WithAlpha(75) - }; - var pathTop = new SKPath { FillType = SKPathFillType.EvenOdd }; - pathTop.MoveTo(icon.Margin, icon.Margin); - pathTop.LineTo(icon.Margin + (icon.Width / 17 * 10), icon.Margin); - pathTop.LineTo(icon.Margin, icon.Margin + (icon.Height / 17)); - pathTop.Close(); - c.DrawPath(pathTop, paint); - - var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; - pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin); - pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 2.5f)); - pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 4.5f)); - pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin); - pathBottom.Close(); - c.DrawPath(pathBottom, paint); - } - break; - } - default: - { - if (icon is BaseIcon i && i.RarityBackgroundImage != null) - c.DrawBitmap(i.RarityBackgroundImage, new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - else - c.DrawRect(new SKRect(icon.Margin, icon.Margin, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient( - new SKPoint(icon.Width / 2, icon.Height / 2), - icon.Width / 5 * 4, - icon.RarityBackgroundColors, - SKShaderTileMode.Clamp) - }); - break; - } - } - } - } -} diff --git a/FModel/Creator/Rarities/Serie.cs b/FModel/Creator/Rarities/Serie.cs deleted file mode 100644 index 37aa60b1..00000000 --- a/FModel/Creator/Rarities/Serie.cs +++ /dev/null @@ -1,43 +0,0 @@ -using FModel.Creator.Bases; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; -using SkiaSharp; - -namespace FModel.Creator.Rarities -{ - static class Serie - { - public static void GetRarity(BaseIcon icon, ObjectProperty o) - { - if (o.Value.Resource != null) - { - Package p = Utils.GetPropertyPakPackage(o.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var obj = p.GetExport(); - if (obj != null) - GetRarity(icon, obj); - } - } - } - - public static void GetRarity(BaseIcon icon, IUExport export) - { - if (export.TryGetValue("BackgroundTexture", out var t) && t is SoftObjectProperty sop) - icon.RarityBackgroundImage = Utils.GetSoftObjectTexture(sop); - - if (export.TryGetValue("Colors", out var v) && v is StructProperty s && s.Value is UObject colors) - { - if (colors.TryGetValue("Color1", out var c1) && c1 is StructProperty s1 && s1.Value is FLinearColor color1 && - colors.TryGetValue("Color2", out var c2) && c2 is StructProperty s2 && s2.Value is FLinearColor color2 && - colors.TryGetValue("Color4", out var c4) && c4 is StructProperty s4 && s4.Value is FLinearColor color4) - { - icon.RarityBackgroundColors = new SKColor[2] { SKColor.Parse(color1.Hex), SKColor.Parse(color4.Hex) }; - icon.RarityBorderColor = new SKColor[2] { SKColor.Parse(color2.Hex), SKColor.Parse(color1.Hex) }; - } - } - } - } -} diff --git a/FModel/Creator/Stats/Statistic.cs b/FModel/Creator/Stats/Statistic.cs deleted file mode 100644 index 4a8e343e..00000000 --- a/FModel/Creator/Stats/Statistic.cs +++ /dev/null @@ -1,12 +0,0 @@ -using SkiaSharp; - -namespace FModel.Creator.Stats -{ - public class Statistic - { - public SKBitmap Icon; - public string Description; - public string DisplayName; - public int Height; - } -} diff --git a/FModel/Creator/Stats/Statistics.cs b/FModel/Creator/Stats/Statistics.cs deleted file mode 100644 index 598d183d..00000000 --- a/FModel/Creator/Stats/Statistics.cs +++ /dev/null @@ -1,231 +0,0 @@ -using FModel.Creator.Bases; -using FModel.Creator.Texts; -using FModel.Utils; -using SkiaSharp; -using SkiaSharp.HarfBuzz; -using System; -using System.Windows; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; - -namespace FModel.Creator.Stats -{ - static class Statistics - { - public static void GetAmmoData(BaseIcon icon, SoftObjectProperty ammoData) - { - if (!ammoData.Value.AssetPathName.String.StartsWith("/Game/Athena/Items/Consumables/")) - { - Package p = Utils.GetPropertyPakPackage(ammoData.Value.AssetPathName.String); - if (p.HasExport() && !p.Equals(default)) - { - var obj = p.GetExport(); - if (obj != null) - { - if (obj.TryGetValue("DisplayName", out var v1) && v1 is TextProperty displayName && - obj.TryGetValue("SmallPreviewImage", out var v2) && v2 is SoftObjectProperty smallPreviewImage) - { - icon.Stats.Add(new Statistic - { - Icon = Utils.GetSoftObjectTexture(smallPreviewImage), - Description = Text.GetTextPropertyBase(displayName).ToUpper() - }); - } - } - } - } - } - - public static void GetWeaponStats(BaseIcon icon, StructProperty weaponStatHandle) - { - if (weaponStatHandle.Value is UObject o1 && - o1.TryGetValue("DataTable", out var c1) && c1 is ObjectProperty dataTable && - o1.TryGetValue("RowName", out var c2) && c2 is NameProperty rowName) - { - Package p = Utils.GetPropertyPakPackage(dataTable.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var table = p.GetExport(); - if (table != null) - { - if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject stats) - { - if (stats.TryGetValue("ReloadTime", out var s1) && s1 is FloatProperty reloadTime && reloadTime.Value != 0) - icon.Stats.Add(new Statistic - { - Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ReloadTime_Weapon_Stats.png")).Stream), - Description = $"{Localizations.GetLocalization(string.Empty, "6EA26D1A4252034FBD869A90F9A6E49A", "Reload Time")} ({Localizations.GetLocalization(string.Empty, "6BA53D764BA5CC13E821D2A807A72365", "seconds")}) : {reloadTime.Value:0.0}".ToUpper() - }); - - if (stats.TryGetValue("ClipSize", out var s2) && s2 is IntProperty clipSize && clipSize.Value != 0) - icon.Stats.Add(new Statistic - { - Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_ClipSize_Weapon_Stats.png")).Stream), - Description = $"{Localizations.GetLocalization(string.Empty, "068239DD4327B36124498C9C5F61C038", "Magazine Size")} : {clipSize.Value}".ToUpper() - }); - - if (stats.TryGetValue("DmgPB", out var s3) && s3 is FloatProperty dmgPB && dmgPB.Value != 0) - icon.Stats.Add(new Statistic - { - Icon = SKBitmap.Decode(Application.GetResourceStream(new Uri("pack://application:,,,/Resources/T_DamagePerBullet_Weapon_Stats.png")).Stream), - Description = $"{Localizations.GetLocalization(string.Empty, "BF7E3CF34A9ACFF52E95EAAD4F09F133", "Damage to Player")} : {dmgPB.Value}".ToUpper() - }); - } - } - } - } - } - - public static void GetHeroStats(BaseIcon icon, ObjectProperty heroGameplayDefinition) - { - Package p = Utils.GetPropertyPakPackage(heroGameplayDefinition.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var obj = p.GetExport(); - if (obj != null) - { - if (obj.TryGetValue("HeroPerk", out var v1) && v1 is StructProperty s1 && s1.Value is UObject heroPerk) - { - GetAbilityKit(icon, heroPerk); - } - - if (obj.TryGetValue("TierAbilityKits", out var v2) && v2 is ArrayProperty tierAbilityKits) - { - foreach (StructProperty abilityKit in tierAbilityKits.Value) - { - if (abilityKit.Value is UObject kit) - { - GetAbilityKit(icon, kit); - } - } - } - } - } - } - - private static void GetAbilityKit(BaseIcon icon, UObject parent) - { - if (parent.TryGetValue("GrantedAbilityKit", out var v) && v is SoftObjectProperty grantedAbilityKit) - { - Package k = Utils.GetPropertyPakPackage(grantedAbilityKit.Value.AssetPathName.String); - if (k.HasExport() && !k.Equals(default)) - { - var kit = k.GetExport(); - if (kit != null && - kit.GetExport("DisplayName") is TextProperty displayName && - kit.GetExport("IconBrush") is StructProperty brush && brush.Value is UObject iconBrush && - iconBrush.TryGetValue("ResourceObject", out var s) && s is ObjectProperty resourceObject) - { - icon.Stats.Add(new Statistic - { - Icon = Utils.GetObjectTexture(resourceObject), - Description = Text.GetTextPropertyBase(displayName).ToUpper() - }); - } - } - } - } - - public static void DrawStats(SKCanvas c, BaseIcon icon) - { - int size = 48; - int iconSize = 40; - int textSize = 25; - int y = icon.Size; - foreach (Statistic stat in icon.Stats) - { - c.DrawRect(new SKRect(0, y, icon.Size, y + size), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateLinearGradient( - new SKPoint(icon.Size / 2, icon.Size), - new SKPoint(icon.Size, icon.Size / 4), - icon.RarityBorderColor, - SKShaderTileMode.Clamp) - }); - - - if ((EIconDesign)Properties.Settings.Default.AssetsIconDesign == EIconDesign.Flat) - { - c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = icon.RarityBackgroundColors[0] - }); - } - else - { - c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Shader = SKShader.CreateRadialGradient( - new SKPoint(icon.Size / 2, icon.Size / 2), - icon.Size / 5 * 4, - icon.RarityBackgroundColors, - SKShaderTileMode.Clamp) - }); - } - - c.DrawRect(new SKRect(icon.Margin, y, icon.Size - icon.Margin, y + size - icon.Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = new SKColor(0, 0, 50, 75) - }); - - if (stat.Icon != null) c.DrawBitmap(stat.Icon.Resize(iconSize, iconSize), new SKPoint(icon.Margin * (int)2.5, y + 4), new SKPaint { FilterQuality = SKFilterQuality.High, IsAntialias = true }); - - var statPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = Text.TypeFaces.DisplayNameTypeface, - TextSize = textSize, - Color = SKColors.White, - TextAlign = SKTextAlign.Center, - }; - - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(statPaint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(stat.Description, statPaint); - shapedTextWidth = shapedText.Points[^1].X + statPaint.TextSize / 2f; - - if (shapedTextWidth > (icon.Size - (icon.Margin * 2) - iconSize)) - { - statPaint.TextSize -= 2; - } - else - { - break; - } - } - - c.DrawShapedText(shaper, stat.Description, (icon.Size - shapedTextWidth) / 2f, y + 32, statPaint); - } - else - { - while (statPaint.MeasureText(stat.Description) > (icon.Size - (icon.Margin * 2) - iconSize)) - { - statPaint.TextSize = textSize -= 2; - } - c.DrawText(stat.Description, icon.Size / 2, y + 32, statPaint); - } - - y += size; - } - } - } -} diff --git a/FModel/Creator/Texts/ETextSide.cs b/FModel/Creator/Texts/ETextSide.cs deleted file mode 100644 index 2d4d9aba..00000000 --- a/FModel/Creator/Texts/ETextSide.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FModel.Creator.Texts -{ - public enum ETextSide - { - Center, - Right, - Left - } -} diff --git a/FModel/Creator/Texts/GameplayTag.cs b/FModel/Creator/Texts/GameplayTag.cs deleted file mode 100644 index 29d4219a..00000000 --- a/FModel/Creator/Texts/GameplayTag.cs +++ /dev/null @@ -1,73 +0,0 @@ -using FModel.Creator.Bases; -using FModel.Creator.Icons; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; -using FModel.Utils; - -namespace FModel.Creator.Texts -{ - static class GameplayTag - { - public static void GetGameplayTags(BaseIcon icon, StructProperty s, string exportType) - { - if (s.Value is FGameplayTagContainer g) - { - if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Source.", out var source)) - icon.CosmeticSource = source.String.Substring("Cosmetics.Source.".Length); - else if(g.GameplayTags.TryGetGameplayTag("Athena.ItemAction.", out var action)) - icon.CosmeticSource = action.String.Substring("Athena.ItemAction.".Length); - - if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Set.", out var set)) - icon.Description += GetCosmeticSet(set.String); - if (g.GameplayTags.TryGetGameplayTag("Cosmetics.Filter.Season.", out var season)) - icon.Description += GetCosmeticSeason(season.String); - - UserFacingFlag.GetUserFacingFlags( - g.GameplayTags.GetAllGameplayTag("Cosmetics.UserFacingFlags.", "Homebase.Class.", "NPC.CharacterType.Survivor.Defender."), - icon, exportType); - } - } - - private static string GetCosmeticSet(string setName) - { - Package p = Utils.GetPropertyPakPackage("/Game/Athena/Items/Cosmetics/Metadata/CosmeticSets"); - if (p.HasExport() && !p.Equals(default)) - { - var d = p.GetExport(); - if (d != null && d.TryGetCaseInsensitiveValue(setName, out var obj) && obj is UObject o) - { - if (o.TryGetValue("DisplayName", out var displayName) && displayName is TextProperty t) - { - (string n, string k, string s) = Text.GetTextPropertyBases(t); - string set = Localizations.GetLocalization(n, k, s); - string format = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_SetMembership_NotRich", "\nPart of the {0} set."); - return string.Format(format, set); - } - } - } - return string.Empty; - } - - private static string GetCosmeticSeason(string seasonNumber) - { - string s = seasonNumber.Substring("Cosmetics.Filter.Season.".Length); - int number = int.Parse(s); - if (number == 10) - s = "X"; - - string season = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}"); - string introduced = Localizations.GetLocalization("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in {0}.") - .Replace("", string.Empty).Replace("", string.Empty); - if (number > 10) - { - string chapter = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}"); - string chapterFormat = Localizations.GetLocalization("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}"); - string d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..])); - return string.Format(introduced, d); - } - else return string.Format(introduced, string.Format(season, s)); - } - } -} diff --git a/FModel/Creator/Texts/Helper.cs b/FModel/Creator/Texts/Helper.cs deleted file mode 100644 index 3d513483..00000000 --- a/FModel/Creator/Texts/Helper.cs +++ /dev/null @@ -1,161 +0,0 @@ -using FModel.Creator.Bases; -using SkiaSharp; -using System; -using System.Collections.Generic; -using System.Text; - -using FModel.Properties; - -using SkiaSharp.HarfBuzz; - -namespace FModel.Creator.Texts -{ - static class Helper - { - public class Line - { - public string Value { get; set; } - public float Width { get; set; } - } - - public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, IBase icon, ETextSide side, SKRect area, SKPaint paint) - => DrawCenteredMultilineText(canvas, text, maxLineCount, icon.Width, icon.Margin, side, area, paint); - public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, int size, int margin, ETextSide side, SKRect area, SKPaint paint) - { - float lineHeight = paint.TextSize * 1.2f; - List lines = SplitLines(text, paint, area.Width - margin); - - if (lines == null) - return; - if (lines.Count <= maxLineCount) - maxLineCount = lines.Count; - - float height = maxLineCount * lineHeight; - float y = area.MidY - height / 2; - SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null; - - for (int i = 0; i < maxLineCount; i++) - { - Line line = lines[i]; - - y += lineHeight; - float x = side switch - { - ETextSide.Center => area.MidX - line.Width / 2, - ETextSide.Right => size - margin - line.Width, - ETextSide.Left => margin, - _ => area.MidX - line.Width / 2 - }; - - string lineText = line.Value.TrimEnd(); - - if (shaper == null) - { - canvas.DrawText(lineText, x, y, paint); - } - else - { - if (side == ETextSide.Center) - { - SKShaper.Result shapedText = shaper.Shape(lineText, paint); - float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; - canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint); - } - else - { - canvas.DrawShapedText(shaper, lineText, x, y, paint); - } - } - } - } - - public static void DrawMultilineText(SKCanvas canvas, string text, int size, int margin, ETextSide side, SKRect area, SKPaint paint, out int yPos) - { - float lineHeight = paint.TextSize * 1.2f; - List lines = SplitLines(text, paint, area.Width); - if (lines == null) - { - yPos = (int)area.Top; - return; - } - - float y = area.Top; - SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null; - - for (int i = 0; i < lines.Count; i++) - { - var line = lines[i]; - float x = side switch - { - ETextSide.Center => area.MidX - line.Width / 2, - ETextSide.Right => size - margin - line.Width, - ETextSide.Left => area.Left, - _ => area.MidX - line.Width / 2 - }; - - string lineText = line.Value.TrimEnd(); - - if (shaper == null) - { - canvas.DrawText(lineText, x, y, paint); - } - else - { - if (side == ETextSide.Center) - { - SKShaper.Result shapedText = shaper.Shape(lineText, paint); - float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; - canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint); - } - else - { - canvas.DrawShapedText(shaper, lineText, x, y, paint); - } - } - - y += lineHeight; - } - yPos = (int)area.Top + ((int)lineHeight * lines.Count); - } - - public static List SplitLines(string text, SKPaint paint, float maxWidth) - { - if (string.IsNullOrEmpty(text)) - return null; - - float spaceWidth = paint.MeasureText(" "); - string[] lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - List ret = new List(lines.Length); - for (int i = 0; i < lines.Length; i++) - { - if (string.IsNullOrWhiteSpace(lines[i])) - continue; - - float width = 0; - var lineResult = new StringBuilder(); - string[] words = lines[i].Split(' '); - foreach (var word in words) - { - float wordWidth = paint.MeasureText(word); - float wordWithSpaceWidth = wordWidth + spaceWidth; - string wordWithSpace = word + " "; - - if (width + wordWidth > maxWidth) - { - ret.Add(new Line { Value = lineResult.ToString(), Width = width }); - lineResult = new StringBuilder(wordWithSpace); - width = wordWithSpaceWidth; - } - else - { - lineResult.Append(wordWithSpace); - width += wordWithSpaceWidth; - } - } - ret.Add(new Line { Value = lineResult.ToString(), Width = width }); - } - return ret; - } - } -} diff --git a/FModel/Creator/Texts/Text.cs b/FModel/Creator/Texts/Text.cs deleted file mode 100644 index 1d626eea..00000000 --- a/FModel/Creator/Texts/Text.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System.Collections.Generic; -using FModel.Creator.Bases; -using FModel.PakReader; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.Objects; -using FModel.PakReader.Parsers.PropertyTagData; -using SkiaSharp; -using SkiaSharp.HarfBuzz; - -namespace FModel.Creator.Texts -{ - static class Text - { - public static Typefaces TypeFaces = new Typefaces(); - private const int _STARTER_TEXT_POSITION = 380; - private static int _BOTTOM_TEXT_SIZE = 15; - private static int _NAME_TEXT_SIZE = 45; - - public static string GetTextPropertyBase(TextProperty t) - { - if (t.Value is { } text) - if (text.Text is FTextHistory.None n) - return n.CultureInvariantString; - else if (text.Text is FTextHistory.Base b) - return b.SourceString.Replace("", string.Empty).Replace("", string.Empty); - else if (text.Text is FTextHistory.StringTableEntry s) - { - Package p = Utils.GetPropertyPakPackage(s.TableId.String); - if (p.HasExport() && !p.Equals(default)) - { - var table = p.GetExport(); - if (table != null) - { - if (table.TryGetValue("StringTable", out var v1) && v1 is FStringTable stringTable && - stringTable.KeysToMetadata.TryGetValue(stringTable.TableNamespace, out var v2) && v2 is Dictionary dico && - dico.TryGetValue(s.Key, out var ret)) - { - return ret; - } - } - } - } - - return string.Empty; - } - public static string GetTextPropertyBase(ArrayProperty a) - { - if (a.Value.Length > 0 && a.Value[0] is TextProperty t) - return GetTextPropertyBase(t); - return string.Empty; - } - - public static (string, string, string) GetTextPropertyBases(TextProperty t) - { - if (t.Value is { } text && text.Text is FTextHistory.Base b) - return (b.Namespace, b.Key, b.SourceString); - return (string.Empty, string.Empty, string.Empty); - } - - public static string GetMaxStackSize(StructProperty maxStackSize) - { - if (maxStackSize.Value is UObject o1) - { - if (o1.TryGetValue("Value", out var c) && c is FloatProperty value && value.Value != -1) // old way - return $"MaxStackSize : {value.Value}"; - - if ( - o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 && - o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable && - o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way - { - Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var table = p.GetExport(); - if (table != null) - { - if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount && - maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys && - keys.Value.Length > 0 && (keys.Value[0] as StructProperty)?.Value is FSimpleCurveKey amount && - amount.KeyValue != -1) - { - return $"MaxStackSize : {amount.KeyValue}"; - } - } - } - } - } - return string.Empty; - } - - public static string GetXpRewardAmount(StructProperty xpRewardAmount) - { - if (xpRewardAmount.Value is UObject o1) - { - if ( - o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 && - o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable && - o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way - { - Package p = Utils.GetPropertyPakPackage(curveTable.Value.Resource.OuterIndex.Resource.ObjectName.String); - if (p.HasExport() && !p.Equals(default)) - { - var table = p.GetExport(); - if (table != null) - { - if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount && - maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys && - keys.Value.Length > 0 && (keys.Value[0] as StructProperty)?.Value is FSimpleCurveKey amount && - amount.KeyValue != -1) - { - return $"{amount.KeyValue} Xp"; - } - } - } - } - } - return string.Empty; - } - - public static void DrawBackground(SKCanvas c, IBase icon) - { - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) - { - case EIconDesign.Flat: - { - var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; - pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin); - pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 2.5f); - pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 4.5f); - pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin); - pathBottom.Close(); - c.DrawPath(pathBottom, new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = new SKColor(0, 0, 50, 75), - }); - break; - } - default: - { - c.DrawRect( - new SKRect(icon.Margin, _STARTER_TEXT_POSITION, icon.Width - icon.Margin, icon.Height - icon.Margin), - new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Color = new SKColor(0, 0, 50, 75), - }); - break; - } - } - } - - public static void DrawDisplayName(SKCanvas c, IBase icon) - { - _NAME_TEXT_SIZE = 45; - string text = icon.DisplayName; - - if (string.IsNullOrEmpty(text)) return; - - SKTextAlign side = SKTextAlign.Center; - int x = icon.Width / 2; - int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE; - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) - { - case EIconDesign.Mini: - { - _NAME_TEXT_SIZE = 47; - text = text.ToUpperInvariant(); - break; - } - case EIconDesign.Flat: - { - _NAME_TEXT_SIZE = 47; - side = SKTextAlign.Right; - x = icon.Width - icon.Margin * 2; - break; - } - } - - SKPaint namePaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = TypeFaces.DisplayNameTypeface, - TextSize = _NAME_TEXT_SIZE, - Color = SKColors.White, - TextAlign = side - }; - - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - SKShaper shaper = new SKShaper(namePaint.Typeface); - float shapedTextWidth; - - while (true) - { - SKShaper.Result shapedText = shaper.Shape(text, namePaint); - shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f; - - if (shapedTextWidth > icon.Width - icon.Margin * 2) - { - namePaint.TextSize = _NAME_TEXT_SIZE -= 1; - } - else - { - break; - } - } - - c.DrawShapedText(shaper, text, side == SKTextAlign.Right ? (x - shapedTextWidth) : ((icon.Width - shapedTextWidth) / 2f), y, namePaint); - } - else - { - // resize if too long - while (namePaint.MeasureText(text) > icon.Width - icon.Margin * 2) - { - namePaint.TextSize = _NAME_TEXT_SIZE -= 1; - } - - c.DrawText(text, x, y, namePaint); - } - } - - public static void DrawDescription(SKCanvas c, IBase icon) - { - _BOTTOM_TEXT_SIZE = 15; - string text = icon.Description; - - if (string.IsNullOrEmpty(text)) return; - - int maxLine = 4; - ETextSide side = ETextSide.Center; - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) - { - case EIconDesign.Mini: - { - maxLine = 5; - _BOTTOM_TEXT_SIZE = icon.Margin; - text = text.ToUpper(); - break; - } - case EIconDesign.Flat: - { - side = ETextSide.Right; - break; - } - } - - SKPaint descriptionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = TypeFaces.DescriptionTypeface, - TextSize = 13, - Color = SKColors.White, - }; - - // wrap if too long - Helper.DrawCenteredMultilineText(c, text, maxLine, icon, side, - new SKRect(icon.Margin, _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE, icon.Width - icon.Margin, icon.Height - _BOTTOM_TEXT_SIZE), - descriptionPaint); - } - - public static void DrawToBottom(SKCanvas c, BaseIcon icon, ETextSide side, string text) - { - if (string.IsNullOrEmpty(text)) return; - - SKPaint shortDescriptionPaint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High, - Typeface = side == ETextSide.Left ? TypeFaces.BottomDefaultTypeface ?? TypeFaces.DisplayNameTypeface : TypeFaces.BottomDefaultTypeface ?? TypeFaces.DefaultTypeface, - TextSize = TypeFaces.BottomDefaultTypeface == null ? 15 : 13, - Color = SKColors.White, - TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right, - }; - - if (side == ETextSide.Left) - { - if ((ELanguage)Properties.Settings.Default.AssetsLanguage == ELanguage.Arabic) - { - shortDescriptionPaint.TextSize -= 4f; - SKShaper shaper = new SKShaper(shortDescriptionPaint.Typeface); - c.DrawShapedText(shaper, text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f - shortDescriptionPaint.TextSize * .5f /* ¯\_(ツ)_/¯ */, shortDescriptionPaint); - } - else - { - c.DrawText(text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint); - } - } - else - { - c.DrawText(text, icon.Size - icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint); - } - } - } -} diff --git a/FModel/Creator/Texts/Typefaces.cs b/FModel/Creator/Texts/Typefaces.cs deleted file mode 100644 index 79f11c37..00000000 --- a/FModel/Creator/Texts/Typefaces.cs +++ /dev/null @@ -1,243 +0,0 @@ -using FModel.Utils; -using FModel.ViewModels.DataGrid; -using SkiaSharp; -using System; -using System.Windows; - -namespace FModel.Creator.Texts -{ - public class Typefaces - { -#pragma warning disable IDE0051 - private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/"; - private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite - private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian - private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); // other languages fortnite unofficial - private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black"; - 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"; - private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic"; - private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular"; - private const string _NOTO_SANS_ITALIC = "NotoSans-Italic"; - private const string _NOTO_SANS_REGULAR = "NotoSans-Regular"; - 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_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"; - private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite - private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular"; - private const string _BURBANK_SMALL_BLACK = "burbanksmall-black"; - private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold"; - - private const string _VALORANT_BASE_PATH = "/Game/"; - private const string _TUNGSTEN_BOLD = "Tungsten-Bold"; - private const string _DINNEXT_LTARABIC_HEAVY = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Heavy"; - private const string _TUNGSTEN_CYRILLIC = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/Cyrillic_Tungsten"; - private const string _TUNGSTEN_JAPANESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/JP_Tungsten"; - private const string _TUNGSTEN_KOREAN = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/KR_Tungsten"; - private const string _TUNGSTEN_SIMPLIFIED_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-CN_Tungsten"; - private const string _TUNGSTEN_TRADITIONAL_CHINESE = "UI/Fonts/FinalFonts/LOCFonts/LOC_Tungsten/zh-TW_Tungsten"; - private const string _DINNEXT_W1G_LIGHT = "UI/Fonts/FinalFonts/DINNextW1G-Light"; - private const string _DINNEXT_LTARABIC_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/DIN_Next_Arabic/DINNextLTArabic-Light"; - private const string _NOTOSANS_CJK_LIGHT = "UI/Fonts/FinalFonts/LOCFonts/CJK/NotoSansCJK-Light"; // chinese, japanese, korean - private const string _DINNEXT_W1G_BOLD = "UI/Fonts/FinalFonts/DINNextW1G-Bold"; - - 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"; - - private const string _BATTLE_BREAKERS_BASE_PATH = "/Game/UMG/Fonts/Faces/"; - private const string _HEMIHEAD426 = "HemiHead426"; - private const string _ROBOTO_BOLD = "Roboto-Bold"; - private const string _ROBOTO_BOLD_ALL_CAPS = "Roboto-BoldAllCaps"; - private const string _ROBOTO_REGULAR = "Roboto-Regular"; - private const string _NOTO_SANS_JP_REGULAR = "NotoSansJP-Regular"; - private const string _LATO_LIGHT = "Lato-Light"; - private const string _LATO_MEDIUM = "Lato-Medium"; - private const string _LATO_BLACK_ITALIC = "Lato-BlackItalic"; - private const string _LATO_BLACK = "Lato-Black"; -#pragma warning restore IDE0051 - - public SKTypeface DefaultTypeface; // used as default font for all untranslated strings (item source, ...) - public SKTypeface BundleDefaultTypeface; // used for the last folder string - public SKTypeface BottomDefaultTypeface; - public SKTypeface DisplayNameTypeface; - public SKTypeface DescriptionTypeface; - public SKTypeface BundleDisplayNameTypeface; - - public Typefaces() - { - DefaultTypeface = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream); - if (Globals.Game.ActualGame == EGame.Fortnite) - { - ArraySegment[] t = Utils.GetPropertyArraySegmentByte(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK); - if (t != null && t.Length == 3) - { - if (t[0].Array != null) - BundleDefaultTypeface = SKTypeface.FromStream(t[0].AsStream()); - if (BundleDefaultTypeface != null && t[2].Array != null) - BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream()); - } - else BundleDefaultTypeface = DefaultTypeface; - - string namePath = _FORTNITE_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK : - string.Empty); - if (!namePath.Equals(_FORTNITE_BASE_PATH)) - { - t = Utils.GetPropertyArraySegmentByte(namePath); - if (t != null && t.Length == 3) - { - if (t[0].Array != null) - DisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream()); - if (DisplayNameTypeface != null && t[2].Array != null) - DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); - } - } - else DisplayNameTypeface = DefaultTypeface; - - string bottomPath = _FORTNITE_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? string.Empty : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? string.Empty : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? string.Empty : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? string.Empty : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? string.Empty : - _BURBANK_SMALL_BOLD); - t = Utils.GetPropertyArraySegmentByte(bottomPath); - if (t != null && t.Length == 3) - { - if (t[0].Array != null) - BottomDefaultTypeface = SKTypeface.FromStream(t[0].AsStream()); - if (BottomDefaultTypeface != null && t[2].Array != null) - BottomDefaultTypeface = SKTypeface.FromStream(t[2].AsStream()); - } - - string descriptionPath = _FORTNITE_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_BOLD : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR : - _NOTO_SANS_REGULAR); - t = Utils.GetPropertyArraySegmentByte(descriptionPath); - if (t != null && t.Length == 3) - { - if (t[0].Array != null) - DescriptionTypeface = SKTypeface.FromStream(t[0].AsStream()); - if (DescriptionTypeface != null && t[2].Array != null) - DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream()); - } - else DescriptionTypeface = DefaultTypeface; - - string bundleNamePath = _FORTNITE_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _ASIA_ERINM : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _BURBANK_BIG_CONDENSED_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NIS_JYAU : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _NOTO_SANS_ARABIC_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTO_SANS_TC_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_BLACK : - string.Empty); - if (!bundleNamePath.Equals(_FORTNITE_BASE_PATH)) - { - t = Utils.GetPropertyArraySegmentByte(bundleNamePath); - if (t != null && t.Length == 3) - { - if (t[0].Array != null) - BundleDisplayNameTypeface = SKTypeface.FromStream(t[0].AsStream()); - if (BundleDisplayNameTypeface != null && t[2].Array != null) - BundleDisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); - } - } - else BundleDisplayNameTypeface = BundleDefaultTypeface; - } - else if (Globals.Game.ActualGame == EGame.Valorant) - { - string namePath = _VALORANT_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _TUNGSTEN_KOREAN : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _TUNGSTEN_CYRILLIC : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _TUNGSTEN_JAPANESE : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_HEAVY : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _TUNGSTEN_TRADITIONAL_CHINESE : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _TUNGSTEN_SIMPLIFIED_CHINESE : - _TUNGSTEN_BOLD); - ArraySegment[] t = Utils.GetPropertyArraySegmentByte(namePath); - if (t != null && t.Length == 3 && t[2].Array != null) - DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); - else DisplayNameTypeface = DefaultTypeface; - - string descriptionPath = _VALORANT_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTOSANS_CJK_LIGHT : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTOSANS_CJK_LIGHT : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Arabic ? _DINNEXT_LTARABIC_LIGHT : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.TraditionalChinese ? _NOTOSANS_CJK_LIGHT : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTOSANS_CJK_LIGHT : - _DINNEXT_W1G_LIGHT); - t = Utils.GetPropertyArraySegmentByte(descriptionPath); - if (t != null && t.Length == 3 && t[2].Array != null) - DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream()); - else DescriptionTypeface = DefaultTypeface; - - t = Utils.GetPropertyArraySegmentByte(_VALORANT_BASE_PATH + _DINNEXT_W1G_BOLD); - if (t != null && t.Length == 3 && t[2].Array != null) - BundleDefaultTypeface = SKTypeface.FromStream(t[2].AsStream()); - else BundleDefaultTypeface = DefaultTypeface; - } - else if (Globals.Game.ActualGame == EGame.Spellbreak) - { - ArraySegment[] t = Utils.GetPropertyArraySegmentByte(_SPELLBREAK_BASE_PATH + _QUADRAT_BOLD); - if (t != null && t.Length == 3 && t[2].Array != null) - DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); - else DisplayNameTypeface = DefaultTypeface; - - t = Utils.GetPropertyArraySegmentByte(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD); - if (t != null && t.Length == 3 && t[2].Array != null) - DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream()); - else DescriptionTypeface = DefaultTypeface; - } - else if (Globals.Game.ActualGame == EGame.BattleBreakers) - { - string namePath = _BATTLE_BREAKERS_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _LATO_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR : - _HEMIHEAD426); - ArraySegment[] t = Utils.GetPropertyArraySegmentByte(namePath); - if (t != null && t.Length == 3 && t[2].Array != null) - DisplayNameTypeface = SKTypeface.FromStream(t[2].AsStream()); - else DisplayNameTypeface = DefaultTypeface; - - string descriptionPath = _BATTLE_BREAKERS_BASE_PATH + ( - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Korean ? _NOTO_SANS_KR_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Russian ? _LATO_BLACK : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Japanese ? _NOTO_SANS_JP_REGULAR : - Properties.Settings.Default.AssetsLanguage == (long)ELanguage.Chinese ? _NOTO_SANS_SC_REGULAR : - _HEMIHEAD426); - t = Utils.GetPropertyArraySegmentByte(descriptionPath); - if (t != null && t.Length == 3 && t[2].Array != null) - DescriptionTypeface = SKTypeface.FromStream(t[2].AsStream()); - else DescriptionTypeface = DefaultTypeface; - } - } - - public bool NeedReload(bool forceReload) => forceReload ? - DataGridVm.dataGridViewModel.Count > 0 : //reload only if at least one pak is loaded - DataGridVm.dataGridViewModel.Count > 0 && (BundleDefaultTypeface == DefaultTypeface && DisplayNameTypeface == DefaultTypeface && DescriptionTypeface == DefaultTypeface && BundleDisplayNameTypeface == BundleDefaultTypeface); - } -} diff --git a/FModel/Creator/Typefaces.cs b/FModel/Creator/Typefaces.cs new file mode 100644 index 00000000..f473b4f2 --- /dev/null +++ b/FModel/Creator/Typefaces.cs @@ -0,0 +1,224 @@ +using System; +using System.IO; +using System.Windows; +using CUE4Parse.UE4.Versions; +using FModel.Settings; +using FModel.ViewModels; +using SkiaSharp; + +namespace FModel.Creator +{ + public class Typefaces + { + private readonly Uri _BURBANK_BIG_CONDENSED_BOLD = new Uri("pack://application:,,,/Resources/BurbankBigCondensed-Bold.ttf"); + private const string _EXT = ".ufont"; + + // FortniteGame + private const string _FORTNITE_BASE_PATH = "/Game/UI/Foundation/Fonts/"; + private const string _ASIA_ERINM = "AsiaERINM"; // korean fortnite + private const string _BURBANK_BIG_CONDENSED_BLACK = "BurbankBigCondensed-Black"; // russian + private const string _BURBANK_BIG_REGULAR_BLACK = "BurbankBigRegular-Black"; + 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"; + private const string _NOTO_SANS_FORTNITE_ITALIC = "NotoSans-Fortnite-Italic"; + private const string _NOTO_SANS_FORTNITE_REGULAR = "NotoSans-Fortnite-Regular"; + private const string _NOTO_SANS_ITALIC = "NotoSans-Italic"; + private const string _NOTO_SANS_REGULAR = "NotoSans-Regular"; + 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_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"; + private const string _NOTO_SANS_TC_BLACK = "NotoSansTC-Black"; // traditional chinese fortnite + private const string _NOTO_SANS_TC_REGULAR = "NotoSansTC-Regular"; + private const string _BURBANK_SMALL_BLACK = "burbanksmall-black"; + private const string _BURBANK_SMALL_BOLD = "burbanksmall-bold"; + + // 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, ...) + public readonly SKTypeface DisplayName; + public readonly SKTypeface Description; + public readonly SKTypeface Bottom; // must be null for non-latin base languages + public readonly SKTypeface Bundle; + public readonly SKTypeface BundleNumber; + + public Typefaces(CUE4ParseViewModel viewModel) + { + byte[] data; + _viewModel = viewModel; + var language = UserSettings.Default.AssetLanguage; + Default = SKTypeface.FromStream(Application.GetResourceStream(_BURBANK_BIG_CONDENSED_BOLD)?.Stream); + + switch (viewModel.Game) + { + case FGame.FortniteGame: + { + var namePath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => string.Empty + }; + if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + DisplayName = SKTypeface.FromStream(m); + } + else DisplayName = Default; + + + var descriptionPath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _NOTO_SANS_KR_REGULAR, + ELanguage.Japanese => _NOTO_SANS_JP_BOLD, + ELanguage.Arabic => _NOTO_SANS_ARABIC_REGULAR, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_REGULAR, + ELanguage.Chinese => _NOTO_SANS_SC_REGULAR, + _ => _NOTO_SANS_REGULAR + }; + if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + Description = SKTypeface.FromStream(m); + } + else Description = Default; + + + var bottomPath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => string.Empty, + ELanguage.Japanese => string.Empty, + ELanguage.Arabic => string.Empty, + ELanguage.TraditionalChinese => string.Empty, + ELanguage.Chinese => string.Empty, + _ => _BURBANK_SMALL_BOLD + }; + if (viewModel.Provider.TrySaveAsset(bottomPath + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + Bottom = SKTypeface.FromStream(m); + } + // else keep it null + + + if (viewModel.Provider.TrySaveAsset(_FORTNITE_BASE_PATH + _BURBANK_BIG_CONDENSED_BLACK + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + BundleNumber = SKTypeface.FromStream(m); + } + else BundleNumber = Default; + + + var bundleNamePath = _FORTNITE_BASE_PATH + + language switch + { + ELanguage.Korean => _ASIA_ERINM, + ELanguage.Russian => _BURBANK_BIG_CONDENSED_BLACK, + ELanguage.Japanese => _NIS_JYAU, + ELanguage.Arabic => _NOTO_SANS_ARABIC_BLACK, + ELanguage.TraditionalChinese => _NOTO_SANS_TC_BLACK, + ELanguage.Chinese => _NOTO_SANS_SC_BLACK, + _ => string.Empty + }; + if (viewModel.Provider.TrySaveAsset(bundleNamePath + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + Bundle = SKTypeface.FromStream(m); + } + else Bundle = BundleNumber; + + break; + } + case FGame.WorldExplorers: + { + var namePath = _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 + }; + if (viewModel.Provider.TrySaveAsset(namePath + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + DisplayName = SKTypeface.FromStream(m); + } + else DisplayName = Default; + + var descriptionPath = _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 + }; + if (viewModel.Provider.TrySaveAsset(descriptionPath + _EXT, out data)) + { + var m = new MemoryStream(data) {Position = 0}; + Description = SKTypeface.FromStream(m); + } + else Description = Default; + + break; + } + case FGame.ShooterGame: + break; + case FGame.DeadByDaylight: + break; + case FGame.OakGame: + break; + case FGame.Dungeons: + break; + case FGame.g3: + break; + case FGame.StateOfDecay2: + break; + case FGame.Prospect: + break; + case FGame.Indiana: + break; + case FGame.RogueCompany: + break; + case FGame.SwGame: + break; + case FGame.Platform: + break; + } + } + + public SKTypeface OnTheFly(string path) + { + if (!_viewModel.Provider.TrySaveAsset(path, out var data)) return Default; + var m = new MemoryStream(data) {Position = 0}; + return SKTypeface.FromStream(m); + } + } +} \ No newline at end of file diff --git a/FModel/Creator/Utils.cs b/FModel/Creator/Utils.cs index 61e5b199..f3084d30 100644 --- a/FModel/Creator/Utils.cs +++ b/FModel/Creator/Utils.cs @@ -1,129 +1,309 @@ -using FModel.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.Texture; +using CUE4Parse.UE4.Assets.Objects; +using CUE4Parse.UE4.Objects.UObject; +using CUE4Parse.Utils; +using CUE4Parse_Conversion.Textures; +using FModel.Framework; +using FModel.Services; +using FModel.ViewModels; using SkiaSharp; -using System; -using System.Runtime.CompilerServices; -using FModel.PakReader; -using FModel.PakReader.IO; -using FModel.PakReader.Parsers.Class; -using FModel.PakReader.Parsers.PropertyTagData; +using SkiaSharp.HarfBuzz; namespace FModel.Creator { - static class Utils + public static class Utils { - public static string GetFullPath(FPackageId id) + private static ApplicationViewModel _applicationView => ApplicationService.ApplicationView; + private static readonly Regex _htmlRegex = new("<.*?>"); + public static Typefaces Typefaces; + + public static string RemoveHtmlTags(string s) { - foreach (var ioStore in Globals.CachedIoStores.Values) + var match = _htmlRegex.Match(s); + while (match.Success) { - if (ioStore.Chunks.TryGetValue(id.Id, out string path)) + s = s.Replace(match.Value, string.Empty); + match = match.NextMatch(); + } + + return s; + } + + public static bool TryGetDisplayAsset(UObject uObject, out SKBitmap preview) + { + if (uObject.TryGetValue(out FSoftObjectPath displayAsset, "DisplayAssetPath")) + { + preview = GetDisplayAsset(displayAsset); + return preview != null; + } + + if (uObject.TryGetValue(out FSoftObjectPath sidePanelIcon, "SidePanelIcon")) + { + preview = GetBitmap(sidePanelIcon); + return preview != null; + } + + var path = $"/Game/Catalog/MI_OfferImages/MI_{uObject.Name.Replace("Athena_Commando_", string.Empty)}"; + if (!TryLoadObject(path, out UMaterialInstanceConstant material)) // non-obfuscated item definition + { + if (!TryLoadObject($"{path[..path.LastIndexOf('_')]}_{path.SubstringAfterLast('_').ToLower()}", out material)) // Try to get MI with lowercase obfuscation + TryLoadObject(path[..path.LastIndexOf('_')], out material); // hopefully gets obfuscated item definition + } + + preview = GetBitmap(material); + return preview != null; + } + + public static SKBitmap GetDisplayAsset(FSoftObjectPath path) + { + if (!TryLoadObject(path.AssetPathName.Text, out UObject obj)) return null; + + if (obj.TryGetValue(out FStructFallback type, "DetailsImage") && + type.TryGetValue(out FPackageIndex resource, "ResourceObject") && resource.ResolvedObject?.Outer != null && + !resource.ResolvedObject.Outer.Name.Text.Contains("FortniteGame/Content/Athena/Prototype/Textures/")) + { + return GetBitmap(resource); + } + + return null; + } + public static SKBitmap GetBitmap(FPackageIndex packageIndex) + { + while (true) + { + if (!TryGetPackageIndexExport(packageIndex, out UExport export)) return null; + switch (export) { - return ioStore.MountPoint + path; + case UTexture2D texture: + return GetBitmap(texture); + case UMaterialInstanceConstant material: + return GetBitmap(material); + case UObject uObject: + { + if (uObject.TryGetValue(out FSoftObjectPath previewImage, "LargePreviewImage", "SmallPreviewImage")) return GetBitmap(previewImage); + if (uObject.TryGetValue(out string largePreview, "LargePreviewImage")) return GetBitmap(largePreview); + if (uObject.TryGetValue(out FPackageIndex smallPreview, "SmallPreviewImage")) + { + packageIndex = smallPreview; + continue; + } + + return null; + } + default: + return null; + } + } + } + public static SKBitmap GetBitmap(UMaterialInstanceConstant material) + { + if (material == null) return null; + foreach (var textureParameter in material.TextureParameterValues) + { + if (!(textureParameter.ParameterValue is UTexture2D texture)) continue; + switch (textureParameter.ParameterInfo.Name.Text) + { + case "TextureA": + case "TextureB": + case "OfferImage": + { + return GetBitmap(texture); + } } } return null; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string GetFullPath(string partialPath) - { - foreach (var fileReader in Globals.CachedPakFiles.Values) - if (fileReader.TryGetPartialKey(partialPath, out var fullPath)) - { - return fullPath; - } + 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 : SKBitmap.Decode(texture.Decode()?.Encode()); + public static SKBitmap GetBitmap(byte[] data) => SKBitmap.Decode(data); - foreach (var ioStoreReader in Globals.CachedIoStores.Values) - { - if (ioStoreReader.TryGetPartialKey(partialPath, out var fullPath)) - { - return fullPath; - } - } - return string.Empty; - } + public static SKBitmap Resize(this SKBitmap me, int size) => me.Resize(size, size); - public static Package GetPropertyPakPackage(string value) - { - string path = Strings.FixPath(value); - foreach (var fileReader in Globals.CachedPakFiles.Values) - if (fileReader.TryGetCaseInsensiteveValue(path, out var entry)) - { - // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯ - string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length); - return Assets.GetPackage(entry, mount); - } - foreach (var ioStoreReader in Globals.CachedIoStores.Values) - if (ioStoreReader.TryGetCaseInsensiteveValue(path, out var entry)) - { - // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯ - string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length); - return Assets.GetPackage(entry, mount); - } - return default; - } - - public static ArraySegment[] GetPropertyArraySegmentByte(string value) - { - string path = Strings.FixPath(value); - foreach (var fileReader in Globals.CachedPakFiles.Values) - if (fileReader.TryGetCaseInsensiteveValue(path, out var entry)) - { - // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯ - string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length); - return Assets.GetArraySegmentByte(entry, mount); - } - foreach (var ioStoreReader in Globals.CachedIoStores.Values) - if (ioStoreReader.TryGetCaseInsensiteveValue(path, out var entry)) - { - // kinda sad to use Globals.CachedPakFileMountPoint when the mount point is already in the path ¯\_(ツ)_/¯ - string mount = path.Substring(0, path.Length - entry.Name.Substring(0, entry.Name.LastIndexOf('.')).Length); - return Assets.GetArraySegmentByte(entry, mount); - } - return default; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static SKBitmap NewZeroedBitmap(int width, int height) => new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static SKBitmap Resize(this SKBitmap me, int width, int height) { - var bmp = NewZeroedBitmap(width, height); + var bmp = new SKBitmap(new SKImageInfo(width, height), SKBitmapAllocFlags.ZeroPixels); using var pixmap = bmp.PeekPixels(); me.ScalePixels(pixmap, SKFilterQuality.Medium); return bmp; } - public static SKBitmap GetObjectTexture(ObjectProperty o) => GetTexture(o.Value.Resource.OuterIndex.Resource.ObjectName.String); - public static SKBitmap GetSoftObjectTexture(SoftObjectProperty s) => GetTexture(s.Value.AssetPathName.String); - public static SKBitmap GetTexture(string s) + public static bool TryGetPackageIndexExport(FPackageIndex packageIndex, out T export) where T : UExport { - if (s != null) + if (packageIndex.ResolvedObject == null) { - if (s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/FeaturedItems/Outfit/T_UI_InspectScreen_annualPass")) - s += "_1024"; - else if (s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/BattlePass/T-BattlePass-Season14-Tile") || s.Equals("/Game/UI/Foundation/Textures/BattleRoyale/BattlePass/T-BattlePassWithLevels-Season14-Tile")) - s += "_1"; - else if (s.Equals("/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_WolfsBlood_UIT")) - s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_Wolfsblood_UIT"; - else if (s.Equals("/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_Timeweaver_UIT")) - s = "/Game/UI/Textures/assets/cosmetics/skins/headshot/Skin_Headshot_TimeWeaver_UIT"; + export = default; + return false; } - var p = GetPropertyPakPackage(s); - if (p != null && p.HasExport() && !p.Equals(default)) + var outerChain = new List(); + var current = packageIndex.ResolvedObject.Outer; + while (current != null) { - var i = p.GetExport(); - if (i != null) - return SKBitmap.Decode(i.Image.Encode()); - - var u = p.GetExport(); - if (u != null) - if (u.TryGetValue("TextureParameterValues", out var v) && v is ArrayProperty a) - if (a.Value.Length > 0 && a.Value[0] is StructProperty str && str.Value is UObject o) - if (o.TryGetValue("ParameterValue", out var obj) && obj is ObjectProperty parameterValue) - return GetObjectTexture(parameterValue); + outerChain.Add(current.Name.Text); + current = current.Outer; } - return null; + + if (outerChain.Count < 1) + { + export = default; + return false; + } + + if (!_applicationView.CUE4Parse.Provider.TryLoadPackage(outerChain[^1], out var pkg)) + { + export = default; + return false; + } + + export = pkg.GetExport(packageIndex.ResolvedObject.Index) as T; + return export != null; + } + + // fullpath must be either without any extension or with the export objectname + public static bool TryLoadObject(string fullPath, out T export) where T : UExport + { + return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export); + } + + public static IEnumerable LoadExports(string fullPath) + { + return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath); + } + + public static string GetLocalizedResource(string namespacee, string key, string defaultValue) + { + return _applicationView.CUE4Parse.Provider.GetLocalizedString(namespacee, key, defaultValue); + } + + public static string GetFullPath(string partialPath) + { + var regex = new Regex(partialPath, RegexOptions.IgnoreCase | RegexOptions.Compiled); + foreach (var path in _applicationView.CUE4Parse.Provider.Files.Keys) + { + if (regex.IsMatch(path)) + { + return path; + } + } + + return string.Empty; + } + + public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint) + { + var lineHeight = paint.TextSize * 1.2f; + var lines = SplitLines(text, paint, area.Width - margin); + +#if DEBUG + c.DrawRect(new SKRect(area.Left, area.Top, area.Right, area.Bottom), new SKPaint {Color = SKColors.Red, IsStroke = true}); +#endif + + if (lines == null) return; + if (lines.Count <= maxCount) maxCount = lines.Count; + var height = maxCount * lineHeight; + var y = area.MidY - height / 2; + + var shaper = new CustomSKShaper(paint.Typeface); + for (var i = 0; i < maxCount; i++) + { + var line = lines[i]; + if (line == null) continue; + + var lineText = line.Trim(); + var shapedText = shaper.Shape(lineText, paint); + + y += lineHeight; + var x = side switch + { + SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2, + SKTextAlign.Right => size - margin - shapedText.Points[^1].X, + SKTextAlign.Left => margin, + _ => throw new NotImplementedException() + }; + + c.DrawShapedText(shaper, lineText, x, y, paint); + } + } + + public static void DrawMultilineText(SKCanvas c, string text, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint, out float yPos) + { + yPos = area.Top; + var lineHeight = paint.TextSize * 1.2f; + var lines = SplitLines(text, paint, area.Width); + if (lines == null) return; + + foreach (var line in lines) + { + if (line == null) continue; + var lineText = line.Trim(); + var shaper = new CustomSKShaper(paint.Typeface); + var shapedText = shaper.Shape(lineText, paint); + + var x = side switch + { + SKTextAlign.Center => area.MidX - shapedText.Points[^1].X / 2, + SKTextAlign.Right => size - margin - shapedText.Points[^1].X, + SKTextAlign.Left => area.Left, + _ => throw new NotImplementedException() + }; + + c.DrawShapedText(shaper, lineText, x, yPos, paint); + yPos += lineHeight; + } + +#if DEBUG + c.DrawRect(new SKRect(area.Left, area.Top - paint.TextSize, area.Right, yPos), new SKPaint {Color = SKColors.Red, IsStroke = true}); +#endif + } + + public static List SplitLines(string text, SKPaint paint, float maxWidth) + { + if (string.IsNullOrEmpty(text)) return null; + + var spaceWidth = paint.MeasureText(" "); + var lines = text.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + var ret = new List(lines.Length); + foreach (var line in lines) + { + if (string.IsNullOrWhiteSpace(line)) continue; + + float width = 0; + var words = line.Split(' ', StringSplitOptions.RemoveEmptyEntries); + var lineResult = new StringBuilder(); + foreach (var word in words) + { + var wordWidth = paint.MeasureText(word); + var wordWithSpaceWidth = wordWidth + spaceWidth; + var wordWithSpace = word + " "; + + if (width + wordWidth > maxWidth) + { + ret.Add(lineResult.ToString()); + lineResult = new StringBuilder(wordWithSpace); + width = wordWithSpaceWidth; + } + else + { + lineResult.Append(wordWithSpace); + width += wordWithSpaceWidth; + } + } + + ret.Add(lineResult.ToString()); + } + + return ret; } } -} +} \ No newline at end of file diff --git a/FModel/Discord/DiscordIntegration.cs b/FModel/Discord/DiscordIntegration.cs deleted file mode 100644 index 55d0ef90..00000000 --- a/FModel/Discord/DiscordIntegration.cs +++ /dev/null @@ -1,70 +0,0 @@ -using DiscordRPC; -using DiscordRPC.Logging; -using FModel.Logger; -using System; -using System.Reflection; - -namespace FModel.Discord -{ - static class DiscordIntegration - { - private const string _DISCORD_APP_ID = "684489366189768767"; - - private static readonly Timestamps _baseTimestamp = new Timestamps { Start = DateTime.UtcNow }; - private static readonly Assets _assets = new Assets - { - LargeImageKey = "official_logo", - SmallImageKey = "verified", - SmallImageText = $"v{Assembly.GetExecutingAssembly().GetName().Version.ToString().Substring(0, 5)}" - }; - private static readonly DiscordRpcClient _client = new DiscordRpcClient(_DISCORD_APP_ID); - private static RichPresence _presence; - - public static void Dispose() => _client.Dispose(); - public static void Deinitialize() => _client.Deinitialize(); - private static void Initialize() - { - _client.Logger = new ConsoleLogger() { Level = LogLevel.Warning }; - _client.OnReady += (sender, e) => - { - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Discord RPC]", $"Ready for {e.User.Username}#{e.User.Discriminator} ({e.User.ID})"); - }; - _client.Initialize(); - } - - public static void StartClient() - { - _client.SetPresence(new RichPresence - { - Assets = _assets, - Timestamps = _baseTimestamp, - State = string.Format(Properties.Resources.Idling, Globals.Game.GetName()) - }); - Initialize(); - SaveCurrentPresence(); - } - - public static void Update(string detail = null, string state = null) - { - _client.SetPresence(new RichPresence - { - Assets = _assets, - Timestamps = _baseTimestamp, - Details = string.IsNullOrEmpty(detail) ? _presence?.Details : detail, - State = string.IsNullOrEmpty(state) ? _presence?.State : state - }); - _client.Invoke(); - } - - public static void Restore() - { - if (_presence != null) - { - _client.SetPresence(_presence); - _client.Invoke(); - } - } - - public static void SaveCurrentPresence() => _presence = _client.CurrentPresence; - } -} diff --git a/FModel/Enums.cs b/FModel/Enums.cs index 6c45c0ff..46004c12 100644 --- a/FModel/Enums.cs +++ b/FModel/Enums.cs @@ -1,86 +1,139 @@ -namespace FModel -{ - public enum EGame - { - Unknown, - Fortnite, - Valorant, - DeadByDaylight, - Borderlands3, - MinecraftDungeons, - BattleBreakers, - Spellbreak, - StateOfDecay2, - TheCycle, - TheOuterWorlds, - RogueCompany - } +using System.ComponentModel; - public enum EFModel +namespace FModel +{ + public enum EBuildKind { Debug, Release, Unknown } - public enum EPakLoader + public enum EErrorKind { + Close, + Ignore, + Restart + } + + public enum SettingsOut + { + Restart, + ReloadLocres, + Nothing + } + + public enum EStatusKind + { + Ready, // ready + Loading, // doing stuff + Stopping, // trying to stop + Stopped, // stopped + Failed, // crashed + Completed // worked + } + + public enum EAesReload + { + [Description("Always")] + Always, + [Description("Never")] + Never, + [Description("Once Per Day")] + OncePerDay + } + + public enum EDiscordRpc + { + [Description("Always")] + Always, + [Description("Never")] + Never + } + + public enum EEnabledDisabled + { + [Description("Disabled")] + Disabled, + [Description("Enabled")] + Enabled + } + + 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 + } + + public enum ELoadingMode + { + [Description("Single")] Single, + [Description("Multiple")] + Multiple, + [Description("All")] All, - New, - Modified, - NewModified + [Description("All (New)")] + AllButNew, + [Description("All (Modified)")] + AllButModified } - public enum ECopy + public enum EUpdateMode { - Path, - PathNoExt, - PathNoFile, - File, - FileNoExt + [Description("Stable")] + Stable, + [Description("Beta")] + Beta } - public enum ELanguage : long + public enum ECompressedAudio { - English = 0, - AustralianEnglish = 15, - BritishEnglish = 16, - French = 1, - German = 2, - Italian = 3, - Spanish = 4, - SpanishLatin = 5, - SpanishMexico = 17, - Arabic = 6, - Japanese = 7, - Korean = 8, - Polish = 9, - PortugueseBrazil = 10, - PortuguesePortugal = 18, - Russian = 11, - Turkish = 12, - Chinese = 13, - TraditionalChinese = 14, - Swedish = 19, - Thai = 20, - Indonesian = 21, - VietnameseVietnam = 22, - Zulu = 23 + [Description("Play the decompressed data")] + PlayDecompressed, + [Description("Play the compressed data (might not always be a valid audio data)")] + PlayCompressed } - public enum EJsonType: long - { - Default, - Positioned - } - - public enum EIconDesign : long + public enum EIconStyle { + [Description("Default")] Default, + [Description("No Background")] NoBackground, + [Description("No Text")] NoText, - Mini, - Flat + [Description("Flat")] + Flat, + [Description("Cataba")] + Cataba, + // [Description("Community")] + // CommunityMade } -} +} \ No newline at end of file diff --git a/FModel/Extensions/AvalonExtensions.cs b/FModel/Extensions/AvalonExtensions.cs new file mode 100644 index 00000000..486c1f35 --- /dev/null +++ b/FModel/Extensions/AvalonExtensions.cs @@ -0,0 +1,47 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Xml; +using ICSharpCode.AvalonEdit.Highlighting; +using ICSharpCode.AvalonEdit.Highlighting.Xshd; + +namespace FModel.Extensions +{ + public static class AvalonExtensions + { + private static readonly IHighlightingDefinition _jsonHighlighter = LoadHighlighter("Json.xshd"); + private static readonly IHighlightingDefinition _iniHighlighter = LoadHighlighter("Ini.xshd"); + private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd"); + private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd"); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static IHighlightingDefinition LoadHighlighter(string resourceName) + { + var executingAssembly = Assembly.GetExecutingAssembly(); + using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{resourceName}"); + using var reader = new XmlTextReader(stream); + return HighlightingLoader.Load(reader, HighlightingManager.Instance); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IHighlightingDefinition HighlighterSelector(string ext) + { + switch (ext) + { + case "ini": + case "csv": + return _iniHighlighter; + case "xml": + return _xmlHighlighter; + case "h": + case "cpp": + return _cppHighlighter; + case "bat": + case "txt": + case "po": + return null; + default: + return _jsonHighlighter; + } + } + } +} \ No newline at end of file diff --git a/FModel/Extensions/CollectionExtensions.cs b/FModel/Extensions/CollectionExtensions.cs new file mode 100644 index 00000000..b26a33f5 --- /dev/null +++ b/FModel/Extensions/CollectionExtensions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace FModel.Extensions +{ + public static class CollectionExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Next(this IList collection, T value) + { + var i = collection.IndexOf(value) + 1; + return i >= collection.Count ? collection.First() : collection[i]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Next(this IList collection, int index) + { + var i = index + 1; + return i >= collection.Count ? collection.First() : collection[i]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Previous(this IList collection, T value) + { + var i = collection.IndexOf(value) - 1; + return i < 0 ? collection.Last() : collection[i]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Previous(this IList collection, int index) + { + var i = index - 1; + return i < 0 ? collection.Last() : collection[i]; + } + } +} \ No newline at end of file diff --git a/FModel/Extensions/EnumExtensions.cs b/FModel/Extensions/EnumExtensions.cs new file mode 100644 index 00000000..f5242580 --- /dev/null +++ b/FModel/Extensions/EnumExtensions.cs @@ -0,0 +1,84 @@ +using FModel.Properties; +using System; +using System.ComponentModel; +using System.Resources; +using System.Runtime.CompilerServices; + +namespace FModel.Extensions +{ + public static class EnumExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetDescription(this Enum value) + { + var fi = value.GetType().GetField(value.ToString()); + if (fi == null) return $"{value} ({value:D})"; + var attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : $"{value} ({value:D})"; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetLocalizedDescription(this Enum value) => value.GetLocalizedDescription(Resources.ResourceManager); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetLocalizedDescription(this Enum value, ResourceManager resourceManager) + { + var resourceName = value.GetType().Name + "_" + value; + var description = resourceManager.GetString(resourceName); + + if (string.IsNullOrEmpty(description)) + { + description = value.GetDescription(); + } + + return description; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetLocalizedCategory(this Enum value) => value.GetLocalizedCategory(Resources.ResourceManager); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetLocalizedCategory(this Enum value, ResourceManager resourceManager) + { + var resourceName = value.GetType().Name + "_" + value + "_Category"; + var description = resourceManager.GetString(resourceName); + + return description; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T ToEnum(this string value, T defaultValue) + { + if (!Enum.TryParse(typeof(T), value, true, out var ret)) + return defaultValue; + + return (T) ret; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool HasAnyFlags(this T flags, T contains) where T : Enum, IConvertible => (flags.ToInt32(null) & contains.ToInt32(null)) != 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetIndex(this Enum value) + { + var values = Enum.GetValues(value.GetType()); + return Array.IndexOf(values, value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Next(this Enum value) + { + var values = Enum.GetValues(value.GetType()); + var i = Array.IndexOf(values, value) + 1; + return i == values.Length ? (T) values.GetValue(0) : (T) values.GetValue(i); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Previous(this Enum value) + { + var values = Enum.GetValues(value.GetType()); + var i = Array.IndexOf(values, value) - 1; + return i == -1 ? (T) values.GetValue(values.Length - 1) : (T) values.GetValue(i); + } + } +} \ No newline at end of file diff --git a/FModel/Extensions/StreamExtensions.cs b/FModel/Extensions/StreamExtensions.cs new file mode 100644 index 00000000..21c95978 --- /dev/null +++ b/FModel/Extensions/StreamExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; +using System.Runtime.CompilerServices; + +namespace FModel.Extensions +{ + public enum Endianness + { + LittleEndian, + BigEndian, + } + + public static class StreamExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint ReadUInt32(this Stream s, Endianness endian = Endianness.LittleEndian) + { + var b1 = s.ReadByte(); + var b2 = s.ReadByte(); + var b3 = s.ReadByte(); + var b4 = s.ReadByte(); + + return endian switch + { + Endianness.LittleEndian => (uint) (b4 << 24 | b3 << 16 | b2 << 8 | b1), + Endianness.BigEndian => (uint) (b1 << 24 | b2 << 16 | b3 << 8 | b4), + _ => throw new Exception("unknown endianness") + }; + } + } +} \ No newline at end of file diff --git a/FModel/Utils/Strings.cs b/FModel/Extensions/StringExtensions.cs similarity index 59% rename from FModel/Utils/Strings.cs rename to FModel/Extensions/StringExtensions.cs index 60812ac6..700c1ee2 100644 --- a/FModel/Utils/Strings.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -1,113 +1,90 @@ using System; +using System.IO; using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; -namespace FModel.Utils +namespace FModel.Extensions { - - public static class Strings + public static class StringExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetReadableSize(double size) { - string[] sizes = { Properties.Resources.B, Properties.Resources.KB, Properties.Resources.MB, Properties.Resources.GB, Properties.Resources.TB }; - int order = 0; + if (size == 0) return "0 B"; + + string[] sizes = {"B", "KB", "MB", "GB", "TB"}; + var order = 0; while (size >= 1024 && order < sizes.Length - 1) { order++; size /= 1024; } - return string.Format("{0:# ###.##} {1}", size, sizes[order]).TrimStart(); + + return $"{size:# ###.##} {sizes[order]}".TrimStart(); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string FixPath(string path) - { - if (string.IsNullOrWhiteSpace(path) || path.Length < 5) - return string.Empty; - - string trigger; - { - if (path.Contains("/")) - { - string tempPath = path[1..]; - trigger = tempPath.Substring(0, tempPath.IndexOf('/')); - } - else - trigger = path; - } - - Regex regex = new Regex(trigger); - if (trigger.Equals("SrirachaRanchCore") || trigger.Equals("SrirachaRanchHoagie") || trigger.Equals("SrirachaRanchValet")) - trigger = "SrirachaRanch/" + trigger; - - string fixedPath = trigger switch - { - "Game" => regex.Replace(path, $"{Folders.GetGameName()}/Content", 1), - "RegionCN" => regex.Replace(path, $"{Folders.GetGameName()}/Plugins/{trigger}/Content", 1), - _ => regex.Replace(path, $"{Folders.GetGameName()}/Plugins/GameFeatures/{trigger}/Content", 1) - }; - - int sep = fixedPath.LastIndexOf('.'); - return fixedPath.Substring(0, sep > 0 ? sep : fixedPath.Length); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringBefore(this string s, char delimiter) { var index = s.IndexOf(delimiter); - return index == -1 ? s : s.Substring(0, index); + return index == -1 ? s : s[..index]; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringBefore(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) { var index = s.IndexOf(delimiter, comparisonType); - return index == -1 ? s : s.Substring(0, index); + return index == -1 ? s : s[..index]; } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringAfter(this string s, char delimiter) { var index = s.IndexOf(delimiter); return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringAfter(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) { var index = s.IndexOf(delimiter, comparisonType); return index == -1 ? s : s.Substring(index + delimiter.Length, s.Length - index - delimiter.Length); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringBeforeLast(this string s, char delimiter) { var index = s.LastIndexOf(delimiter); - return index == -1 ? s : s.Substring(0, index); + return index == -1 ? s : s[..index]; } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static string SubstringBeforeWithLast(this string s, char delimiter) - { - var index = s.LastIndexOf(delimiter); - return index == -1 ? s : s.Substring(0, index + 1); - } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringBeforeLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) { var index = s.LastIndexOf(delimiter, comparisonType); - return index == -1 ? s : s.Substring(0, index); + return index == -1 ? s : s[..index]; } - + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBeforeWithLast(this string s, char delimiter) + { + var index = s.LastIndexOf(delimiter); + return index == -1 ? s : s[..(index + 1)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string SubstringBeforeWithLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) + { + var index = s.LastIndexOf(delimiter, comparisonType); + return index == -1 ? s : s[..(index + 1)]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringAfterLast(this string s, char delimiter) { var index = s.LastIndexOf(delimiter); return index == -1 ? s : s.Substring(index + 1, s.Length - index - 1); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string SubstringAfterLast(this string s, string delimiter, StringComparison comparisonType = StringComparison.Ordinal) { @@ -116,7 +93,42 @@ namespace FModel.Utils } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this string orig, string value, StringComparison comparisonType) => - orig.IndexOf(value, comparisonType) >= 0; + public static int GetLineNumber(this string s, string lineToFind) + { + if (int.TryParse(lineToFind, out var index)) + return s.GetLineNumber(index); + + lineToFind = $" \"Name\": \"{lineToFind}\","; + using var reader = new StringReader(s); + var lineNum = 0; + string line; + while ((line = reader.ReadLine()) != null) + { + lineNum++; + if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase)) + return lineNum; + } + + return 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetLineNumber(this string s, int index) + { + using var reader = new StringReader(s); + var lineNum = 0; + string line; + while ((line = reader.ReadLine()) != null) + { + lineNum++; + if (line.Equals(" {")) + index--; + + if (index == -1) + return lineNum + 1; + } + + return 1; + } } -} +} \ No newline at end of file diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 6609283d..43f84da0 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -1,201 +1,185 @@ - + WinExe - netcoreapp3.1 + net5.0-windows true FModel.ico - FModel.App - Asval - - 3.1.2.0 - 3.1.2.0 - FModel.ico - - https://github.com/iAmAsval/FModel - - 3.1.2 - x64 - false + 4.0.0 + 4.0.0.0 + 4.0.0.0 + false + true + win-x64 + true + true - - x64 - true - 1701;1702;NU1701 - - + x64 true 1701;1702;NU1701 + + x64 + true + NU1701 + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - True - - + + + + + + + + + + + + + + - - + - - + + + - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + @@ -204,11 +188,6 @@ True Resources.resx - - True - True - Settings.settings - @@ -218,10 +197,4 @@ - - - PublicSettingsSingleFileGenerator - Settings.Designer.cs - - diff --git a/FModel/FModel.sln b/FModel/FModel.sln new file mode 100644 index 00000000..a53a4881 --- /dev/null +++ b/FModel/FModel.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30804.86 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FModel", "FModel.csproj", "{B1F494EA-90A6-4C24-800E-2F724A1884CA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse", "..\CUE4Parse\CUE4Parse\CUE4Parse.csproj", "{C4620341-BBB7-4384-AC7D-5082D3E0386E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Fortnite", "..\CUE4Parse\CUE4Parse-Fortnite\CUE4Parse-Fortnite.csproj", "{7765FB4C-B54D-427B-ABB6-1073687E56BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CUE4Parse-Conversion", "..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj", "{D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B1F494EA-90A6-4C24-800E-2F724A1884CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B1F494EA-90A6-4C24-800E-2F724A1884CA}.Release|Any CPU.Build.0 = Release|Any CPU + {C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4620341-BBB7-4384-AC7D-5082D3E0386E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4620341-BBB7-4384-AC7D-5082D3E0386E}.Release|Any CPU.Build.0 = Release|Any CPU + {7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7765FB4C-B54D-427B-ABB6-1073687E56BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7765FB4C-B54D-427B-ABB6-1073687E56BD}.Release|Any CPU.Build.0 = Release|Any CPU + {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D0E1E8F7-F56D-469A-8E24-C2439B9FFD83}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {53DB7A15-4E15-4575-9402-0110BDF2794E} + EndGlobalSection +EndGlobal diff --git a/FModel/Framework/AsyncQueue.cs b/FModel/Framework/AsyncQueue.cs new file mode 100644 index 00000000..a24d30ea --- /dev/null +++ b/FModel/Framework/AsyncQueue.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks.Dataflow; + +namespace FModel.Framework +{ + public class AsyncQueue : IAsyncEnumerable + { + private readonly SemaphoreSlim _semaphore = new(1); + private readonly BufferBlock _buffer = new(); + + public int Count => _buffer.Count; + + public void Enqueue(T item) => _buffer.Post(item); + + public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken token = default) + { + await _semaphore.WaitAsync(token); + try + { + while (Count > 0) + { + token.ThrowIfCancellationRequested(); + yield return await _buffer.ReceiveAsync(token); + } + } + finally + { + _semaphore.Release(); + } + } + } +} diff --git a/FModel/Framework/Command.cs b/FModel/Framework/Command.cs new file mode 100644 index 00000000..90396053 --- /dev/null +++ b/FModel/Framework/Command.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows.Input; + +namespace FModel.Framework +{ + public abstract class Command : ICommand + { + public abstract void Execute(object parameter); + + public abstract bool CanExecute(object parameter); + + public void RaiseCanExecuteChanged() + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + + public event EventHandler CanExecuteChanged; + } +} \ No newline at end of file diff --git a/FModel/Framework/CustomSKShaper.cs b/FModel/Framework/CustomSKShaper.cs new file mode 100644 index 00000000..6c92d43b --- /dev/null +++ b/FModel/Framework/CustomSKShaper.cs @@ -0,0 +1,96 @@ +using System; +using HarfBuzzSharp; +using SkiaSharp; +using SkiaSharp.HarfBuzz; +using Buffer = HarfBuzzSharp.Buffer; + +namespace FModel.Framework +{ + public class CustomSKShaper : SKShaper + { + private const int _FONT_SIZE_SCALE = 512; + private readonly Font _font; + private readonly Buffer _buffer; + + public CustomSKShaper(SKTypeface typeface) : base(typeface) + { + using (var blob = Typeface.OpenStream(out var index).ToHarfBuzzBlob()) + using (var face = new Face(blob, index)) + { + face.Index = index; + face.UnitsPerEm = Typeface.UnitsPerEm; + + _font = new Font(face); + _font.SetScale(_FONT_SIZE_SCALE, _FONT_SIZE_SCALE); + _font.SetFunctionsOpenType(); + } + + _buffer = new Buffer(); + } + + public new Result Shape(Buffer buffer, float xOffset, float yOffset, SKPaint paint) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (paint == null) + throw new ArgumentNullException(nameof(paint)); + + // do the shaping + _font.Shape(buffer); + + // get the shaping results + var len = buffer.Length; + var info = buffer.GlyphInfos; + var pos = buffer.GlyphPositions; + + // get the sizes + var textSizeY = paint.TextSize / _FONT_SIZE_SCALE; + var textSizeX = textSizeY * paint.TextScaleX; + + var points = new SKPoint[len]; + var clusters = new uint[len]; + var codepoints = new uint[len]; + + for (var i = 0; i < len; i++) + { + // move the cursor + xOffset += pos[i].XAdvance * textSizeX; + yOffset += pos[i].YAdvance * textSizeY; + + codepoints[i] = info[i].Codepoint; + clusters[i] = info[i].Cluster; + points[i] = new SKPoint(xOffset + pos[i].XOffset * textSizeX, yOffset - pos[i].YOffset * textSizeY); + } + + return new Result(codepoints, clusters, points); + } + + public new Result Shape(string text, SKPaint paint) => Shape(text, 0, 0, paint); + + public new Result Shape(string text, float xOffset, float yOffset, SKPaint paint) + { + if (string.IsNullOrEmpty(text)) + return new Result(); + + using var buffer = new Buffer(); + switch (paint.TextEncoding) + { + case SKTextEncoding.Utf8: + buffer.AddUtf8(text); + break; + case SKTextEncoding.Utf16: + buffer.AddUtf16(text); + break; + case SKTextEncoding.Utf32: + buffer.AddUtf32(text); + break; + default: + throw new NotSupportedException("TextEncoding of type GlyphId is not supported."); + } + + buffer.GuessSegmentProperties(); + return Shape(buffer, xOffset, yOffset, paint); + } + } +} \ No newline at end of file diff --git a/FModel/Framework/FullyObservableCollection.cs b/FModel/Framework/FullyObservableCollection.cs new file mode 100644 index 00000000..e0a13fad --- /dev/null +++ b/FModel/Framework/FullyObservableCollection.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; + +namespace FModel.Framework +{ + public class FullyObservableCollection : ObservableCollection where T : INotifyPropertyChanged + { + /// + /// Occurs when a property is changed within an item. + /// + public event EventHandler ItemPropertyChanged; + + public FullyObservableCollection() + { + } + + public FullyObservableCollection(List list) : base(list) + { + ObserveAll(); + } + + public FullyObservableCollection(IEnumerable enumerable) : base(enumerable) + { + ObserveAll(); + } + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (e.Action is NotifyCollectionChangedAction.Remove or + NotifyCollectionChangedAction.Replace) + { + foreach (T item in e.OldItems) + item.PropertyChanged -= ChildPropertyChanged; + } + + if (e.Action is NotifyCollectionChangedAction.Add or + NotifyCollectionChangedAction.Replace) + { + foreach (T item in e.NewItems) + item.PropertyChanged += ChildPropertyChanged; + } + + base.OnCollectionChanged(e); + } + + protected void OnItemPropertyChanged(ItemPropertyChangedEventArgs e) + { + ItemPropertyChanged?.Invoke(this, e); + } + + protected void OnItemPropertyChanged(int index, PropertyChangedEventArgs e) + { + OnItemPropertyChanged(new ItemPropertyChangedEventArgs(index, e)); + } + + protected override void ClearItems() + { + foreach (T item in Items) + item.PropertyChanged -= ChildPropertyChanged; + + base.ClearItems(); + } + + private void ObserveAll() + { + foreach (T item in Items) + item.PropertyChanged += ChildPropertyChanged; + } + + private void ChildPropertyChanged(object sender, PropertyChangedEventArgs e) + { + var typedSender = (T) sender; + var i = Items.IndexOf(typedSender); + + if (i < 0) + throw new ArgumentException("Received property notification from item not in collection"); + + OnItemPropertyChanged(i, e); + } + } + + /// + /// Provides data for the event. + /// + public class ItemPropertyChangedEventArgs : PropertyChangedEventArgs + { + /// + /// Gets the index in the collection for which the property change has occurred. + /// + /// + /// Index in parent collection. + /// + public int CollectionIndex { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The index in the collection of changed item. + /// The name of the property that changed. + public ItemPropertyChangedEventArgs(int index, string name) : base(name) + { + CollectionIndex = index; + } + + /// + /// Initializes a new instance of the class. + /// + /// The index. + /// The instance containing the event data. + public ItemPropertyChangedEventArgs(int index, PropertyChangedEventArgs args) : this(index, args.PropertyName) + { + } + } +} \ No newline at end of file diff --git a/FModel/Framework/Hotkey.cs b/FModel/Framework/Hotkey.cs new file mode 100644 index 00000000..f20116a2 --- /dev/null +++ b/FModel/Framework/Hotkey.cs @@ -0,0 +1,50 @@ +using System.Text; +using System.Windows.Input; + +namespace FModel.Framework +{ + public class Hotkey : ViewModel + { + private Key _key; + public Key Key + { + get => _key; + set => SetProperty(ref _key, value); + } + + private ModifierKeys _modifiers; + public ModifierKeys Modifiers + { + get => _modifiers; + set => SetProperty(ref _modifiers, value); + } + + public Hotkey(Key key, ModifierKeys modifiers = ModifierKeys.None) + { + Key = key; + Modifiers = modifiers; + } + + public bool IsTriggered(Key e) + { + return e == Key && Keyboard.Modifiers.HasFlag(Modifiers); + } + + public override string ToString() + { + var str = new StringBuilder(); + + if (Modifiers.HasFlag(ModifierKeys.Control)) + str.Append("Ctrl + "); + if (Modifiers.HasFlag(ModifierKeys.Shift)) + str.Append("Shift + "); + if (Modifiers.HasFlag(ModifierKeys.Alt)) + str.Append("Alt + "); + if (Modifiers.HasFlag(ModifierKeys.Windows)) + str.Append("Win + "); + + str.Append(Key); + return str.ToString(); + } + } +} \ No newline at end of file diff --git a/FModel/Framework/JsonNetSerializer.cs b/FModel/Framework/JsonNetSerializer.cs new file mode 100644 index 00000000..9d9e50b8 --- /dev/null +++ b/FModel/Framework/JsonNetSerializer.cs @@ -0,0 +1,43 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using RestSharp; +using RestSharp.Serialization; + +namespace FModel.Framework +{ + public class JsonNetSerializer : IRestSerializer + { + public static readonly JsonSerializerSettings SerializerSettings = new() + { + NullValueHandling = NullValueHandling.Ignore, + MissingMemberHandling = MissingMemberHandling.Ignore, + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + public string Serialize(object obj) + { + return JsonConvert.SerializeObject(obj); + } + + [Obsolete] + public string Serialize(Parameter parameter) + { + return JsonConvert.SerializeObject(parameter.Value); + } + + public T Deserialize(IRestResponse response) + { + return JsonConvert.DeserializeObject(response.Content, SerializerSettings); + } + + public string[] SupportedContentTypes { get; } = + { + "application/json", "application/json; charset=UTF-8" + }; + + public string ContentType { get; set; } = "application/json; charset=UTF-8"; + + public DataFormat DataFormat => DataFormat.Json; + } +} \ No newline at end of file diff --git a/FModel/Framework/RangeObservableCollection.cs b/FModel/Framework/RangeObservableCollection.cs new file mode 100644 index 00000000..130683c1 --- /dev/null +++ b/FModel/Framework/RangeObservableCollection.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; + +namespace FModel.Framework +{ + public sealed class RangeObservableCollection : ObservableCollection + { + private bool _suppressNotification; + + protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) + { + if (!_suppressNotification) + base.OnCollectionChanged(e); + } + + public void AddRange(IEnumerable list) + { + if (list == null) + throw new ArgumentNullException(nameof(list)); + + _suppressNotification = true; + + foreach (var item in list) + Add(item); + + _suppressNotification = false; + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public void SetSuppressionState(bool state) + { + _suppressNotification = state; + } + + public void InvokeOnCollectionChanged(NotifyCollectionChangedAction changedAction = NotifyCollectionChangedAction.Reset) + { + OnCollectionChanged(new NotifyCollectionChangedEventArgs(changedAction)); + } + } +} \ No newline at end of file diff --git a/FModel/Framework/ViewModel.cs b/FModel/Framework/ViewModel.cs new file mode 100644 index 00000000..e1a55336 --- /dev/null +++ b/FModel/Framework/ViewModel.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using Newtonsoft.Json; + +namespace FModel.Framework +{ + public class ViewModel : INotifyPropertyChanged, INotifyDataErrorInfo, IDataErrorInfo + { + private readonly Dictionary> _validationErrors = new(); + + public string this[string propertyName] + { + get + { + if (string.IsNullOrEmpty(propertyName)) + return Error; + + return _validationErrors.ContainsKey(propertyName) ? string.Join(Environment.NewLine, _validationErrors[propertyName]) : string.Empty; + } + } + + [JsonIgnore] public string Error => string.Join(Environment.NewLine, GetAllErrors()); + [JsonIgnore] public bool HasErrors => _validationErrors.Any(); + + public IEnumerable GetErrors(string propertyName) + { + if (string.IsNullOrEmpty(propertyName)) + return _validationErrors.SelectMany(kvp => kvp.Value); + + return _validationErrors.TryGetValue(propertyName, out var errors) ? errors : Enumerable.Empty(); + } + + private IEnumerable GetAllErrors() + { + return _validationErrors.SelectMany(kvp => kvp.Value).Where(e => !string.IsNullOrEmpty(e)); + } + + public void AddValidationError(string propertyName, string errorMessage) + { + if (!_validationErrors.ContainsKey(propertyName)) + _validationErrors.Add(propertyName, new List()); + + _validationErrors[propertyName].Add(errorMessage); + } + + public void ClearValidationErrors(string propertyName) + { + if (_validationErrors.ContainsKey(propertyName)) + _validationErrors.Remove(propertyName); + } + + public event PropertyChangedEventHandler PropertyChanged; +#pragma warning disable 67 + public event EventHandler ErrorsChanged; +#pragma warning disable 67 + + protected void RaisePropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected virtual bool SetProperty(ref T storage, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(storage, value)) + return false; + + storage = value; + RaisePropertyChanged(propertyName); + return true; + } + } +} \ No newline at end of file diff --git a/FModel/Framework/ViewModelCommand.cs b/FModel/Framework/ViewModelCommand.cs new file mode 100644 index 00000000..4d064f2e --- /dev/null +++ b/FModel/Framework/ViewModelCommand.cs @@ -0,0 +1,50 @@ +using System; + +namespace FModel.Framework +{ + public abstract class ViewModelCommand : Command where TContextViewModel : ViewModel + { + private WeakReference _parent; + + public TContextViewModel ContextViewModel + { + get + { + if (_parent is {IsAlive: true}) + return (TContextViewModel) _parent.Target; + + return null; + } + + private set + { + if (ContextViewModel == value) + return; + + _parent = value != null ? new WeakReference(value) : null; + } + } + + protected ViewModelCommand(TContextViewModel contextViewModel) + { + ContextViewModel = contextViewModel /*?? throw new ArgumentNullException(nameof(contextViewModel))*/; + } + + public sealed override void Execute(object parameter) + { + Execute(ContextViewModel, parameter); + } + + public abstract void Execute(TContextViewModel contextViewModel, object parameter); + + public sealed override bool CanExecute(object parameter) + { + return CanExecute(ContextViewModel, parameter); + } + + public virtual bool CanExecute(TContextViewModel contextViewModel, object parameter) + { + return true; + } + } +} \ No newline at end of file diff --git a/FModel/Globals.cs b/FModel/Globals.cs deleted file mode 100644 index bae21c68..00000000 --- a/FModel/Globals.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using FModel.PakReader.IO; -using FModel.PakReader.Pak; -using FModel.PakReader.Parsers.Objects; -using FModel.Properties; -using ToastNotifications; -using ToastNotifications.Lifetime; -using ToastNotifications.Position; -using UsmapNET.Classes; - -namespace FModel -{ - static class Globals - { - /// - /// string is the pak file name - /// PakFileReader is the reader where you can grab the FPakEntries, MountPoint and more - /// - public static readonly Dictionary CachedPakFiles = new Dictionary(); - public static readonly Dictionary CachedIoStores = new Dictionary(); - public static readonly Dictionary CachedSchemas = new Dictionary(); - public static Usmap Usmap = null; - public static FIoGlobalData GlobalData = null; - public static readonly Notifier gNotifier = new Notifier(cfg => - { - cfg.LifetimeSupervisor = new TimeAndCountBasedLifetimeSupervisor(TimeSpan.FromSeconds(7), MaximumNotificationCount.FromCount(15)); - cfg.PositionProvider = new PrimaryScreenPositionProvider(Corner.BottomRight, 5, 5); - cfg.Dispatcher = Application.Current.Dispatcher; - }); - public static bool bSearch = false; // trigger the event to select a file thank to the search window - public static string sSearch = string.Empty; // this will be the file name triggered - public static FGame Game = new FGame(EGame.Unknown, EPakVersion.LATEST, 0); - public const EFModel Build = -#if RELEASE - EFModel.Release; -#elif DEBUG - EFModel.Debug; -#else - EFModel.Unknown; -#endif - } - - public class FGame - { - public EGame ActualGame; - public EPakVersion Version; - public int SubVersion; - - public FGame(EGame game, EPakVersion version, int subVersion) - { - ActualGame = game; - Version = version; - SubVersion = subVersion; - } - - public string GetName() - { - return ActualGame switch - { - EGame.Fortnite => Resources.GameName_Fortnite, - EGame.Valorant => Resources.GameName_Valorant, - EGame.DeadByDaylight => Resources.GameName_DeadByDaylight, - EGame.Borderlands3 => Resources.GameName_Borderlands3, - EGame.MinecraftDungeons => Resources.GameName_MinecraftDungeons, - EGame.BattleBreakers => Resources.GameName_BattleBreakers, - EGame.Spellbreak => Resources.GameName_Spellbreak, - EGame.StateOfDecay2 => Resources.GameName_StateofDecay2, - EGame.TheCycle => Resources.GameName_TheCycle, - EGame.TheOuterWorlds => Resources.GameName_TheOuterWorlds, - EGame.RogueCompany => Resources.GameName_RogueCompany, - EGame.Unknown => "Unknown", - _ => "Unknown" - }; - } - } - - static class FColors - { - public const string Red = "#E06C75"; - public const string Orange = "#D19A66"; - public const string Yellow = "#E5C07B"; - public const string Purple = "#C678DD"; - public const string Blue = "#61AFEF"; - public const string Discord = "#8B9BD4"; - public const string Green = "#98C379"; - public const string LightGray = "#BBBBBB"; - public const string DarkGray = "#9B9B9B"; - public const string White = "#EFEFEF"; - } -} diff --git a/FModel/Grabber/Aes/AesData.cs b/FModel/Grabber/Aes/AesData.cs deleted file mode 100644 index 5646cff6..00000000 --- a/FModel/Grabber/Aes/AesData.cs +++ /dev/null @@ -1,37 +0,0 @@ -using FModel.Logger; -using FModel.Utils; -using FModel.Windows.CustomNotifier; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Net.NetworkInformation; -using System.Threading.Tasks; - -namespace FModel.Grabber.Aes -{ - static class AesData - { - public static async Task GetData() - { - if (NetworkInterface.GetIsNetworkAvailable()) - { - BenResponse data = await Endpoints.GetJsonEndpoint(Endpoints.BENBOT_AES, string.Empty).ConfigureAwait(false); - return data; - } - else - { - Globals.gNotifier.ShowCustomMessage(Properties.Resources.AES, Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico"); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", "No internet"); - return null; - } - } - } - - public class BenResponse - { - [JsonProperty("mainKey")] - public string MainKey { get; set; } - - [JsonProperty("dynamicKeys")] - public Dictionary DynamicKeys { get; set; } - } -} diff --git a/FModel/Grabber/Aes/AesGrabber.cs b/FModel/Grabber/Aes/AesGrabber.cs deleted file mode 100644 index e80fc730..00000000 --- a/FModel/Grabber/Aes/AesGrabber.cs +++ /dev/null @@ -1,85 +0,0 @@ -using FModel.Logger; -using FModel.ViewModels.MenuItem; -using FModel.Windows.CustomNotifier; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace FModel.Grabber.Aes -{ - static class AesGrabber - { - public static async Task Load(bool forceReload = false) - { - if (Globals.Game.ActualGame == EGame.Fortnite && MenuItems.pakFiles.AtLeastOnePak()) - { - if (forceReload) - { - Dictionary staticKeys = new Dictionary(); - if (!string.IsNullOrEmpty(Properties.Settings.Default.StaticAesKeys)) - staticKeys = JsonConvert.DeserializeObject>(Properties.Settings.Default.StaticAesKeys); - - Dictionary> oldDynamicKeys = new Dictionary>(); - try - { - if (!string.IsNullOrEmpty(Properties.Settings.Default.DynamicAesKeys)) - oldDynamicKeys = JsonConvert.DeserializeObject>>(Properties.Settings.Default.DynamicAesKeys); - } - catch (JsonSerializationException) { /* Needed for the transition bewteen global dynamic keys and "per game" dynamic keys */ } - - BenResponse benResponse = await AesData.GetData().ConfigureAwait(false); - if (benResponse != null) - { - if (!string.IsNullOrEmpty(benResponse.MainKey)) - { - string mainKey = $"0x{benResponse.MainKey[2..].ToUpper()}"; - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Main key is {mainKey}"); - staticKeys[Globals.Game.ActualGame.ToString()] = mainKey; - Properties.Settings.Default.StaticAesKeys = JsonConvert.SerializeObject(staticKeys, Formatting.None); - } - - if (oldDynamicKeys.TryGetValue(Globals.Game.ActualGame.ToString(), out var gameDict)) - { - Dictionary difference = benResponse.DynamicKeys - .Where(x => !x.Key.Contains("optional") && (!gameDict.ContainsKey(x.Key) || !gameDict[x.Key].Equals(x.Value))) - .ToDictionary(x => x.Key, x => x.Value); - foreach (KeyValuePair KvP in difference) - { - Globals.gNotifier.ShowCustomMessage( - Properties.Resources.PakFiles, - string.Format( - Properties.Resources.PakCanBeOpened, - KvP.Key[(KvP.Key.IndexOf("Paks/") + "Paks/".Length)..]), - "/FModel;component/Resources/lock-open-variant.ico"); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"{KvP.Key} with key {KvP.Value} can be opened"); - } - } - - foreach (var (key, value) in benResponse.DynamicKeys.ToList()) - { - if (key.Contains("optional")) - { - if (!benResponse.DynamicKeys.TryGetValue(key.Replace("optional", ""), out string _)) - benResponse.DynamicKeys[key.Replace("optional", "")] = value; - } - else - { - if (!benResponse.DynamicKeys.TryGetValue(key.Replace("-WindowsClient", "optional-WindowsClient"), out string _)) - benResponse.DynamicKeys[key.Replace("-WindowsClient", "optional-WindowsClient")] = value; - } - } - - oldDynamicKeys[Globals.Game.ActualGame.ToString()] = benResponse.DynamicKeys; - Properties.Settings.Default.DynamicAesKeys = JsonConvert.SerializeObject(oldDynamicKeys, Formatting.None); - Properties.Settings.Default.Save(); - - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[AES]", $"BenBot Dynamic keys are {Properties.Settings.Default.DynamicAesKeys}"); - return true; - } - } - } - return false; - } - } -} diff --git a/FModel/Grabber/Cdn/CdnData.cs b/FModel/Grabber/Cdn/CdnData.cs deleted file mode 100644 index f0df72c6..00000000 --- a/FModel/Grabber/Cdn/CdnData.cs +++ /dev/null @@ -1,53 +0,0 @@ -using AutoUpdaterDotNET; -using FModel.Logger; -using FModel.Utils; -using FModel.Windows.CustomNotifier; -using System.Collections.Generic; -using System.Net.NetworkInformation; -using System.Threading.Tasks; - -namespace FModel.Grabber.Cdn -{ - static class CdnData - { - public static readonly bool bInternet = NetworkInterface.GetIsNetworkAvailable(); - - public static async Task GetData() - { - if (bInternet) - return await Endpoints.GetJsonEndpoint(Endpoints.FMODEL_JSON).ConfigureAwait(false); - else - { - Globals.gNotifier.ShowCustomMessage("CDN", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico"); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", "No internet"); - return null; - } - } - } - - public class CdnResponse - { - public Dictionary Backups { get; set; } - public Dictionary GlobalMessages { get; set; } - public FUpdater Updater { get; set; } - } - public class Backup - { - public string Header { get; set; } - public string DownloadUrl { get; set; } - public double Size { get; set; } - } - public class GlobalMessage - { - public string Message { get; set; } - public string Color { get; set; } - public bool NewLine { get; set; } - } - public class FUpdater - { - public string Version { get; set; } - public string Url { get; set; } - public string Changelog { get; set; } - public Mandatory Mandatory { get; set; } - } -} diff --git a/FModel/Grabber/Cdn/CdnDataGrabber.cs b/FModel/Grabber/Cdn/CdnDataGrabber.cs deleted file mode 100644 index 3f88784b..00000000 --- a/FModel/Grabber/Cdn/CdnDataGrabber.cs +++ /dev/null @@ -1,102 +0,0 @@ -using FModel.Logger; -using FModel.Utils; -using FModel.ViewModels.MenuItem; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media.Imaging; - -namespace FModel.Grabber.Cdn -{ - static class CdnDataGrabber - { - private static CdnResponse _data = null; // avoid multiple requests on same endpoint - - public static async Task DoCDNStuff() - { - await PopulateBackups().ConfigureAwait(false); // step by step - await ShowGMessages().ConfigureAwait(false); // step by step - } - - public static async Task PopulateBackups() - { - Application.Current.Dispatcher.Invoke(delegate - { - // Backup PAKs Menu Item - MenuItems.backupFiles.Add(new BackupMenuItemViewModel - { - Header = Properties.Resources.BackupPaks, - Icon = new Image { Source = new BitmapImage(new Uri("Resources/backup-restore.png", UriKind.Relative)) } - }); - MenuItems.backupFiles.Add(new Separator { }); - }); - - List backupsInfos = await GetBackups().ConfigureAwait(false); - if (backupsInfos.Any()) - { - foreach (BackupMenuItemViewModel b in backupsInfos) - { - Application.Current.Dispatcher.Invoke(delegate - { - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[CDN]", $"{b.Header} is available to download"); - MenuItems.backupFiles.Add(b); - }); - } - } - } - - public static async Task GetUpdateData() - { - if (_data == null) - _data = await CdnData.GetData().ConfigureAwait(false); - - if (_data != null) - { - return _data.Updater; - } - return null; - } - - private static async Task> GetBackups() - { - if (_data == null) - _data = await CdnData.GetData().ConfigureAwait(false); - - if (_data != null && _data.Backups.TryGetValue(Globals.Game.ActualGame.ToString(), out var backups)) - { - return JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(backups)); - } - return new List(); - } - - public static async Task ShowGMessages() - { - Dictionary globalMessages = await GetGlobalMessages().ConfigureAwait(false); - if (globalMessages.Any()) - { - string version = Assembly.GetExecutingAssembly().GetName().Version.ToString(); - if (globalMessages.ContainsKey(version)) - if (!string.IsNullOrEmpty(globalMessages[version][0].Message)) - foreach (GlobalMessage gm in globalMessages[version]) - FConsole.AppendText(gm.Message, gm.Color, gm.NewLine); - } - } - - private static async Task> GetGlobalMessages() - { - if (_data == null && CdnData.bInternet) // if data is still null after getting backups, that means there's no internet, do not try again - _data = await CdnData.GetData().ConfigureAwait(false); // ^ will also avoid showing "No internet" notifier twice - - if (_data != null) - { - return JsonConvert.DeserializeObject>(JsonConvert.SerializeObject(_data.GlobalMessages)); - } - return new Dictionary(); - } - } -} diff --git a/FModel/Grabber/Manifests/ManifestGrabber.cs b/FModel/Grabber/Manifests/ManifestGrabber.cs deleted file mode 100644 index 143e0d53..00000000 --- a/FModel/Grabber/Manifests/ManifestGrabber.cs +++ /dev/null @@ -1,38 +0,0 @@ -using EpicManifestParser.Objects; - -using FModel.Utils; - -using System; -using System.Threading.Tasks; - -namespace FModel.Grabber.Manifests -{ - static class ManifestGrabber - { - public static async Task TryGetLatestManifestInfo() - { - if (IsExpired()) - { - OAuth auth = await Endpoints.GetOAuthInfo().ConfigureAwait(false); - - if (auth != null) - { - Properties.Settings.Default.AccessToken = auth.AccessToken; - Properties.Settings.Default.LauncherExpiration = DateTimeOffset.Now.AddSeconds(Convert.ToDouble(auth.ExpiresIn)).ToUnixTimeMilliseconds(); - Properties.Settings.Default.Save(); - } - } - - string ret = await Endpoints.GetStringEndpoint("https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live", Properties.Settings.Default.AccessToken).ConfigureAwait(false); - return string.IsNullOrEmpty(ret) ? null : new ManifestInfo(ret); - } - - private static bool IsExpired() - { - if (string.IsNullOrEmpty(Properties.Settings.Default.AccessToken)) return true; - - long currentTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - return currentTime - 60000 >= Properties.Settings.Default.LauncherExpiration; - } - } -} \ No newline at end of file diff --git a/FModel/Grabber/Manifests/ValorantAPIManifest.cs b/FModel/Grabber/Manifests/ValorantAPIManifest.cs deleted file mode 100644 index 2f3809f7..00000000 --- a/FModel/Grabber/Manifests/ValorantAPIManifest.cs +++ /dev/null @@ -1,780 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using FModel.PakReader; -using FModel.Utils; - -using Ionic.Zlib; - -namespace FModel.Grabber.Manifests -{ - public class ValorantAPIManifestV1 - { - private const string _url = "https://fmodel.fortnite-api.com/valorant/v1/manifest"; - - private readonly HttpClient _client; - private readonly DirectoryInfo _chunkDirectory; - - public readonly ulong Id; - public readonly Dictionary Chunks; - public readonly ValorantPakV1[] Paks; - - public ValorantAPIManifestV1(byte[] data, DirectoryInfo directoryInfo) : this(new MemoryStream(data, false), directoryInfo) { } - public ValorantAPIManifestV1(Stream stream, DirectoryInfo directoryInfo) : this(new BinaryReader(stream), directoryInfo) { } - public ValorantAPIManifestV1(BinaryReader reader, DirectoryInfo directoryInfo) - { - using (reader) - { - Id = reader.ReadUInt64(); - var chunks = reader.ReadInt32(); - Chunks = new Dictionary(chunks); - - for (var i = 0; i < chunks; i++) - { - var chunk = new ValorantChunkV1(reader); - Chunks.Add(chunk.Id, chunk); - } - - Paks = reader.ReadTArray(() => new ValorantPakV1(reader)); - } - - _client = new HttpClient(new HttpClientHandler - { - UseProxy = false, - UseCookies = false, - AutomaticDecompression = DecompressionMethods.All, - CheckCertificateRevocationList = false, - PreAuthenticate = false, - MaxConnectionsPerServer = 1337 - }); - _chunkDirectory = directoryInfo; - } - - public Stream GetPakStream(int index) - { - return new ValorantPakV1Stream(this, index); - } - - public async Task PrefetchChunk(ValorantChunkV1 chunk, CancellationToken cancellationToken) - { - var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk"); - - if (File.Exists(chunkPath)) - { - return; - } - - using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url); - using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.OK) - { - var chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); - await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false); - } - #if DEBUG - else - { - var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Debugger.Break(); - } - #endif - } - - public async Task GetChunkBytes(ValorantChunkV1 chunk, CancellationToken cancellationToken) - { - var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk"); - byte[] chunkBytes; - - if (File.Exists(chunkPath)) - { - chunkBytes = new byte[chunk.Size]; - await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read); - await fs.ReadAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false); - } - else - { - using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url); - using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.OK) - { - chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); - await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false); - } - else - { - #if DEBUG - var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Debugger.Break(); - #endif - chunkBytes = null; - } - } - - return chunkBytes; - } - - public static async Task DownloadAndParse(DirectoryInfo directoryInfo) - { - using var client = new HttpClient(new HttpClientHandler - { - UseProxy = false, - UseCookies = false, - AutomaticDecompression = DecompressionMethods.All, - CheckCertificateRevocationList = false, - PreAuthenticate = false - }); - using var request = new HttpRequestMessage(HttpMethod.Get, _url); - - try - { - using var response = await client.SendAsync(request).ConfigureAwait(false); - - if (response.StatusCode != HttpStatusCode.OK) - { - return null; - } - - var responseStream = await response.Content.ReadAsStreamAsync(); - return new ValorantAPIManifestV1(responseStream, directoryInfo); - } - catch - { - return null; - } - } - } - - public class ValorantAPIManifestV2 - { - private const string _url = "https://fmodel.fortnite-api.com/valorant/v2/manifest"; - - private readonly HttpClient _client; - private readonly DirectoryInfo _chunkDirectory; - - public readonly ValorantAPIManifestHeaderV2 Header; - public readonly ValorantChunkV2[] Chunks; - public readonly ValorantPakV2[] Paks; - - public ValorantAPIManifestV2(byte[] data, DirectoryInfo directoryInfo) : this(new MemoryStream(data, false), directoryInfo) { } - public ValorantAPIManifestV2(Stream stream, DirectoryInfo directoryInfo) : this(new BinaryReader(stream), directoryInfo) { } - public ValorantAPIManifestV2(BinaryReader reader, DirectoryInfo directoryInfo) - { - using (reader) - { - Header = new ValorantAPIManifestHeaderV2(reader); - - var compressedBuffer = reader.ReadBytes((int)Header.CompressedSize); - var uncompressedBuffer = ZlibStream.UncompressBuffer(compressedBuffer); - - if (uncompressedBuffer.Length != Header.UncompressedSize) - { - throw new FileLoadException("invalid decompressed manifest body"); - } - - using var bodyMs = new MemoryStream(uncompressedBuffer, false); - using var bodyReader = new BinaryReader(bodyMs); - - Chunks = new ValorantChunkV2[Header.ChunkCount]; - - for (var i = 0u; i < Header.ChunkCount; i++) - { - Chunks[i] = new ValorantChunkV2(bodyReader); - } - - Paks = new ValorantPakV2[Header.PakCount]; - - for (var i = 0u; i < Header.PakCount; i++) - { - Paks[i] = new ValorantPakV2(bodyReader); - } - } - - _client = new HttpClient(new HttpClientHandler - { - UseProxy = false, - UseCookies = false, - AutomaticDecompression = DecompressionMethods.All, - CheckCertificateRevocationList = false, - PreAuthenticate = false, - MaxConnectionsPerServer = 1337, - UseDefaultCredentials = false, - AllowAutoRedirect = false - }); - _chunkDirectory = directoryInfo; - } - - public Stream GetPakStream(int index) - { - return new ValorantPakV2Stream(this, index); - } - - public async Task PrefetchChunk(ValorantChunkV2 chunk, CancellationToken cancellationToken) - { - var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk"); - - if (File.Exists(chunkPath)) - { - return; - } - - using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url); - using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.OK) - { - var chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); - await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false); - } - #if DEBUG - else - { - var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Debugger.Break(); - } - #endif - } - - public async Task GetChunkBytes(ValorantChunkV2 chunk, CancellationToken cancellationToken) - { - var chunkPath = Path.Combine(_chunkDirectory.FullName, $"{chunk.Id}.valchunk"); - byte[] chunkBytes; - - if (File.Exists(chunkPath)) - { - chunkBytes = new byte[chunk.Size]; - await using var fs = new FileStream(chunkPath, FileMode.Open, FileAccess.Read, FileShare.Read); - await fs.ReadAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false); - } - else - { - using var request = new HttpRequestMessage(HttpMethod.Get, chunk.Url); - using var response = await _client.SendAsync(request, cancellationToken).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.OK) - { - chunkBytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); - await using var fs = new FileStream(chunkPath, FileMode.Create, FileAccess.Write, FileShare.Read); - await fs.WriteAsync(chunkBytes, 0, chunkBytes.Length, cancellationToken).ConfigureAwait(false); - } - else - { - #if DEBUG - var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Debugger.Break(); - #endif - chunkBytes = null; - } - } - - return chunkBytes; - } - - public static async Task DownloadAndParse(DirectoryInfo directoryInfo) - { - using var client = new HttpClient(new HttpClientHandler - { - UseProxy = false, - UseCookies = false, - AutomaticDecompression = DecompressionMethods.All, - CheckCertificateRevocationList = false, - PreAuthenticate = false - }); - using var request = new HttpRequestMessage(HttpMethod.Get, _url); - - try - { - using var response = await client.SendAsync(request).ConfigureAwait(false); - - if (response.StatusCode != HttpStatusCode.OK) - { - return null; - } - - var responseStream = await response.Content.ReadAsStreamAsync(); - return new ValorantAPIManifestV2(responseStream, directoryInfo); - } - catch - { - return null; - } - } - } - - public readonly struct ValorantAPIManifestHeaderV2 - { - public const uint MAGIC = 0xC3D088F7u; - - public readonly uint Magic; - public readonly uint HeaderSize; - public readonly ulong ManifestId; - public readonly uint UncompressedSize; - public readonly uint CompressedSize; - public readonly uint ChunkCount; - public readonly uint PakCount; - public readonly string GameVersion; - - public ValorantAPIManifestHeaderV2(BinaryReader reader) - { - Magic = reader.ReadUInt32(); - - if (Magic != MAGIC) - { - throw new FileLoadException("invalid manifest magic"); - } - - HeaderSize = reader.ReadUInt32(); - ManifestId = reader.ReadUInt64(); - UncompressedSize = reader.ReadUInt32(); - CompressedSize = reader.ReadUInt32(); - ChunkCount = reader.ReadUInt32(); - PakCount = reader.ReadUInt32(); - - var gameVersionLength = (int)reader.ReadByte(); - if (gameVersionLength == 0) - { - GameVersion = null; - } - else - { - var gameVersionBuffer = reader.ReadBytes(gameVersionLength); - GameVersion = Encoding.ASCII.GetString(gameVersionBuffer); - } - - reader.BaseStream.Position = HeaderSize; - } - } - - public readonly struct ValorantChunkV1 - { - private const string _baseUrl = "https://fmodel.fortnite-api.com/valorant/v1/chunks/"; - - public readonly ulong Id; - public readonly uint Size; - public string Url => _baseUrl + Id; - - public ValorantChunkV1(BinaryReader reader) - { - Id = reader.ReadUInt64(); - Size = reader.ReadUInt32(); - } - - public override string ToString() - { - return $"{Id:X8} | {Strings.GetReadableSize(Size)}"; - } - } - - public readonly struct ValorantChunkV2 - { - private const string _baseUrl = "https://fmodel.fortnite-api.com/valorant/v2/chunks/"; - - public readonly ulong Id; - public readonly uint Size; - public string Url => $"{_baseUrl}{Id}"; - - public ValorantChunkV2(BinaryReader reader) - { - Id = reader.ReadUInt64(); - Size = reader.ReadUInt32(); - } - - public override string ToString() - { - return $"{Id:X8} | {Strings.GetReadableSize(Size)}"; - } - } - - public readonly struct ValorantPakV1 - { - public readonly ulong Id; - public readonly uint Size; - public readonly string Name; - public readonly ulong[] ChunkIds; - - public ValorantPakV1(BinaryReader reader) - { - Id = reader.ReadUInt64(); - Size = reader.ReadUInt32(); - var nameLength = reader.ReadInt32(); - var nameBytes = reader.ReadBytes(nameLength); - Name = Encoding.ASCII.GetString(nameBytes); - ChunkIds = new ulong[reader.ReadInt32()]; - - for (var i = 0; i < ChunkIds.Length; i++) - { - ChunkIds[i] = reader.ReadUInt64(); - } - } - - public override string ToString() - { - return $"{Name} | {Strings.GetReadableSize(Size)}"; - } - } - - public readonly struct ValorantPakV2 - { - public readonly ulong Id; - public readonly uint Size; - public readonly uint[] ChunkIndices; - public readonly string Name; - - public ValorantPakV2(BinaryReader reader) - { - Id = reader.ReadUInt64(); - Size = reader.ReadUInt32(); - - var chunkIndicesLength = reader.ReadUInt32(); - ChunkIndices = new uint[chunkIndicesLength]; - for (uint i = 0; i < chunkIndicesLength; i++) - { - ChunkIndices[i] = reader.ReadUInt32(); - } - - var nameLength = (int)reader.ReadByte(); - var nameBytes = reader.ReadBytes(nameLength); - Name = Encoding.ASCII.GetString(nameBytes); - } - - public override string ToString() - { - return $"{Name} | {Strings.GetReadableSize(Size)}"; - } - } - - public class ValorantPakV1Stream : Stream - { - public override bool CanRead { get; } = true; - public override bool CanSeek { get; } = true; - public override bool CanWrite { get; } = false; - public override long Length { get; } - - private long _position; - - public override long Position - { - get => _position; - set - { - if (value >= Length || value < 0) - { - throw new ArgumentOutOfRangeException(); - } - - _position = value; - } - } - - public string FileName { get; } - private readonly ValorantAPIManifestV1 _manifest; - private readonly ValorantChunkV1[] _chunks; - - public ValorantPakV1Stream(ValorantAPIManifestV1 manifest, int pakIndex) - { - _manifest = manifest; - var pak = manifest.Paks[pakIndex]; - FileName = pak.Name; - Length = pak.Size; - _chunks = new ValorantChunkV1[pak.ChunkIds.Length]; - - for (var i = 0; i < _chunks.Length; i++) - { - _chunks[i] = manifest.Chunks[pak.ChunkIds[i]]; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); - } - - public async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4) - { - var tasks = new List(); - var sem = new SemaphoreSlim(concurrentDownloads); - - while (count > 0) - { - await sem.WaitAsync(cancellationToken).ConfigureAwait(false); - var chunk = _chunks[i++]; - tasks.Add(PrefetchChunkAsync(chunk)); - - if (i == _chunks.Length) - { - break; - } - - count -= chunk.Size - startPos; - startPos = 0u; - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - sem.Dispose(); - - async Task PrefetchChunkAsync(ValorantChunkV1 chunk) - { - await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false); - sem.Release(); - } - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var (i, startPos) = GetChunkIndex(_position); - - if (i == -1) - { - return 0; - } - - await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false); - var bytesRead = 0; - - while (true) - { - var chunk = _chunks[i]; - var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false); - - var chunkBytes = chunk.Size - startPos; - var bytesLeft = count - bytesRead; - - if (bytesLeft <= chunkBytes) - { - Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft); - bytesRead += bytesLeft; - break; - } - - Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes); - bytesRead += (int)chunkBytes; - startPos = 0u; - - if (++i == _chunks.Length) - { - break; - } - } - - _position += bytesRead; - return bytesRead; - } - - private (int Index, uint ChunkPos) GetChunkIndex(long position) - { - for (var i = 0; i < _chunks.Length; i++) - { - var size = _chunks[i].Size; - - if (position < size) - { - return (i, (uint)position); - } - - position -= size; - } - - return (-1, 0u); - } - - public override long Seek(long offset, SeekOrigin origin) - { - Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => offset + _position, - SeekOrigin.End => Length + offset, - _ => throw new ArgumentOutOfRangeException() - }; - return _position; - } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } - - public class ValorantPakV2Stream : Stream - { - public override bool CanRead { get; } = true; - public override bool CanSeek { get; } = true; - public override bool CanWrite { get; } = false; - public override long Length { get; } - - private long _position; - - public override long Position - { - get => _position; - set - { - if (value >= Length || value < 0) - { - throw new ArgumentOutOfRangeException(); - } - - _position = value; - } - } - - public string FileName { get; } - private readonly ValorantAPIManifestV2 _manifest; - private readonly ValorantChunkV2[] _chunks; - - public ValorantPakV2Stream(ValorantAPIManifestV2 manifest, int pakIndex) - { - _manifest = manifest; - var pak = manifest.Paks[pakIndex]; - FileName = pak.Name; - Length = pak.Size; - - _chunks = new ValorantChunkV2[pak.ChunkIndices.Length]; - for (var i = 0; i < pak.ChunkIndices.Length; i++) - { - _chunks[i] = manifest.Chunks[pak.ChunkIndices[i]]; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); - } - - public async Task PrefetchAsync(int i, uint startPos, long count, CancellationToken cancellationToken, int concurrentDownloads = 4) - { - var tasks = new List(); - var sem = new SemaphoreSlim(concurrentDownloads); - - while (count > 0) - { - await sem.WaitAsync(cancellationToken).ConfigureAwait(false); - var chunk = _chunks[i++]; - tasks.Add(PrefetchChunkAsync(chunk)); - - if (i == _chunks.Length) - { - break; - } - - count -= chunk.Size - startPos; - startPos = 0u; - } - - await Task.WhenAll(tasks).ConfigureAwait(false); - sem.Dispose(); - - async Task PrefetchChunkAsync(ValorantChunkV2 chunk) - { - await _manifest.PrefetchChunk(chunk, cancellationToken).ConfigureAwait(false); - sem.Release(); - } - } - - public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - var (i, startPos) = GetChunkIndex(_position); - - if (i == -1) - { - return 0; - } - - await PrefetchAsync(i, startPos, count, cancellationToken).ConfigureAwait(false); - var bytesRead = 0; - - while (true) - { - var chunk = _chunks[i]; - var chunkData = await _manifest.GetChunkBytes(chunk, cancellationToken).ConfigureAwait(false); - - var chunkBytes = chunk.Size - startPos; - var bytesLeft = count - bytesRead; - - if (bytesLeft <= chunkBytes) - { - Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], (uint)bytesLeft); - bytesRead += bytesLeft; - break; - } - - Unsafe.CopyBlockUnaligned(ref buffer[bytesRead + offset], ref chunkData[startPos], chunkBytes); - bytesRead += (int)chunkBytes; - startPos = 0u; - - if (++i == _chunks.Length) - { - break; - } - } - - _position += bytesRead; - return bytesRead; - } - - private (int Index, uint ChunkPos) GetChunkIndex(long position) - { - for (var i = 0; i < _chunks.Length; i++) - { - var size = _chunks[i].Size; - - if (position < size) - { - return (i, (uint)position); - } - - position -= size; - } - - return (-1, 0u); - } - - public override long Seek(long offset, SeekOrigin origin) - { - Position = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.Current => offset + _position, - SeekOrigin.End => Length + offset, - _ => throw new ArgumentOutOfRangeException() - }; - return _position; - } - - public override void Flush() - { - throw new NotSupportedException(); - } - - public override void SetLength(long value) - { - throw new NotSupportedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } - } -} \ No newline at end of file diff --git a/FModel/Grabber/Mappings/MappingsData.cs b/FModel/Grabber/Mappings/MappingsData.cs deleted file mode 100644 index d301fbd9..00000000 --- a/FModel/Grabber/Mappings/MappingsData.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FModel.Logger; -using FModel.Utils; -using FModel.Windows.CustomNotifier; -using Newtonsoft.Json; -using System.Net.NetworkInformation; -using System.Threading.Tasks; - -namespace FModel.Grabber.Mappings -{ - static class MappingsData - { - public static async Task GetData() - { - if (NetworkInterface.GetIsNetworkAvailable()) - { - Mapping[] data = await Endpoints.GetJsonEndpoint(Endpoints.BENBOT_MAPPINGS, string.Empty).ConfigureAwait(false); - return data; - } - else - { - Globals.gNotifier.ShowCustomMessage("Mappings", Properties.Resources.NoInternet, "/FModel;component/Resources/wifi-strength-off.ico"); - DebugHelper.WriteLine("{0} {1} {2}", "[FModel]", "[Mappings]", "No internet"); - return null; - } - } - } - - public class Mapping - { - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("fileName")] - public string FileName { get; set; } - [JsonProperty("hash")] - public string Hash { get; set; } - [JsonProperty("length")] - public string Length { get; set; } - [JsonProperty("uploaded")] - public string Uploaded { get; set; } - [JsonProperty("meta")] - public Metadata Meta { get; set; } - } - public class Metadata - { - [JsonProperty("version")] - public string Version { get; set; } - [JsonProperty("compressionMethod")] - public string CompressionMethod { get; set; } - } -} diff --git a/FModel/Grabber/Mappings/MappingsGrabber.cs b/FModel/Grabber/Mappings/MappingsGrabber.cs deleted file mode 100644 index 4d744e7b..00000000 --- a/FModel/Grabber/Mappings/MappingsGrabber.cs +++ /dev/null @@ -1,57 +0,0 @@ -using FModel.Utils; -using System; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using UsmapNET.Classes; - -namespace FModel.Grabber.Mappings -{ - static class MappingsGrabber - { - public static async Task Load(bool forceReload = false) - { - if (Globals.Game.ActualGame == EGame.Fortnite) - { - Mapping[] benMappings = await MappingsData.GetData().ConfigureAwait(false); - if (benMappings != null) - { - foreach (Mapping mapping in benMappings) - { - if (mapping.Meta.CompressionMethod == "Brotli") - { - DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")); - string mappingPath = Path.Combine(chunksDir.FullName, mapping.FileName); - - byte[] mappingsData; - if (!forceReload && File.Exists(mappingPath)) - { - mappingsData = await File.ReadAllBytesAsync(mappingPath); - } - else - { - mappingsData = await Endpoints.GetRawDataAsync(new Uri(mapping.Url)).ConfigureAwait(false); - await File.WriteAllBytesAsync(mappingPath, mappingsData).ConfigureAwait(false); - } - - FConsole.AppendText($"Mappings pulled from {mapping.FileName}", FColors.Yellow, true); - Globals.Usmap = new Usmap(mappingsData); - return true; - } - } - } - - var latestUsmaps = new DirectoryInfo(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")).GetFiles("*_br.usmap"); - if (Globals.Usmap == null && latestUsmaps.Length > 0) - { - var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last(); - byte[] mappingsData = await File.ReadAllBytesAsync(latestUsmapInfo.FullName); - FConsole.AppendText($"Mappings pulled from {latestUsmapInfo.Name}", FColors.Yellow, true); - Globals.Usmap = new Usmap(mappingsData); - return true; - } - } - return false; - } - } -} diff --git a/FModel/Grabber/Paks/InstallsJson.cs b/FModel/Grabber/Paks/InstallsJson.cs deleted file mode 100644 index dc91922d..00000000 --- a/FModel/Grabber/Paks/InstallsJson.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; - -namespace FModel.Grabber.Paks -{ - public class InstallsJson - { - [JsonProperty("associated_client")] - public Dictionary AssociatedClient; - } -} diff --git a/FModel/Grabber/Paks/LauncherDat.cs b/FModel/Grabber/Paks/LauncherDat.cs deleted file mode 100644 index c5db7c8b..00000000 --- a/FModel/Grabber/Paks/LauncherDat.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace FModel.Grabber.Paks -{ - public class LauncherDat - { - public InstallationList[] InstallationList; - } - - public class InstallationList - { - public string InstallLocation; - public string AppName; - public string AppVersion; - } -} diff --git a/FModel/Grabber/Paks/LauncherSettings.cs b/FModel/Grabber/Paks/LauncherSettings.cs deleted file mode 100644 index 458798fd..00000000 --- a/FModel/Grabber/Paks/LauncherSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FModel.Grabber.Paks -{ - public class LauncherSettings - { - public string productLibraryDir; - } -} \ No newline at end of file diff --git a/FModel/Grabber/Paks/PaksGrabber.cs b/FModel/Grabber/Paks/PaksGrabber.cs deleted file mode 100644 index 71950b2e..00000000 --- a/FModel/Grabber/Paks/PaksGrabber.cs +++ /dev/null @@ -1,289 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media.Imaging; -using EpicManifestParser.Objects; -using FModel.Grabber.Manifests; -using FModel.Logger; -using FModel.PakReader.IO; -using FModel.PakReader.Pak; -using FModel.Utils; -using FModel.ViewModels.MenuItem; -using FModel.Windows.Launcher; - -namespace FModel.Grabber.Paks -{ - static class PaksGrabber - { - private static readonly Regex _pakFileRegex = new Regex(@"FortniteGame(/|\\)Content(/|\\)Paks(/|\\)(pakchunk(?:0|10.*|\w+)-WindowsClient|global)\.(pak|ucas)$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - - public static async Task PopulateMenu() - { - await Application.Current.Dispatcher.InvokeAsync(delegate - { - PopulateBase(); - }); - - await Task.Run(async () => - { - if (string.IsNullOrEmpty(Properties.Settings.Default.PakPath)) - { - await Application.Current.Dispatcher.InvokeAsync(delegate - { - var launcher = new FLauncher(); - bool? result = launcher.ShowDialog(); - if (result.HasValue && result.Value) - { - Properties.Settings.Default.PakPath = launcher.Path; - Properties.Settings.Default.Save(); - } - }); - } - - // Add Pak Files - if (Properties.Settings.Default.PakPath.EndsWith("-fn.manifest")) - { - ManifestInfo manifestInfo = await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false); - - if (manifestInfo == null) - { - throw new Exception("Failed to load latest manifest."); - } - - DirectoryInfo chunksDir = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")); - string manifestPath = Path.Combine(chunksDir.FullName, manifestInfo.Filename); - byte[] manifestData; - - if (File.Exists(manifestPath)) - { - manifestData = await File.ReadAllBytesAsync(manifestPath); - } - else - { - manifestData = await manifestInfo.DownloadManifestDataAsync().ConfigureAwait(false); - await File.WriteAllBytesAsync(manifestPath, manifestData).ConfigureAwait(false); - } - - Manifest manifest = new Manifest(manifestData, new ManifestOptions - { - ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/CloudDir/ChunksV3/", UriKind.Absolute), - ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks")) - }); - int pakFiles = 0; - - foreach (FileManifest fileManifest in manifest.FileManifests) - { - if (!_pakFileRegex.IsMatch(fileManifest.Name)) - { - continue; - } - - var pakStream = fileManifest.GetStream(); - if (pakStream.Length == 365) continue; - - var pakFileName = fileManifest.Name.Replace('/', '\\'); - if (pakFileName.EndsWith(".pak")) - { - PakFileReader pakFile = new PakFileReader(pakFileName, pakStream); - if (pakFiles++ == 0) - { - // define the current game thank to the pak path - Folders.SetGameName(pakFileName); - - Globals.Game.Version = pakFile.Info.Version; - Globals.Game.SubVersion = pakFile.Info.SubVersion; - } - - await Application.Current.Dispatcher.InvokeAsync(delegate - { - MenuItems.pakFiles.Add(new PakMenuItemViewModel - { - PakFile = pakFile, - IsEnabled = false - }); - }); - } - else if (pakFileName.EndsWith(".ucas")) - { - var utocStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".ucas", ".utoc"))); - var ioStore = new FFileIoStoreReader(pakFileName.SubstringAfterLast('\\'), pakFileName.SubstringBeforeLast('\\'), utocStream.GetStream(), pakStream); - await Application.Current.Dispatcher.InvokeAsync(delegate - { - MenuItems.pakFiles.Add(new PakMenuItemViewModel - { - IoStore = ioStore, - IsEnabled = false - }); - }); - } - } - - FConsole.AppendText($"Fortnite-Manifest version: {manifest.Version}-{manifest.CL}", FColors.Yellow, true); - } - else if (Properties.Settings.Default.PakPath.EndsWith("-val.manifest")) - { - //var manifest = await ValorantAPIManifestV1.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false); - var manifest = await ValorantAPIManifestV2.DownloadAndParse(Directory.CreateDirectory(Path.Combine(Properties.Settings.Default.OutputPath, "PakChunks"))).ConfigureAwait(false); - - if (manifest == null) - { - throw new Exception("Failed to load latest manifest."); - } - - for (var i = 0; i < manifest.Paks.Length; i++) - { - var pak = manifest.Paks[i]; - var pakFileName = @$"ShooterGame\Content\Paks\{pak.Name}"; - var pakFile = new PakFileReader(pakFileName, manifest.GetPakStream(i)); - - if (i == 0) - { - Folders.SetGame(EGame.Valorant); - Globals.Game.Version = pakFile.Info.Version; - Globals.Game.SubVersion = pakFile.Info.SubVersion; - } - - await Application.Current.Dispatcher.InvokeAsync(delegate - { - MenuItems.pakFiles.Add(new PakMenuItemViewModel - { - PakFile = pakFile, - IsEnabled = false - }); - }); - } - - FConsole.AppendText($"Valorant-Manifest version: {manifest.Header.GameVersion ?? "Unknown"}", FColors.Yellow, true); - } - else if (Directory.Exists(Properties.Settings.Default.PakPath)) - { - // define the current game thank to the pak path - Folders.SetGameName(Properties.Settings.Default.PakPath); - - // paks - string[] paks = Directory.GetFiles(Properties.Settings.Default.PakPath); - for (int i = 0; i < paks.Length; i++) - { - var pakInfo = new FileInfo(paks[i]); - if (pakInfo.Length == 365) - { - continue; - } - - if (!Utils.Paks.IsFileReadLocked(pakInfo)) - { - if (paks[i].EndsWith(".pak")) - { - PakFileReader pakFile = new PakFileReader(paks[i]); - DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Registering]", $"{pakFile.FileName} with GUID {pakFile.Info.EncryptionKeyGuid.Hex}"); - if (i == 0) - { - Globals.Game.Version = pakFile.Info.Version; - Globals.Game.SubVersion = pakFile.Info.SubVersion; - } - - await Application.Current.Dispatcher.InvokeAsync(delegate - { - MenuItems.pakFiles.Add(new PakMenuItemViewModel - { - PakFile = pakFile, - IsEnabled = false - }); - }); - } - else if (paks[i].EndsWith(".ucas")) - { - var utoc = paks[i].Replace(".ucas", ".utoc"); - if (!Utils.Paks.IsFileReadLocked(new FileInfo(utoc))) - { - var utocStream = File.OpenRead(utoc); - var ucasStream = File.OpenRead(paks[i]); - var ioStore = new FFileIoStoreReader(paks[i].SubstringAfterLast('\\'), paks[i].SubstringBeforeLast('\\'), utocStream, ucasStream); - DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Registering]", $"{ioStore.FileName} with GUID {ioStore.TocResource.Header.EncryptionKeyGuid.Hex}"); - - await Application.Current.Dispatcher.InvokeAsync(delegate - { - MenuItems.pakFiles.Add(new PakMenuItemViewModel - { - IoStore = ioStore, - IsEnabled = false - }); - }); - } - else - { - FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(utoc)), FColors.Red, true); - DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[IO Store]", "[Locked]", utoc); - } - } - } - else - { - FConsole.AppendText(string.Format(Properties.Resources.PakFileLocked, Path.GetFileNameWithoutExtension(paks[i])), FColors.Red, true); - DebugHelper.WriteLine("{0} {1} {2} {3}", "[FModel]", "[PAK]", "[Locked]", paks[i]); - } - } - } - }); - } - - private static void PopulateBase() - { - // Loading Mode - PakMenuItemViewModel parent = new PakMenuItemViewModel - { - Header = $"{Properties.Resources.LoadingMode} 🠞 {Properties.Resources.Default}", - Icon = new Image { Source = new BitmapImage(new Uri("Resources/progress-download.png", UriKind.Relative)) } - }; - parent.Childrens = new ObservableCollection - { - new PakMenuItemViewModel // Default Mode - { - Header = Properties.Resources.Default, - Parent = parent, - IsCheckable = true, - IsChecked = true, - StaysOpenOnClick = true - }, - new PakMenuItemViewModel - { - Header = Properties.Resources.NewFiles, - Parent = parent, - IsCheckable = true, - StaysOpenOnClick = true - }, - new PakMenuItemViewModel - { - Header = Properties.Resources.ModifiedFiles, - Parent = parent, - IsCheckable = true, - StaysOpenOnClick = true - }, - new PakMenuItemViewModel - { - Header = Properties.Resources.NewModifiedFiles, - Parent = parent, - IsCheckable = true, - StaysOpenOnClick = true - } - }; - MenuItems.pakFiles.Add(parent); - - // Load All - MenuItems.pakFiles.Add(new PakMenuItemViewModel - { - Header = Properties.Resources.LoadAll, - Icon = new Image { Source = new BitmapImage(new Uri("Resources/folder-download.png", UriKind.Relative)) }, - IsEnabled = false - }); - - // Separator - MenuItems.pakFiles.Add(new Separator { }); - } - } -} \ No newline at end of file diff --git a/FModel/Helper.cs b/FModel/Helper.cs new file mode 100644 index 00000000..ddee04f2 --- /dev/null +++ b/FModel/Helper.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows; + +namespace FModel +{ + public static class Helper + { + [StructLayout(LayoutKind.Explicit)] + private struct NanUnion + { + [FieldOffset(0)] + internal double DoubleValue; + [FieldOffset(0)] + internal readonly ulong UlongValue; + } + + public static void OpenWindow(string windowName, Action action) where T : Window + { + if (!IsWindowOpen(windowName)) + { + action(); + } + else + { + var w = GetOpenedWindow(windowName); + if (windowName == "Search View") w.WindowState = WindowState.Normal; + w.Focus(); + } + } + + public static T GetWindow(string windowName, Action action) where T : Window + { + if (!IsWindowOpen(windowName)) + { + action(); + } + + var ret = (T) GetOpenedWindow(windowName); + ret.Focus(); + ret.Activate(); + return ret; + } + + public static void CloseWindow(string windowName) where T : Window + { + if (!IsWindowOpen(windowName)) return; + GetOpenedWindow(windowName).Close(); + } + + private static bool IsWindowOpen(string name = "") where T : Window + { + return string.IsNullOrEmpty(name) + ? Application.Current.Windows.OfType().Any() + : Application.Current.Windows.OfType().Any(w => w.Title.Equals(name)); + } + + private static Window GetOpenedWindow(string name) where T : Window + { + return Application.Current.Windows.OfType().FirstOrDefault(w => w.Title.Equals(name)); + } + + public static bool IsNaN(double value) + { + var t = new NanUnion {DoubleValue = value}; + var exp = t.UlongValue & 0xfff0000000000000; + var man = t.UlongValue & 0x000fffffffffffff; + return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0; + } + + public static bool AreVirtuallyEqual(double d1, double d2) + { + if (double.IsPositiveInfinity(d1)) + return double.IsPositiveInfinity(d2); + + if (double.IsNegativeInfinity(d1)) + return double.IsNegativeInfinity(d2); + + if (IsNaN(d1)) + return IsNaN(d2); + + var n = d1 - d2; + var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15; + return -d < n && d > n; + } + } +} \ No newline at end of file diff --git a/FModel/Logger/DebugHelper.cs b/FModel/Logger/DebugHelper.cs deleted file mode 100644 index 6a2833c1..00000000 --- a/FModel/Logger/DebugHelper.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Configuration; -using System.Diagnostics; - -namespace FModel.Logger -{ - static class DebugHelper - { - public static Logger Logger { get; private set; } - - public static void Init(string logFilePath) => Logger = new Logger(logFilePath); - - public static void WriteLine(string message = "") - { - if (Logger != null) - Logger.WriteLine(message); - else - Debug.WriteLine(message); - } - - public static void WriteLine(string format, params object[] args) => WriteLine(string.Format(format, args)); - - public static void WriteException(string exception, string message = "Exception") - { - if (Logger != null) - Logger.WriteException(exception, message); - else - Debug.WriteLine(exception); - } - - public static void WriteException(Exception exception, string message = "Exception") => WriteException(exception.ToString(), message); - - public static void WriteUserSettings() - { - foreach (SettingsProperty currentProperty in Properties.Settings.Default.Properties) - { - WriteLine("{0} {1} {2} {3}", "[FModel]", "[User Settings]", $"[{currentProperty.Name}]", Properties.Settings.Default[currentProperty.Name]); - } - } - } -} diff --git a/FModel/Logger/Logger.cs b/FModel/Logger/Logger.cs deleted file mode 100644 index 50d22a49..00000000 --- a/FModel/Logger/Logger.cs +++ /dev/null @@ -1,178 +0,0 @@ -using FModel.Windows.DarkMessageBox; -using Microsoft.Win32; -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading.Tasks; -using System.Windows; - -namespace FModel.Logger -{ - class Logger - { - public delegate void MessageAddedEventHandler(string message); - - public event MessageAddedEventHandler MessageAdded; - - public string MessageFormat { get; set; } = "{0:yyyy-MM-dd HH:mm:ss.fff} - {1}"; - public bool AsyncWrite { get; set; } = true; - public bool DebugWrite { get; set; } = Globals.Build == EFModel.Debug; - public bool StringWrite { get; set; } = true; - public bool FileWrite { get; set; } = false; - public string LogFilePath { get; private set; } - - private readonly object loggerLock = new object(); - private readonly ConcurrentQueue messageQueue = new ConcurrentQueue(); - private readonly StringBuilder sbMessages = new StringBuilder(); - - public Logger() - { - } - - public Logger(string logFilePath) - { - FileWrite = true; - LogFilePath = logFilePath; - CreateDirectoryFromFilePath(LogFilePath); - } - - protected void OnMessageAdded(string message) => MessageAdded?.Invoke(message); - - private void ProcessMessageQueue() - { - lock (loggerLock) - { - while (messageQueue.TryDequeue(out string message)) - { - if (DebugWrite) - Debug.Write(message); - - if (StringWrite && sbMessages != null) - sbMessages.Append(message); - - if (FileWrite && !string.IsNullOrEmpty(LogFilePath)) - { - try - { - File.AppendAllText(LogFilePath, message, Encoding.UTF8); - } - catch (Exception e) - { - Debug.WriteLine(e); - } - } - - OnMessageAdded(message); - } - } - } - - public void Write(string message) - { - if (message != null) - { - message = string.Format(MessageFormat, DateTime.Now, message); - messageQueue.Enqueue(message); - - if (AsyncWrite) - Task.Run(() => ProcessMessageQueue()); - else - ProcessMessageQueue(); - } - } - - public void Write(string format, params object[] args) => Write(string.Format(format, args)); - public void WriteLine(string message) => Write(message + Environment.NewLine); - public void WriteLine(string format, params object[] args) => WriteLine(string.Format(format, args)); - public void WriteException(string exception, string message = "Exception") => WriteLine($"{message}:{Environment.NewLine}{exception}"); - public void WriteException(Exception exception, string message = "Exception") => WriteException(exception.ToString(), message); - - public void Clear() - { - lock (loggerLock) - { - if (sbMessages != null) - sbMessages.Clear(); - } - } - - public override string ToString() - { - lock (loggerLock) - { - if (sbMessages != null && sbMessages.Length > 0) - return sbMessages.ToString(); - - return string.Empty; - } - } - - public static void CreateDirectoryFromFilePath(string filePath) - { - if (!string.IsNullOrEmpty(filePath)) - { - string directoryPath = Path.GetDirectoryName(filePath); - CreateDirectoryFromDirectoryPath(directoryPath); - } - } - - public static void CreateDirectoryFromDirectoryPath(string directoryPath) - { - if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath)) - { - try - { - Directory.CreateDirectory(directoryPath); - } - catch (Exception e) - { - DebugHelper.WriteException(e); - DarkMessageBoxHelper.Show(Properties.Resources.CouldNotCreateDirectory + "\r\n\r\n" + e, "FModel - " + Properties.Resources.Error, MessageBoxButton.OK, MessageBoxImage.Error); - } - } - } - - public static string GetOperatingSystemProductName(bool includeBit = false) - { - string productName = null; - - try - { - productName = GetRegistryValue(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", RegistryHive.LocalMachine); - } - catch - { - //hello world :) - } - - if (string.IsNullOrEmpty(productName)) - productName = Environment.OSVersion.VersionString; - - if (includeBit) - { - string bit; - if (Environment.Is64BitOperatingSystem) - bit = "64"; - else - bit = "32"; - - productName = $"{productName} ({bit}-bit)"; - } - - return productName; - } - - public static string GetRegistryValue(string path, string name = null, RegistryHive root = RegistryHive.CurrentUser) - { - using (RegistryKey rk = RegistryKey.OpenBaseKey(root, RegistryView.Default).OpenSubKey(path)) - { - if (rk != null) - return rk.GetValue(name, null) as string; - } - - return null; - } - } -} diff --git a/FModel/MainWindow.xaml b/FModel/MainWindow.xaml index a5b595a6..89289977 100644 --- a/FModel/MainWindow.xaml +++ b/FModel/MainWindow.xaml @@ -1,284 +1,230 @@ - - - - - - - - - - - - - - - - - - - - - - + xmlns:local="clr-namespace:FModel" + xmlns:controls="clr-namespace:FModel.Views.Resources.Controls" + xmlns:converters="clr-namespace:FModel.Views.Resources.Converters" + xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI" + xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI" + xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI" + WindowStartupLocation="CenterScreen" Closing="OnClosing" Loaded="OnLoaded" PreviewKeyDown="OnWindowKeyDown" + Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.85'}" + Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}"> + + + + + + + + + + - + + - - - - + + + - + - + - + - + - + - + - + - + - - + + - + - + - + - + - + - - + + - + - + - - + - + - - - - - - - - - - - - - - + - + - + - + - + - + - + + + + + + + + + + + + + + + + + + - + - + - + + + + - - + - + - + - - + + + - + - + - + - + - + - + - + - + - - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -286,351 +232,562 @@ - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - + + + - - - - - + + + - + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - -