UE4 reverse engineering-1_FNamePool

write in front

1.There is a difference between versions below UE4.23 and above 4.23. The UE source code used by the author is 4.27.

2.UE4 is an open source GitHub project, but if you want to download its source code, you need to bind your epic account to your GitHub account, and then you will receive an email to join the GitHub organization.

https://github.com/EpicGames

1. Before you start

1. Since C++ does not have reflection, and the reflection implemented by UE itself exposes a lot of interfaces, we can make use of these exposed interfaces.

2.GNames is an array of Unicode strings.

3.GObject is an array of class pointers.

After having GObjects, you can access most objects in the game, and GName corresponds to the name of its object?

With this information, an SDK can be generated to modify the game.

2. GNames

a.What are Gnames?

1.GNames 指的是 alignas(FNamePool) static uint8 NamePoolData[sizeof(FNamePool)];

2.This is alsoNamePoolData.

3. It is an array structure.

b.NamePoolData/FNamePool

The following is the prototype of the structure:


class FNamePool
{
public:
	FNamePool();
	
	void			Reserve(uint32 NumBlocks, uint32 NumEntries);
	FNameEntryId	Store(FNameStringView View);
	FNameEntryId	Find(FNameStringView View) const;
	FNameEntryId	Find(EName Ename) const;
	const EName*	FindEName(FNameEntryId Id) const;

	/** @pre !!Handle */
	FNameEntry&		Resolve(FNameEntryHandle Handle) const { return Entries.Resolve(Handle); }

	bool			IsValid(FNameEntryHandle Handle) const;

	FNameEntryId	StoreValue(const FNameComparisonValue& Value);
	void			StoreBatch(uint32 ShardIdx, TArrayView<FNameComparisonLoad> Batch)	{ ComparisonShards[ShardIdx].InsertBatch(Batch); }
#if WITH_CASE_PRESERVING_NAME
	FNameEntryId	StoreValue(const FNameDisplayValue& Value, bool bReuseComparisonId);
	void			StoreBatch(uint32 ShardIdx, TArrayView<FNameDisplayLoad> Batch)		{ DisplayShards[ShardIdx].InsertBatch(Batch); }
	bool			ReuseComparisonEntry(bool bAddedComparisonEntry, const FNameDisplayValue& DisplayValue);
#endif
	/// Stats and debug related functions ///

	uint32			NumEntries() const;
	uint32			NumAnsiEntries() const;
	uint32			NumWideEntries() const;
	uint32			NumBlocks() const { return Entries.NumBlocks(); }
	uint32			NumSlots() const;
	void			LogStats(FOutputDevice& Ar) const;
	uint8**			GetBlocksForDebugVisualizer() { return Entries.GetBlocksForDebugVisualizer(); }
	TArray<const FNameEntry*> DebugDump() const;

private:
	enum { MaxENames = 512 };

	FNameEntryAllocator Entries;

#if WITH_CASE_PRESERVING_NAME
	FNamePoolShard<ENameCase::CaseSensitive> DisplayShards[FNamePoolShards];
#endif
	FNamePoolShard<ENameCase::IgnoreCase> ComparisonShards[FNamePoolShards];

	// Put constant lookup on separate cache line to avoid it being constantly invalidated by insertion
	alignas(PLATFORM_CACHE_LINE_SIZE) FNameEntryId ENameToEntry[NAME_MaxHardcodedNameIndex] = {};
	uint32 LargestEnameUnstableId;
	TMap<FNameEntryId, EName, TInlineSetAllocator<MaxENames>> EntryToEName;
};

c.FNameEntryAllocator

1. We can ignore most of the FNamePool structure.

2. We need to focus more on:FNameEntryAllocator Entries;


class FNamePool
{
private:
	FNameEntryAllocator Entries;
};

It should be noted that FNameEntryAllocator is not a pointer, but a complete structure.

Think of it as a FNamePool.

FNamePool = FNameEntryAllocator。

Again, most of this structure can be ignored and focus on the following:

static constexpr uint32 FNameMaxBlocks = 1 << FNameMaxBlockBits;
class FNameEntryAllocator
{
	mutable FRWLock Lock;                    //0x0000
	uint32 CurrentBlock = 0;                 //0x0008
	uint32 CurrentByteCursor = 0;            //0x000C
	uint8* Blocks[FNameMaxBlocks] = {};      //0x0010
};

What is stored in FName is actually a bunch of strings, and what does the content in the string look like?

Next we start looking for it.

3. Find NamePoolData

1. Finding NamePoolData is actually very simple. You just need to open the game and use CE to attach the game.

2. Search string: MulticastDelegateProperty, be careful not to check UTF-16 (wide character).

3. After searching for the string and browsing the memory-related area, use the shortcut key Ctrl+B and scroll up:

You can see that the memory area is similar to .None....ByteProperty....IntProperty.

4. At this time, push 6 bytes forward from None, that is:...None, and record the address:

Record the address, return to CE to perform a new scan, and check HEX to scan 8 bytes:

If it cannot be scanned, you can try adjusting the writable options of CE.

Notice:

You can try to push 2 bytes previously to get the address for search. If the search cannot be found, push more bytes forward.

If the game cannot be searched by pushing forward 2 bytes, the author will directly replace the last digit of the address with 0 (that is, push forward by 6 bytes) and the search can be found.

Different games may be different.

Scanned address: "DeadByDaylight-Win64-Shipping.exe"+D20C610

It points to uint8* Blocks[FNameMaxBlocks] = {};  in FNameEntryAllocator

static constexpr uint32 FNameMaxBlocks = 1 << FNameMaxBlockBits;
class FNameEntryAllocator
{
	mutable FRWLock Lock;                    //0x0000
	uint32 CurrentBlock = 0;                 //0x0008
	uint32 CurrentByteCursor = 0;            //0x000C
	uint8* Blocks[FNameMaxBlocks] = {};      //0x0010
};

5. Now, just use the scanned address minus Blocks(uint8* Blocks[FNameMaxBlocks] = {};) Offset 0x10, you can get FNamePool, use ReClass to verify:

1. Open ReClass and open the file and select FNamePool.reclass in the attachment.

2. Click the Select button to select the game process.

3. Click the Edit button, select FNamePool, and fill in the address for verification.

At this point, the work of finding NamePoolData is completed. Names can be exported by modifying the source code of UnrealDumper:

Guess you like

Origin blog.csdn.net/qq_18120361/article/details/133932030