- Joined
- Jul 30, 2012
- Messages
- 156
Reading memory from the script - direct access to any data in game
Last year I have discovered a critical exploit in WC3, as I described here. The vulnerability allowed for unlimited memory access from within a map's script, which is obviously a critical security issue. Anyone with this knowledge could create and distribute a modified map and gain complete control over a user's machine.However the possibility of memory access also brings a whole new potential for map making in Warcraft. It allows to finally achieve the always dreamed things that were never possible before. Basically there are no limits to what we can do with this power.
After patch 1.27b the exploit has been fixed, and it's no longer possible to write to memory. But I requested Blizzard to keep the read-only access, and they did it, so read-only memory access is still possible in the latest patches!
In the last year DracoL1ch, karaulov and I have been researching about the game memory. They have achieved a lot of amazing things, and released it in the Memory hack thread. Although that exploit doesn't work anymore (because it tries to unlock read-write access), some parts of that API can be ported to work this library, as long as they don't modify things in memory.
The following code provides the ability to read from any place in WC3 memory, but NOT write. It is split into 2 parts: the first library provides typecasting, and the second uses it to get memory access.
1 - Typecasting in the latest patch
As I already explained, the basis of the exploit is the ability to typecast values, which is still possible in the latest patches. Then, by usingC2I
and I2C
we can manipulate the Jass VM by skipping lines of execution, and ultimately running bytecode directly. The following library provides the typecasting functionality:
JASS:
library Typecast
globals
//These are not used, they are here just to fool Jasshelper.
code Code
integer Int
string Str
boolean Bool
//These are the actual ones used for typecasting.
code l__Code
integer l__Int
string l__Str
boolean l__Bool
endglobals
//The "return" line prevents Jasshelper from inlining these functions
function setCode takes code c returns nothing
set l__Code = c
return
endfunction
function setInt takes integer i returns nothing
set l__Int = i
return
endfunction
function setStr takes string s returns nothing
set l__Str = s
return
endfunction
function setBool takes boolean b returns nothing
set l__Bool = b
return
endfunction
//Jasshelper will append an "l__" prefix to all Typecast locals
private function Typecast1 takes nothing returns nothing
local integer Str //l__Str
local string Int //l__Int
endfunction
//# +nosemanticerror
function SH2I takes string s returns integer
call setStr(s)
return l__Str
endfunction
//# +nosemanticerror
function I2SH takes integer i returns string
call setInt(i)
return l__Int
endfunction
private function Typecast2 takes nothing returns nothing
local integer Bool //l_Bool
local boolean Int //l_Int
endfunction
//# +nosemanticerror
function B2I takes boolean b returns integer
call setBool(b)
return l__Bool
endfunction
//# +nosemanticerror
function I2B takes integer i returns boolean
call setInt(i)
return l__Int
endfunction
private function Typecast3 takes nothing returns nothing
local integer Code //l__Code
local code Int //l_Int
endfunction
//# +nosemanticerror
function C2I takes code c returns integer
call setCode(c)
return l__Code
endfunction
//# +nosemanticerror
function I2C takes integer i returns code
call setInt(i)
return l__Int
endfunction
//# +nosemanticerror
function realToIndex takes real r returns integer
return r
endfunction
function cleanInt takes integer i returns integer
return i
endfunction
//# +nosemanticerror
function indexToReal takes integer i returns real
return i
endfunction
function cleanReal takes real r returns real
return r
endfunction
endlibrary
This snippet requires that you use the experimental version of pjass, that can disable syntax checking on specific functions. After all, the code makes pretty much no sense. But it works!
This is because of a bug that occurs during the parsing of the script: if you declare a local variable with the same name as a global, it will cause the type of the global variable to be transformed, and become the type of the local! This means that all code that comes after the
Typecast3
function will treat variable l__Code
as an integer, and variable l__Int
as code.But the code that comes before Typecast will still treat those variables as their original types. That's why the
setCode
and setInt
functions are used. Since they come before Typecast, they are not affected by the bug. So they can be used to assign a value to those variables, and then that value can be read as a different type.2 - Getting access to memory
To unlock access to memory, we need to execute the JASS bytecode directly from an array. However. to obtain the memory address of an array, we need the ability to access memory in first place! This leads to a dead end, forcing us to find other ways to get memory access.There are some ways to solve this, and I decided to use a variable named "stand" to get around that problem, since this string appears in blizzard.j, it will always have the same id. But keep in mind that map optimizers will usually rename variables, so keep an eye on that if you optimize your map, because this name may change in the final MPQ, and you will need to change it back.
JASS:
library Memory initializer InitArrays requires Typecast
globals
integer array Memory
real array RMemory
//Arrays for unaligned memory access.
integer array Memory1
integer array Memory2
integer array Memory3
integer bytecode // Not used, it's here just to fool Jasshelper
integer array l__bytecode
private integer Count = 0
private trigger t = CreateTrigger()
private integer GetArrayAddress_Id
endglobals
function ReadMemory takes integer address returns integer
return Memory[address/4] //Inline-friendly
endfunction
function ReadMemory1 takes integer address returns integer
return Memory1[address/4] //Inline-friendly
endfunction
function ReadMemory2 takes integer address returns integer
return Memory2[address/4] //Inline-friendly
endfunction
function ReadMemory3 takes integer address returns integer
return Memory3[address/4] //Inline-friendly
endfunction
function ReadMemoryReal takes integer address returns real
return RMemory[address/4] //Inline-friendly
endfunction
private function Structs takes nothing returns integer
return -0x120
return 0x220
return 0x320
endfunction
function GetArrayAddress takes integer StructAddr returns integer
return Memory[StructAddr/4+3]
endfunction
//# +nosemanticerror
private function RegisterBCArray takes integer id, code func returns nothing
set l__bytecode[Count] = 0x0E000400
set l__bytecode[Count+1] = id
call setCode(func)
set l__bytecode[Count+2] = 0x11000000
set l__bytecode[Count+3] = Memory[l__Code/4 + 1]
set l__bytecode[Count+4] = 0x16000000
set l__bytecode[Count+5] = Memory[l__Code/4 - 1]
set Count = Count + 6
set l__bytecode[Count] = 0x27000000
endfunction
module Bytecode
integer value
static integer id
static integer address
static if thistype.hasTrigger then
static trigger trigger = CreateTrigger()
endif
static method operator[] takes integer i returns integer
return thistype(i).value
endmethod
static method operator[]= takes integer i, integer value returns nothing
set thistype(i).value = value
endmethod
private static method onRegister takes nothing returns nothing
set address = Memory[address/4 + 3]
static if thistype.hasTrigger then
call TriggerClearConditions(trigger)
call TriggerAddCondition(trigger, Condition(I2C(address)))
endif
endmethod
private static method onInit takes nothing returns nothing
set thistype(8190).value = 0 //Always allocate full size of the array
set id = Memory[C2I(function thistype.onInit)/4 + 9]
call RegisterBCArray(id, function thistype.onRegister)
endmethod
endmodule
private function RegisterMemArray takes integer id, integer id2 returns nothing
set l__bytecode[Count] = 0x0E000400
set l__bytecode[Count+1] = id
set l__bytecode[Count+2] = 0x13000000
set l__bytecode[Count+4] = 0x16000000
set l__bytecode[Count+5] = GetArrayAddress_Id
set l__bytecode[Count+6] = 0x0B010000
set l__bytecode[Count+8] = 0x06040000
set l__bytecode[Count+9] = id2
set l__bytecode[Count+10] = 0x11000000
set l__bytecode[Count+11] = id2
set Count = Count + 12
set l__bytecode[Count] = 0x27000000
endfunction
//! textmacro MemArray takes NAME, TYPE
module MemArray$NAME$
$TYPE$ value
private integer struct
static method operator address takes nothing returns integer
return thistype(3).struct
endmethod
static method operator address= takes integer i returns nothing
set thistype(3).struct = i
endmethod
static method operator[] takes integer i returns $TYPE$
return thistype(i).value
endmethod
private static method onInit takes nothing returns nothing
set thistype(2).struct = 0xFFFFFFFF
set thistype(1).struct = 0xFFFFFFFF
call RegisterMemArray(Memory[C2I(function thistype.onInit)/4 + 9], Memory[C2I(function thistype.onInit)/4-13])
endmethod
endmodule
//! endtextmacro
//! runtextmacro MemArray("","integer")
//! runtextmacro MemArray("Real","real")
private function InitBytecode takes integer i, integer r, integer func returns nothing
set l__bytecode[8190] = 0
set l__bytecode[0] = 0x0C000300 //op: 0C(LITERAL), type: 03(code), reg: 00
set l__bytecode[1] = C2I(function Structs)
set l__bytecode[2] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
set l__bytecode[3] = i
set l__bytecode[4] = 0x11000000 //op: 11(SETVAR), reg: 01, var: "Memory"
set l__bytecode[5] = i
set l__bytecode[6] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
set l__bytecode[7] = r
set l__bytecode[8] = 0x11000000 //op: 11(SETVAR), reg: 01, var: "RMemory"
set l__bytecode[9] = r
set l__bytecode[10] = 0x16000000 //op: 16(JASSCALL), func: "Step2"
set l__bytecode[11] = func
endfunction
//# +nosemanticerror
private function Step2 takes nothing returns nothing
set l__bytecode[12] = 0x0C010300 //op: 0C(LITERAL), type: 03(code), reg: 01
set l__bytecode[13] = l__Code-7
set l__bytecode[14] = 0x0C020300 //op: 0C(LITERAL), type: 03(code), reg: 02
set l__bytecode[15] = l__Code+25
set l__bytecode[16] = 0x0C030300 //op: 0C(LITERAL), type: 03(code), reg: 03
set l__bytecode[17] = l__Code+49
set l__bytecode[18] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
set l__bytecode[19] = Memory[C2I(function ReadMemory1)/4+13]
set l__bytecode[20] = 0x11010000 //op: 11(SETVAR), reg: 01, var: "Memory1"
set l__bytecode[21] = l__bytecode[19]
set l__bytecode[22] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
set l__bytecode[23] = Memory[C2I(function ReadMemory2)/4+13]
set l__bytecode[24] = 0x11020000 //op: 11(SETVAR), reg: 02, var: "Memory2"
set l__bytecode[25] = l__bytecode[23]
set l__bytecode[26] = 0x06030000 //op: 06(NEWGLOBAL), type: 03(code)
set l__bytecode[27] = Memory[C2I(function ReadMemory3)/4+13]
set l__bytecode[28] = 0x11030000 //op: 11(SETVAR), reg: 03, var: "Memory3"
set l__bytecode[29] = l__bytecode[27]
set l__bytecode[30] = 0x27000000
set GetArrayAddress_Id = Memory[C2I(function GetArrayAddress)/4-1]
endfunction
private function Typecast takes nothing returns nothing
local integer bytecode
endfunction
//# +nosemanticerror
private function Step1 takes nothing returns nothing
local integer array stand
call InitBytecode(stand[C2I(function ReadMemory)/4+13], stand[C2I(function ReadMemoryReal)/4+13], stand[C2I(function Step2)/4-1])
call TriggerAddCondition(t, Condition(I2C(stand[l__bytecode/4+3])))
endfunction
private function Step0 takes nothing returns integer
local code stand = function Structs
return -0xC5F0603
return 0x2700
endfunction
//Initialize registered bytecode arrays.
//# +nosemanticerror
private function InitArrays takes nothing returns boolean
call ForForce(bj_FORCE_PLAYER[0], I2C(Memory[l__bytecode/4+3]))
return false
endfunction
private module Init
//# +nosemanticerror
private static method onInit takes nothing returns nothing
call setCode(function Step0)
call TriggerAddCondition(t, Condition(I2C(l__Code+26)))
call TriggerAddCondition(t, Condition(I2C(l__Code+8)))
call setCode(function Step1)
call TriggerAddCondition(t, Condition(I2C(l__Code+8)))
call TriggerEvaluate(t)
call TriggerClearConditions(t)
call TriggerRegisterGameEvent(t, EVENT_GAME_LOADED)
call TriggerAddCondition(t, Condition(function InitArrays))
endmethod
endmodule
private struct MemInit extends array
implement Init
endstruct
struct CustomMem extends array
implement MemArray
endstruct
endlibrary
Here are a few libraries that provide some real functionality, since this is what everyone is expecting after all. This is just the beginning of course, as I said you can read basically any information from the memory, it's just a matter of finding where it is.
EDIT 04/10/2017:: Updated all libraries. Please check the changelog and get the new versions.
JASS:
/* Provides automatic detection of GameState object, Jass context, Unit
and Ability object data tables and GameUI object for Mouse functions.
This code is expected to work on all versions of WC3, even future ones. */
library Version initializer Init requires Memory, optional DummyCaster
globals
integer GameState
integer pJassContext
integer pGameUI
integer TagTable
integer StringHandles
integer array ObjectTables
integer IsMac = 0
integer IsLegacy = 0
endglobals
struct HandleTable extends array
implement MemArray
endstruct
struct GameTypeSupported extends array
implement MemArray
endstruct
native DebugBreak takes integer i returns nothing
private function GetDummyUnit takes nothing returns unit
static if LIBRARY_DummyCaster then
return DUMMY
else
return CreateUnit(Player(0), 'uloc', .0, .0, .0)
endif
endfunction
private struct BC extends array
static constant boolean hasTrigger = true
implement Bytecode
endstruct
private function FindDataTable takes integer func, boolean inlined returns integer
set CustomMem.address = func
loop
exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9 //always search after first CALL (E8)
set CustomMem.address = CustomMem.address + 1
endloop
if IsMac == 1 then //all code in Mac is position-independent, first CALL returns current EIP
set func = CustomMem.address
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]/16777216 == 0xFFFFFFE9 //begin searching after 2nd call
endloop
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[2]*256/16777216 == 0xFFFFFF8E and CustomMem[2]/1073741824 == 0xFFFFFFFF //LEA r32, [r32+offset]
endloop
return CustomMem[3] + func + 36
endif
if inlined then //GetUnitData is inlined in 1.27+, windows only.
loop
//"CMP r32,-1", followed by JZ SHORT (83F? FF, 74 XX)
exitwhen CustomMem[2]*16777216 == 0x83000000 and CustomMem[2]/2048 == 0xE9FFF
set CustomMem.address = CustomMem.address + 1
endloop
return CustomMem[1] - 8
endif
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]/16777216 == 0xFFFFFFBA //"MOV ECX, offset" (B9)
endloop
return CustomMem[2] + 28
endfunction
private function FindTagTable takes integer func returns integer
set CustomMem.address = func
loop
exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9
set CustomMem.address = CustomMem.address + 1
endloop
set func = CustomMem.address
//First call in Mac is GetEIP, in Windows it is DecodeTags.
//To my surprise, DecodeTags is inlined in Mac but not in Windows.
if IsMac == 1 then
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]*256/16777216 == 0xFFFFFF8C and CustomMem[1]/1073741824 == 0xFFFFFFFF //MOV r32, [r32+offset]
endloop
return CustomMem[2] + func + 8
endif
set CustomMem.address = CustomMem[1] + func + 6 //begin searching at DecodeTags function
loop
exitwhen (CustomMem[0]*32/2097152 == 0xFFFFFD8C and (CustomMem[0]+1073741824)/1073741824 == 1) or CustomMem[0]/16777216 == 0xFFFFFFA2 //MOV r32, [address]
set CustomMem.address = CustomMem.address + 1
endloop
return CustomMem[1]
endfunction
private function FindGameUI takes integer func returns integer
set CustomMem.address = func
if IsMac == 1 then
loop
exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9
set CustomMem.address = CustomMem.address + 1
endloop
set func = CustomMem.address
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]*256/16777216 == 0xFFFFFF8C and CustomMem[1]/1073741824 == 0xFFFFFFFF //MOV r32, [r32+offset]
endloop
return CustomMem[2] + func + 8
endif
loop
exitwhen CustomMem[0]/65536 == 0xFFFFA365 and CustomMem[1] == 0
set CustomMem.address = CustomMem.address + 1
endloop
if IsLegacy == 1 then
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]/65536 == 0x3D83 //CMP [address], constant
endloop
return CustomMem[2]
endif
loop
set CustomMem.address = CustomMem.address + 1
exitwhen (CustomMem[1]-0x80000000)/16777216 == 33 or (CustomMem[1]*32/2097152 == 0xFFFFFD8C and (CustomMem[1]+1073741824)/1073741824 == 1) //MOV r32, [address]
endloop
return CustomMem[2]
endfunction
private function Step2 takes integer i returns nothing
local unit u = GetDummyUnit()
call UnitAddAbility(u, 'AInv')
set GameState = i
set HandleTable.address = Memory[Memory[GameState/4+7]/4+103] - 0xBFFFFC
set GameTypeSupported.address = Memory[GameState/4+12]+48
set i = HandleTable[GetHandleId(u)*3]/4 //ConvertHandle(u)
set CustomMem.address = Memory[Memory[i]/4+88+IsMac]-3
loop
exitwhen (CustomMem[0]-0x80000000)/33554432 == 52 //matches both CALL (0xE8) and JMP (0xE9)
set CustomMem.address = CustomMem.address + 1
endloop
set ObjectTables[0] = FindDataTable(CustomMem[1] + CustomMem.address + 5, (IsLegacy == 0))/4 //GetUnitData is inlined on 1.27+, windows only.
set CustomMem.address = Memory[Memory[Memory[i+126]/4]/4+16+IsMac]-3
loop
exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9 //Skip first CALL
set CustomMem.address = CustomMem.address + 1
endloop
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]/16777216 == 0xFFFFFFE9 //2nd CALL is GetAbilityData
endloop
set ObjectTables[1] = FindDataTable(CustomMem[2] + CustomMem.address + 9, false)/4 //GetAbilityData is never inlined
set TagTable = FindTagTable(Memory[Memory[HandleTable[GetHandleId(Condition(function GetDummyUnit))*3]/4]/4+30+IsMac])/4
set CustomMem.address = Memory[Memory[i]/4+101+IsMac]
if IsMac == 1 then //First call in Mac is GetEIP, so skip it
loop
exitwhen CustomMem[0]/16777216 == 0xFFFFFFE9
set CustomMem.address = CustomMem.address + 1
endloop
endif
loop
set CustomMem.address = CustomMem.address + 1
exitwhen CustomMem[1]/16777216 == 0xFFFFFFE9 //2nd CALL is GetAbilityData
endloop
set pGameUI = FindGameUI(CustomMem[2] + CustomMem.address + 9)/4
static if LIBRARY_Mouse then
set MouseEnv.address = Memory[Memory[pGameUI]/4 + 239]+784
endif
static if not LIBRARY_DummyCaster then
call RemoveUnit(u)
endif
set u = null
endfunction
private function Step1 takes integer p, integer i returns nothing
set i = i/4
loop
exitwhen Memory[i] == BC.address and Memory[i+3] == 300000
set i = i+1
endloop
set pJassContext = Memory[i-1]
if Memory[pJassContext/4+17] == 300000 then
set IsMac = 1
else
loop
set i = i-1
set pJassContext = Memory[i]
exitwhen ModuloInteger(pJassContext*65536/65536, 0x28B0) == 0x88 and pJassContext>65536 and Memory[pJassContext/4+17] == 300000
endloop
endif
set StringHandles = Memory[pJassContext/4+2589]/4+2
if p != GetPlayers() then
set BC(33).value = 623 //SetPlayers
return
endif
set IsLegacy = 1
if IsMac == 1 then
set BC(28).value = 0x13000000
set BC(33).value = 631 //SetStartLocPrio
return
endif
set BC[33] = 642 //SetGamePlacement
call GetGamePlacement()
return
endfunction
private function DebugBreak_Id takes nothing returns nothing
call DebugBreak(0)
endfunction
//# +nosemanticerror
private function InitBytecode takes nothing returns nothing
set BC[0] = 0x15000000
set BC[1] = 652 //GetPlayers
set BC[2] = 0x13000000
set BC[4] = 0x15000000
set BC[5] = 623 //SetPlayers
set BC[6] = 0x17000000
set BC[8] = 0x13000000
set BC[10] = 0x15000000
set BC[11] = 595 //R2I
set BC[12] = 0x13000000
set BC[14] = 0x13000000
set BC[16] = 0x15000000
set BC[17] = Memory[C2I(function DebugBreak_Id)/4+5]
set BC[18] = 0x17000000
set BC[20] = 0x13000000
set BC[22] = 0x15000000
set BC[23] = 595 //R2I
set BC[24] = 0x13000000
set BC[26] = 0x16000000
set BC[27] = Memory[C2I(function Step1)/4-1]
set BC[30] = 0x13000000
set BC[32] = 0x15000000
set BC[34] = 0x0D010000
set BC[36] = 0x17000000
set BC[38] = 0x13000000
set BC[40] = 0x15000000
set BC[41] = 595 //R2I
set BC[42] = 0x21010100
set BC[44] = 0x17010000
set BC[46] = 0x13010000
set BC[48] = 0x0D010000
set BC[50] = 0x15000000
set BC[51] = 595 //R2I
set BC[52] = 0x20000100
set BC[54] = 0x13000000
set BC[56] = 0x16000000
set BC[57] = Memory[C2I(function Step2)/4-1]
set BC[58] = 0x27000000
endfunction
private function Init takes nothing returns nothing
call InitBytecode()
call TriggerRegisterGameEvent(BC.trigger, EVENT_GAME_LOADED)
call TriggerEvaluate(BC.trigger)
endfunction
endlibrary
JASS:
library Mouse requires Memory, Version
struct MouseEnv extends array //Library Version will initialize the struct.
implement MemArrayReal
endstruct
function GetMouseX takes nothing returns real
return MouseEnv(0).value
endfunction
function GetMouseY takes nothing returns real
return MouseEnv(1).value
endfunction
function GetMouseZ takes nothing returns real
return MouseEnv(2).value
endfunction
endlibrary
JASS:
/* Provides the binary AND, OR and XOR operations, using natives
and memory reading. This should be faster than all alternatives
that use math operations and/or lookup tables */
library Bitwise requires Memory, Version
globals
constant gametype GAME_TYPE_ALL = ConvertGameType(0xFFFFFFFF)
endglobals
function GetGameTypeSupported takes nothing returns integer
return GameTypeSupported[0]
endfunction
function BitwiseNot takes integer i returns integer
return 0xFFFFFFFF - i
endfunction
function BitwiseOr takes integer a, integer b returns integer
call SetGameTypeSupported(GAME_TYPE_ALL, false)
call SetGameTypeSupported(ConvertGameType(a), true)
call SetGameTypeSupported(ConvertGameType(b), true)
return GetGameTypeSupported()
endfunction
function BitwiseAnd takes integer a, integer b returns integer
call SetGameTypeSupported(GAME_TYPE_ALL, false)
call SetGameTypeSupported(ConvertGameType(a), true)
call SetGameTypeSupported(ConvertGameType(BitwiseNot(b)), false)
return GetGameTypeSupported()
endfunction
function BitwiseXor takes integer a, integer b returns integer
call SetGameTypeSupported(GAME_TYPE_ALL, false)
call SetGameTypeSupported(ConvertGameType(a), true)
call SetGameTypeSupported(ConvertGameType(b), true)
return GetGameTypeSupported()*2 - a - b
endfunction
endlibrary
JASS:
library ObjectData initializer onInit requires Memory, Version, Bitwise, Table
globals
constant integer UNIT_OBJECT_DATA = 0
constant integer ABILITY_OBJECT_DATA = 1
private integer array H
private Table array tb
endglobals
function IntegerHash takes integer i returns integer
local integer a = 0x7FED7FED
local integer b = 0xEEEEEEEE
local integer byte
loop
set byte = i*16777216/16777216
set a = BitwiseXor(a+b,H[byte])
set i = i/256
exitwhen i == 0
set b = b*32+a+b+byte+3
endloop
return a
endfunction
/* Searches the provided data table for the object's data structure.
This function will return 0 if the object has not been loaded yet!
You can force an object to be loaded by calling GetObjectName. It's
up to you to decide if you're going to call it or not. */
function FindObjectData takes integer pData, integer rawcode returns integer
local integer hash = IntegerHash(rawcode)
local integer list = Memory[pData]/4 + ModuloInteger(hash, Memory[pData+2]+1)*3
local integer i = Memory[list+2]/4
loop
exitwhen i <= 0
if Memory[i] == hash and Memory[i+5] == rawcode then
return i
endif
set i = Memory[Memory[list]/4+i+1]/4
endloop
return 0
endfunction
/*Object data tables are actually... hashtables! The computational work to
retrieve a object's data is exactly the same done by hashtable natives.
But doing that work under native code is much faster than doing it in
JASS. So caching the entries is certainly a good idea.*/
function GetObjectData takes integer objType, integer rawcode returns integer
/*Should I cache everything into a single Table, or is it better to keep
separete Tables for each object type? */
local integer data = tb[objType][rawcode]
if data == 0 then
set data = FindObjectData(ObjectTables[objType], rawcode)
set tb[objType][rawcode] = data
endif
return data
endfunction
//Cache becomes invalid after saved game is loaded
private function OnGameLoaded takes nothing returns boolean
local integer i = 0
loop
call tb[i].flush()
set i = i+1
exitwhen ObjectTables[i] == 0
endloop
return false
endfunction
private function onInit takes nothing returns nothing
local integer i = 0
local trigger t = CreateTrigger()
call TriggerRegisterGameEvent(t, EVENT_GAME_LOADED)
call TriggerAddCondition(t, Condition(function OnGameLoaded))
loop
set tb[i] = Table.create()
set i = i+1
exitwhen ObjectTables[i] == 0
endloop
set H[1]=0xA22E726E
set H[2]=0xD43D94C0
set H[3]=0x6DE064C7
set H[4]=0xFE8D4B2F
set H[5]=0x345A287E
set H[6]=0x13941BCF
set H[7]=0xD822114D
set H[8]=0xA79E1270
set H[9]=0xFB2D4CF9
set H[10]=0xCB25DDAE
set H[11]=0x7B5E64D5
set H[12]=0x88544672
set H[13]=0xF201BF3F
set H[14]=0x677CAF6E
set H[15]=0x34502020
set H[16]=0x5DD18D92
set H[18]=0x320F2252
set H[19]=0xCBB1F259
set H[20]=0x5C5ED8C1
set H[21]=0x922BB610
set H[22]=0x7165A961
set H[23]=0x35F39EDF
set H[24]=0x056FA002
set H[25]=0x58FEDA8B
set H[26]=0x28F76B40
set H[27]=0xD92FF267
set H[28]=0xE625D404
set H[29]=0x4FD34CD1
set H[30]=0xC54E3D00
set H[31]=0x9221ADB2
set H[32]=0x2BC26B40
set H[33]=0xCDF0DDAE
set H[35]=0x99A2D007
set H[36]=0x2A4FB66F
set H[37]=0x601C93BE
set H[38]=0x3F56870F
set H[39]=0x03E47C8D
set H[40]=0xD3607DB0
set H[41]=0x26EFB839
set H[42]=0xF6E848EE
set H[43]=0xA720D015
set H[44]=0xB416B1B2
set H[45]=0x1DC42A7F
set H[46]=0x933F1AAE
set H[47]=0x60128B60
set H[48]=0x921F9B39
set H[49]=0x344E0DA7
set H[50]=0x665D2FF9
set H[52]=0x90ACE668
set H[53]=0xC679C3B7
set H[54]=0xA5B3B708
set H[55]=0x6A41AC86
set H[56]=0x39BDADA9
set H[57]=0x8D4CE832
set H[58]=0x5D4578E7
set H[59]=0x0D7E000E
set H[60]=0x1A73E1AB
set H[61]=0x84215A78
set H[62]=0xF99C4AA7
set H[63]=0xC66FBB59
set H[64]=0x0172B4D1
set H[65]=0xA3A1273F
set H[66]=0xD5B04991
set H[67]=0x6F531998
set H[69]=0x35CCDD4F
set H[70]=0x1506D0A0
set H[71]=0xD994C61E
set H[72]=0xA910C741
set H[73]=0xFCA001CA
set H[74]=0xCC98927F
set H[75]=0x7CD119A6
set H[76]=0x89C6FB43
set H[77]=0xF3747410
set H[78]=0x68EF643F
set H[79]=0x35C2D4F1
set H[80]=0xCBA5D782
set H[81]=0x6DD449F0
set H[82]=0x9FE36C42
set H[83]=0x39863C49
set H[84]=0xCA3322B1
set H[86]=0xDF39F351
set H[87]=0xA3C7E8CF
set H[88]=0x7343E9F2
set H[89]=0xC6D3247B
set H[90]=0x96CBB530
set H[91]=0x47043C57
set H[92]=0x53FA1DF4
set H[93]=0xBDA796C1
set H[94]=0x332286F0
set H[95]=0xFFF5F7A2
set H[96]=0xEC6BE431
set H[97]=0x8E9A569F
set H[98]=0xC0A978F1
set H[99]=0x5A4C48F8
set H[100]=0xEAF92F60
set H[101]=0x20C60CAF
set H[103]=0xC48DF57E
set H[104]=0x9409F6A1
set H[105]=0xE799312A
set H[106]=0xB791C1DF
set H[107]=0x67CA4906
set H[108]=0x74C02AA3
set H[109]=0xDE6DA370
set H[110]=0x53E8939F
set H[111]=0x20BC0451
set H[112]=0x27DDEEB3
set H[113]=0xCA0C6121
set H[114]=0xFC1B8373
set H[115]=0x95BE537A
set H[116]=0x266B39E2
set H[117]=0x5C381731
set H[118]=0x3B720A82
set H[120]=0xCF7C0123
set H[121]=0x230B3BAC
set H[122]=0xF303CC61
set H[123]=0xA33C5388
set H[124]=0xB0323525
set H[125]=0x19DFADF2
set H[126]=0x8F5A9E21
set H[127]=0x5C2E0ED3
set H[128]=0x5861ED90
set H[129]=0xFA905FFE
set H[130]=0x2C9F8250
set H[131]=0xC6425257
set H[132]=0x56EF38BF
set H[133]=0x8CBC160E
set H[134]=0x6BF6095F
set H[135]=0x3083FEDD
set H[137]=0x538F3A89
set H[138]=0x2387CB3E
set H[139]=0xD3C05265
set H[140]=0xE0B63402
set H[141]=0x4A63ACCF
set H[142]=0xBFDE9CFE
set H[143]=0x8CB20DB0
set H[144]=0x04D2B307
set H[145]=0xA7012575
set H[146]=0xD91047C7
set H[147]=0x72B317CE
set H[148]=0x035FFE36
set H[149]=0x392CDB85
set H[150]=0x1866CED6
set H[151]=0xDCF4C454
set H[152]=0xAC70C577
set H[154]=0xCFF890B5
set H[155]=0x803117DC
set H[156]=0x8D26F979
set H[157]=0xF6D47246
set H[158]=0x6C4F6275
set H[159]=0x3922D327
set H[160]=0x34DA2252
set H[161]=0xD70894C0
set H[162]=0x0917B712
set H[163]=0xA2BA8719
set H[164]=0x33676D81
set H[165]=0x69344AD0
set H[166]=0x486E3E21
set H[167]=0x0CFC339F
set H[168]=0xDC7834C2
set H[169]=0x30076F4B
set H[171]=0xB0388727
set H[172]=0xBD2E68C4
set H[173]=0x26DBE191
set H[174]=0x9C56D1C0
set H[175]=0x692A4272
set H[176]=0x84A19B2B
set H[177]=0x26D00D99
set H[178]=0x58DF2FEB
set H[179]=0xF281FFF2
set H[180]=0x832EE65A
set H[181]=0xB8FBC3A9
set H[182]=0x9835B6FA
set H[183]=0x5CC3AC78
set H[184]=0x2C3FAD9B
set H[185]=0x7FCEE824
set H[186]=0x4FC778D9
set H[188]=0x0CF5E19D
set H[189]=0x76A35A6A
set H[190]=0xEC1E4A99
set H[191]=0xB8F1BB4B
set H[192]=0x77ABB98E
set H[193]=0x19DA2BFC
set H[194]=0x4BE94E4E
set H[195]=0xE58C1E55
set H[196]=0x763904BD
set H[197]=0xAC05E20C
set H[198]=0x8B3FD55D
set H[199]=0x4FCDCADB
set H[200]=0x1F49CBFE
set H[201]=0x72D90687
set H[202]=0x42D1973C
set H[203]=0xF30A1E63
set H[205]=0x69AD78CD
set H[206]=0xDF2868FC
set H[207]=0xABFBD9AE
set H[208]=0x0DFE40C1
set H[209]=0xB02CB32F
set H[210]=0xE23BD581
set H[211]=0x7BDEA588
set H[212]=0x0C8B8BF0
set H[213]=0x4258693F
set H[214]=0x21925C90
set H[215]=0xE620520E
set H[216]=0xB59C5331
set H[217]=0x092B8DBA
set H[218]=0xD9241E6F
set H[219]=0x895CA596
set H[220]=0x96528733
set H[222]=0x757AF02F
set H[223]=0x424E60E1
set H[224]=0x98835092
set H[225]=0x3AB1C300
set H[226]=0x6CC0E552
set H[227]=0x0663B559
set H[228]=0x97109BC1
set H[229]=0xCCDD7910
set H[230]=0xAC176C61
set H[231]=0x70A561DF
set H[232]=0x40216302
set H[233]=0x93B09D8B
set H[234]=0x63A92E40
set H[235]=0x13E1B567
set H[236]=0x20D79704
set H[237]=0x8A850FD1
set H[239]=0xCCD370B2
set H[240]=0xCBAFDFE0
set H[241]=0x6DDE524E
set H[242]=0x9FED74A0
set H[243]=0x399044A7
set H[244]=0xCA3D2B0F
set H[245]=0x000A085E
set H[246]=0xDF43FBAF
set H[247]=0xA3D1F12D
set H[248]=0x734DF250
set H[249]=0xC6DD2CD9
set H[250]=0x96D5BD8E
set H[251]=0x470E44B5
set H[252]=0x54042652
set H[253]=0xBDB19F1F
set H[254]=0x332C8F4E
endfunction
endlibrary
JASS:
library Utils requires Memory, Version, ObjectData
function ConvertHandle takes handle h returns integer
return HandleTable[GetHandleId(h)*3]
endfunction
function GetStringAddress takes string s returns integer
return Memory[Memory[Memory[StringHandles]/4+SH2I(s)*4+2]/4+7]
endfunction
function DecodeTags takes integer a, integer b returns integer
local integer i = Memory[TagTable]/4
if a < 0 then
set i = i+8
set a = a-0x80000000
endif
if a < Memory[i+7] then
set i = Memory[i+3]/4+a*2
if Memory[i] == 0xFFFFFFFE then
set i = Memory[i+1]/4
if Memory[i+6] == b then
return i
endif
endif
endif
return 0
endfunction
function GetAgentFromRef takes integer ref returns integer
local integer i = Memory[ref+1]
set ref = Memory[ref]
if ref !=0xFFFFFFFF and i !=0xFFFFFFFF then
set i = DecodeTags(ref, i)
if i != 0 and Memory[i+8] == 0 then
return Memory[i+21]
endif
endif
return 0
endfunction
function GetUnitAbility takes unit u, integer id returns integer
local integer i = GetAgentFromRef(ConvertHandle(u)/4+119)
loop
exitwhen i == 0 or Memory[i/4+13] == id
set i = GetAgentFromRef(i/4+9)
endloop
return i
endfunction
function GetUnitFlags takes unit u returns integer
return Memory[ConvertHandle(u)/4+23]
endfunction
function IsUnitStunned takes unit u returns boolean
return Memory[ConvertHandle(u)/4+102] > 0
endfunction
function GetAgentType takes agent a returns integer
return Memory1[Memory[Memory[ConvertHandle(a)/4]/4+7]/4]
endfunction
function GetUnitArmor takes unit u returns real
return RMemory[ConvertHandle(u)/4+56]
endfunction
function GetHeroPrimaryAttribute takes unit u returns integer
return Memory[Memory[ConvertHandle(u)/4+124]/4+51]
endfunction
function GetAbilityMaxLevel takes integer abil returns integer
return Memory[GetObjectData(ABILITY_OBJECT_DATA, abil)+20]
endfunction
function GetAbilityManaCost takes integer abil, integer level returns integer
return Memory[Memory[GetObjectData(ABILITY_OBJECT_DATA, abil)+21]/4-22+level*26]
endfunction
function GetAbilityCooldown takes integer abil, integer level returns real
return RMemory[Memory[GetObjectData(ABILITY_OBJECT_DATA, abil)+21]/4-21+level*26]
endfunction
function GetAbilityCurrentCooldown takes integer address returns real
local integer pData = Memory[address/4+55]/4
if pData != 0 then
return RMemory[pData+1] - RMemory[Memory[pData+3]/4+16]
endif
return .0
endfunction
endlibrary
I am making all of this for free, but if you feel like making a donation for my work, it would really please my heart ♥
If you want to make a donation, please send a PM to me.
03/10/2017
-Mouse functions are back! Now present in a separate library, tested and working in all versions of WC3 through automatic detection! Also optimized so that getting the mouse coordinates is now a simple array lookup.
-Implemented automatic detection of Ability object table. Now functions such as GetAbilityManaCost and GetAbilityCooldown are working again.
-Introduced function
GetUnitAbility
which searches and returns an ability object from a unit's current abilities. Notice that this function returns an object address, and currently the only thing you can do with it is call GetAbilityCurrentCooldown
. Also you should only use that address as soon as you get it, you are not supposed to save it anywhere as it may become invalid later.-Fixed a bug where Step2 was not being executed when the map was compiled in Debug Mode or with optimization disabled.
29/09/2017
-Introduced new module: MemArray. This module provides a custom array for memory access, you just implement it in your struct and set the address, and then your struct becomes an array-like object where
Struct[0]
returns the memory at the specified address, and so on. The library Memory also exposes an array named "CustomMem" that implements this module, you can use it as well.-Module "Bytecode" now exposes an "id" member, that returns the string id of the bytecode array. This makes it easier for you to write self-modifying bytecode, you no longer need to worry about obtaining the variable id, just use the provided id in the GETARRAY and SETARRAY instructions.
-ConvertHandle function from library Utils now uses the new MemArray module. Handle conversion is now a simple array lookup
HandleTable[GetHandleId(h)*3]
instead of a bunch of memory reads. The same principle was also used in the Bitwise library, to speed up bitwise operations.-Library Version now detects UnitData table and AgentTags table. Detection of other data tables and Mouse functions shall be implemented soon.
-Reworked library ObjectData to work with new code, also it now caches the found values in a Table object (after all, ObjectTables are hashtables internally, so it's faster to delegate this work to native code instead of doing it in pure JASS)
08/09/2017
-Introduced new functionality for bytecode arrays! Now you can have your bytecode array get automatically assigned to a trigger! Just write the line
static constant boolean hasTrigger = true
before implementing the Bytecode module, and it will automatically create a trigger and assign your bytecode array to it. Then you can use the .trigger
member to access that trigger. You can register events to it, or call it directly with TriggerEvaluate
-Implemented support for loading saved games! When saved games are loaded, everything is located in different addresses. Now the Bytecode module will automatically obtain the new addresses of all bytecode arrays, and update the corresponding triggers. Also Version library will redetect game addresses after the game is loaded.
-Minor improvements to the code, Memory library now uses a different method to unlock memory access.
15/08/2017
-Introduced experimental detection of game addresses. Currently detects the address of Jass Context in all versions of WC3 already released, also expected to work in future versions. Detection of main game class shall be included in the future.
-Introduced new syntax for bytecode execution! Now it's much easier to run bytecode from an array:
All you need to do is declare a struct that
Now you can use that struct normally as a regular array:
And you can easily use the member
extends array
, and then implement the Bytecode module:
JASS:
struct MyBCArray extends array
implement Bytecode
endstruct
JASS:
function InitBytecode takes nothing returns nothing
set MyBCArray[0] = <my>
set MyBCArray[1] = <bytecode>
set MyBCArray[2] = <here>
endfunction
MyBCArray.address
to obtain the address of that array. Then you just need to pass that address to I2C
and execute it.14/04/2017
-Mouse functions added! Credits to DracoL1ch for finding the offsets.
-Typecast library slightly modified to allow functions C2I and I2C to be inlined.
-Memory library updated with better code, memory reading is now a simple array lookup instead of a trigger evaluation
-Added array
RMemory
to allow reading the memory as a real without the need for typecasting-Version library updated with offsets for patches 1.27b and 1.28
-Changed implementation of XOR in Bitwise library,
XOR(a,b)
is now implemented with OR(a,b)*2 - a - b
26/05/2016
-Typecast library updated with typecasts of all basic types to and from integer.
-Initial release of libraries Version, Bitwise, ObjectData and Utils
16/05/2016
-Initial release of libraries Memory and Typecast
Last edited: