逆向一款UE4游戏的提前准备工作。
main
map for generating a FName
FName TestHUDName = FName(TEXT("ThisIsMyTestFName"));
- FName::FName()
- FNameHelper::MakeDetectNumber()
- MakeWithNumber()
- Make()
- GetNamePool(); // 拿到单例的全局FNamePool 实例
- Pool.Store(View); // 将FName 试图添加到FNamePool 实例中
- ComparisonShards[ComparisonValue.Hash.ShardIndex].Insert(ComparisonValue, bAdded); // ComparisonShards 是FNamePool的一个成员, Insert方法调用的是
FNamePoolShard::Insert()
- Entries->Create(Value.Name, Value.ComparisonId, Value.Hash.EntryProbeHeader); // 为FNameStringVIew的字符串分配一块内存,并设置其所在的block,以及在block中的偏移offset,返回一个
FNameEntryHandle
对象 - ClaimSlot(Slot, FNameSlot(NewEntryId, Value.Hash.SlotProbeHash)); // 超出边界则调整边界大小,以及重新hash等操作
- Entries->Create(Value.Name, Value.ComparisonId, Value.Hash.EntryProbeHeader); // 为FNameStringVIew的字符串分配一块内存,并设置其所在的block,以及在block中的偏移offset,返回一个
- ComparisonShards[ComparisonValue.Hash.ShardIndex].Insert(ComparisonValue, bAdded); // ComparisonShards 是FNamePool的一个成员, Insert方法调用的是
- Make()
- MakeWithNumber()
- FNameHelper::MakeDetectNumber()
signleton
// NamePoolData 就是一块 sizeof(FNamePool) 大小的内存, 其作用就是为单例的 FNamePoll 提供一块内存而已
static bool bNamePoolInitialized;
alignas(FNamePool) static uint8 NamePoolData[sizeof(FNamePool)];
function
GetNamePool()
// Only call this once per public FName function called
//
// Not using magic statics to run as little code as possible
static FNamePool &GetNamePool()
{
// 已经初始化过,直接返回 NamePollData的指针
if (bNamePoolInitialized)
{
return *(FNamePool *)NamePoolData;
}
// 此处调用的不是调用malloc的new, 而是以 NamePollData这一块内存来调用 placement-params new
FNamePool *Singleton = new (NamePoolData) FNamePool;
bNamePoolInitialized = true;
return *Singleton;
}
Probe
/** Find slot containing value or the first free slot that should be used to store it */
FNameSlot &Probe(const FNameValue<Sensitivity> &Value) const
{
return Probe(Value.Hash.UnmaskedSlotIndex,
[&](FNameSlot Slot)
{ return Slot.GetProbeHash() == Value.Hash.SlotProbeHash &&
EntryEqualsValue<Sensitivity>(Entries->Resolve(Slot.GetId()), Value); });
}
/** Find slot that fulfills predicate or the first free slot */
template <class PredicateFn>
FNameSlot &Probe(uint32 UnmaskedSlotIndex, PredicateFn Predicate) const
{
// 遍历所有的Slots,通过对比哈希值来判断是否已经存在该字符串
const uint32 Mask = CapacityMask;
for (uint32 I = FNameHash::GetProbeStart(UnmaskedSlotIndex, Mask); true; I = (I + 1) & Mask)
{
FNameSlot &Slot = Slots[I];
if (!Slot.Used() || Predicate(Slot))
{
return Slot;
}
}
}
FNameEntryId::Insert
FNameEntryId Insert(const FNameValue<Sensitivity> &Value, bool &bCreatedNewEntry)
{
FRWScopeLock _(Lock, FRWScopeLockType::SLT_Write);
// 所谓的 Probe 其实就是check
FNameSlot &Slot = Probe(Value);
// 字符串已经存在,返回 id
if (Slot.Used())
{
return Slot.GetId();
}
// 此处调用了 FNamePool::Create方法,返回一个FNameEntry,此处强制转化为了 FNameEntryId
FNameEntryId NewEntryId = Entries->Create(Value.Name, Value.ComparisonId, Value.Hash.EntryProbeHeader);
ClaimSlot(Slot, FNameSlot(NewEntryId, Value.Hash.SlotProbeHash));
bCreatedNewEntry = true;
return NewEntryId;
}
FNamePoll::Store
FNameEntryId FNamePool::Store(FNameStringView Name)
{
#if WITH_CASE_PRESERVING_NAME
FNameDisplayValue DisplayValue(Name);
FNamePoolShard<ENameCase::CaseSensitive> &DisplayShard = DisplayShards[DisplayValue.Hash.ShardIndex];
if (FNameEntryId Existing = DisplayShard.Find(DisplayValue))
{
return Existing;
}
#endif
// buffer中已经存在的字符串的数量
TAtomic<uint32> &EntryCount = Name.IsAnsi() ? AnsiCount : WideCount;
bool bAdded = false;
// Insert comparison name first since display value must contain comparison name
// FNameComparisonValue 会对传入的FNameStringView进行hash等操作
FNameComparisonValue ComparisonValue(Name);
// 将当前的FName的hash值试图插入到保存字符串的表中,
FNameEntryId ComparisonId = ComparisonShards[ComparisonValue.Hash.ShardIndex].Insert(ComparisonValue, bAdded);
EntryCount += bAdded;
#if WITH_CASE_PRESERVING_NAME
// Check if ComparisonId can be used as DisplayId
if (bAdded || EqualsSameDimensions<ENameCase::CaseSensitive>(Resolve(ComparisonId), Name))
{
DisplayShard.InsertExistingEntry(DisplayValue.Hash, ComparisonId);
return ComparisonId;
}
else
{
bAdded = false;
DisplayValue.ComparisonId = ComparisonId;
FNameEntryId DisplayId = DisplayShard.Insert(DisplayValue, bAdded);
EntryCount += bAdded;
return DisplayId;
}
#else
return ComparisonId;
#endif
}
FNameHelper::Make()
static FName Make(FNameStringView View, EFindName FindType, int32 InternalNumber)
{
// 拿到存储字符串表的单例FNamePoll的句柄
FNamePool &Pool = GetNamePool();
FNameEntryId DisplayId, ComparisonId;
// 本次操作的类型是添加 StringView 到NamePool中
if (FindType == FNAME_Add)
{
DisplayId = Pool.Store(View);
#if WITH_CASE_PRESERVING_NAME
ComparisonId = Pool.Resolve(DisplayId).ComparisonId;
#else
ComparisonId = DisplayId;
#endif
}
// 本次操作的类型是从NamePool查找StringView 到
else if (FindType == FNAME_Find)
{
DisplayId = Pool.Find(View);
#if WITH_CASE_PRESERVING_NAME
ComparisonId = DisplayId ? Pool.Resolve(DisplayId).ComparisonId : DisplayId;
#else
ComparisonId = DisplayId;
#endif
}
// other
else
{
check(FindType == FNAME_Replace_Not_Safe_For_Threading);
#if FNAME_WRITE_PROTECT_PAGES
checkf(false, TEXT("FNAME_Replace_Not_Safe_For_Threading can't be used together with page protection."));
#endif
DisplayId = Pool.Store(View);
#if WITH_CASE_PRESERVING_NAME
ComparisonId = Pool.Resolve(DisplayId).ComparisonId;
#else
ComparisonId = DisplayId;
#endif
ReplaceName(Pool.Resolve(ComparisonId), View);
}
return FName(ComparisonId, DisplayId, InternalNumber);
}
class
FNameEntry
struct FNameEntry
{
private:
#if WITH_CASE_PRESERVING_NAME
FNameEntryId ComparisonId;
#endif
FNameEntryHeader Header;
union
{
ANSICHAR AnsiName[NAME_SIZE];
WIDECHAR WideName[NAME_SIZE];
};
...
};
FNameHash
/** Hashes name into 64 bits that determines shard and slot index.
*
* Small parts of the hash is also stored in unused bits of the slot and entry.
* The former optimizes linear probing by accessing less entry data.
* The latter optimizes linear probing by avoiding copying and deobfuscating entry data.
*
* The slot index could be stored in the slot, at least in non shipping / test configs.
* This costs memory by doubling slot size but would essentially never touch entry data
* nor copy and deobfuscate a name needlessy. It also allows growing the hash table
* without rehashing the strings, since the unmasked slot index would be known.
*/
struct FNameHash
{
uint32 ShardIndex;
uint32 UnmaskedSlotIndex; // Determines at what slot index to start probing
uint32 SlotProbeHash; // Helps cull equality checks (decode + strnicmp) when probing slots
FNameEntryHeader EntryProbeHeader; // Helps cull equality checks when probing inspects entries
template <class CharType>
FNameHash(const CharType *Str, int32 Len)
{
// 将字符串hash得到一个uint64,高32位计算后可用作ShardIndex和SlotProbeHash,
// 低32位直接被当作 UnmaskedSlotIndex
uint64 Hash = CityHash64(reinterpret_cast<const char *>(Str), Len * sizeof(CharType));
uint32 Hi = static_cast<uint32>(Hash >> 32);
uint32 Lo = static_cast<uint32>(Hash);
// "None" has FNameEntryId with a value of zero
// Always set a bit in SlotProbeHash for "None" to distinguish unused slot values from None
// @see FNameSlot::Used()
uint32 IsNoneBit = IsAnsiNone(Str, Len) << FNameSlot::ProbeHashShift;
static constexpr uint32 ShardMask = FNamePoolShards - 1;
static_assert((ShardMask & FNameSlot::ProbeHashMask) == 0, "Masks overlap");
ShardIndex = Hi & ShardMask;
UnmaskedSlotIndex = Lo;
SlotProbeHash = (Hi & FNameSlot::ProbeHashMask) | IsNoneBit;
EntryProbeHeader.Len = Len;
EntryProbeHeader.bIsWide = sizeof(CharType) == sizeof(WIDECHAR);
// When we always use lowercase hashing, we can store parts of the hash in the entry
// to avoid copying and decoding entries needlessly. WITH_CUSTOM_NAME_ENCODING
// that makes this important is normally on when WITH_CASE_PRESERVING_NAME is off.
#if !WITH_CASE_PRESERVING_NAME
static constexpr uint32 EntryProbeMask = (1u << FNameEntryHeader::ProbeHashBits) - 1;
EntryProbeHeader.LowercaseProbeHash = static_cast<uint16>((Hi >> FNamePoolShardBits) & EntryProbeMask);
#endif
}
uint32 GetProbeStart(uint32 SlotMask) const
{
return UnmaskedSlotIndex & SlotMask;
}
static uint32 GetProbeStart(uint32 UnmaskedSlotIndex, uint32 SlotMask)
{
return UnmaskedSlotIndex & SlotMask;
}
static uint32 IsAnsiNone(const WIDECHAR *Str, int32 Len)
{
return 0;
}
static uint32 IsAnsiNone(const ANSICHAR *Str, int32 Len)
{
if (Len != 4)
{
return 0;
}
#if PLATFORM_LITTLE_ENDIAN
static constexpr uint32 NoneAsInt = 0x454e4f4e;
#else
static constexpr uint32 NoneAsInt = 0x4e4f4e45;
#endif
static constexpr uint32 ToUpperMask = 0xdfdfdfdf;
uint32 FourChars = FPlatformMemory::ReadUnaligned<uint32>(Str);
return (FourChars & ToUpperMask) == NoneAsInt;
}
};
FName
FName是ue4引擎提供的一种轻量级字符串使用方式,其类定义在Engine\Source\Runtime\Core\Public\UObject\NameTypes.h
文件中。
/**
* Public name, available to the world. Names are stored as a combination of
* an index into a table of unique strings and an instance number.
* Names are case-insensitive, but case-preserving (when WITH_CASE_PRESERVING_NAME is 1)
*/
class CORE_API FName {
...
private:
/** Index into the Names array (used to find String portion of the string/number pair used for comparison) */
// 当前FName所保存的字符串在Names array中的index
FNameEntryId ComparisonIndex;
#if WITH_CASE_PRESERVING_NAME
/** Index into the Names array (used to find String portion of the string/number pair used for display) */
FNameEntryId DisplayIndex;
#endif
/** Number portion of the string/number pair (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) */
// 实例的数量,对于[字符串+数组]这种情况,对于空串而言,Number为0,也就是没有实例
uint32 Number;
};
FNameEntryHandle
/** An unpacked FNameEntryId */
struct FNameEntryHandle
{
uint32 Block = 0;
uint32 Offset = 0;
FNameEntryHandle(uint32 InBlock, uint32 InOffset)
: Block(InBlock)
, Offset(InOffset)
{}
FNameEntryHandle(FNameEntryId Id)
: Block(Id.ToUnstableInt() >> FNameBlockOffsetBits)
, Offset(Id.ToUnstableInt() & (FNameBlockOffsets - 1))
{}
operator FNameEntryId() const
{
return FNameEntryId::FromUnstableInt(Block << FNameBlockOffsetBits | Offset);
}
explicit operator bool() const { return Block | Offset; }
};
FNameEntryId
/** Opaque id to a deduplicated name */
struct FNameEntryId
{
...
private:
uint32 Value;
};
FNameEntryAllocator
/** Remember to update natvis if you change these */
enum { FNameMaxBlockBits = 13 }; // Limit block array a bit, still allowing 8k * block size = 1GB - 2G of FName entry data
enum { FNameBlockOffsetBits = 16 };
enum { FNameMaxBlocks = 1 << FNameMaxBlockBits };
enum { FNameBlockOffsets = 1 << FNameBlockOffsetBits };
class FNameEntryAllocator
{
...
mutable FRWLock Lock;
uint32 CurrentBlock = 0;
uint32 CurrentByteCursor = 0;
uint8* Blocks[FNameMaxBlocks] = {}; // FNameMaxBlocks = 8192
};
FNamePool
FNamePoll
定义在Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
中,其在全局中只有一个单例实例。
static bool bNamePoolInitialized;
alignas(FNamePool) static uint8 NamePoolData[sizeof(FNamePool)];
enum { FNamePoolShards = 1 << FNamePoolShardBits };
class FNamePool
{
...
private:
FNameEntryAllocator Entries;
TAtomic<uint32> AnsiCount;
TAtomic<uint32> WideCount;
#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;
};