As you probably know, there's an exploit that players use to change channels by abusing the LoginByKey function (a hack function). This function lacks any limitations, any connection can use it at any time. When combined with poorly written
systems, it can be used to duplicate items or yang.
I have already shared the fix privately with many people. At one point, someone even posted it on a forum, but it appears that the content has since been deleted. I’ve now been informed that this same user is selling the “fix”, despite the fact that it isn’t even theirs.
The fix isn’t perfect, but it works. Essentially, it ensures that only players with a valid logout can use LoginByKey, as it should be.
// common/tables.h
// 1. Search:
typedef struct SLogoutPacket
{
char login[LOGIN_MAX_LEN + 1];
char passwd[PASSWD_MAX_LEN + 1];
} TLogoutPacket;
// 1. Replace with:
typedef struct SLogoutPacket
{
char login[LOGIN_MAX_LEN + 1];
char passwd[PASSWD_MAX_LEN + 1];
bool bCanUseLoginByKey;
} TLogoutPacket;
// db/ClientManagerLogin.cpp
// 1. Search: (void CClientManager::QUERY_LOGIN_BY_KEY)
if (memcmp(pkLoginData->GetClientKey(), p->adwClientKey, sizeof(DWORD) * 4))
{
const DWORD* pdwClientKey = pkLoginData->GetClientKey();
sys_log(0, "LOGIN_BY_KEY client key differ %s %lu %lu %lu %lu, %lu %lu %lu %lu", r.login, p->adwClientKey[0], p->adwClientKey[1], p->adwClientKey[2], p->adwClientKey[3], pdwClientKey[0], pdwClientKey[1], pdwClientKey[2], pdwClientKey[3]);
pkPeer->EncodeReturn(HEADER_DG_LOGIN_NOT_EXIST, dwHandle);
return;
}
// 1. Add after:
if (!pkLoginData->IsAllowLoginByKey())
{
sys_err("LOGIN_BY_KEY without request %s", r.login); // To track them down
sys_log(0, "LOGIN_BY_KEY without request %s %lu", r.login, p->dwLoginKey);
TPacketDGLoginAlready ptog;
strlcpy(ptog.szLogin, szLogin, sizeof(ptog.szLogin));
pkPeer->EncodeHeader(HEADER_DG_LOGIN_ALREADY, dwHandle, sizeof(TPacketDGLoginAlready));
pkPeer->Encode(&ptog, sizeof(TPacketDGLoginAlready));
return;
}
pkLoginData->SetAllowLoginByKey(false);
// db/ClientManagerLogin.cpp
// 2. Search: (void CClientManager::QUERY_LOGOUT)
CLoginData* pLoginData = GetLoginDataByLogin(packet->login);
if (pLoginData == NULL)
return;
// 2. Add after:
pLoginData->SetAllowLoginByKey(packet->bCanUseLoginByKey);
// db/LoginData.cpp
// 1. Inside constructor add: (CLoginData::CLoginData())
m_bAllowLoginByKey = true; // We allow loginByKey by default (so the first login doesn't fail)
// db/LoginData.h
// 1. Add:
public:
bool IsAllowLoginByKey() const { return m_bAllowLoginByKey; }
void SetAllowLoginByKey(bool bFlag) { m_bAllowLoginByKey = bFlag; }
private:
bool m_bAllowLoginByKey;
// game/char.cpp
// 1. Somwhere inside void CHARACTER::Initialize() add:
m_bCanUseLoginByKey = false;
// 2. Inside your warp functions search: (WarpSet, ChangeChannel, etc)
Stop();
Save();
// 2. Add before that:
m_bCanUseLoginByKey = true;
// game/char.h
// 1. Add:
public:
bool CanUseLoginByKey() const { return m_bCanUseLoginByKey; }
protected:
bool m_bCanUseLoginByKey;
// game/desc.cpp
// 1. Search: (void DESC::Destroy())
if (m_lpCharacter)
{
m_lpCharacter->Disconnect("DESC::~DESC");
m_lpCharacter = NULL;
}
// 1. Replace with:
bool bCanUseLoginByKey = true;
if (m_lpCharacter)
{
bCanUseLoginByKey = m_lpCharacter->CanUseLoginByKey();
m_lpCharacter->Disconnect("DESC::~DESC");
m_lpCharacter = NULL;
}
// game/desc.cpp
// 2. Search: (void DESC::Destroy())
if (!g_bAuthServer)
{
if (m_accountTable.login[0] && m_accountTable.passwd[0])
{
TLogoutPacket pack;
strlcpy(pack.login, m_accountTable.login, sizeof(pack.login));
strlcpy(pack.passwd, m_accountTable.passwd, sizeof(pack.passwd));
db_clientdesc->DBPacket(HEADER_GD_LOGOUT, m_dwHandle, &pack, sizeof(TLogoutPacket));
}
}
// 2. Change it like this:
if (!g_bAuthServer)
{
if (m_accountTable.login[0] && m_accountTable.passwd[0])
{
TLogoutPacket pack;
strlcpy(pack.login, m_accountTable.login, sizeof(pack.login));
strlcpy(pack.passwd, m_accountTable.passwd, sizeof(pack.passwd));
pack.bCanUseLoginByKey = bCanUseLoginByKey;
db_clientdesc->DBPacket(HEADER_GD_LOGOUT, m_dwHandle, &pack, sizeof(TLogoutPacket));
}
}
// game/input_db.cpp
// 1. Search: (void CInputDB::LoginSuccess)
if (!d)
{
sys_log(0, "CInputDB::LoginSuccess - cannot find handle [%s]", pTab->login);
TLogoutPacket pack;
strlcpy(pack.login, pTab->login, sizeof(pack.login));
db_clientdesc->DBPacket(HEADER_GD_LOGOUT, dwHandle, &pack, sizeof(pack));
return;
}
if (strcmp(pTab->status, "OK"))
{
sys_log(0, "CInputDB::LoginSuccess - status[%s] is not OK [%s]", pTab->status, pTab->login);
TLogoutPacket pack;
strlcpy(pack.login, pTab->login, sizeof(pack.login));
db_clientdesc->DBPacket(HEADER_GD_LOGOUT, dwHandle, &pack, sizeof(pack));
LoginFailure(d, pTab->status);
return;
}
// 1. Replace with:
if (!d)
{
sys_log(0, "CInputDB::LoginSuccess - cannot find handle [%s]", pTab->login);
TLogoutPacket pack;
strlcpy(pack.login, pTab->login, sizeof(pack.login));
pack.bCanUseLoginByKey = false;
db_clientdesc->DBPacket(HEADER_GD_LOGOUT, dwHandle, &pack, sizeof(pack));
return;
}
if (strcmp(pTab->status, "OK"))
{
sys_log(0, "CInputDB::LoginSuccess - status[%s] is not OK [%s]", pTab->status, pTab->login);
TLogoutPacket pack;
strlcpy(pack.login, pTab->login, sizeof(pack.login));
pack.bCanUseLoginByKey = false;
db_clientdesc->DBPacket(HEADER_GD_LOGOUT, dwHandle, &pack, sizeof(pack));
LoginFailure(d, pTab->status);
return;
}