LifePlay-Revival/Source/LifePlayEditorTools/LPFixParseIntoArrayCommandlet.cpp
edujiro5972 0d703d47a7 L4-CX-B08 : ParseIntoArray au comportement UE 4.27 (cause d'ERR-027)
LPStringCompat::ParseIntoArray (algorithme 4.27 : chaine vide -> [], separateur
sensible a la casse) ; commandlet LPFixParseIntoArray : 114 noeuds rebranches dans
17 Blueprints, 0 ecart de liaison (controle integre), 0 broche orpheline,
CompileAllBlueprints 0 erreur. LPDumpFunction : option -Exec.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:56:16 +02:00

210 lines
6.1 KiB
C++

// 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<FName, FString> Topology(const UEdGraphNode* Node)
{
TMap<FName, FString> Out;
for (const UEdGraphPin* Pin : Node->Pins)
{
if (!Pin || Pin->bOrphanedPin)
{
continue;
}
TArray<FString> 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<FAssetRegistryModule>(TEXT("AssetRegistry"));
AssetRegistryModule.Get().SearchAllAssets(true);
TArray<FAssetData> 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<UBlueprint>(AssetData.GetAsset());
if (!Blueprint)
{
continue;
}
TArray<UEdGraph*> Graphs;
Blueprint->GetAllGraphs(Graphs);
int32 FixedHere = 0;
int32 FailedHere = 0;
for (UEdGraph* Graph : Graphs)
{
if (!Graph)
{
continue;
}
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
for (UEdGraphNode* GraphNode : Snapshot)
{
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(GraphNode);
if (!CallNode || CallNode->GetTargetFunction() != OldFunction)
{
continue;
}
++NodesFound;
const TMap<FName, FString> 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<FName, FString> After = Topology(CallNode);
bool bSame = Before.Num() == After.Num();
for (const TPair<FName, FString>& 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;
}