Rather not name drop it here for various reasons. (You need to know how to get your own to use this script!)
This utilizes an old memory function library-- SO old I don't remember exactly who made it or where I found it.
Experienced users can easily rewrite this to work with the somewhat modernized classMemory.ahk by RHCP from kalamity github.
Without further ado:
Spoiler
The library:
If anyone knows who actually made this library please do let me know!
Code: Select all
IF NOT A_IsAdmin
{
Run *RunAs "%A_ScriptFullPath%"
ExitApp
}
;Makes the script rerun itself as admin if not elevated already
;This will have a UAC prompt like anything that tries to run as admin if UAC is enabled.
#Include, memory.ahk ;FIle must be in same folder or further specified!
#SingleInstance force ;This makes it so we can only open one instance of the script at a time.
;Mostly irrelevant without UAC disabled since memory requires admin anyway.
WinActivate, Atlantica Online ;Force this to open so the below ToolTip gets a static position.
ToolTip, |DEL=Exit|End=Stop|Home=Start|, 103, 75, 3 ;Feel free to remove these two lines.
;Personally I need the reminder.
;I write far too many scripts for far too many different games with different hotkeys!
WinGet, Atlantica, PID, Atlantica Online ;Point to the client to retrieve PID as var
hwnd1 := MemoryOpenFromPID(Atlantica)
;Just like cheat engine in its most basic form, we're opening the memory from the client for access.
btspd = 0xF25288 ;address I used for battlespeed, it should be a static address in all versions of AO--
;and probably on the internet somewhere for your server already
inbt = 0xF6F9E4 ;address I used for if in battle. If not in battle = 0 and if in battle = 1
;(Easy to find and it's also static, no pointer required.)
whot = 0xF1A798 ;address I used for whose turn. If my turn = 1 and if enemy turn = 0 (Same as above)
abc = 0x1352BC
;address I used for auto battle count, requires offset for pointer.
;(This is as simple as finding the 4byte integer for your visually obvious AB count in-game)
abcoff = 0x012BA8 ;Offset for auto battle count.
;To obtain this offset you need to learn how to find a pointer to the auto battle count address.
Home::
btvc = 0
;btvc is my weird abbreviation for battle variable count which is even worded oddly.
;This will be our auto battle total divided by 5 (Specifically counting up from 0, not when using auto battle!)
Flag = 0
;Flag will be our global variable working much like a railroad switch.
Goto, BattleManager1
;The above hotkey automatically sets two of our global variables.
;It jumps into our first label with the press of home key.
BattleManager1:
Loop,
{
speed := MemoryReadPointer(hwnd1,btspd, "int",,Offsets=0)
;Assigns the memory found from address btspd to variable "speed"
inbattle := MemoryReadPointer(hwnd1,inbt, "int",,Offsets=0)
;Assigns the memory found from address inbt to variable "inbattle"
whoseturn := MemoryReadPointer(hwnd1,whot, "int",,Offsets=0)
;Assigns the memory found from address whot to variable "whoseturn"
abcount := MemoryReadPointer(hwnd1,abc, "int",,1,abcoff)
;Assigns the memory found from address abc+offset abcoff to variable "abcount"
ToolTip, Started!, 402, 62, 1 ;Just a visual indicator that you started the code.
If (speed != 420)
{
MemoryWrite(hwnd1,btspd,420,"int",,Offsets=0)
Sleep, 1000
Goto, BattleManager2
}
Else If (speed = 420)
{
Sleep, 1000
Goto, BattleManager2
}
}
;The above automatically checks if battlespeed is set to 420, if not it sets it to 420.
;(Max normally is 120 from in-game setting "very fast")
BattleManager2:
{
If (inbattle = 0)
{
Goto, BattleManager1
;If not in battle we're going back to the last label--
;Because there's no point in going forward otherwise.
}
Else If (Flag = 0) AND (whoseturn = 1) AND (inbattle = 1) AND (abcount != 0)
{
Goto, BattleManager3
;If our AB isn't empty we're going to keep doing auto battles.
}
Else If (Flag = 1) AND (inbattle = 1)
{
Goto, BattleManager4
;If our AB is empty we'll be doing guard battles.
}
}
BattleManager3:
Loop,
{
ToolTip, AB=%abcount%, 452, 62, 2 ;Visual display of auto battle count.
;Mostly a pointless line but you can put whatever is useful to you or remove it.
Sleep, 333
inbattle := MemoryReadPointer(hwnd1,inbt, "int",,Offsets=0)
whoseturn := MemoryReadPointer(hwnd1,whot, "int",,Offsets=0)
abcount := MemoryReadPointer(hwnd1,abc, "int",,1,abcoff)
If (abcount = 0) AND (inbattle = 0)
{
Flag = 1
Goto, BattleManager1
;If we're out of auto battles and the battle ended Flag=1 as a global variable--
;To let BattleManager2 know to go to BattleManager4 instead
}
Else If (inbattle = 0)
{
Goto, BattleManager1
;If battle ends for any other reason we continue as normal.
}
}
BattleManager4:
Loop,
{
ToolTip, 5x=%btvc%, 452, 62, 2 ;Another useless visual indication.
;Just a reminder that our actual ab count is 5x btvc's value
Sleep, 333
inbattle := MemoryReadPointer(hwnd1,inbt, "int",,Offsets=0)
whoseturn := MemoryReadPointer(hwnd1,whot, "int",,Offsets=0)
If (inbattle = 0) and (Flag = 1)
{
btvc++
Goto, BattleManager1
;If we finished a battle and our auto battles aren't full yet we add 1 to btvc
}
Else If (whoseturn = 1) AND (inbattle = 1) AND (btvc < 4) and (Flag = 1)
{
ControlSend,,{g},Atlantica Online
Sleep, 8000
Goto, BattleManager4
;If It's our turn, we are in battle, btvc is less than 4, and auto battles aren't full
;we spam guard every 8 seconds. The time may/may not matter!
;If you're playing an official or populated server then I would suggest using logical random sleeps
;So that it remains mostly undetectable.
;In my case with 0 chance of a ban on the server I play, I just don't like it using my keyboard
;any more often than that.
}
Else If (whoseturn = 1) AND (inbattle = 1) AND (btvc = 4)
{
WinActivate, Atlantica Online
Click, 1000, 562
btvc = 0
Flag = 0
Goto, BattleManager1
;If it's our turn, we're in battle, and btvc = 4 (specifying auto battles should be back to 20)
;Clicks autobattle icon, resets global variables, goes back to first label.
}
}
End::
ToolTip, Stopped!, 50, 20, 1
Suspend
Pause,,1
return
Delete::exitapp
;Junky pause + suspend and classic exitapp hotkey.
;If pause/suspend is important to you I suggest looking into ahk panic.
;It's very useful, and the person who made it is basically genius.
Code: Select all
MemoryOpenFromPID(PID, Privilege=0x1F0FFF)
{
HWND := DllCall("OpenProcess", "Uint", Privilege, "int", 0, "int", PID)
return HWND
}
MemoryOpenFromName(Name, Privilege=0x1F0FFF)
{
Process, Exist, %Name%
PID := ErrorLevel
Return MemoryOpenFromPID(PID, Privilege)
}
MemoryOpenFromTitle(title, privilege=0x1F0FFF)
{
WinGet, PID, PID, %title%
Return MemoryOpenFromPID(PID, Privilege)
}
MemoryClose(hwnd)
{
return DllCall("CloseHandle", "int", hwnd)
}
MemoryWrite(hwnd, address, writevalue, datatype="int", length=4, offset=0)
{
VarSetCapacity(finalvalue, length, 0)
NumPut(writevalue, finalvalue, 0, datatype)
return DllCall("WriteProcessMemory", "Uint", hwnd, "Uint", address+offset, "Uint", &finalvalue, "Uint", length, "Uint", 0)
}
MemoryRead(hwnd, address, datatype="int", length=4, offset=0)
{
VarSetCapacity(readvalue,length, 0)
DllCall("ReadProcessMemory","Uint",hwnd,"Uint",address+offset,"Str",readvalue,"Uint",length,"Uint *",0)
finalvalue := NumGet(readvalue,0,datatype)
return finalvalue
}
MemoryWritePointer(hwnd, base, writevalue, datatype="int", length=4, offsets=0, offset_1=0, offset_2=0, offset_3=0, offset_4=0, offset_5=0, offset_6=0, offset_7=0, offset_8=0, offset_9=0)
{
B_FormatInteger := A_FormatInteger
Loop, %offsets%
{
baseresult := MemoryRead(hwnd,base)
Offset := Offset_%A_Index%
SetFormat, integer, h
base := baseresult + Offset
SetFormat, integer, d
}
SetFormat, Integer, %B_FormatInteger%
return MemoryWrite(hwnd,address,writevalue,datatype,length)
}
MemoryReadPointer(hwnd, base, datatype="int", length=4, offsets=0, offset_1=0, offset_2=0, offset_3=0, offset_4=0, offset_5=0, offset_6=0, offset_7=0, offset_8=0, offset_9=0)
{
B_FormatInteger := A_FormatInteger
Loop, %offsets%
{
baseresult := MemoryRead(hwnd,base)
Offset := Offset_%A_Index%
SetFormat, integer, h
base := baseresult + Offset
SetFormat, integer, d
}
SetFormat, Integer, %B_FormatInteger%
return MemoryRead(hwnd,base,datatyp,length)
}
MemoryGetAddrPID(PID, DllName)
{
VarSetCapacity(me32, 548, 0)
NumPut(548, me32)
snapMod := DllCall("CreateToolhelp32Snapshot", "Uint", 0x00000008, "Uint", PID)
If (snapMod = -1)
Return 0
If (DllCall("Module32First", "Uint", snapMod, "Uint", &me32))
{
Loop
{
If (!DllCall("lstrcmpi", "Str", DllName, "UInt", &me32 + 32)) {
DllCall("CloseHandle", "UInt", snapMod)
Return NumGet(&me32 + 20)
}
}
Until !DllCall("Module32Next", "Uint", snapMod, "UInt", &me32)
}
DllCall("CloseHandle", "Uint", snapMod)
Return 0
}
MemoryGetAddrName(Name, DllName)
{
Process, Exist, %Name%
PID := ErrorLevel
Return MemoryGetAddrPID(PID, DllName)
}
MemoryGetAddrTitle(Title, DllName)
{
WinGet, PID, PID, %Title%
Return MemoryGetAddrPID(PID, DllName)
}
SetPrivilege(privilege = "SeDebugPrivilege")
{
success := DllCall("advapi32.dll\LookupPrivilegeValueA","uint",0,"str",privilege,"int64*",luid_SeDebugPrivilege)
if (success = 1) && (ErrorLevel = 0)
{
returnval = 0
}
else
{
returnval = %ErrorLevel%
}
return %returnval%
}
SuspendProcess(hwnd)
{
return DllCall("ntdll\NtSuspendProcess","uint",hwnd)
}
ResumeProcess(hwnd)
{
return DllCall("ntdll\NtResumeProcess","uint",hwnd)
}
Extra notes:
Depending on the server/client version auto battle max count could be 50 rather than 20.
If this is too confusing you need to study a little about memory and ahk. I can't hold anybodys hands with code like this because it depends so much on the user.
I don't expect this to be extremely useful or anything but I happened to notice all the ahk scripts for AO were absolutely worthless.
If anything I hope this provides direction for any individuals on similar missions.
Personally I did this out of boredom; in tandem with not coding very much at all this year.
Discord bots burned me out harshly, but ahk + assembly basics is a breath of old/familiar fresh air!
Lastly, you CAN obtain basically every address on AO. It would be quite possible to take something like this and go the extra miles to create "full automation".
Short list of things you would need to accomplish this:
- 1. Addresses for all mercs hp (Not needed if they won't die) How2: I didn't test this at all but I would assume it's just a double pointer like our hero.
2. Addresses for various mobs (Not needed if being aggro'd) How2: Can be found by how the client interprets mobs. I.E coordinate position on freeze, mob ID, etc.
3. Addresses for click icon changes (Not needed if 1&2 of the above aren't needed.) How2: No clue honestly, there is an autoit project you can reverse engineer to figure it out easily enough though.
4. Addresses for various npcs (Not needed if you're not trying to make something literally insane.) How2: Same as 3.
5. Market interface coordinates (Not needed unless you're specifically trying to make an automated farm.) How2: Literally just click, x, y is enough.
6. Addresses for jackpot. (I'm not sure this is possible via memory honestly, it's a screwy interface.) How2: If you can't figure out a method to at least checklist the pool:
6.2. PixelGetColor, easy.
7. Address for sit (Only necessary if you need to regen, refresh coordinate memory, and/or for getting zoom addresses) How2: Same as 3.
8. Address for zoom positions and amounts. (This wouldn't really be necessary at all but if you want it...) How2: Same as 3.
Despite my lack of explanation for 3, 4, 7. & 8; They are all perfectly possible and have been done before with theonlyone's autoit project.
If my interest in AO in particular persists I may convert their entire project. Not a big fan of autoit though so we'll see.