db: prepare player create flow
Some checks failed
build / Linux asan (push) Has been cancelled
build / Linux release (push) Has been cancelled
build / FreeBSD build (push) Has been cancelled

This commit is contained in:
server
2026-04-14 12:42:05 +02:00
parent 6b274186c5
commit d85fa6c12c
3 changed files with 223 additions and 97 deletions

View File

@@ -8,6 +8,7 @@
#include "ItemAwardManager.h" #include "ItemAwardManager.h"
#include "HB.h" #include "HB.h"
#include "Cache.h" #include "Cache.h"
#include "libsql/Statement.h"
extern bool g_bHotBackup; extern bool g_bHotBackup;
@@ -15,6 +16,181 @@ extern std::string g_stLocale;
extern int g_test_server; extern int g_test_server;
extern int g_log; 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;
}
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();
}
}
// //
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!!!!!!!!!! IMPORTANT !!!!!!!!!!!! // !!!!!!!!!!! IMPORTANT !!!!!!!!!!!!
@@ -810,9 +986,7 @@ static time_by_id_map_t s_createTimeByAccountID;
*/ */
void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerCreatePacket* packet) void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerCreatePacket* packet)
{ {
char queryStr[QUERY_MAX_LEN]; DWORD player_id = 0;
int queryLen;
int player_id;
// 한 계정에 X초 내로 캐릭터 생성을 할 수 없다. // 한 계정에 X초 내로 캐릭터 생성을 할 수 없다.
time_by_id_map_t::iterator it = s_createTimeByAccountID.find(packet->account_id); time_by_id_map_t::iterator it = s_createTimeByAccountID.find(packet->account_id);
@@ -828,81 +1002,40 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC
} }
} }
queryLen = snprintf(queryStr, sizeof(queryStr), DWORD existingPlayerId = 0;
"SELECT pid%u FROM player_index%s WHERE id=%d", packet->account_index + 1, GetTablePostfix(), packet->account_id); bool foundPlayerIndexRow = false;
if (!LoadPlayerIndexSlot(packet->account_id, packet->account_index, &existingPlayerId, &foundPlayerIndexRow))
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); peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
return; return;
} }
if (g_stLocale == "sjis") if (!foundPlayerIndexRow)
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); peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
return; return;
} }
queryLen = snprintf(queryStr, sizeof(queryStr), if (existingPlayerId > 0)
"INSERT INTO player%s " {
"(id, account_id, name, level, st, ht, dx, iq, " peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
"job, voice, dir, x, y, z, " sys_log(0, "ALREADY EXIST AccountChrIdx %d ID %d", packet->account_index, existingPlayerId);
"hp, mp, random_hp, random_sp, stat_point, stamina, part_base, part_main, part_hair, gold, playtime, " return;
"skill_level, quickslot) " }
"VALUES(0, %u, '%s', %d, %d, %d, %d, %d, "
"%d, %d, %d, %d, %d, %d, %d, " unsigned long long playerCount = 0;
"%d, %d, %d, %d, %d, %d, %d, 0, %d, 0, ", if (!CountPlayersByName(packet->player_table.name, &playerCount))
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, peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
packet->player_table.job, packet->player_table.voice, packet->player_table.dir, packet->player_table.x, packet->player_table.y, packet->player_table.z, return;
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 (playerCount != 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", sys_log(0, "PlayerCreate accountid %d name %s level %d gold %d, st %d ht %d job %d",
packet->account_id, packet->account_id,
@@ -913,40 +1046,16 @@ void CClientManager::__QUERY_PLAYER_CREATE(CPeer *peer, DWORD dwHandle, TPlayerC
packet->player_table.ht, packet->player_table.ht,
packet->player_table.job); packet->player_table.job);
static char text[8192 + 1]; if (!InsertPlayerRecord(packet, &player_id))
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); peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
sys_log(0, "ALREADY EXIST3 query: %s AffectedRows %lu", queryStr, pMsg2->Get()->uiAffectedRows); sys_log(0, "ALREADY EXIST3 name %s", packet->player_table.name);
return; return;
} }
player_id = pMsg2->Get()->uiInsertID; if (!UpdatePlayerIndexSlot(packet->account_id, packet->account_index, player_id))
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)
{ {
sys_err("QUERY_ERROR: %s", queryStr); DeletePlayerById(player_id);
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); peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
return; return;
} }
@@ -1372,4 +1481,3 @@ void CClientManager::FlushPlayerCacheSet(DWORD pid)
delete c; delete c;
} }
} }

View File

@@ -171,3 +171,19 @@ bool CStmt::Fetch()
{ {
return !mysql_stmt_fetch(m_pkStmt); 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);
}

View File

@@ -17,6 +17,8 @@ class CStmt
bool BindResult(enum_field_types type, void * p, int iMaxLen=0); bool BindResult(enum_field_types type, void * p, int iMaxLen=0);
int Execute(); int Execute();
bool Fetch(); bool Fetch();
unsigned long long GetInsertId() const;
unsigned long long GetAffectedRows() const;
void Error(const char * c_pszMsg); void Error(const char * c_pszMsg);