// Portage UE 5.5 - voir LPFixParseIntoArrayCommandlet.h #include "LPFixParseIntoArrayCommandlet.h" #include "AssetRegistry/AssetData.h" #include "AssetRegistry/AssetRegistryModule.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "Engine/Blueprint.h" #include "K2Node_CallFunction.h" #include "Kismet/KismetStringLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "LPStringCompat.h" #include "Misc/PackageName.h" #include "UObject/Package.h" #include "UObject/SavePackage.h" #include "UObject/UObjectGlobals.h" DEFINE_LOG_CATEGORY_STATIC(LogLPFixParse, Log, All); namespace { const FName ParseIntoArrayName(TEXT("ParseIntoArray")); /** Liaisons et valeur par defaut de chaque broche, par nom : l'empreinte a retrouver. */ TMap Topology(const UEdGraphNode* Node) { TMap Out; for (const UEdGraphPin* Pin : Node->Pins) { if (!Pin || Pin->bOrphanedPin) { continue; } TArray Links; for (const UEdGraphPin* L : Pin->LinkedTo) { Links.Add(FString::Printf(TEXT("%s.%s"), L && L->GetOwningNode() ? *L->GetOwningNode()->GetName() : TEXT("?"), L ? *L->PinName.ToString() : TEXT("?"))); } Links.Sort(); Out.Add(Pin->PinName, FString::Printf(TEXT("[%s] defaut='%s'"), *FString::Join(Links, TEXT(", ")), *Pin->DefaultValue)); } return Out; } } ULPFixParseIntoArrayCommandlet::ULPFixParseIntoArrayCommandlet() { IsClient = false; IsServer = false; IsEditor = true; LogToConsole = true; } int32 ULPFixParseIntoArrayCommandlet::Main(const FString& Params) { const bool bDryRun = Params.Contains(TEXT("DryRun"), ESearchCase::IgnoreCase); UFunction* OldFunction = UKismetStringLibrary::StaticClass()->FindFunctionByName(ParseIntoArrayName); UFunction* NewFunction = ULPStringCompat::StaticClass()->FindFunctionByName(ParseIntoArrayName); if (!OldFunction || !NewFunction) { UE_LOG(LogLPFixParse, Error, TEXT("Fonction introuvable (ancienne=%s, nouvelle=%s)."), OldFunction ? TEXT("ok") : TEXT("NULL"), NewFunction ? TEXT("ok") : TEXT("NULL")); return 1; } UE_LOG(LogLPFixParse, Warning, TEXT("=== KismetStringLibrary::ParseIntoArray -> LPStringCompat::ParseIntoArray%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 NodesFound = 0; int32 NodesFixed = 0; int32 Unhandled = 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 FixedHere = 0; int32 FailedHere = 0; for (UEdGraph* Graph : Graphs) { if (!Graph) { continue; } TArray Snapshot = Graph->Nodes; for (UEdGraphNode* GraphNode : Snapshot) { UK2Node_CallFunction* CallNode = Cast(GraphNode); if (!CallNode || CallNode->GetTargetFunction() != OldFunction) { continue; } ++NodesFound; const TMap Before = Topology(CallNode); UE_LOG(LogLPFixParse, Warning, TEXT(" %s :: %s -> %s | Delimiter %s | CullEmptyStrings %s"), *Blueprint->GetName(), *Graph->GetName(), *CallNode->GetName(), *Before.FindRef(TEXT("Delimiter")), *Before.FindRef(TEXT("CullEmptyStrings"))); if (bDryRun) { continue; } CallNode->Modify(); CallNode->SetFromFunction(NewFunction); CallNode->ReconstructNode(); const TMap After = Topology(CallNode); bool bSame = Before.Num() == After.Num(); for (const TPair& It : Before) { const FString* A = After.Find(It.Key); if (!A || *A != It.Value) { bSame = false; UE_LOG(LogLPFixParse, Error, TEXT(" broche %s : avant %s | apres %s"), *It.Key.ToString(), *It.Value, A ? **A : TEXT("(absente)")); } } if (bSame && CallNode->GetTargetFunction() == NewFunction) { ++NodesFixed; ++FixedHere; } else { ++Unhandled; ++FailedHere; } } } if (FailedHere > 0) { // Rien n'est sauvegarde : le paquet reste tel qu'il est sur disque. UE_LOG(LogLPFixParse, Error, TEXT(" -> %s : %d noeud(s) en ecart, Blueprint NON sauvegarde."), *Blueprint->GetName(), FailedHere); continue; } 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(LogLPFixParse, 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(LogLPFixParse, Warning, TEXT(" -> %s sauvegarde (%d noeud(s))."), *Package->GetName(), FixedHere); } else { ++SaveFailures; UE_LOG(LogLPFixParse, Error, TEXT(" -> ECHEC de sauvegarde de %s"), *Package->GetName()); } } } UE_LOG(LogLPFixParse, Warning, TEXT("=== Termine : %d noeud(s) trouve(s), %d rebranche(s), %d Blueprint(s) modifie(s), %d en ecart, %d echec(s) de sauvegarde. ==="), NodesFound, NodesFixed, BlueprintsModified, Unhandled, SaveFailures); return (SaveFailures > 0 || Unhandled > 0) ? 1 : 0; }