// Portage UE 5.5 - voir LPFixAssetRegistryPinsCommandlet.h #include "LPFixAssetRegistryPinsCommandlet.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "K2Node_BreakStruct.h" #include "K2Node_CallFunction.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "LPAssetRegistryHelpers.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" #include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixARPins, Log, All); namespace { const FName DeprecatedClassNamePin(TEXT("ClassName")); const FName DeprecatedObjectPathPin(TEXT("ObjectPath")); const FName AssetDataPin(TEXT("AssetData")); const FName AssetDataStructName(TEXT("AssetData")); const FName GetAssetsByClassFunction(TEXT("GetAssetsByClass")); const FName ReplacementEnumFunction(TEXT("GetAssetsByClassName")); const FName ReplacementPathFunction(TEXT("GetAssetObjectPath")); /** Broche orpheline portant ce nom, s'il y en a une. */ UEdGraphPin* FindOrphan(UEdGraphNode* Node, FName PinName) { for (UEdGraphPin* Pin : Node->Pins) { if (Pin && Pin->bOrphanedPin && Pin->PinName == PinName) { return Pin; } } return nullptr; } } ULPFixAssetRegistryPinsCommandlet::ULPFixAssetRegistryPinsCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixAssetRegistryPinsCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); UFunction* EnumFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(ReplacementEnumFunction); UFunction* PathFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(ReplacementPathFunction); if (!EnumFunction || !PathFunction) { UE_LOG(LogLPFixARPins, Error, TEXT("Fonctions de remplacement introuvables (%s=%s, %s=%s)."), *ReplacementEnumFunction.ToString(), EnumFunction ? TEXT("ok") : TEXT("NULL"), *ReplacementPathFunction.ToString(), PathFunction ? TEXT("ok") : TEXT("NULL")); return 1; } UE_LOG(LogLPFixARPins, Warning, TEXT("=== Broches AssetRegistry perdues a la migration 5.1%s ==="), bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray BlueprintAssets; AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true); int32 EnumFixed = 0; int32 PathFixed = 0; int32 Unhandled = 0; int32 BlueprintsModified = 0; int32 SaveFailures = 0; const UEdGraphSchema_K2* Schema = GetDefault(); for (const FAssetData& AssetData : BlueprintAssets) { if (!AssetData.PackageName.ToString().StartsWith(TEXT("/Game/"))) { continue; } UBlueprint* Blueprint = Cast(AssetData.GetAsset()); if (!Blueprint) { continue; } TArray Graphs; Blueprint->GetAllGraphs(Graphs); int32 FixedHere = 0; for (UEdGraph* Graph : Graphs) { if (!Graph) { continue; } TArray Snapshot = Graph->Nodes; for (UEdGraphNode* GraphNode : Snapshot) { // ------------------------------------------------------------------- // 1. GetAssetsByClass : la valeur de classe est restee sur l'orpheline. // On bascule sur l'enrobage FName, qui la reprend telle quelle. // ------------------------------------------------------------------- if (UK2Node_CallFunction* CallNode = Cast(GraphNode)) { if (CallNode->FunctionReference.GetMemberName() != GetAssetsByClassFunction) { continue; } UEdGraphPin* Orphan = FindOrphan(CallNode, DeprecatedClassNamePin); if (!Orphan) { continue; // deja corrige, ou noeud sain } const FString ClassValue = Orphan->DefaultValue; UE_LOG(LogLPFixARPins, Warning, TEXT(" [enum] %s :: %s -> %s | classe='%s'"), *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), *ClassValue); if (ClassValue.IsEmpty()) { ++Unhandled; UE_LOG(LogLPFixARPins, Error, TEXT(" orpheline vide : rien a reporter, reprise manuelle.")); continue; } if (bDryRun) { continue; } CallNode->Modify(); // La broche orpheline doit disparaitre AVANT la reconstruction, sinon // ReconstructNode la conserve (garde-fou d'UE contre la perte de donnees). Orphan->BreakAllPinLinks(); Orphan->DefaultValue.Empty(); CallNode->SetFromFunction(EnumFunction); CallNode->ReconstructNode(); // La fonction de remplacement a une broche ClassName de meme nom : on y // remet la valeur recuperee. if (UEdGraphPin* NewPin = CallNode->FindPin(DeprecatedClassNamePin, EGPD_Input)) { NewPin->DefaultValue = ClassValue; UE_LOG(LogLPFixARPins, Warning, TEXT(" -> GetAssetsByClassName(ClassName='%s')"), *ClassValue); ++EnumFixed; ++FixedHere; } else { ++Unhandled; UE_LOG(LogLPFixARPins, Error, TEXT(" broche ClassName absente apres reconstruction.")); } continue; } // ------------------------------------------------------------------- // 2. Break AssetData : la sortie ObjectPath n'existe plus. On insere un // appel a GetAssetObjectPath alimente par la meme structure, et on lui // transfere les liaisons restees sur l'orpheline. // ------------------------------------------------------------------- UK2Node_BreakStruct* BreakNode = Cast(GraphNode); if (!BreakNode || !BreakNode->StructType || BreakNode->StructType->GetFName() != AssetDataStructName) { continue; } UEdGraphPin* Orphan = FindOrphan(BreakNode, DeprecatedObjectPathPin); if (!Orphan) { continue; } UEdGraphPin* StructInput = BreakNode->FindPin(AssetDataPin, EGPD_Input); UE_LOG(LogLPFixARPins, Warning, TEXT(" [path] %s :: %s -> %s | %d consommateur(s)"), *Blueprint->GetName(), *Graph->GetName(), *GraphNode->GetName(), Orphan->LinkedTo.Num()); if (Orphan->LinkedTo.Num() == 0) { // Personne ne la lit : la vider suffit a la faire disparaitre. if (!bDryRun) { BreakNode->Modify(); Orphan->DefaultValue.Empty(); BreakNode->ReconstructNode(); ++PathFixed; ++FixedHere; } continue; } if (!StructInput || StructInput->LinkedTo.Num() == 0) { ++Unhandled; UE_LOG(LogLPFixARPins, Error, TEXT(" entree AssetData non connectee : reprise manuelle.")); continue; } if (bDryRun) { continue; } // Noeud de remplacement, place juste sous le Break. UK2Node_CallFunction* PathNode = NewObject(Graph); PathNode->SetFromFunction(PathFunction); PathNode->NodePosX = BreakNode->NodePosX; PathNode->NodePosY = BreakNode->NodePosY + 120; PathNode->CreateNewGuid(); PathNode->PostPlacedNewNode(); PathNode->AllocateDefaultPins(); Graph->Modify(); Graph->AddNode(PathNode, false, false); UEdGraphPin* PathStructIn = PathNode->FindPin(AssetDataPin, EGPD_Input); UEdGraphPin* PathReturn = PathNode->GetReturnValuePin(); bool bOk = PathStructIn && PathReturn; // Meme source de structure que le Break. if (bOk) { UEdGraphPin* Source = StructInput->LinkedTo[0]; bOk = Schema->TryCreateConnection(Source, PathStructIn); if (!bOk) { UE_LOG(LogLPFixARPins, Error, TEXT(" source AssetData incompatible avec la nouvelle entree.")); } } // Transfert des consommateurs de l'orpheline vers la nouvelle sortie. if (bOk) { const FPinConnectionResponse R = Schema->MovePinLinks(*Orphan, *PathReturn); bOk = R.Response != CONNECT_RESPONSE_DISALLOW; if (!bOk) { UE_LOG(LogLPFixARPins, Error, TEXT(" transfert impossible (%s)."), *R.Message.ToString()); } } if (!bOk) { ++Unhandled; PathNode->DestroyNode(); continue; } BreakNode->Modify(); Orphan->BreakAllPinLinks(); Orphan->DefaultValue.Empty(); BreakNode->ReconstructNode(); UE_LOG(LogLPFixARPins, Warning, TEXT(" -> GetAssetObjectPath inserE")); ++PathFixed; ++FixedHere; } } if (FixedHere > 0 && !bDryRun) { ++BlueprintsModified; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); FKismetEditorUtilities::CompileBlueprint(Blueprint); UPackage* Package = Blueprint->GetOutermost(); FString Filename; if (!FPackageName::TryConvertLongPackageNameToFilename( Package->GetName(), Filename, FPackageName::GetAssetPackageExtension())) { ++SaveFailures; UE_LOG(LogLPFixARPins, Error, TEXT(" chemin introuvable pour %s"), *Package->GetName()); continue; } ResetLoaders(Package); FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; SaveArgs.SaveFlags = SAVE_None; SaveArgs.bWarnOfLongFilename = true; // GWarn et non GError : en commandlet, GError rend toute erreur de save fatale. SaveArgs.Error = GWarn; if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) { UE_LOG(LogLPFixARPins, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixARPins, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } } UE_LOG(LogLPFixARPins, Warning, TEXT("=== Termine : %d enumeration(s), %d chemin(s), %d Blueprint(s) modifie(s), %d cas non traite(s), %d echec(s). ==="), EnumFixed, PathFixed, BlueprintsModified, Unhandled, SaveFailures); return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0; }