예제 #1
0
/**
 * Debug spew for components
 * @param Object object to dump component spew for
 */
void DumpComponents(UObject *Object)
{
    for ( FObjectIterator It; It; ++It )
    {
        It->UnMark(EObjectMark(OBJECTMARK_TagImp | OBJECTMARK_TagExp));
    }

    if (FPlatformMisc::IsDebuggerPresent() )
    {
        // if we have a debugger attached, the watch window won't be able to display the full output if we attempt to log it as a single string
        // so pass in GLog instead so that each line is sent separately;  this causes the output to have an extra line break between each log statement,
        // but at least we'll be able to see the full output in the debugger's watch window
        UE_LOG(LogExporter, Log, TEXT("Components for '%s':"), *Object->GetFullName());
        ExportProperties( NULL, *GLog, Object->GetClass(), (uint8*)Object, 0, NULL, NULL, Object, PPF_SubobjectsOnly );
        UE_LOG(LogExporter, Log, TEXT("<--- DONE!"));
    }
    else
    {
        FStringOutputDevice Output;
        Output.Logf(TEXT("Components for '%s':\r\n"), *Object->GetFullName());
        ExportProperties( NULL, Output, Object->GetClass(), (uint8*)Object, 2, NULL, NULL, Object, PPF_SubobjectsOnly );
        Output.Logf(TEXT("<--- DONE!\r\n"));
        UE_LOG(LogExporter, Log, TEXT("%s"), *Output);
    }
}
/**
 * Returns a list of all assets referenced by the specified UObject.
 */
void FFindReferencedAssets::BuildAssetList(UObject *Object, const TArray<UClass*>& IgnoreClasses, const TArray<UObject*>& IgnorePackages, TSet<UObject*>& ReferencedAssets, bool bIncludeDefaultRefs)
{
	TArray<FReferencedAssets> LocalReferencers;

	// Create a new entry for this actor.
	new( LocalReferencers ) FReferencedAssets( Object );

	for (FObjectIterator It; It; ++It)
	{
		// Skip the level, world, and any packages that should be ignored
		if ( ShouldSearchForAssets(*It, IgnoreClasses, IgnorePackages, bIncludeDefaultRefs) )
		{
			It->Mark(OBJECTMARK_TagExp);
		}
		else
		{
			It->UnMark(OBJECTMARK_TagExp);
		}
	}

	// Add to the list of referenced assets.
	FFindAssetsArchive( Object, LocalReferencers.Last().AssetList, NULL, /*MaxRecursion=*/0, /*bIncludeClasses=*/true, bIncludeDefaultRefs );

	ReferencedAssets = LocalReferencers.Last().AssetList;
}
bool UOnlineHotfixManager::HotfixPakIniFile(const FString& FileName)
{
	FConfigFile* ConfigFile = GetConfigFile(FileName);
	ConfigFile->Combine(FileName);
	UE_LOG(LogHotfixManager, Log, TEXT("Hotfix merged INI (%s) found in a PAK file"), *FileName);

	FName IniFileName(*FileName, FNAME_Find);
	int32 NumObjectsReloaded = 0;
	const double StartTime = FPlatformTime::Seconds();
	// Now that we have a list of classes to update, we can iterate objects and
	// reload if they match the INI file that was changed
	for (FObjectIterator It; It; ++It)
	{
		UClass* Class = It->GetClass();
		if (Class->HasAnyClassFlags(CLASS_Config) &&
			Class->ClassConfigName == IniFileName)
		{
			// Force a reload of the config vars
			It->ReloadConfig();
			NumObjectsReloaded++;
		}
	}
	UE_LOG(LogHotfixManager, Log, TEXT("Updating config from %s took %f seconds reloading %d objects"),
		*FileName, FPlatformTime::Seconds() - StartTime, NumObjectsReloaded);
	return true;
}
예제 #4
0
	void MarkAllObjects()
	{
		// Mark all objects so we don't get into an endless recursion
		for (FObjectIterator It; It; ++It)
		{
			It->Mark(OBJECTMARK_TagExp);
		}
	}
// This is from FArchiveTraceRoute -This only creates object graph of all objects 
// This can be used by other classes such as FTraceReferences - trace references of one object
FArchiveObjectGraph::FArchiveObjectGraph(bool IncludeTransients, EObjectFlags	KeepFlags)
:	CurrentReferencer(NULL),
	bIncludeTransients(IncludeTransients), 
	RequiredFlags(KeepFlags)
{
	ArIsObjectReferenceCollector = true;

	// ALL objects reference their outers...it's just log spam here
	//ArIgnoreOuterRef = true;

	TArray<UObject*> RootObjects;

	// allocate enough memory for all objects
	ObjectGraph.Empty(GetUObjectArray().GetObjectArrayNum());
	RootObjects.Empty(GetUObjectArray().GetObjectArrayNum());

	// search for objects that have the right flags and add them to the list of objects that we're going to start with
	// all other objects need to be tagged so that we can tell whether they've been serialized or not.
	for( FObjectIterator It; It; ++It )
	{
		UObject* CurrentObject = *It;
		if ( CurrentObject->HasAnyFlags(RequiredFlags) )
		{
			// make sure it isn't tagged
			// ASKRON: WHY do we need this?
			CurrentObject->UnMark(OBJECTMARK_TagExp);
			RootObjects.Add(CurrentObject);
			ObjectGraph.Add(CurrentObject, new FObjectGraphNode(CurrentObject));
		}
		else
		{
			// ASKRON: WHY do we need this?
			CurrentObject->Mark(OBJECTMARK_TagExp);
		}
	}

	// Populate the ObjectGraph - this serializes our root set to map out the relationships between all rooted objects
	GenerateObjectGraph(RootObjects);

	// we won't be adding any additional objects for the arrays and graphs, so free up any memory not being used.
	RootObjects.Shrink();
	ObjectGraph.Shrink();

	// we're done with serialization; clear the tags so that we don't interfere with anything else
	for( FObjectIterator It; It; ++It )
	{
		It->UnMark(OBJECTMARK_TagExp);
	}
}
예제 #6
0
void UUnrealEdEngine::GetPackageList( TArray<UPackage*>* InPackages, UClass* InClass )
{
	InPackages->Empty();

	for( FObjectIterator It ; It ; ++It )
	{
		if( It->GetOuter() && It->GetOuter() != GetTransientPackage() )
		{
			UObject* TopParent = NULL;

			if( InClass == NULL || It->IsA( InClass ) )
				TopParent = It->GetOutermost();

			if( Cast<UPackage>(TopParent) )
				InPackages->AddUnique( (UPackage*)TopParent );
		}
	}
}
예제 #7
0
/**
 * prefilter shader for suspension raycasts
 */
static PxQueryHitType::Enum WheelRaycastPreFilter(	
	PxFilterData SuspensionData, 
	PxFilterData HitData,
	const void* constantBlock, PxU32 constantBlockSize,
	PxHitFlags& filterFlags)
{
	// SuspensionData is the vehicle suspension raycast.
	// HitData is the shape potentially hit by the raycast.

	// don't collide with owner chassis
	if ( SuspensionData.word0 == HitData.word0 )
	{
		return PxSceneQueryHitType::eNONE;
	}

	// collision channels filter
	ECollisionChannel SuspensionChannel = (ECollisionChannel)(SuspensionData.word3 >> 24);

	if ( ECC_TO_BITFIELD(SuspensionChannel) & HitData.word1)
	{
		// debug what object we hit
		if ( false )
		{
			for ( FObjectIterator It; It; ++It )
			{
				if ( It->GetUniqueID() == HitData.word0 )
				{
					UObject* HitObj = *It;
					FString HitObjName = HitObj->GetName();
					break;
				}
			}
		}

		return PxSceneQueryHitType::eBLOCK;
	}

	return PxSceneQueryHitType::eNONE;
}
예제 #8
0
void FEnumEditorUtils::BroadcastChanges(const UUserDefinedEnum* Enum, const TArray<TPair<FName, int8>>& OldNames, bool bResolveData)
{
	check(NULL != Enum);
	if (bResolveData)
	{
		FArchiveEnumeratorResolver EnumeratorResolver(Enum, OldNames);

		TArray<UClass*> ClassesToCheck;
		for (TObjectIterator<UByteProperty> PropertyIter; PropertyIter; ++PropertyIter)
		{
			const UByteProperty* ByteProperty = *PropertyIter;
			if (ByteProperty && (Enum == ByteProperty->GetIntPropertyEnum()))
			{
				UClass* OwnerClass = ByteProperty->GetOwnerClass();
				if (OwnerClass)
				{
					ClassesToCheck.Add(OwnerClass);
				}
			}
		}

		for (FObjectIterator ObjIter; ObjIter; ++ObjIter)
		{
			for (auto ClassIter = ClassesToCheck.CreateConstIterator(); ClassIter; ++ClassIter)
			{
				if (ObjIter->IsA(*ClassIter))
				{
					ObjIter->Serialize(EnumeratorResolver);
					break;
				}
			}
		}
	}

	struct FNodeValidatorHelper
	{
		static bool IsValid(UK2Node* Node)
		{
			return Node
				&& (NULL != Cast<UEdGraph>(Node->GetOuter()))
				&& !Node->HasAnyFlags(RF_Transient | RF_PendingKill);
		}
	};

	TSet<UBlueprint*> BlueprintsToRefresh;

	{
		//CUSTOM NODES DEPENTENT ON ENUM

		for (TObjectIterator<UK2Node> It(RF_Transient); It; ++It)
		{
			UK2Node* Node = *It;
			INodeDependingOnEnumInterface* NodeDependingOnEnum = Cast<INodeDependingOnEnumInterface>(Node);
			if (FNodeValidatorHelper::IsValid(Node) && NodeDependingOnEnum && (Enum == NodeDependingOnEnum->GetEnum()))
			{
				if (UBlueprint* Blueprint = Node->GetBlueprint())
				{
					if (NodeDependingOnEnum->ShouldBeReconstructedAfterEnumChanged())
					{
						Node->ReconstructNode();
					}
					BlueprintsToRefresh.Add(Blueprint);
				}
			}
		}
	}

	for (TObjectIterator<UEdGraphPin> It(RF_Transient); It; ++It)
	{
		UEdGraphPin* Pin = *It;
		if (Pin && (Enum == Pin->PinType.PinSubCategoryObject.Get()) && (EEdGraphPinDirection::EGPD_Input == Pin->Direction))
		{
			UK2Node* Node = Cast<UK2Node>(Pin->GetOuter());
			if (FNodeValidatorHelper::IsValid(Node))
			{
				if (UBlueprint* Blueprint = Node->GetBlueprint())
				{
					if (INDEX_NONE == Enum->FindEnumIndex(*Pin->DefaultValue))
					{
						Pin->Modify();
						if (Blueprint->BlueprintType == BPTYPE_Interface)
						{
							Pin->DefaultValue = Enum->GetEnumName(0);
						}
						else
						{
							Pin->DefaultValue = FEnumEditorUtilsHelper::InvalidName();
						}
						Node->PinDefaultValueChanged(Pin);
						BlueprintsToRefresh.Add(Blueprint);
					}
				}
			}
		}
	}

	for (auto It = BlueprintsToRefresh.CreateIterator(); It; ++It)
	{
		FBlueprintEditorUtils::MarkBlueprintAsModified(*It);
		(*It)->BroadcastChanged();
	}

	FEnumEditorManager::Get().PostChange(Enum, EEnumEditorChangeInfo::Changed);
}
예제 #9
0
	/**
	 * Builds a list of assets to display from the currently selected actors.
	 * NOTE: It ignores assets that are there because they are always loaded
	 * such as default materials, textures, etc.
	 */
	void BuildAssetList(UWorld* InWorld, int32 Depth, bool bShowDefault, bool bShowScript)
	{
		// Clear the old list
		Referencers.Empty();
		ReferenceGraph.Empty();
		ObjectNameCache.Empty();

		TArray<UObject*> BspMats;
		// Search all BSP surfaces for ones that are selected and add their
		// Materials to a temp list
		for (int32 Index = 0; Index < InWorld->GetModel()->Surfs.Num(); Index++)
		{
			// Only add materials that are selected
			if (InWorld->GetModel()->Surfs[Index].PolyFlags & PF_Selected)
			{
				// No point showing the default material
				if (InWorld->GetModel()->Surfs[Index].Material != NULL)
				{
					BspMats.AddUnique(InWorld->GetModel()->Surfs[Index].Material);
				}
			}
		}
		// If any BSP surfaces are selected
		if (BspMats.Num() > 0)
		{
			FReferencedAssets* Referencer = new(Referencers) FReferencedAssets(InWorld->GetModel());

			// Now copy the array
			Referencer->AssetList = BspMats;
			ReferenceGraph.Add(InWorld->GetModel(), BspMats);
		}

		// This is the maximum depth to use when searching for references
		const int32 MaxRecursionDepth = Depth;

		USelection* ActorSelection = GEditor->GetSelectedActors();

		// Mark all objects so we don't get into an endless recursion
		for (FObjectIterator It; It; ++It)
		{
			// Skip the level, world, and any packages that should be ignored
			if (ShouldSearchForAssets(*It, IgnoreClasses, IgnorePackages, bShowDefault))
			{
				It->Mark(OBJECTMARK_TagExp);
			}
			else
			{
				It->UnMark(OBJECTMARK_TagExp);
			}
		}

		TArray<AActor*> SelectedActors;
		// Get the list of currently selected actors
		ActorSelection->GetSelectedObjects<AActor>(SelectedActors);

		// Build the list of assets from the set of selected actors
		for (int32 Index = 0; Index < SelectedActors.Num(); Index++)
		{
			// Set the flag for the selected item, as it could have actually been cleared by an
			// earlier selected object, which would result in a crash later
			SelectedActors[Index]->Mark(OBJECTMARK_TagExp);

			// Create a new entry for this actor
			FReferencedAssets* Referencer = new(Referencers) FReferencedAssets(SelectedActors[Index]);

			// Add to the list of referenced assets
			FFindAssetsArchive(SelectedActors[Index], Referencer->AssetList, &ReferenceGraph, MaxRecursionDepth, bShowScript, bShowDefault);
		}

		// Rebuild the name cache
		for (int32 RefIndex = 0; RefIndex < Referencers.Num(); RefIndex++)
		{
			FReferencedAssets& Referencer = Referencers[RefIndex];
			if (!ObjectNameCache.Contains(Referencer.Referencer))
			{
				ObjectNameCache.Add(Referencer.Referencer, *Referencer.Referencer->GetName());
			}

			for (int32 AssetIndex = 0; AssetIndex < Referencer.AssetList.Num(); AssetIndex++)
			{
				if (!ObjectNameCache.Contains(Referencer.AssetList[AssetIndex]))
				{
					ObjectNameCache.Add(Referencer.AssetList[AssetIndex], *Referencer.AssetList[AssetIndex]->GetName());
				}
			}
		}
	}
int32 UGenerateDistillFileSetsCommandlet::Main( const FString& InParams )
{
	// Parse command line.
	TArray<FString> Tokens;
	TArray<FString> Switches;
	UCommandlet::ParseCommandLine(*InParams, Tokens, Switches);

	TArray<FString> MapList;
	for ( int32 MapIdx = 0; MapIdx < Tokens.Num(); ++MapIdx )
	{
		const FString& Map = Tokens[MapIdx];
		if ( FPackageName::IsShortPackageName(Map) )
		{
			FString LongPackageName;
			if ( FPackageName::SearchForPackageOnDisk(Map, &LongPackageName) )
			{
				MapList.Add(LongPackageName);
			}
			else
			{
				UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Unable to find package for map %s."), *Map);
				return 1;
			}
		}
		else
		{
			MapList.Add(Map);
		}
	}

	if ( MapList.Num() <= 0 )
	{
		// No map tokens were supplied on the command line, so assume all maps
		TArray<FString> AllPackageFilenames;
		FEditorFileUtils::FindAllPackageFiles(AllPackageFilenames);
		for (int32 PackageIndex = 0; PackageIndex < AllPackageFilenames.Num(); PackageIndex++)
		{
			const FString& Filename = AllPackageFilenames[PackageIndex];
			if (FPaths::GetExtension(Filename, true) == FPackageName::GetMapPackageExtension() )
			{
				FString LongPackageName;
				if ( FPackageName::TryConvertFilenameToLongPackageName(Filename, LongPackageName) )
				{
					// Warn about maps in "NoShip" or "TestMaps" folders.  Those should have been filtered out during the Distill process!
					if( !Filename.Contains( "/NoShip/") && !Filename.Contains( "/TestMaps/"))
					{
						// @todo plugins add support for plugins?
						if ( LongPackageName.StartsWith(TEXT("/Game")) )
						{
							UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Discovered map package %s..." ), *LongPackageName );
							MapList.Add(LongPackageName);
						}
					}
					else
					{
						UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Skipping map package %s in TestMaps or NoShip folder"), *Filename);
					}
				}
				else
				{
					UE_LOG(LogGenerateDistillFileSetsCommandlet, Warning, TEXT("Failed to determine package name for map file %s."), *Filename);
				}
			}
		}
	}
	
	const FString TemplateFileSwitch = TEXT("Template=");
	const FString OutputFileSwitch = TEXT("Output=");
	const FString TemplateFolderSwitch = TEXT("TemplateFolder=");
	const FString OutputFolderSwitch = TEXT("OutputFolder=");
	FString TemplateFilename;
	FString OutputFilename;
	FString TemplateFolder;
	FString OutputFolder;
	for (int32 SwitchIdx = 0; SwitchIdx < Switches.Num(); ++SwitchIdx)
	{
		const FString& Switch = Switches[SwitchIdx];
		if ( Switch.StartsWith(TemplateFileSwitch) )
		{
			Switch.Split(TEXT("="), NULL, &TemplateFilename);
		}
		else if ( Switch.StartsWith(OutputFileSwitch) )
		{
			Switch.Split(TEXT("="), NULL, &OutputFilename);
		}
		else if ( Switch.StartsWith(TemplateFolderSwitch) )
		{
			Switch.Split(TEXT("="), NULL, &TemplateFolder);
			FPaths::NormalizeFilename(TemplateFolder);
			if ( !TemplateFolder.EndsWith(TEXT("/")) )
			{
				TemplateFolder += TEXT("/");
			}
			UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Using template folder: "), *TemplateFolder);
		}
		else if ( Switch.StartsWith(OutputFolderSwitch) )
		{
			Switch.Split(TEXT("="), NULL, &OutputFolder);
			FPaths::NormalizeFilename(OutputFolder);
			if ( !OutputFolder.EndsWith(TEXT("/")) )
			{
				OutputFolder += TEXT("/");
			}
			UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT("Using output folder: "), *OutputFolder);
		}
	}

	if ( TemplateFilename.IsEmpty() || OutputFilename.IsEmpty() )
	{
		UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("You must supply a -Template=TemplateFilename and -Output=OutputFilename. These files are relative to the Game/Build directory."));
		return 1;
	}

	// If no folder was specified, filenames are relative to the build dir.
	if ( TemplateFolder.IsEmpty() )
	{
		TemplateFolder = FPaths::GameDir() + TEXT("Build/");
	}
	if ( OutputFolder.IsEmpty() )
	{
		OutputFolder = FPaths::GameDir() + TEXT("Build/");
	}
	TemplateFilename = TemplateFolder + TemplateFilename;
	OutputFilename = OutputFolder + OutputFilename;

	// Load the template file
	FString TemplateFileContents;
	if ( !FFileHelper::LoadFileToString(TemplateFileContents, *TemplateFilename) )
	{
		UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Failed to load template file '%s'"), *TemplateFilename);
		return 1;
	}

	// Form a full unique package list
	TSet<FString> AllPackageNames;

	//@todo SLATE: This is a hack to ensure all slate referenced assets get cooked.
	// Slate needs to be refactored to properly identify required assets at cook time.
	// Simply jamming everything in a given directory into the cook list is error-prone
	// on many levels - assets not required getting cooked/shipped; assets not put under 
	// the correct folder; etc.
	{
		TArray<FString> UIContentPaths;
		if (GConfig->GetArray(TEXT("UI"), TEXT("ContentDirectories"), UIContentPaths, GEditorIni) > 0)
		{
			for (int32 DirIdx = 0; DirIdx < UIContentPaths.Num(); DirIdx++)
			{
				FString ContentPath = FPackageName::LongPackageNameToFilename(UIContentPaths[DirIdx]);

				TArray<FString> Files;
				IFileManager::Get().FindFilesRecursive(Files, *ContentPath, *(FString(TEXT("*")) + FPackageName::GetAssetPackageExtension()), true, false);
				for (int32 Index = 0; Index < Files.Num(); Index++)
				{
					FString StdFile = Files[Index];
					FPaths::MakeStandardFilename(StdFile);
					StdFile = FPackageName::FilenameToLongPackageName(StdFile);
					AllPackageNames.Add(StdFile);
				}
			}

		}
	}

	// Load all maps
	{
		for ( auto MapIt = MapList.CreateConstIterator(); MapIt; ++MapIt )
		{
			const FString& MapPackage = *MapIt;
			UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Loading %s..." ), *MapPackage );
			UPackage* Package = LoadPackage( NULL, *MapPackage, LOAD_None );
			if( Package != NULL )
			{
				AllPackageNames.Add(Package->GetName());

				UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Finding content referenced by %s..." ), *MapPackage );
				for ( FObjectIterator ObjIt; ObjIt; ++ObjIt )
				{
					AllPackageNames.Add(ObjIt->GetOutermost()->GetName());
				}

				UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Collecting garbage..." ) );
				CollectGarbage(RF_Native);
			}
		}
	}

	// Sort the results to make it easier to diff files. No necessary but useful sometimes.
	TArray<FString> SortedPackageNames = AllPackageNames.Array();
	SortedPackageNames.Sort();

	// For the list of FileSets to include in the distill
	FString AllFileSets;
	const FString FileSetPathRoot = TEXT("Content");
	for (auto PackageIt = SortedPackageNames.CreateConstIterator(); PackageIt; ++PackageIt)
	{
		const FString& PackageName = *PackageIt;
		// @todo plugins add support for plugins?
		if ( PackageName.StartsWith(TEXT("/Game")) )
		{
			const FString PathWithoutRoot( PackageName.Mid( 5 ) );
			const FString FileSetPath = FileSetPathRoot + PathWithoutRoot;
			AllFileSets += FString::Printf( TEXT("<FileSet Path=\"%s.*\" bIsRecursive=\"false\"/>") LINE_TERMINATOR, *FileSetPath );
		}
	}

	// Write the output file
	FString OutputFileContents = TemplateFileContents.Replace(TEXT("%INSTALLEDCONTENTFILESETS%"), *AllFileSets, ESearchCase::CaseSensitive);
	if ( FApp::HasGameName() )
	{
		UE_LOG(LogGenerateDistillFileSetsCommandlet, Display, TEXT( "Replacing %%GAMENAME%% with (%s)..." ), FApp::GetGameName() );
		OutputFileContents = OutputFileContents.Replace(TEXT("%GAMENAME%"), FApp::GetGameName(), ESearchCase::CaseSensitive);
	}
	else
	{
		UE_LOG(LogGenerateDistillFileSetsCommandlet, Warning, TEXT("Failed to replace %%GAMENAME%% since we are running without a game name."));
	}

	if ( !FFileHelper::SaveStringToFile(OutputFileContents, *OutputFilename) )
	{
		UE_LOG(LogGenerateDistillFileSetsCommandlet, Error, TEXT("Failed to save output file '%s'"), *OutputFilename);
		return 1;
	}

	return 0;
}
예제 #11
0
bool EngineUtils::FindOrLoadAssetsByPath(const FString& Path, TArray<UObject*>& OutAssets)
{
	if ( !FPackageName::IsValidLongPackageName(Path, true) )
	{
		return false;
	}

	// Convert the package path to a filename with no extension (directory)
	const FString FilePath = FPackageName::LongPackageNameToFilename(Path);

	// Gather the package files in that directory and subdirectories
	TArray<FString> Filenames;
	FPackageName::FindPackagesInDirectory(Filenames, FilePath);

	// Cull out map files
	for (int32 FilenameIdx = Filenames.Num() - 1; FilenameIdx >= 0; --FilenameIdx)
	{
		const FString Extension = FPaths::GetExtension(Filenames[FilenameIdx], true);
		if ( Extension == FPackageName::GetMapPackageExtension() )
		{
			Filenames.RemoveAt(FilenameIdx);
		}
	}

	// Load packages or find existing ones and fully load them
	TSet<UPackage*> Packages;
	for (int32 FileIdx = 0; FileIdx < Filenames.Num(); ++FileIdx)
	{
		const FString& Filename = Filenames[FileIdx];

		UPackage* Package = FindPackage(NULL, *FPackageName::FilenameToLongPackageName(Filename));

		if (Package)
		{
			Package->FullyLoad();
		}
		else
		{
			Package = LoadPackage(NULL, *Filename, LOAD_None);
		}

		if (Package)
		{
			Packages.Add(Package);
		}
	}

	// If any packages were successfully loaded, find all assets that were in the packages and add them to OutAssets
	if ( Packages.Num() > 0 )
	{
		for (FObjectIterator ObjIt; ObjIt; ++ObjIt)
		{
			if ( Packages.Contains(ObjIt->GetOutermost()) && ObjIt->IsAsset() )
			{
				OutAssets.Add(*ObjIt);
			}
		}
	}

	return true;
}
bool UOnlineHotfixManager::HotfixIniFile(const FString& FileName, const FString& IniData)
{
	FConfigFile* ConfigFile = GetConfigFile(FileName);
	// Merge the string into the config file
	ConfigFile->CombineFromBuffer(IniData);
	TArray<UClass*> Classes;
	TArray<UObject*> PerObjectConfigObjects;
	int32 StartIndex = 0;
	int32 EndIndex = 0;
	// Find the set of object classes that were affected
	while (StartIndex >= 0 && StartIndex < IniData.Len() && EndIndex >= StartIndex)
	{
		// Find the next section header
		StartIndex = IniData.Find(TEXT("["), ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex);
		if (StartIndex > -1)
		{
			// Find the ending section identifier
			EndIndex = IniData.Find(TEXT("]"), ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex);
			if (EndIndex > StartIndex)
			{
				int32 PerObjectNameIndex = IniData.Find(TEXT(" "), ESearchCase::IgnoreCase, ESearchDir::FromStart, StartIndex);
				// Per object config entries will have a space in the name, but classes won't
				if (PerObjectNameIndex == -1 || PerObjectNameIndex > EndIndex)
				{
					if (IniData.StartsWith(TEXT("[/Script/"), ESearchCase::IgnoreCase))
					{
						const int32 ScriptSectionTag = 9;
						// Snip the text out and try to find the class for that
						const FString PackageClassName = IniData.Mid(StartIndex + ScriptSectionTag, EndIndex - StartIndex - ScriptSectionTag);
						// Find the class for this so we know what to update
						UClass* Class = FindObject<UClass>(nullptr, *PackageClassName, true);
						if (Class)
						{
							// Add this to the list to check against
							Classes.Add(Class);
						}
					}
				}
				// Handle the per object config case by finding the object for reload
				else
				{
					const int32 Count = PerObjectNameIndex - StartIndex - 1;
					const FString PerObjectName = IniData.Mid(StartIndex + 1, Count);
					// Explicitly search the transient package (won't update non-transient objects)
					UObject* PerObject = FindObject<UObject>(ANY_PACKAGE, *PerObjectName, false);
					if (PerObject != nullptr)
					{
						PerObjectConfigObjects.Add(PerObject);
					}
				}
				StartIndex = EndIndex;
			}
		}
	}

	int32 NumObjectsReloaded = 0;
	const double StartTime = FPlatformTime::Seconds();
	if (Classes.Num())
	{
		// Now that we have a list of classes to update, we can iterate objects and reload
		for (FObjectIterator It; It; ++It)
		{
			UClass* Class = It->GetClass();
			if (Class->HasAnyClassFlags(CLASS_Config))
			{
				// Check to see if this class is in our list (yes, potentially n^2, but not in practice)
				for (int32 ClassIndex = 0; ClassIndex < Classes.Num(); ClassIndex++)
				{
					if (It->IsA(Classes[ClassIndex]))
					{
						// Force a reload of the config vars
						It->ReloadConfig();
						NumObjectsReloaded++;
						break;
					}
				}
			}
		}
	}
	// Reload any PerObjectConfig objects that were affected
	for (auto ReloadObject : PerObjectConfigObjects)
	{
		ReloadObject->ReloadConfig();
		NumObjectsReloaded++;
	}
	UE_LOG(LogHotfixManager, Log, TEXT("Updating config from %s took %f seconds and reloaded %d objects"),
		*FileName, FPlatformTime::Seconds() - StartTime, NumObjectsReloaded);
	return true;
}