// Portage UE 4.27 (2026.9 beta) - voir LPFixAssetRegistryNodesCommandlet.h #include "LPFixAssetRegistryNodesCommandlet.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "K2Node_CallFunction.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "LPAssetRegistryHelpers.h" #include "Misc/FeedbackContext.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" #include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixNodes, Log, All); namespace { const TCHAR* TargetFunctionName = TEXT("GetAssetByObjectPath"); /** * Le noeud impur possede des broches Exec que la version pure n'aura plus. * Si elles sont reliees, on raccorde l'amont a l'aval pour ne pas casser la chaine * d'execution avant de reconstruire le noeud. */ void BridgeExecPins(UK2Node_CallFunction* Node) { UEdGraphPin* ExecPin = Node->FindPin(UEdGraphSchema_K2::PN_Execute, EGPD_Input); UEdGraphPin* ThenPin = Node->FindPin(UEdGraphSchema_K2::PN_Then, EGPD_Output); if (!ExecPin || !ThenPin || ExecPin->LinkedTo.Num() == 0 || ThenPin->LinkedTo.Num() == 0) { return; } TArray Sources = ExecPin->LinkedTo; TArray Destinations = ThenPin->LinkedTo; for (UEdGraphPin* Source : Sources) { for (UEdGraphPin* Destination : Destinations) { Source->MakeLinkTo(Destination); } } UE_LOG(LogLPFixNodes, Warning, TEXT(" (broches Exec reliees : chaine d'execution pontee sur %s)"), *Node->GetName()); } } ULPFixAssetRegistryNodesCommandlet::ULPFixAssetRegistryNodesCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixAssetRegistryNodesCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); UFunction* NewFunction = ULPAssetRegistryHelpers::StaticClass()->FindFunctionByName(FName(TargetFunctionName)); if (!NewFunction) { UE_LOG(LogLPFixNodes, Error, TEXT("ULPAssetRegistryHelpers::%s introuvable."), TargetFunctionName); return 1; } UE_LOG(LogLPFixNodes, Warning, TEXT("=== Correction des noeuds %s%s ==="), TargetFunctionName, bDryRun ? TEXT(" [DRY RUN]") : TEXT("")); FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().SearchAllAssets(true); TArray BlueprintAssets; // 5.1+ : GetAssetsByClass prend un FTopLevelAssetPath, plus un FName de classe. AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), BlueprintAssets, true); UE_LOG(LogLPFixNodes, Warning, TEXT("%d Blueprints a inspecter."), BlueprintAssets.Num()); int32 NodesFixed = 0; int32 BlueprintsModified = 0; int32 SaveFailures = 0; 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 NodesFixedHere = 0; for (UEdGraph* Graph : Graphs) { if (!Graph) { continue; } TArray NodesSnapshot = Graph->Nodes; for (UEdGraphNode* GraphNode : NodesSnapshot) { UK2Node_CallFunction* CallNode = Cast(GraphNode); if (!CallNode) { continue; } if (CallNode->FunctionReference.GetMemberName() != FName(TargetFunctionName)) { continue; } // Deja migre vers la version pure : rien a faire. if (CallNode->FunctionReference.GetMemberParentClass() == ULPAssetRegistryHelpers::StaticClass()) { continue; } UE_LOG(LogLPFixNodes, Warning, TEXT(" %s :: %s -> noeud %s"), *Blueprint->GetName(), *Graph->GetName(), *CallNode->GetName()); if (!bDryRun) { Blueprint->Modify(); Graph->Modify(); CallNode->Modify(); BridgeExecPins(CallNode); // L'ancien noeud est un appel d'interface : sa broche Target pointe vers // l'objet AssetRegistry. La version pure est une fonction statique de // bibliotheque : sa broche Target est d'un autre type et le lien serait // reapplique tel quel par la reconstruction -> incompatibilite. On le coupe. if (UEdGraphPin* SelfPin = CallNode->FindPin(UEdGraphSchema_K2::PN_Self, EGPD_Input)) { SelfPin->BreakAllPinLinks(); } CallNode->SetFromFunction(NewFunction); CallNode->ReconstructNode(); } ++NodesFixedHere; ++NodesFixed; } } if (NodesFixedHere == 0) { continue; } ++BlueprintsModified; if (bDryRun) { continue; } FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); FKismetEditorUtilities::CompileBlueprint(Blueprint); UPackage* Package = Blueprint->GetOutermost(); Package->FullyLoad(); Package->SetDirtyFlag(true); const FString Filename = FPackageName::LongPackageNameToFilename( Package->GetName(), FPackageName::GetAssetPackageExtension()); // Le linker du package garde le .uasset ouvert : sans cela SavePackage ne peut pas // remplacer le fichier ("DeleteFile was unable to delete ..."). ResetLoaders(Package); UE_LOG(LogLPFixNodes, Warning, TEXT(" -> sauvegarde vers %s"), *Filename); // 5.x : les arguments passent par FSavePackageArgs (Conform n'est plus implemente). FSavePackageArgs SaveArgs; SaveArgs.TopLevelFlags = RF_Public | RF_Standalone; SaveArgs.SaveFlags = SAVE_None; SaveArgs.bForceByteSwapping = false; SaveArgs.bWarnOfLongFilename = true; // GWarn et non GError : en commandlet, GError rend toute erreur de sauvegarde fatale. SaveArgs.Error = GWarn; if (UPackage::SavePackage(Package, nullptr, *Filename, SaveArgs)) { UE_LOG(LogLPFixNodes, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), NodesFixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixNodes, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } UE_LOG(LogLPFixNodes, Warning, TEXT("=== Termine : %d noeud(s) dans %d Blueprint(s), %d echec(s) de sauvegarde. ==="), NodesFixed, BlueprintsModified, SaveFailures); return SaveFailures > 0 ? 1 : 0; }