-
Member
WoW Classic 1.15.5.57638 Object Manager Changes
Got most of my pointers updated after the classic era/SOD client patch today but it seems like object manager must've had some changes following similar changes in the retail client last month.
Code:
# 1.15.5.57638
object_manager = 0x38317F8 # not verified. big object manager changes? object array and max object count seem to have migrated... or my pointer is incorrect.
player_guid = 0x3776F70
mouseover_guid = 0x3891530
chat_frame_open = 0x0 # no working ptr as of 1.15.4.x; BinDiff and ByteSigs failed to locate suitable replacement function
zone_text = 0x3890778
subzone_text = zone_text + 0x8
minimap_zone_text = subzone_text + 0x8
# target guid ptrs
player_target_guid = 0x355D0B8
# camera
camera_manager = 0x3772B18
Trying to go back and compare 1.15.4.x object manager code in IDA to the new stuff but not making much head way thus far.
If anyone is willing to share their progress it would be muchly appreciated!
Also it looks like some other offsets moved around. for player inventory fields I have "player_field_inventory = 0x138E8 + 0x8"
UnitChannelInfo might've moved to 0x6F4, 0x6F8, 0x6FC (channel_spell, channel_start, channel_end)
Need to verify both of those but can't do it until I have object base addresses to test with.
Fortunately Camera stuff seems to be the same (0x3A48 offset for camera from camera manager ptr).
I'm hoping object offsets haven't moved around too much but again, not there yet.
Thanks again, folks.
-
Member
Im on the same page as you, almost all my objMgr sigs from last patch are no longer usable except for one which leads me to 0x38317F8, however i can not find any useful imformation at this address, although it does reset to 0 when i logged out.
const UINT64 OBJECT_MANAGER_OFFSET = 0x38317F8;
const UINT64 MOUSEOVER_GUID_OFFSET = 0x3891530;
const UINT64 CORPSE_POS_GUID_OFFSET = 0x3305280 + 0x40;
const UINT64 LAST_TARGET_GUID_OFFSET = 0x355D0C8;
const UINT64 TARGET_GUID_OFFSET = 0x355D0B8;
Hope someone can clarify this, until then ill keep up my own research
-
Member
-
Member
That is what i have come up thus far.
Keep in mind that a very similar approach is being used on 11.0.7 PTR at the moment with an extra step of them using an int* IndexArray to get the index of the EntityEntry.
Code:
struct WowGUID
{
uint64_t lo;
uint64_t hi;
};
struct WowObject
{
void** vftable;
byte object_type; // 0x8
char fill[7];
uint64_t EntityIndex; // 0x10
WowGUID guid; // 0x18
}
// offsets[flags[0]] has the offset of the object ptr ( which is always 0x20 atm but the client always gets it from this array)
// flags[0] != 0xFF means valid entry
// flags[0x12] != 0xFF means Active Object
// flags[0x13] != 0xFF means Visible Object
struct EntityCategory // size 0xC0
{
int32_t *offsets;
byte* flags;
char fill[0xb0];
}
// (EntityIndex >> 54) == EntryHash this determines on which ObjectManager the object belongs to
// (EntityIndex & 0x3FFFFFFF) this gives the index of the ObjectEntity array
struct ObjectEntity
{
int32_t CategoryIndex;
int32_t gap;
uint64_t EntityIndex;
WowGUID guid;
void* object;
}
// No longer a chained hash list, instead it goes through a linear expansion
// previous ((guid->lo * 0xD6D018F5 + guid->hi * 0xA2AA033B) % EntitiesSize) can still be used to access the "first" index
// and keep walking forward index = (++index % EntitiesSize) until we find an empty entry [0,0,?]
// NOTE: Clients skips over [1,0x400000000000000,0] entries as they point to "none" unitID
// (EntityIndex >> 54) == EntryHash this determines on which ObjectManager the object belongs to
// (EntityIndex & 0x3FFFFFFF) this gives the index of the ObjectEntity array
struct HashEntry
{
WowGUID guid;
uint64_t EntityIndex;
}
struct ObjectManager
{
int32_t EntryHash;
uint32_t gap;
ObjectEntity** entities; // 0x8
uint64_t EntitiesSize; // 0x10
char fill[0x28];
uint64_t HashSize; // 0x40
HashEntry* hashes; // 0x48 // the hashes array seems to be shared across all ObjectManager objects
uint64_t gap2;
uint64_t IsUsingOld; // 0x58 when EntryHash == -1 aka the previous Om is being used? / copied over?
uint64_t gap3;
EntityCategory* categories; // 0x68
}
// EntryHash -1 means previous Om (Can be seen after logout)
// EntryHash -2 means invalid entry
// NOTE: The Client does AcquireSRWLockShared so the access to OmArray is multi-threaded
// also it uses a new hashing algorithm for determining the index of the EntryHash inside the OmArray
// but still keeps walking it "forward" (++index % OmArraySize) until it hits -2 EntryHash or a match
struct ObjectManagerEntry
{
int32_t EntryHash;
uint32_t gap;
ObjectManager* entry;
}
// 1.15.5.57638
// uint64_t* OmArraySize = 0x32AA510
// ObjectManagerEntry* OmArray = 0x32AA518
// ObjectManager* WowCSMain = 0x3831808
// fGetObjectMatrix = 0x1B36F70
// fGetOmForObject = 0x0519080 // fGetOmForObject(EntityIndex >> 54) returns ObjectManager* entry or nullptr
The ObjectTransformationMatrix can be constructed from the TransportAngle as well
WorldMatrix.png
Just keep in mind that Wow uses a Column Based array system for Matrixes ( CameraMatrix ObjectMatrix etc ) while the Image above i have no idea if it used a column or a Row based Matrix.
-
Post Thanks / Like - 2 Thanks
-
Active Member
seek help.
I missed the WowClassic.exe file for game version 1.15.5.57638 that I used for my IDA.
If there is no original file, decrypted WowClassic.exe can be accepted.
Please send the file to my email, thank you very much!
My email [email protected]
Last edited by gdfsxwy; 2 Weeks Ago at 08:03 PM.
-
Member
Originally Posted by
gdfsxwy
seek help.
I missed the WowClassic.exe file for game version 1.15.5.57638 that I used for my IDA.
If there is no original file, decrypted WowClassic.exe can be accepted.
Please send the file to my email, thank you very much!
My email
[email protected]
I uploaded my copy of the 1.15.5.57638 to filebin for ya. It was unpacked with namreeb's wowdump. Hope this helps.
Filebin | 4z8df3jngbbdopo0
-
Post Thanks / Like - 1 Thanks
gdfsxwy (1 members gave Thanks to dreadcraft for this useful post)
-
Member
Even more fun! Blizzard just took the servers down for an hour (supposedly to increase the time between mob respawns for the crystal/AQ world event). When servers came back up we had another client update.
Here is a 7zipped copy of the 1.15.5.57716 client, again unpacked with namreeb's tool:
Filebin | vk31omgvlw8sms3j
I haven't had a chance to look at anything in it yet. After looking at how badly obfuscated the "ObjectName" function was on the last build I'm definitely going to need some kind of non-free IDA build that will allow me to use the python api for de-obfuscating all of the non-sensical JMPs.
IDA Free is awesome and I wish they had done it years ago but sometimes you need all the tools at your disposal.
Not sure if anyone has been using Ghidra lately but it seems to take so much longer to auto-analyze than IDA does.
-
Active Member
Originally Posted by
dreadcraft
I uploaded my copy of the 1.15.5.57638 to filebin for ya. It was unpacked with namreeb's wowdump. Hope this helps.
Filebin | 4z8df3jngbbdopo0
thank you!
-
-
Post Thanks / Like - 1 Thanks
dreadcraft (1 members gave Thanks to Razzue for this useful post)
-
-
Member
Originally Posted by
Razzue
For anyone who may prefer c#... :P
Code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ExampleGuid
{
public long Value1;
public long Value2;
public long Hash
=> unchecked((0xA2AA033B * Value2) + (0xD6D018F5 * Value1));
public bool IsEmpty()
=> Value2 == 0 && Value1 == 0;
public bool IsValid()
=> !(Value1 == 1 && Value2 == 0x400000000000000);
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct ActivePlayerData
{
[FieldOffset(0x10)]
public ExampleGuid Guid;
}
Code:
[StructLayout(LayoutKind.Sequential, Pack = 1)]public struct EntityHashEntry
{
public ExampleGuid Guid;
public long EntityIndex;
public long Hash
=> EntityIndex >> 54;
public long Index
=> EntityIndex & 0x3FFFFFFF;
}
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x50)]
public struct EntityBuilder
{
[FieldOffset(0x4)]
public int Index;
[FieldOffset(0x10)]
public ExampleGuid Guid;
[FieldOffset(0x20)]
public IntPtr Pointer;
public int GuidType
=> (int)((Guid.Value2 >> 58) & 0x3F);
public int EntityType
=> Memory.Read<byte>(Pointer + 0x8);
}
Code:
[StructLayout(LayoutKind.Explicit, Pack = 1)]
public struct ObjectManagerData
{
[FieldOffset(0x8)]
public IntPtr EntityArrayPointer;
[FieldOffset(0x10)]
public int EntityArrayCount;
public EntityBuilder GetEntityBuilder(EntityHashEntry entry)
{
try { return Memory.Read<EntityBuilder>(Memory.Read<IntPtr>(EntityArrayPointer + ((int)entry.Index * 0x8))); }
catch (Exception e) { Console.WriteLine(e); }
return default;
}
[FieldOffset(0x40)]
public int HashArrayMaximum;
[FieldOffset(0x48)]
public IntPtr HashArrayPointer;
[FieldOffset(0x50)]
public int HashArrayCount;
public EntityHashEntry GetHashEntry(ExampleGuid guid)
{
try
{
if (guid.IsEmpty() || !guid.IsValid()) return default;
var index = guid.Hash & (HashArrayMaximum - 1);
var result = Memory.Read<EntityHashEntry>(HashArrayPointer + ((int)index * Marshal.SizeOf<EntityHashEntry>()));
return result;
}
catch (Exception e) { Console.WriteLine(e); }
return default;
}
}
[StructLayout(LayoutKind.Explicit, Pack = 1)]public struct ExampleObjectManager
{
[FieldOffset(0x0)]
public IntPtr ActivePlayerPointer;
public ActivePlayerData ActivePlayer
=> ActivePlayerPointer != IntPtr.Zero ? Memory.Read<ActivePlayerData>(ActivePlayerPointer) : default;
public EntityBuilder GetActivePlayer()
{
try
{
var guid = ActivePlayer.Guid;
if (guid.IsEmpty() || !guid.IsValid()) return default;
var hashEntry = ManagerData.GetHashEntry(guid);
return ManagerData.GetEntityBuilder(hashEntry);
}
catch (Exception e) { Console.WriteLine(e); }
return default;
}
[FieldOffset(0x10)]
public IntPtr ManagerDataPointer;
public ObjectManagerData ManagerData
=> ActivePlayerPointer != IntPtr.Zero ? Memory.Read<ObjectManagerData>(ManagerDataPointer) : default;
public EntityBuilder GetBuilder(ExampleGuid guid)
{
try
{
if (guid.IsEmpty() || !guid.IsValid()) return default;
var hashEntry = ManagerData.GetHashEntry(guid);
return hashEntry.Index < 0 ? default :
ManagerData.GetEntityBuilder(hashEntry);
}
catch (Exception e) { Console.WriteLine(e); }
return default;
}
}
Usage: ( seems to work fine so far *knock on wood* )
Code:
var objectManager = Memory.Read<ExampleObjectManager>(address + omOffset);var player = objectManager.GetActivePlayer();
Console.WriteLine($"Player [{player.Pointer.ToInt64():X}]: Entity Type: {player.EntityType} | Guid Type: {player.GuidType}");
var tGuid = Memory.Read<ExampleGuid>(tGuidOffset);
var target = objectManager.GetBuilder(tGuid);
var targetName = target.GetUnitName();
Console.WriteLine($"Target [{target.Pointer.ToInt64():X}]: Entity Type: {target.EntityType} | {targetName}");
Thanks for this - but how do you walk the list? This is assuming you have a GUID to search for. How can you get the list of all object GUIDs?
-
Member
It is quite clear at this point that hitting a [0,0] and returning leaves gaps and you end up with inaccessible objects if you search by guid.
While the game does use sub_5C8D90 for getting the Hash Entry by GUID there is also sub_2B6560 that uses the old method with [next, WowGUID, ?Unknown] but i have yet to find where such a hash table is stored atm.
All current cross references of sub_2B6560 that actually accept an offset are actually null while in -game.
There is definitely a fallback somewhere if the first method fails.
If anyone has an idea that would be awesome.
Above offsets are for 1.15.5.57716.
-
Member
When I summon a pet, I can traverse the pet's objects. But when I dismiss the pet, I can still traverse it. What's the problem?
-