-
Site Donator
Playername from cache?
Hello,
im having some trouble figuring out how the name cache is structured and how to grab a player name from it. is there anyone who could help me understand it? i've been staring at it in IDA for hours and i've tried bruteforcing but my brain is starting to hurt.
-
Elite User
doing this while rolling so mind the meh but its basically a object manager loop as well used a few other things
Code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PlayerCacheEntry
{
public IntPtr next;
public UInt128 guid;
public Byte pad;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x30)]
public Byte[] name;
}
public override string Name
{
get
{
try
{
int count = ProcessMemory.Read<int>(ProcessMemory.BaseAddress + (int)Pointers.UnitName.PlayerNameCachePointer + 0x8);
IntPtr startAddress = ProcessMemory.Read<IntPtr>(ProcessMemory.BaseAddress + (int)Pointers.UnitName.PlayerNameCachePointer + 0x10);
IntPtr address = ProcessMemory.Read<IntPtr>(startAddress);
for (int x = 0; x < count; x++)
{
var entry = ProcessMemory.Read<PlayerCacheEntry>(ProcessMemory.Read<IntPtr>(startAddress + (0x8 * x)));
var entryName = ProcessMemory.UTF8Decoder(entry.name);
var target = entry.next;
while (target != IntPtr.Zero)
{
var inner = ProcessMemory.Read<PlayerCacheEntry>(target);
target = inner.next;
}
if (address == IntPtr.Zero)
{
// current entry is empty, go to the next one
x++;
address = ProcessMemory.Read<IntPtr>(startAddress + 0x8 * x);
//Logging.Write(string.Format("[PlayerName] address: {0}", address));
continue;
}
PlayerCacheEntry data = ProcessMemory.Read<PlayerCacheEntry>(address);
//Logging.Write(string.Format("[PlayerName] dataguid: {0}", data.guid));
if (data.guid == GUID)
{
String name = ProcessMemory.UTF8Decoder(data.name);
//Logging.Write(string.Format("[PlayerName] dataname: {0}", data.name));
if (name != "")
{
return name;
}
}
//Logging.Write(string.Format("[PlayerName] datanext: {0}", data.name));
if (data.next == IntPtr.Zero)
{
// end of the current chain, go to next array entry
x++;
address = ProcessMemory.Read<IntPtr>(startAddress + 0x8 * x);
}
else
{
// go to next cache entry in this chain, do not increment x
address = data.next;
}
//Logging.Write(string.Format("[PlayerName] namesssssss: {0}", data.name));
}
}
catch
{
// return "Error Reading Name";
}
return "Error Reading Name";
}
}
//private string _name
// public override string Name => GetPlayerNameCacheEntries().FirstOrDefault(x => x.guid == GUID).Select(x => Encoding.ASCII.GetString(x));
private IEnumerable<PlayerCacheEntry> GetPlayerNameCacheEntries()
{
var nameCount = ProcessMemory.Read<int>(ProcessMemory.BaseAddress + (int)Pointers.UnitName.PlayerNameCachePointer + 0x8);
var startAddress = ProcessMemory.Read<IntPtr>(ProcessMemory.BaseAddress + (int)Pointers.UnitName.PlayerNameCachePointer + 0x10);
for (var i = 0; i < nameCount; i++)
{
var entry = ProcessMemory.Read<PlayerCacheEntry>(ProcessMemory.Read<IntPtr>(startAddress + (0x8 * i)));
var entryName = ProcessMemory.UTF8Decoder(entry.name);
yield return entry;
var target = entry.next;
while (target != IntPtr.Zero)
{
var inner = ProcessMemory.Read<PlayerCacheEntry>(target);
yield return inner;
target = inner.next;
}
}
}
private string GetNameForGuid(UInt128 guid, IEnumerable<PlayerCacheEntry> entries)
{
var nameCacheEntry = entries.FirstOrDefault(x => x.guid == guid);
if (nameCacheEntry.guid == 0)
{
return string.Empty;
}
return ProcessMemory.UTF8Decoder(nameCacheEntry.name);
}
Last edited by charles420; 05-28-2025 at 11:25 AM.
-
Post Thanks / Like - 1 Thanks
chaosrage (1 members gave Thanks to charles420 for this useful post)
-
Site Donator
-
Site Donator
fiiiiiinally figured it out! the names were packed in 8-byte chunks starting at an offset within the name field, and I also had to correctly handle a leading null character before the actual name
Code:
public string GetName()
{
if (nameBytes == null) return string.Empty;
// Relevant QWORDs for name data within nameBytes array:
// QWORD0_CEPattern (often null): nameBytes[7..14] (EntryBasePtr + 0x20)
// QWORD1_NamePart1: nameBytes[15..22] (EntryBasePtr + 0x28)
// QWORD2_NamePart2: nameBytes[23..30]
// QWORD3_NamePart3: nameBytes[31..38]
// QWORD4_NamePart4: nameBytes[39..46]
int[] nameQwordStartOffsetsInNameBytes = { 15, 23, 31, 39 };
List<byte> collectedCharBytes = new List<byte>(32); // Max 32 chars
bool nameTerminatedInCache = false;
for (int i = 0; i < nameQwordStartOffsetsInNameBytes.Length; i++)
{
int currentQwordStartOffset = nameQwordStartOffsetsInNameBytes[i];
if (currentQwordStartOffset + 7 >= nameBytes.Length) break;
for (int k = 0; k < 8; k++)
{
byte charByte = nameBytes[currentQwordStartOffset + k];
if (charByte == 0)
{
if (i == 0 && k == 0 && collectedCharBytes.Count == 0)
{
// continue to the next byte in this QWORD
}
else
{
nameTerminatedInCache = true;
break; // Stop processing this QWORD
}
}
else
{
collectedCharBytes.Add(charByte);
}
}
if (nameTerminatedInCache)
{
break;
}
}
if (collectedCharBytes.Count == 0)
{
return string.Empty;
}
return Encoding.UTF8.GetString(collectedCharBytes.ToArray(), 0, collectedCharBytes.Count);
}
thank you for pointing me in the right direction