├── BFV.py ├── LICENSE ├── MemAccess.py ├── README.md ├── Radar.py ├── RadarSprites.py ├── Start-Radar.bat ├── demo ├── firestorm.png ├── firestorm2.png └── operations.png ├── images ├── ammo_spot.png ├── crate.jpg ├── crate.png ├── dead.png ├── explosive.png ├── flag.png ├── health.png ├── plane.png ├── radio.png ├── safe.jpg ├── safe.png ├── sentry-gun.png ├── tank.png └── transport.png └── requirements.txt /BFV.py: -------------------------------------------------------------------------------- 1 | import MemAccess 2 | import copy 3 | import time 4 | from MemAccess import * 5 | 6 | # BFV Related Offsets 7 | NDM_FRAMES = 0 # 8 | NDM_BUSY = 4 # 9 | NDM_LOCALPLAYER = 8 # 10 | NDM_PLAYERLIST = 0x10 # 11 | NDM_TYPEINFOLIST = 0x18 # 12 | NDM_ENTITYKEYLIST = 0x20 # 13 | ClientPlayer_TeamID = 0x1C48 # 14 | ClientPlayer_Soldier = 0x1d50 # 15 | ClientPlayer_Vehicle = 0x1d60 # 16 | GameRenderer_RenderView = 0x60 # 17 | RenderView_ViewMatrix = 0x2F0 # 18 | HC_Health = 0x20 19 | HC_MaxHealth = 0x24 20 | CVE_TeamID = 0x234 21 | CSE_HealthComponent = 0x2e8 # DONE 22 | CCPE_Transform = 0x3a0#0x3c0 23 | CSE_Player = 0x3A8 24 | CVE_VehicleEntityData = 0x38 25 | VED_ControllableType = 0x1F8 26 | CCAT_ActiveTrigger = 0xD84 27 | CCAT_TriggerData = 0x28 28 | CCAT_ppAreaBounds = 0x60 29 | VVSD_PointsArray = 0x20 30 | AOD_ObjectiveArray = 0x18 31 | OD_Transform = 0x30 32 | OD_ShortName = 0x20 33 | OD_LongName = 0x80 34 | OD_TeamState = 0x88 35 | OD_ControlledState = 0x8C 36 | 37 | global offsets 38 | offsets = {} 39 | 40 | def isValid(addr): 41 | return ((addr >= 0x10000) and (addr < 0x0000001000000000)) 42 | 43 | def isValidInGame(addr): 44 | return ((addr >= 0x140000000) and (addr < 0x14FFFFFFF)) 45 | 46 | def numOfZeros(value): 47 | tmp = value 48 | ret = 0; 49 | for i in range(8): 50 | if (((tmp>>(i*8))&0xFF) == 0x00): 51 | ret += 1 52 | return ret 53 | 54 | 55 | class PointerManager(): 56 | def __init__(self,pHandle): 57 | self.mem = MemAccess(pHandle) 58 | self.pHandle = pHandle 59 | self.gpumemptr = 0 60 | self.OBFUS_MGR = 0 61 | if (offsets["OBFUS_MGR"] == 0): 62 | offsets["OBFUS_MGR"] = self.GetObfuscationMgr() 63 | else: 64 | self.OBFUS_MGR = offsets["OBFUS_MGR"] 65 | 66 | @staticmethod 67 | def decrypt_ptr(encptr, key): 68 | # Grab byte at location 69 | def GRAB_BYTE(x,n): 70 | return (x >> (n*8))&0xFF 71 | ret = 0 72 | subkey = (key^((5*key)%(2**64)))%(2**64) 73 | for i in range(7): 74 | y = GRAB_BYTE(subkey,i) 75 | subkey += 8 76 | t1 = (y*0x3B)%(2**8) 77 | t2 = (y + GRAB_BYTE(encptr,i)) % (2**8) 78 | ret |= (t2^t1)<<(i*8) 79 | ret |= GRAB_BYTE(encptr,7)<< 56 80 | ret &= 0x7FFFFFFFFFFFFFFF 81 | return ret 82 | 83 | 84 | def GetObfuscationMgr(self): 85 | api._cache_en = False 86 | print ("[+] Searching for ObfuscationMgr...") 87 | addr = -1 88 | OM = 0 89 | ss = StackAccess(self.pHandle,self.mem[offsets["PROTECTED_THREAD"]].read_uint32(0)) 90 | while (1): 91 | addr = -1 92 | time.sleep(0.001) 93 | buf = ss.read() 94 | 95 | for i in range(0,len(buf),8): 96 | testptr = int.from_bytes(buf[i:i+8],"little") 97 | if (isValid(testptr)): 98 | if self.mem[testptr].read_uint64(0x0) == offsets["OBFUS_MGR_PTR_1"]: 99 | OM = testptr 100 | self.OBFUS_MGR = testptr 101 | break 102 | 103 | if (OM>0): break 104 | ss.close() 105 | print ("[+] Found ObfuscationMgr @ 0x%08x "%(OM)) 106 | api._cache_en = True 107 | return OM 108 | 109 | def GetDx11Secret(self): 110 | def TestDx11Secret(self, testkey): 111 | mem = self.mem 112 | typeinfo = offsets["ClientStaticModelEntity"] 113 | 114 | flink = mem[typeinfo].read_uint64(0x88) 115 | 116 | ObfManager = self.OBFUS_MGR 117 | HashTableKey = mem[typeinfo](0).me() ^ mem[ObfManager].read_uint64(0xE0) 118 | 119 | hashtable = ObfManager+0x78 120 | EncryptionKey = self.hashtable_find(hashtable, HashTableKey) 121 | 122 | if (EncryptionKey == 0): 123 | return 0 124 | 125 | EncryptionKey ^= testkey 126 | ptr = PointerManager.decrypt_ptr(flink, EncryptionKey) 127 | 128 | if (isValid(ptr)): 129 | return True 130 | else: 131 | return False 132 | 133 | 134 | api._cache_en = False 135 | if (TestDx11Secret(self,offsets["Dx11Secret"])): 136 | api._cache_en = True 137 | return offsets["Dx11Secret"] 138 | 139 | if (offsets["GPUMemPtr"]): 140 | for offset in range(0,0x400,0x100): 141 | testptr = self.mem[offsets["GPUMemPtr"]].read_uint64(offset) 142 | if (testptr): 143 | if (TestDx11Secret(self,testptr)): 144 | if (testptr != offsets["Dx11Secret"]): 145 | print ("[+] Found Dx11 key scraping GPU mem @ 0x%x"%(offsets["GPUMemPtr"]+offset)) 146 | offsets["Dx11Secret"] = testptr 147 | api._cache_en = True 148 | return offsets["Dx11Secret"] 149 | offsets["GPUMemPtr"] = 0 150 | 151 | 152 | ss = StackAccess(self.pHandle,self.mem[offsets["PROTECTED_THREAD"]].read_uint32(0)) 153 | if (self.mem[self.OBFUS_MGR].read_uint64(0x100) != 0): 154 | addr = -1 155 | OM = 0 156 | i = 0 157 | print("[+] Locating initial Dx11 key location, please wait...") 158 | while (1): 159 | addr = -1 160 | buf = ss.read() 161 | addr = buf.find((offsets["OBFUS_MGR_RET_1"]).to_bytes(8, byteorder='little')) 162 | while (addr > -1): 163 | i = 0x38 164 | gpumem = int.from_bytes(buf[addr+i:addr+i+8],"little") 165 | testptr = self.mem[gpumem].read_uint64(0x0) 166 | if (TestDx11Secret(self,testptr)): 167 | if (testptr != offsets["Dx11Secret"]): 168 | offsets["GPUMemPtr"] = gpumem&0xFFFFFFFFFFFFFC00 169 | print ("[+] Found Initial Dx11 key scraping GPU mem @ 0x%x"%(offsets["GPUMemPtr"])) 170 | offsets["Dx11Secret"] = testptr 171 | api._cache_en = True 172 | ss.close() 173 | return offsets["Dx11Secret"] 174 | addr = buf.find((offsets["OBFUS_MGR_RET_1"]).to_bytes(8, byteorder='little'),addr+8) 175 | else: 176 | offsets["Dx11Secret"] = 0 177 | api._cache_en = True 178 | ss.close() 179 | return 0 180 | 181 | def CheckCryptMode(self): 182 | api._cache_en = False 183 | DecFunc = self.mem[self.OBFUS_MGR].read_uint64(0xE0) ^ self.mem[self.OBFUS_MGR].read_uint64(0xF8) 184 | Dx11EncBuffer = self.mem[self.OBFUS_MGR].read_uint64(0x100) 185 | 186 | if ((Dx11EncBuffer != 0) and (offsets["Dx11EncBuffer"] != Dx11EncBuffer)): 187 | self.GetDx11Secret() 188 | print ("[+] Dynamic key loaded, root key set to 0x%x"%(offsets["Dx11Secret"])) 189 | offsets["Dx11EncBuffer"] = Dx11EncBuffer 190 | offsets["CryptMode"] = 1 191 | elif (offsets["CryptMode"] == 0): 192 | if ((DecFunc == offsets["OBFUS_MGR_DEC_FUNC"]) and (Dx11EncBuffer != 0)): 193 | self.GetDx11Secret() 194 | print ("[+] Dynamic key loaded, retrieving key...") 195 | offsets["Dx11EncBuffer"] = Dx11EncBuffer 196 | offsets["CryptMode"] = 1 197 | elif (offsets["CryptMode"] == 1): 198 | if (DecFunc != offsets["OBFUS_MGR_DEC_FUNC"]): 199 | offsets["Dx11Secret"] = 0x598447EFD7A36912 200 | print ("[+] Static key loaded, root key set to 0x%x"%(offsets["Dx11Secret"])) 201 | offsets["CryptMode"] = 0 202 | self.gpumemptr = 0 203 | api._cache_en = True 204 | 205 | def hashtable_find(self, table, key): 206 | mem = self.mem 207 | bucketCount = mem[table].read_uint32(0x10) 208 | if (bucketCount == 0): 209 | return 0 210 | elemCount = mem[table].read_uint32(0x14) 211 | startcount = key % bucketCount 212 | node = mem[table](0x8)(0x8*startcount).me() 213 | 214 | if (node == 0): 215 | return 0 216 | 217 | while 1: 218 | first = mem[node].read_uint64(0x0) 219 | second = mem[node].read_uint64(0x8) 220 | next = mem[node].read_uint64(0x10) 221 | 222 | if first == key: 223 | #print ("Key: 0x%016x Node: 0x%016x"%(key^ mem[self.OBFUS_MGR].read_uint64(0xE0),node)) 224 | return second 225 | elif (next == 0): 226 | return 0 227 | 228 | node = next 229 | 230 | def GetLocalPlayer(self): 231 | self.CheckCryptMode() 232 | mem = self.mem 233 | ClientPlayerManager = mem[offsets["CLIENT_GAME_CONTEXT"]](0).read_uint64(0x60) 234 | ObfManager = self.OBFUS_MGR 235 | LocalPlayerListXorValue = mem[ClientPlayerManager].read_uint64(0xF8) 236 | LocalPlayerListKey = LocalPlayerListXorValue ^ mem[ObfManager].read_uint64(0xE0) 237 | 238 | hashtable = ObfManager+0x10 239 | EncryptedPlayerManager = self.hashtable_find(hashtable, LocalPlayerListKey) 240 | if (EncryptedPlayerManager == 0): 241 | return 0 242 | MaxPlayerCount = mem[EncryptedPlayerManager].read_uint32(0x18) 243 | 244 | if (MaxPlayerCount != 1): 245 | return 0 246 | 247 | XorValue1 = mem[EncryptedPlayerManager].read_uint64(0x20) ^ mem[EncryptedPlayerManager].read_uint64(0x8) 248 | XorValue2 = mem[EncryptedPlayerManager].read_uint64(0x10) ^ offsets["Dx11Secret"] 249 | 250 | LocalPlayer = mem[XorValue2].read_uint64(0) ^ XorValue1 251 | 252 | return LocalPlayer 253 | 254 | def GetPlayerById(self,id): 255 | self.CheckCryptMode() 256 | mem = self.mem 257 | ClientPlayerManager = mem[offsets["CLIENT_GAME_CONTEXT"]](0).read_uint64(0x60) 258 | ObfManager = self.OBFUS_MGR 259 | PlayerListXorValue = mem[ClientPlayerManager].read_uint64(0x100) 260 | PlayerListKey = PlayerListXorValue ^ mem[ObfManager].read_uint64(0xE0) 261 | 262 | hashtable = ObfManager+0x10 263 | EncryptedPlayerManager = self.hashtable_find(hashtable, PlayerListKey) 264 | if (EncryptedPlayerManager == 0): 265 | return 0 266 | MaxPlayerCount = mem[EncryptedPlayerManager].read_uint32(0x18) 267 | 268 | if (MaxPlayerCount != 70): 269 | return 0 270 | 271 | XorValue1 = mem[EncryptedPlayerManager].read_uint64(0x20) ^ mem[EncryptedPlayerManager].read_uint64(0x8) 272 | XorValue2 = mem[EncryptedPlayerManager].read_uint64(0x10) ^ offsets["Dx11Secret"] 273 | 274 | ClientPlayer = mem[XorValue2].read_uint64(0x8*id) ^ XorValue1 275 | 276 | return ClientPlayer 277 | 278 | def GetSpectatorById(self,id): 279 | self.CheckCryptMode() 280 | mem = self.mem 281 | ClientPlayerManager = mem[offsets["CLIENT_GAME_CONTEXT"]](0).read_uint64(0x60) 282 | ObfManager = self.OBFUS_MGR 283 | PlayerListXorValue = mem[ClientPlayerManager].read_uint64(0xF0) 284 | PlayerListKey = PlayerListXorValue ^ mem[ObfManager].read_uint64(0xE0) 285 | 286 | hashtable = ObfManager+0x10 287 | EncryptedPlayerManager = self.hashtable_find(hashtable, PlayerListKey) 288 | if (EncryptedPlayerManager == 0): 289 | return 0 290 | MaxPlayerCount = mem[EncryptedPlayerManager].read_uint32(0x18) 291 | 292 | if (MaxPlayerCount == 0) or (id >= MaxPlayerCount): 293 | return 0 294 | 295 | XorValue1 = mem[EncryptedPlayerManager].read_uint64(0x20) ^ mem[EncryptedPlayerManager].read_uint64(0x8) 296 | XorValue2 = mem[EncryptedPlayerManager].read_uint64(0x10) ^ offsets["Dx11Secret"] 297 | 298 | ClientPlayer = mem[XorValue2].read_uint64(0x8*id) ^ XorValue1 299 | 300 | return ClientPlayer 301 | 302 | def GetEntityKey(self,PointerKey): 303 | self.CheckCryptMode() 304 | mem = self.mem 305 | ObfManager = self.OBFUS_MGR 306 | HashTableKey = PointerKey ^ mem[ObfManager].read_uint64(0xE0) 307 | 308 | hashtable = ObfManager+0x78 309 | EncryptionKey = self.hashtable_find(hashtable, HashTableKey) 310 | 311 | if (EncryptionKey == 0): 312 | return 0 313 | 314 | EncryptionKey ^= offsets["Dx11Secret"] 315 | 316 | return EncryptionKey 317 | 318 | def DecryptPointer(self,EncPtr,PointerKey): 319 | self.CheckCryptMode() 320 | if not (EncPtr&0x8000000000000000): 321 | return 0 322 | mem = self.mem 323 | ObfManager = self.OBFUS_MGR 324 | HashTableKey = PointerKey ^ mem[ObfManager].read_uint64(0xE0) 325 | hashtable = ObfManager+0x78 326 | EncryptionKey = self.hashtable_find(hashtable, HashTableKey) 327 | 328 | if (EncryptionKey == 0): 329 | return 0 330 | 331 | EncryptionKey ^= offsets["Dx11Secret"] 332 | 333 | return PointerManager.decrypt_ptr(EncPtr,EncryptionKey) 334 | 335 | 336 | def find_typeinfo(name,first,pHandle): 337 | mem = MemAccess(pHandle) 338 | typeinfo = first 339 | while (typeinfo): 340 | if mem[typeinfo](0).read_pstring(0) == name: 341 | return typeinfo 342 | typeinfo = mem[typeinfo].read_uint64(8) 343 | return -1 344 | 345 | 346 | def build_offsets(pHandle): 347 | global offsets 348 | print ("[+] Gathering offsets, please wait...") 349 | x = sigscan(pHandle) 350 | mem = MemAccess(pHandle) 351 | offsets["OBFUS_MGR"] = 0; 352 | offsets["CryptMode"] = 0 353 | offsets["GPUMemPtr"] = 0 354 | offsets["Dx11Secret"] = 0x598447EFD7A36912 355 | offsets["Dx11EncBuffer"] = 0 356 | offsets["TIMESTAMP"] = get_buildtime(pHandle) 357 | 358 | offsets["GAMERENDERER"] = 0x1447f6fb8 359 | offsets["CLIENT_GAME_CONTEXT"] = 0x1447522a8 360 | offsets["OBJECTIVE_MANAGER"] = 0x14468B8B0 # FF 0D ? ? ? ? 48 8B 1D [? ? ? ?] 48 8B 43 10 48 8B 4B 08 48 3B C8 74 0E 361 | offsets["CLIENTSHRINKINGPLAYAREA"] = 0x1446645A0 # ? 8B F2 48 8B D9 ? 8B 35 [? ? ? ?] ? 85 F6 362 | offsets["ClientSoldierEntity"] = 0x144F2EF50 363 | offsets["ClientVehicleEntity"] = 0x144E3A170 364 | offsets["ClientSupplySphereEntity"] = 0x144C54550 365 | offsets["ClientCombatAreaTriggerEntity"] = 0x144E3B870 366 | offsets["ClientExplosionPackEntity"] = 0x144F346A0 367 | offsets["ClientProxyGrenadeEntity"] = 0x144F34370 368 | offsets["ClientGrenadeEntity"] = 0x144F34590 369 | offsets["ClientInteractableGrenadeEntity"] = 0x144C5BCB0 370 | offsets["ClientCapturePointEntity"] = 0x144C8DD30 371 | offsets["ClientLootItemEntity"] = 0x144C473A0 372 | offsets["ClientArmorVestLootItemEntity"] = 0x144C89090 373 | offsets["ClientStaticModelEntity"] = 0x144E32F10 374 | offsets["PROTECTED_THREAD"] = 0x144752654 375 | offsets["OBFUS_MGR_PTR_1"] = 0x1438B46D0 376 | offsets["OBFUS_MGR_RET_1"] = 0x147E38436 377 | offsets["OBFUS_MGR_DEC_FUNC"] = 0x14161F880 378 | offsets["OBJECTIVE_VTBL"] = 0x1437A7EF8 379 | 380 | 381 | return offsets 382 | 383 | def GetLocalPlayerList(pHandle): 384 | global offsets 385 | pm = PointerManager(pHandle) 386 | ind = 0 387 | plist = [] 388 | 389 | for i in range(70): 390 | pPlayer = pm.GetPlayerById(i) 391 | if pPlayer != 0: 392 | plist += [pPlayer] 393 | 394 | return plist 395 | 396 | def GetEncKey(pHandle,typeinfo): 397 | global offsets 398 | cache_en = api._cache_en 399 | api._cache_en = False 400 | global keystore 401 | mem = MemAccess(pHandle) 402 | pm = PointerManager(pHandle) 403 | 404 | if (mem[typeinfo].read_uint64(0x88) == 0): 405 | api._cache_en = cache_en 406 | return 0 407 | try: 408 | keystore 409 | except NameError: 410 | keystore = {} 411 | if typeinfo in keystore: 412 | api._cache_en = cache_en 413 | #print ("[+] Typeinfo: 0x%x Encryption Key: 0x%x"% (typeinfo,keystore[typeinfo])) 414 | return keystore[typeinfo] 415 | 416 | pm = PointerManager(pHandle) 417 | key = pm.GetEntityKey(mem[typeinfo](0).me()) 418 | 419 | if key == 0: 420 | return 0 421 | 422 | keystore[typeinfo] = key 423 | 424 | api._cache_en = cache_en 425 | print ("[+] Typeinfo: 0x%x Encryption Key: 0x%x"% (typeinfo,keystore[typeinfo])) 426 | return keystore[typeinfo] 427 | 428 | def GetEntityList(pHandle,typeinfo,flink_offset=0x80): 429 | elist = [] 430 | mem = MemAccess(pHandle) 431 | flink = mem[typeinfo].read_uint64(0x88) 432 | key = GetEncKey(pHandle,typeinfo) 433 | 434 | 435 | while (flink): 436 | ent = PointerManager.decrypt_ptr(flink,key) 437 | if ent >= 0x100000000000: 438 | return [] 439 | elist += [ent-flink_offset] 440 | flink = mem[ent].read_uint64(0x0) 441 | 442 | return elist 443 | 444 | def GetNextEntity(pHandle,Ptr,typeinfo,flink_offset=0x88): 445 | elist = [] 446 | mem = MemAccess(pHandle) 447 | key = GetEncKey(pHandle,typeinfo) 448 | if Ptr == 0: 449 | flink = mem[typeinfo].read_uint64(0x88) 450 | else: 451 | flink = mem[Ptr].read_uint64(flink_offset) 452 | 453 | ptr = PointerManager.decrypt_ptr(flink,key)-flink_offset 454 | #if (typeinfo == offsets["ClientArmorVestLootItemEntity"]): 455 | #print (hex(ptr)) 456 | if (isValid(ptr)): 457 | return ptr 458 | return 0 459 | 460 | 461 | def GetHandle(): 462 | def yes_or_no(question): 463 | while "the answer is invalid": 464 | reply = str(input(question+' (y/n): ')).lower().strip() 465 | if reply[:1] == 'y': 466 | return True 467 | if reply[:1] == 'n': 468 | return False 469 | pid = api.get_processid_by_name("bfv.exe") 470 | if type(pid) == type(None): 471 | return 0 472 | pHandle = HANDLE(api.OpenProcess(DWORD(0x1f0fff),False,DWORD(pid))) 473 | priv = api.is_elevated(pHandle) 474 | if (priv == 2): 475 | ans = yes_or_no("[+] WARNING! BFV.exe is running as admin, do you still want to continue?") 476 | if (ans == False): 477 | exit(0) 478 | return pHandle.value 479 | 480 | def GetEntityTransform(pHandle,Entity): 481 | mem = MemAccess(pHandle) 482 | flags = mem[Entity](0x40).read_uint64(0x8) 483 | if flags == None: 484 | return 0 485 | _9 = (flags>>8)&0xFF 486 | _10 = (flags>>16)&0xFF 487 | transform = mem[Entity](0x40).read_mat4((0x20*(_10+(2*_9)))+0x10) 488 | return transform 489 | 490 | def list_current_entities(pHandle): 491 | global offsets 492 | mem = MemAccess(pHandle) 493 | next = offsets["FIRST_TYPEINFO"] 494 | while (next!=0): 495 | if (mem[next].read_uint64(0x68) &0x8000000000000000): 496 | str = mem[next](0).read_pstring(0) 497 | 498 | if len(str)>0: 499 | num = len(GetEntityList(pHandle,next)) 500 | print("%d: %s" % (num,str)) 501 | next = mem[next].read_uint64(0x8) 502 | 503 | class GameSoldierData(): 504 | name = "" 505 | pointer = 0 506 | transform = None 507 | health = 0 508 | maxhealth = 0 509 | teamid = 0 510 | alive = True 511 | vehicle = 0 512 | 513 | class GameVehicleData(): 514 | pointer = 0 515 | transform = None 516 | teamid = 0 517 | vehicletype = "" 518 | 519 | class GameCapturePointData(): 520 | pointer = 0 521 | transform = None 522 | objectivedata = None 523 | initialteamowner = 0 524 | radius = 0 525 | 526 | class UIObjectiveData(): 527 | pointer = 0 528 | transform = None 529 | shortname = "" 530 | longname = "" 531 | teamstate = 0 532 | controlledstate = 0 533 | capturepoint = None 534 | 535 | class GameBoundsData(): 536 | pointer = 0 537 | teamid = 0 538 | teamspecific = False 539 | points = [] 540 | 541 | class GameLootData(): 542 | LootName = "" 543 | ItemName = "" 544 | LootType = 0 545 | VestEntity = False 546 | AccessCount = 0 547 | transform = [0,0,0,0] 548 | 549 | class GameDebugPointData(): 550 | chr = "" 551 | transform = [0,0,0,0] 552 | 553 | class GameExplosiveData(): 554 | pointer = 0 555 | teadid = 0 556 | transform = [0,0,0,0] 557 | 558 | class GameGrenadeData(): 559 | pointer = 0 560 | transform = [0,0,0,0] 561 | 562 | class GameSupplyData(): 563 | pointer = 0 564 | name = "" 565 | transform = [0,0,0,0] 566 | 567 | class FSObjectData(): 568 | pointer = 0 569 | typename = "" 570 | transform = [0,0,0,0] 571 | 572 | class GameCircleData(): 573 | pointer = 0 574 | OuterCircle_Moving = [0,0,0,0] 575 | InnerCircle_Const = [0,0,0,0] 576 | OuterCircleRadius_Moving = 0.0 577 | InnerCircleRadius_Const = 0.0 578 | 579 | class GameCircleData(): 580 | pointer = 0 581 | OuterCircle_Moving = [0,0,0,0] 582 | InnerCircle_Const = [0,0,0,0] 583 | OuterCircleRadius_Moving = 0.0 584 | InnerCircleRadius_Const = 0.0 585 | 586 | class GameData(): 587 | infirestorm = False 588 | testcrates = [] 589 | testsafes = [] 590 | myplayer = 0 591 | mysoldier = 0 592 | myteamid = 0 593 | myvehicle = 0 594 | myviewmatrix = 0 595 | mytransform = 0 596 | valid = False 597 | 598 | def __init__(self): 599 | self.soldiers = [] 600 | self.vehicles = [] 601 | self.capturepoints = [] 602 | self.debugpoints = [] 603 | self.loots = {} 604 | self.explosives = [] 605 | self.grenades = [] 606 | self.supplies = [] 607 | self.fsobjects = [] 608 | self.uiobjectives = [] 609 | self.boundsdata = [[],[],[]] 610 | self.boundsstate = 0 611 | self.LastLootPtr = 0 612 | self.LastVestLootPtr = 0 613 | self.boundslimits = None# x low, x high, y low, y high 614 | self.circledata = None 615 | self.testpoint = False 616 | def AddSoldier(self,soldier): 617 | self.soldiers += [soldier] 618 | def ClearSoldiers(self): 619 | self.soldiers = [] 620 | def AddVehicle(self,vehicle): 621 | self.vehicles += [vehicle] 622 | def ClearVehicles(self): 623 | self.vehicles = [] 624 | def AddCapturePoint(self,capturepoint): 625 | self.capturepoints += [capturepoint] 626 | def ClearCapturePoints(self): 627 | self.capturepoints = [] 628 | def AddUIObjective(self,uiobjective): 629 | self.uiobjectives += [uiobjective] 630 | def ClearUIObjectives(self): 631 | self.uiobjectives = [] 632 | def AddDebugPoint(self,debugpoint): 633 | self.debugpoints += [debugpoint] 634 | def ClearDebugPoints(self): 635 | self.debugpoints = [] 636 | def AddSupply(self,supply): 637 | self.supplies += [supply] 638 | def ClearSupplies(self): 639 | self.supplies = [] 640 | def AddGrenade(self,grenade): 641 | self.grenades += [grenade] 642 | def ClearGrenades(self): 643 | self.grenades = [] 644 | def AddExplosive(self,explosive): 645 | self.explosives += [explosive] 646 | def ClearExplosives(self): 647 | self.explosives = [] 648 | 649 | def AddBoundsData(self,boundsdata, TeamID): 650 | for b in self.boundsdata[TeamID]: 651 | if b.pointer == boundsdata.pointer: 652 | return 0 653 | self.boundsdata[TeamID] += [boundsdata] 654 | for p in boundsdata.points: 655 | if (self.boundslimits == None): 656 | self.boundslimits = [p[0],p[0],p[1],p[1]] 657 | continue 658 | if p[0] < self.boundslimits[0]: 659 | self.boundslimits[0] = p[0] 660 | if p[0] > self.boundslimits[1]: 661 | self.boundslimits[1] = p[0] 662 | if p[1] < self.boundslimits[2]: 663 | self.boundslimits[2] = p[1] 664 | if p[1] > self.boundslimits[3]: 665 | self.boundslimits[3] = p[1] 666 | return 1 667 | def ClearBoundsData(self): 668 | self.boundsdata[0] = [] # Neutral 669 | self.boundsdata[1] = [] # TeamID 1 670 | self.boundsdata[2] = [] # TeamID 2 671 | self.boundslimits = None 672 | 673 | 674 | def DebugPrintMatrix(mat): 675 | print("[%.3f %.3f %.3f %.3f ]" %(mat[0][0],mat[0][1],mat[0][2],mat[0][3])) 676 | print("[%.3f %.3f %.3f %.3f ]" %(mat[1][0],mat[1][1],mat[1][2],mat[1][3])) 677 | print("[%.3f %.3f %.3f %.3f ]" %(mat[2][0],mat[2][1],mat[2][2],mat[2][3])) 678 | print("[%.3f %.3f %.3f %.3f ]\n"%(mat[3][0],mat[3][1],mat[3][2],mat[3][3])) 679 | 680 | def DebugPrintVec4(Vec4): 681 | print("[%.3f %.3f %.3f %.3f ]\n" %(Vec4[0],Vec4[1],Vec4[2],Vec4[3])) 682 | 683 | def MakeBoundsData(pHandle,VVSDAddr,Team,IsTeamSpecific): 684 | mem = MemAccess(pHandle) 685 | PointsList = mem[VVSDAddr](VVSD_PointsArray).me() 686 | PointsListSize = mem[PointsList-0x4].read_uint32() 687 | BoundsData = GameBoundsData() 688 | BoundsData.teamid = Team 689 | BoundsData.teamspecific = (False,True)[IsTeamSpecific] 690 | BoundsData.points = [] 691 | BoundsData.pointer = VVSDAddr 692 | for i in range(PointsListSize): 693 | BoundsData.points += [mem[PointsList+(i*16)].read_vec4(0)] 694 | return BoundsData 695 | 696 | 697 | def Process(pHandle,cnt): 698 | global offsets 699 | api._access=0 700 | #api._cache_en = True 701 | del api._cache 702 | api._cache = {} 703 | 704 | mem = MemAccess(pHandle) 705 | pm = PointerManager(pHandle) 706 | 707 | global g_gamedata 708 | try: 709 | g_gamedata 710 | except NameError: 711 | g_gamedata = GameData() 712 | 713 | def GetEntityVec4(pHandle,Entity): 714 | mem = MemAccess(pHandle) 715 | flags = mem[Entity](0x40).read_uint64(0x8) 716 | if flags == None: 717 | return 0 718 | _9 = (flags>>8)&0xFF 719 | _10 = (flags>>16)&0xFF 720 | _off = (0x20*(_10+(2*_9)))+0x10 721 | v4 = [mem[Entity](0x40).read_uint32(_off+0x30), 722 | mem[Entity](0x40).read_uint32(_off+0x34), 723 | mem[Entity](0x40).read_uint32(_off+0x38), 724 | mem[Entity](0x40).read_uint32(_off+0x40)] 725 | return v4 726 | 727 | 728 | 729 | # Get Local Info 730 | MyPlayer = pm.GetLocalPlayer() 731 | MySoldier = mem[MyPlayer].weakptr(ClientPlayer_Soldier).me() 732 | MyTeamId = mem[MyPlayer].read_uint32(ClientPlayer_TeamID) 733 | MyVehicle = mem[MyPlayer].weakptr(ClientPlayer_Vehicle).me() 734 | MyViewmatrix = mem[offsets["GAMERENDERER"]]()(GameRenderer_RenderView).read_mat4(RenderView_ViewMatrix) 735 | MyTransform = GetEntityTransform(pHandle,MySoldier) 736 | MyPos = GetEntityVec4(pHandle,MySoldier) 737 | g_gamedata.myplayer = MyPlayer 738 | g_gamedata.mysoldier = MySoldier 739 | g_gamedata.myteamid = MyTeamId 740 | g_gamedata.myvehicle = MyVehicle 741 | g_gamedata.myviewmatrix = MyViewmatrix 742 | g_gamedata.mytransform = MyTransform 743 | 744 | 745 | #print ("MyPlayer : 0x%016X" % MyPlayer) 746 | #print ("MySoldier: 0x%016X" % MySoldier) 747 | #print ("MyTeamId : 0x%016X" % MyTeamId) 748 | #print ("MyPos : %s\n" % str(MyPos)) 749 | 750 | if MySoldier == 0: 751 | g_gamedata.myviewmatrix = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]] 752 | g_gamedata.mytransform = [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]] 753 | 754 | g_gamedata.valid = True 755 | 756 | # Render Soldiers 757 | g_gamedata.ClearSoldiers() 758 | for Soldier in GetEntityList(pHandle,offsets["ClientSoldierEntity"],0xf0): 759 | #print ("0x%016x"%Soldier) 760 | 761 | # if you are me, skip 762 | if (Soldier == MySoldier): 763 | continue 764 | 765 | # if you are not attached to a ClientPlayer, skip 766 | if (mem[Soldier](CSE_Player).me() == 0): 767 | continue 768 | 769 | # if you are in my vehicle, skip 770 | Vehicle = mem[Soldier](CSE_Player).weakptr(ClientPlayer_Vehicle).me() 771 | if ((MyVehicle>0) and Vehicle == MyVehicle): 772 | continue 773 | 774 | TeamId = mem[Soldier](CSE_Player).read_uint32(ClientPlayer_TeamID) 775 | Transform = GetEntityTransform(pHandle,Soldier) 776 | 777 | 778 | if Transform == 0: 779 | continue 780 | 781 | Health = mem[Soldier](CSE_HealthComponent).read_float(HC_Health) 782 | MaxHealth = mem[Soldier](CSE_HealthComponent).read_float(HC_MaxHealth) 783 | 784 | name = mem[Soldier](CSE_Player).read_string(0x40) 785 | 786 | Alive = True 787 | if (Health <= 0.0): 788 | Alive = False 789 | 790 | SoldierData = GameSoldierData() 791 | SoldierData.teamid = TeamId 792 | SoldierData.transform = Transform 793 | SoldierData.alive = Alive 794 | SoldierData.vehicle = Vehicle 795 | SoldierData.pointer = Soldier 796 | SoldierData.health = Health 797 | SoldierData.maxhealth = MaxHealth 798 | SoldierData.name = name 799 | 800 | g_gamedata.AddSoldier(SoldierData) 801 | 802 | # Render Vehicles 803 | g_gamedata.ClearVehicles() 804 | for Vehicle in GetEntityList(pHandle,offsets["ClientVehicleEntity"],0xF0): 805 | if (Vehicle == MyVehicle): 806 | continue 807 | #print (hex(Vehicle)) 808 | Transform = GetEntityTransform(pHandle,Vehicle) 809 | 810 | if Transform == 0: 811 | continue 812 | 813 | VehicleData = GameVehicleData() 814 | VehicleData.ownership = 0 815 | VehicleData.transform = Transform 816 | VehicleData.pointer = Vehicle 817 | VehicleData.vehicletype = mem[Vehicle](CVE_VehicleEntityData).read_pstring(VED_ControllableType) 818 | VehicleData.teamid = (mem[Vehicle].read_uint32(CVE_TeamID)) 819 | g_gamedata.AddVehicle(VehicleData) 820 | #print ("0x%016x"%Vehicle) 821 | 822 | # Get all objectives by accessing ObjectiveManager and iterating all ObjectiveData 823 | g_gamedata.ClearUIObjectives() 824 | i=0 825 | while (1): 826 | UIObj = mem[offsets["OBJECTIVE_MANAGER"]](0)(0x38).read_uint64(i*8) 827 | i+=1 828 | if mem[UIObj].read_uint64(0) != offsets["OBJECTIVE_VTBL"]: 829 | break 830 | 831 | Transform = mem[UIObj].read_mat4(OD_Transform) 832 | ShortName = mem[UIObj].read_pstring(OD_ShortName) 833 | LongName = mem[UIObj].read_pstring(OD_LongName) 834 | TeamState = mem[UIObj].read_uint32(OD_TeamState) 835 | ControlledState = mem[UIObj].read_uint32(OD_ControlledState) 836 | 837 | UIObjective = UIObjectiveData() 838 | UIObjective.pointer = UIObj 839 | UIObjective.transform = Transform 840 | UIObjective.shortname = ShortName 841 | UIObjective.longname = LongName 842 | UIObjective.teamstate = TeamState 843 | UIObjective.controlledstate = ControlledState 844 | g_gamedata.AddUIObjective(UIObjective) 845 | 846 | 847 | 848 | 849 | 850 | # Get the shape of the map bounds by iterating ClientCombatAreaTriggerEntity and reading bounds points 851 | ST_UPDATE = 0 852 | ST_UPDATENEXT = 1 853 | ST_SCAN = 2 854 | for ClientCombatAreaTrigger in GetEntityList(pHandle,offsets["ClientCombatAreaTriggerEntity"],0xD40): 855 | ActiveTrigger = mem[ClientCombatAreaTrigger].read_uint32(CCAT_ActiveTrigger) 856 | ClientCombatAreaTriggerData = mem[ClientCombatAreaTrigger](CCAT_TriggerData).me() 857 | Team = mem[ClientCombatAreaTriggerData].read_uint32(0x28) 858 | IsTeamSpecific = mem[ClientCombatAreaTriggerData].read_uint8(0x2D) 859 | updateShape = True 860 | 861 | ShapeData = mem[ClientCombatAreaTrigger](CCAT_ppAreaBounds)(0x0).me() 862 | 863 | if (g_gamedata.boundsstate == ST_SCAN): 864 | for Shape in g_gamedata.boundsdata[0]: 865 | if Shape.pointer == ShapeData: 866 | updateShape = False 867 | if (updateShape): 868 | g_gamedata.boundsstate = ST_UPDATENEXT 869 | 870 | if (g_gamedata.boundsstate == ST_UPDATE): 871 | g_gamedata.AddBoundsData(MakeBoundsData(pHandle,ShapeData,Team,IsTeamSpecific),0) 872 | 873 | i = 0xF0 874 | 875 | while (1): 876 | ShapeData = mem[ClientCombatAreaTrigger](i).me() 877 | if (ShapeData == 0): break 878 | 879 | if (g_gamedata.boundsstate == ST_SCAN): 880 | updateShape = True 881 | for Shape in g_gamedata.boundsdata[Team]: 882 | if Shape.pointer == ShapeData: 883 | updateShape = False 884 | if (updateShape and len(g_gamedata.boundsdata[Team])): 885 | g_gamedata.boundsstate = ST_UPDATENEXT 886 | break 887 | if (g_gamedata.boundsstate == ST_UPDATE): 888 | g_gamedata.AddBoundsData(MakeBoundsData(pHandle,ShapeData,Team,IsTeamSpecific),Team) 889 | else: 890 | break 891 | i+= 0x60 892 | if (g_gamedata.boundsstate == ST_UPDATENEXT): 893 | g_gamedata.boundsstate = ST_UPDATE 894 | g_gamedata.ClearBoundsData() 895 | elif (g_gamedata.boundsstate == ST_UPDATE): 896 | g_gamedata.boundsstate = ST_SCAN 897 | 898 | g_gamedata.ClearExplosives() 899 | for Explosive in GetEntityList(pHandle,offsets["ClientExplosionPackEntity"],0xf0): 900 | #print ("Explosive: " + hex(Explosive)) 901 | Transform = GetEntityTransform(pHandle,Explosive) 902 | Team = mem[Explosive].read_uint32(0x4c0) 903 | ExplosiveData = GameExplosiveData() 904 | ExplosiveData.transform = Transform 905 | ExplosiveData.teamid = Team 906 | ExplosiveData.pointer = Explosive 907 | g_gamedata.AddExplosive(ExplosiveData) 908 | 909 | g_gamedata.ClearGrenades() 910 | for Grenade in (GetEntityList(pHandle,offsets["ClientProxyGrenadeEntity"],0xf0)+GetEntityList(pHandle,offsets["ClientGrenadeEntity"],0xf0)+GetEntityList(pHandle,offsets["ClientInteractableGrenadeEntity"],0xf0)): 911 | Transform = GetEntityTransform(pHandle,Grenade) 912 | GrenadeData = GameGrenadeData() 913 | GrenadeData.transform = Transform 914 | GrenadeData.pointer = Grenade 915 | g_gamedata.AddGrenade(GrenadeData) 916 | 917 | g_gamedata.ClearSupplies() 918 | for Supply in GetEntityList(pHandle,offsets["ClientSupplySphereEntity"],0xb8): 919 | #print (hex(Supply)) 920 | SupplyName = mem[Supply](0x38).read_pstring(0xB8) 921 | pos = mem[Supply].read_vec4(0x100) 922 | #if pos == 0: 923 | # continue 924 | 925 | #print ("0x%x (%s)"% (Supply,SupplyName)) 926 | #print("%f %f %f %f"%(pos[0],pos[1],pos[2],pos[3])) 927 | #print("%f %f %f %f"%(MyTransform[1][0],MyTransform[1][1],MyTransform[1][2],MyTransform[1][3])) 928 | #print("%f %f %f %f"%(MyTransform[2][0],MyTransform[2][1],MyTransform[2][2],MyTransform[2][3])) 929 | #print("%f %f %f %f"%(MyTransform[3][0],MyTransform[3][1],MyTransform[3][2],MyTransform[3][3])) 930 | 931 | 932 | SupplyData = GameSupplyData() 933 | SupplyData.transform = [[0,0,0,0],[0,0,0,0],[0,0,0,0],pos] 934 | SupplyData.name = SupplyName 935 | SupplyData.pointer = Supply 936 | g_gamedata.AddSupply(SupplyData) 937 | 938 | 939 | 940 | # This pointer only exists if we are in FireStorm mode 941 | ShrinkingPlayArea = mem[offsets["CLIENTSHRINKINGPLAYAREA"]](0).me() 942 | g_gamedata.circledata = None 943 | if (not ShrinkingPlayArea): 944 | g_gamedata.infirestorm = False 945 | g_gamedata.fsobjects = [] 946 | 947 | if (ShrinkingPlayArea): 948 | if (not g_gamedata.infirestorm): 949 | for model in GetEntityList(pHandle,offsets["ClientStaticModelEntity"],0xf0): 950 | name = mem[model](0x38)(0xA8).read_pstring(0x18) 951 | if name == "artassets/props/gadgetcrate_01/gadgetcrate_01_200_paperfilling_Mesh": 952 | fsobject = FSObjectData() 953 | fsobject.pointer = model 954 | fsobject.typename = "crate" 955 | fsobject.transform = GetEntityTransform(pHandle,model) 956 | g_gamedata.fsobjects += [fsobject] 957 | elif name == "dakar/gameplay/prefabs/objectives/dk_safe_02_lid_Mesh": 958 | fsobject = FSObjectData() 959 | fsobject.pointer = model 960 | fsobject.typename = "safe" 961 | fsobject.transform = GetEntityTransform(pHandle,model) 962 | g_gamedata.fsobjects += [fsobject] 963 | g_gamedata.infirestorm = True 964 | 965 | 966 | 967 | 968 | CircleData = GameCircleData() 969 | CircleData.OuterCircle_Moving = mem[ShrinkingPlayArea].read_vec4(0x40) 970 | CircleData.InnerCircle_Const = mem[ShrinkingPlayArea].read_vec4(0x50) 971 | CircleData.OuterCircleRadius_Moving = mem[ShrinkingPlayArea].read_float(0x64) 972 | CircleData.InnerCircleRadius_Const = mem[ShrinkingPlayArea].read_float(0x68) 973 | g_gamedata.circledata = CircleData 974 | 975 | # So because python is slow and there are a lot of lootentities 976 | # lets just walk them 5 entities per render so we don't completely 977 | # kill our fps. We don't need low latency for these 978 | for n in range(5): 979 | g_gamedata.LastLootPtr = GetNextEntity(pHandle,g_gamedata.LastLootPtr,offsets["ClientLootItemEntity"],flink_offset=0xf0) 980 | if (g_gamedata.LastLootPtr!=0): 981 | if g_gamedata.LastLootPtr not in g_gamedata.loots: 982 | if (mem[g_gamedata.LastLootPtr].read_int32(0x238) != -1): 983 | Loot = GameLootData() 984 | Loot.LootName = mem[g_gamedata.LastLootPtr](0x720).read_pstring(0x40) 985 | Loot.LootType = mem[g_gamedata.LastLootPtr](0x38).read_uint32(0x118) 986 | Loot.ItemName = mem[g_gamedata.LastLootPtr](0x38)(0x100)(0x0).read_pstring(0x18) 987 | 988 | Loot.transform = GetEntityTransform(pHandle,g_gamedata.LastLootPtr) 989 | g_gamedata.loots[g_gamedata.LastLootPtr] = Loot 990 | else: 991 | g_gamedata.loots[g_gamedata.LastLootPtr].AccessCount += 1 992 | if (mem[g_gamedata.LastLootPtr].read_int32(0x238) == -1): 993 | del g_gamedata.loots[g_gamedata.LastLootPtr] 994 | elif (g_gamedata.loots[g_gamedata.LastLootPtr].AccessCount >= 50): 995 | loots = copy.copy(g_gamedata.loots) 996 | for LootPtr in loots: 997 | if g_gamedata.loots[LootPtr].AccessCount < 10: 998 | del g_gamedata.loots[LootPtr] 999 | else: 1000 | g_gamedata.loots[LootPtr].AccessCount = 0 1001 | 1002 | # So because python is slow and there are a lot of lootentities 1003 | # lets just walk them 5 entities per render so we don't completely 1004 | # kill our fps. We don't need low latency for these 1005 | for n in range(5): 1006 | g_gamedata.LastVestLootPtr = GetNextEntity(pHandle,g_gamedata.LastVestLootPtr,offsets["ClientArmorVestLootItemEntity"],flink_offset=0xf0) 1007 | 1008 | if (g_gamedata.LastVestLootPtr!=0): 1009 | 1010 | if g_gamedata.LastVestLootPtr not in g_gamedata.loots: 1011 | if (mem[g_gamedata.LastVestLootPtr].read_int32(0x238) != -1): 1012 | Loot = GameLootData() 1013 | Loot.LootName = mem[g_gamedata.LastVestLootPtr](0x720).read_pstring(0x40) 1014 | 1015 | Loot.VestEntity = True 1016 | Loot.ItemName = mem[g_gamedata.LastVestLootPtr](0x38)(0x100)(0x0).read_pstring(0x18) 1017 | Loot.transform = GetEntityTransform(pHandle,g_gamedata.LastVestLootPtr) 1018 | g_gamedata.loots[g_gamedata.LastVestLootPtr] = Loot 1019 | else: 1020 | g_gamedata.loots[g_gamedata.LastVestLootPtr].AccessCount += 1 1021 | if (mem[g_gamedata.LastVestLootPtr].read_int32(0x238) == -1): 1022 | del g_gamedata.loots[g_gamedata.LastVestLootPtr] 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | def initialize(pHandle): 1030 | global offsets 1031 | PAGE_SIZE = 0x1000 1032 | ALL_ACCESS = 0x1f0fff 1033 | PAGE_FLR = 0xFFFFFFFFFFFFF000 1034 | PAGE_RWX = 0x40 1035 | offsets = build_offsets(pHandle) 1036 | return 1037 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Tormund 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /MemAccess.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.wintypes import * 3 | import os 4 | import sys 5 | import time 6 | 7 | class MEMORY_BASIC_INFORMATION(Structure): 8 | _fields_ = [('BaseAddress', c_void_p), 9 | ('AllocationBase', c_void_p), 10 | ('AllocationProtect', DWORD), 11 | ('RegionSize', c_size_t), 12 | ('State', DWORD), 13 | ('Protect', DWORD), 14 | ('Type', DWORD)] 15 | 16 | class MEMORY_BASIC_INFORMATION64(Structure): 17 | _fields_ = [('BaseAddress', c_ulonglong), 18 | ('AllocationBase', c_ulonglong), 19 | ('AllocationProtect', DWORD), 20 | ('alignement1', DWORD), 21 | ('RegionSize', c_ulonglong), 22 | ('State', DWORD), 23 | ('Protect', DWORD), 24 | ('Type', DWORD), 25 | ('alignement2', DWORD)] 26 | 27 | class SYSTEM_INFO(Structure): 28 | _fields_ = [('wProcessorArchitecture', WORD), 29 | ('wReserved', WORD), 30 | ('dwPageSize', DWORD), 31 | ('lpMinimumApplicationAddress', LPVOID), 32 | ('lpMaximumApplicationAddress', LPVOID), 33 | ('dwActiveProcessorMask', c_ulonglong), 34 | ('dwNumberOfProcessors', DWORD), 35 | ('dwProcessorType', DWORD), 36 | ('dwAllocationGranularity', DWORD), 37 | ('wProcessorLevel', WORD), 38 | ('wProcessorRevision', WORD)] 39 | 40 | PAGE_EXECUTE_READWRITE = 64 41 | PAGE_EXECUTE_READ = 32 42 | PAGE_READONLY = 2 43 | PAGE_READWRITE = 4 44 | PAGE_NOCACHE = 512 45 | PAGE_WRITECOMBINE = 1024 46 | PAGE_GUARD = 256 47 | 48 | MEM_COMMIT = 4096 49 | MEM_FREE = 65536 50 | MEM_RESERVE = 8192 51 | 52 | class _TOKEN_ELEVATION(Structure): 53 | _fields_ = [ 54 | ("TokenIsElevated", DWORD), 55 | ] 56 | TOKEN_ELEVATION = _TOKEN_ELEVATION 57 | 58 | class WinApi(): 59 | def __init__(self): 60 | self.GetTokenInformation = windll.advapi32.GetTokenInformation 61 | self.GetTokenInformation.argtypes = [ 62 | HANDLE, # TokenHandle 63 | c_uint, # TOKEN_INFORMATION_CLASS value 64 | c_void_p, # TokenInformation 65 | DWORD, # TokenInformationLength 66 | POINTER(DWORD), # ReturnLength 67 | ] 68 | self.GetTokenInformation.restype = BOOL 69 | self.OpenProcessToken = windll.advapi32.OpenProcessToken 70 | self.OpenProcessToken.argtypes = (HANDLE, DWORD, POINTER(HANDLE)) 71 | self.OpenProcessToken.restype = BOOL 72 | self.CreateToolhelp32Snapshot = CDLL("kernel32.dll").CreateToolhelp32Snapshot 73 | self.Process32First = CDLL("kernel32.dll").Process32First 74 | self.Process32Next = CDLL("kernel32.dll").Process32Next 75 | self.GetLastError = CDLL("kernel32.dll").GetLastError 76 | self.CloseHandle = CDLL("kernel32.dll").CloseHandle 77 | self.OpenProcess = CDLL("kernel32.dll").OpenProcess 78 | self.ReadProcessMemory = CDLL("kernel32.dll").ReadProcessMemory 79 | self.WriteProcessMemory = CDLL("kernel32.dll").WriteProcessMemory 80 | self.VirtualProtectEx = CDLL("kernel32.dll").VirtualProtectEx 81 | self._debug = False 82 | self._access = 0 83 | self._cache = {} 84 | self._cache_en = True 85 | 86 | si = self.GetNativeSystemInfo() 87 | self.max_addr = si.lpMaximumApplicationAddress 88 | self.min_addr = si.lpMinimumApplicationAddress 89 | 90 | self.FindWindow = windll.user32.FindWindowW 91 | self.SetWindowPos = windll.user32.SetWindowPos 92 | 93 | def set_topmost(self, classname, windowname): 94 | if (classname == "pygame"): 95 | print ("[+] WARNING: Setting the radar window as TOP MOST (ANTI-CHEAT RISK!)") 96 | hwnd = self.FindWindow(classname,windowname) 97 | if (hwnd == 0): 98 | raise RuntimeError("set_topmost: Could not find window") 99 | HWND_TOPMOST = -1 100 | SWP_NOMOVE = 0x0002 101 | SWP_NOSIZE = 0x0001 102 | ret = self.SetWindowPos(hwnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE) 103 | if (ret == 0): 104 | raise RuntimeError("set_topmost: Could not set window as top-most") 105 | 106 | def is_elevated(self,phandle): 107 | token = HANDLE() 108 | TOKEN_QUERY = 0x0008 109 | res = self.OpenProcessToken(phandle, TOKEN_QUERY, token) 110 | if not res > 0: 111 | raise RuntimeError("Couldn't get process token") 112 | TokenElevationType = 18 113 | elev = DWORD(0) 114 | retlen = DWORD() 115 | res = self.GetTokenInformation( token, TokenElevationType , pointer(elev), sizeof(elev), pointer(retlen) ) 116 | if not res > 0: 117 | raise RuntimeError("Couldn't get process token information") 118 | api.CloseHandle(token) 119 | return elev.value 120 | 121 | def get_processid_by_name(self,name): 122 | class PROCESSENTRY32(Structure): 123 | _fields_ = [ ( 'dwSize' , DWORD ) , 124 | ( 'cntUsage' , DWORD) , 125 | ( 'th32ProcessID' , DWORD) , 126 | ( 'th32DefaultHeapID' , POINTER(ULONG)) , 127 | ( 'th32ModuleID' , DWORD) , 128 | ( 'cntThreads' , DWORD) , 129 | ( 'th32ParentProcessID' , DWORD) , 130 | ( 'pcPriClassBase' , LONG) , 131 | ( 'dwFlags' , DWORD) , 132 | ( 'szExeFile' , c_char * 260 ) ] 133 | global api 134 | pid = 0 135 | snapshot = HANDLE(api.CreateToolhelp32Snapshot(DWORD(0x00000002),DWORD(0))) 136 | process = PROCESSENTRY32() 137 | process.cntUsage = 0 138 | process.th32ProcessID = 0 139 | process.th32ModuleID = 0 140 | process.cntThreads = 0 141 | process.th32ParentProcessID = 0 142 | process.pcPriClassBase = 0 143 | process.dwFlags = 0 144 | process.szExeFile = b"" 145 | process.dwSize = sizeof(PROCESSENTRY32) 146 | 147 | i = 0 148 | pid = -1 149 | while 1: 150 | if (i==0): 151 | last = not api.Process32First(snapshot,byref(process)) 152 | else: 153 | last = not api.Process32Next(snapshot,byref(process)) 154 | procname = process.szExeFile 155 | if procname.decode("utf-8").lower() == name.lower(): 156 | pid = process.th32ProcessID 157 | break 158 | 159 | if (last): 160 | break 161 | i+=1 162 | api.CloseHandle(snapshot) 163 | if (pid > -1): 164 | return pid 165 | return None 166 | 167 | def rpm_uint8(self,handle,addr): 168 | buffer = c_ubyte(0) 169 | addr_ = c_ulonglong(addr) 170 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 171 | self._access+=1 172 | if (ret == 0): 173 | if (self._debug): 174 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 175 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 176 | return 0 177 | #exit(1) 178 | if (self._debug): print ("rpm_uint8 -> addr: 0x%x val: 0x%x"%(addr,buffer.value)) 179 | return buffer.value 180 | 181 | def rpm_uint16(self,handle,addr): 182 | buffer = c_ushort(0) 183 | addr_ = c_ulonglong(addr) 184 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 185 | self._access+=1 186 | if (ret == 0): 187 | if (self._debug): 188 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 189 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 190 | return 0 191 | #exit(1) 192 | if (self._debug): print ("rpm_uint16 -> addr: 0x%x val: 0x%x"%(addr,buffer.value)) 193 | return buffer.value 194 | 195 | def rpm_uint32(self,handle, addr): 196 | buffer = c_ulong(0) 197 | addr_ = c_ulonglong(addr) 198 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 199 | self._access+=1 200 | if (ret == 0): 201 | if (self._debug): 202 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 203 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 204 | return 0 205 | #exit(1) 206 | if (self._debug): print ("rpm_uint32 -> addr: 0x%x val: 0x%x"%(addr,buffer.value)) 207 | return buffer.value 208 | 209 | def rpm_int32(self,handle, addr): 210 | buffer = c_long(0) 211 | addr_ = c_ulonglong(addr) 212 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 213 | self._access+=1 214 | if (ret == 0): 215 | if (self._debug): 216 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 217 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 218 | return 0 219 | #exit(1) 220 | if (self._debug): print ("rpm_int32 -> addr: 0x%x val: 0x%x"%(addr,buffer.value)) 221 | return buffer.value 222 | 223 | def rpm_float(self,handle, addr): 224 | buffer = c_float(0) 225 | addr_ = c_ulonglong(addr) 226 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 227 | self._access+=1 228 | if (ret == 0): 229 | if (self._debug): 230 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 231 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 232 | return 0 233 | #exit(1) 234 | if (self._debug): print ("rpm_float -> addr: 0x%x val: %f"%(addr,buffer.value)) 235 | return buffer.value 236 | 237 | 238 | def rpm_uint64(self,handle, addr): 239 | #if ((self._cache_en) and (addr in self._cache)): 240 | # return self._cache[addr] 241 | buffer = c_ulonglong(0) 242 | addr_ = c_ulonglong(addr) 243 | 244 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 245 | #if (self._cache_en): 246 | # self._cache[addr] = buffer.value 247 | self._access+=1 248 | 249 | if (ret == 0): 250 | if (self._debug): 251 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 252 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 253 | return 0 254 | #exit(1) 255 | if (self._debug): print ("rpm_uint64 -> addr: 0x%x val: 0x%x"%(addr,buffer.value)) 256 | return buffer.value 257 | 258 | def wpm_uint32(self,handle, addr, value): 259 | if (self._debug): print ("wpm_uint632 -> addr: 0x%x val: 0x%x"%(addr,value)) 260 | buffer = c_ulong(value) 261 | addr_ = c_ulonglong(addr) 262 | ret = self.WriteProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 263 | if (ret == 0): 264 | if (self._debug): 265 | print ("[+] ERROR: WriteProcessMemory Failed: 0x%x" %(self.GetLastError())) 266 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 267 | #exit(1) 268 | 269 | def wpm_uint64(self,handle, addr, value): 270 | if (self._debug): print ("wpm_uint64 -> addr: 0x%x val: 0x%x"%(addr,value)) 271 | buffer = c_ulonglong(value) 272 | addr_ = c_ulonglong(addr) 273 | ret = self.WriteProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 274 | if (ret == 0): 275 | if (self._debug): 276 | print ("[+] ERROR: WriteProcessMemory Failed: 0x%x" %(self.GetLastError())) 277 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 278 | #exit(1) 279 | 280 | def rpm_string(self,handle,addr): 281 | buffer = c_ulonglong(addr) 282 | str = "" 283 | while (1): 284 | c = c_char() 285 | ret = self.ReadProcessMemory(handle,buffer,byref(c),sizeof(c),None) 286 | self._access+=1 287 | if (ret == 0): 288 | if (self._debug): 289 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 290 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 291 | return "" 292 | #exit(1) 293 | if (c.value[0] == 0): 294 | break 295 | str += chr(c.value[0]) 296 | buffer.value += 1 297 | if (self._debug): print ("rpm_uint64 -> addr: 0x%x val: %s"%(addr,str)) 298 | return str 299 | 300 | 301 | def rpm_pstring(self,handle,addr): 302 | buffer = c_ulonglong(0) 303 | addr_ = c_ulonglong(addr) 304 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 305 | self._access+=1 306 | if (ret == 0): 307 | if (self._debug): 308 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 309 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 310 | return "" 311 | #exit(1) 312 | str = "" 313 | while (1): 314 | c = c_char() 315 | ret = self.ReadProcessMemory(handle,buffer,byref(c),sizeof(c),None) 316 | self._access+=1 317 | if (ret == 0): 318 | if (self._debug): 319 | print ("[+] ERROR: ReadProcessMemory Failed: 0x%x" %(self.GetLastError())) 320 | print ("[+] ERROR: Access of Address 0x%x failed" % (addr)) 321 | return "" 322 | #exit(1) 323 | if (c.value[0] == 0): 324 | break 325 | str += chr(c.value[0]) 326 | buffer.value += 1 327 | if (self._debug): print ("rpm_uint64 -> addr: 0x%x val: %s"%(addr,str)) 328 | return str 329 | 330 | def rpm_vec4(self,handle,addr): 331 | vec4 = c_float * 4 332 | buffer = vec4() 333 | addr_ = c_ulonglong(addr) 334 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 335 | self._access+=1 336 | if (ret == 0): 337 | return 0 338 | return buffer 339 | 340 | def rpm_mat4(self,handle,addr): 341 | mat4 = (c_float * 4) * 4 342 | buffer = mat4() 343 | addr_ = c_ulonglong(addr) 344 | ret = self.ReadProcessMemory(handle,addr_,byref(buffer),sizeof(buffer),None) 345 | self._access+=1 346 | if (ret == 0): 347 | return 0 348 | return buffer 349 | 350 | def GetNativeSystemInfo(self): 351 | si = SYSTEM_INFO() 352 | windll.kernel32.GetNativeSystemInfo(byref(si)) 353 | return si 354 | 355 | def VirtualQueryEx(self, handle, lpAddress): 356 | mbi = MEMORY_BASIC_INFORMATION() 357 | if not windll.kernel32.VirtualQueryEx(handle, LPCVOID(lpAddress), byref(mbi), sizeof(mbi)): 358 | print('Error VirtualQueryEx: 0x%08X 0x%08X' % (lpAddress,GetLastError())) 359 | return mbi 360 | 361 | def VirtualQueryEx64(self, handle, lpAddress): 362 | mbi = MEMORY_BASIC_INFORMATION64() 363 | if not windll.kernel32.VirtualQueryEx(handle, LPCVOID(lpAddress), byref(mbi), sizeof(mbi)): 364 | raise ProcessException('Error VirtualQueryEx: 0x%08X' % lpAddress) 365 | return mbi 366 | 367 | def iter_region(self, handle,start_offset=None, end_offset=None, protec=None, optimizations=None): 368 | 369 | offset = start_offset or self.min_addr 370 | end_offset = end_offset or self.max_addr 371 | 372 | while True: 373 | if offset >= end_offset: 374 | break 375 | mbi = self.VirtualQueryEx64(handle,offset) 376 | offset = mbi.BaseAddress 377 | chunk = mbi.RegionSize 378 | protect = mbi.Protect 379 | state = mbi.State 380 | if state & MEM_FREE or state & MEM_RESERVE: 381 | offset += chunk 382 | continue 383 | if protec: 384 | if not protect & protec or protect & PAGE_NOCACHE or protect & PAGE_WRITECOMBINE or protect & PAGE_GUARD: 385 | offset += chunk 386 | continue 387 | yield offset, chunk 388 | offset += chunk 389 | 390 | class MemAccess(object): 391 | def __init__(self,pHandle): 392 | self.pHandle = pHandle 393 | 394 | def __getitem__(self,key): 395 | self.next_base = key 396 | if not self.isValid(key): 397 | #print ("Ptr Validity Error: 0x%x"%key) 398 | self.next_base = 0 399 | return self 400 | 401 | def __call__(self,key=0): 402 | if not self.isValid(self.next_base): 403 | #print ("Ptr Validity Error: 0x%x"%key) 404 | self.next_base = 0 405 | return self 406 | value = api.rpm_uint64(self.pHandle,key+self.next_base) 407 | self.next_base = value 408 | return self 409 | 410 | def isValid(self,addr): 411 | return ((addr >= 0x10000) and (addr < 0x000F000000000000)); 412 | 413 | def me(self): 414 | if not self.isValid(self.next_base): 415 | return 0 416 | return self.next_base 417 | 418 | def weakptr(self,addr): 419 | self.next_base = api.rpm_uint64(self.pHandle,addr+self.next_base) 420 | if not self.isValid(self.next_base): 421 | self.next_base = 0 422 | return self 423 | self.next_base = api.rpm_uint64(self.pHandle,self.next_base)-0x8 424 | if not self.isValid(self.next_base): 425 | self.next_base = 0 426 | return self 427 | return self 428 | 429 | def read_uint8(self,off=0): 430 | if not self.isValid(self.next_base): 431 | return 0 432 | value = api.rpm_uint8(self.pHandle,off+self.next_base) 433 | return value 434 | 435 | def read_uint16(self,off=0): 436 | if not self.isValid(self.next_base): 437 | return 0 438 | value = api.rpm_uint16(self.pHandle,off+self.next_base) 439 | return value 440 | 441 | def read_uint32(self,off=0): 442 | if not self.isValid(self.next_base): 443 | return 0 444 | value = api.rpm_uint32(self.pHandle,off+self.next_base) 445 | return value 446 | 447 | def read_int32(self,off=0): 448 | if not self.isValid(self.next_base): 449 | return 0 450 | value = api.rpm_int32(self.pHandle,off+self.next_base) 451 | return value 452 | 453 | def read_uint64(self,off=0): 454 | if not self.isValid(self.next_base): 455 | return 0 456 | value = api.rpm_uint64(self.pHandle,off+self.next_base) 457 | 458 | return value 459 | 460 | def write_uint32(self,val,off=0): 461 | api.wpm_uint32(self.pHandle,off+self.next_base,val) 462 | 463 | def write_uint64(self,val,off=0): 464 | api.wpm_uint64(self.pHandle,off+self.next_base,val) 465 | 466 | def read_string(self,off): 467 | str = api.rpm_string(self.pHandle,off+self.next_base) 468 | return str 469 | 470 | def read_pstring(self,off): 471 | str = api.rpm_pstring(self.pHandle,off+self.next_base) 472 | return str 473 | 474 | def read_vec4(self,off=0): 475 | value = api.rpm_vec4(self.pHandle,off+self.next_base) 476 | return value 477 | 478 | def read_mat4(self,off=0): 479 | value = api.rpm_mat4(self.pHandle,off+self.next_base) 480 | return value 481 | 482 | def read_float(self,off=0): 483 | value = api.rpm_float(self.pHandle,off+self.next_base) 484 | return value 485 | 486 | 487 | class memscan(): 488 | def __init__(self,pHandle): 489 | for a in api.iter_region(pHandle): 490 | virtaddr = a[0] 491 | virtsize = a[1] 492 | data = bytearray(virtsize) 493 | datatype = (c_ubyte*virtsize) 494 | buf = datatype.from_buffer(data) 495 | api.ReadProcessMemory(pHandle,LPCVOID(virtaddr),buf,c_int(virtsize),None) 496 | data.find(b'\xff\xc0\x22\x90') 497 | del data 498 | 499 | 500 | class sigscan(): 501 | def __init__(self,pHandle): 502 | 503 | self._sections = [] 504 | start = 0x140000000 505 | mem = MemAccess(pHandle) 506 | e_lfanew = mem[start].read_uint32(0x3C) 507 | NumberOfSections = mem[start+e_lfanew].read_uint16(0x6) 508 | SizeOfOptionalHeader = mem[start+e_lfanew].read_uint16(0x14) 509 | sectionarr = start+e_lfanew+0x18+SizeOfOptionalHeader 510 | 511 | for j in range(NumberOfSections): 512 | sec = sectionarr + j*0x28 513 | secname = "" 514 | for i in range(8): 515 | val = mem[sec].read_uint8(i) 516 | if (val==0): break 517 | secname += chr(val) 518 | virtsize = mem[sec].read_uint32(0x8) 519 | virtaddr = mem[sec].read_uint32(0xC) 520 | chars = mem[sec].read_uint32(0x24) 521 | data = bytearray(virtsize) 522 | datatype = (c_ubyte*virtsize) 523 | buf = datatype.from_buffer(data) 524 | api.ReadProcessMemory(pHandle,LPCVOID(start+virtaddr),buf,c_int(virtsize),None) 525 | self._sections += [[secname,start+virtaddr,virtsize,chars,data]] 526 | 527 | def scan(self,sig): 528 | sig = sig.split() 529 | q = [] 530 | match = True 531 | keydone = False 532 | key = bytearray() 533 | for elem in sig: 534 | if ((elem == "?") or (elem == "??")): 535 | q += [None] 536 | keydone = True 537 | else: 538 | val = int(elem,16) 539 | q += [val] 540 | if not keydone: key.append(val) 541 | for sec in self._sections: 542 | data = sec[4] 543 | size = sec[2] 544 | ind = 0 545 | i = 0 546 | while (i!=-1): 547 | match = True 548 | i = data.find(key,ind) 549 | if (i==-1): 550 | match = False 551 | break 552 | ind = i+1 553 | for j in range(len(q)): 554 | if q[j] == None: 555 | continue 556 | elif q[j] != data[i+j]: 557 | match = False 558 | break 559 | if (match): 560 | break 561 | if (match): 562 | break 563 | if (match): 564 | return sec[1] + i 565 | else: 566 | return -1 567 | 568 | def get_codecave(pHandle): 569 | start = 0x140000000 570 | mem = MemAccess(pHandle) 571 | e_lfanew = mem[start].read_uint32(0x3C) 572 | NumberOfSections = mem[start+e_lfanew].read_uint16(0x6) 573 | SizeOfOptionalHeader = mem[start+e_lfanew].read_uint16(0x14) 574 | sectionarr = start+e_lfanew+0x18+SizeOfOptionalHeader 575 | 576 | codecaves=[] 577 | 578 | for j in range(NumberOfSections): 579 | sec = sectionarr + j*0x28 580 | secname = "" 581 | for i in range(8): 582 | val = mem[sec].read_uint8(i) 583 | if (val==0): break 584 | secname += chr(val) 585 | virtsize = mem[sec].read_uint32(0x8) 586 | virtaddr = mem[sec].read_uint32(0xC) 587 | chars = mem[sec].read_uint32(0x24) 588 | store = DWORD(chars) 589 | prot = DWORD() 590 | api.VirtualProtectEx(pHandle,LPVOID(start+virtaddr+(virtsize&0xfffff000)),c_int(0x1000),store,byref(prot)) 591 | api.VirtualProtectEx(pHandle,LPVOID(start+virtaddr+(virtsize&0xfffff000)),c_int(0x1000),prot,None) 592 | 593 | if ((prot.value & 0x20) and (virtsize&0xFFF)): 594 | ccspace = 0x1000 - (virtsize & 0xFFF) 595 | if (ccspace >= 0x410): 596 | codecaves += [start+virtaddr+(virtsize&0xfffff000)+0x1000-0x400] 597 | return codecaves[-1] 598 | 599 | 600 | def get_buildtime(pHandle): 601 | start = 0x140000000 602 | mem = MemAccess(pHandle) 603 | e_lfanew = mem[start].read_uint32(0x3C) 604 | timestamp = mem[start+e_lfanew].read_uint32(0x8) 605 | return timestamp 606 | 607 | 608 | global api 609 | api = WinApi() 610 | 611 | 612 | def patch(pHandle,addr,bytes): 613 | PAGE_SIZE = 0x1000 614 | PAGE_FLR = 0xFFFFFFFFFFFFF000 615 | PAGE_RWX = 0x40 616 | protection = DWORD() 617 | api.VirtualProtectEx(pHandle,LPVOID(addr&PAGE_FLR),c_int(PAGE_SIZE),DWORD(PAGE_RWX),byref(protection)) 618 | buff = (c_ubyte * len(bytes)).from_buffer_copy(bytes) 619 | api.WriteProcessMemory(pHandle,LPCVOID(addr),buff,c_int(len(bytes)),None) 620 | api.VirtualProtectEx(pHandle,LPVOID(addr&PAGE_FLR),c_int(PAGE_SIZE),protection,byref(protection)) 621 | 622 | 623 | ULONG_PTR = PVOID = LPVOID = PVOID64 = c_void_p 624 | NTSTATUS = DWORD 625 | KAFFINITY = ULONG_PTR 626 | SDWORD = c_int32 627 | ThreadBasicInformation = 0 628 | STATUS_SUCCESS = 0 629 | 630 | class CLIENT_ID(Structure): 631 | _fields_ = [ 632 | ("UniqueProcess", PVOID), 633 | ("UniqueThread", PVOID), 634 | ] 635 | 636 | class THREAD_BASIC_INFORMATION(Structure): 637 | _fields_ = [ 638 | ("ExitStatus", NTSTATUS), 639 | ("TebBaseAddress", PVOID), # PTEB 640 | ("ClientId", CLIENT_ID), 641 | ("AffinityMask", KAFFINITY), 642 | ("Priority", SDWORD), 643 | ("BasePriority", SDWORD), 644 | ] 645 | 646 | windll.ntdll.NtQueryInformationThread.argtypes = [HANDLE, DWORD, POINTER(THREAD_BASIC_INFORMATION), ULONG, POINTER(ULONG)] 647 | windll.ntdll.NtQueryInformationThread.restype = NTSTATUS 648 | 649 | 650 | class StackAccess(): 651 | def __init__(self,handle,threadid): 652 | self.buffer = b"" 653 | 654 | self.phandle = handle 655 | mem = MemAccess(handle) 656 | #print("[+] Inspecting Thread ID: 0x%x"% (threadid)) 657 | h_thread = windll.kernel32.OpenThread(0x001F03FF, None, threadid) 658 | self.h_thread = h_thread 659 | tbi = THREAD_BASIC_INFORMATION() 660 | len = c_ulonglong() 661 | result = windll.ntdll.NtQueryInformationThread(h_thread, ThreadBasicInformation, byref(tbi), sizeof(tbi), None) 662 | if result == STATUS_SUCCESS: 663 | teb_base = tbi.TebBaseAddress 664 | self.stack_start = mem[teb_base].read_uint32(0x8) 665 | self.stack_end = mem[teb_base].read_uint32(0x10) 666 | self.stack_size = self.stack_start - self.stack_end 667 | self.buffer = create_string_buffer(self.stack_size) 668 | 669 | def read(self): 670 | cbuff = (c_char * len(self.buffer)).from_buffer(self.buffer) 671 | 672 | val = api.ReadProcessMemory(self.phandle,c_ulonglong(self.stack_end),byref(cbuff),sizeof(self.buffer),None) 673 | if val == 0: 674 | return b"" 675 | 676 | return self.buffer.raw 677 | 678 | def close(self): 679 | return windll.kernel32.CloseHandle(self.h_thread) 680 | 681 | 682 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tormund-BFV-Radar 2 | External Radar Mod for Battlefield V using Python and PyGame 3 | 4 | # Installation 5 | - Install Python 3.6.8 64-bit 6 | - Make sure its added to your path 7 | - go into your clone directory in commandline 8 | - pip.exe install -r requirements.txt (to install PyGame) 9 | - Run BFV.exe 10 | - python.exe .\Radar.py 1920 1200 11 | 12 | ## Features: 13 | - Radar follows your local player perspective 14 | - Not internal to BFV so game cannot take screenshots 15 | - Tracks all enemy locations 16 | - Tracks all vehicle locations 17 | - Colors vehicles based on team id 18 | - Vehicle specific icons 19 | - Tracks spawn beacons 20 | - Tracks Stationary Weapons 21 | - Tracks Ammo and Health supply stations 22 | - Tracks Grenade Entities 23 | - Tracks Explosion Packs 24 | - Renders Level Borders and Team specific Borders 25 | 26 | ## Firestorm Features: 27 | - Draws map borders 28 | - Draws inner circle 29 | - Draws outer circle 30 | - Draws LootEntities (as they pop up in memory) 31 | - White period = Tier 1 Loot 32 | - Green asterisk = Tier 2 Loot 33 | - Blinking Red/Green asterisk or hash = Tier 3 Loot 34 | 35 | ## Updates / Patches / AntiCheat 36 | As of April 2019 this is known to work. Unknown if DICE anticheat has ways to detect (most likely). Offsets found using generic sigs, hopefully as game updates the sigs find correct offsets 37 | 38 | ## Demos: 39 | 40 | ![Alt text](/demo/operations.png?raw=true "Operations") 41 | ![Alt text](/demo/firestorm.png?raw=true "Firestorm during airdrop") 42 | ![Alt text](/demo/firestorm2.png?raw=true "Firestorm on the ground") 43 | 44 | Shoutouts: Speedi13, txt, shellBullet, huangfengye, RozenMaiden, USSR, JD62, Coltonon, Robm 45 | -------------------------------------------------------------------------------- /Radar.py: -------------------------------------------------------------------------------- 1 | import RadarSprites 2 | import pygame 3 | import BFV 4 | import random 5 | import math 6 | import sys, os 7 | import traceback 8 | import time 9 | import MemAccess 10 | import struct 11 | from ctypes import * 12 | from ctypes.wintypes import * 13 | 14 | 15 | 16 | def is_admin(): 17 | try: 18 | is_admin = os.getuid() == 0 19 | except AttributeError: 20 | is_admin = windll.shell32.IsUserAnAdmin() != 0 21 | return is_admin 22 | 23 | def is_python3(): 24 | if sys.version_info[0] < 3: 25 | return False 26 | return True 27 | 28 | def get_pythonArch(): 29 | bitness = (8 * struct.calcsize("P")) 30 | return bitness 31 | 32 | GetAsyncKeyState = cdll.user32.GetAsyncKeyState 33 | 34 | class Color: 35 | BLACK = (0, 0, 0) 36 | WHITE = (255, 255, 255) 37 | BLUE = (0, 0, 255) 38 | GREEN = (0, 255, 0) 39 | RED = (255, 0, 0) 40 | YELLOW = (255, 255,0) 41 | CYAN = (0, 255,255) 42 | 43 | def Vec3Difference(a,b): 44 | ret = (c_float*3)() 45 | for i in range(3): ret[i] = a[i] - b[i] 46 | return ret 47 | 48 | def Vec3Length(a): 49 | return math.sqrt((a[0]*a[0]) + (a[1]*a[1]) + (a[2]*a[2])) 50 | 51 | def Vec3Normalize(a,limit): 52 | ret = (c_float*3)() 53 | if len != 0: 54 | for i in range(3): ret[i] = a[i]/limit 55 | return ret 56 | 57 | def Vec3Scale(a,scale): 58 | ret = (c_float*3)() 59 | for i in range(3): ret[i] = a[i]*scale 60 | return ret 61 | 62 | def Vec3Sum(a,b): 63 | ret = (c_float*3)() 64 | for i in range(3): ret[i] = a[i]+b[i] 65 | return ret 66 | 67 | def rotate_point(pos, cen, angle, angle_in_radians=True): 68 | angle *= math.pi / 180 69 | cos_theta = math.cos(angle) 70 | sin_theta = math.sin(angle) 71 | 72 | ret = (c_float*3)() 73 | 74 | ret[0] = (cos_theta * (pos[0] - cen[0]) - sin_theta * (pos[2] - cen[2])) + cen[0] 75 | ret[1] = 0 76 | ret[2] = (sin_theta * (pos[0] - cen[0]) + cos_theta * (pos[2] - cen[2])) + cen[2] 77 | return ret 78 | 79 | class Radar(): 80 | def __init__(self,width,height): 81 | # Initialize PyGame 82 | pygame.init() 83 | pygame.display.init() 84 | 85 | # Load Sprites 86 | self.gfx = RadarSprites.RadarSprites() 87 | 88 | # Randomize Window Title 89 | random.seed(int(time.time())) 90 | caption = "" 91 | for i in range(random.randint(5, 15)): 92 | caption += chr((random.randint(65, 90),random.randint(97, 122))[random.randint(0, 1)]) 93 | pygame.display.set_caption(caption) 94 | self.caption = caption 95 | 96 | # Set Screen Parameters 97 | self.height = height 98 | self.width = width 99 | self.screen = pygame.display.set_mode((self.width, self.height)) 100 | self.distance = self.height 101 | self.zoom = 2.0 102 | 103 | # Initialize Fonts 104 | self.myfont = pygame.font.SysFont('Arial', 16) 105 | self.myfontbig = pygame.font.SysFont('Arial', 30) 106 | 107 | # Initialize Update Count 108 | self.UpdateCount = 0 109 | 110 | # -- ALWAYS ON TOP OPTIONAL SETTING -- 111 | # NOTE: Uncomment the following 2 lines of code to have 112 | # your pygame window stay always on top of every window 113 | # 114 | # WARNING: KEEPING THE WINDOW ON TOP OF THE GAME WINDOW 115 | # MAKES YOU SUSCEPTIBLE TO ANTI-SHEET SCREEN SHOT DETECTION 116 | # 117 | #api = MemAccess.WinApi() 118 | #api.set_topmost("pygame", self.caption) 119 | 120 | def quit(self): 121 | pygame.display.quit() 122 | 123 | def GetRadarData(self,MyPosition,MyViewmatrix,Transform): 124 | try: 125 | Pos = Transform[3] 126 | except: 127 | Pos = [0,0,0,0] 128 | #print (str(Transform)) 129 | #return (0,0,0) 130 | Pos = Vec3Difference(MyPosition,Pos) 131 | Pos = Vec3Normalize(Pos,self.distance/8) 132 | Pos = Vec3Scale(Pos,(self.distance/8)*self.zoom) 133 | angle = 360 - (math.atan2(-MyViewmatrix[0][0], MyViewmatrix[2][0]) * (180/math.pi)) 134 | sangle = (math.atan2(-Transform[0][0], Transform[2][0]) * (180/math.pi)) 135 | Pos = rotate_point(Pos,(0,0,0),angle) 136 | Pos = (-Pos[2], Pos[0]) # Change to vec2 137 | return (Pos,sangle-angle,angle) 138 | 139 | # Draw Enemy/Friend Arrow + Angle 140 | def DrawArrow(self,x,y,color,angle=0): 141 | def rotate(pos, angle): 142 | cen = (5+x,0+y) 143 | angle *= -(math.pi/180) 144 | cos_theta = math.cos(angle) 145 | sin_theta = math.sin(angle) 146 | ret = ((cos_theta * (pos[0] - cen[0]) - sin_theta * (pos[1] - cen[1])) + cen[0], 147 | (sin_theta * (pos[0] - cen[0]) + cos_theta * (pos[1] - cen[1])) + cen[1]) 148 | return ret 149 | 150 | p0 = rotate((0+x ,-4+y),angle+90) 151 | p1 = rotate((0+x ,4+y ),angle+90) 152 | p2 = rotate((10+x ,0+y ),angle+90) 153 | 154 | pygame.draw.polygon(self.screen, color, [p0,p1,p2]) 155 | 156 | # Transposes center based coordinates to top/left based coordinates 157 | def FromCenter(self,x,y): 158 | class point(): 159 | def __init__(self,x,y): 160 | self.x = x;self.y = y; 161 | return point(int((self.width/2)+x),int((self.height/2)+y)) 162 | 163 | 164 | def UpdateObjectives(self,data): 165 | for CapturePoint in data.capturepoints: 166 | if CapturePoint.objectivedata == None: 167 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,CapturePoint.transform) 168 | Pos = RadarData[0] 169 | Yaw = RadarData[1] 170 | Position = self.FromCenter(Pos[0],Pos[1]) 171 | if (CapturePoint.initialteamowner == data.myteamid): 172 | textsurface = self.myfontbig.render(hex(CapturePoint.pointer), False, Color.GREEN) 173 | self.screen.blit(textsurface,(Position.x-2,Position.y-1)) 174 | else: 175 | textsurface = self.myfontbig.render(hex(CapturePoint.pointer), False, Color.RED) 176 | self.screen.blit(textsurface,(Position.x-2,Position.y-1)) 177 | 178 | for UIObjective in data.uiobjectives: 179 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,UIObjective.transform) 180 | Pos = RadarData[0] 181 | Yaw = RadarData[1] 182 | 183 | Position = self.FromCenter(Pos[0],Pos[1]) 184 | 185 | if (UIObjective.teamstate == 1): 186 | self.screen.blit(self.gfx.flaggreen,(Position.x,Position.y-20)) 187 | textsurface = self.myfont.render(UIObjective.shortname, False, Color.GREEN) 188 | else: 189 | self.screen.blit(self.gfx.flagred,(Position.x,Position.y-20)) 190 | textsurface = self.myfont.render(UIObjective.shortname, False, Color.RED) 191 | self.screen.blit(textsurface,(Position.x-2,Position.y-1)) 192 | 193 | def UpdateExplosives(self,data): 194 | for Explosive in data.explosives: 195 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,Explosive.transform) 196 | Pos = RadarData[0] 197 | Yaw = RadarData[1] 198 | Position = self.FromCenter(Pos[0],Pos[1]) 199 | textsurface = self.myfont.render("x", False, (Color.RED,Color.GREEN)[data.myteamid == Explosive.teamid]) 200 | self.screen.blit(textsurface,(Position.x,Position.y)) 201 | 202 | def UpdateGrenades(self,data): 203 | for Grenade in data.grenades: 204 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,Grenade.transform) 205 | Pos = RadarData[0] 206 | Yaw = RadarData[1] 207 | Position = self.FromCenter(Pos[0],Pos[1]) 208 | if((cnt%16)>= 8):textsurface = self.myfont.render("G", False, Color.RED) 209 | else: textsurface = self.myfont.render("G", False, Color.GREEN) 210 | self.screen.blit(textsurface,(Position.x,Position.y)) 211 | 212 | 213 | def UpdateSupplies(self,data): 214 | for Supply in data.supplies: 215 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,Supply.transform) 216 | Pos = RadarData[0] 217 | Yaw = RadarData[1] 218 | Position = self.FromCenter(Pos[0],Pos[1]) 219 | if (Supply.name == "Supply_Ammo_Station"): 220 | self.screen.blit(self.gfx.ammospot,(Position.x,Position.y)) 221 | elif (Supply.name == "Supply_Medical_Station"): 222 | self.screen.blit(self.gfx.health,(Position.x,Position.y)) 223 | else: 224 | continue 225 | 226 | def UpdateSoldiers(self,data): 227 | # Main Soldier Entity Render Loop 228 | for Soldier in data.soldiers: 229 | # Check if soldier is enemy based on teamid 230 | Enemy = (False,True)[data.myteamid != Soldier.teamid] 231 | # If the soldier is in a vehicle lets skip rendering it 232 | if Soldier.vehicle: 233 | continue 234 | 235 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,Soldier.transform) 236 | Pos = RadarData[0] 237 | Yaw = RadarData[1] 238 | 239 | # Transpose soldier coordinates to radar 240 | Position = self.FromCenter(Pos[0],Pos[1]) 241 | 242 | if Soldier.alive: 243 | # if our soldier is alive we draw an arrow in the direction of its yaw 244 | # we color the soldier based on if it is an enemy or not 245 | self.DrawArrow(Position.x,Position.y,(Color.GREEN,Color.RED)[Enemy],Yaw) 246 | else: 247 | if (Enemy): 248 | # if we have a dead enemy soldier mark it with red skull 249 | self.screen.blit(self.gfx.deadiconred,(Position.x,Position.y)) 250 | else: 251 | # if we have a dead team soldier mark it with green skull 252 | self.screen.blit(self.gfx.deadicongreen,(Position.x,Position.y)) 253 | 254 | def DrawTank(self,Position,yaw,VColor): 255 | if (VColor == Color.RED): 256 | rot = pygame.transform.rotate(self.gfx.tankred,yaw) 257 | elif (VColor == Color.GREEN): 258 | rot = pygame.transform.rotate(self.gfx.tankgreen,yaw) 259 | else: 260 | rot = pygame.transform.rotate(self.gfx.tankwhite,yaw) 261 | self.screen.blit(rot,(Position.x-7,Position.y-15)) 262 | 263 | def DrawPlane(self,Position,yaw,VColor): 264 | if (VColor == Color.RED): 265 | rot = pygame.transform.rotate(self.gfx.planered,yaw) 266 | elif (VColor == Color.GREEN): 267 | rot = pygame.transform.rotate(self.gfx.planegreen,yaw) 268 | else: 269 | rot = pygame.transform.rotate(self.gfx.planewhite,yaw) 270 | self.screen.blit(rot,(Position.x-14,Position.y-20)) 271 | 272 | def DrawBeacon(self,Position,VColor): 273 | if (VColor == Color.RED): 274 | self.screen.blit(self.gfx.beaconiconred,(Position.x,Position.y)) 275 | elif (VColor == Color.GREEN): 276 | self.screen.blit(self.gfx.beaconicongreen,(Position.x,Position.y)) 277 | else: 278 | self.screen.blit(self.gfx.beaconiconwhite,(Position.x,Position.y)) 279 | 280 | def DrawStationary(self,Position,VColor): 281 | if (VColor == Color.RED): 282 | self.screen.blit(self.gfx.stationgunred,(Position.x,Position.y)) 283 | elif (VColor == Color.GREEN): 284 | self.screen.blit(self.gfx.stationgungreen,(Position.x,Position.y)) 285 | else: 286 | self.screen.blit(self.gfx.stationgunwhite,(Position.x,Position.y)) 287 | 288 | def DrawTransport(self,Position,yaw,VColor): 289 | if (VColor == Color.RED): 290 | rot = pygame.transform.rotate(self.gfx.carred,yaw) 291 | elif (VColor == Color.GREEN): 292 | rot = pygame.transform.rotate(self.gfx.cargreen,yaw) 293 | else: 294 | rot = pygame.transform.rotate(self.gfx.carwhite,yaw) 295 | self.screen.blit(rot,(Position.x-12,Position.y-20)) 296 | 297 | def UpdateVehicles(self,data): 298 | # Main Vehicle Render Loop 299 | for Vehicle in data.vehicles: 300 | 301 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,Vehicle.transform) 302 | Pos = RadarData[0] 303 | Yaw = RadarData[1] 304 | 305 | # Transpose vehicle coordinates to radar 306 | Position = self.FromCenter(Pos[0],Pos[1]) 307 | 308 | # Set a color based on vehicle's association 309 | # Green if friendly 310 | # Red if enemy 311 | # White is neutral 312 | if (data.myteamid == Vehicle.teamid): 313 | VColor = Color.GREEN 314 | elif (Vehicle.teamid): 315 | VColor = Color.RED 316 | else: 317 | VColor = Color.WHITE 318 | 319 | if "Stationary" in Vehicle.vehicletype: 320 | self.DrawStationary(Position,VColor) 321 | elif "Towable" in Vehicle.vehicletype: 322 | self.DrawStationary(Position,VColor) 323 | elif "Tank" in Vehicle.vehicletype: 324 | self.DrawTank(Position,Yaw,VColor) 325 | elif "ArmoredCar" in Vehicle.vehicletype: 326 | self.DrawTank(Position,Yaw,VColor) 327 | elif "Halftrack" in Vehicle.vehicletype: 328 | self.DrawTank(Position,Yaw,VColor) 329 | elif "Airplane" in Vehicle.vehicletype: 330 | self.DrawPlane(Position,Yaw,VColor) 331 | elif "SpawnBeacon" in Vehicle.vehicletype: 332 | self.DrawBeacon(Position,VColor) 333 | else: 334 | self.DrawTransport(Position,Yaw,VColor) 335 | 336 | def DrawDot(self,pos,color): 337 | self.screen.set_at((pos[0], pos[1]-1), color) 338 | self.screen.set_at((pos[0]+1, pos[1]-1), color) 339 | self.screen.set_at((pos[0]-1, pos[1]-1), color) 340 | self.screen.set_at((pos[0], pos[1]), color) 341 | self.screen.set_at((pos[0]+1, pos[1]), color) 342 | self.screen.set_at((pos[0]-1, pos[1]), color) 343 | self.screen.set_at((pos[0], pos[1]+1), color) 344 | self.screen.set_at((pos[0]+1, pos[1]+1), color) 345 | self.screen.set_at((pos[0]-1, pos[1]+1), color) 346 | 347 | 348 | def UpdateBounds(self,data): 349 | for t in range(2,-1,-1): 350 | if (t == 2): C = (Color.RED,Color.GREEN)[(data.myteamid-1) % (2)] 351 | elif (t == 1): C = (Color.GREEN,Color.RED)[(data.myteamid-1) % (2)] 352 | else: C = (252, 164, 40) 353 | 354 | for Shape in data.boundsdata[t]: 355 | PointTransformed = [] 356 | for Point in Shape.points: 357 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,[([0]*4),([0]*4),([0]*4),[Point[0],0,Point[2],0]]) 358 | Pos = RadarData[0] 359 | Pos = self.FromCenter(Pos[0],Pos[1]) 360 | PointTransformed += [(Pos.x,Pos.y)] 361 | if len(PointTransformed) > 1: 362 | pygame.draw.polygon(self.screen, C, PointTransformed,3) 363 | 364 | def UpdateFirestorm(self,data): 365 | 366 | for fsobject in data.fsobjects: 367 | if fsobject.typename == "safe": 368 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,fsobject.transform) 369 | Pos = RadarData[0] 370 | Yaw = RadarData[1] 371 | 372 | # Transpose vehicle coordinates to radar 373 | Position = self.FromCenter(Pos[0],Pos[1]) 374 | self.screen.blit(self.gfx.safe,(Position.x,Position.y)) 375 | 376 | # Crates are kind of noisy, if you want them uncomment this block 377 | 378 | #elif fsobject.typename == "crate": 379 | # RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,fsobject.transform) 380 | # Pos = RadarData[0] 381 | # Yaw = RadarData[1] 382 | # 383 | # # Transpose vehicle coordinates to radar 384 | # Position = self.FromCenter(Pos[0],Pos[1]) 385 | # self.screen.blit(self.gfx.crate,(Position.x,Position.y)) 386 | 387 | if (data.circledata != None): 388 | c = data.circledata 389 | 390 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,[[0,0,0,0],[0,0,0,0],[0,0,0,0],c.OuterCircle_Moving]) 391 | Pos = RadarData[0] 392 | Yaw = RadarData[1] 393 | Position = self.FromCenter(Pos[0],Pos[1]) 394 | rad = c.OuterCircleRadius_Moving * self.zoom 395 | if (int(rad)>3): 396 | pygame.draw.circle(self.screen,(246,108,0),(Position.x,Position.y),int(rad),3) 397 | 398 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,[[0,0,0,0],[0,0,0,0],[0,0,0,0],c.InnerCircle_Const]) 399 | Pos = RadarData[0] 400 | Yaw = RadarData[1] 401 | Position = self.FromCenter(Pos[0],Pos[1]) 402 | rad = c.InnerCircleRadius_Const * self.zoom 403 | if (int(rad)>3): 404 | pygame.draw.circle(self.screen,(55,229,251),(Position.x,Position.y),int(rad),3) 405 | 406 | for LootEntityPtr in data.loots: 407 | LootEntity = data.loots[LootEntityPtr] 408 | RadarData = self.GetRadarData(data.mytransform[3],data.myviewmatrix,LootEntity.transform) 409 | Pos = RadarData[0] 410 | Yaw = RadarData[1] 411 | Position = self.FromCenter(Pos[0],Pos[1]) 412 | x = Position.x 413 | y = Position.y 414 | #if "armor" in LootEntity.ItemName.lower(): 415 | # print (LootEntity.ItemName + " " + LootEntity.LootName) 416 | if ("U_Dakar_Bandages" in LootEntity.ItemName): 417 | color = Color.RED 418 | self.DrawDot((x,y),color) 419 | elif ("U_Boys" in LootEntity.ItemName): 420 | color = self.blink() 421 | self.Text("By",color,x,y) 422 | elif ("FlareGun" in LootEntity.ItemName): 423 | if ("V1Rocket" in LootEntity.ItemName): 424 | color = Color.GREEN 425 | self.Text("F",color,x,y) 426 | elif ("DangerZone" in LootEntity.ItemName): 427 | color = Color.RED 428 | self.Text("F",color,x,y) 429 | elif ("U_BREN" in LootEntity.ItemName): 430 | if ("Tier2" in LootEntity.LootName): 431 | color = Color.GREEN 432 | self.Text("B",color,x,y) 433 | elif ("Tier3" in LootEntity.LootName): 434 | color = self.blink() 435 | self.Text("B",color,x,y) 436 | else: 437 | color = Color.WHITE 438 | self.Text("B",color,x,y) 439 | elif ("BoltAction" in LootEntity.ItemName): 440 | if ("Tier2" in LootEntity.LootName): 441 | color = Color.GREEN 442 | self.Text("S",color,x,y) 443 | elif ("Tier3" in LootEntity.LootName): 444 | color = self.blink() 445 | self.Text("S",color,x,y) 446 | else: 447 | color = Color.WHITE 448 | self.Text("S",color,x,y) 449 | elif "ArmorVest_Medium" in LootEntity.ItemName: 450 | color = Color.YELLOW 451 | self.Text("V",color,x,y) 452 | elif "ArmorVest_Large" in LootEntity.ItemName: 453 | color = self.blink() 454 | self.Text("V",color,x,y) 455 | elif ("Armor" in LootEntity.ItemName): 456 | color = Color.YELLOW 457 | self.DrawDot((x,y),color) 458 | elif ("Ammo" in LootEntity.ItemName): 459 | if ("U_Dakar_Ammo_Sniper" in LootEntity.ItemName): 460 | color = Color.GREEN 461 | self.DrawDot((x,y),color) 462 | elif ("U_Dakar_Ammo_MG" in LootEntity.ItemName): 463 | color = Color.CYAN 464 | self.DrawDot((x,y),color) 465 | else: 466 | color = Color.WHITE 467 | self.Text(".",color,x,y) 468 | 469 | 470 | else: 471 | color = Color.WHITE 472 | self.Text(".",color,x,y) 473 | 474 | 475 | def blink(self): 476 | global cnt 477 | if ((cnt%16)>= 8): 478 | return Color.RED 479 | return Color.GREEN 480 | 481 | def Text(self,text,color,x,y): 482 | textsurface = self.myfont.render(text, False, color) 483 | self.screen.blit(textsurface,(x,y)) 484 | 485 | # The main PyGame render loop, this takes a GameData object 486 | # which contains information on all relvant entities and draws 487 | # them onto the radar 488 | def Update(self): 489 | g_gamedata = BFV.g_gamedata 490 | 491 | for event in pygame.event.get(): # User did something 492 | if event.type == pygame.QUIT: # If user clicked close 493 | pygame.quit() 494 | 495 | inc_factor = self.zoom/100.0 * 4 496 | 497 | if (GetAsyncKeyState(0x6b)&0x8000): # '+' key 498 | if (self.zoom <= 39.9): self.zoom += inc_factor 499 | else: self.zoom = 40.0 500 | 501 | elif (GetAsyncKeyState(0x6d)&0x8000): # '-' key 502 | if (self.zoom >= 0.100001): self.zoom -= inc_factor 503 | else: self.zoom = 0.1 504 | 505 | # Set our background first, everything else on top of it 506 | try: 507 | self.screen.fill(Color.BLACK) 508 | except: 509 | print("[+] Quitting...") 510 | exit(0) 511 | 512 | pygame.draw.line(self.screen, Color.RED, (self.width/2,0),(self.width/2,self.height)) 513 | pygame.draw.line(self.screen, Color.RED, (0,self.height/2),(self.width,self.height/2)) 514 | 515 | if (g_gamedata.valid): 516 | if (g_gamedata.mysoldier == 0): 517 | if (g_gamedata.circledata != None): 518 | c = g_gamedata.circledata 519 | g_gamedata.myviewmatrix = [[0,0,0,0],[0,0,0,0],[0,0,0,0],c.InnerCircle_Const] 520 | g_gamedata.mytransform = [[0,0,0,0],[0,0,0,0],[0,0,0,0],c.InnerCircle_Const] 521 | 522 | self.UpdateBounds(g_gamedata) 523 | self.UpdateVehicles(g_gamedata) 524 | self.UpdateObjectives(g_gamedata) 525 | self.UpdateGrenades(g_gamedata) 526 | self.UpdateExplosives(g_gamedata) 527 | self.UpdateSupplies(g_gamedata) 528 | self.UpdateFirestorm(g_gamedata) 529 | self.UpdateSoldiers(g_gamedata) 530 | 531 | 532 | 533 | pygame.display.update() 534 | self.UpdateCount += 1 535 | 536 | def StartRadar(): 537 | global cnt 538 | global rad 539 | rad = None 540 | print ("[+] Searching for BFV.exe...") 541 | phandle = BFV.GetHandle() 542 | if (phandle): 543 | time.sleep(1) 544 | else: 545 | print ("[+] Error: Cannot find BFV.exe") 546 | exit(1) 547 | print ("[+] BFV.exe found, Handle: 0x%x"%(phandle)) 548 | 549 | BFV.initialize(phandle) # Gather offsets, patch the game 550 | 551 | print ("[+] Starting Radar...") 552 | rad = Radar(w,h) 553 | print ("[+] Done") 554 | 555 | cnt = 0 556 | while 1: 557 | BFV.Process(phandle,cnt) # this accesses game memory for data 558 | rad.Update() # this renders data to radar 559 | cnt += 1 560 | 561 | if __name__ == "__main__": 562 | print ("[+] Tormund's External Radar v1.4 for Battlefield V (Fall Update 2021) - Nov 4, 2021)") 563 | 564 | if (is_admin() == False): 565 | print ("[+] Error: python (or commandline) must be ran with admin privledges") 566 | input("Press Enter to continue...") 567 | exit(1) 568 | 569 | if (is_python3() == False): 570 | print ("[+] Error: detected Python 2, this script requires Python 3") 571 | raw_input("Press Enter to continue...") 572 | exit(1) 573 | 574 | arch = get_pythonArch() 575 | if (arch != 64): 576 | print ("[+] Error: detected Python 3.* %i-bit, this script requires Python 3 64-bit"% (arch)) 577 | print ("[+] Error: you may need to reinstall python to a 3.* 64-bit version") 578 | input("Press Enter to continue...") 579 | exit(1) 580 | 581 | if len(sys.argv) == 1: 582 | w = 800 583 | h = 600 584 | 585 | elif len(sys.argv) != 3: 586 | print ("[+] Usage: python ./radar.py [radar width] [radar height]") 587 | input("Press Enter to continue...") 588 | exit(1) 589 | 590 | else: 591 | try: 592 | w = int(sys.argv[1]) 593 | h = int(sys.argv[2]) 594 | except: 595 | print ("[+] Error: Cannot parse arguments") 596 | print ("[+] Usage: python ./radar.py [radar width] [radar height]") 597 | input("Press Enter to continue...") 598 | exit(1) 599 | 600 | # Main exception trap 601 | try: 602 | StartRadar() # Start the rest of the radar setup 603 | except BaseException: 604 | global rad 605 | if (rad != None): rad.quit() 606 | if str(sys.exc_info()[0]) != "": 607 | print ("[+] ") 608 | print ("[+] SEVERE: EXCEPTION CAUGHT!") 609 | print ("[+] \n") 610 | print(traceback.format_exc()) 611 | print ("[+] ") 612 | print ("[+] Exiting Radar...") 613 | finally: 614 | input("Press Enter to continue...") 615 | 616 | 617 | -------------------------------------------------------------------------------- /RadarSprites.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import sys 3 | import os 4 | 5 | class RadarSprites(): 6 | def __init__(self): 7 | 8 | scriptdir = os.path.dirname(os.path.realpath(__file__)) 9 | 10 | # Stationary Gun White 11 | self.stationgunwhite = pygame.image.load(scriptdir+"/images/sentry-gun.png") 12 | self.stationgunwhite = pygame.transform.scale(self.stationgunwhite,(20,20)) 13 | pygame.transform.threshold(self.stationgunwhite, self.stationgunwhite, search_color=(255,255,255,255), set_color=(0,0,0,0)) 14 | 15 | # Stationary Gun Red 16 | self.stationgunred = self.stationgunwhite.copy() 17 | pygame.transform.threshold(self.stationgunred, self.stationgunwhite, search_color=(0,0,0,255), set_color=(255,0,0,255)) 18 | 19 | # Stationary Gun Green 20 | self.stationgungreen = self.stationgunwhite.copy() 21 | pygame.transform.threshold(self.stationgungreen, self.stationgunwhite, search_color=(0,0,0,255), set_color=(0,255,0,255)) 22 | 23 | # Dead Soldier Icon White 24 | self.deadicon = pygame.image.load(scriptdir+"/images/dead.png") 25 | self.force_black(self.deadicon) 26 | self.swap_pixels(self.deadicon,[0,0,0,0xFF],[0,0,0,0x0]) 27 | self.deadicon = pygame.transform.scale(self.deadicon,(15,15)) 28 | self.deadicongreen = self.deadicon.copy() 29 | self.deadiconred = self.deadicon.copy() 30 | 31 | # Dead Soldier Red 32 | self.swap_pixels(self.deadiconred,[0xFF,0xFF,0xFF,0xFF],[0xFF,0x0,0,0xFF]) 33 | 34 | # Dead Soldier Green 35 | self.swap_pixels(self.deadicongreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 36 | 37 | # SpawnBeacon Icon White 38 | self.beaconiconwhite = pygame.image.load(scriptdir+"/images/radio.png") 39 | self.beaconiconwhite = pygame.transform.scale(self.beaconiconwhite,(15,15)) 40 | self.force_black(self.beaconiconwhite) 41 | self.swap_pixels(self.beaconiconwhite,[0,0,0,0xFF],[0,0,0,0x0]) 42 | 43 | # SpawnBeacon Icon Red 44 | self.beaconiconred = self.beaconiconwhite.copy() 45 | self.swap_pixels(self.beaconiconred,[0xFF,0xFF,0xFF,0xFF],[0xFF,0x0,0,0xFF]) 46 | 47 | # SpawnBeacon Icon Green 48 | self.beaconicongreen = self.beaconiconwhite.copy() 49 | self.swap_pixels(self.beaconicongreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 50 | 51 | # Flag White 52 | self.flagwhite = pygame.image.load(scriptdir+"/images/flag.png") 53 | self.flagwhite = pygame.transform.scale(self.flagwhite,(20,20)) 54 | self.force_black(self.flagwhite) 55 | self.swap_pixels(self.flagwhite,[0,0,0,0xFF],[0,0,0,0x0]) 56 | self.flagred = self.flagwhite.copy() 57 | self.flaggreen = self.flagwhite.copy() 58 | self.swap_pixels(self.flagred,[0xFF,0xFF,0xFF,0xFF],[0xFF,0,0,0xFF]) 59 | self.swap_pixels(self.flaggreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 60 | 61 | # Tank White 62 | self.tankwhite = pygame.image.load(scriptdir+"/images/tank.png") 63 | self.tankwhite = pygame.transform.scale(self.tankwhite,(14,30)) 64 | self.force_black(self.tankwhite) 65 | self.tankwhite = self.swap_pixels(self.tankwhite,[0,0,0,0xFF],[0,0,0,0x0]) 66 | 67 | self.tankred = self.tankwhite.copy() 68 | self.tankgreen = self.tankwhite.copy() 69 | self.swap_pixels(self.tankred,[0xFF,0xFF,0xFF,0xFF],[0xFF,0,0,0xFF]) 70 | self.swap_pixels(self.tankgreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 71 | 72 | # Plane White 73 | self.planewhite = pygame.image.load(scriptdir+"/images/plane.png") 74 | self.planewhite = pygame.transform.scale(self.planewhite,(28,40)) 75 | self.force_black(self.planewhite) 76 | self.planewhite = self.swap_pixels(self.planewhite,[0,0,0,0xFF],[0,0,0,0x0]) 77 | 78 | self.planered = self.planewhite.copy() 79 | self.planegreen = self.planewhite.copy() 80 | self.swap_pixels(self.planered,[0xFF,0xFF,0xFF,0xFF],[0xFF,0,0,0xFF]) 81 | self.swap_pixels(self.planegreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 82 | 83 | 84 | # Car White 85 | self.carwhite = pygame.image.load(scriptdir+"/images/transport.png") 86 | self.carwhite = pygame.transform.scale(self.carwhite,(24,40)) 87 | self.force_black(self.carwhite) 88 | self.carwhite = self.swap_pixels(self.carwhite,[0,0,0,0xFF],[0,0,0,0x0]) 89 | 90 | self.carred = self.carwhite.copy() 91 | self.cargreen = self.carwhite.copy() 92 | self.swap_pixels(self.carred,[0xFF,0xFF,0xFF,0xFF],[0xFF,0,0,0xFF]) 93 | self.swap_pixels(self.cargreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 94 | 95 | # Health Red 96 | self.health = pygame.image.load(scriptdir+"/images/health.png") 97 | self.health = pygame.transform.scale(self.health,(20,20)) 98 | self.swap_pixels(self.health,[0,0,0,0xFF],[0,0,0,0x0]) 99 | self.swap_pixels(self.health,[0xFF,0xFF,0xFF,0xFF],[0,0,0xFF,0xFF]) 100 | 101 | # Ammo Spot 102 | self.ammospot = pygame.image.load(scriptdir+"/images/ammo_spot.png") 103 | self.ammospot = pygame.transform.scale(self.ammospot,(10,28)) 104 | self.force_black(self.ammospot) 105 | self.ammospot = self.swap_pixels(self.ammospot,[0,0,0,0xFF],[0,0,0,0x0]) 106 | self.swap_pixels(self.ammospot,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 107 | 108 | 109 | # Explosive 110 | self.explosive = pygame.image.load(scriptdir+"/images/explosive.png") 111 | self.explosive = pygame.transform.scale(self.explosive,(15,15)) 112 | self.force_black(self.explosive) 113 | self.explosive = self.swap_pixels(self.explosive,[0,0,0,0xFF],[0,0,0,0x0]) 114 | 115 | self.explosivered = self.explosive.copy() 116 | self.explosivegreen = self.explosive.copy() 117 | self.swap_pixels(self.explosivered,[0xFF,0xFF,0xFF,0xFF],[0xFF,0,0,0xFF]) 118 | self.swap_pixels(self.explosivegreen,[0xFF,0xFF,0xFF,0xFF],[0x0,0xFF,0,0xFF]) 119 | 120 | # Crate 121 | self.crate = pygame.image.load(scriptdir+"/images/crate.png") 122 | # Safe 123 | self.safe = pygame.image.load(scriptdir+"/images/safe.png") 124 | 125 | def swap_pixels(self,img,colorbefore,colorafter): 126 | width,height=img.get_size() 127 | for x in range(0,width): 128 | for y in range(0,height): 129 | r,g,b,a=img.get_at((x,y)) 130 | if ((colorbefore[0] == r) and 131 | (colorbefore[1] == g) and 132 | (colorbefore[2] == b) and 133 | (colorbefore[3] == a)): 134 | img.set_at((x,y),(colorafter[0],colorafter[1],colorafter[2],colorafter[3])) 135 | return img 136 | 137 | def force_black(self,img): 138 | width,height=img.get_size() 139 | for x in range(0,width): 140 | for y in range(0,height): 141 | r,g,b,a=img.get_at((x,y)) 142 | if (((r != 0) and (r != 0xFF)) or 143 | ((g != 0) and (g != 0xFF)) or 144 | ((b != 0) and (b != 0xFF))): 145 | if ((r+g+b) >= 0x180): 146 | img.set_at((x,y),(0xFF,0xFF,0xFF,a)) 147 | else: 148 | img.set_at((x,y),(0x0,0x0,0x0,a)) -------------------------------------------------------------------------------- /Start-Radar.bat: -------------------------------------------------------------------------------- 1 | powershell -Command "Start-Process 'python' -Verb runAs -ArgumentList 'Radar.py 1680 900'" 2 | @if NOT ["%errorlevel%"]==["0"] pause -------------------------------------------------------------------------------- /demo/firestorm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/demo/firestorm.png -------------------------------------------------------------------------------- /demo/firestorm2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/demo/firestorm2.png -------------------------------------------------------------------------------- /demo/operations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/demo/operations.png -------------------------------------------------------------------------------- /images/ammo_spot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/ammo_spot.png -------------------------------------------------------------------------------- /images/crate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/crate.jpg -------------------------------------------------------------------------------- /images/crate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/crate.png -------------------------------------------------------------------------------- /images/dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/dead.png -------------------------------------------------------------------------------- /images/explosive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/explosive.png -------------------------------------------------------------------------------- /images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/flag.png -------------------------------------------------------------------------------- /images/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/health.png -------------------------------------------------------------------------------- /images/plane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/plane.png -------------------------------------------------------------------------------- /images/radio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/radio.png -------------------------------------------------------------------------------- /images/safe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/safe.jpg -------------------------------------------------------------------------------- /images/safe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/safe.png -------------------------------------------------------------------------------- /images/sentry-gun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/sentry-gun.png -------------------------------------------------------------------------------- /images/tank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/tank.png -------------------------------------------------------------------------------- /images/transport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/70RMUND/Tormund-BFV-Radar/e000a5f1b0c00041bd2cf3dc4838ac7724a6f95a/images/transport.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pygame==1.9.4 2 | --------------------------------------------------------------------------------