diff --git a/src/db/ClientManagerPlayer.cpp b/src/db/ClientManagerPlayer.cpp index 0b59fa1..001ca92 100644 --- a/src/db/ClientManagerPlayer.cpp +++ b/src/db/ClientManagerPlayer.cpp @@ -8,6 +8,7 @@ #include "ItemAwardManager.h" #include "HB.h" #include "Cache.h" +#include "libsql/Statement.h" extern bool g_bHotBackup; @@ -15,6 +16,22 @@ extern std::string g_stLocale; extern int g_test_server; extern int g_log; +namespace +{ + bool PreparePlayerStmt(CStmt& stmt, const std::string& query, int slot = SQL_PLAYER) + { + CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(slot); + + if (!sql) + { + sys_err("player SQL handle is not initialized"); + return false; + } + + return stmt.Prepare(sql, query.c_str()); + } +} + // // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!! IMPORTANT !!!!!!!!!!!! @@ -810,9 +827,7 @@ static time_by_id_map_t s_createTimeByAccountID; */ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerCreatePacket* packet) { - char queryStr[QUERY_MAX_LEN]; - int queryLen; - int player_id; + uint32_t player_id = 0; // 한 계정에 X초 내로 캐릭터 생성을 할 수 없다. time_by_id_map_t::iterator it = s_createTimeByAccountID.find(packet->account_id); @@ -828,81 +843,53 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC } } - queryLen = snprintf(queryStr, sizeof(queryStr), - "SELECT pid%u FROM player_index%s WHERE id=%d", packet->account_index + 1, GetTablePostfix(), packet->account_id); + uint32_t existingPid = 0; + CStmt playerIndexStmt; + const std::string playerIndexQuery = "SELECT pid" + std::to_string(packet->account_index + 1) + + " FROM player_index" + std::string(GetTablePostfix()) + " WHERE 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 + if (!PreparePlayerStmt(playerIndexStmt, playerIndexQuery) + || !playerIndexStmt.BindParam(MYSQL_TYPE_LONG, &packet->account_id) + || !playerIndexStmt.BindResult(MYSQL_TYPE_LONG, &existingPid) + || !playerIndexStmt.Execute() + || playerIndexStmt.iRows == 0 + || !playerIndexStmt.Fetch()) { peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); return; } + if (existingPid > 0) + { + peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); + sys_log(0, "ALREADY EXIST AccountChrIdx %d ID %d", packet->account_index, existingPid); + return; + } + + unsigned long long nameCount = 0; + CStmt playerNameStmt; + std::string playerNameQuery = "SELECT COUNT(*) FROM player" + std::string(GetTablePostfix()) + " WHERE name=?"; + 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); + playerNameQuery += " collate sjis_japanese_ci"; - 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 + if (!PreparePlayerStmt(playerNameStmt, playerNameQuery) + || !playerNameStmt.BindParam(MYSQL_TYPE_STRING, packet->player_table.name, sizeof(packet->player_table.name)) + || !playerNameStmt.BindResult(MYSQL_TYPE_LONGLONG, &nameCount) + || !playerNameStmt.Execute() + || playerNameStmt.iRows == 0 + || !playerNameStmt.Fetch()) { peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, 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); + if (nameCount > 0) + { + sys_log(0, "ALREADY EXIST name %s", packet->player_table.name); + peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); + return; + } sys_log(0, "PlayerCreate accountid %d name %s level %d gold %d, st %d ht %d job %d", packet->account_id, @@ -913,39 +900,77 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC packet->player_table.ht, packet->player_table.job); - static char text[8192 + 1]; + CStmt createPlayerStmt; + uint16_t partHair = 0; + int32_t playtime = 0; + const std::string createPlayerQuery = "INSERT INTO player" + std::string(GetTablePostfix()) + + " (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, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; - 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) + if (!PreparePlayerStmt(createPlayerStmt, createPlayerQuery) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->account_id) + || !createPlayerStmt.BindParam(MYSQL_TYPE_STRING, packet->player_table.name, sizeof(packet->player_table.name)) + || !createPlayerStmt.BindParam(MYSQL_TYPE_TINY, &packet->player_table.level) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.st) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.ht) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.dx) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.iq) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.job) + || !createPlayerStmt.BindParam(MYSQL_TYPE_TINY, &packet->player_table.voice) + || !createPlayerStmt.BindParam(MYSQL_TYPE_TINY, &packet->player_table.dir) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.x) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.y) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.z) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.hp) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.sp) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.sRandomHP) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.sRandomSP) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.stat_point) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &packet->player_table.stamina) + || !createPlayerStmt.BindParam(MYSQL_TYPE_TINY, &packet->player_table.part_base) + || !createPlayerStmt.BindParam(MYSQL_TYPE_TINY, &packet->player_table.part_base) + || !createPlayerStmt.BindParam(MYSQL_TYPE_SHORT, &partHair) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &packet->player_table.gold) + || !createPlayerStmt.BindParam(MYSQL_TYPE_LONG, &playtime) + || !createPlayerStmt.BindParam(MYSQL_TYPE_BLOB, packet->player_table.skills, sizeof(packet->player_table.skills)) + || !createPlayerStmt.BindParam(MYSQL_TYPE_BLOB, packet->player_table.quickslot, sizeof(packet->player_table.quickslot))) { - peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); - sys_log(0, "ALREADY EXIST3 query: %s AffectedRows %lu", queryStr, pMsg2->Get()->uiAffectedRows); + peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); return; } - 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) + if (!createPlayerStmt.Execute() || createPlayerStmt.GetAffectedRows() <= 0) { - sys_err("QUERY_ERROR: %s", queryStr); + peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0); + sys_log(0, "ALREADY EXIST3 name %s affected_rows %llu", packet->player_table.name, createPlayerStmt.GetAffectedRows()); + return; + } - snprintf(queryStr, sizeof(queryStr), "DELETE FROM player%s WHERE id=%d", GetTablePostfix(), player_id); - CDBManager::instance().DirectQuery(queryStr); + player_id = static_cast(createPlayerStmt.GetInsertId()); + + CStmt updatePlayerIndexStmt; + const std::string updatePlayerIndexQuery = "UPDATE player_index" + std::string(GetTablePostfix()) + + " SET pid" + std::to_string(packet->account_index + 1) + "=? WHERE id=?"; + + if (!PreparePlayerStmt(updatePlayerIndexStmt, updatePlayerIndexQuery) + || !updatePlayerIndexStmt.BindParam(MYSQL_TYPE_LONG, &player_id) + || !updatePlayerIndexStmt.BindParam(MYSQL_TYPE_LONG, &packet->account_id) + || !updatePlayerIndexStmt.Execute() + || updatePlayerIndexStmt.GetAffectedRows() <= 0) + { + sys_err("QUERY_ERROR: failed to update player_index for account %u pid %u", packet->account_id, player_id); + + CStmt rollbackDeleteStmt; + const std::string rollbackDeleteQuery = "DELETE FROM player" + std::string(GetTablePostfix()) + " WHERE id=?"; + + if (PreparePlayerStmt(rollbackDeleteStmt, rollbackDeleteQuery)) + { + rollbackDeleteStmt.BindParam(MYSQL_TYPE_LONG, &player_id); + rollbackDeleteStmt.Execute(); + } peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0); return; @@ -1082,12 +1107,15 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg) } char queryStr[QUERY_MAX_LEN]; + CStmt archivePlayerStmt; + const std::string archivePlayerQuery = "INSERT INTO player" + std::string(GetTablePostfix()) + + "_deleted SELECT * FROM player" + std::string(GetTablePostfix()) + " WHERE 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) + if (!PreparePlayerStmt(archivePlayerStmt, archivePlayerQuery) + || !archivePlayerStmt.BindParam(MYSQL_TYPE_LONG, &pi->player_id) + || !archivePlayerStmt.Execute() + || archivePlayerStmt.GetAffectedRows() == 0 + || archivePlayerStmt.GetAffectedRows() == static_cast(-1)) { sys_log(0, "PLAYER_DELETE FAILED %u CANNOT INSERT TO player%s_deleted", dwPID, GetTablePostfix()); @@ -1099,10 +1127,6 @@ 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); @@ -1131,15 +1155,15 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg) m_map_pkItemCacheSetPtr.erase(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); + CStmt resetPlayerIndexStmt; + const std::string resetPlayerIndexQuery = "UPDATE player_index" + std::string(GetTablePostfix()) + + " SET pid" + std::to_string(pi->account_index + 1) + "=0 WHERE pid" + std::to_string(pi->account_index + 1) + "=?"; - auto pMsg = CDBManager::instance().DirectQuery(queryStr); - - if (pMsg->Get()->uiAffectedRows == 0 || pMsg->Get()->uiAffectedRows == (uint32_t)-1) + if (!PreparePlayerStmt(resetPlayerIndexStmt, resetPlayerIndexQuery) + || !resetPlayerIndexStmt.BindParam(MYSQL_TYPE_LONG, &pi->player_id) + || !resetPlayerIndexStmt.Execute() + || resetPlayerIndexStmt.GetAffectedRows() == 0 + || resetPlayerIndexStmt.GetAffectedRows() == static_cast(-1)) { sys_log(0, "PLAYER_DELETE FAIL WHEN UPDATE account table"); peer->EncodeHeader(DG::PLAYER_DELETE_FAILED, pi->dwHandle, 1); @@ -1147,11 +1171,26 @@ void CClientManager::__RESULT_PLAYER_DELETE(CPeer *peer, SQLMsg* msg) return; } - snprintf(queryStr, sizeof(queryStr), "DELETE FROM player%s WHERE id=%d", GetTablePostfix(), pi->player_id); - CDBManager::instance().DirectQuery(queryStr); + CStmt deletePlayerStmt; + const std::string deletePlayerQuery = "DELETE FROM player" + std::string(GetTablePostfix()) + " WHERE id=?"; + if (PreparePlayerStmt(deletePlayerStmt, deletePlayerQuery)) + { + deletePlayerStmt.BindParam(MYSQL_TYPE_LONG, &pi->player_id); + deletePlayerStmt.Execute(); + } - 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); + CStmt deleteItemStmt; + int32_t safeboxWindow = SAFEBOX; + int32_t dragonSoulInventory = DRAGON_SOUL_INVENTORY; + const std::string deleteItemQuery = "DELETE FROM item" + std::string(GetTablePostfix()) + + " WHERE owner_id=? AND (window < ? or window = ?)"; + if (PreparePlayerStmt(deleteItemStmt, deleteItemQuery)) + { + deleteItemStmt.BindParam(MYSQL_TYPE_LONG, &pi->player_id); + deleteItemStmt.BindParam(MYSQL_TYPE_LONG, &safeboxWindow); + deleteItemStmt.BindParam(MYSQL_TYPE_LONG, &dragonSoulInventory); + deleteItemStmt.Execute(); + } snprintf(queryStr, sizeof(queryStr), "DELETE FROM quest%s WHERE dwPID=%d", GetTablePostfix(), pi->player_id); CDBManager::instance().AsyncQuery(queryStr); @@ -1372,4 +1411,3 @@ void CClientManager::FlushPlayerCacheSet(DWORD pid) delete c; } } -