#include "Servo.h" #include #pragma comment(lib, "Psapi.lib") #include #include #include #include #include // trim from start (in place) static inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); } // trim from end (in place) static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); } // trim from both ends (in place) static inline void trim(std::string &s) { ltrim(s); rtrim(s); } // trim from start (copying) static inline std::string ltrimmed(std::string s) { ltrim(s); return s; } // trim from end (copying) static inline std::string rtrimmed(std::string s) { rtrim(s); return s; } // trim from both ends (copying) static inline std::string trimmed(std::string s) { trim(s); return s; } std::string Servo::StringSub(std::string instring, std::string subin, std::string subout) { if (instring.find(subin) != std::string::npos) return(instring.replace(instring.find(subin), subin.length(), subout)); else return instring; } void Servo::formatCommand(std::string* command) { std::stringstream id; id << m_AshitaCore->GetDataManager()->GetTarget()->GetTargetServerId(); auto p0 = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(0); auto p1 = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(1); auto p2 = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(2); auto p3 = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(3); auto p4 = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(4); auto p5 = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(5); *command = StringSub(*command, "[me]", p0 != nullptr ? p0 : ""); *command = StringSub(*command, "[p0]", p0 != nullptr ? p0 : ""); *command = StringSub(*command, "[p1]", p1 != nullptr ? p1 : ""); *command = StringSub(*command, "[p2]", p2 != nullptr ? p2 : ""); *command = StringSub(*command, "[p3]", p3 != nullptr ? p3 : ""); *command = StringSub(*command, "[p4]", p4 != nullptr ? p4 : ""); *command = StringSub(*command, "[p5]", p5 != nullptr ? p5 : ""); *command = StringSub(*command, "[t]", id.str()); } void Servo::SendCommand(std::string command) { while (!lock.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(20)); buffer.push_back(command); lock.unlock(); } void Servo::WalkTo(float x, float z) { if (zoning) return; auto myX = m_AshitaCore->GetDataManager()->GetEntity()->GetLocalX(m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0)); auto myZ = m_AshitaCore->GetDataManager()->GetEntity()->GetLocalZ(m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0)); auto myStatus = m_AshitaCore->GetDataManager()->GetEntity()->GetStatus(m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0)); float dist = sqrt(pow((myX - x), 2.0f) + pow((myZ - z), 2.0f)); if (dist > 1.5f && (myStatus == 0 || myStatus == 5)) { if (StructPointer->AutoRun != 1) StructPointer->AutoRun = 1; StructPointer->DirectionX = x - myX; StructPointer->DirectionZ = z - myZ; StructPointer->DirectionY = 0; } else StructPointer->AutoRun = 0; } void Servo::RecvCommand(std::string* command) { command->erase(); if (lock.try_lock()) { if (!buffer.empty()) { command->swap(buffer.front()); buffer.pop_front(); } lock.unlock(); } } void Servo::SetState(ServoMode::Mode mode) { ServoMode::Mode prevState = state; state = ServoMode::Mode::Standby; follow = false; zoning = false; connected = false; if (prevState != ServoMode::Mode::Standby) { conn.join(); pos.join(); } while (!buffer.empty()) buffer.pop_front(); state = mode; } void Servo::Sync(std::string host) { me = m_AshitaCore->GetDataManager()->GetParty()->GetMemberName(0); log << Chat::Format::RoyalBlue << "[Servo] Servo started in client mode, connected to: "; while (!lservAddr.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (host != "") { servAddr = host; log << servAddr; } else { servAddr = ""; log << "localhost"; } lservAddr.unlock(); SetState(ServoMode::Mode::Client); conn.swap(std::thread(&Servo::Client, this)); pos.swap(std::thread(&Servo::Follow, this)); log << Chat::Control::flush; } bool Servo::Initialize(IAshitaCore* ashitaCore, ILogManager* logManager, uint32_t dwPluginId) { this->m_AshitaCore = ashitaCore; this->m_PluginId = dwPluginId; // Obtain the base module information.. MODULEINFO mod = { 0 }; if (!::GetModuleInformation(::GetCurrentProcess(), ::GetModuleHandle("FFXiMain.dll"), &mod, sizeof(MODULEINFO))) return false; auto Pointer = (unsigned char*)Ashita::Memory::FindPattern((uintptr_t)mod.lpBaseOfDll, mod.SizeOfImage, "8BCFE8????????8B0D????????E8????????8BE885??750CB9", 0, 0); StructPointer = nullptr; if (Pointer != nullptr) { Pointer += 25; StructPointer = (auto_follow*)(*((DWORD*)Pointer)); } else ashitaCore->GetChatManager()->Write("[Servo] Failed to locate required signature for autofollow!"); if (StructPointer) { StructPointer->DirectionX; StructPointer->DirectionZ; StructPointer->AutoRun; } cmdParse = new CommandParser; state = ServoMode::Mode::Standby; lservAddr.lock(); servAddr = ""; lservAddr.unlock(); me = ""; follow = false; zoning = false; zContext = zmq::context_t(1); conn = std::thread(); pos = std::thread(); log.SetCore(m_AshitaCore); log << Chat::Mode::Echo; return 1; } void Servo::Serv() { zmq::socket_t publisher(zContext, ZMQ_PUB); std::string hostmask = "tcp://"; while (!lservAddr.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (!servAddr.empty()) hostmask.append(servAddr); else hostmask.append("127.0.0.1"); lservAddr.unlock(); hostmask.append(":56556"); try { publisher.bind(hostmask.c_str()); } catch (...) { this->m_AshitaCore->GetChatManager()->Write("[Servo] Failed to bind to socket."); publisher.close(); return; } connected = true; while (connected) { while (!lock.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (!buffer.empty()) { auto it = buffer.begin(); zmq::message_t message(it->length() + 1); strcpy_s((char*)message.data(), (size_t)(it->length() + 1), it->c_str()); buffer.pop_front(); lock.unlock(); publisher.send(message, ZMQ_DONTWAIT); } else lock.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(20)); } publisher.close(); } void Servo::ServPos() { zmq::socket_t publisher(zContext, ZMQ_PUB); std::string hostmask = "tcp://"; while (!lservAddr.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (!servAddr.empty()) hostmask.append(servAddr); else hostmask.append("127.0.0.1"); lservAddr.unlock(); hostmask.append(":56557"); try { publisher.bind(hostmask.c_str()); } catch (...) { this->m_AshitaCore->GetChatManager()->Write("[Servo] Failed to bind to socket."); publisher.close(); return; } if (!connected) std::this_thread::sleep_for(std::chrono::milliseconds(100)); while (connected) { if (!zoning && m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0)) { auto myX = m_AshitaCore->GetDataManager()->GetEntity()->GetLocalX(m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0)); auto myZ = m_AshitaCore->GetDataManager()->GetEntity()->GetLocalZ(m_AshitaCore->GetDataManager()->GetParty()->GetMemberTargetIndex(0)); std::stringstream ss; ss << m_AshitaCore->GetDataManager()->GetParty()->GetMemberZone(0) << " " << myX << " " << myZ; zmq::message_t message(strlen(ss.str().c_str()) + 1); strcpy_s((char*)message.data(), strlen(ss.str().c_str()) + 1, ss.str().c_str()); publisher.send(message, ZMQ_DONTWAIT); } std::this_thread::sleep_for(std::chrono::milliseconds(200)); } publisher.close(); } void Servo::Client() { std::string host = "tcp://"; while (!lservAddr.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (!servAddr.empty()) host.append(servAddr); else host.append("localhost"); lservAddr.unlock(); host.append(":56556"); zmq::socket_t subscriber(zContext, ZMQ_SUB); subscriber.connect(host.c_str()); subscriber.setsockopt(ZMQ_SUBSCRIBE, me.c_str(), me.size()); for (auto it = groups.begin(); it != groups.end(); ++it) subscriber.setsockopt(ZMQ_SUBSCRIBE, it->c_str(), it->size()); subscriber.setsockopt(ZMQ_SUBSCRIBE, "::", 2); subscriber.setsockopt(ZMQ_SUBSCRIBE, ":SERVOFUNC:", 11); connected = true; while (connected) { zmq::message_t update; while (subscriber.recv(&update, ZMQ_DONTWAIT) == false) { if (connected) std::this_thread::sleep_for(std::chrono::milliseconds(20)); else break; } std::string s(static_cast(update.data())); if (!s.empty()) { while (!lock.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); buffer.push_back(s); lock.unlock(); } std::this_thread::sleep_for(std::chrono::milliseconds(20)); } subscriber.close(); } void Servo::Follow() { std::string host = "tcp://"; while (!lservAddr.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (!servAddr.empty()) host.append(servAddr); else host.append("localhost"); lservAddr.unlock(); host.append(":56557"); zmq::socket_t subscriber(zContext, ZMQ_SUB); subscriber.connect(host.c_str()); subscriber.setsockopt(ZMQ_SUBSCRIBE, "", 0); if (!connected) std::this_thread::sleep_for(std::chrono::milliseconds(100)); while (connected) { zmq::message_t update; while (follow) { update = zmq::message_t(); while (subscriber.recv(&update, ZMQ_DONTWAIT) == false) { if (connected) std::this_thread::sleep_for(std::chrono::milliseconds(50)); else break; } std::stringstream ss(static_cast(update.data())); if (!ss.str().empty()) { float WARPX, WARPY; short zoneid; ss >> zoneid >> WARPX >> WARPY; if (zoneid == m_AshitaCore->GetDataManager()->GetParty()->GetMemberZone(0)) WalkTo(WARPX, WARPY); else StructPointer->AutoRun = 0; // If no longer sharing the same zone, stop running (will trigger after the server char has arrived in the new area, so there is some delay) } std::this_thread::sleep_for(std::chrono::milliseconds(50)); } while (subscriber.recv(&update, ZMQ_DONTWAIT) == false) { if (connected & !follow) std::this_thread::sleep_for(std::chrono::milliseconds(50)); else break; } update.data(); // Discard data when not following. std::this_thread::sleep_for(std::chrono::milliseconds(500)); } subscriber.close(); } void Servo::Release() { SetState(ServoMode::Mode::Standby); zContext.close(); delete cmdParse; } plugininfo_t Servo::GetPluginInfo(void) { return (*g_PluginInfo); } bool Servo::HandleCommand(const char* szCommand, int iType) { if (iType == 0) { return false; } char szClean[1024]; if (iType != -2) m_AshitaCore->GetChatManager()->ParseAutoTranslate(szCommand, szClean, 1024, false); else strncpy_s(szClean, szCommand, 1024); std::string arg; cmdParse->InputCommand(szClean); cmdParse->GetFirstCommand(&arg); if (arg == "/servo") { cmdParse->GetNextCommand(&arg); if (arg == "on") { log << Chat::Format::RoyalBlue << "[Servo] Servo started in server mode, with a hostmask of: "; while (!lservAddr.try_lock()) std::this_thread::sleep_for(std::chrono::milliseconds(50)); if (cmdParse->GetNextCommand(&arg)) { servAddr = arg; log << servAddr; } else { servAddr = ""; log << "localhost"; } lservAddr.unlock(); SetState(ServoMode::Mode::Server); conn.swap(std::thread(&Servo::Serv, this)); pos.swap(std::thread(&Servo::ServPos, this)); log << Chat::Control::flush; } else if (arg == "sync") { if (!cmdParse->GetNextCommand(&arg)) arg = ""; Sync(arg); } else if (arg == "off") { SetState(ServoMode::Mode::Standby); log << Chat::Format::RoyalBlue << "[Servo] Servo set to standy!" << Chat::Control::flush; } else if (arg == "command" || arg == "send") { if (cmdParse->GetRemainingCommands(&arg)) { arg = ":: " + arg; formatCommand(&arg); SendCommand(arg); } } else if (arg == "sendto") { if (cmdParse->GetRemainingCommands(&arg)) { formatCommand(&arg); SendCommand(arg); } } else if (arg == "followme") { if (state.load() == ServoMode::Mode::Server) { if (!cmdParse->GetNextCommand(&arg)) { if (arg == "on") SendCommand(":SERVOFUNC: FOLLOWON"); else if (arg == "off") SendCommand(":SERVOFUNC: FOLLOWOFF"); } else SendCommand(":SERVOFUNC: FOLLOW"); } } else if (arg == "follow") { if (state.load() == ServoMode::Mode::Client) { if (!cmdParse->GetNextCommand(&arg)) { if (arg == "on") follow = true; else if (arg == "off") follow = false; } else follow = follow ? false : true; } } else if (arg == "addgroup") { std::string group; cmdParse->GetNextCommand(&group); groups.push_back(group); if (state == ServoMode::Mode::Client) Sync(servAddr); } else if (arg == "delgroup") { bool changed = false; std::string group; cmdParse->GetNextCommand(&group); for (auto it = groups.begin(); it != groups.end();) { if (*it == group) { changed = true; it = groups.erase(it); } else ++it; } if (state == ServoMode::Mode::Client && changed) Sync(servAddr); } return true; } return false; } bool Servo::Direct3DInitialize(IDirect3DDevice8* mDevice) { return true; } void Servo::Direct3DRender() { if (state.load() == ServoMode::Mode::Client) { std::string commandtype; std::string incommand; RecvCommand(&incommand); if (!incommand.empty()) { commandtype = incommand.substr(0, incommand.find_first_of(" ")); incommand = incommand.substr(incommand.find_first_of(" ") + 1); if (commandtype != ":SERVOFUNC:") { auto cmd = incommand; trim(cmd); m_AshitaCore->GetChatManager()->QueueCommand(cmd.c_str(), (int32_t)Ashita::CommandInputType::Menu); } else { if (incommand == "FOLLOWON") follow = true; else if (incommand == "FOLLOWOFF") follow = false; else if (incommand == "FOLLOW") follow = follow ? false : true; } } } } bool Servo::HandleOutgoingPacket(uint16_t id, uint32_t size, void* data, void* modified, bool blocked) { if (id == 0x5E) zoning = true; return false; } bool Servo::HandleIncomingPacket(uint16_t id, uint32_t size, void* data, void* modified, bool blocked) { if (id == 0x0A) zoning = false; return false; } __declspec(dllexport) double __stdcall GetInterfaceVersion(void) { return ASHITA_INTERFACE_VERSION; } __declspec(dllexport) void __stdcall CreatePluginInfo(plugininfo_t* lpBuffer) { g_PluginInfo = lpBuffer; strcpy_s(g_PluginInfo->Name, sizeof(g_PluginInfo->Name), "Servo"); strcpy_s(g_PluginInfo->Author, sizeof(g_PluginInfo->Author), "bluekirby0 & atom0s"); g_PluginInfo->InterfaceVersion = ASHITA_INTERFACE_VERSION; g_PluginInfo->PluginVersion = 3.02f; g_PluginInfo->Priority = 0; } __declspec(dllexport) IPlugin* __stdcall CreatePlugin(void) { return (IPlugin*)new Servo(); }