Then searching for the # of bullets in memory, as a DWORD, leads to this spot where bullets are being written as you fire:
Code: Select all
00000001425EBB30 | 48:8B41 08 | MOV RAX,QWORD PTR DS:[RCX+8] |
00000001425EBB34 | 45:33C0 | XOR R8D,R8D |
00000001425EBB37 | 85D2 | TEST EDX,EDX |
00000001425EBB39 | 44:0F4FC2 | CMOVG R8D,EDX |
00000001425EBB3D | 44:8940 18 | MOV DWORD PTR DS:[RAX+18],R8D | <--
00000001425EBB41 | 48:8B01 | MOV RAX,QWORD PTR DS:[RCX] |
00000001425EBB44 | 48:FF60 18 | JMP QWORD PTR DS:[RAX+18] |
Looking at RCX and its member-functions shows that this is called
C_WeaponItem (in my case, 0000004E6CB50BF0).
That "jmp [rax+18]" leads to here:
Code: Select all
mafiadefinitiveedition.exe+25EB990 - C6 41 11 01 - mov byte ptr [rcx+11],01
mafiadefinitiveedition.exe+25EB994 - C3 - ret
I'm assuming this is the "is firing" piece of code, a status BYTE. Cuz that write happens just as you fired. Or maybe it's "barrel's empty, do some lag till next shot is fired". Hence the delay between shots. I wonder what happens if you NOP it so it stays 0. Will you get fast fire?
EDIT: Nah, just a status BOOL.
So how do we get from
C_WeaponItem to
C_Player2? Is there any intermediate pointer in-between the relationship or not? Like a
C_Inventory, perhaps? Cuz that's the usual logic: Player -> Inventory -> Weapons -> Bullets.
I did a bit of back-tracing and found this out:
Code: Select all
0000000142B185A0 | 40:57 | PUSH RDI |
0000000142B185A2 | 41:56 | PUSH R14 |
0000000142B185A4 | 41:57 | PUSH R15 |
0000000142B185A6 | 48:83EC 50 | SUB RSP,50 |
0000000142B185AA | 8B51 58 | MOV EDX,DWORD PTR DS:[RCX+58] | 0000004E6D71A420 == C_InventoryWrapper
0000000142B185AD | 48:8BF9 | MOV RDI,RCX |
0000000142B185B0 | E8 FB3BABFF | CALL mafiadefinitiveedition.1425CC1B0 |
0000000142B185B5 | 8B57 5C | MOV EDX,DWORD PTR DS:[RDI+5C] | RAX == 0000004E6CB50BF0 == C_WeaponItem
Then back-tracing one more step out of this function, I saw that..
Code: Select all
0000000142BB40E3 | 48:8B89 28010000 | MOV RCX,QWORD PTR DS:[RCX+128] | rcx == 00000000E3010D30 == C_Player2
0000000142BB40EA | E8 B144F6FF | CALL mafiadefinitiveedition.142B185A0 |
So the logic goes like this:
[C_Player2 + 0x128] = C_InventoryWrapper
[C_InventoryWrapper + 0x58 ] = id of the selected item (in this case, for Pistol, the value is 0x21)
With this id and RCX set to C_InventoryWrapper, we go into the CALL @ 1425CC1B0. Where this happens:
Code: Select all
00000001425CC1B0 | 48:8B41 40 | MOV RAX,QWORD PTR DS:[RCX+40] |start
00000001425CC1B4 | 4C:8B49 48 | MOV R9,QWORD PTR DS:[RCX+48] |end
00000001425CC1B8 | 49:3BC1 | CMP RAX,R9 |if start == end
00000001425CC1BB | 74 19 | JE mafiadefinitiveedition.1425CC1D6 |exit
00000001425CC1BD | 0F1F00 | NOP DWORD PTR DS:[RAX],EAX |else
00000001425CC1C0 | 48:8B08 | MOV RCX,QWORD PTR DS:[RAX] |read_placeholder
00000001425CC1C3 | 4C:8B41 08 | MOV R8,QWORD PTR DS:[RCX+8] |read_item
00000001425CC1C7 | 41:3950 0C | CMP DWORD PTR DS:[R8+C],EDX |check id (stored at item+0xC)
00000001425CC1CB | 74 0C | JE mafiadefinitiveedition.1425CC1D9 |if equal, we found it
00000001425CC1CD | 48:83C0 08 | ADD RAX,8 |else, move to next placeholder
00000001425CC1D1 | 49:3BC1 | CMP RAX,R9 |did we finish the table?
00000001425CC1D4 | 75 EA | JNE mafiadefinitiveedition.1425CC1C0 |if not, return to read_loop
00000001425CC1D6 | 33C0 | XOR EAX,EAX |if not found, 0
00000001425CC1D8 | C3 | RET |exit
00000001425CC1D9 | 48:8BC1 | MOV RAX,RCX |move found ptr to RAX
00000001425CC1DC | C3 | RET |exit
The above is a look-up function that returns the pointer to the Inventory-related item based on its ID. Not any ID, but the ID that's stored @ [C_InventoryWrapper+0x58].. which is the ID of the active/current item/weapon. How do I know this? Well.. if you holster the weapon, 0x21 becomes 0x01. Watch:
Then at [C_WeaponItem + 0x8] you have another pointer:
C_ItemDataWeapon. This holds these:
What's interesting about the picture above is you can clearly see the Property
offsets. Not sure if you've noticed.. but.. when I fired the weapon, this is the spot that writes to clip:
Code: Select all
00000001425EBB3D | 44:8940 18 | MOV DWORD PTR DS:[RAX+18],R8D | <--
Offset is 0x18. Now.. back to the picture above:
Pimp, eh?
So:
-
m_Ammo is at C_ItemDataWeapon + 0x18
-
m_AmmoAUX is at C_ItemDataWeapon + 0x1C
-
m_Upgrades is at C_ItemDataWeapon + 0x20
Furthermore, the address that's below the string points to the
data type Watch this:
So.. m_Ammo is a property that's located at 0x18 in C_ItemDataWeapon. Its data type is "u32" which is a 0x4 (DWORD).
If we check m_Upgrades' data type memory block, we see this:
So.. offset 0x20 in C_ItemDataWeapon should contain a POINTER to a table of upgrades. That's what the vector translates to. Let's check:
Nada, no upgrades
Found out that [C_InventoryWrapper+0x5C] holds the id of the previously selected/active item/weapon
In my case, 0x1, as in the Holster "item"
Recap:
[C_Player2 + 0x128] = C_InventoryWrapper
[C_InventoryWrapper + 0x58 ] = id of the selected item (in this case, for Pistol, the value is 0x21)
--> C_WeaponItem
[C_WeaponItem + 0x8 ] = C_ItemDataWeapon
[C_ItemDataWeapon + 0x18] = m_Ammo
[C_ItemDataWeapon + 0x1C] = m_AmmoAUX
[C_ItemDataWeapon + 0x20] = m_Upgrades