Active Member Koray 2001 Posted October 2, 2015 Active Member Share Posted October 2, 2015 M2 Download Center This is the hidden content, please Sign In or Sign Up ( Internal ) V4 (ZED) Spoiler bool COXEventManager::CheckIpAddress(LPCHARACTER ch) { DWORD restricted_ox_flag = quest::CQuestManager::instance().GetEventFlag("restricted_ox"); for (itertype(m_map_attender) it = m_map_attender.begin(); it != m_map_attender.end(); ++it) { LPCHARACTER tch = CHARACTER_MANAGER::Instance().FindByPID(it->second); if (!tch || !tch->GetDesc()) continue; if (restricted_ox_flag > 0 && !strcmp(ch->GetDesc()->GetHostName(), tch->GetDesc()->GetHostName()) && ch->GetMapIndex() == tch->GetMapIndex()) { LogManager::Instance().HackLog("MULTI_IP_OX", ch); ch->GoHome(); return false; } } return true; } Commands: /e restricted_ox 0 -> disable /e restricted_ox 1 -> enable V3 (MALI61) (MAX 2) Spoiler Open OXEvent.h, find GetAttenderCount() and add those things under that line. // Ox-event IP-Checker public: bool CheckIpAddress(LPCHARACTER ch); void RemoveFromAttenderList(DWORD dwPID); Open OXEvent.cpp, add those lines after COxEventManager::LogWinner() bool COXEventManager::CheckIpAddress(LPCHARACTER ch) { int IPCount = std::count_if(m_map_attender.begin(), m_map_attender.end(), [ch](const decltype(*m_map_attender.begin())& v) { LPCHARACTER tch = CHARACTER_MANAGER::Instance().FindByPID(v.second); if (!tch || !tch->GetDesc()) return false; return !strcmp(ch->GetDesc()->GetHostName(), tch->GetDesc()->GetHostName()); }); const int MaxPlayer = 2; return !(IPCount >= MaxPlayer); } bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { if (!CheckIpAddress(pkChar)) return false; // // Will send you to your empire, if you are not GM DWORD pid = pkChar->GetPlayerID(); m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) COXEventManager::Instance().RemoveFromAttenderList(GetPlayerID()); V2 (KEN) Spoiler Open OXEvent.h, find GetAttenderCount() and add those things under that line. // Ox-event IP-Checker public: bool CheckIpAddress(LPCHARACTER ch); void RemoveFromAttenderList(DWORD dwPID); Open OXEvent.cpp, add those lines after COxEventManager::LogWinner() bool COXEventManager::CheckIpAddress(LPCHARACTER ch) { for (itertype(m_map_attender) it = m_map_attender.begin(); it != m_map_attender.end(); ++it) { LPCHARACTER tch = CHARACTER_MANAGER::Instance().FindByPID(it->second); if (!tch || !tch->GetDesc()) continue; if (!strcmp(ch->GetDesc()->GetHostName(), tch->GetDesc()->GetHostName()) && ch->GetMapIndex() == tch->GetMapIndex()) { LogManager::Instance().HackLog("MULTI_IP_OX", ch); ch->GoHome(); return false; } } return true; } void COXEventManager::RemoveFromAttenderList(DWORD dwPID) { m_map_attender.erase(dwPID); } bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { DWORD pid = pkChar->GetPlayerID(); if (CheckIpAddress(pkChar)) { m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } return false; } char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) COXEventManager::Instance().RemoveFromAttenderList(GetPlayerID()); V1 (KORAY) Spoiler *char.cpp Spoiler Find: #include "buff_on_attributes.h" Add it under: #include "OXEvent.h" Find: MessengerManager::instance().Logout(GetName()); Add it under: if (GetMapIndex() == OXEVENT_MAP_INDEX) COXEventManager::instance().RemoveFromIpList(GetDesc()->GetHostName()); *OXEvent.h Spoiler Find: bool EnterAttender(LPCHARACTER pChar); Add it under: std::set<std::string> m_list_iplist; Find: bool LoadQuizScript(const char* szFileName); Add it under: void CheckIpAdr(DWORD pidm); void RemoveFromIpList(const char* gelenip); *OXEvent.cpp Spoiler Search: COXEventManager::Initialize() Find: m_vec_quiz.clear(); Add it under: m_list_iplist.clear(); Search: COXEventManager::Destroy() Find: m_vec_quiz.clear(); Add it under: m_list_iplist.clear(); Find: bool COXEventManager::EnterAttender(LPCHARACTER pkChar) Add it upper: void COXEventManager::RemoveFromIpList(const char* gelenip){ std::string silinecekip = gelenip; m_list_iplist.erase(silinecekip); } void COXEventManager::CheckIpAdr(DWORD pidm){ LPCHARACTER pkMyChar = CHARACTER_MANAGER::instance().FindByPID(pidm); char pkChrIP[250]; snprintf(pkChrIP, sizeof(pkChrIP), "%s", pkMyChar->GetDesc()->GetHostName()); for (itertype(m_list_iplist) it = m_list_iplist.begin(); it != m_list_iplist.end(); ++it) { const std::string& loopdaki_ip = *it; if (loopdaki_ip.empty()) return; char listdekiIp[250]; snprintf(listdekiIp, sizeof(listdekiIp), "%s", loopdaki_ip.c_str()); if (!strcmp(listdekiIp, pkChrIP)) { pkMyChar->GetDesc()->DelayedDisconnect(5); pkMyChar->ChatPacket(CHAT_TYPE_INFO, "Multi IP detected"); } } } Find: m_map_attender.insert(std::make_pair(pid, pid)); Add it under: CheckIpAdr(pid); m_list_iplist.insert(pkChar->GetDesc()->GetHostName()); 30 11 1 36 Link to comment Share on other sites More sharing options...
terrorr 15 Posted October 2, 2015 Share Posted October 2, 2015 OXEvent.cpp:138: error: invalid conversion from 'const char*' to 'int'OXEvent.cpp:138: error: initializing argument 1 of 'bool DESC::DelayedDisconnect(int)' LoL :] 1 Link to comment Share on other sites More sharing options...
Premium V0iĐ 114 Posted October 3, 2015 Premium Share Posted October 3, 2015 Try! Rewrite this: pkMyChar->GetDesc()->DelayedDisconnect("5"); To: pkMyChar->GetDesc()->DelayedDisconnect(5); 3 Link to comment Share on other sites More sharing options...
Active Member Koray 2001 Posted October 3, 2015 Author Active Member Share Posted October 3, 2015 Thanks updated first post Link to comment Share on other sites More sharing options...
miguelmig 13 Posted October 4, 2015 Share Posted October 4, 2015 What is this fixing? Multiple accounts on OX? Link to comment Share on other sites More sharing options...
Active Member Koray 2001 Posted October 4, 2015 Author Active Member Share Posted October 4, 2015 yes Link to comment Share on other sites More sharing options...
adisala 2 Posted November 3, 2015 Share Posted November 3, 2015 does not work for me, I use mainline_released Link to comment Share on other sites More sharing options...
Active Member ReFresh 2340 Posted November 4, 2015 Active Member Share Posted November 4, 2015 Thanks Working good. I'll be always helpful! Link to comment Share on other sites More sharing options...
Gradoux 1 Posted November 11, 2015 Share Posted November 11, 2015 Nice share, thanks Link to comment Share on other sites More sharing options...
wezt 95 Posted November 29, 2015 Share Posted November 29, 2015 Hi, If player/person which was disconnected 1st time, login again the system won't disconnect him because of: if (GetMapIndex() == OXEVENT_MAP_INDEX) COXEventManager::instance().RemoveFromIpList(GetDesc()->GetHostName()); //with this part you removing ip address from check-list I suggest to add few changes in this system. 1) Delete part from char.cpp 2) Add in bool COXEventManager::CloseEvent() this m_list_iplist.clear(); 3) Replace disconnects with warp_to_village and add option to enable/disable ip-check with event-flag if (!strcmp(listdekiIp, pkChrIP) && quest::CQuestManager::instance().GetEventFlag("oxevent_chk_ip") == 1) { //pkMyChar->GetDesc()->DelayedDisconnect(5); pkMyChar->ChatPacket(CHAT_TYPE_INFO, "Multi IP detected!"); m_map_attender.erase(pidm); BYTE bEmpire = pkMyChar->GetEmpire(); pkMyChar->WarpSet( g_start_position[bEmpire][0], g_start_position[bEmpire][1] ); } Regards 1 Link to comment Share on other sites More sharing options...
Eigenartig 7 Posted December 15, 2015 Share Posted December 15, 2015 thank you Link to comment Share on other sites More sharing options...
BackPlayer 55 Posted December 30, 2015 Share Posted December 30, 2015 On 29/11/2015 at 10:09 PM, wezt said: //pkMyChar->GetDesc()->DelayedDisconnect(5); pkMyChar->ChatPacket(CHAT_TYPE_INFO, "Multi IP detected!"); m_map_attender.erase(pidm); BYTE bEmpire = pkMyChar->GetEmpire(); pkMyChar->WarpSet( g_start_position[bEmpire][0], g_start_position[bEmpire][1] ); } your way have bug too, if players disconnect them from both accounts you can't connect it again with anyone character while the ox_event is enable Link to comment Share on other sites More sharing options...
loyein 18 Posted January 3, 2016 Share Posted January 3, 2016 Nice, thanks. Link to comment Share on other sites More sharing options...
wezt 95 Posted January 4, 2016 Share Posted January 4, 2016 On 12/30/2015 at 2:59 PM, BackPlayer said: your way have bug too, if players disconnect them from both accounts you can't connect it again with anyone character while the ox_event is enable Hi, yes you're right. I've made some changes in code, now this bug should be fixed, also it doesn't use m_list_iplist anymore: Spoiler In char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) { COXEventManager::instance().RemoveFromList(GetPlayerID()); } In OXEvent.h Change "void RemoveFromList(const char* gelenip);" to "void RemoveFromList(DWORD pidm);" And "void CheckIpAdr(DWORD pidm);" to "bool CheckIpAdr(DWORD pidm);" In OXEvent.cpp Replace: void COXEventManager::RemoveFromIpList(const char* gelenip){ std::string silinecekip = gelenip; m_list_iplist.erase(silinecekip); } With this: void COXEventManager::RemoveFromList(DWORD pidm) { m_map_attender.erase(pidm); } Then change "bool COXEventManager::EnterAttender(LPCHARACTER pkChar)" to: bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { DWORD pid = pkChar->GetPlayerID(); if(CheckIpAdr(pid)) { m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } return false; } And "bool COXEventManager::CheckIpAdr(DWORD pidm)" to: bool COXEventManager::CheckIpAdr(DWORD pidm) { LPCHARACTER pkMyChar = CHARACTER_MANAGER::instance().FindByPID(pidm); char pkMyChrIP[250]; snprintf(pkMyChrIP, sizeof(pkMyChrIP), "%s", pkMyChar->GetDesc()->GetHostName()); itertype(m_map_attender) iter = m_map_attender.begin(); for (; iter != m_map_attender.end(); ++iter) { LPCHARACTER pkChar = CHARACTER_MANAGER::instance().FindByPID(iter->second); if(pkChar!=NULL) { char pkChrIP[250]; snprintf(pkChrIP, sizeof(pkChrIP), "%s", pkChar->GetDesc()->GetHostName()); if(!strcmp(pkMyChrIP, pkChrIP) && quest::CQuestManager::instance().GetEventFlag("oxevent_chk_ip") == 1 && pkChar->GetMapIndex() == OXEVENT_MAP_INDEX && pkMyChar->GetMapIndex() == OXEVENT_MAP_INDEX) { pkMyChar->ChatPacket(CHAT_TYPE_INFO, "Multi IP detected!"); sys_err("COXEventManager Multi IP detected %s",pkMyChar->GetName()); BYTE bEmpire = pkMyChar->GetEmpire(); pkMyChar->WarpSet( g_start_position[bEmpire][0], g_start_position[bEmpire][1] ); return false; } } } return true; } Regards 1 Link to comment Share on other sites More sharing options...
BackPlayer 55 Posted January 4, 2016 Share Posted January 4, 2016 (edited) 37 minutes ago, wezt said: Hi, yes you're right. I've made some changes in code, now this bug should be fixed, also it doesn't use m_list_iplist anymore: Hide contents In char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) { COXEventManager::instance().RemoveFromList(GetPlayerID()); } In OXEvent.h Change "void RemoveFromList(const char* gelenip);" to "void RemoveFromList(DWORD pidm);" In OXEvent.cpp Replace: void COXEventManager::RemoveFromIpList(const char* gelenip){ std::string silinecekip = gelenip; m_list_iplist.erase(silinecekip); } With this: void COXEventManager::RemoveFromList(DWORD pidm) { m_map_attender.erase(pidm); } Then change "bool COXEventManager::EnterAttender(LPCHARACTER pkChar)" to: bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { DWORD pid = pkChar->GetPlayerID(); if(CheckIpAdr(pid)) { m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } return false; } And "bool COXEventManager::CheckIpAdr(DWORD pidm)" to: bool COXEventManager::CheckIpAdr(DWORD pidm) { LPCHARACTER pkMyChar = CHARACTER_MANAGER::instance().FindByPID(pidm); char pkMyChrIP[250]; snprintf(pkMyChrIP, sizeof(pkMyChrIP), "%s", pkMyChar->GetDesc()->GetHostName()); itertype(m_map_attender) iter = m_map_attender.begin(); for (; iter != m_map_attender.end(); ++iter) { LPCHARACTER pkChar = CHARACTER_MANAGER::instance().FindByPID(iter->second); if(pkChar!=NULL) { char pkChrIP[250]; snprintf(pkChrIP, sizeof(pkChrIP), "%s", pkChar->GetDesc()->GetHostName()); if(!strcmp(pkMyChrIP, pkChrIP) && quest::CQuestManager::instance().GetEventFlag("oxevent_chk_ip") == 1 && pkChar->GetMapIndex() == OXEVENT_MAP_INDEX && pkMyChar->GetMapIndex() == OXEVENT_MAP_INDEX) { pkMyChar->ChatPacket(CHAT_TYPE_INFO, "Multi IP detected!"); BYTE bEmpire = pkMyChar->GetEmpire(); pkMyChar->WarpSet( g_start_position[bEmpire][0], g_start_position[bEmpire][1] ); return false; } } } return true; } Regards I will try it , i hav a question this system block hardware IP or Internet IP? cuz my english is shi.t i mean when 2 people go in the Internet cafe they can join on Ox Event? Compile problem: https://metin2.download/picture/9FXDi61o93zQTWI586yF2D71mnv0z0tJ/.png Edited September 1, 2022 by Metin2 Dev Core X - External 2 Internal 1 Link to comment Share on other sites More sharing options...
wezt 95 Posted January 4, 2016 Share Posted January 4, 2016 Sry, forgot to add all changes for OXEvent.h. Change function type, "void CheckIpAdr(DWORD pidm);" to "bool CheckIpAdr(DWORD pidm);" 25 minutes ago, BackPlayer said: when 2 people go in the interndet cafe they can join on Ox Event Nope, this system checks "Internet IP". 1 Link to comment Share on other sites More sharing options...
BackPlayer 55 Posted January 4, 2016 Share Posted January 4, 2016 Thanks you for your fix,works perfect you could make new topic with your fix! Link to comment Share on other sites More sharing options...
Ken 904 Posted January 4, 2016 Share Posted January 4, 2016 On 1/4/2016 at 11:39 AM, wezt said: Hi, yes you're right. I've made some changes in code, now this bug should be fixed, also it doesn't use m_list_iplist anymore: Hide contents In char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) { COXEventManager::instance().RemoveFromList(GetPlayerID()); } In OXEvent.h Change "void RemoveFromList(const char* gelenip);" to "void RemoveFromList(DWORD pidm);" And "void CheckIpAdr(DWORD pidm);" to "bool CheckIpAdr(DWORD pidm);" In OXEvent.cpp Replace: void COXEventManager::RemoveFromIpList(const char* gelenip){ std::string silinecekip = gelenip; m_list_iplist.erase(silinecekip); } With this: void COXEventManager::RemoveFromList(DWORD pidm) { m_map_attender.erase(pidm); } Then change "bool COXEventManager::EnterAttender(LPCHARACTER pkChar)" to: bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { DWORD pid = pkChar->GetPlayerID(); if(CheckIpAdr(pid)) { m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } return false; } And "bool COXEventManager::CheckIpAdr(DWORD pidm)" to: bool COXEventManager::CheckIpAdr(DWORD pidm) { LPCHARACTER pkMyChar = CHARACTER_MANAGER::instance().FindByPID(pidm); char pkMyChrIP[250]; snprintf(pkMyChrIP, sizeof(pkMyChrIP), "%s", pkMyChar->GetDesc()->GetHostName()); itertype(m_map_attender) iter = m_map_attender.begin(); for (; iter != m_map_attender.end(); ++iter) { LPCHARACTER pkChar = CHARACTER_MANAGER::instance().FindByPID(iter->second); if(pkChar!=NULL) { char pkChrIP[250]; snprintf(pkChrIP, sizeof(pkChrIP), "%s", pkChar->GetDesc()->GetHostName()); if(!strcmp(pkMyChrIP, pkChrIP) && quest::CQuestManager::instance().GetEventFlag("oxevent_chk_ip") == 1 && pkChar->GetMapIndex() == OXEVENT_MAP_INDEX && pkMyChar->GetMapIndex() == OXEVENT_MAP_INDEX) { pkMyChar->ChatPacket(CHAT_TYPE_INFO, "Multi IP detected!"); sys_err("COXEventManager Multi IP detected %s",pkMyChar->GetName()); BYTE bEmpire = pkMyChar->GetEmpire(); pkMyChar->WarpSet( g_start_position[bEmpire][0], g_start_position[bEmpire][1] ); return false; } } } return true; } Regards You shouldn't follow the long way and Koray too. Open OXEvent.h, find GetAttenderCount() and add those things under that line. // Ox-event IP-Checker public: bool CheckIpAddress(LPCHARACTER ch); void RemoveFromAttenderList(DWORD dwPID); Open OXEvent.cpp, add those lines after COxEventManager::LogWinner() bool COXEventManager::CheckIpAddress(LPCHARACTER ch) { for (itertype(m_map_attender) it = m_map_attender.begin(); it != m_map_attender.end(); ++it) { LPCHARACTER tch = CHARACTER_MANAGER::Instance().FindByPID(it->second); if (!tch || !tch->GetDesc()) continue; if (!strcmp(ch->GetDesc()->GetHostName(), tch->GetDesc()->GetHostName()) && ch->GetMapIndex() == tch->GetMapIndex()) { LogManager::Instance().HackLog("MULTI_IP_OX", ch); ch->GoHome(); return false; } } return true; } void COXEventManager::RemoveFromAttenderList(DWORD dwPID) { m_map_attender.erase(dwPID); } bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { DWORD pid = pkChar->GetPlayerID(); if (CheckIpAddress(pkChar)) { m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } return false; } char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) COXEventManager::Instance().RemoveFromAttenderList(GetPlayerID()); Best Regards Ken 4 Do not be sorry, be better. Link to comment Share on other sites More sharing options...
wezt 95 Posted January 4, 2016 Share Posted January 4, 2016 Hey, Thanks for your tips @Ken , you've showed the same way, but with more elegant steps. Regards. 1 Link to comment Share on other sites More sharing options...
BackPlayer 55 Posted January 5, 2016 Share Posted January 5, 2016 (edited) On 4/1/2016 at 2:34 PM, Ken said: You shouldn't follow the long way and Koray too. Open OXEvent.h, find GetAttenderCount() and add those things under that line. // Ox-event IP-Checker public: bool CheckIpAddress(LPCHARACTER ch); void RemoveFromAttenderList(DWORD dwPID); Open OXEvent.cpp, add those lines after COxEventManager::LogWinner() bool COXEventManager::CheckIpAddress(LPCHARACTER ch) { for (itertype(m_map_attender) it = m_map_attender.begin(); it != m_map_attender.end(); ++it) { LPCHARACTER tch = CHARACTER_MANAGER::Instance().FindByPID(*it); if (!tch || !tch->GetDesc()) continue; if (!strcmp(ch->GetDesc()->GetHostName(), tch->GetDesc()->GetHostName()) && ch->GetMapIndex() == tch->GetMapIndex()) { LogManager::Instance().HackLog("MULTI_IP_OX", ch); ch->GoHome(); return false; } } return true; } void COXEventManager::RemoveFromAttenderList(DWORD dwPID) { m_map_attender.erase(dwPID); } bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { DWORD pid = pkChar->GetPlayerID(); if (CheckIpAddress(pkChar)) { m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } return false; } char.cpp if (GetMapIndex() == OXEVENT_MAP_INDEX) COXEventManager::Instance().RemoveFromAttenderList(GetPlayerID()); Best Regards Ken https://metin2.download/picture/L35kMKEX0zGBO7L3Cw21omeRxROyYzjV/.png what i do wrong? Edited September 1, 2022 by Metin2 Dev Core X - External 2 Internal Link to comment Share on other sites More sharing options...
Ken 904 Posted January 6, 2016 Share Posted January 6, 2016 @BackPlayer I fixed the error. You can now use it. Best Regards Ken 1 Do not be sorry, be better. Link to comment Share on other sites More sharing options...
BackPlayer 55 Posted January 6, 2016 Share Posted January 6, 2016 (edited) 1 hour ago, Ken said: @BackPlayer I fixed the error. You can now use it. Best Regards Ken Thank you! It works perfect this way too. Edited January 6, 2016 by BackPlayer Link to comment Share on other sites More sharing options...
DeYaN. 29 Posted November 24, 2020 Share Posted November 24, 2020 (edited) Is working perfect , but i can limit for 2-3 players for 1 ip ? Edited November 24, 2020 by DeYaN. Link to comment Share on other sites More sharing options...
Honorable Member Mali 41882 Posted November 24, 2020 Honorable Member Share Posted November 24, 2020 3 hours ago, DeYaN. said: Is working perfect , but i can limit for 2-3 players for 1 ip ? bool COXEventManager::EnterAttender(LPCHARACTER pkChar) { if (!CheckIpAddress(pkChar)) return false; // // Will send you to your empire, if you are not GM DWORD pid = pkChar->GetPlayerID(); m_map_char.insert(std::make_pair(pid, pid)); m_map_attender.insert(std::make_pair(pid, pid)); return true; } bool COXEventManager::CheckIpAddress(LPCHARACTER ch) { int IPCount = std::count_if(m_map_attender.begin(), m_map_attender.end(), [ch](const decltype(*m_map_attender.begin())& v) { LPCHARACTER tch = CHARACTER_MANAGER::Instance().FindByPID(v.second); if (!tch || !tch->GetDesc()) return false; return !strcmp(ch->GetDesc()->GetHostName(), tch->GetDesc()->GetHostName()); }); const int MaxPlayer = 2; return !(IPCount >= MaxPlayer); } 4 Link to comment Share on other sites More sharing options...
Management AZICKO 7349 Posted November 24, 2020 Management Share Posted November 24, 2020 #Updated Thanks to @Ken & @Mali61. Scamming ? Reselling ? metin2.download | metin2.dev | fr.metin2.dev | metin2dev.org | metin2.top | top-metin2.org Link to comment Share on other sites More sharing options...
Recommended Posts