// Copyright Epic Games, Inc. All Rights Reserved. #include "MetaHumanConfig.h" #include "MetaHumanConfigLog.h" #include "CaptureData.h" #if WITH_EDITOR #include "MetaHumanConformer.h" #endif #include "DNAUtils.h" #include "DNAAsset.h" #include "Engine/SkeletalMesh.h" #include "Interfaces/IPluginManager.h" #include "Misc/FileHelper.h" #include "IPlatformCrypto.h" #include "EncryptionContextOpenSSL.h" #include "PlatformCryptoTypes.h" #include "Misc/MessageDialog.h" #include "UObject/UObjectGlobals.h" #include "UObject/Package.h" #include "Serialization/EditorBulkData.h" #include "Serialization/BulkDataReader.h" #include "Serialization/BulkDataWriter.h" #include "MetaHumanFaceTrackerInterface.h" #include "Features/IModularFeatures.h" #include "TrackerOpticalFlowConfiguration.h" #define LOCTEXT_NAMESPACE "MetaHumanConfig" #define USE_BASE_CONFIG_DATA "UseBaseConfigData" static FName CompressionFormatName = TEXT("Zlib"); static int32 BulkDataMask = 64; static void SetBulkData(FByteBulkData& InBulkData, const TArray& InData) { InBulkData.RemoveBulkData(); // Need to clear out any existing data as below is an append operation const bool bIsPersistent = true; FBulkDataWriter Writer(InBulkData, bIsPersistent); Writer << const_cast&>(InData); } static void ResetBulkData(FByteBulkData& InBulkData) { TArray Empty; SetBulkData(InBulkData, Empty); } static TArray ReadBulkData(const FByteBulkData& InBulkData) { TArray Data; const bool bIsPersistent = true; FBulkDataReader Reader(const_cast(InBulkData), bIsPersistent); Reader << Data; return Data; } static void UpgradeEditorBulkData(const UE::Serialization::FEditorBulkData& InEditorData, FByteBulkData& OutBulkData) { if (InEditorData.HasPayloadData()) { TFuture PayloadFuture = InEditorData.GetPayload(); if (PayloadFuture.Get().GetSize() > TNumericLimits::Max()) // Blocking call. Max that can be stored in a TArray { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Payload size too large")); return; } TArray Data; Data.Append((const uint8*)PayloadFuture.Get().GetData(), PayloadFuture.Get().GetSize()); SetBulkData(OutBulkData, Data); } } bool FMetaHumanConfig::GetInfo(UCaptureData* InCaptureData, const FString& InComponent, FString& OutDisplayName) { UMetaHumanConfig* Config; return GetInfo(InCaptureData, InComponent, OutDisplayName, Config); } bool FMetaHumanConfig::GetInfo(UCaptureData* InCaptureData, const FString& InComponent, UMetaHumanConfig*& OutConfig) { FString DisplayName; return GetInfo(InCaptureData, InComponent, DisplayName, OutConfig); } bool FMetaHumanConfig::GetInfo(UCaptureData* InCaptureData, const FString& InComponent, FString& OutDisplayName, UMetaHumanConfig*& OutConfig) { bool bSpecifiedCaptureData = true; FString ConfigAsset; if (InCaptureData) { if (InCaptureData->IsA()) { if (InComponent == TEXT("Solver")) { ConfigAsset = TEXT("stereo_hmc"); } else { ConfigAsset = TEXT("Mesh2MetaHuman"); } OutDisplayName = TEXT("Mesh2MetaHuman"); } else if (InCaptureData->IsA()) { UFootageCaptureData* FootageCaptureData = Cast(InCaptureData); switch (FootageCaptureData->Metadata.DeviceClass) { case EFootageDeviceClass::iPhone11OrEarlier: case EFootageDeviceClass::iPhone12: ConfigAsset = TEXT("iphone12"); break; case EFootageDeviceClass::iPhone13: case EFootageDeviceClass::iPhone14OrLater: case EFootageDeviceClass::OtheriOSDevice: ConfigAsset = TEXT("iphone13"); break; case EFootageDeviceClass::StereoHMC: ConfigAsset = TEXT("stereo_hmc"); break; case EFootageDeviceClass::Unspecified: default: UE_LOG(LogMetaHumanConfig, Warning, TEXT("Unspecified device class, assuming iPhone 13")); ConfigAsset = TEXT("iphone13"); break; }; // Display name is currently the DeviceClass name as text, eg "iPhone 12". In time this maybe more complicated // and use the DeviceModel (eg "iphone13,3") to have a more user friendly display name, eg "iPhone 12 Pro". FText DeviceClassText; UEnum::GetDisplayValueAsText(FootageCaptureData->Metadata.DeviceClass, DeviceClassText); OutDisplayName = DeviceClassText.ToString(); bSpecifiedCaptureData = FootageCaptureData->Metadata.DeviceClass != EFootageDeviceClass::Unspecified; } else { checkf(false, TEXT("Unhandled capture data type")); } } else { // The Identity editor "finalizes" the identity (creates PCA model) upon creation and before any capture data has been set. // In order for this to succeed and not produce any log errors an arbitrary, but valid, config is needed. // Finalize is called again once the identity has been setup and capture data set, so the results of the initial finalize // are never actually used. ConfigAsset = TEXT("stereo_hmc"); OutDisplayName = TEXT(""); bSpecifiedCaptureData = false; } FString PluginContentDir = TEXT("/" UE_PLUGIN_NAME); if (InComponent == TEXT("Solver")) { PluginContentDir += TEXT("/Solver/"); } else { PluginContentDir += TEXT("/MeshFitting/"); } FString Path = PluginContentDir + ConfigAsset + TEXT(".") + ConfigAsset; check(IsInGameThread()); OutConfig = LoadObject(GetTransientPackage(), *Path); return bSpecifiedCaptureData; } static FString FindVersion(const TArray& InVersionLines, const FString& InDeviceName) { for (const FString& VersionLine : InVersionLines) { if (VersionLine.Contains(InDeviceName)) { return FPaths::GetPathLeaf(VersionLine); } } return TEXT(""); } bool UMetaHumanConfig::VerifySolverConfig(const FString& InSolverTemplateDataJson, const FString& InSolverConfigDataJson, const FString& InSolverDefinitionsDataJson, const FString& InSolverHierarchicalDefinitionsDataJson, const FString& InSolverPCAFromDNADataJson, FString& OutErrorString) const { if (IModularFeatures::Get().IsModularFeatureAvailable(IFaceTrackerNodeImplFactory::GetModularFeatureName())) { OutErrorString = TEXT("Please make sure Depth Processing plugin is enabled"); return false; } IFaceTrackerNodeImplFactory& FaceTrackerImplFactory = IModularFeatures::Get().GetModularFeature(IFaceTrackerNodeImplFactory::GetModularFeatureName()); TSharedPtr FaceTracker = FaceTrackerImplFactory.CreateFaceTrackerImplementor(); if (!FaceTracker->Init(InSolverTemplateDataJson, InSolverConfigDataJson, {}, "")) { OutErrorString = TEXT("face tracking config contains invalid data."); return false; } // check the optical flow config auto OpticalFlow = FaceTrackerImplFactory.CreateOpticalFlowImplementor(); if (!OpticalFlow->Init(InSolverConfigDataJson, FString{})) { OutErrorString = TEXT("optical flow part of face tracking config contains invalid data."); return false; } const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT(UE_PLUGIN_NAME))->GetContentDir(); const FString PathToDNA = PluginDir + TEXT("/IdentityTemplate/Face_Archetype.ardna"); TObjectPtr ArchetypeDnaAsset = GetDNAAssetFromFile(PathToDNA, GetTransientPackage()); if (!ArchetypeDnaAsset) { OutErrorString = TEXT("failed to get face archetype DNA"); return false; } #if WITH_EDITOR // check the PCA from DNA data; note this functionality is only available with editor if (!UE::Wrappers::FMetaHumanConformer::CheckPcaModelFromDnaRigConfig(InSolverPCAFromDNADataJson, ArchetypeDnaAsset)) { OutErrorString = TEXT("PCA model from DNA rig config contains invalid data."); return false; } #endif // check the face tracking post processing config TSharedPtr FaceTrackerPostProcessing = FaceTrackerImplFactory.CreateFaceTrackerPostProcessingImplementor(); if (!FaceTrackerPostProcessing->Init(InSolverTemplateDataJson, InSolverConfigDataJson)) { OutErrorString = TEXT("face tracking post-processing config contains invalid data."); return false; } #if WITH_EDITOR // check the solver definitions; note this functionality is only available with editor if (!FaceTrackerPostProcessing->LoadDNA(ArchetypeDnaAsset, InSolverDefinitionsDataJson)) { OutErrorString = TEXT("face tracking solver definitions contains invalid data."); return false; } // check the solver definitions; note this functionality is only available with editor if (!FaceTrackerPostProcessing->LoadDNA(ArchetypeDnaAsset, InSolverHierarchicalDefinitionsDataJson)) { OutErrorString = TEXT("face tracking hierarchical solver definitions contains invalid data."); return false; } #endif return true; } bool UMetaHumanConfig::ReadFromDirectory(const FString& InPath) { EMetaHumanConfigType ConfigType = EMetaHumanConfigType::Unspecified; FString SolverTemplateDataFile = InPath + TEXT("/template_description.json"); FString SolverConfigDataFile = InPath + TEXT("/configuration.json"); FString SolverDefinitionsDataFile = InPath + TEXT("/solver_definitions.json"); FString SolverHierarchicalDefinitionsDataFile = InPath + TEXT("/hierarchical_solver_definitions.json"); FString SolverPCAFromDNADataFile = InPath + TEXT("/pca_from_dna_configuration.json"); FString FittingTemplateDataFile = InPath + TEXT("/template_description.json"); FString FittingConfigDataFile = InPath + TEXT("/configuration_autorig.json"); FString FittingConfigTeethDataFile = InPath + TEXT("/configuration_teeth_fitting.json"); FString FittingIdentityModelDataFile = InPath + TEXT("/dna_database_description.json"); FString FittingControlsDataFile = InPath + TEXT("/controls.json"); FString PredictiveBrowseDataFile = InPath + TEXT("/nnsolver_brows_data.bin"); FString PredictiveEyesDataFile = InPath + TEXT("/nnsolver_eyes_data.bin"); FString PredictiveJawDataFile = InPath + TEXT("/nnsolver_jaw_no_teeth_data.bin"); FString PredictiveLowerDataFile = InPath + TEXT("/nnsolver_lower_data.bin"); if (ConfigType == EMetaHumanConfigType::Unspecified && FPaths::FileExists(SolverTemplateDataFile) && FPaths::FileExists(SolverConfigDataFile) && FPaths::FileExists(SolverDefinitionsDataFile) && FPaths::FileExists(SolverHierarchicalDefinitionsDataFile) && FPaths::FileExists(SolverPCAFromDNADataFile)) { ConfigType = EMetaHumanConfigType::Solver; } if (ConfigType == EMetaHumanConfigType::Unspecified && FPaths::FileExists(FittingTemplateDataFile) && FPaths::FileExists(FittingConfigDataFile) && FPaths::FileExists(FittingConfigTeethDataFile) && FPaths::FileExists(FittingIdentityModelDataFile) && FPaths::FileExists(FittingControlsDataFile)) { ConfigType = EMetaHumanConfigType::Fitting; } if (ConfigType == EMetaHumanConfigType::Unspecified && FPaths::FileExists(PredictiveBrowseDataFile) && FPaths::FileExists(PredictiveEyesDataFile) && FPaths::FileExists(PredictiveJawDataFile) && FPaths::FileExists(PredictiveLowerDataFile)) { ConfigType = EMetaHumanConfigType::PredictiveSolver; } if (ConfigType == EMetaHumanConfigType::Unspecified) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Missing config files", "Missing configuration files")); return false; } FString SolverTemplateDataJson, SolverConfigDataJson, SolverDefinitionsDataJson, SolverHierarchicalDefinitionsDataJson, SolverPCAFromDNADataJson; FString FittingTemplateDataJson, FittingConfigDataJson, FittingConfigTeethDataJson, FittingIdentityModelDataJson, FittingControlsDataJson; TArray GlobalTeethPredictiveSolverTrainingDataMemoryBuffer, PredictiveSolversTrainingDataMemoryBuffer; FString ParentDirectoryName; FString VersionFilename; IFaceTrackerNodeImplFactory& FaceTrackerImplFactory = IModularFeatures::Get().GetModularFeature(IFaceTrackerNodeImplFactory::GetModularFeatureName()); TSharedPtr FaceTracker = FaceTrackerImplFactory.CreateFaceTrackerImplementor(); if (ConfigType == EMetaHumanConfigType::Solver) { if (FaceTracker->CreateFlattenedJsonStringWrapper(SolverTemplateDataFile, SolverTemplateDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *SolverTemplateDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(SolverConfigDataFile, SolverConfigDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *SolverConfigDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(SolverDefinitionsDataFile, SolverDefinitionsDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *SolverDefinitionsDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(SolverHierarchicalDefinitionsDataFile, SolverHierarchicalDefinitionsDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *SolverHierarchicalDefinitionsDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(SolverPCAFromDNADataFile, SolverPCAFromDNADataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *SolverPCAFromDNADataFile); return false; } ParentDirectoryName = FPaths::GetPathLeaf(FPaths::GetPath(InPath)); VersionFilename = InPath + TEXT("/../../../config_versions.txt"); FString ErrorString; if (!VerifySolverConfig(SolverTemplateDataJson, SolverConfigDataJson, SolverDefinitionsDataJson, SolverHierarchicalDefinitionsDataJson, SolverPCAFromDNADataJson, ErrorString)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Solving Config validation error: %s"), *ErrorString); return false; } } else if (ConfigType == EMetaHumanConfigType::Fitting) { if (FaceTracker->CreateFlattenedJsonStringWrapper(FittingTemplateDataFile, FittingTemplateDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *FittingTemplateDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(FittingConfigDataFile, FittingConfigDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *FittingConfigDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(FittingConfigTeethDataFile, FittingConfigTeethDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *FittingConfigTeethDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(FittingIdentityModelDataFile, FittingIdentityModelDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *FittingIdentityModelDataFile); return false; } if (FaceTracker->CreateFlattenedJsonStringWrapper(FittingControlsDataFile, FittingControlsDataJson)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load file %s"), *FittingControlsDataFile); return false; } ParentDirectoryName = FPaths::GetPathLeaf(InPath); VersionFilename = InPath + TEXT("/../../config_versions.txt"); FString ErrorString; if (!VerifyFittingConfig(FittingTemplateDataJson, FittingConfigDataJson, FittingConfigTeethDataJson, FittingIdentityModelDataJson, FittingControlsDataJson, ErrorString)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Fitting Config validation error: %s"), *ErrorString); return false; } } else if (ConfigType == EMetaHumanConfigType::PredictiveSolver) //-V547 { TArray PredictiveSolverDataFiles; PredictiveSolverDataFiles.Push(PredictiveLowerDataFile); PredictiveSolverDataFiles.Push(PredictiveEyesDataFile); PredictiveSolverDataFiles.Push(PredictiveBrowseDataFile); // this also validates the content of the predictive solver training data if (!FaceTracker->LoadPredictiveSolverTrainingDataWrapper(PredictiveJawDataFile, PredictiveSolverDataFiles, GlobalTeethPredictiveSolverTrainingDataMemoryBuffer, PredictiveSolversTrainingDataMemoryBuffer)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to load predictive solver files")); return false; } ParentDirectoryName = FPaths::GetPathLeaf(InPath); VersionFilename = InPath + TEXT("/../../config_versions.txt"); } else { check(false); } if (ParentDirectoryName != TEXT("iphone12") && ParentDirectoryName != TEXT("iphone13") && ParentDirectoryName != TEXT("stereo_hmc") && ParentDirectoryName != TEXT("predictivesolvers") && ParentDirectoryName != TEXT("Mesh2MetaHuman")) { UE_LOG(LogMetaHumanConfig, Warning, TEXT("Unknown directory name %s"), *ParentDirectoryName); } TArray VersionLines; if (FPaths::FileExists(VersionFilename)) { if (!FFileHelper::LoadANSITextFileToStrings(*VersionFilename, nullptr, VersionLines)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to read versions file %s"), *VersionFilename); return false; } } else { UE_LOG(LogMetaHumanConfig, Warning, TEXT("Missing version file %s"), *VersionFilename); } Type = ConfigType; // Version number is a combination of the lower 6 bits to defined the content // OR'd with a bit which represents if the data is stored in FEditorBulkData or FBulkData. // 1 = no compression, editor bulk data // 2 = compressed, editor bulk data // 1 | BulkDataMask = 65 = no compression, bulk data // 2 | BulkDataMask = 66 = compressed, bulk data InternalVersion = (2 | BulkDataMask); if (ParentDirectoryName == TEXT("iphone12")) { Name = TEXT("iPhone 12"); Version = FindVersion(VersionLines, ParentDirectoryName); } else if (ParentDirectoryName == TEXT("iphone13")) { Name = TEXT("iPhone 13"); Version = FindVersion(VersionLines, ParentDirectoryName); } else if (ParentDirectoryName == TEXT("stereo_hmc")) { Name = TEXT("Stereo HMC"); Version = FindVersion(VersionLines, TEXT("hmc")); } else if (ParentDirectoryName == TEXT("predictivesolvers")) { Name = TEXT("Predictive solvers"); Version = FindVersion(VersionLines, TEXT("posed_based_solver")); } else if (ParentDirectoryName == TEXT("Mesh2MetaHuman")) { Name = TEXT("Mesh2MetaHuman"); Version = FindVersion(VersionLines, TEXT("ue_mesh2metahuman")); } else { Name = TEXT("Unknown"); Version = TEXT("Unknown"); } ResetBulkData(SolverTemplateDataCipherText); ResetBulkData(SolverConfigDataCipherText); ResetBulkData(SolverDefinitionsCipherText); ResetBulkData(SolverHierarchicalDefinitionsCipherText); ResetBulkData(SolverPCAFromDNACipherText); ResetBulkData(FittingTemplateDataCipherText); ResetBulkData(FittingConfigDataCipherText); ResetBulkData(FittingConfigTeethDataCipherText); ResetBulkData(FittingIdentityModelDataCipherText); ResetBulkData(FittingControlsDataCipherText); ResetBulkData(PredictiveGlobalTeethTrainingData); ResetBulkData(PredictiveTrainingData); if (ConfigType == EMetaHumanConfigType::Solver || ConfigType == EMetaHumanConfigType::Fitting) { UMetaHumanConfig* BaseConfig = GetBaseConfig(); if (BaseConfig) { if (ConfigType == EMetaHumanConfigType::Solver) { if (SolverTemplateDataFile == BaseConfig->GetSolverTemplateData()) { SolverTemplateDataFile = USE_BASE_CONFIG_DATA; } if (SolverConfigDataFile == BaseConfig->GetSolverConfigData()) { SolverConfigDataFile = USE_BASE_CONFIG_DATA; } if (SolverDefinitionsDataFile == BaseConfig->GetSolverDefinitionsData()) { SolverDefinitionsDataFile = USE_BASE_CONFIG_DATA; } if (SolverHierarchicalDefinitionsDataFile == BaseConfig->GetSolverHierarchicalDefinitionsData()) { SolverHierarchicalDefinitionsDataFile = USE_BASE_CONFIG_DATA; } if (SolverPCAFromDNADataFile == BaseConfig->GetSolverPCAFromDNAData()) { SolverPCAFromDNADataFile = USE_BASE_CONFIG_DATA; } } else if (ConfigType == EMetaHumanConfigType::Fitting) { if (FittingTemplateDataJson == BaseConfig->GetFittingTemplateData()) { FittingTemplateDataJson = USE_BASE_CONFIG_DATA; } if (FittingConfigDataJson == BaseConfig->GetFittingConfigData()) { FittingConfigDataJson = USE_BASE_CONFIG_DATA; } if (FittingConfigTeethDataJson == BaseConfig->GetFittingConfigTeethData()) { FittingConfigTeethDataJson = USE_BASE_CONFIG_DATA; } if (FittingIdentityModelDataJson == BaseConfig->GetFittingIdentityModelData()) { FittingIdentityModelDataJson = USE_BASE_CONFIG_DATA; } if (FittingControlsDataJson == BaseConfig->GetFittingControlsData()) { FittingControlsDataJson = USE_BASE_CONFIG_DATA; } } } } Encrypt(SolverTemplateDataJson, SolverTemplateDataCipherText); Encrypt(SolverConfigDataJson, SolverConfigDataCipherText); Encrypt(SolverDefinitionsDataJson, SolverDefinitionsCipherText); Encrypt(SolverHierarchicalDefinitionsDataJson, SolverHierarchicalDefinitionsCipherText); Encrypt(SolverPCAFromDNADataJson, SolverPCAFromDNACipherText); Encrypt(FittingTemplateDataJson, FittingTemplateDataCipherText); Encrypt(FittingConfigDataJson, FittingConfigDataCipherText); Encrypt(FittingConfigTeethDataJson, FittingConfigTeethDataCipherText); Encrypt(FittingIdentityModelDataJson, FittingIdentityModelDataCipherText); Encrypt(FittingControlsDataJson, FittingControlsDataCipherText); SetBulkData(PredictiveGlobalTeethTrainingData, GlobalTeethPredictiveSolverTrainingDataMemoryBuffer); SetBulkData(PredictiveTrainingData, PredictiveSolversTrainingDataMemoryBuffer); MarkPackageDirty(); return true; } bool UMetaHumanConfig::VerifyFittingConfig(const FString& InFittingTemplateDataJson, const FString& InFittingConfigDataJson, const FString& InFittingConfigTeethDataJson, const FString& InFittingIdentityModelDataJson, const FString& InFittingControlsDataJson, FString& OutErrorString) const { #if WITH_EDITOR // try and instantiate a Fitting object UE::Wrappers::FMetaHumanConformer ConformerNeutral; if (!ConformerNeutral.Init(InFittingTemplateDataJson, InFittingIdentityModelDataJson, InFittingConfigDataJson)) { OutErrorString = TEXT("neutral pose config contains invalid data."); return false; } UE::Wrappers::FMetaHumanConformer ConformerTeeth; if (!ConformerTeeth.Init(InFittingTemplateDataJson, InFittingIdentityModelDataJson, InFittingConfigTeethDataJson)) { OutErrorString = TEXT("teeth pose config contains invalid data."); return false; } if (!ConformerTeeth.CheckControlsConfig(InFittingControlsDataJson)) { OutErrorString = TEXT("fitting controls contains invalid data."); return false; } #endif return true; } FString UMetaHumanConfig::GetSolverTemplateData() const { FString Data = DecryptToString(SolverTemplateDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetSolverTemplateData(); } return Data; } FString UMetaHumanConfig::GetSolverConfigData() const { FString Data = DecryptToString(SolverConfigDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetSolverConfigData(); } return Data; } FString UMetaHumanConfig::GetSolverDefinitionsData() const { FString Data = DecryptToString(SolverDefinitionsCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetSolverDefinitionsData(); } return Data; } FString UMetaHumanConfig::GetSolverHierarchicalDefinitionsData() const { FString Data = DecryptToString(SolverHierarchicalDefinitionsCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetSolverHierarchicalDefinitionsData(); } return Data; } FString UMetaHumanConfig::GetSolverPCAFromDNAData() const { FString Data = DecryptToString(SolverPCAFromDNACipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetSolverPCAFromDNAData(); } return Data; } FString UMetaHumanConfig::GetFittingTemplateData() const { FString Data = DecryptToString(FittingTemplateDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetFittingTemplateData(); } return Data; } FString UMetaHumanConfig::GetFittingConfigData() const { FString Data = DecryptToString(FittingConfigDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetFittingConfigData(); } return Data; } FString UMetaHumanConfig::GetFittingConfigTeethData() const { FString Data = DecryptToString(FittingConfigTeethDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetFittingConfigTeethData(); } return Data; } FString UMetaHumanConfig::GetFittingIdentityModelData() const { FString Data = DecryptToString(FittingIdentityModelDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetFittingIdentityModelData(); } return Data; } FString UMetaHumanConfig::GetFittingControlsData() const { FString Data = DecryptToString(FittingControlsDataCipherText); if (Data == USE_BASE_CONFIG_DATA) { Data = GetBaseConfig()->GetFittingControlsData(); } return Data; } TArray UMetaHumanConfig::GetPredictiveGlobalTeethTrainingData() const { TArray Data; if (PredictiveGlobalTeethTrainingData.GetElementCount() > 0) { if (PredictiveGlobalTeethTrainingData.GetElementCount() > TNumericLimits::Max()) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Payload size too large")); return Data; } Data = ReadBulkData(PredictiveGlobalTeethTrainingData); } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to get PredictiveGlobalTeethTraining payload")); } return Data; } TArray UMetaHumanConfig::GetPredictiveTrainingData() const { TArray Data; if (PredictiveTrainingData.GetElementCount() > 0) { if (PredictiveTrainingData.GetElementCount() > TNumericLimits::Max()) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Payload size too large")); return Data; } Data = ReadBulkData(PredictiveTrainingData); } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to get PredictiveTrainingData payload")); } return Data; } void UMetaHumanConfig::Serialize(FArchive& Ar) { Super::Serialize(Ar); if ((InternalVersion & BulkDataMask) == 0) // Back compatibility case where data is stored in FEditorBulkData - need to move it to FByteBulkData { UE::Serialization::FEditorBulkData SolverTemplateDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData SolverConfigDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData SolverDefinitionsCipherText_DEPRECATED; UE::Serialization::FEditorBulkData SolverHierarchicalDefinitionsCipherText_DEPRECATED; UE::Serialization::FEditorBulkData SolverPCAFromDNACipherText_DEPRECATED; UE::Serialization::FEditorBulkData FittingTemplateDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData FittingConfigDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData FittingConfigTeethDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData FittingIdentityModelDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData FittingControlsDataCipherText_DEPRECATED; UE::Serialization::FEditorBulkData PredictiveGlobalTeethTrainingData_DEPRECATED; UE::Serialization::FEditorBulkData PredictiveTrainingData_DEPRECATED; SolverTemplateDataCipherText_DEPRECATED.Serialize(Ar, this); SolverConfigDataCipherText_DEPRECATED.Serialize(Ar, this); SolverDefinitionsCipherText_DEPRECATED.Serialize(Ar, this); SolverHierarchicalDefinitionsCipherText_DEPRECATED.Serialize(Ar, this); SolverPCAFromDNACipherText_DEPRECATED.Serialize(Ar, this); FittingTemplateDataCipherText_DEPRECATED.Serialize(Ar, this); FittingConfigDataCipherText_DEPRECATED.Serialize(Ar, this); FittingConfigTeethDataCipherText_DEPRECATED.Serialize(Ar, this); FittingIdentityModelDataCipherText_DEPRECATED.Serialize(Ar, this); FittingControlsDataCipherText_DEPRECATED.Serialize(Ar, this); PredictiveGlobalTeethTrainingData_DEPRECATED.Serialize(Ar, this); PredictiveTrainingData_DEPRECATED.Serialize(Ar, this); UpgradeEditorBulkData(SolverTemplateDataCipherText_DEPRECATED, SolverTemplateDataCipherText); UpgradeEditorBulkData(SolverConfigDataCipherText_DEPRECATED, SolverConfigDataCipherText); UpgradeEditorBulkData(SolverDefinitionsCipherText_DEPRECATED, SolverDefinitionsCipherText); UpgradeEditorBulkData(SolverHierarchicalDefinitionsCipherText_DEPRECATED, SolverHierarchicalDefinitionsCipherText); UpgradeEditorBulkData(SolverPCAFromDNACipherText_DEPRECATED, SolverPCAFromDNACipherText); UpgradeEditorBulkData(FittingTemplateDataCipherText_DEPRECATED, FittingTemplateDataCipherText); UpgradeEditorBulkData(FittingConfigDataCipherText_DEPRECATED, FittingConfigDataCipherText); UpgradeEditorBulkData(FittingConfigTeethDataCipherText_DEPRECATED, FittingConfigTeethDataCipherText); UpgradeEditorBulkData(FittingIdentityModelDataCipherText_DEPRECATED, FittingIdentityModelDataCipherText); UpgradeEditorBulkData(FittingControlsDataCipherText_DEPRECATED, FittingControlsDataCipherText); UpgradeEditorBulkData(PredictiveGlobalTeethTrainingData_DEPRECATED, PredictiveGlobalTeethTrainingData); UpgradeEditorBulkData(PredictiveTrainingData_DEPRECATED, PredictiveTrainingData); InternalVersion = (InternalVersion | BulkDataMask); } else { SolverTemplateDataCipherText.Serialize(Ar, this); SolverConfigDataCipherText.Serialize(Ar, this); SolverDefinitionsCipherText.Serialize(Ar, this); SolverHierarchicalDefinitionsCipherText.Serialize(Ar, this); SolverPCAFromDNACipherText.Serialize(Ar, this); FittingTemplateDataCipherText.Serialize(Ar, this); FittingConfigDataCipherText.Serialize(Ar, this); FittingConfigTeethDataCipherText.Serialize(Ar, this); FittingIdentityModelDataCipherText.Serialize(Ar, this); FittingControlsDataCipherText.Serialize(Ar, this); PredictiveGlobalTeethTrainingData.Serialize(Ar, this); PredictiveTrainingData.Serialize(Ar, this); } } bool UMetaHumanConfig::Encrypt(const FString& InPlainText, FByteBulkData& OutCipherText) const { // Encrypting the data is just to stop casual inspection of the plain text json config data. // Is it not meant to hide the data from a determined attacker. // Think more simple data obfuscation than true data encryption. TArray CipherText; FModuleManager::LoadModuleChecked("PlatformCrypto"); TUniquePtr EncryptionContext = IPlatformCrypto::Get().CreateContext(); TArray Key; // Key present in both Encrypt and Decrypt functions Key.SetNumZeroed(32); Key[0] = 'a'; Key[12] = 'L'; Key[2] = 'x'; Key[23] = '*'; TUniquePtr Encryptor = EncryptionContext->CreateEncryptor_AES_256_ECB(Key); TArray PlainText; PlainText.SetNumUninitialized(InPlainText.Len()); StringToBytes(InPlainText, PlainText.GetData(), PlainText.Num()); FCompression Compression; TArray CompressedPlainText; CompressedPlainText.SetNumUninitialized(4 + Compression.CompressMemoryBound(CompressionFormatName, InPlainText.Len())); CompressedPlainText[0] = (InPlainText.Len() >> 24) & 0xFF; CompressedPlainText[1] = (InPlainText.Len() >> 16) & 0xFF; CompressedPlainText[2] = (InPlainText.Len() >> 8) & 0xFF; CompressedPlainText[3] = InPlainText.Len() & 0xFF; int32 CompressedPlainTextSize = CompressedPlainText.Num() - 4; Compression.CompressMemory(CompressionFormatName, &CompressedPlainText.GetData()[4], CompressedPlainTextSize, PlainText.GetData(), PlainText.Num()); CompressedPlainText.SetNum(CompressedPlainTextSize + 4); TArray PartialCipherText; PartialCipherText.SetNumUninitialized(Encryptor->GetUpdateBufferSizeBytes(CompressedPlainText)); int32 PartialCipherTextSize; if (Encryptor->Update(CompressedPlainText, PartialCipherText, PartialCipherTextSize) == EPlatformCryptoResult::Success) { PartialCipherText.SetNum(PartialCipherTextSize); CipherText += PartialCipherText; PartialCipherText.SetNumUninitialized(Encryptor->GetFinalizeBufferSizeBytes()); if (Encryptor->Finalize(PartialCipherText, PartialCipherTextSize) == EPlatformCryptoResult::Success) { PartialCipherText.SetNum(PartialCipherTextSize); CipherText += PartialCipherText; SetBulkData(OutCipherText, CipherText); return true; } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to finalize config encrypt")); } } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to update config encrypt")); } return false; } bool UMetaHumanConfig::Decrypt(const FByteBulkData& InCipherText, FString& OutPlainText) const { // A limit on the data size at each stage - encryted data can not be bigger than this size, nor can compressed or // uncompressed data. This prevents any possible buffer overflow, eg a maliciously modified config asset that would // result in a decrpyted config bigger than the int32 bit limit of a TArray. Keeping the check simple - no part bigger // than 1Gb - since to do it accurately using TNumericLimits::Max would be error prone since the max size of some // stages is less than this to account for headers and cypher block size. constexpr int64 MaxDataSize = 1024 * 1024 * 1024; // 1Gb TArray CipherText; if (InCipherText.GetElementCount() > 0) { if (InCipherText.GetElementCount() > MaxDataSize) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Payload size too large")); return false; } CipherText = ReadBulkData(InCipherText); } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to get payload")); } FModuleManager::LoadModuleChecked("PlatformCrypto"); TUniquePtr EncryptionContext = IPlatformCrypto::Get().CreateContext(); TArray Key; // Key present in both Encrypt and Decrypt functions Key.SetNumZeroed(32); Key[0] = 'a'; Key[12] = 'L'; Key[2] = 'x'; Key[23] = '*'; TUniquePtr Decryptor = EncryptionContext->CreateDecryptor_AES_256_ECB(Key); TArray PartialPlainText; PartialPlainText.SetNumUninitialized(Decryptor->GetUpdateBufferSizeBytes(CipherText)); int32 PartialPlainTextSize; TArray PlainText; if (Decryptor->Update(CipherText, PartialPlainText, PartialPlainTextSize) == EPlatformCryptoResult::Success) { if (PartialPlainTextSize > MaxDataSize || PlainText.Num() + PartialPlainTextSize > MaxDataSize) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("PartialPlainTextSize too large")); return false; } PartialPlainText.SetNum(PartialPlainTextSize); PlainText += PartialPlainText; PartialPlainText.SetNumUninitialized(Decryptor->GetFinalizeBufferSizeBytes()); if (Decryptor->Finalize(PartialPlainText, PartialPlainTextSize) == EPlatformCryptoResult::Success) { if (PartialPlainTextSize > MaxDataSize || PlainText.Num() + PartialPlainTextSize > MaxDataSize) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("PartialPlainTextSize too large")); return false; } PartialPlainText.SetNum(PartialPlainTextSize); PlainText += PartialPlainText; if ((InternalVersion & ~BulkDataMask) == 2) { if (PlainText.Num() < 5) // 4 for size header plus at least 1 for data { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("PlainText too small")); return false; } int32 UncompressedPlainTextSize = 0; UncompressedPlainTextSize += PlainText[0] << 24; UncompressedPlainTextSize += PlainText[1] << 16; UncompressedPlainTextSize += PlainText[2] << 8; UncompressedPlainTextSize += PlainText[3]; if (UncompressedPlainTextSize < 0 || UncompressedPlainTextSize > MaxDataSize) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("UncompressedPlainTextSize bad size")); return false; } FCompression Compression; TArray UncompressedPlainText; UncompressedPlainText.SetNumUninitialized(UncompressedPlainTextSize); if (!Compression.UncompressMemory(CompressionFormatName, UncompressedPlainText.GetData(), UncompressedPlainTextSize, &PlainText.GetData()[4], PlainText.Num() - 4)) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to decompress config")); return false; } OutPlainText = BytesToString(UncompressedPlainText.GetData(), UncompressedPlainText.Num()); } else { OutPlainText = BytesToString(PlainText.GetData(), PlainText.Num()); } return true; } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to finalize config decrypt")); } } else { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed to update config decrypt")); } return false; } FString UMetaHumanConfig::DecryptToString(const FByteBulkData& InCipherText) const { FString PlainText; Decrypt(InCipherText, PlainText); return PlainText; } UMetaHumanConfig* UMetaHumanConfig::GetBaseConfig() const { if (Name == "iPhone 12") { return nullptr; } else { FString Path = FString("/" UE_PLUGIN_NAME "/"); if (Type == EMetaHumanConfigType::Fitting) { Path += TEXT("MeshFitting"); } else if (Type == EMetaHumanConfigType::Solver) { Path += TEXT("Solver"); } else { check(false); } Path += TEXT("/iphone12.iphone12"); UMetaHumanConfig* BaseConfig = LoadObject(GetTransientPackage(), *Path); if (!BaseConfig) { UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed load base config")); } return BaseConfig; } } #undef USE_BASE_CONFIG_DATA #undef LOCTEXT_NAMESPACE