UE4 - IDA定位查找GameInstance/Actors/ULevel

定位GameInstance

UPROPERTY()
class UGameInstance* OwningGameInstance;

他在UWorld类里面定义如上代码,但是我们要怎么找到他的偏移,神经病一样自问自答...

虚幻引擎内这个OwningGameInstance是私有的外部不能直接获取,是通过一个方法获取的,虽然编译后和直接获取没什么区别(

if (GetWorld() == nullptr)
{
	UE_LOG(LogDemo, Error, TEXT("GetWorld() == nullptr"));
	return false;
}

if (GetWorld()->GetGameInstance() == nullptr)
{
	UE_LOG(LogDemo, Error, TEXT("GetWorld()->GetGameInstance() == nullptr"));
	return false;
}

你看,这里有调用获取的方法,还带上了我们最喜欢的日志!虚幻引擎真的是太好玩了!

没啥意思,再见!

ULevel的定位

这里这个定位,我会介绍两种方法,一种是市面上比较泛滥的,一种是对照源码比较直观的!

第一种

这种方法比较不明所以,是我从网上学来的,虽然我不知道对应的源代码在什么地方,但是这个定位方法好像是依赖一块打印输出当初帧率CPU占用的代码的,

对GWorld进行xrefs,然后找下面有相同的两次CALL(arm64是BL)的汇编

然后我们IDA跳过去,兜兜转转又回到了这里,

第二种

我们看看下面的一个代码,这个是完整的代码,没有任何阉割!

	GWorld = NewWorld;

	WorldContext.SetCurrentWorld(NewWorld);
	WorldContext.World()->WorldType = WorldContext.WorldType;

#if !UE_BUILD_SHIPPING
	GWorld->bCreateRenderStateForHiddenComponents = bOldWorldWasShowingCollisionForHiddenComponents;
#endif

	// Fixme: hacky but we need to set PackageFlags here if we are in a PIE Context.
	// Also, don't add to root when in PIE, since PIE doesn't remove world from root
	if (WorldContext.WorldType == EWorldType::PIE) // xxx == 3
	{
		check(WorldContext.World()->GetOutermost()->HasAnyPackageFlags(PKG_PlayInEditor));
		WorldContext.World()->ClearFlags(RF_Standalone);
	}
	else
	{
		WorldContext.World()->AddToRoot();
	}

	// In the PIE case the world will already have been initialized as part of CreatePIEWorldByDuplication
	if (!WorldContext.World()->bIsWorldInitialized)
	{
		WorldContext.World()->InitWorld();
	}

	// Handle pending level.
	if( Pending )
	{
		check(Pending == WorldContext.PendingNetGame);
		MovePendingLevel(WorldContext);
	}
	else
	{
		check(!WorldContext.World()->GetNetDriver());
	}

	WorldContext.World()->SetGameMode(URL);

	if (FAudioDevice* AudioDevice = WorldContext.World()->GetAudioDevice())
	{
		AudioDevice->SetDefaultBaseSoundMix(WorldContext.World()->GetWorldSettings()->DefaultBaseSoundMix);
	}

我们发现GWorld = NewWorld;是一处赋值语句,我们定位出GWorld之后可以全局(就是去xrefs)去找这种赋值的操作,缩小一下查找范围,接下来我们找if (WorldContext.WorldType == EWorldType::PIE)其实这在ida里面对应的就是

这在GWorld = NewWorld之后必然有一个xxx == 3的操作,我们的目的是要找一下GetWorldSettings的方法,下面是UWorld::GetWorldSettings的源代码,我们发现里面对PersistentLevel进行了调用。

AWorldSettings* UWorld::GetWorldSettings( const bool bCheckStreamingPersistent, const bool bChecked ) const
{
	checkSlow(!IsInActualRenderingThread());
	AWorldSettings* WorldSettings = nullptr;
	if (PersistentLevel)
	{
		WorldSettings = PersistentLevel->GetWorldSettings(bChecked);

		if( bCheckStreamingPersistent )
		{
			if( StreamingLevels.Num() > 0 &&
				StreamingLevels[0] &&
				StreamingLevels[0]->IsA<ULevelStreamingPersistent>()) 
			{
				ULevel* Level = StreamingLevels[0]->GetLoadedLevel();
				if (Level != nullptr)
				{
					WorldSettings = Level->GetWorldSettings(bChecked);
				}
			}
		}
	}
	return WorldSettings;
}

回到ida,对应的第一个Level::GetWorldSettings的入参就是ULevel啦!

Actor的定位

这个的定位其实最好是通过伟大的内存搜索器,比如CE、GG修改器什么的去做,因为在ULevel里面保存Actors的是一个TArray一个模板类,

class TARRAT<T> {
  T* data;
  dword size;
}

我们定位到ULevel之后,可以去翻内存,找一个指针,并且下面偏移8字节有一个数组大小的东西,那个大概率是Actors(也可能是ActorsForGC)... 回到正题!

else if (WorldType != EWorldType::Inactive && !IsRunningCommandlet())
{
	// Inactive worlds do not have a world context, otherwise only worlds in the middle of seamless travel should have no context,
	// and in that case, we shouldn't be destroying actors on them until they have become the current world (i.e. CopyWorldData has been called)
	UE_LOG(LogSpawn, Warning, TEXT("UWorld::DestroyActor: World has no context! World: %s, Actor: %s"), *GetName(), *ThisActor->GetPathName());
}

// Remove the actor from the actor list.
RemoveActor( ThisActor, bShouldModifyLevel );

先贴一段源代码,我们搜索字符串UWorld::DestroyActor: World has no context! 。搜索这个干什么呢?为了找RemoveActor !这对我们的大聪明来说轻而易举好吧,

这里贴一下UWorld::RemoveActor源代码

void UWorld::RemoveActor(AActor* Actor, bool bShouldModifyLevel) const
{
	ULevel* CheckLevel = Actor->GetLevel();
	const int32 ActorListIndex = CheckLevel->Actors.Find( Actor );
	// Search the entire list.
	if( ActorListIndex != INDEX_NONE )
	{
		if ( bShouldModifyLevel && GUndo )
		{
			ModifyLevel( CheckLevel );
		}
		
		if (!IsGameWorld())
		{
			CheckLevel->Actors[ActorListIndex]->Modify();
		}
		
		CheckLevel->Actors[ActorListIndex] = nullptr;
	}

	// Remove actor from network list
	RemoveNetworkActor( Actor );
}

因为呢编译器会把Find那个玩意内联优化,可能反汇编里面的结构不太一致,

但是我贴个这个总能看明白了吧!!!!!!!!!