World is what you look in to find your local player, tables of entities, etc.
NetworkManager is what you look into find the scoreboard.
We’re going to take a look at some script functions in order to find those ‘base’ offsets for the two data structures everyone uses most. Continue reading
This is going to be a little messy. There are better ways (multiple vtables you can hook, also you can change a pointer in a script VM context to stop an individual thread from reporting to BE, etc.) but this has the advantage of not requiring any hooks, whether hard hooks or vtable hooks, which makes it considerably less detectable by Battleye, which currently checks a couple locations to check for BE Callback function VTable modifications. The giant disadvantage is you have to predict how the script engine will parse your scripts, which could be difficult.
Every time your client executes a script, it’s broken into expressions that are executed by the script engine. These expressions are pushed through a series of functions that eventually end up at Battleye’s callback function if you are in a multiplayer game with Battleye enabled. After it runs through Battleye’s callback function, it’s added to an AutoArray of recently run script expressions. Then, the next time a script is run, it’s checked against the array of recent expressions and if it matches one of these expressions perfectly (a map exists a few bytes earlier in the function, if the key for that expression exists in the map, it isn’t sent to Battleye for processing)
||This image has been resized. Click this bar to view the full image. The original image is sized 1274×772.
I’m sure you all see where this is going.
The script expressions are just regular c strings – they’re pointers to character arrays with ascii strings of the expression.
||This image has been resized. Click this bar to view the full image. The original image is sized 1446×816.
It’s in the NetworkClient class, the same place that links to your scoreboard. It’s at 0xDEEAE8 + 0x24] + 0x1DC. It contains a max of 200 expressions, and is a FIFO queue. So if you know you need to run a couple of expressions that will get you kicked, you could add these expressions to the end of the queue and run these expressions and these expressions would not be run through Battleye’s callback function, bypassing the server side script restriction filters in many cases and bypassing Battleye’s client side script detections entirely.
This is a copy of the article I wrote on UnknownCheats regarding manually spawning items and weapons through memory manipulation in Arma2OA. I would add that Arma uses its own memory manager, tbb4malloc_bi.dll by default. Arma also has a garbage collection system, so when an item’s references reaches 0 OR the game loads a new mission, the garbage collector frees the item. If you use regular malloc to allocate the memory you use to spawn an item, you will crash when Arma tries to garbage collect that item, because the Arma memory manager is trying to free something it didn’t allocate. To get around this, you can just call Arma’s memory manager’s Alloc function that way it can free the alloc’d item. I popped open tbb4malloc_bi.dll and found the address of the function (it’s in the export table) and made a function pointer to it in my version:
_MemAlloc = 0;
HMODULE hMalloc = GetModuleHandle(“tbb4malloc_bi.dll”);
_MemAlloc = (MemAlloc)((char *)hMalloc + 0x7A90);
if(*(DWORD *)_MemAlloc != 0x56ec8b55)
_MemAlloc = 0;
I then made a memory allocation function that calls the right version (_MemAlloc or malloc) depending on what’s available
void *allocateMem(SIZE_T size)
You can’t use a single pointer and set it to either _MemAlloc or malloc because they have different calling conventions, so this was my solution. Maybe not especially elegant, but it works, and my users that use different memory managers can still use the hack. I could find all the allocation functions for each possible memory manager (Arma offers quite a few) but…it’s not that big a priorty really.
Anyway, here is the original article: