Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Commandlets/DiffAssetsCommandlet.cpp
2025-05-18 13:04:45 +08:00

232 lines
6.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/**
* Commandlet to allow diff in P4V, and expose that functionality to the editor
*/
#include "Commandlets/DiffAssetsCommandlet.h"
#include "Containers/Map.h"
#include "Exporters/Exporter.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformCrt.h"
#include "HAL/PlatformProcess.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Math/UnrealMathUtility.h"
#include "Misc/CString.h"
#include "Misc/FileHelper.h"
#include "Misc/PackageName.h"
#include "Misc/Paths.h"
#include "Templates/UnrealTemplate.h"
#include "Trace/Detail/Channel.h"
#include "UObject/NameTypes.h"
#include "UObject/Object.h"
#include "UObject/Package.h"
#include "UObject/PropertyPortFlags.h"
#include "UObject/UObjectIterator.h"
#include "UnrealExporter.h"
DEFINE_LOG_CATEGORY_STATIC(LogDiffAssetsCommandlet, Log, All);
UDiffAssetsCommandlet::UDiffAssetsCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
bool UDiffAssetsCommandlet::ExportFilesToTextAndDiff(const FString& InParams)
{
FString Params = InParams.Replace(TEXT("\\\""), TEXT("\"")); // this deals with an anomaly of P4V
UE_LOG(LogDiffAssetsCommandlet, Log, TEXT("Params: %s"), *Params);
TArray<FString> Tokens;
TArray<FString> Switches;
ParseCommandLine(*Params, Tokens, Switches);
const FString EqualStr(TEXT("="));
const FString DiffCmdKey(TEXT("DiffCmd"));
TArray<FString> PositionalTokens;
FString DiffCmd;
for (FString& Token : Tokens)
{
FString Key, Value;
if (Token.Split(EqualStr, &Key, &Value))
{
if (DiffCmd.IsEmpty() && Key.Equals(DiffCmdKey, ESearchCase::IgnoreCase))
{
DiffCmd = Value;
}
}
else
{
PositionalTokens.Add(MoveTemp(Token));
}
}
const FString AssetPackageExtension = FPackageName::GetAssetPackageExtension();
if (DiffCmd.IsEmpty() || PositionalTokens.Num() < 2)
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Usage: UDiffAssets File1%s File2%s DiffCmd=\"C:/Program Files/Araxis/Araxis Merge/AraxisP4Diff.exe {1} {2}\""), *AssetPackageExtension, *AssetPackageExtension);
return false;
}
if (!DiffCmd.Contains(TEXT("{1}")) || !DiffCmd.Contains(TEXT("{2}")) )
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Usage: UDiffAssets File1%s File2%s DiffCmd=\"C:/Program Files/Araxis/Araxis Merge/AraxisP4Diff.exe {1} {2}\""), *AssetPackageExtension, *AssetPackageExtension);
return false;
}
return ExportFilesToTextAndDiff(PositionalTokens[0], PositionalTokens[1], DiffCmd);
}
bool UDiffAssetsCommandlet::CopyFileToTempLocation(FString& InOutFilename)
{
FString InFilename = InOutFilename;
// Get base filename (no extension) so we can easily fix it up without
// having to worry about skipping dots and slashes.
FString OutBaseFilename = FPaths::GetBaseFilename(InFilename);
// Make sure the filename doesn't contain any invalid name characters.
// Replace them with '_' if found.
const TCHAR* InvalidCharacters = INVALID_LONGPACKAGE_CHARACTERS;
for (; *InvalidCharacters; ++InvalidCharacters)
{
const TCHAR InvalidStr[] = { *InvalidCharacters, '\0' };
OutBaseFilename.ReplaceInline(InvalidStr, TEXT("_"));
}
// Add path and the extension (with a dot)
FString OutFilename = FString::Printf(TEXT("%s%s%s"), *FPaths::DiffDir(), *OutBaseFilename, *FPaths::GetExtension(InFilename, true));
if (IFileManager::Get().Copy(*OutFilename, *InFilename, true, true))
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Failed to copy %s to %s."), *InFilename, *OutFilename);
return false;
}
InOutFilename = *OutFilename;
return true;
}
bool UDiffAssetsCommandlet::LoadFile(const FString& Filename, TArray<UObject *>& LoadedObjects)
{
UPackage* Package = LoadPackage( NULL, *Filename, LOAD_ForDiff);
if (!Package)
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Could not load %s"), *Filename);
return false;
}
for (TObjectIterator<UObject> It; It; ++It)
{
if (It->GetOuter() == Package)
{
LoadedObjects.Add(*It);
}
}
if (!LoadedObjects.Num())
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Loaded %s, but it didn't contain any objects."), *Filename);
return false;
}
LoadedObjects.Sort();
return true;
}
bool UDiffAssetsCommandlet::ExportFile(const FString& Filename, const TArray<UObject *>& LoadedObjects)
{
FString Extension = TEXT("t3d");
FStringOutputDevice Buffer;
const FExportObjectInnerContext Context;
for (int32 Index = 0; Index < LoadedObjects.Num(); Index++)
{
UExporter* Exporter = UExporter::FindExporter( LoadedObjects[Index], *Extension );
if (!Exporter)
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Could not find exporter."));
return false;
}
UExporter::ExportToOutputDevice( &Context, LoadedObjects[Index], Exporter, Buffer, *Extension, 0, PPF_ExportsNotFullyQualified, false );
TMap<FString,FString> NativePropertyValues;
if ( LoadedObjects[Index]->GetNativePropertyValues(NativePropertyValues) && NativePropertyValues.Num())
{
int32 LargestKey = 0;
for ( TMap<FString,FString>::TIterator It(NativePropertyValues); It; ++It )
{
LargestKey = FMath::Max(LargestKey, It.Key().Len());
}
for ( TMap<FString,FString>::TIterator It(NativePropertyValues); It; ++It )
{
Buffer.Logf(TEXT(" %s=%s"), *It.Key().RightPad(LargestKey), *It.Value());
}
}
}
if (!Buffer.Len())
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("No text was exported!"));
return false;
}
if( !FFileHelper::SaveStringToFile( Buffer, *Filename ) )
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Could not write %s"), *Filename);
return false;
}
return true;
}
bool UDiffAssetsCommandlet::ExportFilesToTextAndDiff(const FString& InFilename1, const FString& InFilename2, const FString& DiffCommand)
{
FString Filename1 = InFilename1;
FString Filename2 = InFilename2;
if (!CopyFileToTempLocation(Filename1))
{
return false;
}
if (!CopyFileToTempLocation(Filename2))
{
return false;
}
FString TextFilename1 = Filename1 + TEXT(".t3d");
FString TextFilename2 = Filename2 + TEXT(".t3d");
{
TArray<UObject *> ObjectsToExport;
if (!LoadFile(Filename1, ObjectsToExport))
{
return false;
}
if (!ExportFile(TextFilename1, ObjectsToExport))
{
return false;
}
}
{
TArray<UObject *> ObjectsToExport;
if (!LoadFile(Filename2, ObjectsToExport))
{
return false;
}
if (!ExportFile(TextFilename2, ObjectsToExport))
{
return false;
}
}
FString ReplacedDiffCmd = DiffCommand.Replace(TEXT("{1}"), *TextFilename1, ESearchCase::CaseSensitive).Replace(TEXT("{2}"), *TextFilename2, ESearchCase::CaseSensitive);
int32 ArgsAt = DiffCommand.Find(TEXT("{1}"), ESearchCase::CaseSensitive) - 1;
FString Args;
if (ArgsAt > 0)
{
Args = *ReplacedDiffCmd + ArgsAt + 1;
ReplacedDiffCmd.LeftInline(ArgsAt, EAllowShrinking::No);
}
if (!FPlatformProcess::CreateProc(*ReplacedDiffCmd, *Args, true, false, false, NULL, 0, NULL, NULL).IsValid())
{
UE_LOG(LogDiffAssetsCommandlet, Warning, TEXT("Could not launch %s."), *ReplacedDiffCmd);
return false;
}
return true;
}