Compare commits
2 Commits
main
...
test/disco
| Author | SHA1 | Date | |
|---|---|---|---|
| ad6ac77199 | |||
| bfeb31c454 |
@@ -17,7 +17,6 @@
|
||||
#include "Marriage.h"
|
||||
#include "ItemIDRangeManager.h"
|
||||
#include "Cache.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -38,167 +37,6 @@ CPacketInfo g_item_info;
|
||||
int g_item_count = 0;
|
||||
int g_query_count[2];
|
||||
|
||||
namespace
|
||||
{
|
||||
bool PrepareClientPlayerStmt(CStmt& stmt, const char* query)
|
||||
{
|
||||
CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(SQL_PLAYER);
|
||||
|
||||
if (!sql)
|
||||
{
|
||||
sys_err("player SQL handle is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Prepare(sql, query);
|
||||
}
|
||||
|
||||
bool LoadSafeboxPasswordByAccountId(DWORD accountId, char* password, size_t passwordSize, bool* found)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "SELECT password FROM safebox%s WHERE account_id = ?", GetTablePostfix());
|
||||
|
||||
*found = false;
|
||||
password[0] = '\0';
|
||||
|
||||
CStmt stmt;
|
||||
if (!PrepareClientPlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_STRING, password, passwordSize))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
*found = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateSafeboxPasswordByAccountId(DWORD accountId, const char* password)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "UPDATE safebox%s SET password = ? WHERE account_id = ?", GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!PrepareClientPlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(password), SAFEBOX_PASSWORD_MAX_LEN))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
return stmt.Execute() != 0;
|
||||
}
|
||||
|
||||
bool MatchesSafeboxPassword(const char* storedPassword, const char* providedPassword)
|
||||
{
|
||||
if ((storedPassword && *storedPassword))
|
||||
return !strcasecmp(storedPassword, providedPassword);
|
||||
|
||||
return !strcmp("000000", providedPassword);
|
||||
}
|
||||
|
||||
bool LoadSafeboxTableByAccountId(DWORD accountId, const char* providedPassword, bool isMall, TSafeboxTable* safebox, bool* wrongPassword)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "SELECT account_id, size, password FROM safebox%s WHERE account_id = ?", GetTablePostfix());
|
||||
|
||||
DWORD loadedAccountId = 0;
|
||||
DWORD loadedSize = 0;
|
||||
char storedPassword[SAFEBOX_PASSWORD_MAX_LEN + 1] = {};
|
||||
|
||||
CStmt stmt;
|
||||
if (!PrepareClientPlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
*wrongPassword = false;
|
||||
memset(safebox, 0, sizeof(TSafeboxTable));
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &loadedAccountId))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &loadedSize))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_STRING, storedPassword, sizeof(storedPassword)))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
{
|
||||
safebox->dwID = accountId;
|
||||
*wrongPassword = strcmp("000000", providedPassword) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
if (((!storedPassword[0]) && strcmp("000000", providedPassword))
|
||||
|| (storedPassword[0] && strcmp(storedPassword, providedPassword)))
|
||||
{
|
||||
*wrongPassword = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
safebox->dwID = loadedAccountId == 0 ? accountId : loadedAccountId;
|
||||
safebox->bSize = static_cast<BYTE>(loadedSize);
|
||||
|
||||
if (isMall)
|
||||
{
|
||||
safebox->bSize = 1;
|
||||
sys_log(0, "MALL id[%u] size[%u]", safebox->dwID, safebox->bSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_log(0, "SAFEBOX id[%u] size[%u]", safebox->dwID, safebox->bSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void QueueSafeboxItemsLoad(CPeer* peer, CClientManager::ClientHandleInfo* info)
|
||||
{
|
||||
char query[512];
|
||||
snprintf(query, sizeof(query),
|
||||
"SELECT id, window+0, pos, count, vnum, socket0, socket1, socket2, "
|
||||
"attrtype0, attrvalue0, "
|
||||
"attrtype1, attrvalue1, "
|
||||
"attrtype2, attrvalue2, "
|
||||
"attrtype3, attrvalue3, "
|
||||
"attrtype4, attrvalue4, "
|
||||
"attrtype5, attrvalue5, "
|
||||
"attrtype6, attrvalue6 "
|
||||
"FROM item%s WHERE owner_id=%u AND window='%s'",
|
||||
GetTablePostfix(), info->account_id, info->ip[0] == 0 ? "SAFEBOX" : "MALL");
|
||||
|
||||
CDBManager::instance().ReturnQuery(query, QID_SAFEBOX_LOAD, peer->GetHandle(), info);
|
||||
}
|
||||
|
||||
void EncodeSafeboxPasswordChangeAnswer(CPeer* pkPeer, DWORD dwHandle, BYTE success)
|
||||
{
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_CHANGE_PASSWORD_ANSWER, dwHandle, sizeof(BYTE));
|
||||
pkPeer->EncodeBYTE(success);
|
||||
}
|
||||
}
|
||||
|
||||
CClientManager::CClientManager() :
|
||||
m_pkAuthPeer(NULL),
|
||||
m_iPlayerIDStart(0),
|
||||
@@ -612,30 +450,16 @@ void CClientManager::QUERY_SAFEBOX_LOAD(CPeer * pkPeer, DWORD dwHandle, TSafebox
|
||||
pi->account_index = 0;
|
||||
pi->ip[0] = bMall ? 1 : 0;
|
||||
strlcpy(pi->login, packet->szLogin, sizeof(pi->login));
|
||||
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery),
|
||||
"SELECT account_id, size, password FROM safebox%s WHERE account_id=%u",
|
||||
GetTablePostfix(), packet->dwID);
|
||||
|
||||
if (g_log)
|
||||
sys_log(0, "GD::SAFEBOX_LOAD (handle: %d account.id %u is_mall %d)", dwHandle, packet->dwID, bMall ? 1 : 0);
|
||||
|
||||
TSafeboxTable* safebox = new TSafeboxTable;
|
||||
bool wrongPassword = false;
|
||||
|
||||
if (!LoadSafeboxTableByAccountId(packet->dwID, pi->safebox_password, bMall, safebox, &wrongPassword))
|
||||
{
|
||||
delete safebox;
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
|
||||
if (wrongPassword)
|
||||
{
|
||||
delete safebox;
|
||||
delete pi;
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_WRONG_PASSWORD, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
pi->pSafebox = safebox;
|
||||
QueueSafeboxItemsLoad(pkPeer, pi);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_SAFEBOX_LOAD, pkPeer->GetHandle(), pi);
|
||||
}
|
||||
|
||||
void CClientManager::RESULT_SAFEBOX_LOAD(CPeer * pkPeer, SQLMsg * msg)
|
||||
@@ -643,79 +467,168 @@ void CClientManager::RESULT_SAFEBOX_LOAD(CPeer * pkPeer, SQLMsg * msg)
|
||||
CQueryInfo * qi = (CQueryInfo *) msg->pvUserData;
|
||||
ClientHandleInfo * pi = (ClientHandleInfo *) qi->pvData;
|
||||
DWORD dwHandle = pi->dwHandle;
|
||||
if (!pi->pSafebox)
|
||||
|
||||
// 여기에서 사용하는 account_index는 쿼리 순서를 말한다.
|
||||
// 첫번째 패스워드 알아내기 위해 하는 쿼리가 0
|
||||
// 두번째 실제 데이터를 얻어놓는 쿼리가 1
|
||||
|
||||
if (pi->account_index == 0)
|
||||
{
|
||||
sys_err("null safebox pointer!");
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
char szSafeboxPassword[SAFEBOX_PASSWORD_MAX_LEN + 1];
|
||||
strlcpy(szSafeboxPassword, pi->safebox_password, sizeof(szSafeboxPassword));
|
||||
|
||||
// 쿼리에 에러가 있었으므로 응답할 경우 창고가 비어있는 것 처럼
|
||||
// 보이기 때문에 창고가 아얘 안열리는게 나음
|
||||
if (!msg->Get()->pSQLResult)
|
||||
{
|
||||
sys_err("null safebox result");
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
TSafeboxTable * pSafebox = new TSafeboxTable;
|
||||
memset(pSafebox, 0, sizeof(TSafeboxTable));
|
||||
|
||||
static std::vector<TPlayerItem> s_items;
|
||||
CreateItemTableFromRes(msg->Get()->pSQLResult, &s_items, pi->account_id);
|
||||
SQLResult * res = msg->Get();
|
||||
|
||||
std::set<TItemAward *> * pSet = ItemAwardManager::instance().GetByLogin(pi->login);
|
||||
|
||||
if (pSet && !m_vec_itemTable.empty())
|
||||
{
|
||||
|
||||
CGrid grid(5, MAX(1, pi->pSafebox->bSize) * 9);
|
||||
bool bEscape = false;
|
||||
|
||||
for (DWORD i = 0; i < s_items.size(); ++i)
|
||||
if (res->uiNumRows == 0)
|
||||
{
|
||||
TPlayerItem & r = s_items[i];
|
||||
|
||||
itertype(m_map_itemTableByVnum) it = m_map_itemTableByVnum.find(r.vnum);
|
||||
|
||||
if (it == m_map_itemTableByVnum.end())
|
||||
if (strcmp("000000", szSafeboxPassword))
|
||||
{
|
||||
bEscape = true;
|
||||
sys_err("invalid item vnum %u in safebox: login %s", r.vnum, pi->login);
|
||||
break;
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_WRONG_PASSWORD, dwHandle, 0);
|
||||
delete pSafebox;
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(res->pSQLResult);
|
||||
|
||||
// 비밀번호가 틀리면..
|
||||
if (((!row[2] || !*row[2]) && strcmp("000000", szSafeboxPassword)) ||
|
||||
((row[2] && *row[2]) && strcmp(row[2], szSafeboxPassword)))
|
||||
{
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_WRONG_PASSWORD, dwHandle, 0);
|
||||
delete pSafebox;
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
|
||||
grid.Put(r.pos, 1, it->second->bSize);
|
||||
if (!row[0])
|
||||
pSafebox->dwID = 0;
|
||||
else
|
||||
str_to_number(pSafebox->dwID, row[0]);
|
||||
|
||||
if (!row[1])
|
||||
pSafebox->bSize = 0;
|
||||
else
|
||||
str_to_number(pSafebox->bSize, row[1]);
|
||||
/*
|
||||
if (!row[3])
|
||||
pSafebox->dwGold = 0;
|
||||
else
|
||||
pSafebox->dwGold = atoi(row[3]);
|
||||
*/
|
||||
if (pi->ip[0] == 1)
|
||||
{
|
||||
pSafebox->bSize = 1;
|
||||
sys_log(0, "MALL id[%d] size[%d]", pSafebox->dwID, pSafebox->bSize);
|
||||
}
|
||||
else
|
||||
sys_log(0, "SAFEBOX id[%d] size[%d]", pSafebox->dwID, pSafebox->bSize);
|
||||
}
|
||||
|
||||
if (!bEscape)
|
||||
if (0 == pSafebox->dwID)
|
||||
pSafebox->dwID = pi->account_id;
|
||||
|
||||
pi->pSafebox = pSafebox;
|
||||
|
||||
char szQuery[512];
|
||||
snprintf(szQuery, sizeof(szQuery),
|
||||
"SELECT id, window+0, pos, count, vnum, socket0, socket1, socket2, "
|
||||
"attrtype0, attrvalue0, "
|
||||
"attrtype1, attrvalue1, "
|
||||
"attrtype2, attrvalue2, "
|
||||
"attrtype3, attrvalue3, "
|
||||
"attrtype4, attrvalue4, "
|
||||
"attrtype5, attrvalue5, "
|
||||
"attrtype6, attrvalue6 "
|
||||
"FROM item%s WHERE owner_id=%d AND window='%s'",
|
||||
GetTablePostfix(), pi->account_id, pi->ip[0] == 0 ? "SAFEBOX" : "MALL");
|
||||
|
||||
pi->account_index = 1;
|
||||
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_SAFEBOX_LOAD, pkPeer->GetHandle(), pi);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (!pi->pSafebox)
|
||||
{
|
||||
std::vector<std::pair<DWORD, DWORD> > vec_dwFinishedAwardID;
|
||||
sys_err("null safebox pointer!");
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
|
||||
__typeof(pSet->begin()) it = pSet->begin();
|
||||
|
||||
char szQuery[512];
|
||||
// 쿼리에 에러가 있었으므로 응답할 경우 창고가 비어있는 것 처럼
|
||||
// 보이기 때문에 창고가 아얘 안열리는게 나음
|
||||
if (!msg->Get()->pSQLResult)
|
||||
{
|
||||
sys_err("null safebox result");
|
||||
delete pi;
|
||||
return;
|
||||
}
|
||||
|
||||
while (it != pSet->end())
|
||||
static std::vector<TPlayerItem> s_items;
|
||||
CreateItemTableFromRes(msg->Get()->pSQLResult, &s_items, pi->account_id);
|
||||
|
||||
std::set<TItemAward *> * pSet = ItemAwardManager::instance().GetByLogin(pi->login);
|
||||
|
||||
if (pSet && !m_vec_itemTable.empty())
|
||||
{
|
||||
|
||||
CGrid grid(5, MAX(1, pi->pSafebox->bSize) * 9);
|
||||
bool bEscape = false;
|
||||
|
||||
for (DWORD i = 0; i < s_items.size(); ++i)
|
||||
{
|
||||
TItemAward * pItemAward = *(it++);
|
||||
const DWORD& dwItemVnum = pItemAward->dwVnum;
|
||||
TPlayerItem & r = s_items[i];
|
||||
|
||||
if (pItemAward->bTaken)
|
||||
continue;
|
||||
|
||||
if (pi->ip[0] == 0 && pItemAward->bMall)
|
||||
continue;
|
||||
|
||||
if (pi->ip[0] == 1 && !pItemAward->bMall)
|
||||
continue;
|
||||
|
||||
itertype(m_map_itemTableByVnum) it = m_map_itemTableByVnum.find(pItemAward->dwVnum);
|
||||
itertype(m_map_itemTableByVnum) it = m_map_itemTableByVnum.find(r.vnum);
|
||||
|
||||
if (it == m_map_itemTableByVnum.end())
|
||||
{
|
||||
sys_err("invalid item vnum %u in item_award: login %s", pItemAward->dwVnum, pi->login);
|
||||
continue;
|
||||
bEscape = true;
|
||||
sys_err("invalid item vnum %u in safebox: login %s", r.vnum, pi->login);
|
||||
break;
|
||||
}
|
||||
|
||||
grid.Put(r.pos, 1, it->second->bSize);
|
||||
}
|
||||
|
||||
if (!bEscape)
|
||||
{
|
||||
std::vector<std::pair<DWORD, DWORD> > vec_dwFinishedAwardID;
|
||||
|
||||
__typeof(pSet->begin()) it = pSet->begin();
|
||||
|
||||
char szQuery[512];
|
||||
|
||||
while (it != pSet->end())
|
||||
{
|
||||
TItemAward * pItemAward = *(it++);
|
||||
const DWORD& dwItemVnum = pItemAward->dwVnum;
|
||||
|
||||
if (pItemAward->bTaken)
|
||||
continue;
|
||||
|
||||
if (pi->ip[0] == 0 && pItemAward->bMall)
|
||||
continue;
|
||||
|
||||
if (pi->ip[0] == 1 && !pItemAward->bMall)
|
||||
continue;
|
||||
|
||||
itertype(m_map_itemTableByVnum) it = m_map_itemTableByVnum.find(pItemAward->dwVnum);
|
||||
|
||||
if (it == m_map_itemTableByVnum.end())
|
||||
{
|
||||
sys_err("invalid item vnum %u in item_award: login %s", pItemAward->dwVnum, pi->login);
|
||||
continue;
|
||||
}
|
||||
|
||||
TItemTable * pItemTable = it->second;
|
||||
|
||||
int iPos;
|
||||
@@ -853,21 +766,22 @@ void CClientManager::RESULT_SAFEBOX_LOAD(CPeer * pkPeer, SQLMsg * msg)
|
||||
grid.Put(iPos, 1, it->second->bSize);
|
||||
}
|
||||
|
||||
for (DWORD i = 0; i < vec_dwFinishedAwardID.size(); ++i)
|
||||
ItemAwardManager::instance().Taken(vec_dwFinishedAwardID[i].first, vec_dwFinishedAwardID[i].second);
|
||||
for (DWORD i = 0; i < vec_dwFinishedAwardID.size(); ++i)
|
||||
ItemAwardManager::instance().Taken(vec_dwFinishedAwardID[i].first, vec_dwFinishedAwardID[i].second);
|
||||
}
|
||||
}
|
||||
|
||||
pi->pSafebox->wItemCount = s_items.size();
|
||||
|
||||
pkPeer->EncodeHeader(pi->ip[0] == 0 ? DG::SAFEBOX_LOAD : DG::MALL_LOAD, dwHandle, sizeof(TSafeboxTable) + sizeof(TPlayerItem) * s_items.size());
|
||||
|
||||
pkPeer->Encode(pi->pSafebox, sizeof(TSafeboxTable));
|
||||
|
||||
if (!s_items.empty())
|
||||
pkPeer->Encode(&s_items[0], sizeof(TPlayerItem) * s_items.size());
|
||||
|
||||
delete pi;
|
||||
}
|
||||
|
||||
pi->pSafebox->wItemCount = s_items.size();
|
||||
|
||||
pkPeer->EncodeHeader(pi->ip[0] == 0 ? DG::SAFEBOX_LOAD : DG::MALL_LOAD, dwHandle, sizeof(TSafeboxTable) + sizeof(TPlayerItem) * s_items.size());
|
||||
|
||||
pkPeer->Encode(pi->pSafebox, sizeof(TSafeboxTable));
|
||||
|
||||
if (!s_items.empty())
|
||||
pkPeer->Encode(&s_items[0], sizeof(TPlayerItem) * s_items.size());
|
||||
|
||||
delete pi;
|
||||
}
|
||||
|
||||
void CClientManager::QUERY_SAFEBOX_CHANGE_SIZE(CPeer * pkPeer, DWORD dwHandle, TSafeboxChangeSizePacket * p)
|
||||
@@ -903,28 +817,15 @@ void CClientManager::RESULT_SAFEBOX_CHANGE_SIZE(CPeer * pkPeer, SQLMsg * msg)
|
||||
|
||||
void CClientManager::QUERY_SAFEBOX_CHANGE_PASSWORD(CPeer * pkPeer, DWORD dwHandle, TSafeboxChangePasswordPacket * p)
|
||||
{
|
||||
char storedPassword[SAFEBOX_PASSWORD_MAX_LEN + 1];
|
||||
bool found = false;
|
||||
ClientHandleInfo * pi = new ClientHandleInfo(dwHandle);
|
||||
strlcpy(pi->safebox_password, p->szNewPassword, sizeof(pi->safebox_password));
|
||||
strlcpy(pi->login, p->szOldPassword, sizeof(pi->login));
|
||||
pi->account_id = p->dwID;
|
||||
|
||||
if (!LoadSafeboxPasswordByAccountId(p->dwID, storedPassword, sizeof(storedPassword), &found))
|
||||
{
|
||||
EncodeSafeboxPasswordChangeAnswer(pkPeer, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT password FROM safebox%s WHERE account_id=%u", GetTablePostfix(), p->dwID);
|
||||
|
||||
if (!found || !MatchesSafeboxPassword(storedPassword, p->szOldPassword))
|
||||
{
|
||||
EncodeSafeboxPasswordChangeAnswer(pkPeer, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UpdateSafeboxPasswordByAccountId(p->dwID, p->szNewPassword))
|
||||
{
|
||||
EncodeSafeboxPasswordChangeAnswer(pkPeer, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
EncodeSafeboxPasswordChangeAnswer(pkPeer, dwHandle, 1);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_SAFEBOX_CHANGE_PASSWORD, pkPeer->GetHandle(), pi);
|
||||
}
|
||||
|
||||
void CClientManager::RESULT_SAFEBOX_CHANGE_PASSWORD(CPeer * pkPeer, SQLMsg * msg)
|
||||
@@ -2500,8 +2401,7 @@ void CClientManager::ProcessPackets(CPeer * peer)
|
||||
break;
|
||||
|
||||
default:
|
||||
sys_err("Unknown header (header=%u packet_handle=%u length=%u peer_handle=%u host=%s recv=%d processed=%d)",
|
||||
header, dwHandle, dwLength, peer->GetHandle(), peer->GetHost(), peer->GetRecvLength(), i);
|
||||
sys_err("Unknown header (header: %d handle: %d length: %d)", header, dwHandle, dwLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -2661,10 +2561,20 @@ int CClientManager::AnalyzeQueryResult(SQLMsg * msg)
|
||||
case QID_ITEM_AWARD_TAKEN:
|
||||
break;
|
||||
|
||||
// PLAYER_INDEX_CREATE_BUG_FIX
|
||||
case QID_PLAYER_INDEX_CREATE:
|
||||
RESULT_PLAYER_INDEX_CREATE(peer, msg);
|
||||
break;
|
||||
// END_PLAYER_INDEX_CREATE_BUG_FIX
|
||||
|
||||
case QID_PLAYER_DELETE:
|
||||
__RESULT_PLAYER_DELETE(peer, msg);
|
||||
break;
|
||||
|
||||
case QID_LOGIN_BY_KEY:
|
||||
RESULT_LOGIN_BY_KEY(peer, msg);
|
||||
break;
|
||||
|
||||
// MYSHOP_PRICE_LIST
|
||||
case QID_ITEMPRICE_LOAD:
|
||||
RESULT_PRICELIST_LOAD(peer, msg);
|
||||
@@ -2887,25 +2797,18 @@ int CClientManager::Process()
|
||||
switch (fdwatch_check_event(m_fdWatcher, peer->GetFd(), idx))
|
||||
{
|
||||
case FDW_READ:
|
||||
switch (peer->Recv())
|
||||
if (peer->Recv() < 0)
|
||||
{
|
||||
case -2:
|
||||
sys_log(0, "Peer disconnected cleanly. (host=%s peer_handle=%u fd=%d)", peer->GetHost(), peer->GetHandle(), peer->GetFd());
|
||||
RemovePeer(peer);
|
||||
break;
|
||||
sys_err("Recv failed");
|
||||
RemovePeer(peer);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (peer == m_pkAuthPeer)
|
||||
if (g_log)
|
||||
sys_log(0, "AUTH_PEER_READ: size %d", peer->GetRecvLength());
|
||||
|
||||
case -1:
|
||||
sys_err("Recv failed (host=%s peer_handle=%u fd=%d)", peer->GetHost(), peer->GetHandle(), peer->GetFd());
|
||||
RemovePeer(peer);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (peer == m_pkAuthPeer)
|
||||
if (g_log)
|
||||
sys_log(0, "AUTH_PEER_READ: size %d", peer->GetRecvLength());
|
||||
|
||||
ProcessPackets(peer);
|
||||
break;
|
||||
ProcessPackets(peer);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -244,6 +244,7 @@ class CClientManager : public CNetBase, public singleton<CClientManager>
|
||||
void RESULT_AFFECT_LOAD(CPeer * pkPeer, MYSQL_RES * pRes, DWORD dwHandle);
|
||||
|
||||
// PLAYER_INDEX_CREATE_BUG_FIX
|
||||
void RESULT_PLAYER_INDEX_CREATE(CPeer *pkPeer, SQLMsg *msg);
|
||||
// END_PLAYER_INDEX_CREATE_BUG_FIX
|
||||
|
||||
// MYSHOP_PRICE_LIST
|
||||
@@ -325,6 +326,7 @@ class CClientManager : public CNetBase, public singleton<CClientManager>
|
||||
void QUERY_AUTH_LOGIN(CPeer * pkPeer, DWORD dwHandle, TPacketGDAuthLogin * p);
|
||||
|
||||
void QUERY_LOGIN_BY_KEY(CPeer * pkPeer, DWORD dwHandle, TPacketGDLoginByKey * p);
|
||||
void RESULT_LOGIN_BY_KEY(CPeer * peer, SQLMsg * msg);
|
||||
|
||||
void ChargeCash(const TRequestChargeCash * p);
|
||||
|
||||
|
||||
@@ -7,120 +7,12 @@
|
||||
#include "Config.h"
|
||||
#include "QID.h"
|
||||
#include "Cache.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
extern std::string g_stLocale;
|
||||
extern bool CreatePlayerTableFromRes(MYSQL_RES * res, TPlayerTable * pkTab);
|
||||
extern int g_test_server;
|
||||
extern int g_log;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool LoadPlayerIndexByAccountId(DWORD account_id, TAccountTable* account_table, bool* found)
|
||||
{
|
||||
*found = false;
|
||||
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "SELECT pid1, pid2, pid3, pid4, empire FROM player_index%s WHERE id = ?", GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
unsigned int player_ids[PLAYER_PER_ACCOUNT] = {};
|
||||
unsigned int empire = 0;
|
||||
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(SQL_PLAYER), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &account_id))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
|
||||
{
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &player_ids[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &empire))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
|
||||
account_table->players[i].dwID = player_ids[i];
|
||||
|
||||
account_table->bEmpire = static_cast<BYTE>(empire);
|
||||
*found = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreatePlayerIndexForAccount(DWORD account_id)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "INSERT IGNORE INTO player_index%s (id) VALUES(?)", GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(SQL_PLAYER), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &account_id))
|
||||
return false;
|
||||
|
||||
return stmt.Execute() != 0;
|
||||
}
|
||||
|
||||
bool CountPlayersByNameExcludingId(const char* player_name, DWORD player_id, unsigned long long* count)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
|
||||
if (g_stLocale == "sjis")
|
||||
snprintf(query, sizeof(query), "SELECT COUNT(*) FROM player%s WHERE name = ? COLLATE sjis_japanese_ci AND id <> ?", GetTablePostfix());
|
||||
else
|
||||
snprintf(query, sizeof(query), "SELECT COUNT(*) FROM player%s WHERE name = ? AND id <> ?", GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(SQL_PLAYER), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, (void*) player_name, CHARACTER_NAME_MAX_LEN))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &player_id))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONGLONG, count))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute() || !stmt.Fetch())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdatePlayerName(DWORD player_id, const char* player_name)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "UPDATE player%s SET name = ?, change_name = 0 WHERE id = ?", GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(SQL_PLAYER), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, (void*) player_name, CHARACTER_NAME_MAX_LEN))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &player_id))
|
||||
return false;
|
||||
|
||||
return stmt.Execute() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool CClientManager::InsertLogonAccount(const char * c_pszLogin, DWORD dwHandle, const char * c_pszIP)
|
||||
{
|
||||
char szLogin[LOGIN_MAX_LEN + 1];
|
||||
@@ -232,31 +124,55 @@ void CClientManager::QUERY_LOGIN_BY_KEY(CPeer * pkPeer, DWORD dwHandle, TPacketG
|
||||
strlcpy(info->ip, p->szIP, sizeof(info->ip));
|
||||
|
||||
sys_log(0, "LOGIN_BY_KEY success %s %lu %s", r.login, p->dwLoginKey, info->ip);
|
||||
bool found = false;
|
||||
if (!LoadPlayerIndexByAccountId(info->pAccountTable->id, info->pAccountTable, &found))
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT pid1, pid2, pid3, pid4, empire FROM player_index%s WHERE id=%u", GetTablePostfix(), r.id);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_LOGIN_BY_KEY, pkPeer->GetHandle(), info);
|
||||
}
|
||||
|
||||
void CClientManager::RESULT_LOGIN_BY_KEY(CPeer * peer, SQLMsg * msg)
|
||||
{
|
||||
CQueryInfo * qi = (CQueryInfo *) msg->pvUserData;
|
||||
ClientHandleInfo * info = (ClientHandleInfo *) qi->pvData;
|
||||
|
||||
if (msg->uiSQLErrno != 0)
|
||||
{
|
||||
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, info->dwHandle);
|
||||
delete info->pAccountTable;
|
||||
peer->EncodeReturn(DG::LOGIN_NOT_EXIST, info->dwHandle);
|
||||
delete info;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
sys_log(0, "RESULT_LOGIN_BY_KEY FAIL player_index's NULL : ID:%d", info->pAccountTable->id);
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
|
||||
if (!CreatePlayerIndexForAccount(info->pAccountTable->id) ||
|
||||
!LoadPlayerIndexByAccountId(info->pAccountTable->id, info->pAccountTable, &found) ||
|
||||
!found)
|
||||
if (msg->Get()->uiNumRows == 0)
|
||||
{
|
||||
DWORD account_id = info->pAccountTable->id;
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT pid1, pid2, pid3, pid4, empire FROM player_index%s WHERE id=%u", GetTablePostfix(), account_id);
|
||||
auto pMsg = CDBManager::instance().DirectQuery(szQuery, SQL_PLAYER);
|
||||
|
||||
sys_log(0, "RESULT_LOGIN_BY_KEY FAIL player_index's NULL : ID:%d", account_id);
|
||||
|
||||
if (pMsg->Get()->uiNumRows == 0)
|
||||
{
|
||||
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, info->dwHandle);
|
||||
delete info->pAccountTable;
|
||||
delete info;
|
||||
return;
|
||||
sys_log(0, "RESULT_LOGIN_BY_KEY FAIL player_index's NULL : ID:%d", account_id);
|
||||
|
||||
// PLAYER_INDEX_CREATE_BUG_FIX
|
||||
//snprintf(szQuery, sizeof(szQuery), "INSERT IGNORE INTO player_index%s (id) VALUES(%lu)", GetTablePostfix(), info->pAccountTable->id);
|
||||
snprintf(szQuery, sizeof(szQuery), "INSERT INTO player_index%s (id) VALUES(%u)", GetTablePostfix(), info->pAccountTable->id);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_PLAYER_INDEX_CREATE, peer->GetHandle(), info);
|
||||
// END_PLAYER_INDEX_CREATE_BUF_FIX
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
MYSQL_ROW row = mysql_fetch_row(msg->Get()->pSQLResult);
|
||||
|
||||
int col = 0;
|
||||
|
||||
for (; col < PLAYER_PER_ACCOUNT; ++col)
|
||||
str_to_number(info->pAccountTable->players[col].dwID, row[col]);
|
||||
|
||||
str_to_number(info->pAccountTable->bEmpire, row[col++]);
|
||||
info->account_index = 1;
|
||||
|
||||
extern std::string g_stLocale;
|
||||
@@ -273,9 +189,22 @@ void CClientManager::QUERY_LOGIN_BY_KEY(CPeer * pkPeer, DWORD dwHandle, TPacketG
|
||||
GetTablePostfix(), info->pAccountTable->id);
|
||||
}
|
||||
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_LOGIN, pkPeer->GetHandle(), info);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_LOGIN, peer->GetHandle(), info);
|
||||
}
|
||||
|
||||
// PLAYER_INDEX_CREATE_BUG_FIX
|
||||
void CClientManager::RESULT_PLAYER_INDEX_CREATE(CPeer * pkPeer, SQLMsg * msg)
|
||||
{
|
||||
CQueryInfo * qi = (CQueryInfo *) msg->pvUserData;
|
||||
ClientHandleInfo * info = (ClientHandleInfo *) qi->pvData;
|
||||
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT pid1, pid2, pid3, pid4, empire FROM player_index%s WHERE id=%u", GetTablePostfix(),
|
||||
info->pAccountTable->id);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_LOGIN_BY_KEY, pkPeer->GetHandle(), info);
|
||||
}
|
||||
// END_PLAYER_INDEX_CREATE_BUG_FIX
|
||||
|
||||
TAccountTable * CreateAccountTableFromRes(MYSQL_RES * res)
|
||||
{
|
||||
char input_pwd[PASSWD_MAX_LEN + 1];
|
||||
@@ -535,24 +464,44 @@ void CClientManager::QUERY_LOGOUT(CPeer * peer, DWORD dwHandle,const char * data
|
||||
|
||||
void CClientManager::QUERY_CHANGE_NAME(CPeer * peer, DWORD dwHandle, TPacketGDChangeName * p)
|
||||
{
|
||||
unsigned long long count = 0;
|
||||
if (!CountPlayersByNameExcludingId(p->name, p->pid, &count))
|
||||
char queryStr[QUERY_MAX_LEN];
|
||||
|
||||
if (g_stLocale == "sjis")
|
||||
snprintf(queryStr, sizeof(queryStr),
|
||||
"SELECT COUNT(*) as count FROM player%s WHERE name='%s' collate sjis_japanese_ci AND id <> %u",
|
||||
GetTablePostfix(), p->name, p->pid);
|
||||
else
|
||||
snprintf(queryStr, sizeof(queryStr),
|
||||
"SELECT COUNT(*) as count FROM player%s WHERE name='%s' AND id <> %u", GetTablePostfix(), p->name, p->pid);
|
||||
|
||||
auto pMsg = CDBManager::instance().DirectQuery(queryStr, SQL_PLAYER);
|
||||
|
||||
if (pMsg->Get()->uiNumRows)
|
||||
{
|
||||
if (!pMsg->Get()->pSQLResult)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
|
||||
|
||||
if (*row[0] != '0')
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
snprintf(queryStr, sizeof(queryStr),
|
||||
"UPDATE player%s SET name='%s',change_name=0 WHERE id=%u", GetTablePostfix(), p->name, p->pid);
|
||||
|
||||
if (!UpdatePlayerName(p->pid, p->name))
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
auto pMsg0 = CDBManager::instance().DirectQuery(queryStr, SQL_PLAYER);
|
||||
|
||||
TPacketDGChangeName pdg;
|
||||
peer->EncodeHeader(DG::CHANGE_NAME, dwHandle, sizeof(TPacketDGChangeName));
|
||||
@@ -560,3 +509,4 @@ void CClientManager::QUERY_CHANGE_NAME(CPeer * peer, DWORD dwHandle, TPacketGDCh
|
||||
strlcpy(pdg.name, p->name, sizeof(pdg.name));
|
||||
peer->Encode(&pdg, sizeof(TPacketDGChangeName));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "ItemAwardManager.h"
|
||||
#include "HB.h"
|
||||
#include "Cache.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
extern bool g_bHotBackup;
|
||||
|
||||
@@ -16,246 +15,6 @@ extern std::string g_stLocale;
|
||||
extern int g_test_server;
|
||||
extern int g_log;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool PreparePlayerStmt(CStmt& stmt, const char* query)
|
||||
{
|
||||
CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(SQL_PLAYER);
|
||||
|
||||
if (!sql)
|
||||
{
|
||||
sys_err("player SQL handle is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Prepare(sql, query);
|
||||
}
|
||||
|
||||
bool LoadPlayerIndexSlot(DWORD accountId, BYTE accountIndex, DWORD* playerId, bool* foundRow)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "SELECT pid%u FROM player_index%s WHERE id = ?", accountIndex + 1, GetTablePostfix());
|
||||
|
||||
*playerId = 0;
|
||||
*foundRow = false;
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, playerId))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
*foundRow = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CountPlayersByName(const char* playerName, unsigned long long* count)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
|
||||
if (g_stLocale == "sjis")
|
||||
snprintf(query, sizeof(query),
|
||||
"SELECT COUNT(*) FROM player%s WHERE name = ? COLLATE sjis_japanese_ci",
|
||||
GetTablePostfix());
|
||||
else
|
||||
snprintf(query, sizeof(query),
|
||||
"SELECT COUNT(*) FROM player%s WHERE name = ?",
|
||||
GetTablePostfix());
|
||||
|
||||
*count = 0;
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(playerName), CHARACTER_NAME_MAX_LEN + 1))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONGLONG, count))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return false;
|
||||
|
||||
return stmt.Fetch();
|
||||
}
|
||||
|
||||
bool InsertPlayerRecord(const TPlayerCreatePacket* packet, DWORD* playerId)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query),
|
||||
"INSERT INTO player%s "
|
||||
"(id, account_id, name, level, st, ht, dx, iq, "
|
||||
"job, voice, dir, x, y, z, "
|
||||
"hp, mp, random_hp, random_sp, stat_point, stamina, part_base, part_main, part_hair, gold, playtime, "
|
||||
"skill_level, quickslot) "
|
||||
"VALUES(0, %u, ?, %d, %d, %d, %d, %d, "
|
||||
"%d, %d, %d, %d, %d, %d, %d, "
|
||||
"%d, %d, %d, %d, %d, %d, %d, 0, %d, 0, ?, ?)",
|
||||
GetTablePostfix(),
|
||||
packet->account_id,
|
||||
packet->player_table.level,
|
||||
packet->player_table.st,
|
||||
packet->player_table.ht,
|
||||
packet->player_table.dx,
|
||||
packet->player_table.iq,
|
||||
packet->player_table.job,
|
||||
packet->player_table.voice,
|
||||
packet->player_table.dir,
|
||||
packet->player_table.x,
|
||||
packet->player_table.y,
|
||||
packet->player_table.z,
|
||||
packet->player_table.hp,
|
||||
packet->player_table.sp,
|
||||
packet->player_table.sRandomHP,
|
||||
packet->player_table.sRandomSP,
|
||||
packet->player_table.stat_point,
|
||||
packet->player_table.stamina,
|
||||
packet->player_table.part_base,
|
||||
packet->player_table.part_base,
|
||||
packet->player_table.gold);
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(packet->player_table.name), CHARACTER_NAME_MAX_LEN + 1))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_BLOB, const_cast<TPlayerSkill*>(packet->player_table.skills), sizeof(packet->player_table.skills)))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_BLOB, const_cast<TQuickslot*>(packet->player_table.quickslot), sizeof(packet->player_table.quickslot)))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.GetAffectedRows() == 0)
|
||||
return false;
|
||||
|
||||
*playerId = static_cast<DWORD>(stmt.GetInsertId());
|
||||
return *playerId != 0;
|
||||
}
|
||||
|
||||
bool UpdatePlayerIndexSlot(DWORD accountId, BYTE accountIndex, DWORD playerId)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "UPDATE player_index%s SET pid%u = ? WHERE id = ?", GetTablePostfix(), accountIndex + 1);
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
return stmt.GetAffectedRows() != 0;
|
||||
}
|
||||
|
||||
bool ArchiveDeletedPlayerById(DWORD playerId)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "INSERT INTO player%s_deleted SELECT * FROM player%s WHERE id = ?",
|
||||
GetTablePostfix(), GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
const unsigned long long affectedRows = stmt.GetAffectedRows();
|
||||
return affectedRows != 0 && affectedRows != static_cast<unsigned long long>(-1);
|
||||
}
|
||||
|
||||
void DeletePlayerById(DWORD playerId)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "DELETE FROM player%s WHERE id = ?", GetTablePostfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
return;
|
||||
|
||||
stmt.Execute();
|
||||
}
|
||||
|
||||
bool ResetPlayerIndexSlotForDelete(BYTE accountIndex, DWORD playerId)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "UPDATE player_index%s SET pid%u = 0 WHERE pid%u = ?",
|
||||
GetTablePostfix(), accountIndex + 1, accountIndex + 1);
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
const unsigned long long affectedRows = stmt.GetAffectedRows();
|
||||
return affectedRows != 0 && affectedRows != static_cast<unsigned long long>(-1);
|
||||
}
|
||||
|
||||
void DeletePlayerItemsByOwnerId(DWORD playerId)
|
||||
{
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "DELETE FROM item%s WHERE owner_id = ? AND (window < ? OR window = ?)",
|
||||
GetTablePostfix());
|
||||
|
||||
int32_t safeboxWindow = SAFEBOX;
|
||||
int32_t dragonSoulInventory = DRAGON_SOUL_INVENTORY;
|
||||
|
||||
CStmt stmt;
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
return;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &safeboxWindow))
|
||||
return;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &dragonSoulInventory))
|
||||
return;
|
||||
|
||||
stmt.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
// !!!!!!!!!!! IMPORTANT !!!!!!!!!!!!
|
||||
@@ -1051,7 +810,9 @@ static time_by_id_map_t s_createTimeByAccountID;
|
||||
*/
|
||||
void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerCreatePacket* packet)
|
||||
{
|
||||
DWORD player_id = 0;
|
||||
char queryStr[QUERY_MAX_LEN];
|
||||
int queryLen;
|
||||
int player_id;
|
||||
|
||||
// 한 계정에 X초 내로 캐릭터 생성을 할 수 없다.
|
||||
time_by_id_map_t::iterator it = s_createTimeByAccountID.find(packet->account_id);
|
||||
@@ -1067,40 +828,81 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC
|
||||
}
|
||||
}
|
||||
|
||||
DWORD existingPlayerId = 0;
|
||||
bool foundPlayerIndexRow = false;
|
||||
if (!LoadPlayerIndexSlot(packet->account_id, packet->account_index, &existingPlayerId, &foundPlayerIndexRow))
|
||||
queryLen = snprintf(queryStr, sizeof(queryStr),
|
||||
"SELECT pid%u FROM player_index%s WHERE id=%d", packet->account_index + 1, GetTablePostfix(), packet->account_id);
|
||||
|
||||
auto pMsg0 = CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
if (pMsg0->Get()->uiNumRows != 0)
|
||||
{
|
||||
if (!pMsg0->Get()->pSQLResult)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg0->Get()->pSQLResult);
|
||||
|
||||
DWORD dwPID = 0; str_to_number(dwPID, row[0]);
|
||||
if (row[0] && dwPID > 0)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
sys_log(0, "ALREADY EXIST AccountChrIdx %d ID %d", packet->account_index, dwPID);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!foundPlayerIndexRow)
|
||||
if (g_stLocale == "sjis")
|
||||
snprintf(queryStr, sizeof(queryStr),
|
||||
"SELECT COUNT(*) as count FROM player%s WHERE name='%s' collate sjis_japanese_ci",
|
||||
GetTablePostfix(), packet->player_table.name);
|
||||
else
|
||||
snprintf(queryStr, sizeof(queryStr),
|
||||
"SELECT COUNT(*) as count FROM player%s WHERE name='%s'", GetTablePostfix(), packet->player_table.name);
|
||||
|
||||
auto pMsg1 = CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
if (pMsg1->Get()->uiNumRows)
|
||||
{
|
||||
if (!pMsg1->Get()->pSQLResult)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg1->Get()->pSQLResult);
|
||||
|
||||
if (*row[0] != '0')
|
||||
{
|
||||
sys_log(0, "ALREADY EXIST name %s, row[0] %s query %s", packet->player_table.name, row[0], queryStr);
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingPlayerId > 0)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
sys_log(0, "ALREADY EXIST AccountChrIdx %d ID %d", packet->account_index, existingPlayerId);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long long playerCount = 0;
|
||||
if (!CountPlayersByName(packet->player_table.name, &playerCount))
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerCount != 0)
|
||||
{
|
||||
sys_log(0, "ALREADY EXIST name %s", packet->player_table.name);
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
queryLen = snprintf(queryStr, sizeof(queryStr),
|
||||
"INSERT INTO player%s "
|
||||
"(id, account_id, name, level, st, ht, dx, iq, "
|
||||
"job, voice, dir, x, y, z, "
|
||||
"hp, mp, random_hp, random_sp, stat_point, stamina, part_base, part_main, part_hair, gold, playtime, "
|
||||
"skill_level, quickslot) "
|
||||
"VALUES(0, %u, '%s', %d, %d, %d, %d, %d, "
|
||||
"%d, %d, %d, %d, %d, %d, %d, "
|
||||
"%d, %d, %d, %d, %d, %d, %d, 0, %d, 0, ",
|
||||
GetTablePostfix(),
|
||||
packet->account_id, packet->player_table.name, packet->player_table.level, packet->player_table.st, packet->player_table.ht, packet->player_table.dx, packet->player_table.iq,
|
||||
packet->player_table.job, packet->player_table.voice, packet->player_table.dir, packet->player_table.x, packet->player_table.y, packet->player_table.z,
|
||||
packet->player_table.hp, packet->player_table.sp, packet->player_table.sRandomHP, packet->player_table.sRandomSP, packet->player_table.stat_point, packet->player_table.stamina, packet->player_table.part_base, packet->player_table.part_base, packet->player_table.gold);
|
||||
|
||||
sys_log(0, "PlayerCreate accountid %d name %s level %d gold %d, st %d ht %d job %d",
|
||||
packet->account_id,
|
||||
@@ -1111,16 +913,40 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC
|
||||
packet->player_table.ht,
|
||||
packet->player_table.job);
|
||||
|
||||
if (!InsertPlayerRecord(packet, &player_id))
|
||||
static char text[8192 + 1];
|
||||
|
||||
CDBManager::instance().EscapeString(text, packet->player_table.skills, sizeof(packet->player_table.skills));
|
||||
queryLen += snprintf(queryStr + queryLen, sizeof(queryStr) - queryLen, "'%s', ", text);
|
||||
if (g_test_server)
|
||||
sys_log(0, "Create_Player queryLen[%d] TEXT[%s]", queryLen, text);
|
||||
|
||||
CDBManager::instance().EscapeString(text, packet->player_table.quickslot, sizeof(packet->player_table.quickslot));
|
||||
queryLen += snprintf(queryStr + queryLen, sizeof(queryStr) - queryLen, "'%s')", text);
|
||||
|
||||
auto pMsg2 = CDBManager::instance().DirectQuery(queryStr);
|
||||
if (g_test_server)
|
||||
sys_log(0, "Create_Player queryLen[%d] TEXT[%s]", queryLen, text);
|
||||
|
||||
if (pMsg2->Get()->uiAffectedRows <= 0)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
sys_log(0, "ALREADY EXIST3 name %s", packet->player_table.name);
|
||||
sys_log(0, "ALREADY EXIST3 query: %s AffectedRows %lu", queryStr, pMsg2->Get()->uiAffectedRows);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!UpdatePlayerIndexSlot(packet->account_id, packet->account_index, player_id))
|
||||
player_id = pMsg2->Get()->uiInsertID;
|
||||
|
||||
snprintf(queryStr, sizeof(queryStr), "UPDATE player_index%s SET pid%d=%d WHERE id=%d",
|
||||
GetTablePostfix(), packet->account_index + 1, player_id, packet->account_id);
|
||||
auto pMsg3 = CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
if (pMsg3->Get()->uiAffectedRows <= 0)
|
||||
{
|
||||
DeletePlayerById(player_id);
|
||||
sys_err("QUERY_ERROR: %s", queryStr);
|
||||
|
||||
snprintf(queryStr, sizeof(queryStr), "DELETE FROM player%s WHERE id=%d", GetTablePostfix(), player_id);
|
||||
CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
@@ -1257,7 +1083,11 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg)
|
||||
|
||||
char queryStr[QUERY_MAX_LEN];
|
||||
|
||||
if (!ArchiveDeletedPlayerById(pi->player_id))
|
||||
snprintf(queryStr, sizeof(queryStr), "INSERT INTO player%s_deleted SELECT * FROM player%s WHERE id=%d",
|
||||
GetTablePostfix(), GetTablePostfix(), pi->player_id);
|
||||
auto pIns = CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
if (pIns->Get()->uiAffectedRows == 0 || pIns->Get()->uiAffectedRows == (uint32_t)-1)
|
||||
{
|
||||
sys_log(0, "PLAYER_DELETE FAILED %u CANNOT INSERT TO player%s_deleted", dwPID, GetTablePostfix());
|
||||
|
||||
@@ -1269,6 +1099,10 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg)
|
||||
// 삭제 성공
|
||||
sys_log(0, "PLAYER_DELETE SUCCESS %u", dwPID);
|
||||
|
||||
char account_index_string[16];
|
||||
|
||||
snprintf(account_index_string, sizeof(account_index_string), "player_id%d", m_iPlayerIDStart + pi->account_index);
|
||||
|
||||
// 플레이어 테이블을 캐쉬에서 삭제한다.
|
||||
CPlayerTableCache * pkPlayerCache = GetPlayerCache(pi->player_id);
|
||||
|
||||
@@ -1297,7 +1131,15 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg)
|
||||
m_map_pkItemCacheSetPtr.erase(pi->player_id);
|
||||
}
|
||||
|
||||
if (!ResetPlayerIndexSlotForDelete(pi->account_index, pi->player_id))
|
||||
snprintf(queryStr, sizeof(queryStr), "UPDATE player_index%s SET pid%u=0 WHERE pid%u=%d",
|
||||
GetTablePostfix(),
|
||||
pi->account_index + 1,
|
||||
pi->account_index + 1,
|
||||
pi->player_id);
|
||||
|
||||
auto pMsg = CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
if (pMsg->Get()->uiAffectedRows == 0 || pMsg->Get()->uiAffectedRows == (uint32_t)-1)
|
||||
{
|
||||
sys_log(0, "PLAYER_DELETE FAIL WHEN UPDATE account table");
|
||||
peer->EncodeHeader(DG::PLAYER_DELETE_FAILED, pi->dwHandle, 1);
|
||||
@@ -1305,9 +1147,11 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg)
|
||||
return;
|
||||
}
|
||||
|
||||
DeletePlayerById(pi->player_id);
|
||||
snprintf(queryStr, sizeof(queryStr), "DELETE FROM player%s WHERE id=%d", GetTablePostfix(), pi->player_id);
|
||||
CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
DeletePlayerItemsByOwnerId(pi->player_id);
|
||||
snprintf(queryStr, sizeof(queryStr), "DELETE FROM item%s WHERE owner_id=%d AND (window < %d or window = %d)", GetTablePostfix(), pi->player_id, SAFEBOX, DRAGON_SOUL_INVENTORY);
|
||||
CDBManager::instance().DirectQuery(queryStr);
|
||||
|
||||
snprintf(queryStr, sizeof(queryStr), "DELETE FROM quest%s WHERE dwPID=%d", GetTablePostfix(), pi->player_id);
|
||||
CDBManager::instance().AsyncQuery(queryStr);
|
||||
@@ -1528,3 +1372,4 @@ void CClientManager::FlushPlayerCacheSet(DWORD pid)
|
||||
delete c;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,26 +4,6 @@
|
||||
|
||||
extern std::string g_stLocale;
|
||||
|
||||
namespace
|
||||
{
|
||||
const char* SQLSlotName(int slot)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case SQL_PLAYER:
|
||||
return "player";
|
||||
case SQL_ACCOUNT:
|
||||
return "account";
|
||||
case SQL_COMMON:
|
||||
return "common";
|
||||
case SQL_HOTBACKUP:
|
||||
return "hotbackup";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CDBManager::CDBManager()
|
||||
{
|
||||
Initialize();
|
||||
@@ -65,16 +45,6 @@ void CDBManager::Quit()
|
||||
{
|
||||
for (int i = 0; i < SQL_MAX_NUM; ++i)
|
||||
{
|
||||
sys_log(0,
|
||||
"[SHUTDOWN] DBManager slot=%s begin main_pending=%u main_copied=%u main_results=%u async_pending=%u async_copied=%u async_results=%u",
|
||||
SQLSlotName(i),
|
||||
m_mainSQL[i] ? m_mainSQL[i]->CountQuery() : 0,
|
||||
m_mainSQL[i] ? m_mainSQL[i]->CountCopiedQueryQueue() : 0,
|
||||
m_mainSQL[i] ? m_mainSQL[i]->CountResult() : 0,
|
||||
m_asyncSQL[i] ? m_asyncSQL[i]->CountQuery() : 0,
|
||||
m_asyncSQL[i] ? m_asyncSQL[i]->CountCopiedQueryQueue() : 0,
|
||||
m_asyncSQL[i] ? m_asyncSQL[i]->CountResult() : 0);
|
||||
|
||||
if (m_mainSQL[i])
|
||||
m_mainSQL[i]->Quit();
|
||||
|
||||
@@ -83,8 +53,6 @@ void CDBManager::Quit()
|
||||
|
||||
if (m_directSQL[i])
|
||||
m_directSQL[i]->Quit();
|
||||
|
||||
sys_log(0, "[SHUTDOWN] DBManager slot=%s done", SQLSlotName(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,3 +182,4 @@ void CDBManager::QueryLocaleSet()
|
||||
m_asyncSQL[n]->QueryLocaleSet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ class CDBManager : public singleton<CDBManager>
|
||||
void ReturnQuery(const char * c_pszQuery, int iType, IDENT dwIdent, void * pvData, int iSlot = SQL_PLAYER);
|
||||
void AsyncQuery(const char * c_pszQuery, int iSlot = SQL_PLAYER);
|
||||
std::unique_ptr<SQLMsg> DirectQuery(const char* c_pszQuery, int iSlot = SQL_PLAYER);
|
||||
CAsyncSQL* GetDirectSQL(int iSlot = SQL_PLAYER) { return m_directSQL[iSlot].get(); }
|
||||
|
||||
SQLMsg * PopResult();
|
||||
SQLMsg * PopResult(eSQL_SLOT slot );
|
||||
|
||||
@@ -3,83 +3,12 @@
|
||||
#include "DBManager.h"
|
||||
#include "ItemAwardManager.h"
|
||||
#include "Peer.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
#include "ClientManager.h"
|
||||
|
||||
|
||||
|
||||
DWORD g_dwLastCachedItemAwardID = 0;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool FallbackItemAwardLoadQuery()
|
||||
{
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery),
|
||||
"SELECT id,login,vnum,count,socket0,socket1,socket2,mall,why "
|
||||
"FROM item_award WHERE taken_time IS NULL and id > %d",
|
||||
g_dwLastCachedItemAwardID);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_ITEM_AWARD_LOAD, 0, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FallbackItemAwardTakenQuery(DWORD dwAwardID, DWORD dwItemID)
|
||||
{
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery),
|
||||
"UPDATE item_award SET taken_time=NOW(),item_id=%u WHERE id=%u AND taken_time IS NULL",
|
||||
dwItemID, dwAwardID);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_ITEM_AWARD_TAKEN, 0, NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProcessItemAwardRow(DWORD dwID, const char* login, DWORD dwVnum, DWORD dwCount,
|
||||
DWORD dwSocket0, DWORD dwSocket1, DWORD dwSocket2, bool bMall, const char* why)
|
||||
{
|
||||
if (ItemAwardManager::instance().GetMapAward().find(dwID) != ItemAwardManager::instance().GetMapAward().end())
|
||||
return;
|
||||
|
||||
TItemAward* kData = new TItemAward;
|
||||
memset(kData, 0, sizeof(TItemAward));
|
||||
|
||||
kData->dwID = dwID;
|
||||
trim_and_lower(login, kData->szLogin, sizeof(kData->szLogin));
|
||||
kData->dwVnum = dwVnum;
|
||||
kData->dwCount = dwCount;
|
||||
kData->dwSocket0 = dwSocket0;
|
||||
kData->dwSocket1 = dwSocket1;
|
||||
kData->dwSocket2 = dwSocket2;
|
||||
kData->bMall = bMall;
|
||||
|
||||
if (why && *why)
|
||||
{
|
||||
strlcpy(kData->szWhy, why, sizeof(kData->szWhy));
|
||||
char* whyStr = kData->szWhy;
|
||||
char cmdStr[100] = "";
|
||||
strcpy(cmdStr, whyStr);
|
||||
char command[20] = "";
|
||||
strcpy(command, CClientManager::instance().GetCommand(cmdStr));
|
||||
if (!(strcmp(command, "GIFT")))
|
||||
{
|
||||
TPacketItemAwardInfromer giftData;
|
||||
strcpy(giftData.login, kData->szLogin);
|
||||
strcpy(giftData.command, command);
|
||||
giftData.vnum = kData->dwVnum;
|
||||
CClientManager::instance().ForwardPacket(DG::ITEMAWARD_INFORMER, &giftData, sizeof(TPacketItemAwardInfromer));
|
||||
}
|
||||
}
|
||||
|
||||
ItemAwardManager::instance().GetMapAward().insert(std::make_pair(dwID, kData));
|
||||
|
||||
printf("ITEM_AWARD load id %u bMall %d \n", kData->dwID, kData->bMall);
|
||||
sys_log(0, "ITEM_AWARD: load id %lu login %s vnum %lu count %u socket %lu", kData->dwID, kData->szLogin, kData->dwVnum, kData->dwCount, kData->dwSocket0);
|
||||
std::set<TItemAward*>& kSet = ItemAwardManager::instance().GetMapkSetAwardByLogin()[kData->szLogin];
|
||||
kSet.insert(kData);
|
||||
|
||||
if (dwID > g_dwLastCachedItemAwardID)
|
||||
g_dwLastCachedItemAwardID = dwID;
|
||||
}
|
||||
}
|
||||
|
||||
ItemAwardManager::ItemAwardManager()
|
||||
{
|
||||
}
|
||||
@@ -90,45 +19,9 @@ ItemAwardManager::~ItemAwardManager()
|
||||
|
||||
void ItemAwardManager::RequestLoad()
|
||||
{
|
||||
static const char* query =
|
||||
"SELECT id, login, vnum, count, socket0, socket1, socket2, mall, COALESCE(why, '') "
|
||||
"FROM item_award WHERE taken_time IS NULL AND id > ?";
|
||||
|
||||
CStmt stmt;
|
||||
DWORD dwID = 0;
|
||||
DWORD dwVnum = 0;
|
||||
DWORD dwCount = 0;
|
||||
DWORD dwSocket0 = 0;
|
||||
DWORD dwSocket1 = 0;
|
||||
DWORD dwSocket2 = 0;
|
||||
char login[LOGIN_MAX_LEN + 1] = {};
|
||||
char why[ITEM_AWARD_WHY_MAX_LEN + 1] = {};
|
||||
DWORD dwMall = 0;
|
||||
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(), query) ||
|
||||
!stmt.BindParam(MYSQL_TYPE_LONG, &g_dwLastCachedItemAwardID) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwID) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_STRING, login, sizeof(login)) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwVnum) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwCount) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwSocket0) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwSocket1) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwSocket2) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_LONG, &dwMall) ||
|
||||
!stmt.BindResult(MYSQL_TYPE_STRING, why, sizeof(why)) ||
|
||||
!stmt.Execute())
|
||||
{
|
||||
sys_err("ITEM_AWARD: prepared load failed, falling back to legacy query path");
|
||||
FallbackItemAwardLoadQuery();
|
||||
return;
|
||||
}
|
||||
|
||||
while (stmt.Fetch())
|
||||
{
|
||||
ProcessItemAwardRow(dwID, login, dwVnum, dwCount, dwSocket0, dwSocket1, dwSocket2, dwMall != 0, why);
|
||||
memset(login, 0, sizeof(login));
|
||||
memset(why, 0, sizeof(why));
|
||||
}
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT id,login,vnum,count,socket0,socket1,socket2,mall,why FROM item_award WHERE taken_time IS NULL and id > %d", g_dwLastCachedItemAwardID);
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_ITEM_AWARD_LOAD, 0, NULL);
|
||||
}
|
||||
|
||||
void ItemAwardManager::Load(SQLMsg * pMsg)
|
||||
@@ -142,22 +35,51 @@ void ItemAwardManager::Load(SQLMsg * pMsg)
|
||||
|
||||
DWORD dwID = 0;
|
||||
str_to_number(dwID, row[col++]);
|
||||
const char* login = row[col++];
|
||||
|
||||
DWORD dwVnum = 0;
|
||||
DWORD dwCount = 0;
|
||||
DWORD dwSocket0 = 0;
|
||||
DWORD dwSocket1 = 0;
|
||||
DWORD dwSocket2 = 0;
|
||||
DWORD dwMall = 0;
|
||||
str_to_number(dwVnum, row[col++]);
|
||||
str_to_number(dwCount, row[col++]);
|
||||
str_to_number(dwSocket0, row[col++]);
|
||||
str_to_number(dwSocket1, row[col++]);
|
||||
str_to_number(dwSocket2, row[col++]);
|
||||
str_to_number(dwMall, row[col++]);
|
||||
if (m_map_award.find(dwID) != m_map_award.end())
|
||||
continue;
|
||||
|
||||
ProcessItemAwardRow(dwID, login, dwVnum, dwCount, dwSocket0, dwSocket1, dwSocket2, dwMall != 0, row[col] ? row[col] : "");
|
||||
TItemAward * kData = new TItemAward;
|
||||
memset(kData, 0, sizeof(TItemAward));
|
||||
|
||||
kData->dwID = dwID;
|
||||
trim_and_lower(row[col++], kData->szLogin, sizeof(kData->szLogin));
|
||||
str_to_number(kData->dwVnum, row[col++]);
|
||||
str_to_number(kData->dwCount, row[col++]);
|
||||
str_to_number(kData->dwSocket0, row[col++]);
|
||||
str_to_number(kData->dwSocket1, row[col++]);
|
||||
str_to_number(kData->dwSocket2, row[col++]);
|
||||
str_to_number(kData->bMall, row[col++]);
|
||||
|
||||
if (row[col])
|
||||
{
|
||||
strlcpy(kData->szWhy, row[col], sizeof(kData->szWhy));
|
||||
//게임 중에 why콜룸에 변동이 생기면
|
||||
char* whyStr = kData->szWhy; //why 콜룸 읽기
|
||||
char cmdStr[100] = ""; //why콜룸에서 읽은 값을 임시 문자열에 복사해둠
|
||||
strcpy(cmdStr,whyStr); //명령어 얻는 과정에서 토큰쓰면 원본도 토큰화 되기 때문
|
||||
char command[20] = "";
|
||||
strcpy(command,CClientManager::instance().GetCommand(cmdStr)); // command 얻기
|
||||
//sys_err("%d, %s",pItemAward->dwID,command);
|
||||
if( !(strcmp(command,"GIFT") )) // command 가 GIFT이면
|
||||
{
|
||||
TPacketItemAwardInfromer giftData;
|
||||
strcpy(giftData.login, kData->szLogin); //로그인 아이디 복사
|
||||
strcpy(giftData.command, command); //명령어 복사
|
||||
giftData.vnum = kData->dwVnum; //아이템 vnum도 복사
|
||||
CClientManager::instance().ForwardPacket(DG::ITEMAWARD_INFORMER,&giftData,sizeof(TPacketItemAwardInfromer));
|
||||
}
|
||||
}
|
||||
|
||||
m_map_award.insert(std::make_pair(dwID, kData));
|
||||
|
||||
printf("ITEM_AWARD load id %u bMall %d \n", kData->dwID, kData->bMall);
|
||||
sys_log(0, "ITEM_AWARD: load id %lu login %s vnum %lu count %u socket %lu", kData->dwID, kData->szLogin, kData->dwVnum, kData->dwCount, kData->dwSocket0);
|
||||
std::set<TItemAward *> & kSet = m_map_kSetAwardByLogin[kData->szLogin];
|
||||
kSet.insert(kData);
|
||||
|
||||
if (dwID > g_dwLastCachedItemAwardID)
|
||||
g_dwLastCachedItemAwardID = dwID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,18 +109,13 @@ void ItemAwardManager::Taken(DWORD dwAwardID, DWORD dwItemID)
|
||||
//
|
||||
// Update taken_time in database to prevent not to give him again.
|
||||
//
|
||||
static const char* query =
|
||||
"UPDATE item_award SET taken_time=NOW(), item_id=? WHERE id=? AND taken_time IS NULL";
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(), query) ||
|
||||
!stmt.BindParam(MYSQL_TYPE_LONG, &dwItemID) ||
|
||||
!stmt.BindParam(MYSQL_TYPE_LONG, &dwAwardID) ||
|
||||
!stmt.Execute())
|
||||
{
|
||||
sys_err("ITEM_AWARD: prepared taken update failed, falling back to legacy query path");
|
||||
FallbackItemAwardTakenQuery(dwAwardID, dwItemID);
|
||||
}
|
||||
snprintf(szQuery, sizeof(szQuery),
|
||||
"UPDATE item_award SET taken_time=NOW(),item_id=%u WHERE id=%u AND taken_time IS NULL",
|
||||
dwItemID, dwAwardID);
|
||||
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_ITEM_AWARD_TAKEN, 0, NULL);
|
||||
}
|
||||
|
||||
std::map<DWORD, TItemAward *>& ItemAwardManager::GetMapAward()
|
||||
@@ -209,4 +126,4 @@ std::map<DWORD, TItemAward *>& ItemAwardManager::GetMapAward()
|
||||
std::map<std::string, std::set<TItemAward *> >& ItemAwardManager::GetMapkSetAwardByLogin()
|
||||
{
|
||||
return m_map_kSetAwardByLogin;
|
||||
}
|
||||
}
|
||||
@@ -243,9 +243,7 @@ int main()
|
||||
|
||||
signal_timer_disable();
|
||||
|
||||
sys_log(0, "[SHUTDOWN] DB main loop finished, quitting SQL workers");
|
||||
DBManager.Quit();
|
||||
sys_log(0, "[SHUTDOWN] DB SQL workers stopped");
|
||||
int iCount;
|
||||
|
||||
while (1)
|
||||
@@ -262,8 +260,6 @@ int main()
|
||||
sys_log(0, "WAITING_QUERY_COUNT %d", iCount);
|
||||
}
|
||||
|
||||
sys_log(0, "[SHUTDOWN] DB process exiting cleanly");
|
||||
|
||||
log_destroy();
|
||||
return 0;
|
||||
}
|
||||
@@ -346,7 +342,7 @@ int Start()
|
||||
|
||||
if (!CConfig::instance().GetValue("TABLE_POSTFIX", szBuf, 256))
|
||||
{
|
||||
sys_log(0, "CONFIG: TABLE_POSTFIX not configured, using default table names");
|
||||
sys_err("TABLE_POSTFIX not configured use default");
|
||||
szBuf[0] = '\0';
|
||||
}
|
||||
|
||||
|
||||
@@ -105,10 +105,7 @@ int CPeerBase::Recv()
|
||||
|
||||
if (bytes_read < 0)
|
||||
{
|
||||
if (errno == 0)
|
||||
return -2;
|
||||
|
||||
sys_err("socket_read failed: host=%s fd=%d errno=%d (%s)", m_host, m_fd, errno, strerror(errno));
|
||||
sys_err("socket_read failed %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
else if (bytes_read == 0)
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
#include "xmas_event.h"
|
||||
#include "banword.h"
|
||||
#include "target.h"
|
||||
#include "request_cooldown.h"
|
||||
#include "wedding.h"
|
||||
#include "mob_manager.h"
|
||||
#include "mining.h"
|
||||
@@ -5699,9 +5698,8 @@ void CHARACTER::ReqSafeboxLoad(const char* pszPassword)
|
||||
}
|
||||
|
||||
int iPulse = thecore_pulse();
|
||||
const int last_safebox_load_time = GetSafeboxLoadTime();
|
||||
|
||||
if (HasRecentRequestCooldown(last_safebox_load_time, iPulse, PASSES_PER_SEC(10)))
|
||||
if (iPulse - GetSafeboxLoadTime() < PASSES_PER_SEC(10))
|
||||
{
|
||||
ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<창고> 창고를 닫은지 10초 안에는 열 수 없습니다."));
|
||||
return;
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "unique_item.h"
|
||||
#include "threeway_war.h"
|
||||
#include "log.h"
|
||||
#include "request_cooldown.h"
|
||||
#include "common/VnumHelper.h"
|
||||
|
||||
extern int g_server_id;
|
||||
@@ -939,7 +938,6 @@ ACMD(do_mall_password)
|
||||
}
|
||||
|
||||
int iPulse = thecore_pulse();
|
||||
const int last_mall_load_time = ch->GetMallLoadTime();
|
||||
|
||||
if (ch->GetMall())
|
||||
{
|
||||
@@ -947,7 +945,7 @@ ACMD(do_mall_password)
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasRecentRequestCooldown(last_mall_load_time, iPulse, passes_per_sec * 10)) // 10초에 한번만 요청 가능
|
||||
if (iPulse - ch->GetMallLoadTime() < passes_per_sec * 10) // 10초에 한번만 요청 가능
|
||||
{
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<창고> 창고를 닫은지 10초 안에는 열 수 없습니다."));
|
||||
return;
|
||||
|
||||
225
src/game/db.cpp
225
src/game/db.cpp
@@ -1,5 +1,4 @@
|
||||
#include "stdafx.h"
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
#include "common/length.h"
|
||||
|
||||
@@ -18,211 +17,9 @@
|
||||
#include "login_data.h"
|
||||
#include "locale_service.h"
|
||||
#include "spam.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
extern std::string g_stBlockDate;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct AuthLoginData
|
||||
{
|
||||
char encryptedPassword[45 + 1] = {};
|
||||
char password[45 + 1] = {};
|
||||
char socialId[SOCIAL_ID_MAX_LEN + 1] = {};
|
||||
char status[ACCOUNT_STATUS_MAX_LEN + 1] = {};
|
||||
uint32_t accountId = 0;
|
||||
uint8_t notAvailable = 0;
|
||||
std::array<int, PREMIUM_MAX_NUM> premiumTimes = {};
|
||||
long long createTime = 0;
|
||||
};
|
||||
|
||||
bool IsChannelServiceLogin(const char* login)
|
||||
{
|
||||
return login && login[0] == '[';
|
||||
}
|
||||
|
||||
bool PrepareGameStmt(CStmt& stmt, const std::string& query)
|
||||
{
|
||||
CAsyncSQL* sql = DBManager::instance().GetDirectSQL();
|
||||
|
||||
if (!sql)
|
||||
{
|
||||
sys_err("game direct SQL handle is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Prepare(sql, query.c_str());
|
||||
}
|
||||
|
||||
bool LoadAuthLoginData(const char* login, const char* passwd, AuthLoginData& auth, bool& found)
|
||||
{
|
||||
CStmt stmt;
|
||||
const bool channelServiceLogin = IsChannelServiceLogin(login);
|
||||
const std::string query = channelServiceLogin
|
||||
? "SELECT ?, password, social_id, id, status, availDt - NOW() > 0,"
|
||||
"UNIX_TIMESTAMP(silver_expire),"
|
||||
"UNIX_TIMESTAMP(gold_expire),"
|
||||
"UNIX_TIMESTAMP(safebox_expire),"
|
||||
"UNIX_TIMESTAMP(autoloot_expire),"
|
||||
"UNIX_TIMESTAMP(fish_mind_expire),"
|
||||
"UNIX_TIMESTAMP(marriage_fast_expire),"
|
||||
"UNIX_TIMESTAMP(money_drop_rate_expire),"
|
||||
"UNIX_TIMESTAMP(create_time)"
|
||||
" FROM account WHERE login=?"
|
||||
: "SELECT PASSWORD(?), password, social_id, id, status, availDt - NOW() > 0,"
|
||||
"UNIX_TIMESTAMP(silver_expire),"
|
||||
"UNIX_TIMESTAMP(gold_expire),"
|
||||
"UNIX_TIMESTAMP(safebox_expire),"
|
||||
"UNIX_TIMESTAMP(autoloot_expire),"
|
||||
"UNIX_TIMESTAMP(fish_mind_expire),"
|
||||
"UNIX_TIMESTAMP(marriage_fast_expire),"
|
||||
"UNIX_TIMESTAMP(money_drop_rate_expire),"
|
||||
"UNIX_TIMESTAMP(create_time)"
|
||||
" FROM account WHERE login=?";
|
||||
|
||||
found = false;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(passwd))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(login)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_STRING, auth.encryptedPassword, sizeof(auth.encryptedPassword))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, auth.password, sizeof(auth.password))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, auth.socialId, sizeof(auth.socialId))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.accountId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, auth.status, sizeof(auth.status))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_TINY, &auth.notAvailable)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_EXP])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_ITEM])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_SAFEBOX])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_AUTOLOOT])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_FISH_MIND])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_MARRIAGE_FAST])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &auth.premiumTimes[PREMIUM_GOLD])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONGLONG, &auth.createTime))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FormatCreateDate(const AuthLoginData& auth, char* dst, size_t dstSize)
|
||||
{
|
||||
strlcpy(dst, "00000000", dstSize);
|
||||
|
||||
if (auth.createTime <= 0)
|
||||
return;
|
||||
|
||||
time_t createTime = static_cast<time_t>(auth.createTime);
|
||||
struct tm tmBuf;
|
||||
|
||||
if (!localtime_r(&createTime, &tmBuf))
|
||||
return;
|
||||
|
||||
strftime(dst, dstSize, "%Y%m%d", &tmBuf);
|
||||
}
|
||||
|
||||
void UpdateAccountLastPlay(uint32_t accountId)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = "UPDATE account SET last_play=NOW() WHERE id=?";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return;
|
||||
|
||||
if (!stmt.Execute())
|
||||
{
|
||||
sys_err("failed to update last_play for account %u", accountId);
|
||||
}
|
||||
}
|
||||
|
||||
void FinalizeAuthLogin(LPDESC d, const char* login, const char* passwd, const AuthLoginData& auth, const char* logPrefix)
|
||||
{
|
||||
char createDate[256] = "00000000";
|
||||
|
||||
if (LC_IsEurope() || test_server)
|
||||
{
|
||||
FormatCreateDate(auth, createDate, sizeof(createDate));
|
||||
sys_log(0, "Create_Time %lld %s", auth.createTime, createDate);
|
||||
sys_log(0, "Block Time %d ", strncmp(createDate, g_stBlockDate.c_str(), 8));
|
||||
}
|
||||
|
||||
if (strcmp(auth.encryptedPassword, auth.password))
|
||||
{
|
||||
RecordLoginFailure(d->GetHostName());
|
||||
LoginFailure(d, "WRONGPWD");
|
||||
sys_log(0, " WRONGPWD");
|
||||
return;
|
||||
}
|
||||
|
||||
if (auth.notAvailable)
|
||||
{
|
||||
LoginFailure(d, "NOTAVAIL");
|
||||
sys_log(0, " NOTAVAIL");
|
||||
return;
|
||||
}
|
||||
|
||||
if (DESC_MANAGER::instance().FindByLoginName(login))
|
||||
{
|
||||
LoginFailure(d, "ALREADY");
|
||||
sys_log(0, " ALREADY");
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(auth.status, "OK"))
|
||||
{
|
||||
LoginFailure(d, auth.status);
|
||||
sys_log(0, " STATUS: %s", auth.status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LC_IsEurope())
|
||||
{
|
||||
if (strncmp(createDate, g_stBlockDate.c_str(), 8) >= 0)
|
||||
{
|
||||
LoginFailure(d, "BLKLOGIN");
|
||||
sys_log(0, " BLKLOGIN");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateAccountLastPlay(auth.accountId);
|
||||
}
|
||||
|
||||
TAccountTable& r = d->GetAccountTable();
|
||||
int premiumTimes[PREMIUM_MAX_NUM];
|
||||
|
||||
r.id = auth.accountId;
|
||||
trim_and_lower(login, r.login, sizeof(r.login));
|
||||
strlcpy(r.passwd, passwd, sizeof(r.passwd));
|
||||
strlcpy(r.social_id, auth.socialId, sizeof(r.social_id));
|
||||
DESC_MANAGER::instance().ConnectAccount(r.login, d);
|
||||
ClearLoginFailure(d->GetHostName());
|
||||
|
||||
thecore_memcpy(premiumTimes, auth.premiumTimes.data(), sizeof(premiumTimes));
|
||||
DBManager::instance().LoginPrepare(d, premiumTimes);
|
||||
sys_log(0, "%s: SUCCESS %s", logPrefix, login);
|
||||
}
|
||||
}
|
||||
|
||||
DBManager::DBManager() : m_bIsConnect(false)
|
||||
{
|
||||
}
|
||||
@@ -435,28 +232,6 @@ void DBManager::LoginPrepare(LPDESC d, int * paiPremiumTimes)
|
||||
SendAuthLogin(d);
|
||||
}
|
||||
|
||||
void DBManager::AuthenticateLogin(LPDESC d, const char* login, const char* passwd)
|
||||
{
|
||||
AuthLoginData auth;
|
||||
bool found = false;
|
||||
|
||||
d->SetLogin(login);
|
||||
sys_log(0, "AUTH_LOGIN_DIRECT: START %u %p", d->GetLoginKey(), get_pointer(d));
|
||||
|
||||
if (IsChannelServiceLogin(login))
|
||||
sys_log(0, "ChannelServiceLogin [%s]", login);
|
||||
|
||||
if (!LoadAuthLoginData(login, passwd, auth, found) || !found)
|
||||
{
|
||||
sys_log(0, " NOID");
|
||||
RecordLoginFailure(d->GetHostName());
|
||||
LoginFailure(d, "NOID");
|
||||
return;
|
||||
}
|
||||
|
||||
FinalizeAuthLogin(d, login, passwd, auth, "AUTH_LOGIN_DIRECT");
|
||||
}
|
||||
|
||||
void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
|
||||
{
|
||||
CReturnQueryInfo * qi = (CReturnQueryInfo *) pMsg->pvUserData;
|
||||
|
||||
@@ -74,7 +74,6 @@ class DBManager : public singleton<DBManager>
|
||||
|
||||
std::unique_ptr<SQLMsg> DirectQuery(const char* c_pszFormat, ...);
|
||||
void ReturnQuery(int iType, DWORD dwIdent, void* pvData, const char * c_pszFormat, ...);
|
||||
CAsyncSQL* GetDirectSQL() { return &m_sql_direct; }
|
||||
|
||||
void Process();
|
||||
void AnalyzeReturnQuery(SQLMsg * pmsg);
|
||||
@@ -82,7 +81,6 @@ class DBManager : public singleton<DBManager>
|
||||
void SendMoneyLog(BYTE type, DWORD vnum, int gold);
|
||||
|
||||
void LoginPrepare(LPDESC d, int * paiPremiumTimes = NULL);
|
||||
void AuthenticateLogin(LPDESC d, const char* login, const char* passwd);
|
||||
void SendAuthLogin(LPDESC d);
|
||||
void SendLoginPing(const char * c_pszLogin);
|
||||
|
||||
|
||||
@@ -17,18 +17,6 @@
|
||||
#include "locale_service.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool IsPeerDisconnectWriteError(int error_code)
|
||||
{
|
||||
#ifdef OS_WINDOWS
|
||||
return error_code == WSAECONNRESET || error_code == WSAECONNABORTED || error_code == WSAENOTCONN;
|
||||
#else
|
||||
return error_code == EPIPE || error_code == ECONNRESET || error_code == ENOTCONN;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
extern int max_bytes_written;
|
||||
extern int current_bytes_written;
|
||||
extern int total_bytes_written;
|
||||
@@ -330,14 +318,7 @@ int DESC::ProcessOutput()
|
||||
}
|
||||
#endif
|
||||
|
||||
const int error_code = errno;
|
||||
BeginClosePhase();
|
||||
|
||||
if (IsPeerDisconnectWriteError(error_code))
|
||||
sys_log(0, "ProcessOutput: peer disconnected during send (host=%s fd=%d errno=%d %s)", GetHostName(), m_sock, error_code, strerror(error_code));
|
||||
else
|
||||
sys_err("ProcessOutput: send failed (host=%s fd=%d errno=%d %s)", GetHostName(), m_sock, error_code, strerror(error_code));
|
||||
|
||||
sys_err("ProcessOutput: send failed: %s (fd %d)", strerror(errno), m_sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -427,23 +408,8 @@ void DESC::LargePacket(const void * c_pvData, int iSize)
|
||||
Packet(c_pvData, iSize);
|
||||
}
|
||||
|
||||
void DESC::BeginClosePhase()
|
||||
{
|
||||
if (m_iPhase == PHASE_CLOSE)
|
||||
return;
|
||||
|
||||
m_iPhase = PHASE_CLOSE;
|
||||
m_pInputProcessor = &m_inputClose;
|
||||
}
|
||||
|
||||
void DESC::SetPhase(int _phase)
|
||||
{
|
||||
if (_phase == PHASE_CLOSE)
|
||||
{
|
||||
BeginClosePhase();
|
||||
return;
|
||||
}
|
||||
|
||||
m_iPhase = _phase;
|
||||
|
||||
TPacketGCPhase pack;
|
||||
|
||||
@@ -146,7 +146,6 @@ class DESC
|
||||
|
||||
protected:
|
||||
void Initialize();
|
||||
void BeginClosePhase();
|
||||
|
||||
protected:
|
||||
CInputProcessor * m_pInputProcessor;
|
||||
|
||||
@@ -15,30 +15,9 @@
|
||||
#include "locale_service.h"
|
||||
#include "guild_manager.h"
|
||||
#include "MarkManager.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool CountGuildsByName(const char* guild_name, unsigned long long* count)
|
||||
{
|
||||
char query[256];
|
||||
snprintf(query, sizeof(query), "SELECT COUNT(*) FROM guild%s WHERE name = ?", get_table_postfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(DBManager::instance().GetDirectSQL(), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, (void*) guild_name, GUILD_NAME_MAX_LEN))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONGLONG, count))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute() || !stmt.Fetch())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FGuildNameSender
|
||||
{
|
||||
@@ -102,19 +81,25 @@ DWORD CGuildManager::CreateGuild(TGuildCreateParameter& gcp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long long guild_count = 0;
|
||||
if (!CountGuildsByName(gcp.name, &guild_count))
|
||||
auto pmsg = DBManager::instance().DirectQuery("SELECT COUNT(*) FROM guild%s WHERE name = '%s'",
|
||||
get_table_postfix(), gcp.name);
|
||||
|
||||
if (pmsg->Get()->uiNumRows > 0)
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(pmsg->Get()->pSQLResult);
|
||||
|
||||
if (!(row[0] && row[0][0] == '0'))
|
||||
{
|
||||
gcp.master->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<길드> 이미 같은 이름의 길드가 있습니다."));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gcp.master->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<길드> 길드를 생성할 수 없습니다."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (guild_count != 0)
|
||||
{
|
||||
gcp.master->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<길드> 이미 같은 이름의 길드가 있습니다."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// new CGuild(gcp) queries guild tables and tell dbcache to notice other game servers.
|
||||
// other game server calls CGuildManager::LoadGuild to load guild.
|
||||
CGuild * pg = M2_NEW CGuild(gcp);
|
||||
@@ -971,3 +956,4 @@ void CGuildManager::ChangeMaster(DWORD dwGID)
|
||||
"SELECT 1");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -141,6 +141,13 @@ bool FN_IS_VALID_LOGIN_STRING(const char *str)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Login_IsInChannelService(const char* c_login)
|
||||
{
|
||||
if (c_login[0] == '[')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
CInputAuth::CInputAuth()
|
||||
{
|
||||
RegisterHandlers();
|
||||
@@ -226,7 +233,50 @@ void CInputAuth::Login(LPDESC d, const char * c_pData)
|
||||
|
||||
sys_log(0, "InputAuth::Login : key %u login %s", dwKey, login);
|
||||
|
||||
DBManager::instance().AuthenticateLogin(d, login, passwd);
|
||||
TPacketCGLogin3 * p = M2_NEW TPacketCGLogin3;
|
||||
thecore_memcpy(p, pinfo, sizeof(TPacketCGLogin3));
|
||||
|
||||
char szPasswd[PASSWD_MAX_LEN * 2 + 1];
|
||||
DBManager::instance().EscapeString(szPasswd, sizeof(szPasswd), passwd, strlen(passwd));
|
||||
|
||||
char szLogin[LOGIN_MAX_LEN * 2 + 1];
|
||||
DBManager::instance().EscapeString(szLogin, sizeof(szLogin), login, strlen(login));
|
||||
|
||||
// CHANNEL_SERVICE_LOGIN
|
||||
if (Login_IsInChannelService(szLogin))
|
||||
{
|
||||
sys_log(0, "ChannelServiceLogin [%s]", szLogin);
|
||||
|
||||
DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p,
|
||||
"SELECT '%s',password,social_id,id,status,availDt - NOW() > 0,"
|
||||
"UNIX_TIMESTAMP(silver_expire),"
|
||||
"UNIX_TIMESTAMP(gold_expire),"
|
||||
"UNIX_TIMESTAMP(safebox_expire),"
|
||||
"UNIX_TIMESTAMP(autoloot_expire),"
|
||||
"UNIX_TIMESTAMP(fish_mind_expire),"
|
||||
"UNIX_TIMESTAMP(marriage_fast_expire),"
|
||||
"UNIX_TIMESTAMP(money_drop_rate_expire),"
|
||||
"UNIX_TIMESTAMP(create_time)"
|
||||
" FROM account WHERE login='%s'",
|
||||
|
||||
szPasswd, szLogin);
|
||||
}
|
||||
// END_OF_CHANNEL_SERVICE_LOGIN
|
||||
else
|
||||
{
|
||||
DBManager::instance().ReturnQuery(QID_AUTH_LOGIN, dwKey, p,
|
||||
"SELECT PASSWORD('%s'),password,social_id,id,status,availDt - NOW() > 0,"
|
||||
"UNIX_TIMESTAMP(silver_expire),"
|
||||
"UNIX_TIMESTAMP(gold_expire),"
|
||||
"UNIX_TIMESTAMP(safebox_expire),"
|
||||
"UNIX_TIMESTAMP(autoloot_expire),"
|
||||
"UNIX_TIMESTAMP(fish_mind_expire),"
|
||||
"UNIX_TIMESTAMP(marriage_fast_expire),"
|
||||
"UNIX_TIMESTAMP(money_drop_rate_expire),"
|
||||
"UNIX_TIMESTAMP(create_time)"
|
||||
" FROM account WHERE login='%s'",
|
||||
szPasswd, szLogin);
|
||||
}
|
||||
}
|
||||
|
||||
int CInputAuth::Analyze(LPDESC d, uint16_t wHeader, const char * c_pData)
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "common/tables.h"
|
||||
#include "packet_structs.h"
|
||||
#include "quest_packet.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
|
||||
namespace quest
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr uint8_t QUEST_SEND_ISBEGIN_LOCAL = 1 << 0;
|
||||
constexpr uint8_t QUEST_SEND_TITLE_LOCAL = 1 << 1;
|
||||
constexpr uint8_t QUEST_SEND_CLOCK_NAME_LOCAL = 1 << 2;
|
||||
constexpr uint8_t QUEST_SEND_CLOCK_VALUE_LOCAL = 1 << 3;
|
||||
constexpr uint8_t QUEST_SEND_COUNTER_NAME_LOCAL = 1 << 4;
|
||||
constexpr uint8_t QUEST_SEND_COUNTER_VALUE_LOCAL = 1 << 5;
|
||||
constexpr uint8_t QUEST_SEND_ICON_FILE_LOCAL = 1 << 6;
|
||||
|
||||
void AppendBytes(std::vector<uint8_t>& packet, const void* data, size_t size)
|
||||
{
|
||||
const auto* bytes = static_cast<const uint8_t*>(data);
|
||||
packet.insert(packet.end(), bytes, bytes + size);
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
void AppendFixedString(std::vector<uint8_t>& packet, const std::string& value)
|
||||
{
|
||||
std::array<char, N> field {};
|
||||
const size_t copy_size = std::min(value.size(), N - 1);
|
||||
if (copy_size > 0)
|
||||
std::memcpy(field.data(), value.data(), copy_size);
|
||||
AppendBytes(packet, field.data(), field.size());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BuildQuestInfoPacket(const QuestInfoPacketData& data)
|
||||
{
|
||||
packet_quest_info header {};
|
||||
header.header = GC::QUEST_INFO;
|
||||
header.length = sizeof(header);
|
||||
header.index = data.quest_index;
|
||||
header.flag = data.send_flags;
|
||||
|
||||
std::vector<uint8_t> packet;
|
||||
packet.reserve(sizeof(header) + 128);
|
||||
AppendBytes(packet, &header, sizeof(header));
|
||||
|
||||
if (data.send_flags & QUEST_SEND_ISBEGIN_LOCAL)
|
||||
{
|
||||
const uint8_t is_begin = data.is_begin ? 1 : 0;
|
||||
AppendBytes(packet, &is_begin, sizeof(is_begin));
|
||||
}
|
||||
|
||||
if (data.send_flags & QUEST_SEND_TITLE_LOCAL)
|
||||
AppendFixedString<30 + 1>(packet, data.title);
|
||||
|
||||
if (data.send_flags & QUEST_SEND_CLOCK_NAME_LOCAL)
|
||||
AppendFixedString<16 + 1>(packet, data.clock_name);
|
||||
|
||||
if (data.send_flags & QUEST_SEND_CLOCK_VALUE_LOCAL)
|
||||
AppendBytes(packet, &data.clock_value, sizeof(data.clock_value));
|
||||
|
||||
if (data.send_flags & QUEST_SEND_COUNTER_NAME_LOCAL)
|
||||
AppendFixedString<16 + 1>(packet, data.counter_name);
|
||||
|
||||
if (data.send_flags & QUEST_SEND_COUNTER_VALUE_LOCAL)
|
||||
AppendBytes(packet, &data.counter_value, sizeof(data.counter_value));
|
||||
|
||||
if (data.send_flags & QUEST_SEND_ICON_FILE_LOCAL)
|
||||
AppendFixedString<24 + 1>(packet, data.icon_file);
|
||||
|
||||
auto* final_header = reinterpret_cast<packet_quest_info*>(packet.data());
|
||||
final_header->length = static_cast<uint16_t>(packet.size());
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace quest
|
||||
{
|
||||
struct QuestInfoPacketData
|
||||
{
|
||||
uint16_t quest_index = 0;
|
||||
uint8_t send_flags = 0;
|
||||
bool is_begin = false;
|
||||
std::string title;
|
||||
std::string clock_name;
|
||||
int clock_value = 0;
|
||||
std::string counter_name;
|
||||
int counter_value = 0;
|
||||
std::string icon_file;
|
||||
};
|
||||
|
||||
std::vector<uint8_t> BuildQuestInfoPacket(const QuestInfoPacketData& data);
|
||||
}
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "utils.h"
|
||||
#include "unique_item.h"
|
||||
#include "mob_manager.h"
|
||||
#include "libsql/Statement.h"
|
||||
#include <cctype>
|
||||
|
||||
#undef sys_err
|
||||
@@ -34,48 +33,6 @@ const int ITEM_BROKEN_METIN_VNUM = 28960;
|
||||
|
||||
namespace quest
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool CountPlayersByName(const char* player_name, unsigned long long* count)
|
||||
{
|
||||
char query[256];
|
||||
snprintf(query, sizeof(query), "SELECT COUNT(*) FROM player%s WHERE name = ?", get_table_postfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(DBManager::instance().GetDirectSQL(), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, (void*) player_name, CHARACTER_NAME_MAX_LEN))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONGLONG, count))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute() || !stmt.Fetch())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdatePlayerName(DWORD player_id, const char* player_name)
|
||||
{
|
||||
char query[256];
|
||||
snprintf(query, sizeof(query), "UPDATE player%s SET name = ?, change_name = 0 WHERE id = ?", get_table_postfix());
|
||||
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(DBManager::instance().GetDirectSQL(), query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, (void*) player_name, CHARACTER_NAME_MAX_LEN))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &player_id))
|
||||
return false;
|
||||
|
||||
return stmt.Execute() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// "pc" Lua functions
|
||||
//
|
||||
@@ -2157,18 +2114,23 @@ teleport_area:
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned long long count = 0;
|
||||
if (!CountPlayersByName(szName, &count))
|
||||
{
|
||||
lua_pushnumber(L, 5);
|
||||
return 1;
|
||||
}
|
||||
char szQuery[1024];
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT COUNT(*) FROM player%s WHERE name='%s'", get_table_postfix(), szName);
|
||||
auto pmsg = DBManager::instance().DirectQuery(szQuery);
|
||||
|
||||
// 이미 해당 이름을 가진 캐릭터가 있음
|
||||
if (count != 0)
|
||||
if ( pmsg->Get()->uiNumRows > 0 )
|
||||
{
|
||||
lua_pushnumber(L, 3);
|
||||
return 1;
|
||||
MYSQL_ROW row = mysql_fetch_row(pmsg->Get()->pSQLResult);
|
||||
|
||||
int count = 0;
|
||||
str_to_number(count, row[0]);
|
||||
|
||||
// 이미 해당 이름을 가진 캐릭터가 있음
|
||||
if ( count != 0 )
|
||||
{
|
||||
lua_pushnumber(L, 3);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD pid = ch->GetPlayerID();
|
||||
@@ -2182,11 +2144,8 @@ teleport_area:
|
||||
/* change_name_log */
|
||||
LogManager::instance().ChangeNameLog(pid, ch->GetName(), szName, ch->GetDesc()->GetHostName());
|
||||
|
||||
if (!UpdatePlayerName(pid, szName))
|
||||
{
|
||||
lua_pushnumber(L, 5);
|
||||
return 1;
|
||||
}
|
||||
snprintf(szQuery, sizeof(szQuery), "UPDATE player%s SET name='%s' WHERE id=%u", get_table_postfix(), szName, pid);
|
||||
DBManager::instance().DirectQuery(szQuery);
|
||||
|
||||
ch->SetNewName(szName);
|
||||
lua_pushnumber(L, 4);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "stdafx.h"
|
||||
#include "constants.h"
|
||||
#include "quest_packet.h"
|
||||
#include "questmanager.h"
|
||||
#include "packet_structs.h"
|
||||
#include "buffer_manager.h"
|
||||
#include "char.h"
|
||||
#include "desc_client.h"
|
||||
#include "questevent.h"
|
||||
@@ -234,34 +234,72 @@ namespace quest
|
||||
assert(m_iSendToClient);
|
||||
assert(m_RunningQuestState);
|
||||
|
||||
QuestInfoPacketData packet_data {};
|
||||
packet_data.quest_index = static_cast<uint16_t>(m_RunningQuestState->iIndex);
|
||||
packet_data.send_flags = static_cast<uint8_t>(m_iSendToClient);
|
||||
packet_data.is_begin = m_RunningQuestState->bStart;
|
||||
packet_data.title = m_RunningQuestState->_title;
|
||||
packet_data.clock_name = m_RunningQuestState->_clock_name;
|
||||
packet_data.clock_value = m_RunningQuestState->_clock_value;
|
||||
packet_data.counter_name = m_RunningQuestState->_counter_name;
|
||||
packet_data.counter_value = m_RunningQuestState->_counter_value;
|
||||
packet_data.icon_file = m_RunningQuestState->_icon_file;
|
||||
packet_quest_info qi;
|
||||
|
||||
qi.header = GC::QUEST_INFO;
|
||||
qi.length = sizeof(struct packet_quest_info);
|
||||
qi.index = m_RunningQuestState->iIndex;
|
||||
qi.flag = m_iSendToClient;
|
||||
|
||||
TEMP_BUFFER buf;
|
||||
buf.write(&qi, sizeof(qi));
|
||||
|
||||
if (m_iSendToClient & QUEST_SEND_ISBEGIN)
|
||||
sys_log(1, "QUEST BeginFlag %d", static_cast<int>(m_RunningQuestState->bStart ? 1 : 0));
|
||||
if (m_iSendToClient & QUEST_SEND_TITLE)
|
||||
sys_log(1, "QUEST Title %s", m_RunningQuestState->_title.c_str());
|
||||
if (m_iSendToClient & QUEST_SEND_CLOCK_NAME)
|
||||
sys_log(1, "QUEST Clock Name %s", m_RunningQuestState->_clock_name.c_str());
|
||||
if (m_iSendToClient & QUEST_SEND_CLOCK_VALUE)
|
||||
sys_log(1, "QUEST Clock Value %d", m_RunningQuestState->_clock_value);
|
||||
if (m_iSendToClient & QUEST_SEND_COUNTER_NAME)
|
||||
sys_log(1, "QUEST Counter Name %s", m_RunningQuestState->_counter_name.c_str());
|
||||
if (m_iSendToClient & QUEST_SEND_COUNTER_VALUE)
|
||||
sys_log(1, "QUEST Counter Value %d", m_RunningQuestState->_counter_value);
|
||||
if (m_iSendToClient & QUEST_SEND_ICON_FILE)
|
||||
sys_log(1, "QUEST Icon File %s", m_RunningQuestState->_icon_file.c_str());
|
||||
{
|
||||
BYTE temp = m_RunningQuestState->bStart?1:0;
|
||||
buf.write(&temp,1);
|
||||
qi.length+=1;
|
||||
|
||||
auto packet = BuildQuestInfoPacket(packet_data);
|
||||
CQuestManager::instance().GetCurrentCharacterPtr()->GetDesc()->Packet(packet.data(), packet.size());
|
||||
sys_log(1, "QUEST BeginFlag %d", (int)temp);
|
||||
}
|
||||
if (m_iSendToClient & QUEST_SEND_TITLE)
|
||||
{
|
||||
m_RunningQuestState->_title.reserve(30+1);
|
||||
buf.write(m_RunningQuestState->_title.c_str(), 30+1);
|
||||
qi.length+=30+1;
|
||||
|
||||
sys_log(1, "QUEST Title %s", m_RunningQuestState->_title.c_str());
|
||||
}
|
||||
if (m_iSendToClient & QUEST_SEND_CLOCK_NAME)
|
||||
{
|
||||
m_RunningQuestState->_clock_name.reserve(16+1);
|
||||
buf.write(m_RunningQuestState->_clock_name.c_str(), 16+1);
|
||||
qi.length+=16+1;
|
||||
|
||||
sys_log(1, "QUEST Clock Name %s", m_RunningQuestState->_clock_name.c_str());
|
||||
}
|
||||
if (m_iSendToClient & QUEST_SEND_CLOCK_VALUE)
|
||||
{
|
||||
buf.write(&m_RunningQuestState->_clock_value, sizeof(int));
|
||||
qi.length+=4;
|
||||
|
||||
sys_log(1, "QUEST Clock Value %d", m_RunningQuestState->_clock_value);
|
||||
}
|
||||
if (m_iSendToClient & QUEST_SEND_COUNTER_NAME)
|
||||
{
|
||||
m_RunningQuestState->_counter_name.reserve(16+1);
|
||||
buf.write(m_RunningQuestState->_counter_name.c_str(), 16+1);
|
||||
qi.length+=16+1;
|
||||
|
||||
sys_log(1, "QUEST Counter Name %s", m_RunningQuestState->_counter_name.c_str());
|
||||
}
|
||||
if (m_iSendToClient & QUEST_SEND_COUNTER_VALUE)
|
||||
{
|
||||
buf.write(&m_RunningQuestState->_counter_value, sizeof(int));
|
||||
qi.length+=4;
|
||||
|
||||
sys_log(1, "QUEST Counter Value %d", m_RunningQuestState->_counter_value);
|
||||
}
|
||||
if (m_iSendToClient & QUEST_SEND_ICON_FILE)
|
||||
{
|
||||
m_RunningQuestState->_icon_file.reserve(24+1);
|
||||
buf.write(m_RunningQuestState->_icon_file.c_str(), 24+1);
|
||||
qi.length+=24+1;
|
||||
|
||||
sys_log(1, "QUEST Icon File %s", m_RunningQuestState->_icon_file.c_str());
|
||||
}
|
||||
|
||||
CQuestManager::instance().GetCurrentCharacterPtr()->GetDesc()->Packet(buf.read_peek(),buf.size());
|
||||
|
||||
m_iSendToClient = 0;
|
||||
|
||||
@@ -684,3 +722,4 @@ namespace quest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
inline bool HasRecentRequestCooldown(int last_request_pulse, int current_pulse, int cooldown_pulses)
|
||||
{
|
||||
return last_request_pulse > 0 && current_pulse - last_request_pulse < cooldown_pulses;
|
||||
}
|
||||
@@ -46,14 +46,10 @@ bool CAsyncSQL::QueryLocaleSet()
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* current_charset = mysql_character_set_name(&m_hDB);
|
||||
if (current_charset && m_stLocale == current_charset)
|
||||
return true;
|
||||
|
||||
if (mysql_set_character_set(&m_hDB, m_stLocale.c_str()))
|
||||
{
|
||||
sys_err("cannot set locale %s by 'mysql_set_character_set' (current=%s), errno %u %s",
|
||||
m_stLocale.c_str(), current_charset ? current_charset : "<unknown>", mysql_errno(&m_hDB), mysql_error(&m_hDB));
|
||||
sys_err("cannot set locale %s by 'mysql_set_character_set', errno %u %s",
|
||||
m_stLocale.c_str(), mysql_errno(&m_hDB), mysql_error(&m_hDB));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -142,16 +138,6 @@ bool CAsyncSQL::Setup(const char* c_pszHost, const char* c_pszUser, const char*
|
||||
|
||||
void CAsyncSQL::Quit()
|
||||
{
|
||||
sys_log(0,
|
||||
"[SHUTDOWN] AsyncSQL quit begin db=%s host=%s worker=%s pending=%u copied=%u results=%u connected=%d",
|
||||
m_stDB.c_str(),
|
||||
m_stHost.c_str(),
|
||||
HasWorkerThread() ? "yes" : "no",
|
||||
CountQuery(),
|
||||
CountCopiedQueryQueue(),
|
||||
CountResult(),
|
||||
IsConnected() ? 1 : 0);
|
||||
|
||||
m_bEnd.store(true, std::memory_order_release);
|
||||
m_cvQuery.notify_all();
|
||||
|
||||
@@ -160,14 +146,6 @@ void CAsyncSQL::Quit()
|
||||
m_thread->join();
|
||||
m_thread.reset();
|
||||
}
|
||||
|
||||
sys_log(0,
|
||||
"[SHUTDOWN] AsyncSQL quit done db=%s host=%s pending=%u copied=%u results=%u",
|
||||
m_stDB.c_str(),
|
||||
m_stHost.c_str(),
|
||||
CountQuery(),
|
||||
CountCopiedQueryQueue(),
|
||||
CountResult());
|
||||
}
|
||||
|
||||
std::unique_ptr<SQLMsg> CAsyncSQL::DirectQuery(const char* c_pszQuery)
|
||||
@@ -345,17 +323,6 @@ DWORD CAsyncSQL::CountResult()
|
||||
return static_cast<DWORD>(m_queue_result.size());
|
||||
}
|
||||
|
||||
DWORD CAsyncSQL::CountCopiedQueryQueue()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mtxQuery);
|
||||
return static_cast<DWORD>(m_queue_query_copy.size());
|
||||
}
|
||||
|
||||
bool CAsyncSQL::HasWorkerThread() const
|
||||
{
|
||||
return m_thread && m_thread->joinable();
|
||||
}
|
||||
|
||||
// Modern profiler using chrono
|
||||
class cProfiler
|
||||
{
|
||||
@@ -561,15 +528,6 @@ void CAsyncSQL::ChildLoop()
|
||||
m_iQueryFinished.fetch_add(1, std::memory_order_acq_rel);
|
||||
}
|
||||
}
|
||||
|
||||
sys_log(0,
|
||||
"[SHUTDOWN] AsyncSQL worker exit db=%s host=%s pending=%u copied=%u results=%u finished=%d",
|
||||
m_stDB.c_str(),
|
||||
m_stHost.c_str(),
|
||||
CountQuery(),
|
||||
CountCopiedQueryQueue(),
|
||||
CountResult(),
|
||||
CountQueryFinished());
|
||||
}
|
||||
|
||||
int CAsyncSQL::CountQueryFinished() const
|
||||
@@ -618,7 +576,5 @@ size_t CAsyncSQL::EscapeString(char* dst, size_t dstSize, const char* src, size_
|
||||
void CAsyncSQL2::SetLocale(const std::string& stLocale)
|
||||
{
|
||||
m_stLocale = stLocale;
|
||||
|
||||
if (!HasWorkerThread())
|
||||
QueryLocaleSet();
|
||||
QueryLocaleSet();
|
||||
}
|
||||
|
||||
@@ -175,8 +175,6 @@ class CAsyncSQL
|
||||
|
||||
DWORD CountQuery();
|
||||
DWORD CountResult();
|
||||
DWORD CountCopiedQueryQueue();
|
||||
bool HasWorkerThread() const;
|
||||
|
||||
void PushResult(std::unique_ptr<SQLMsg> p);
|
||||
bool PopResult(std::unique_ptr<SQLMsg>& p);
|
||||
|
||||
@@ -9,6 +9,7 @@ CStmt::CStmt()
|
||||
m_uiParamCount = 0;
|
||||
m_uiResultCount = 0;
|
||||
iRows = 0;
|
||||
m_puiParamLen = NULL;
|
||||
}
|
||||
|
||||
CStmt::~CStmt()
|
||||
@@ -24,13 +25,11 @@ void CStmt::Destroy()
|
||||
m_pkStmt = NULL;
|
||||
}
|
||||
|
||||
m_vec_param.clear();
|
||||
m_vecParamLen.clear();
|
||||
m_vec_result.clear();
|
||||
m_vecResultLen.clear();
|
||||
m_uiParamCount = 0;
|
||||
m_uiResultCount = 0;
|
||||
iRows = 0;
|
||||
if (m_puiParamLen)
|
||||
{
|
||||
free(m_puiParamLen);
|
||||
m_puiParamLen = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CStmt::Error(const char * c_pszMsg)
|
||||
@@ -40,7 +39,6 @@ void CStmt::Error(const char * c_pszMsg)
|
||||
|
||||
bool CStmt::Prepare(CAsyncSQL * sql, const char * c_pszQuery)
|
||||
{
|
||||
Destroy();
|
||||
m_pkStmt = mysql_stmt_init(sql->GetSQLHandle());
|
||||
m_stQuery = c_pszQuery;
|
||||
|
||||
@@ -50,20 +48,27 @@ bool CStmt::Prepare(CAsyncSQL * sql, const char * c_pszQuery)
|
||||
return false;
|
||||
}
|
||||
|
||||
const unsigned int param_count = mysql_stmt_param_count(m_pkStmt);
|
||||
if (param_count)
|
||||
int iParamCount = 0;
|
||||
|
||||
for (unsigned int i = 0; i < m_stQuery.length(); ++i)
|
||||
if (c_pszQuery[i] == '?')
|
||||
++iParamCount;
|
||||
|
||||
if (iParamCount)
|
||||
{
|
||||
m_vec_param.resize(param_count);
|
||||
memset(&m_vec_param[0], 0, sizeof(MYSQL_BIND) * param_count);
|
||||
m_vecParamLen.resize(param_count, 0);
|
||||
m_vec_param.resize(iParamCount);
|
||||
memset(&m_vec_param[0], 0, sizeof(MYSQL_BIND) * iParamCount);
|
||||
|
||||
m_puiParamLen = (long unsigned int *) calloc(iParamCount, sizeof(long unsigned int));
|
||||
}
|
||||
|
||||
const unsigned int result_count = mysql_stmt_field_count(m_pkStmt);
|
||||
if (result_count)
|
||||
m_vec_result.resize(48);
|
||||
memset(&m_vec_result[0], 0, sizeof(MYSQL_BIND) * 48);
|
||||
|
||||
if (mysql_stmt_bind_result(m_pkStmt, &m_vec_result[0]))
|
||||
{
|
||||
m_vec_result.resize(result_count);
|
||||
memset(&m_vec_result[0], 0, sizeof(MYSQL_BIND) * result_count);
|
||||
m_vecResultLen.resize(result_count, 0);
|
||||
Error("mysql_stmt_bind_result");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -82,8 +87,16 @@ bool CStmt::BindParam(enum_field_types type, void * p, int iMaxLen)
|
||||
bind->buffer_type = type;
|
||||
bind->buffer = (void *) p;
|
||||
bind->buffer_length = iMaxLen;
|
||||
bind->length = m_vecParamLen.empty() ? NULL : &m_vecParamLen[m_uiParamCount];
|
||||
++m_uiParamCount;
|
||||
bind->length = m_puiParamLen + m_uiParamCount;
|
||||
|
||||
if (++m_uiParamCount == m_vec_param.size())
|
||||
{
|
||||
if (mysql_stmt_bind_param(m_pkStmt, &m_vec_param[0]))
|
||||
{
|
||||
Error("mysql_stmt_bind_param");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -101,7 +114,6 @@ bool CStmt::BindResult(enum_field_types type, void * p, int iMaxLen)
|
||||
bind->buffer_type = type;
|
||||
bind->buffer = (void *) p;
|
||||
bind->buffer_length = iMaxLen;
|
||||
bind->length = m_vecResultLen.empty() ? NULL : &m_vecResultLen[m_uiResultCount - 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -119,18 +131,9 @@ int CStmt::Execute()
|
||||
|
||||
if (bind->buffer_type == MYSQL_TYPE_STRING)
|
||||
{
|
||||
m_vecParamLen[i] = strlen((const char *) bind->buffer);
|
||||
*(m_puiParamLen + i) = strlen((const char *) bind->buffer);
|
||||
sys_log(0, "param %d len %d buf %s", i, *m_puiParamLen, (const char *) bind->buffer);
|
||||
}
|
||||
else if (bind->buffer_type == MYSQL_TYPE_BLOB)
|
||||
{
|
||||
m_vecParamLen[i] = bind->buffer_length;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_vec_param.empty() && mysql_stmt_bind_param(m_pkStmt, &m_vec_param[0]))
|
||||
{
|
||||
Error("mysql_stmt_bind_param");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mysql_stmt_execute(m_pkStmt))
|
||||
@@ -139,31 +142,13 @@ int CStmt::Execute()
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!m_vec_result.empty())
|
||||
if (mysql_stmt_store_result(m_pkStmt))
|
||||
{
|
||||
if (m_uiResultCount != m_vec_result.size())
|
||||
{
|
||||
sys_log(0, "Result count mismatch %u, expected %zu query: %s", m_uiResultCount, m_vec_result.size(), m_stQuery.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mysql_stmt_bind_result(m_pkStmt, &m_vec_result[0]))
|
||||
{
|
||||
Error("mysql_stmt_bind_result");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mysql_stmt_store_result(m_pkStmt))
|
||||
{
|
||||
Error("mysql_stmt_store_result");
|
||||
return 0;
|
||||
}
|
||||
|
||||
iRows = mysql_stmt_num_rows(m_pkStmt);
|
||||
return true;
|
||||
Error("mysql_store_result");
|
||||
return 0;
|
||||
}
|
||||
|
||||
iRows = 0;
|
||||
iRows = mysql_stmt_num_rows(m_pkStmt);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -172,18 +157,3 @@ bool CStmt::Fetch()
|
||||
return !mysql_stmt_fetch(m_pkStmt);
|
||||
}
|
||||
|
||||
unsigned long long CStmt::GetInsertId() const
|
||||
{
|
||||
if (!m_pkStmt)
|
||||
return 0;
|
||||
|
||||
return mysql_stmt_insert_id(m_pkStmt);
|
||||
}
|
||||
|
||||
unsigned long long CStmt::GetAffectedRows() const
|
||||
{
|
||||
if (!m_pkStmt)
|
||||
return 0;
|
||||
|
||||
return mysql_stmt_affected_rows(m_pkStmt);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ class CStmt
|
||||
bool BindResult(enum_field_types type, void * p, int iMaxLen=0);
|
||||
int Execute();
|
||||
bool Fetch();
|
||||
unsigned long long GetInsertId() const;
|
||||
unsigned long long GetAffectedRows() const;
|
||||
|
||||
void Error(const char * c_pszMsg);
|
||||
|
||||
@@ -34,11 +32,10 @@ class CStmt
|
||||
|
||||
std::vector<MYSQL_BIND> m_vec_param;
|
||||
unsigned int m_uiParamCount;
|
||||
std::vector<unsigned long> m_vecParamLen;
|
||||
long unsigned int * m_puiParamLen;
|
||||
|
||||
std::vector<MYSQL_BIND> m_vec_result;
|
||||
unsigned int m_uiResultCount;
|
||||
std::vector<unsigned long> m_vecResultLen;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,12 +18,12 @@ static int pid_init(void)
|
||||
{
|
||||
fprintf(fp, "%d", getpid());
|
||||
fclose(fp);
|
||||
sys_log(0, "Start of pid: %d", getpid());
|
||||
sys_err("\nStart of pid: %d\n", getpid());
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("pid_init(): could not open file for writing. (filename: ./pid)");
|
||||
sys_err("Error writing pid file");
|
||||
sys_err("\nError writing pid file\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -36,7 +36,7 @@ static void pid_deinit(void)
|
||||
return;
|
||||
#else
|
||||
remove("./pid");
|
||||
sys_log(0, "End of pid");
|
||||
sys_err("\nEnd of pid\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -45,10 +45,7 @@ int socket_read(socket_t desc, char* read_point, size_t space_left)
|
||||
return ret;
|
||||
|
||||
if (ret == 0) // 정상적으로 접속 끊김
|
||||
{
|
||||
errno = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef EINTR /* Interrupted system call - various platforms */
|
||||
if (errno == EINTR)
|
||||
|
||||
@@ -5,7 +5,6 @@ endif()
|
||||
add_executable(metin_smoke_tests
|
||||
smoke_auth.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/SecureCipher.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/quest_packet.cpp
|
||||
)
|
||||
|
||||
add_executable(metin_login_smoke
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
@@ -24,11 +23,6 @@ namespace
|
||||
constexpr size_t LOGIN_MAX_LEN_LOCAL = 30;
|
||||
constexpr size_t PASSWD_MAX_LEN_LOCAL = 16;
|
||||
constexpr size_t ACCOUNT_STATUS_MAX_LEN_LOCAL = 8;
|
||||
constexpr size_t PLAYER_PER_ACCOUNT_LOCAL = 4;
|
||||
constexpr size_t CHARACTER_NAME_MAX_LEN_LOCAL = 64;
|
||||
constexpr size_t GUILD_NAME_MAX_LEN_LOCAL = 12;
|
||||
constexpr size_t SAFEBOX_PASSWORD_MAX_LEN_LOCAL = 6;
|
||||
constexpr uint8_t CHAT_TYPE_TALKING_LOCAL = 0;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct PacketGCPhase
|
||||
@@ -79,46 +73,6 @@ struct PacketCGLogin2
|
||||
uint32_t login_key;
|
||||
};
|
||||
|
||||
struct PacketCGChat
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t type;
|
||||
};
|
||||
|
||||
struct SimplePlayerLocal
|
||||
{
|
||||
uint32_t id;
|
||||
char name[CHARACTER_NAME_MAX_LEN_LOCAL + 1];
|
||||
uint8_t job;
|
||||
uint8_t level;
|
||||
uint32_t play_minutes;
|
||||
uint8_t st;
|
||||
uint8_t ht;
|
||||
uint8_t dx;
|
||||
uint8_t iq;
|
||||
uint16_t main_part;
|
||||
uint8_t change_name;
|
||||
uint16_t hair_part;
|
||||
uint8_t dummy[4];
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint32_t addr;
|
||||
uint16_t port;
|
||||
uint8_t skill_group;
|
||||
};
|
||||
|
||||
struct PacketGCLoginSuccess4
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
SimplePlayerLocal players[PLAYER_PER_ACCOUNT_LOCAL];
|
||||
uint32_t guild_id[PLAYER_PER_ACCOUNT_LOCAL];
|
||||
char guild_name[PLAYER_PER_ACCOUNT_LOCAL][GUILD_NAME_MAX_LEN_LOCAL + 1];
|
||||
uint32_t handle;
|
||||
uint32_t random_key;
|
||||
};
|
||||
|
||||
struct PacketGCAuthSuccess
|
||||
{
|
||||
uint16_t header;
|
||||
@@ -140,270 +94,14 @@ struct PacketGCEmpire
|
||||
uint16_t length;
|
||||
uint8_t empire;
|
||||
};
|
||||
|
||||
struct PacketCGPlayerCreate
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t index;
|
||||
char name[CHARACTER_NAME_MAX_LEN_LOCAL + 1];
|
||||
uint16_t job;
|
||||
uint8_t shape;
|
||||
uint8_t con;
|
||||
uint8_t intel;
|
||||
uint8_t str;
|
||||
uint8_t dex;
|
||||
};
|
||||
|
||||
struct PacketCGPlayerDelete
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t index;
|
||||
char private_code[8];
|
||||
};
|
||||
|
||||
struct PacketGCPlayerCreateSuccess
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t account_character_index;
|
||||
SimplePlayerLocal player;
|
||||
};
|
||||
|
||||
struct PacketGCCreateFailure
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t type;
|
||||
};
|
||||
|
||||
struct PacketGCPlayerDeleteSuccess
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t account_index;
|
||||
};
|
||||
|
||||
struct PacketCGPlayerSelect
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t index;
|
||||
};
|
||||
|
||||
struct PacketCGEnterGame
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
};
|
||||
|
||||
struct PacketCGClientVersion
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
char filename[32 + 1];
|
||||
char timestamp[32 + 1];
|
||||
};
|
||||
|
||||
struct PacketGCMainCharacter
|
||||
{
|
||||
enum
|
||||
{
|
||||
MUSIC_NAME_LEN = 24
|
||||
};
|
||||
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint32_t vid;
|
||||
uint16_t race_num;
|
||||
char name[CHARACTER_NAME_MAX_LEN_LOCAL + 1];
|
||||
char bgm_name[MUSIC_NAME_LEN + 1];
|
||||
float bgm_vol;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t z;
|
||||
uint8_t empire;
|
||||
uint8_t skill_group;
|
||||
};
|
||||
|
||||
struct PacketGCTime
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
time_t time;
|
||||
};
|
||||
|
||||
struct PacketGCChannel
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t channel;
|
||||
};
|
||||
|
||||
struct PacketGCSafeboxSize
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
struct PacketGCSafeboxWrongPassword
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct SmokeOptions
|
||||
{
|
||||
bool json = false;
|
||||
std::string create_character_name;
|
||||
std::string client_version = "1215955205";
|
||||
std::string expect_auth_failure;
|
||||
std::string expect_channel_failure;
|
||||
std::string delete_private_code;
|
||||
std::string mall_password;
|
||||
};
|
||||
|
||||
struct SmokeResult
|
||||
{
|
||||
bool ok = false;
|
||||
std::string result = "unexpected_failure";
|
||||
std::string stage = "startup";
|
||||
std::string error;
|
||||
std::string failure_status;
|
||||
uint32_t login_key = 0;
|
||||
int empire = -1;
|
||||
int character_index = -1;
|
||||
std::string character_name;
|
||||
bool character_created = false;
|
||||
bool character_deleted = false;
|
||||
int game_channel = -1;
|
||||
bool mall_opened = false;
|
||||
int mall_size = -1;
|
||||
int deleted_character_index = -1;
|
||||
int64_t auth_handshake_ms = -1;
|
||||
int64_t auth_login_ms = -1;
|
||||
int64_t channel_handshake_ms = -1;
|
||||
int64_t channel_login_ms = -1;
|
||||
int64_t character_create_ms = -1;
|
||||
int64_t character_delete_ms = -1;
|
||||
int64_t character_select_ms = -1;
|
||||
int64_t entergame_ms = -1;
|
||||
int64_t mall_open_ms = -1;
|
||||
std::vector<std::string> events;
|
||||
};
|
||||
|
||||
struct AuthResponse
|
||||
{
|
||||
bool success = false;
|
||||
uint32_t login_key = 0;
|
||||
std::string failure_status;
|
||||
};
|
||||
|
||||
struct ChannelLoginResponse
|
||||
{
|
||||
bool success = false;
|
||||
int empire = -1;
|
||||
std::string failure_status;
|
||||
PacketGCLoginSuccess4 login_success {};
|
||||
};
|
||||
|
||||
void Expect(bool condition, std::string_view message)
|
||||
{
|
||||
if (!condition)
|
||||
throw std::runtime_error(std::string(message));
|
||||
}
|
||||
|
||||
std::string JsonEscape(std::string_view input)
|
||||
{
|
||||
std::string escaped;
|
||||
escaped.reserve(input.size() + 8);
|
||||
|
||||
for (const char c : input)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '\\':
|
||||
escaped += "\\\\";
|
||||
break;
|
||||
case '"':
|
||||
escaped += "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
escaped += "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
escaped += "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
escaped += "\\t";
|
||||
break;
|
||||
default:
|
||||
escaped.push_back(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return escaped;
|
||||
}
|
||||
|
||||
void EmitEvent(SmokeResult& result, bool json, const std::string& line)
|
||||
{
|
||||
result.events.push_back(line);
|
||||
if (!json)
|
||||
std::cout << line << "\n";
|
||||
}
|
||||
|
||||
int64_t ElapsedMs(std::chrono::steady_clock::time_point started_at)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - started_at)
|
||||
.count();
|
||||
}
|
||||
|
||||
void PrintJson(const SmokeResult& result)
|
||||
{
|
||||
std::cout
|
||||
<< "{"
|
||||
<< "\"ok\":" << (result.ok ? "true" : "false")
|
||||
<< ",\"result\":\"" << JsonEscape(result.result) << "\""
|
||||
<< ",\"stage\":\"" << JsonEscape(result.stage) << "\""
|
||||
<< ",\"error\":\"" << JsonEscape(result.error) << "\""
|
||||
<< ",\"failure_status\":\"" << JsonEscape(result.failure_status) << "\""
|
||||
<< ",\"login_key\":" << result.login_key
|
||||
<< ",\"empire\":" << result.empire
|
||||
<< ",\"character_index\":" << result.character_index
|
||||
<< ",\"character_name\":\"" << JsonEscape(result.character_name) << "\""
|
||||
<< ",\"character_created\":" << (result.character_created ? "true" : "false")
|
||||
<< ",\"character_deleted\":" << (result.character_deleted ? "true" : "false")
|
||||
<< ",\"deleted_character_index\":" << result.deleted_character_index
|
||||
<< ",\"game_channel\":" << result.game_channel
|
||||
<< ",\"mall_opened\":" << (result.mall_opened ? "true" : "false")
|
||||
<< ",\"mall_size\":" << result.mall_size
|
||||
<< ",\"timings_ms\":{"
|
||||
<< "\"auth_handshake\":" << result.auth_handshake_ms
|
||||
<< ",\"auth_login\":" << result.auth_login_ms
|
||||
<< ",\"channel_handshake\":" << result.channel_handshake_ms
|
||||
<< ",\"channel_login\":" << result.channel_login_ms
|
||||
<< ",\"character_create\":" << result.character_create_ms
|
||||
<< ",\"character_delete\":" << result.character_delete_ms
|
||||
<< ",\"character_select\":" << result.character_select_ms
|
||||
<< ",\"entergame\":" << result.entergame_ms
|
||||
<< ",\"mall_open\":" << result.mall_open_ms
|
||||
<< "},\"events\":[";
|
||||
|
||||
for (size_t i = 0; i < result.events.size(); ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
std::cout << ",";
|
||||
std::cout << "\"" << JsonEscape(result.events[i]) << "\"";
|
||||
}
|
||||
|
||||
std::cout << "]}\n";
|
||||
}
|
||||
|
||||
void WriteExact(int fd, const void* data, size_t length, std::string_view context)
|
||||
{
|
||||
const uint8_t* cursor = static_cast<const uint8_t*>(data);
|
||||
@@ -521,14 +219,6 @@ public:
|
||||
WriteExact(m_fd, &encrypted, sizeof(encrypted), "write encrypted packet");
|
||||
}
|
||||
|
||||
void SendEncryptedFrame(const void* data, size_t length)
|
||||
{
|
||||
std::vector<uint8_t> encrypted(length);
|
||||
std::memcpy(encrypted.data(), data, length);
|
||||
m_cipher.EncryptInPlace(encrypted.data(), encrypted.size());
|
||||
WriteExact(m_fd, encrypted.data(), encrypted.size(), "write encrypted frame");
|
||||
}
|
||||
|
||||
bool WaitForFrame(std::vector<uint8_t>& frame, int timeout_ms)
|
||||
{
|
||||
while (true)
|
||||
@@ -591,28 +281,11 @@ uint16_t FrameLength(const std::vector<uint8_t>& frame)
|
||||
return *reinterpret_cast<const uint16_t*>(frame.data() + sizeof(uint16_t));
|
||||
}
|
||||
|
||||
int RemainingTimeoutMs(std::chrono::steady_clock::time_point deadline)
|
||||
uint32_t Authenticate(const std::string& host, uint16_t auth_port, const std::string& login, const std::string& password)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now >= deadline)
|
||||
return 0;
|
||||
EncryptedClient auth_client(host, auth_port);
|
||||
auth_client.Handshake();
|
||||
|
||||
return static_cast<int>(std::chrono::duration_cast<std::chrono::milliseconds>(deadline - now).count());
|
||||
}
|
||||
|
||||
uint8_t FindFirstPlayableCharacterIndex(const PacketGCLoginSuccess4& success)
|
||||
{
|
||||
for (uint8_t index = 0; index < PLAYER_PER_ACCOUNT_LOCAL; ++index)
|
||||
{
|
||||
if (success.players[index].id != 0 && success.players[index].change_name == 0)
|
||||
return index;
|
||||
}
|
||||
|
||||
throw std::runtime_error("account has no playable characters");
|
||||
}
|
||||
|
||||
AuthResponse Authenticate(EncryptedClient& auth_client, const std::string& login, const std::string& password, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGLogin3 login_packet {};
|
||||
login_packet.header = CG::LOGIN3;
|
||||
login_packet.length = sizeof(login_packet);
|
||||
@@ -633,8 +306,7 @@ AuthResponse Authenticate(EncryptedClient& auth_client, const std::string& login
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected login failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
EmitEvent(result, json, "auth_failure status=" + std::string(failure->status));
|
||||
return AuthResponse {false, 0, failure->status};
|
||||
throw std::runtime_error(std::string("auth login failed: ") + failure->status);
|
||||
}
|
||||
|
||||
if (header == GC::AUTH_SUCCESS)
|
||||
@@ -642,16 +314,19 @@ AuthResponse Authenticate(EncryptedClient& auth_client, const std::string& login
|
||||
Expect(frame.size() == sizeof(PacketGCAuthSuccess), "unexpected auth success size");
|
||||
const auto* success = reinterpret_cast<const PacketGCAuthSuccess*>(frame.data());
|
||||
Expect(success->result != 0, "auth result returned failure");
|
||||
EmitEvent(result, json, "auth_success login_key=" + std::to_string(success->login_key));
|
||||
return AuthResponse {true, success->login_key, {}};
|
||||
std::cout << "auth_success login_key=" << success->login_key << "\n";
|
||||
return success->login_key;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("did not receive AUTH_SUCCESS");
|
||||
}
|
||||
|
||||
ChannelLoginResponse LoginToChannel(EncryptedClient& channel_client, const std::string& login, uint32_t login_key, SmokeResult& result, bool json)
|
||||
void LoginToChannel(const std::string& host, uint16_t channel_port, const std::string& login, uint32_t login_key)
|
||||
{
|
||||
EncryptedClient channel_client(host, channel_port);
|
||||
channel_client.Handshake();
|
||||
|
||||
PacketCGLogin2 login_packet {};
|
||||
login_packet.header = CG::LOGIN2;
|
||||
login_packet.length = sizeof(login_packet);
|
||||
@@ -662,7 +337,6 @@ ChannelLoginResponse LoginToChannel(EncryptedClient& channel_client, const std::
|
||||
bool saw_empire = false;
|
||||
bool saw_login_success = false;
|
||||
uint8_t empire = 0;
|
||||
PacketGCLoginSuccess4 login_success {};
|
||||
std::vector<uint8_t> frame;
|
||||
|
||||
for (int i = 0; i < 16; ++i)
|
||||
@@ -678,8 +352,7 @@ ChannelLoginResponse LoginToChannel(EncryptedClient& channel_client, const std::
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected channel login failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
EmitEvent(result, json, "channel_failure status=" + std::string(failure->status));
|
||||
return ChannelLoginResponse {false, -1, failure->status, {}};
|
||||
throw std::runtime_error(std::string("channel login failed: ") + failure->status);
|
||||
}
|
||||
|
||||
if (header == GC::EMPIRE)
|
||||
@@ -688,307 +361,30 @@ ChannelLoginResponse LoginToChannel(EncryptedClient& channel_client, const std::
|
||||
const auto* empire_packet = reinterpret_cast<const PacketGCEmpire*>(frame.data());
|
||||
saw_empire = true;
|
||||
empire = empire_packet->empire;
|
||||
EmitEvent(result, json, "channel_empire empire=" + std::to_string(static_cast<int>(empire)));
|
||||
std::cout << "channel_empire empire=" << static_cast<int>(empire) << "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header == GC::LOGIN_SUCCESS4)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginSuccess4), "unexpected channel login success size");
|
||||
std::memcpy(&login_success, frame.data(), sizeof(login_success));
|
||||
saw_login_success = true;
|
||||
EmitEvent(result, json, "channel_login_success length=" + std::to_string(length));
|
||||
std::cout << "channel_login_success length=" << length << "\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Expect(saw_empire, "did not receive EMPIRE");
|
||||
Expect(saw_login_success, "did not receive LOGIN_SUCCESS4");
|
||||
return ChannelLoginResponse {true, empire, {}, login_success};
|
||||
}
|
||||
|
||||
uint8_t CreateCharacter(EncryptedClient& channel_client, std::string_view create_name, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGPlayerCreate create_packet {};
|
||||
create_packet.header = CG::CHARACTER_CREATE;
|
||||
create_packet.length = sizeof(create_packet);
|
||||
create_packet.index = 0;
|
||||
create_packet.job = 0;
|
||||
create_packet.shape = 0;
|
||||
create_packet.con = 4;
|
||||
create_packet.intel = 4;
|
||||
create_packet.str = 4;
|
||||
create_packet.dex = 4;
|
||||
std::strncpy(create_packet.name, create_name.data(), sizeof(create_packet.name) - 1);
|
||||
channel_client.SendEncryptedPacket(create_packet);
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
Expect(channel_client.WaitForFrame(frame, 5000), "timed out waiting for character create response");
|
||||
const auto header = FrameHeader(frame);
|
||||
|
||||
if (header == GC::PHASE)
|
||||
continue;
|
||||
|
||||
if (header == GC::PLAYER_CREATE_FAILURE)
|
||||
{
|
||||
if (frame.size() == sizeof(PacketGCCreateFailure))
|
||||
{
|
||||
const auto* failure = reinterpret_cast<const PacketGCCreateFailure*>(frame.data());
|
||||
throw std::runtime_error("character create failed type=" + std::to_string(failure->type));
|
||||
}
|
||||
|
||||
throw std::runtime_error("character create failed");
|
||||
}
|
||||
|
||||
if (header == GC::PLAYER_CREATE_SUCCESS)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCPlayerCreateSuccess), "unexpected player create success size");
|
||||
const auto* success = reinterpret_cast<const PacketGCPlayerCreateSuccess*>(frame.data());
|
||||
EmitEvent(result, json, "character_created index=" + std::to_string(static_cast<int>(success->account_character_index)) +
|
||||
" name=" + success->player.name);
|
||||
result.character_created = true;
|
||||
result.character_name = success->player.name;
|
||||
return success->account_character_index;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("did not receive PLAYER_CREATE_SUCCESS");
|
||||
}
|
||||
|
||||
void DeleteCharacter(EncryptedClient& channel_client, uint8_t character_index, std::string_view private_code, SmokeResult& result, bool json)
|
||||
{
|
||||
Expect(private_code.size() == 7, "delete private code must be exactly 7 characters");
|
||||
|
||||
PacketCGPlayerDelete delete_packet {};
|
||||
delete_packet.header = CG::CHARACTER_DELETE;
|
||||
delete_packet.length = sizeof(delete_packet);
|
||||
delete_packet.index = character_index;
|
||||
std::strncpy(delete_packet.private_code, private_code.data(), sizeof(delete_packet.private_code) - 1);
|
||||
channel_client.SendEncryptedPacket(delete_packet);
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
{
|
||||
Expect(channel_client.WaitForFrame(frame, 5000), "timed out waiting for character delete response");
|
||||
const auto header = FrameHeader(frame);
|
||||
|
||||
if (header == GC::PHASE)
|
||||
continue;
|
||||
|
||||
if (header == GC::LOGIN_FAILURE)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected character delete failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
throw std::runtime_error(std::string("character delete failed: ") + failure->status);
|
||||
}
|
||||
|
||||
if (header == GC::PLAYER_DELETE_WRONG_SOCIAL_ID)
|
||||
{
|
||||
throw std::runtime_error("character delete failed: wrong private code");
|
||||
}
|
||||
|
||||
if (header == GC::PLAYER_DELETE_SUCCESS)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCPlayerDeleteSuccess), "unexpected player delete success size");
|
||||
const auto* success = reinterpret_cast<const PacketGCPlayerDeleteSuccess*>(frame.data());
|
||||
Expect(success->account_index == character_index, "character delete returned unexpected index");
|
||||
result.character_deleted = true;
|
||||
result.deleted_character_index = success->account_index;
|
||||
EmitEvent(result, json, "character_deleted index=" + std::to_string(static_cast<int>(success->account_index)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error("did not receive PLAYER_DELETE_SUCCESS");
|
||||
}
|
||||
|
||||
void SelectCharacter(EncryptedClient& channel_client, uint8_t character_index, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGPlayerSelect select_packet {};
|
||||
select_packet.header = CG::CHARACTER_SELECT;
|
||||
select_packet.length = sizeof(select_packet);
|
||||
select_packet.index = character_index;
|
||||
channel_client.SendEncryptedPacket(select_packet);
|
||||
|
||||
bool saw_phase_loading = false;
|
||||
bool saw_main_character = false;
|
||||
std::vector<uint8_t> frame;
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
|
||||
while (true)
|
||||
{
|
||||
const int timeout_ms = RemainingTimeoutMs(deadline);
|
||||
Expect(timeout_ms > 0, "timed out waiting for character select response");
|
||||
Expect(channel_client.WaitForFrame(frame, timeout_ms), "timed out waiting for character select response");
|
||||
const auto header = FrameHeader(frame);
|
||||
|
||||
if (header == GC::LOGIN_FAILURE)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected character select failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
throw std::runtime_error(std::string("character select failed: ") + failure->status);
|
||||
}
|
||||
|
||||
if (header == GC::PHASE)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCPhase), "unexpected phase packet size");
|
||||
const auto* phase = reinterpret_cast<const PacketGCPhase*>(frame.data());
|
||||
if (phase->phase == PHASE_LOADING)
|
||||
{
|
||||
saw_phase_loading = true;
|
||||
EmitEvent(result, json, "phase_loading");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header == GC::MAIN_CHARACTER)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCMainCharacter), "unexpected main character packet size");
|
||||
const auto* main_character = reinterpret_cast<const PacketGCMainCharacter*>(frame.data());
|
||||
Expect(main_character->name[0] != '\0', "main character name is empty");
|
||||
saw_main_character = true;
|
||||
result.character_name = main_character->name;
|
||||
EmitEvent(result, json, "main_character name=" + std::string(main_character->name));
|
||||
}
|
||||
|
||||
if (saw_phase_loading && saw_main_character)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SendClientVersion(EncryptedClient& channel_client, std::string_view client_version, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGClientVersion version_packet {};
|
||||
version_packet.header = CG::CLIENT_VERSION;
|
||||
version_packet.length = sizeof(version_packet);
|
||||
std::strncpy(version_packet.filename, "Metin2_Debug.exe", sizeof(version_packet.filename) - 1);
|
||||
std::strncpy(version_packet.timestamp, client_version.data(), sizeof(version_packet.timestamp) - 1);
|
||||
channel_client.SendEncryptedPacket(version_packet);
|
||||
EmitEvent(result, json, "client_version " + std::string(version_packet.timestamp));
|
||||
}
|
||||
|
||||
void SendChatCommand(EncryptedClient& channel_client, std::string_view command)
|
||||
{
|
||||
std::vector<uint8_t> frame(sizeof(PacketCGChat) + command.size() + 1, 0);
|
||||
auto* chat = reinterpret_cast<PacketCGChat*>(frame.data());
|
||||
chat->header = CG::CHAT;
|
||||
chat->length = static_cast<uint16_t>(frame.size());
|
||||
chat->type = CHAT_TYPE_TALKING_LOCAL;
|
||||
std::memcpy(frame.data() + sizeof(PacketCGChat), command.data(), command.size());
|
||||
channel_client.SendEncryptedFrame(frame.data(), frame.size());
|
||||
}
|
||||
|
||||
void EnterGame(EncryptedClient& channel_client, SmokeResult& result, bool json)
|
||||
{
|
||||
PacketCGEnterGame enter_game {};
|
||||
enter_game.header = CG::ENTERGAME;
|
||||
enter_game.length = sizeof(enter_game);
|
||||
channel_client.SendEncryptedPacket(enter_game);
|
||||
|
||||
bool saw_phase_game = false;
|
||||
bool saw_time = false;
|
||||
bool saw_channel = false;
|
||||
std::vector<uint8_t> frame;
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
|
||||
while (true)
|
||||
{
|
||||
const int timeout_ms = RemainingTimeoutMs(deadline);
|
||||
Expect(timeout_ms > 0, "timed out waiting for entergame response");
|
||||
Expect(channel_client.WaitForFrame(frame, timeout_ms), "timed out waiting for entergame response");
|
||||
const auto header = FrameHeader(frame);
|
||||
|
||||
if (header == GC::LOGIN_FAILURE)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCLoginFailure), "unexpected entergame failure size");
|
||||
const auto* failure = reinterpret_cast<const PacketGCLoginFailure*>(frame.data());
|
||||
throw std::runtime_error(std::string("entergame failed: ") + failure->status);
|
||||
}
|
||||
|
||||
if (header == GC::PHASE)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCPhase), "unexpected phase packet size");
|
||||
const auto* phase = reinterpret_cast<const PacketGCPhase*>(frame.data());
|
||||
if (phase->phase == PHASE_GAME)
|
||||
{
|
||||
saw_phase_game = true;
|
||||
EmitEvent(result, json, "phase_game");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header == GC::TIME)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCTime), "unexpected time packet size");
|
||||
saw_time = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (header == GC::CHANNEL)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCChannel), "unexpected channel packet size");
|
||||
const auto* channel = reinterpret_cast<const PacketGCChannel*>(frame.data());
|
||||
saw_channel = true;
|
||||
result.game_channel = channel->channel;
|
||||
EmitEvent(result, json, "game_channel channel=" + std::to_string(static_cast<int>(channel->channel)));
|
||||
}
|
||||
|
||||
if (saw_phase_game && saw_time && saw_channel)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenMall(EncryptedClient& channel_client, const std::string& password, SmokeResult& result, bool json)
|
||||
{
|
||||
Expect(!password.empty(), "mall password is empty");
|
||||
Expect(password.size() <= SAFEBOX_PASSWORD_MAX_LEN_LOCAL, "mall password too long");
|
||||
|
||||
SendChatCommand(channel_client, "/mall_password " + password);
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
||||
|
||||
while (true)
|
||||
{
|
||||
const int timeout_ms = RemainingTimeoutMs(deadline);
|
||||
Expect(timeout_ms > 0, "timed out waiting for mall open response");
|
||||
Expect(channel_client.WaitForFrame(frame, timeout_ms), "timed out waiting for mall open response");
|
||||
const auto header = FrameHeader(frame);
|
||||
|
||||
if (header == GC::SAFEBOX_WRONG_PASSWORD)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCSafeboxWrongPassword), "unexpected safebox wrong password packet size");
|
||||
throw std::runtime_error("mall password rejected");
|
||||
}
|
||||
|
||||
if (header == GC::MALL_OPEN)
|
||||
{
|
||||
Expect(frame.size() == sizeof(PacketGCSafeboxSize), "unexpected mall open packet size");
|
||||
const auto* mall_open = reinterpret_cast<const PacketGCSafeboxSize*>(frame.data());
|
||||
result.mall_opened = true;
|
||||
result.mall_size = mall_open->size;
|
||||
EmitEvent(result, json, "mall_open size=" + std::to_string(static_cast<int>(mall_open->size)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
SmokeOptions options;
|
||||
SmokeResult result;
|
||||
|
||||
try
|
||||
{
|
||||
Expect(argc >= 6,
|
||||
Expect(argc == 6,
|
||||
"usage: metin_login_smoke <host> <auth_port> <channel_port> <login> <password>\n"
|
||||
" or: metin_login_smoke <host> <auth_port> <channel_port> <login> --password-env=ENV_NAME\n"
|
||||
" optional: --create-character-name=NAME [--client-version=VERSION] [--json] "
|
||||
"[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS] [--delete-private-code=CODE] "
|
||||
"[--mall-password=PASSWORD]");
|
||||
" or: metin_login_smoke <host> <auth_port> <channel_port> <login> --password-env=ENV_NAME");
|
||||
|
||||
const std::string host = argv[1];
|
||||
const uint16_t auth_port = static_cast<uint16_t>(std::stoi(argv[2]));
|
||||
@@ -1009,220 +405,18 @@ int main(int argc, char** argv)
|
||||
password = password_arg;
|
||||
}
|
||||
|
||||
for (int i = 6; i < argc; ++i)
|
||||
{
|
||||
const std::string create_arg = argv[i];
|
||||
const std::string create_prefix = "--create-character-name=";
|
||||
const std::string version_prefix = "--client-version=";
|
||||
const std::string auth_failure_prefix = "--expect-auth-failure=";
|
||||
const std::string channel_failure_prefix = "--expect-channel-failure=";
|
||||
const std::string delete_private_code_prefix = "--delete-private-code=";
|
||||
const std::string mall_password_prefix = "--mall-password=";
|
||||
if (create_arg.rfind(create_prefix, 0) == 0)
|
||||
{
|
||||
options.create_character_name = create_arg.substr(create_prefix.size());
|
||||
Expect(!options.create_character_name.empty(), "create character name is empty");
|
||||
Expect(options.create_character_name.size() <= CHARACTER_NAME_MAX_LEN_LOCAL, "create character name too long");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(version_prefix, 0) == 0)
|
||||
{
|
||||
options.client_version = create_arg.substr(version_prefix.size());
|
||||
Expect(!options.client_version.empty(), "client version is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(auth_failure_prefix, 0) == 0)
|
||||
{
|
||||
options.expect_auth_failure = create_arg.substr(auth_failure_prefix.size());
|
||||
Expect(!options.expect_auth_failure.empty(), "expected auth failure status is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(channel_failure_prefix, 0) == 0)
|
||||
{
|
||||
options.expect_channel_failure = create_arg.substr(channel_failure_prefix.size());
|
||||
Expect(!options.expect_channel_failure.empty(), "expected channel failure status is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(delete_private_code_prefix, 0) == 0)
|
||||
{
|
||||
options.delete_private_code = create_arg.substr(delete_private_code_prefix.size());
|
||||
Expect(!options.delete_private_code.empty(), "delete private code is empty");
|
||||
Expect(options.delete_private_code.size() == 7, "delete private code must be exactly 7 characters");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg.rfind(mall_password_prefix, 0) == 0)
|
||||
{
|
||||
options.mall_password = create_arg.substr(mall_password_prefix.size());
|
||||
Expect(!options.mall_password.empty(), "mall password is empty");
|
||||
Expect(options.mall_password.size() <= SAFEBOX_PASSWORD_MAX_LEN_LOCAL, "mall password too long");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (create_arg == "--json")
|
||||
{
|
||||
options.json = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw std::runtime_error("unknown optional argument: " + create_arg);
|
||||
}
|
||||
|
||||
Expect(login.size() <= LOGIN_MAX_LEN_LOCAL, "login too long");
|
||||
Expect(password.size() <= PASSWD_MAX_LEN_LOCAL, "password too long");
|
||||
|
||||
result.stage = "auth_handshake";
|
||||
auto started_at = std::chrono::steady_clock::now();
|
||||
EncryptedClient auth_client(host, auth_port);
|
||||
auth_client.Handshake();
|
||||
result.auth_handshake_ms = ElapsedMs(started_at);
|
||||
const uint32_t login_key = Authenticate(host, auth_port, login, password);
|
||||
LoginToChannel(host, channel_port, login, login_key);
|
||||
|
||||
result.stage = "auth_login";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
const AuthResponse auth = Authenticate(auth_client, login, password, result, options.json);
|
||||
result.auth_login_ms = ElapsedMs(started_at);
|
||||
result.failure_status.clear();
|
||||
|
||||
if (!auth.success)
|
||||
{
|
||||
result.failure_status = auth.failure_status;
|
||||
if (!options.expect_auth_failure.empty())
|
||||
{
|
||||
Expect(auth.failure_status == options.expect_auth_failure,
|
||||
"expected auth failure '" + options.expect_auth_failure + "' but got '" + auth.failure_status + "'");
|
||||
result.ok = true;
|
||||
result.result = "expected_auth_failure";
|
||||
result.stage = "auth_login";
|
||||
EmitEvent(result, options.json, "expected_auth_failure status=" + auth.failure_status);
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
throw std::runtime_error("auth login failed: " + auth.failure_status);
|
||||
}
|
||||
|
||||
Expect(options.expect_auth_failure.empty(),
|
||||
"auth login succeeded but expected failure '" + options.expect_auth_failure + "'");
|
||||
|
||||
const uint32_t login_key = auth.login_key;
|
||||
result.login_key = login_key;
|
||||
|
||||
result.stage = "channel_handshake";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
EncryptedClient channel_client(host, channel_port);
|
||||
channel_client.Handshake();
|
||||
result.channel_handshake_ms = ElapsedMs(started_at);
|
||||
|
||||
result.stage = "channel_login";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
const ChannelLoginResponse channel = LoginToChannel(channel_client, login, login_key, result, options.json);
|
||||
result.channel_login_ms = ElapsedMs(started_at);
|
||||
|
||||
if (!channel.success)
|
||||
{
|
||||
result.failure_status = channel.failure_status;
|
||||
if (!options.expect_channel_failure.empty())
|
||||
{
|
||||
Expect(channel.failure_status == options.expect_channel_failure,
|
||||
"expected channel failure '" + options.expect_channel_failure + "' but got '" + channel.failure_status + "'");
|
||||
result.ok = true;
|
||||
result.result = "expected_channel_failure";
|
||||
result.stage = "channel_login";
|
||||
EmitEvent(result, options.json, "expected_channel_failure status=" + channel.failure_status);
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
throw std::runtime_error("channel login failed: " + channel.failure_status);
|
||||
}
|
||||
|
||||
Expect(options.expect_channel_failure.empty(),
|
||||
"channel login succeeded but expected failure '" + options.expect_channel_failure + "'");
|
||||
|
||||
const PacketGCLoginSuccess4& login_success = channel.login_success;
|
||||
result.empire = channel.empire;
|
||||
|
||||
uint8_t character_index = 0;
|
||||
try
|
||||
{
|
||||
character_index = FindFirstPlayableCharacterIndex(login_success);
|
||||
result.character_index = character_index;
|
||||
result.character_name = login_success.players[character_index].name;
|
||||
EmitEvent(result, options.json, "selected_character index=" + std::to_string(static_cast<int>(character_index)));
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
Expect(!options.create_character_name.empty(), "account has no playable characters and no create-character name was provided");
|
||||
result.stage = "character_create";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
character_index = CreateCharacter(channel_client, options.create_character_name, result, options.json);
|
||||
result.character_create_ms = ElapsedMs(started_at);
|
||||
result.character_index = character_index;
|
||||
}
|
||||
|
||||
if (!options.delete_private_code.empty())
|
||||
{
|
||||
result.stage = "character_delete";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
DeleteCharacter(channel_client, character_index, options.delete_private_code, result, options.json);
|
||||
result.character_delete_ms = ElapsedMs(started_at);
|
||||
result.ok = true;
|
||||
result.result = "delete_success";
|
||||
result.stage = "complete";
|
||||
EmitEvent(result, options.json, "delete_success");
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
return 0;
|
||||
}
|
||||
|
||||
result.stage = "character_select";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
SelectCharacter(channel_client, character_index, result, options.json);
|
||||
result.character_select_ms = ElapsedMs(started_at);
|
||||
|
||||
result.stage = "client_version";
|
||||
SendClientVersion(channel_client, options.client_version, result, options.json);
|
||||
|
||||
result.stage = "entergame";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
EnterGame(channel_client, result, options.json);
|
||||
result.entergame_ms = ElapsedMs(started_at);
|
||||
|
||||
if (!options.mall_password.empty())
|
||||
{
|
||||
result.stage = "mall_open";
|
||||
started_at = std::chrono::steady_clock::now();
|
||||
OpenMall(channel_client, options.mall_password, result, options.json);
|
||||
result.mall_open_ms = ElapsedMs(started_at);
|
||||
}
|
||||
|
||||
result.ok = true;
|
||||
result.result = "success";
|
||||
result.stage = "complete";
|
||||
EmitEvent(result, options.json, "full_login_success");
|
||||
if (options.json)
|
||||
PrintJson(result);
|
||||
std::cout << "full_login_success\n";
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
result.ok = false;
|
||||
result.result = "unexpected_failure";
|
||||
result.error = e.what();
|
||||
if (options.json)
|
||||
{
|
||||
PrintJson(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "metin_login_smoke failed: " << e.what() << "\n";
|
||||
}
|
||||
std::cerr << "metin_login_smoke failed: " << e.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,9 @@
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include "game/stdafx.h"
|
||||
#include "common/packet_headers.h"
|
||||
#include "common/tables.h"
|
||||
#include "game/packet_structs.h"
|
||||
#include "game/quest_packet.h"
|
||||
#include "game/request_cooldown.h"
|
||||
#include "game/stdafx.h"
|
||||
#include "game/SecureCipher.h"
|
||||
#include "libthecore/fdwatch.h"
|
||||
#include "libthecore/signal.h"
|
||||
@@ -371,55 +366,6 @@ void TestFdwatchSlotReuseAfterDelete()
|
||||
close(sockets_b[0]);
|
||||
close(sockets_b[1]);
|
||||
}
|
||||
|
||||
void TestQuestInfoPacketFraming()
|
||||
{
|
||||
quest::QuestInfoPacketData data {};
|
||||
data.quest_index = 77;
|
||||
data.send_flags = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
||||
data.is_begin = true;
|
||||
data.title = "Mall reward";
|
||||
data.clock_name = "Soon";
|
||||
data.clock_value = 15;
|
||||
data.counter_name = "Kills";
|
||||
data.counter_value = 2;
|
||||
data.icon_file = "d:/icon/test.tga";
|
||||
|
||||
const auto quest_packet = quest::BuildQuestInfoPacket(data);
|
||||
Expect(!quest_packet.empty(), "Quest info packet is empty");
|
||||
Expect(quest_packet.size() == sizeof(packet_quest_info) + 1 + 31 + 17 + 4 + 17 + 4 + 25,
|
||||
"Unexpected quest info packet size");
|
||||
|
||||
const auto* quest_header = reinterpret_cast<const packet_quest_info*>(quest_packet.data());
|
||||
Expect(quest_header->header == GC::QUEST_INFO, "Unexpected quest info header");
|
||||
Expect(quest_header->length == quest_packet.size(), "Quest info packet length does not match payload size");
|
||||
Expect(quest_packet[sizeof(packet_quest_info)] == 1, "Quest begin flag payload mismatch");
|
||||
|
||||
TPacketGCItemGet item_get {};
|
||||
item_get.header = GC::ITEM_GET;
|
||||
item_get.length = sizeof(item_get);
|
||||
item_get.dwItemVnum = 50187;
|
||||
item_get.bCount = 1;
|
||||
item_get.bArg = 0;
|
||||
|
||||
std::vector<uint8_t> stream = quest_packet;
|
||||
const auto* item_bytes = reinterpret_cast<const uint8_t*>(&item_get);
|
||||
stream.insert(stream.end(), item_bytes, item_bytes + sizeof(item_get));
|
||||
|
||||
const size_t next_frame_offset = quest_header->length;
|
||||
Expect(stream.size() >= next_frame_offset + sizeof(item_get), "Combined stream truncated after quest packet");
|
||||
|
||||
const auto* next_frame = reinterpret_cast<const TPacketGCItemGet*>(stream.data() + next_frame_offset);
|
||||
Expect(next_frame->header == GC::ITEM_GET, "Quest info packet left trailing bytes before next frame");
|
||||
Expect(next_frame->length == sizeof(TPacketGCItemGet), "Item get packet length mismatch after quest packet");
|
||||
}
|
||||
|
||||
void TestRequestCooldownGuard()
|
||||
{
|
||||
Expect(!HasRecentRequestCooldown(0, 5, 10), "Initial zero request pulse should not trigger cooldown");
|
||||
Expect(HasRecentRequestCooldown(95, 100, 10), "Recent request pulse should still be on cooldown");
|
||||
Expect(!HasRecentRequestCooldown(90, 100, 10), "Cooldown boundary should allow request");
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
@@ -433,8 +379,6 @@ int main()
|
||||
TestCheckpointBackendMetadata();
|
||||
TestFdwatchReadAndOneshotWrite();
|
||||
TestFdwatchSlotReuseAfterDelete();
|
||||
TestQuestInfoPacketFraming();
|
||||
TestRequestCooldownGuard();
|
||||
std::cout << "metin smoke tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user