local VERSION = 3.5 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 mmextFound, mmext = pcall(require, "mmext") -- Did we load the client loader? if not mmextFound then --error("Please add mmext.dll to your Micromacro installation!") cprintf(cli.red, "Please add mmext.dll to your Micromacro installation!\n") 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 gameState(win) local proc local state = 0 if win then proc = openProcess( findProcessByWindow(win) ) else proc = getProc() end if proc == nil then error("No valid process found.") end local atInitialized = memoryReadUInt(getProc(), addresses.staticbase_char) ~= 0 -- When g_pGameMain is not initialized local atFPS = atInitialized and (memoryReadFloatPtr(getProc(), addresses.staticbase_char, 0x8c) > 0) -- Measured FPS (m_fps) local atLogin = memoryReadUInt(proc, addresses.editBoxHasFocus_address) == 0 -- Only when not in game local atCharacterSelection = memoryReadUInt(proc, addresses.staticTableSize) == 1 -- Character selection display local atLoadingScreen = memoryReadBytePtr(proc,addresses.loadingScreenPtr, addresses.loadingScreen_offset) ~= 0 -- Loading screen local inGame = memoryReadInt(proc, addresses.isInGame) == 1 -- In game, ready to go if win then closeProcess(proc) end if not atLoadingScreen then if inGame then state = 4 elseif atCharacterSelection then state = 3 elseif atLogin and atFPS then -- Now, we cant be at login unless fps is running!!! state = 2 elseif atFPS then state = 1 end end return state end local function waitState(state) repeat rest(1000) until gameState() >= state end local function cleanupLink(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," ","\" \"") return path end --[[ Main functions to control client --]] function startClient(path, args, dir) if not path then error("No executable specified.") end print("Starting client "..path.." ...") -- Start the client if mmextFound then -- Convert Lua to Windows path local path = string.gsub(path, "/", "\\") pid, __WIN = mmext.Client(path, args, dir) __PROC = openProcess(pid) else 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() rest(5000) __PROC = openProcess( findProcessByWindow(getWin()) ) end -- Wait until frames are running waitState(2) end function loginAccount(account) if gameState() == 3 or gameState() == 4 then return -- if fastLoginAutoLogin or\and fastLoginAutoEnter elseif gameState() ~= 2 then error("Not at login screen!") end -- Attach to process attachKeyboard(__WIN) -- Do the login print("Selecting account "..account.." ...") loginScript("CustomLogin_AccountLogin("..account..")") -- Wait until at character selection waitState(3) end function selectCharacter(character) if gameState() == 4 then return -- if fastLoginAutoEnter elseif gameState() ~= 3 then error("Not at character selection screen!") end print("Selecting character "..character.." ... ") attachKeyboard(__WIN) loginScript("CharacterSelect_Login("..character..")") loginScript("CharacterSelect_EnterWorld()") -- Wait until in game print("Waiting until in-game ...") waitState(4) 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 -- Login the account/character startClient(cleanupLink(client)) loginAccount(account) selectCharacter(character) --== 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 if mmextFound then mmext.KillClient(pid) killed = true else os.execute("TASKKILL /PID " .. pid .. " /F"); killed = true end end end until not windowValid(__WIN) end