Files
UnrealEngine/Engine/Plugins/MetaHuman/MetaHumanAnimator/Source/MetaHumanConfig/Private/MetaHumanConfig.cpp
2025-05-18 13:04:45 +08:00

1096 lines
36 KiB
C++

// 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<uint8>& 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<TArray<uint8>&>(InData);
}
static void ResetBulkData(FByteBulkData& InBulkData)
{
TArray<uint8> Empty;
SetBulkData(InBulkData, Empty);
}
static TArray<uint8> ReadBulkData(const FByteBulkData& InBulkData)
{
TArray<uint8> Data;
const bool bIsPersistent = true;
FBulkDataReader Reader(const_cast<FByteBulkData&>(InBulkData), bIsPersistent);
Reader << Data;
return Data;
}
static void UpgradeEditorBulkData(const UE::Serialization::FEditorBulkData& InEditorData, FByteBulkData& OutBulkData)
{
if (InEditorData.HasPayloadData())
{
TFuture<FSharedBuffer> PayloadFuture = InEditorData.GetPayload();
if (PayloadFuture.Get().GetSize() > TNumericLimits<int32>::Max()) // Blocking call. Max that can be stored in a TArray
{
UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Payload size too large"));
return;
}
TArray<uint8> 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<UMeshCaptureData>())
{
if (InComponent == TEXT("Solver"))
{
ConfigAsset = TEXT("stereo_hmc");
}
else
{
ConfigAsset = TEXT("Mesh2MetaHuman");
}
OutDisplayName = TEXT("Mesh2MetaHuman");
}
else if (InCaptureData->IsA<UFootageCaptureData>())
{
UFootageCaptureData* FootageCaptureData = Cast<UFootageCaptureData>(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<UMetaHumanConfig>(GetTransientPackage(), *Path);
return bSpecifiedCaptureData;
}
static FString FindVersion(const TArray<FString>& 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>(IFaceTrackerNodeImplFactory::GetModularFeatureName());
TSharedPtr<IMetaHumanFaceTrackerInterface> 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<UDNAAsset> 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<IFaceTrackerPostProcessingInterface> 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<uint8> GlobalTeethPredictiveSolverTrainingDataMemoryBuffer, PredictiveSolversTrainingDataMemoryBuffer;
FString ParentDirectoryName;
FString VersionFilename;
IFaceTrackerNodeImplFactory& FaceTrackerImplFactory = IModularFeatures::Get().GetModularFeature<IFaceTrackerNodeImplFactory>(IFaceTrackerNodeImplFactory::GetModularFeatureName());
TSharedPtr<IMetaHumanFaceTrackerInterface> 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<FString> 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<FString> 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<uint8> UMetaHumanConfig::GetPredictiveGlobalTeethTrainingData() const
{
TArray<uint8> Data;
if (PredictiveGlobalTeethTrainingData.GetElementCount() > 0)
{
if (PredictiveGlobalTeethTrainingData.GetElementCount() > TNumericLimits<int32>::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<uint8> UMetaHumanConfig::GetPredictiveTrainingData() const
{
TArray<uint8> Data;
if (PredictiveTrainingData.GetElementCount() > 0)
{
if (PredictiveTrainingData.GetElementCount() > TNumericLimits<int32>::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<uint8> CipherText;
FModuleManager::LoadModuleChecked<IPlatformCrypto>("PlatformCrypto");
TUniquePtr<FEncryptionContext> EncryptionContext = IPlatformCrypto::Get().CreateContext();
TArray<uint8> Key; // Key present in both Encrypt and Decrypt functions
Key.SetNumZeroed(32);
Key[0] = 'a';
Key[12] = 'L';
Key[2] = 'x';
Key[23] = '*';
TUniquePtr<IPlatformCryptoEncryptor> Encryptor = EncryptionContext->CreateEncryptor_AES_256_ECB(Key);
TArray<uint8> PlainText;
PlainText.SetNumUninitialized(InPlainText.Len());
StringToBytes(InPlainText, PlainText.GetData(), PlainText.Num());
FCompression Compression;
TArray<uint8> 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<uint8> 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<int32>::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<uint8> 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<IPlatformCrypto>("PlatformCrypto");
TUniquePtr<FEncryptionContext> EncryptionContext = IPlatformCrypto::Get().CreateContext();
TArray<uint8> Key; // Key present in both Encrypt and Decrypt functions
Key.SetNumZeroed(32);
Key[0] = 'a';
Key[12] = 'L';
Key[2] = 'x';
Key[23] = '*';
TUniquePtr<IPlatformCryptoDecryptor> Decryptor = EncryptionContext->CreateDecryptor_AES_256_ECB(Key);
TArray<uint8> PartialPlainText;
PartialPlainText.SetNumUninitialized(Decryptor->GetUpdateBufferSizeBytes(CipherText));
int32 PartialPlainTextSize;
TArray<uint8> 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<uint8> 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<UMetaHumanConfig>(GetTransientPackage(), *Path);
if (!BaseConfig)
{
UE_LOG(LogMetaHumanConfig, Fatal, TEXT("Failed load base config"));
}
return BaseConfig;
}
}
#undef USE_BASE_CONFIG_DATA
#undef LOCTEXT_NAMESPACE