local VERSION = 3.5 -- Use traditional or direct control of login process local useLoginScript = true -- Don't overwrite value set earlier in login process if not current_client_lnk_in_use then current_client_lnk_in_use = "rom" -- Default client link name. It uses this link if no 'client' link is specified. end -- Load server link list. if fileExists(getExecutionPath() .. "/ServerLinkList.lua") then include("ServerLinkList.lua") end local commandMacro = 1 local resultMacro = 2 -- Send commands to the login window through macro system -- Based on RoMScript function loginScript(script) --- Get the real offset of the address local macro_address = memoryReadUInt(getProc(), addresses.staticbase_macro); --- Macro length is max 255, and after we add the return code, --- we are left with about 155 character limit. local dataPart = 0 -- The part of the data to get local raw = "" -- Combined raw data from 'R' repeat local text -- The command macro if dataPart == 0 then -- The initial command macro text = script else -- command macro to get the rest of the data from 'R' text = "SendMore" end -- Check to make sure length is within bounds local len = string.len(text); local texts = {} if( len > 254 ) then -- Break into parts table.insert(texts,"!"..text:sub(1,253)) local count = 1 while #text:sub(253 * count + 1) > 253 do table.insert(texts,"@"..text:sub(253 * count + 1, 253 * (count + 1))) count = count + 1 end table.insert(texts, "#"..text:sub(253 * count + 1)) end repeat repeat -- Write the command macro if #texts > 0 then text = texts[1] table.remove(texts, 1) end -- Send first part writeToMacro(commandMacro, text, "Command", 1) -- Write something on the first address, to see when its over written memoryWriteByte(getProc(), macro_address + addresses.macroSize *(resultMacro - 1) + addresses.macroBody_offset , 6); -- Execute it keyboardHold(key.VK_F9); --yrest(100) keyboardRelease(key.VK_F9); local tryagain = false -- A cheap version of a Mutex... wait till it is "released" -- Use high-res timers to find out when to time-out local startWaitTime = getTime(); while( memoryReadByte(getProc(), macro_address + addresses.macroSize *(resultMacro - 1) + addresses.macroBody_offset) == 6 ) do if( deltaTime(getTime(), startWaitTime) > 800 ) then if settings.options.DEBUGGING then printf("0x%X\n", addresses.editBoxHasFocus_address) end if memoryReadUInt(getProc(), addresses.editBoxHasFocus_address) == 0 then keyboardPress(settings.hotkeys.ESCAPE.key); rest(500) end tryagain = true break end; rest(5); end until tryagain == false until #texts == 0 --- Read the outcome from the result macro local rawPart = readMacro(resultMacro) raw = raw .. rawPart dataPart = dataPart + 1 until string.len(rawPart) < 255 readsz = ""; ret = {}; cnt = 0; for i = 1, string.len(raw), 1 do local byte = string.byte(raw, i); if( byte == 0 or byte == null) then -- Break on NULL terminator break; elseif( byte == 9 ) then -- Use TAB to seperate -- Implicit casting if( string.find(readsz, "^[%-%+]?%d+%.?%d+$") ) then readsz = tonumber(readsz); elseif( string.find(readsz, "^[%-%+]?%d+$") ) then readsz = tonumber(readsz); elseif( readsz == "true" ) then readsz = true; elseif( readsz == "false" ) then readsz = false; elseif( readsz:sub(1,1) ) == "{" then local func, err = loadstring("return "..readsz) if func then readsz = func() else print(err) end end table.insert(ret, readsz); cnt = cnt+1; readsz = ""; else readsz = readsz .. string.char(byte); end end local err = ret[1] if err == false then error("IGF:".."\\"..script.."\\:IGF "..ret[2],0) elseif err == true then table.remove(ret,1) end return unpack(ret); end local function reserveActiveWindow() local pid = getHwnd() local file, err, raw, apid, atime -- Wait for if other bot needs active window local printed = false repeat while true do file = io.open(getExecutionPath().."/../micromacro.pid", "r"); if file then raw = file:read() or "" file:close() apid, atime = string.match(raw,"pid:(.*) time:(.*)") apid = tonumber(apid) atime = tonumber(atime) if apid == nil or apid == pid or os.time()-tonumber(atime) > 60 then break else if not printed then printf("Waiting for another bot to release the active window.") printed = true else printf(".") end end else break end rest(3000) end if printed then printf("\n") end -- Reserve the active window file, err = io.open(getExecutionPath().."/../micromacro.pid", "w"); if( not file ) then error(err, 0); end file:write("pid:"..getHwnd().." time:"..os.time()) file:close() -- See if we still have the active window. Make sure 2 windows didn't write to the file at the same time. rest(1000) file = io.open(getExecutionPath().."/../micromacro.pid", "r"); if file then raw = file:read() or "" file:close() apid, atime = string.match(raw,"pid:(.*) time:(.*)") apid = tonumber(apid) end until apid == pid end local function releaseActiveWindow() file, err = io.open(getExecutionPath().."/../micromacro.pid", "w"); if( not file ) then error(err, 0); end file:write("") file:close() end local function startClient(client) print("Starting client "..client.." ...") -- Get shortcut if not string.match(client, "%.lnk$") then client = client .. ".lnk" end if not fileExists(getExecutionPath().."/"..client) then error("Game shortcut does not exist: ".. client) end client = string.lower(client) local path = getExecutionPath().."/"..client -- fix spaces path = string.gsub(path," ","\" \"") reserveActiveWindow() -- So you don't accidentally attach to another consoles client. -- Get a list of current clients local beforeWindows = findWindowList("*", "Radiant Arcana"); -- Start client local a,b,c = os.execute("START "..path.." NoCheckVersion") if not a then error("Trouble executing shortcut. Values returned were: "..(a or "nil")..", "..(b or "nil")..", "..(c or "nil")) end -- Wait for new client to start __WIN = nil repeat rest(1000) local nowWindows = findWindowList("*", "Radiant Arcana"); for i = 1, #nowWindows do local found = false for j = 1, #beforeWindows do if nowWindows[i] == beforeWindows[j] then found = true break end end if not found then -- This is the one we just started __WIN = nowWindows[i] break end end until __WIN releaseActiveWindow() current_client_lnk_in_use = client end local function gameState(win) local proc if win then proc = openProcess( findProcessByWindow(win) ) else proc = getProc() end if proc == nil then error("No valid process found.") end local atLogin = memoryReadUInt(proc, addresses.editBoxHasFocus_address) == 0 -- Only when not in game local atCharacterSelection = memoryReadUInt(proc, addresses.staticTableSize) == 1 local atLoadingScreen = memoryReadBytePtr(proc,addresses.loadingScreenPtr, addresses.loadingScreen_offset) ~= 0 local inGame = memoryReadInt(proc, addresses.isInGame) == 1 if win then closeProcess(proc) end if not atLoadingScreen then if inGame then return 3 elseif atCharacterSelection then return 2 elseif atLogin then return 1 end end return 0 -- Means between states end local function clickLoginButton(account) -- Login button array (ba) local ba = {} local a = 0 a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+38]={row=a,col=3} ba[a+49]={row=a,col=4} ba[a+59]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+38]={row=a,col=3} ba[a+49]={row=a,col=4} ba[a+59]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+38]={row=a,col=3} ba[a+49]={row=a,col=4} ba[a+59]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+38]={row=a,col=3} ba[a+49]={row=a,col=4} ba[a+59]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+38]={row=a,col=3} ba[a+49]={row=a,col=4} ba[a+59]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+30]={row=a,col=3} ba[a+51]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+30]={row=a,col=3} ba[a+40]={row=a,col=4} ba[a+51]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+30]={row=a,col=3} ba[a+40]={row=a,col=4} ba[a+51]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+30]={row=a,col=3} ba[a+40]={row=a,col=4} ba[a+51]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+30]={row=a,col=3} ba[a+40]={row=a,col=4} ba[a+51]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} a=a+1 ba[a]={row=a,col=1} ba[a+19]={row=a,col=2} ba[a+30]={row=a,col=3} ba[a+40]={row=a,col=4} ba[a+51]={row=a,col=5} ba[a+70]={row=a,col=6} ba[a+89]={row=a,col=7} local function buttonXPer(butnum) return -62 + 15.5*ba[butnum].col end local function buttonYPer(butnum) return -73 + 3.5*ba[butnum].row end showWindow(getWin(), sw.show) detach() rest(500) -- Get dimensions of window local x,y,w,h = windowRect(getWin()) -- Select button page --local page = 1 + math.floor(account/108) --RoMScript("CustomLogin_SetPage("..page..")") -- Calculate coords --local pacc = account - math.floor(account/108)*108 local pacc = account local xPer,yPer = buttonXPer(pacc), buttonYPer(pacc) moveX,moveY = x+w/2+xPer*h/100, y+h+yPer*h/100 -- Move and click mouse mouseSet(moveX,moveY) rest(100) mouseLClick() attach(getWin()) end local function clickCharacter(num) -- Character click array local charXPer = -18 local charYPer = { [1] = -26, [2] = -19, [3] = -12, [4] = -5, [5] = 2, [6] = 9, [7] = 16, [8] = 23, } showWindow(getWin(), sw.show) detach() rest(500) -- Get dimensions of window local x,y,w,h = windowRect(getWin()) -- Calculate coords local xPer,yPer = charXPer, charYPer[num] moveX,moveY = x+w+xPer*h/100, y+h/2+yPer*h/100 -- Move and click mouse mouseSet(moveX,moveY) rest(100) mouseLClick() attach(getWin()) end function login(character, account, client) client = client or current_client_lnk_in_use if not client then error("No client specified.") end if not account then error("No account specified.") end if not character then error("No character specified.") end startClient(client) rest(5000) __PROC = openProcess( findProcessByWindow(getWin()) ) -- Wait until it's at the login screen. ('atLogin' returns true early so I'm not sure this is necessary) repeat rest(1000) until gameState(__WIN) == 1 rest(3000) -- Click login button until it leave login if not useLoginScript then reserveActiveWindow() print("Clicking account "..account.." ...") repeat clickLoginButton(account) rest(2000) until gameState() ~= 1 releaseActiveWindow() else repeat rest(2000) until gameState() == 1 print("Clicking account "..account.." ...") attachKeyboard(__WIN) loginScript("CustomLogin_Login("..account..")") end -- Wait until at character selection repeat rest(2000) until gameState() == 2 rest(3000) -- Click character if not useLoginScript then reserveActiveWindow() print("Selecting character "..character.." ... ") clickCharacter(character) rest(500) keyboardPress(key.VK_ENTER) releaseActiveWindow() else print("Selecting character "..character.." ... ") attachKeyboard(__WIN) loginScript("CharacterSelect_Login("..character..")") loginScript("CharacterSelect_EnterWorld()") end -- Wait until in game print("Waiting until in-game ...") repeat rest(2000) until gameState() == 3 rest(3000) --== Things that need updating IdAddressTables = {} if CCamera then local cameraAddress = memoryReadUIntPtr(getProc(), addresses.staticbase_char, addresses.camPtr_offset) or 0; camera = CCamera(cameraAddress); end if CGuildbank then guildbank = CGuildbank() end -- Remember login details if player then player:update() -- Makes sure the macro is set up for RoMScripts. CurrentLoginAcc = RoMScript("LogID") CurrentLoginChar = RoMScript("CHARACTER_SELECT.selectedIndex") CurrentLoginServer = RoMScript ("GetCurrentRealm") end end function killClient() if __WIN and windowValid(__WIN) then local pid = findProcessByWindow(__WIN); os.execute("TASKKILL /PID " .. pid .. " /F") end local crashed, pid, killed repeat rest(500) if not killed then crashed, pid = isClientCrashed() if crashed then os.execute("TASKKILL /PID " .. pid .. " /F"); killed = true end end until not windowValid(__WIN) end