Compare commits
13 Commits
main
...
74827a1907
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74827a1907 | ||
|
|
8a5f6e70ee | ||
|
|
0f69efeba4 | ||
|
|
a0ad693e13 | ||
|
|
cc8fa1e78a | ||
|
|
dc76f5da87 | ||
|
|
2bbd624c9a | ||
|
|
44720d0691 | ||
|
|
ccc1a8899d | ||
|
|
8bb5340909 | ||
|
|
6061e43c20 | ||
|
|
54a4134bf7 | ||
|
|
bf72bd2669 |
95
.github/workflows/main.yml
vendored
95
.github/workflows/main.yml
vendored
@@ -5,8 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
@@ -15,54 +13,59 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: release
|
||||
- name: linux-release
|
||||
build_type: Release
|
||||
enable_asan: OFF
|
||||
- name: asan
|
||||
cmake_args: -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
- name: linux-asan
|
||||
build_type: RelWithDebInfo
|
||||
enable_asan: ON
|
||||
name: Linux ${{ matrix.name }}
|
||||
cmake_args: -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_ASAN=ON
|
||||
name: ${{ matrix.name }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y cmake ninja-build pkg-config libsodium-dev libmariadb-dev
|
||||
- name: Configure
|
||||
run: |
|
||||
cmake -S . -B build-${{ matrix.name }} -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
|
||||
-DENABLE_ASAN=${{ matrix.enable_asan }}
|
||||
- name: Build
|
||||
run: cmake --build build-${{ matrix.name }} --parallel
|
||||
- name: Smoke tests
|
||||
run: ctest --test-dir build-${{ matrix.name }} --output-on-failure
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build
|
||||
|
||||
- name: Configure
|
||||
run: cmake -S . -B build ${{ matrix.cmake_args }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build build --parallel
|
||||
|
||||
- name: Upload compile commands
|
||||
if: matrix.name == 'linux-release'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: compile-commands
|
||||
path: build/compile_commands.json
|
||||
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.name }}-binaries
|
||||
path: build/bin
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
name: FreeBSD build
|
||||
name: freebsd-release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: FreeBSD job
|
||||
id: test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
sync: sshfs
|
||||
prepare: |
|
||||
pkg install -y git cmake gmake
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
gmake all -j6
|
||||
ctest --output-on-failure
|
||||
- name: Collect outputs
|
||||
run: |
|
||||
mkdir _output
|
||||
cp build/bin/* _output
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: output_bsd
|
||||
path: _output
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: FreeBSD build
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
sync: sshfs
|
||||
prepare: |
|
||||
pkg install -y git cmake gmake ninja
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --parallel
|
||||
|
||||
- name: Upload binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: freebsd-release-binaries
|
||||
path: build/bin
|
||||
|
||||
67
AGENTS.md
67
AGENTS.md
@@ -1,67 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
## Repository Role
|
||||
|
||||
This repository contains the C++ server source and build system for:
|
||||
|
||||
- `game`
|
||||
- `db`
|
||||
- `qc`
|
||||
- smoke/login test binaries
|
||||
|
||||
The current production VPS runs a Debian deployment built from `main`.
|
||||
|
||||
## Working Rules
|
||||
|
||||
- Prefer small, reviewable changes directly on `main`.
|
||||
- Keep Debian runtime stability ahead of broad refactors.
|
||||
- Do not commit `build/`, temporary captures, or host-local debug output.
|
||||
- Do not commit secrets or production-only credentials.
|
||||
|
||||
## Build And Test
|
||||
|
||||
Typical local build:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -G Ninja
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
|
||||
Important test targets:
|
||||
|
||||
- `metin_smoke_tests`
|
||||
- `metin_login_smoke`
|
||||
|
||||
## High-Risk Areas
|
||||
|
||||
Changes in these areas require extra care:
|
||||
|
||||
- auth/login flow
|
||||
- packet headers and packet structs
|
||||
- `SecureCipher`
|
||||
- `fdwatch` and socket output path
|
||||
- DB login/account flow
|
||||
|
||||
When changing login, auth, packet, or network behavior:
|
||||
|
||||
- build and run smoke tests
|
||||
- keep protocol compatibility with `m2dev-client-src`
|
||||
- keep client asset behavior in sync with `m2dev-client`
|
||||
|
||||
## Production Verification
|
||||
|
||||
The current VPS has a root-only end-to-end login check:
|
||||
|
||||
```bash
|
||||
/usr/local/sbin/metin-login-healthcheck
|
||||
```
|
||||
|
||||
Use it after risky auth/network/runtime changes.
|
||||
|
||||
## Cross-Repo Boundaries
|
||||
|
||||
- server code changes belong here
|
||||
- runtime/config/systemd/docs changes belong in `m2dev-server`
|
||||
- client protocol implementation changes belong in `m2dev-client-src`
|
||||
- client asset/login screen/server list changes belong in `m2dev-client`
|
||||
11
CLAUDE.md
11
CLAUDE.md
@@ -1,11 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Follow [AGENTS.md](AGENTS.md) as the canonical repo guide.
|
||||
|
||||
Short version:
|
||||
|
||||
- this repo owns `game`, `db`, `qc`, and smoke/login test binaries
|
||||
- prefer small changes on `main`
|
||||
- auth/network/packet edits must be tested carefully
|
||||
- keep protocol changes aligned with `m2dev-client-src` and `m2dev-client`
|
||||
- use `/usr/local/sbin/metin-login-healthcheck` on the VPS after risky login/runtime changes
|
||||
@@ -99,6 +99,3 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/vendor/mariadb-connector-c-3.4.5
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(vendor)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "Config.h"
|
||||
#include "DBManager.h"
|
||||
#include "QID.h"
|
||||
#include "libsql/Statement.h"
|
||||
#include "GuildManager.h"
|
||||
#include "PrivManager.h"
|
||||
#include "MoneyLog.h"
|
||||
@@ -17,7 +18,6 @@
|
||||
#include "Marriage.h"
|
||||
#include "ItemIDRangeManager.h"
|
||||
#include "Cache.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -40,7 +40,7 @@ int g_query_count[2];
|
||||
|
||||
namespace
|
||||
{
|
||||
bool PrepareClientPlayerStmt(CStmt& stmt, const char* query)
|
||||
bool PrepareClientPlayerStmt(CStmt& stmt, const std::string& query)
|
||||
{
|
||||
CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(SQL_PLAYER);
|
||||
|
||||
@@ -50,26 +50,25 @@ bool PrepareClientPlayerStmt(CStmt& stmt, const char* query)
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Prepare(sql, query);
|
||||
return stmt.Prepare(sql, query.c_str());
|
||||
}
|
||||
|
||||
bool LoadSafeboxPasswordByAccountId(DWORD accountId, char* password, size_t passwordSize, bool* found)
|
||||
bool LoadSafeboxPassword(uint32_t accountId, char* password, size_t passwordSize, bool* foundRow)
|
||||
{
|
||||
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;
|
||||
const std::string query = std::string("SELECT password FROM safebox") + GetTablePostfix() + " WHERE account_id=?";
|
||||
|
||||
if (!PrepareClientPlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
password[0] = '\0';
|
||||
*foundRow = false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_STRING, password, passwordSize))
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, password, static_cast<int>(passwordSize)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
@@ -80,63 +79,36 @@ bool LoadSafeboxPasswordByAccountId(DWORD accountId, char* password, size_t pass
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
*found = true;
|
||||
size_t passwordLen = stmt.GetResultLength(0);
|
||||
if (passwordLen >= passwordSize)
|
||||
passwordLen = passwordSize - 1;
|
||||
|
||||
password[passwordLen] = '\0';
|
||||
*foundRow = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateSafeboxPasswordByAccountId(DWORD accountId, const char* password)
|
||||
bool LoadSafeboxTable(uint32_t accountId, const char* providedPassword, bool isMall, TSafeboxTable* safebox, bool* wrongPassword)
|
||||
{
|
||||
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;
|
||||
const std::string query = std::string("SELECT account_id, size, password FROM safebox") + GetTablePostfix() + " WHERE account_id=?";
|
||||
uint32_t loadedAccountId = 0;
|
||||
uint32_t 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)))
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &loadedAccountId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &loadedSize)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, storedPassword, sizeof(storedPassword)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
@@ -151,8 +123,14 @@ bool LoadSafeboxTableByAccountId(DWORD accountId, const char* providedPassword,
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
if (((!storedPassword[0]) && strcmp("000000", providedPassword))
|
||||
|| (storedPassword[0] && strcmp(storedPassword, providedPassword)))
|
||||
size_t passwordLen = stmt.GetResultLength(2);
|
||||
if (passwordLen >= sizeof(storedPassword))
|
||||
passwordLen = sizeof(storedPassword) - 1;
|
||||
|
||||
storedPassword[passwordLen] = '\0';
|
||||
|
||||
if (((passwordLen == 0) && strcmp("000000", providedPassword))
|
||||
|| ((passwordLen != 0) && strcmp(storedPassword, providedPassword)))
|
||||
{
|
||||
*wrongPassword = true;
|
||||
return true;
|
||||
@@ -186,16 +164,57 @@ void QueueSafeboxItemsLoad(CPeer* peer, CClientManager::ClientHandleInfo* info)
|
||||
"attrtype4, attrvalue4, "
|
||||
"attrtype5, attrvalue5, "
|
||||
"attrtype6, attrvalue6 "
|
||||
"FROM item%s WHERE owner_id=%u AND window='%s'",
|
||||
"FROM item%s WHERE owner_id=%d 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)
|
||||
bool UpdateSafeboxPassword(uint32_t accountId, const char* password)
|
||||
{
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_CHANGE_PASSWORD_ANSWER, dwHandle, sizeof(BYTE));
|
||||
pkPeer->EncodeBYTE(success);
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("UPDATE safebox") + GetTablePostfix() + " SET password=? WHERE account_id=?";
|
||||
|
||||
if (!PrepareClientPlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(password), SAFEBOX_PASSWORD_MAX_LEN + 1)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool FindPlayerIdByName(const char* playerName, uint32_t* playerId)
|
||||
{
|
||||
CStmt stmt;
|
||||
std::string query;
|
||||
|
||||
if (g_stLocale == "sjis")
|
||||
query = std::string("SELECT id FROM player") + GetTablePostfix() + " WHERE name=? collate sjis_japanese_ci";
|
||||
else
|
||||
query = std::string("SELECT id FROM player") + GetTablePostfix() + " WHERE name=?";
|
||||
|
||||
if (!PrepareClientPlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
*playerId = 0;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(playerName), CHARACTER_NAME_MAX_LEN + 1)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, playerId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
return stmt.Fetch();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,14 +631,14 @@ 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));
|
||||
|
||||
|
||||
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))
|
||||
if (!LoadSafeboxTable(packet->dwID, pi->safebox_password, bMall, safebox, &wrongPassword))
|
||||
{
|
||||
delete safebox;
|
||||
delete pi;
|
||||
@@ -650,6 +669,7 @@ void CClientManager::RESULT_SAFEBOX_LOAD(CPeer * pkPeer, SQLMsg * msg)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 쿼리에 에러가 있었으므로 응답할 경우 창고가 비어있는 것 처럼
|
||||
// 보이기 때문에 창고가 아얘 안열리는게 나음
|
||||
if (!msg->Get()->pSQLResult)
|
||||
@@ -716,15 +736,15 @@ void CClientManager::RESULT_SAFEBOX_LOAD(CPeer * pkPeer, SQLMsg * msg)
|
||||
continue;
|
||||
}
|
||||
|
||||
TItemTable * pItemTable = it->second;
|
||||
TItemTable * pItemTable = it->second;
|
||||
|
||||
int iPos;
|
||||
int iPos;
|
||||
|
||||
if ((iPos = grid.FindBlank(1, it->second->bSize)) == -1)
|
||||
break;
|
||||
if ((iPos = grid.FindBlank(1, it->second->bSize)) == -1)
|
||||
break;
|
||||
|
||||
TPlayerItem item;
|
||||
memset(&item, 0, sizeof(TPlayerItem));
|
||||
TPlayerItem item;
|
||||
memset(&item, 0, sizeof(TPlayerItem));
|
||||
|
||||
// DWORD dwSocket2 = 0;
|
||||
DWORD dwSocket2 = pItemAward->dwSocket2; //Fix
|
||||
@@ -903,28 +923,23 @@ void CClientManager::RESULT_SAFEBOX_CHANGE_SIZE(CPeer * pkPeer, SQLMsg * msg)
|
||||
|
||||
void CClientManager::QUERY_SAFEBOX_CHANGE_PASSWORD(CPeer * pkPeer, DWORD dwHandle, TSafeboxChangePasswordPacket * p)
|
||||
{
|
||||
BYTE result = 0;
|
||||
char storedPassword[SAFEBOX_PASSWORD_MAX_LEN + 1];
|
||||
bool found = false;
|
||||
bool foundRow = false;
|
||||
|
||||
if (!LoadSafeboxPasswordByAccountId(p->dwID, storedPassword, sizeof(storedPassword), &found))
|
||||
if (LoadSafeboxPassword(p->dwID, storedPassword, sizeof(storedPassword), &foundRow))
|
||||
{
|
||||
EncodeSafeboxPasswordChangeAnswer(pkPeer, dwHandle, 0);
|
||||
return;
|
||||
const bool oldPasswordMatches =
|
||||
(foundRow
|
||||
&& ((storedPassword[0] && !strcasecmp(storedPassword, p->szOldPassword))
|
||||
|| (!storedPassword[0] && !strcmp("000000", p->szOldPassword))));
|
||||
|
||||
if (oldPasswordMatches && UpdateSafeboxPassword(p->dwID, p->szNewPassword))
|
||||
result = 1;
|
||||
}
|
||||
|
||||
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);
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_CHANGE_PASSWORD_ANSWER, dwHandle, sizeof(BYTE));
|
||||
pkPeer->EncodeBYTE(result);
|
||||
}
|
||||
|
||||
void CClientManager::RESULT_SAFEBOX_CHANGE_PASSWORD(CPeer * pkPeer, SQLMsg * msg)
|
||||
@@ -932,29 +947,20 @@ void CClientManager::RESULT_SAFEBOX_CHANGE_PASSWORD(CPeer * pkPeer, SQLMsg * msg
|
||||
CQueryInfo * qi = (CQueryInfo *) msg->pvUserData;
|
||||
ClientHandleInfo * p = (ClientHandleInfo *) qi->pvData;
|
||||
DWORD dwHandle = p->dwHandle;
|
||||
BYTE result = 0;
|
||||
|
||||
if (msg->Get()->uiNumRows > 0)
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(msg->Get()->pSQLResult);
|
||||
|
||||
if ((row[0] && *row[0] && !strcasecmp(row[0], p->login)) || ((!row[0] || !*row[0]) && !strcmp("000000", p->login)))
|
||||
{
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
char escape_pwd[64];
|
||||
CDBManager::instance().EscapeString(escape_pwd, p->safebox_password, strlen(p->safebox_password));
|
||||
|
||||
snprintf(szQuery, sizeof(szQuery), "UPDATE safebox%s SET password='%s' WHERE account_id=%u", GetTablePostfix(), escape_pwd, p->account_id);
|
||||
|
||||
CDBManager::instance().ReturnQuery(szQuery, QID_SAFEBOX_CHANGE_PASSWORD_SECOND, pkPeer->GetHandle(), p);
|
||||
return;
|
||||
}
|
||||
result = UpdateSafeboxPassword(p->account_id, p->safebox_password) ? 1 : 0;
|
||||
}
|
||||
|
||||
delete p;
|
||||
|
||||
// Wrong old password
|
||||
pkPeer->EncodeHeader(DG::SAFEBOX_CHANGE_PASSWORD_ANSWER, dwHandle, sizeof(BYTE));
|
||||
pkPeer->EncodeBYTE(0);
|
||||
pkPeer->EncodeBYTE(result);
|
||||
}
|
||||
|
||||
void CClientManager::RESULT_SAFEBOX_CHANGE_PASSWORD_SECOND(CPeer * pkPeer, SQLMsg * msg)
|
||||
@@ -1940,21 +1946,10 @@ void CClientManager::UpdateLand(DWORD * pdw)
|
||||
// BLOCK_CHAT
|
||||
void CClientManager::BlockChat(TPacketBlockChat* p)
|
||||
{
|
||||
char szQuery[256];
|
||||
DWORD pid = 0;
|
||||
|
||||
if (g_stLocale == "sjis")
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT id FROM player%s WHERE name = '%s' collate sjis_japanese_ci", GetTablePostfix(), p->szName);
|
||||
else
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT id FROM player%s WHERE name = '%s'", GetTablePostfix(), p->szName);
|
||||
|
||||
auto pmsg = CDBManager::instance().DirectQuery(szQuery);
|
||||
SQLResult * pRes = pmsg->Get();
|
||||
|
||||
if (pRes->uiNumRows)
|
||||
if (FindPlayerIdByName(p->szName, &pid) && pid != 0)
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(pRes->pSQLResult);
|
||||
DWORD pid = strtoul(row[0], NULL, 10);
|
||||
|
||||
TPacketGDAddAffect pa;
|
||||
pa.dwPID = pid;
|
||||
pa.elem.dwType = 223;
|
||||
@@ -2500,8 +2495,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 +2655,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 +2891,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);
|
||||
|
||||
|
||||
@@ -16,109 +16,231 @@ extern int g_log;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool LoadPlayerIndexByAccountId(DWORD account_id, TAccountTable* account_table, bool* found)
|
||||
bool PreparePlayerStmt(CStmt& stmt, const std::string& query)
|
||||
{
|
||||
CAsyncSQL* sql = CDBManager::instance().GetDirectSQL(SQL_PLAYER);
|
||||
|
||||
if (!sql)
|
||||
{
|
||||
*found = false;
|
||||
sys_err("player SQL handle is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
char query[QUERY_MAX_LEN];
|
||||
snprintf(query, sizeof(query), "SELECT pid1, pid2, pid3, pid4, empire FROM player_index%s WHERE id = ?", GetTablePostfix());
|
||||
return stmt.Prepare(sql, query.c_str());
|
||||
}
|
||||
|
||||
CStmt stmt;
|
||||
unsigned int player_ids[PLAYER_PER_ACCOUNT] = {};
|
||||
unsigned int empire = 0;
|
||||
bool LoadPlayerIndexTable(uint32_t accountId, TAccountTable* accountTable)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("SELECT pid1, pid2, pid3, pid4, empire FROM player_index") + GetTablePostfix() + " WHERE id=?";
|
||||
uint32_t playerIds[PLAYER_PER_ACCOUNT] = {};
|
||||
uint8_t empire = 0;
|
||||
|
||||
if (!stmt.Prepare(CDBManager::instance().GetDirectSQL(SQL_PLAYER), query))
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
|
||||
{
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &playerIds[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &account_id))
|
||||
return false;
|
||||
if (!stmt.BindResult(MYSQL_TYPE_TINY, &empire))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
|
||||
if (!stmt.Execute() || !stmt.Fetch())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
|
||||
accountTable->players[i].dwID = playerIds[i];
|
||||
|
||||
accountTable->bEmpire = empire;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreatePlayerIndexRow(uint32_t accountId)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("INSERT INTO player_index") + GetTablePostfix() + " (id) VALUES(?)";
|
||||
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
void ApplyPlayerSummary(TAccountTable* accountTable, uint32_t playerId, const char* name, uint32_t job, uint32_t level, uint32_t progressValue,
|
||||
int32_t st, int32_t ht, int32_t dx, int32_t iq, uint32_t mainPart, uint32_t hairPart, int32_t x, int32_t y, uint32_t skillGroup, uint32_t changeName)
|
||||
{
|
||||
for (int i = 0; i < PLAYER_PER_ACCOUNT; ++i)
|
||||
{
|
||||
if (accountTable->players[i].dwID != playerId)
|
||||
continue;
|
||||
|
||||
CPlayerTableCache* cache = CClientManager::instance().GetPlayerCache(playerId);
|
||||
TPlayerTable* playerTable = cache ? cache->Get(false) : NULL;
|
||||
|
||||
if (playerTable)
|
||||
{
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &player_ids[i]))
|
||||
return false;
|
||||
strlcpy(accountTable->players[i].szName, playerTable->name, sizeof(accountTable->players[i].szName));
|
||||
accountTable->players[i].byJob = playerTable->job;
|
||||
accountTable->players[i].byLevel = playerTable->level;
|
||||
accountTable->players[i].dwPlayMinutes = playerTable->playtime;
|
||||
accountTable->players[i].byST = playerTable->st;
|
||||
accountTable->players[i].byHT = playerTable->ht;
|
||||
accountTable->players[i].byDX = playerTable->dx;
|
||||
accountTable->players[i].byIQ = playerTable->iq;
|
||||
accountTable->players[i].wMainPart = playerTable->parts[PART_MAIN];
|
||||
accountTable->players[i].wHairPart = playerTable->parts[PART_HAIR];
|
||||
accountTable->players[i].x = playerTable->x;
|
||||
accountTable->players[i].y = playerTable->y;
|
||||
accountTable->players[i].skill_group = playerTable->skill_group;
|
||||
accountTable->players[i].bChangeName = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
strlcpy(accountTable->players[i].szName, name, sizeof(accountTable->players[i].szName));
|
||||
accountTable->players[i].byJob = static_cast<uint8_t>(job);
|
||||
accountTable->players[i].byLevel = static_cast<uint8_t>(level);
|
||||
accountTable->players[i].dwPlayMinutes = progressValue;
|
||||
accountTable->players[i].byST = static_cast<uint8_t>(st);
|
||||
accountTable->players[i].byHT = static_cast<uint8_t>(ht);
|
||||
accountTable->players[i].byDX = static_cast<uint8_t>(dx);
|
||||
accountTable->players[i].byIQ = static_cast<uint8_t>(iq);
|
||||
accountTable->players[i].wMainPart = static_cast<uint16_t>(mainPart);
|
||||
accountTable->players[i].wHairPart = static_cast<uint16_t>(hairPart);
|
||||
accountTable->players[i].x = x;
|
||||
accountTable->players[i].y = y;
|
||||
accountTable->players[i].skill_group = static_cast<uint8_t>(skillGroup);
|
||||
accountTable->players[i].bChangeName = static_cast<uint8_t>(changeName);
|
||||
}
|
||||
|
||||
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;
|
||||
sys_log(0, "%s %lu %lu hair %u",
|
||||
accountTable->players[i].szName,
|
||||
accountTable->players[i].x,
|
||||
accountTable->players[i].y,
|
||||
accountTable->players[i].wHairPart);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool CreatePlayerIndexForAccount(DWORD account_id)
|
||||
bool LoadAccountPlayerSummaries(uint32_t accountId, TAccountTable* accountTable)
|
||||
{
|
||||
CStmt stmt;
|
||||
std::string query;
|
||||
uint32_t playerId = 0;
|
||||
char name[CHARACTER_NAME_MAX_LEN + 1] = {};
|
||||
uint32_t job = 0;
|
||||
uint32_t level = 0;
|
||||
uint32_t progressValue = 0;
|
||||
int32_t st = 0;
|
||||
int32_t ht = 0;
|
||||
int32_t dx = 0;
|
||||
int32_t iq = 0;
|
||||
uint32_t mainPart = 0;
|
||||
uint32_t hairPart = 0;
|
||||
int32_t x = 0;
|
||||
int32_t y = 0;
|
||||
uint32_t skillGroup = 0;
|
||||
uint32_t changeName = 0;
|
||||
|
||||
if (g_stLocale == "gb2312")
|
||||
{
|
||||
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;
|
||||
query = std::string("SELECT id, name, job, level, alignment, st, ht, dx, iq, part_main, part_hair, x, y, skill_group, change_name FROM player")
|
||||
+ GetTablePostfix() + " WHERE account_id=?";
|
||||
}
|
||||
|
||||
bool CountPlayersByNameExcludingId(const char* player_name, DWORD player_id, unsigned long long* count)
|
||||
else
|
||||
{
|
||||
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;
|
||||
query = std::string("SELECT id, name, job, level, playtime, st, ht, dx, iq, part_main, part_hair, x, y, skill_group, change_name FROM player")
|
||||
+ GetTablePostfix() + " WHERE account_id=?";
|
||||
}
|
||||
|
||||
bool UpdatePlayerName(DWORD player_id, const char* player_name)
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, name, sizeof(name))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &job)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &level)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &progressValue)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &st)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &ht)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &dx)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &iq)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &mainPart)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &hairPart)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &x)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &y)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &skillGroup)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &changeName))
|
||||
{
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
while (stmt.Fetch())
|
||||
{
|
||||
size_t nameLen = stmt.GetResultLength(1);
|
||||
if (nameLen >= sizeof(name))
|
||||
nameLen = sizeof(name) - 1;
|
||||
name[nameLen] = '\0';
|
||||
|
||||
ApplyPlayerSummary(accountTable, playerId, name, job, level, progressValue, st, ht, dx, iq, mainPart, hairPart, x, y, skillGroup, changeName);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CountPlayerNames(const char* playerName, uint32_t playerId, uint32_t* count)
|
||||
{
|
||||
CStmt stmt;
|
||||
std::string query;
|
||||
|
||||
if (g_stLocale == "sjis")
|
||||
query = std::string("SELECT COUNT(*) FROM player") + GetTablePostfix() + " WHERE name=? collate sjis_japanese_ci AND id <> ?";
|
||||
else
|
||||
query = std::string("SELECT COUNT(*) FROM player") + GetTablePostfix() + " WHERE name=? AND id <> ?";
|
||||
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(playerName), CHARACTER_NAME_MAX_LEN + 1)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute() && stmt.Fetch();
|
||||
}
|
||||
|
||||
bool UpdatePlayerName(uint32_t playerId, const char* playerName)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("UPDATE player") + GetTablePostfix() + " SET name=?, change_name=0 WHERE id=?";
|
||||
|
||||
if (!PreparePlayerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(playerName), CHARACTER_NAME_MAX_LEN + 1)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
bool CClientManager::InsertLogonAccount(const char * c_pszLogin, DWORD dwHandle, const char * c_pszIP)
|
||||
@@ -218,45 +340,97 @@ void CClientManager::QUERY_LOGIN_BY_KEY(CPeer * pkPeer, DWORD dwHandle, TPacketG
|
||||
return;
|
||||
}
|
||||
|
||||
TAccountTable * pkTab = new TAccountTable;
|
||||
memset(pkTab, 0, sizeof(TAccountTable));
|
||||
TAccountTable accountTable = {};
|
||||
accountTable.id = r.id;
|
||||
trim_and_lower(r.login, accountTable.login, sizeof(accountTable.login));
|
||||
strlcpy(accountTable.passwd, r.passwd, sizeof(accountTable.passwd));
|
||||
strlcpy(accountTable.social_id, r.social_id, sizeof(accountTable.social_id));
|
||||
strlcpy(accountTable.status, "OK", sizeof(accountTable.status));
|
||||
|
||||
pkTab->id = r.id;
|
||||
trim_and_lower(r.login, pkTab->login, sizeof(pkTab->login));
|
||||
strlcpy(pkTab->passwd, r.passwd, sizeof(pkTab->passwd));
|
||||
strlcpy(pkTab->social_id, r.social_id, sizeof(pkTab->social_id));
|
||||
strlcpy(pkTab->status, "OK", sizeof(pkTab->status));
|
||||
sys_log(0, "LOGIN_BY_KEY success %s %lu %s", r.login, p->dwLoginKey, p->szIP);
|
||||
|
||||
ClientHandleInfo * info = new ClientHandleInfo(dwHandle);
|
||||
info->pAccountTable = pkTab;
|
||||
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))
|
||||
if (!LoadPlayerIndexTable(accountTable.id, &accountTable))
|
||||
{
|
||||
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, info->dwHandle);
|
||||
delete info->pAccountTable;
|
||||
delete info;
|
||||
return;
|
||||
}
|
||||
sys_log(0, "LOGIN_BY_KEY missing player_index for account %u", accountTable.id);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
sys_log(0, "RESULT_LOGIN_BY_KEY FAIL player_index's NULL : ID:%d", info->pAccountTable->id);
|
||||
CreatePlayerIndexRow(accountTable.id);
|
||||
|
||||
if (!CreatePlayerIndexForAccount(info->pAccountTable->id) ||
|
||||
!LoadPlayerIndexByAccountId(info->pAccountTable->id, info->pAccountTable, &found) ||
|
||||
!found)
|
||||
if (!LoadPlayerIndexTable(accountTable.id, &accountTable))
|
||||
{
|
||||
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, info->dwHandle);
|
||||
delete info->pAccountTable;
|
||||
delete info;
|
||||
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, dwHandle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!LoadAccountPlayerSummaries(accountTable.id, &accountTable))
|
||||
{
|
||||
pkPeer->EncodeReturn(DG::LOGIN_NOT_EXIST, dwHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InsertLogonAccount(accountTable.login, pkPeer->GetHandle(), p->szIP))
|
||||
{
|
||||
sys_log(0, "RESULT_LOGIN: already logon %s", accountTable.login);
|
||||
|
||||
TPacketDGLoginAlready packet;
|
||||
strlcpy(packet.szLogin, accountTable.login, sizeof(packet.szLogin));
|
||||
|
||||
pkPeer->EncodeHeader(DG::LOGIN_ALREADY, dwHandle, sizeof(TPacketDGLoginAlready));
|
||||
pkPeer->Encode(&packet, sizeof(packet));
|
||||
return;
|
||||
}
|
||||
|
||||
if (CLoginData* loginData = GetLoginDataByLogin(accountTable.login))
|
||||
memcpy(&loginData->GetAccountRef(), &accountTable, sizeof(TAccountTable));
|
||||
|
||||
pkPeer->EncodeHeader(DG::LOGIN_SUCCESS, dwHandle, sizeof(TAccountTable));
|
||||
pkPeer->Encode(&accountTable, sizeof(TAccountTable));
|
||||
}
|
||||
|
||||
void CClientManager::RESULT_LOGIN_BY_KEY(CPeer * peer, SQLMsg * msg)
|
||||
{
|
||||
CQueryInfo * qi = (CQueryInfo *) msg->pvUserData;
|
||||
ClientHandleInfo * info = (ClientHandleInfo *) qi->pvData;
|
||||
|
||||
if (msg->uiSQLErrno != 0)
|
||||
{
|
||||
peer->EncodeReturn(DG::LOGIN_NOT_EXIST, info->dwHandle);
|
||||
delete info;
|
||||
return;
|
||||
}
|
||||
|
||||
char szQuery[QUERY_MAX_LEN];
|
||||
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 +447,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,14 +722,15 @@ 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))
|
||||
uint32_t duplicateCount = 0;
|
||||
|
||||
if (!CountPlayerNames(p->name, p->pid, &duplicateCount))
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_FAILED, dwHandle, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (count != 0)
|
||||
if (duplicateCount != 0)
|
||||
{
|
||||
peer->EncodeHeader(DG::PLAYER_CREATE_ALREADY, dwHandle, 0);
|
||||
return;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +128,12 @@ std::unique_ptr<SQLMsg> CDBManager::DirectQuery(const char* c_pszQuery, int iSlo
|
||||
return msg;
|
||||
}
|
||||
|
||||
CAsyncSQL* CDBManager::GetDirectSQL(int iSlot)
|
||||
{
|
||||
assert(iSlot < SQL_MAX_NUM);
|
||||
return m_directSQL[iSlot].get();
|
||||
}
|
||||
|
||||
extern CPacketInfo g_query_info;
|
||||
extern int g_query_count[2];
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ 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(); }
|
||||
CAsyncSQL* GetDirectSQL(int iSlot = SQL_PLAYER);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
296
src/db/Main.cpp
296
src/db/Main.cpp
@@ -45,158 +45,6 @@ extern const char * _malloc_options;
|
||||
|
||||
extern void WriteVersion();
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SQLConnectionConfig
|
||||
{
|
||||
char host[64];
|
||||
char database[64];
|
||||
char user[64];
|
||||
char password[64];
|
||||
int port;
|
||||
};
|
||||
|
||||
const char* BoolState(bool value)
|
||||
{
|
||||
return value ? "on" : "off";
|
||||
}
|
||||
|
||||
const char* EmptyToLabel(const std::string& value, const char* fallback)
|
||||
{
|
||||
return value.empty() ? fallback : value.c_str();
|
||||
}
|
||||
|
||||
bool CopyEnvString(const char* env_name, char* dest, size_t dest_size)
|
||||
{
|
||||
const char* value = std::getenv(env_name);
|
||||
if (!value)
|
||||
return false;
|
||||
|
||||
strlcpy(dest, value, dest_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CopyEnvInt(const char* env_name, int* dest)
|
||||
{
|
||||
const char* value = std::getenv(env_name);
|
||||
if (!value)
|
||||
return false;
|
||||
|
||||
str_to_number(*dest, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasSQLConfig(const SQLConnectionConfig& config)
|
||||
{
|
||||
return config.host[0] && config.database[0] && config.user[0] && config.password[0];
|
||||
}
|
||||
|
||||
bool ParseSQLConfig(const char* value, SQLConnectionConfig* config, const char* label)
|
||||
{
|
||||
int token_count = sscanf(
|
||||
value,
|
||||
" %63s %63s %63s %63s %d ",
|
||||
config->host,
|
||||
config->database,
|
||||
config->user,
|
||||
config->password,
|
||||
&config->port);
|
||||
|
||||
if (token_count < 4 || !HasSQLConfig(*config))
|
||||
{
|
||||
fprintf(stderr, "%s syntax: <host db user password [port]>\n", label);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadSQLConfig(const char* key, const char* env_prefix, SQLConnectionConfig* config)
|
||||
{
|
||||
char line[256 + 1];
|
||||
bool loaded_from_file = false;
|
||||
|
||||
if (CConfig::instance().GetValue(key, line, sizeof(line) - 1))
|
||||
{
|
||||
if (!ParseSQLConfig(line, config, key))
|
||||
return false;
|
||||
|
||||
loaded_from_file = true;
|
||||
}
|
||||
|
||||
bool overridden = false;
|
||||
const std::string prefix = env_prefix;
|
||||
overridden |= CopyEnvString((prefix + "_HOST").c_str(), config->host, sizeof(config->host));
|
||||
overridden |= CopyEnvString((prefix + "_DB").c_str(), config->database, sizeof(config->database));
|
||||
overridden |= CopyEnvString((prefix + "_USER").c_str(), config->user, sizeof(config->user));
|
||||
overridden |= CopyEnvString((prefix + "_PASSWORD").c_str(), config->password, sizeof(config->password));
|
||||
overridden |= CopyEnvInt((prefix + "_PORT").c_str(), &config->port);
|
||||
|
||||
if (overridden)
|
||||
sys_log(0, "CONFIG: %s overridden from environment", key);
|
||||
|
||||
if (!loaded_from_file && !overridden)
|
||||
{
|
||||
sys_err("%s not configured", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasSQLConfig(*config))
|
||||
{
|
||||
sys_err("%s incomplete after applying config/environment overrides", key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectDatabase(int slot, const SQLConnectionConfig& config, const char* label)
|
||||
{
|
||||
sys_log(0, "connecting to MySQL server (%s)", label);
|
||||
|
||||
int retry_count = 5;
|
||||
do
|
||||
{
|
||||
if (CDBManager::instance().Connect(slot, config.host, config.port, config.database, config.user, config.password))
|
||||
{
|
||||
sys_log(0, " OK");
|
||||
return true;
|
||||
}
|
||||
|
||||
sys_log(0, " failed, retrying in 5 seconds");
|
||||
fprintf(stderr, " failed, retrying in 5 seconds");
|
||||
sleep(5);
|
||||
}
|
||||
while (retry_count--);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void LogStartupSummary(int heart_fps, int player_id_start)
|
||||
{
|
||||
sys_log(0,
|
||||
"[STARTUP] locale=%s table_postfix=%s player_db=%s player_id_start=%d heart_fps=%d test_server=%s log=%s hotbackup=%s",
|
||||
EmptyToLabel(g_stLocale, "<unset>"),
|
||||
EmptyToLabel(g_stTablePostfix, "<none>"),
|
||||
EmptyToLabel(g_stPlayerDBName, "<unset>"),
|
||||
player_id_start,
|
||||
heart_fps,
|
||||
BoolState(g_test_server),
|
||||
BoolState(g_log != 0),
|
||||
BoolState(g_bHotBackup)
|
||||
);
|
||||
|
||||
sys_log(0,
|
||||
"[STARTUP] cache_flush player=%d item=%d pricelist=%d logout=%d locale_name_column=%s",
|
||||
g_iPlayerCacheFlushSeconds,
|
||||
g_iItemCacheFlushSeconds,
|
||||
g_iItemPriceListTableCacheFlushSeconds,
|
||||
g_iLogoutSeconds,
|
||||
EmptyToLabel(g_stLocaleNameColumn, "<unset>")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void emergency_sig(int sig)
|
||||
{
|
||||
if (sig == SIGSEGV)
|
||||
@@ -243,9 +91,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,10 +108,8 @@ int main()
|
||||
sys_log(0, "WAITING_QUERY_COUNT %d", iCount);
|
||||
}
|
||||
|
||||
sys_log(0, "[SHUTDOWN] DB process exiting cleanly");
|
||||
|
||||
log_destroy();
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void emptybeat(LPHEART heart, int pulse)
|
||||
@@ -346,7 +190,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';
|
||||
}
|
||||
|
||||
@@ -393,43 +237,120 @@ int Start()
|
||||
g_stLocaleNameColumn = szBuf;
|
||||
}
|
||||
|
||||
SQLConnectionConfig player_sql = {};
|
||||
SQLConnectionConfig account_sql = {};
|
||||
SQLConnectionConfig common_sql = {};
|
||||
SQLConnectionConfig hotbackup_sql = {};
|
||||
char szAddr[64], szDB[64], szUser[64], szPassword[64];
|
||||
int iPort;
|
||||
char line[256+1];
|
||||
|
||||
if (!LoadSQLConfig("SQL_PLAYER", "METIN2_PLAYER_SQL", &player_sql))
|
||||
if (CConfig::instance().GetValue("SQL_PLAYER", line, 256))
|
||||
{
|
||||
sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort);
|
||||
sys_log(0, "connecting to MySQL server (player)");
|
||||
|
||||
int iRetry = 5;
|
||||
|
||||
do
|
||||
{
|
||||
if (CDBManager::instance().Connect(SQL_PLAYER, szAddr, iPort, szDB, szUser, szPassword))
|
||||
{
|
||||
sys_log(0, " OK");
|
||||
break;
|
||||
}
|
||||
|
||||
sys_log(0, " failed, retrying in 5 seconds");
|
||||
fprintf(stderr, " failed, retrying in 5 seconds");
|
||||
sleep(5);
|
||||
} while (iRetry--);
|
||||
fprintf(stderr, "Success PLAYER\n");
|
||||
SetPlayerDBName(szDB);
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_err("SQL_PLAYER not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ConnectDatabase(SQL_PLAYER, player_sql, "player"))
|
||||
if (CConfig::instance().GetValue("SQL_ACCOUNT", line, 256))
|
||||
{
|
||||
sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort);
|
||||
sys_log(0, "connecting to MySQL server (account)");
|
||||
|
||||
int iRetry = 5;
|
||||
|
||||
do
|
||||
{
|
||||
if (CDBManager::instance().Connect(SQL_ACCOUNT, szAddr, iPort, szDB, szUser, szPassword))
|
||||
{
|
||||
sys_log(0, " OK");
|
||||
break;
|
||||
}
|
||||
|
||||
sys_log(0, " failed, retrying in 5 seconds");
|
||||
fprintf(stderr, " failed, retrying in 5 seconds");
|
||||
sleep(5);
|
||||
} while (iRetry--);
|
||||
fprintf(stderr, "Success ACCOUNT\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_err("SQL_ACCOUNT not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Success PLAYER\n");
|
||||
SetPlayerDBName(player_sql.database);
|
||||
if (CConfig::instance().GetValue("SQL_COMMON", line, 256))
|
||||
{
|
||||
sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort);
|
||||
sys_log(0, "connecting to MySQL server (common)");
|
||||
|
||||
if (!LoadSQLConfig("SQL_ACCOUNT", "METIN2_ACCOUNT_SQL", &account_sql))
|
||||
int iRetry = 5;
|
||||
|
||||
do
|
||||
{
|
||||
if (CDBManager::instance().Connect(SQL_COMMON, szAddr, iPort, szDB, szUser, szPassword))
|
||||
{
|
||||
sys_log(0, " OK");
|
||||
break;
|
||||
}
|
||||
|
||||
sys_log(0, " failed, retrying in 5 seconds");
|
||||
fprintf(stderr, " failed, retrying in 5 seconds");
|
||||
sleep(5);
|
||||
} while (iRetry--);
|
||||
fprintf(stderr, "Success COMMON\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_err("SQL_COMMON not configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ConnectDatabase(SQL_ACCOUNT, account_sql, "account"))
|
||||
if (CConfig::instance().GetValue("SQL_HOTBACKUP", line, 256))
|
||||
{
|
||||
sscanf(line, " %s %s %s %s %d ", szAddr, szDB, szUser, szPassword, &iPort);
|
||||
sys_log(0, "connecting to MySQL server (hotbackup)");
|
||||
|
||||
int iRetry = 5;
|
||||
|
||||
do
|
||||
{
|
||||
if (CDBManager::instance().Connect(SQL_HOTBACKUP, szAddr, iPort, szDB, szUser, szPassword))
|
||||
{
|
||||
sys_log(0, " OK");
|
||||
break;
|
||||
}
|
||||
|
||||
sys_log(0, " failed, retrying in 5 seconds");
|
||||
fprintf(stderr, " failed, retrying in 5 seconds");
|
||||
sleep(5);
|
||||
}
|
||||
while (iRetry--);
|
||||
|
||||
fprintf(stderr, "Success HOTBACKUP\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
sys_err("SQL_HOTBACKUP not configured");
|
||||
return false;
|
||||
|
||||
fprintf(stderr, "Success ACCOUNT\n");
|
||||
|
||||
if (!LoadSQLConfig("SQL_COMMON", "METIN2_COMMON_SQL", &common_sql))
|
||||
return false;
|
||||
|
||||
if (!ConnectDatabase(SQL_COMMON, common_sql, "common"))
|
||||
return false;
|
||||
|
||||
fprintf(stderr, "Success COMMON\n");
|
||||
|
||||
if (!LoadSQLConfig("SQL_HOTBACKUP", "METIN2_HOTBACKUP_SQL", &hotbackup_sql))
|
||||
return false;
|
||||
|
||||
if (!ConnectDatabase(SQL_HOTBACKUP, hotbackup_sql, "hotbackup"))
|
||||
return false;
|
||||
|
||||
fprintf(stderr, "Success HOTBACKUP\n");
|
||||
}
|
||||
|
||||
if (!CNetPoller::instance().Create())
|
||||
{
|
||||
@@ -453,8 +374,6 @@ int Start()
|
||||
return false;
|
||||
}
|
||||
|
||||
LogStartupSummary(heart_beat, iIDStart);
|
||||
|
||||
#ifndef OS_WINDOWS
|
||||
signal(SIGUSR1, emergency_sig);
|
||||
#endif
|
||||
@@ -490,3 +409,4 @@ const char * GetPlayerDBName()
|
||||
{
|
||||
return g_stPlayerDBName.c_str();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,6 @@ bool CNetPoller::Create()
|
||||
return false;
|
||||
}
|
||||
|
||||
sys_log(0, "[STARTUP] fdwatch backend=%s descriptor_limit=%d",
|
||||
fdwatch_backend_name(fdwatch_get_backend(m_fdWatcher)),
|
||||
fdwatch_get_descriptor_limit(m_fdWatcher));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,11 +1,181 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include "config.h"
|
||||
#include "char.h"
|
||||
#include "char_manager.h"
|
||||
#include "db.h"
|
||||
#include "guild_manager.h"
|
||||
#include "marriage.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
struct PlayerIndexEmpireData
|
||||
{
|
||||
uint32_t accountId = 0;
|
||||
std::array<uint32_t, 4> playerIds = {};
|
||||
};
|
||||
|
||||
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 LoadPlayerIndexEmpireData(uint32_t playerId, uint8_t empire, PlayerIndexEmpireData& data)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("SELECT id, pid1, pid2, pid3, pid4 FROM player_index") + get_table_postfix()
|
||||
+ " WHERE (pid1=? OR pid2=? OR pid3=? OR pid4=?) AND empire=?";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_TINY, &empire))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONG, &data.accountId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &data.playerIds[0])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &data.playerIds[1])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &data.playerIds[2])
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &data.playerIds[3]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute() || stmt.iRows == 0 || !stmt.Fetch())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadGuildIdByPlayerId(uint32_t playerId, uint32_t& guildId)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("SELECT guild_id FROM guild_member") + get_table_postfix() + " WHERE pid=?";
|
||||
|
||||
guildId = 0;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &guildId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
return stmt.Fetch();
|
||||
}
|
||||
|
||||
bool UpdatePlayerIndexEmpire(uint32_t playerId, uint8_t currentEmpire, uint8_t newEmpire)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("UPDATE player_index") + get_table_postfix()
|
||||
+ " SET empire=? WHERE (pid1=? OR pid2=? OR pid3=? OR pid4=?) AND empire=?";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_TINY, &newEmpire)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_TINY, ¤tEmpire))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool LoadChangeEmpireCountForAccount(uint32_t accountId, uint32_t& count, bool& found)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = "SELECT change_count FROM change_empire WHERE account_id=?";
|
||||
|
||||
count = 0;
|
||||
found = false;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertChangeEmpireCount(uint32_t accountId, uint32_t count)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = "INSERT INTO change_empire VALUES(?, ?, NOW())";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &accountId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool UpdateChangeEmpireCount(uint32_t accountId, uint32_t count)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = "UPDATE change_empire SET change_count=? WHERE account_id=?";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &count)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &accountId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Return Value
|
||||
@@ -21,31 +191,12 @@ int CHARACTER::ChangeEmpire(BYTE empire)
|
||||
if (GetEmpire() == empire)
|
||||
return 1;
|
||||
|
||||
char szQuery[1024+1];
|
||||
DWORD dwAID;
|
||||
DWORD dwPID[4];
|
||||
memset(dwPID, 0, sizeof(dwPID));
|
||||
PlayerIndexEmpireData indexData;
|
||||
|
||||
{
|
||||
// 1. 내 계정의 모든 pid를 얻어 온다
|
||||
snprintf(szQuery, sizeof(szQuery),
|
||||
"SELECT id, pid1, pid2, pid3, pid4 FROM player_index%s WHERE pid1=%u OR pid2=%u OR pid3=%u OR pid4=%u AND empire=%u",
|
||||
get_table_postfix(), GetPlayerID(), GetPlayerID(), GetPlayerID(), GetPlayerID(), GetEmpire());
|
||||
|
||||
auto msg = DBManager::instance().DirectQuery(szQuery);
|
||||
|
||||
if (msg->Get()->uiNumRows == 0)
|
||||
{
|
||||
if (!LoadPlayerIndexEmpireData(GetPlayerID(), GetEmpire(), indexData))
|
||||
return 0;
|
||||
}
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(msg->Get()->pSQLResult);
|
||||
|
||||
str_to_number(dwAID, row[0]);
|
||||
str_to_number(dwPID[0], row[1]);
|
||||
str_to_number(dwPID[1], row[2]);
|
||||
str_to_number(dwPID[2], row[3]);
|
||||
str_to_number(dwPID[3], row[4]);
|
||||
}
|
||||
|
||||
const int loop = 4;
|
||||
@@ -53,36 +204,21 @@ int CHARACTER::ChangeEmpire(BYTE empire)
|
||||
{
|
||||
// 2. 각 캐릭터의 길드 정보를 얻어온다.
|
||||
// 한 캐릭터라도 길드에 가입 되어 있다면, 제국 이동을 할 수 없다.
|
||||
DWORD dwGuildID[4];
|
||||
CGuild * pGuild[4];
|
||||
|
||||
for (int i = 0; i < loop; ++i)
|
||||
{
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT guild_id FROM guild_member%s WHERE pid=%u", get_table_postfix(), dwPID[i]);
|
||||
uint32_t guildId = 0;
|
||||
CGuild* guild = NULL;
|
||||
|
||||
auto pMsg = DBManager::instance().DirectQuery(szQuery);
|
||||
if (indexData.playerIds[i] == 0)
|
||||
continue;
|
||||
|
||||
if (pMsg != NULL)
|
||||
{
|
||||
if (pMsg->Get()->uiNumRows > 0)
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
|
||||
if (!LoadGuildIdByPlayerId(indexData.playerIds[i], guildId))
|
||||
return 0;
|
||||
|
||||
str_to_number(dwGuildID[i], row[0]);
|
||||
guild = CGuildManager::instance().FindGuild(guildId);
|
||||
|
||||
pGuild[i] = CGuildManager::instance().FindGuild(dwGuildID[i]);
|
||||
|
||||
if (pGuild[i] != NULL)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dwGuildID[i] = 0;
|
||||
pGuild[i] = NULL;
|
||||
}
|
||||
}
|
||||
if (guild != NULL)
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,19 +227,17 @@ int CHARACTER::ChangeEmpire(BYTE empire)
|
||||
// 한 캐릭터라도 결혼 상태라면 제국 이동을 할 수 없다.
|
||||
for (int i = 0; i < loop; ++i)
|
||||
{
|
||||
if (marriage::CManager::instance().IsEngagedOrMarried(dwPID[i]) == true)
|
||||
if (indexData.playerIds[i] == 0)
|
||||
continue;
|
||||
|
||||
if (marriage::CManager::instance().IsEngagedOrMarried(indexData.playerIds[i]) == true)
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// 4. db의 제국 정보를 업데이트 한다.
|
||||
snprintf(szQuery, sizeof(szQuery), "UPDATE player_index%s SET empire=%u WHERE pid1=%u OR pid2=%u OR pid3=%u OR pid4=%u AND empire=%u",
|
||||
get_table_postfix(), empire, GetPlayerID(), GetPlayerID(), GetPlayerID(), GetPlayerID(), GetEmpire());
|
||||
|
||||
auto msg = DBManager::instance().DirectQuery(szQuery);
|
||||
|
||||
if (msg->Get()->uiAffectedRows > 0)
|
||||
if (UpdatePlayerIndexEmpire(GetPlayerID(), GetEmpire(), empire))
|
||||
{
|
||||
// 5. 제국 변경 이력을 추가한다.
|
||||
SetChangeEmpireCount();
|
||||
@@ -117,82 +251,45 @@ int CHARACTER::ChangeEmpire(BYTE empire)
|
||||
|
||||
int CHARACTER::GetChangeEmpireCount() const
|
||||
{
|
||||
char szQuery[1024+1];
|
||||
DWORD dwAID = GetAID();
|
||||
uint32_t count = 0;
|
||||
bool found = false;
|
||||
const uint32_t accountId = GetAID();
|
||||
|
||||
if (dwAID == 0)
|
||||
if (accountId == 0)
|
||||
return 0;
|
||||
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT change_count FROM change_empire WHERE account_id = %u", dwAID);
|
||||
if (!LoadChangeEmpireCountForAccount(accountId, count, found) || !found)
|
||||
return 0;
|
||||
|
||||
auto pMsg = DBManager::instance().DirectQuery(szQuery);
|
||||
|
||||
if (pMsg != NULL)
|
||||
{
|
||||
if (pMsg->Get()->uiNumRows == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
|
||||
|
||||
DWORD count = 0;
|
||||
str_to_number(count, row[0]);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return count;
|
||||
}
|
||||
|
||||
void CHARACTER::SetChangeEmpireCount()
|
||||
{
|
||||
char szQuery[1024+1];
|
||||
const uint32_t accountId = GetAID();
|
||||
uint32_t count = 0;
|
||||
bool found = false;
|
||||
|
||||
DWORD dwAID = GetAID();
|
||||
if (accountId == 0)
|
||||
return;
|
||||
|
||||
if (dwAID == 0) return;
|
||||
if (!LoadChangeEmpireCountForAccount(accountId, count, found))
|
||||
return;
|
||||
|
||||
int count = GetChangeEmpireCount();
|
||||
++count;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
count++;
|
||||
snprintf(szQuery, sizeof(szQuery), "INSERT INTO change_empire VALUES(%u, %d, NOW())", dwAID, count);
|
||||
}
|
||||
if (!found)
|
||||
InsertChangeEmpireCount(accountId, count);
|
||||
else
|
||||
{
|
||||
count++;
|
||||
snprintf(szQuery, sizeof(szQuery), "UPDATE change_empire SET change_count=%d WHERE account_id=%u", count, dwAID);
|
||||
}
|
||||
|
||||
DBManager::instance().DirectQuery(szQuery);
|
||||
UpdateChangeEmpireCount(accountId, count);
|
||||
}
|
||||
|
||||
DWORD CHARACTER::GetAID() const
|
||||
{
|
||||
char szQuery[1024+1];
|
||||
DWORD dwAID = 0;
|
||||
PlayerIndexEmpireData indexData;
|
||||
|
||||
snprintf(szQuery, sizeof(szQuery), "SELECT id FROM player_index%s WHERE pid1=%u OR pid2=%u OR pid3=%u OR pid4=%u AND empire=%u",
|
||||
get_table_postfix(), GetPlayerID(), GetPlayerID(), GetPlayerID(), GetPlayerID(), GetEmpire());
|
||||
|
||||
auto pMsg = DBManager::instance().DirectQuery(szQuery);
|
||||
|
||||
if (pMsg)
|
||||
{
|
||||
if (pMsg->Get()->uiNumRows == 0)
|
||||
return 0;
|
||||
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
|
||||
|
||||
str_to_number(dwAID, row[0]);
|
||||
|
||||
return dwAID;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!LoadPlayerIndexEmpireData(GetPlayerID(), GetEmpire(), indexData))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return indexData.accountId;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#include "stdafx.h"
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#ifndef OS_WINDOWS
|
||||
#include <ifaddrs.h>
|
||||
@@ -20,6 +24,161 @@
|
||||
|
||||
using std::string;
|
||||
|
||||
namespace
|
||||
{
|
||||
enum class ESqlConfigIndex : size_t
|
||||
{
|
||||
Account = 0,
|
||||
Player = 1,
|
||||
Common = 2,
|
||||
Count = 3
|
||||
};
|
||||
|
||||
struct SqlConnectionConfig
|
||||
{
|
||||
std::string host;
|
||||
std::string user;
|
||||
std::string password;
|
||||
std::string database;
|
||||
int port = 0;
|
||||
|
||||
bool IsConfigured() const
|
||||
{
|
||||
return !host.empty() && !user.empty() && !password.empty() && !database.empty();
|
||||
}
|
||||
};
|
||||
|
||||
bool ReadEnvString(const char* envName, std::string& output)
|
||||
{
|
||||
const char* envValue = std::getenv(envName);
|
||||
if (!envValue || !*envValue)
|
||||
return false;
|
||||
|
||||
output = envValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadEnvPort(const char* envName, int& output)
|
||||
{
|
||||
const char* envValue = std::getenv(envName);
|
||||
if (!envValue || !*envValue)
|
||||
return false;
|
||||
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
unsigned long parsed = std::strtoul(envValue, &end, 10);
|
||||
if (errno != 0 || end == envValue || *end != '\0' || parsed > std::numeric_limits<uint16_t>::max())
|
||||
{
|
||||
fprintf(stderr, "Invalid %s value: %s\n", envName, envValue);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
output = static_cast<int>(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
void LogEnvOverride(const char* envName)
|
||||
{
|
||||
fprintf(stdout, "CONFIG: using %s override\n", envName);
|
||||
}
|
||||
|
||||
void ApplySqlEnvOverrides(const char* prefix, SqlConnectionConfig& config)
|
||||
{
|
||||
char envName[64];
|
||||
|
||||
snprintf(envName, sizeof(envName), "METIN2_%s_HOST", prefix);
|
||||
if (ReadEnvString(envName, config.host))
|
||||
LogEnvOverride(envName);
|
||||
|
||||
snprintf(envName, sizeof(envName), "METIN2_%s_USER", prefix);
|
||||
if (ReadEnvString(envName, config.user))
|
||||
LogEnvOverride(envName);
|
||||
|
||||
snprintf(envName, sizeof(envName), "METIN2_%s_PASSWORD", prefix);
|
||||
if (ReadEnvString(envName, config.password))
|
||||
LogEnvOverride(envName);
|
||||
|
||||
snprintf(envName, sizeof(envName), "METIN2_%s_DATABASE", prefix);
|
||||
if (ReadEnvString(envName, config.database))
|
||||
LogEnvOverride(envName);
|
||||
|
||||
snprintf(envName, sizeof(envName), "METIN2_%s_PORT", prefix);
|
||||
if (ReadEnvPort(envName, config.port))
|
||||
LogEnvOverride(envName);
|
||||
}
|
||||
|
||||
void ParseSqlConfigOrExit(const char* tokenName, const char* value, SqlConnectionConfig& config)
|
||||
{
|
||||
char host[64];
|
||||
char user[64];
|
||||
char password[64];
|
||||
char database[64];
|
||||
int port = 0;
|
||||
|
||||
*host = '\0';
|
||||
*user = '\0';
|
||||
*password = '\0';
|
||||
*database = '\0';
|
||||
|
||||
const char* line = two_arguments(value, host, sizeof(host), user, sizeof(user));
|
||||
line = two_arguments(line, password, sizeof(password), database, sizeof(database));
|
||||
|
||||
if (line[0])
|
||||
{
|
||||
char portBuffer[32];
|
||||
one_argument(line, portBuffer, sizeof(portBuffer));
|
||||
str_to_number(port, portBuffer);
|
||||
}
|
||||
|
||||
if (!*host || !*user || !*password || !*database)
|
||||
{
|
||||
fprintf(stderr, "%s syntax: %s <host user password db [port]>\n", tokenName, tokenName);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
config.host = host;
|
||||
config.user = user;
|
||||
config.password = password;
|
||||
config.database = database;
|
||||
config.port = port;
|
||||
}
|
||||
|
||||
void ValidateSqlConfigOrExit(const char* label, const SqlConnectionConfig& config)
|
||||
{
|
||||
if (config.IsConfigured())
|
||||
return;
|
||||
|
||||
fprintf(stderr, "%s must be configured as <host user password db [port]>\n", label);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void ApplyAdminPagePasswordEnvOverride()
|
||||
{
|
||||
if (ReadEnvString("METIN2_ADMINPAGE_PASSWORD", g_stAdminPagePassword))
|
||||
LogEnvOverride("METIN2_ADMINPAGE_PASSWORD");
|
||||
}
|
||||
|
||||
void ValidateAdminPageConfigOrExit()
|
||||
{
|
||||
if (!IsAdminPageEnabled())
|
||||
{
|
||||
if (!g_stAdminPageIP.empty())
|
||||
{
|
||||
fprintf(stderr, "ADMIN_PAGE_PASSWORD must be configured when adminpage_ip is set\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
fprintf(stdout, "ADMIN_PAGE: disabled\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_stAdminPageIP.empty())
|
||||
fprintf(stdout, "ADMIN_PAGE: enabled without IP restriction\n");
|
||||
else
|
||||
fprintf(stdout, "ADMIN_PAGE: enabled for %zu IP entries\n", g_stAdminPageIP.size());
|
||||
}
|
||||
}
|
||||
|
||||
BYTE g_bChannel = 0;
|
||||
WORD mother_port = 50080;
|
||||
int passes_per_sec = 25;
|
||||
@@ -195,124 +354,12 @@ static void FN_log_adminpage()
|
||||
++iter;
|
||||
}
|
||||
|
||||
sys_log(1, "ADMIN_PAGE_PASSWORD = %s", g_stAdminPagePassword.empty() ? "[disabled]" : "[configured]");
|
||||
sys_log(1, "ADMIN_PAGE_PASSWORD = %s", IsAdminPageEnabled() ? "[configured]" : "[disabled]");
|
||||
}
|
||||
|
||||
static void FN_apply_adminpage_password_env()
|
||||
bool IsAdminPageEnabled()
|
||||
{
|
||||
const char* env_password = std::getenv("METIN2_ADMINPAGE_PASSWORD");
|
||||
|
||||
if (!env_password)
|
||||
return;
|
||||
|
||||
g_stAdminPagePassword = env_password;
|
||||
}
|
||||
|
||||
static bool FN_copy_env_string(const char* env_name, char* dest, size_t dest_size)
|
||||
{
|
||||
const char* value = std::getenv(env_name);
|
||||
if (!value)
|
||||
return false;
|
||||
|
||||
strlcpy(dest, value, dest_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FN_copy_env_int(const char* env_name, int* dest)
|
||||
{
|
||||
const char* value = std::getenv(env_name);
|
||||
if (!value)
|
||||
return false;
|
||||
|
||||
str_to_number(*dest, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FN_has_sql_config(const char* host, const char* user, const char* password, const char* database)
|
||||
{
|
||||
return host[0] && user[0] && password[0] && database[0];
|
||||
}
|
||||
|
||||
static bool FN_parse_game_sql_config(
|
||||
const char* value,
|
||||
char* host,
|
||||
size_t host_size,
|
||||
char* user,
|
||||
size_t user_size,
|
||||
char* password,
|
||||
size_t password_size,
|
||||
char* database,
|
||||
size_t database_size,
|
||||
int* port,
|
||||
const char* label)
|
||||
{
|
||||
const char* line = two_arguments(value, host, host_size, user, user_size);
|
||||
line = two_arguments(line, password, password_size, database, database_size);
|
||||
|
||||
if (line[0])
|
||||
{
|
||||
char port_buf[256];
|
||||
one_argument(line, port_buf, sizeof(port_buf));
|
||||
str_to_number(*port, port_buf);
|
||||
}
|
||||
|
||||
if (!FN_has_sql_config(host, user, password, database))
|
||||
{
|
||||
fprintf(stderr, "%s syntax: <host user password db [port]>\n", label);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool FN_apply_game_sql_env(
|
||||
const char* env_prefix,
|
||||
char* host,
|
||||
size_t host_size,
|
||||
char* user,
|
||||
size_t user_size,
|
||||
char* password,
|
||||
size_t password_size,
|
||||
char* database,
|
||||
size_t database_size,
|
||||
int* port,
|
||||
const char* label)
|
||||
{
|
||||
bool overridden = false;
|
||||
std::string prefix = env_prefix;
|
||||
|
||||
overridden |= FN_copy_env_string((prefix + "_HOST").c_str(), host, host_size);
|
||||
overridden |= FN_copy_env_string((prefix + "_USER").c_str(), user, user_size);
|
||||
overridden |= FN_copy_env_string((prefix + "_PASSWORD").c_str(), password, password_size);
|
||||
overridden |= FN_copy_env_string((prefix + "_DB").c_str(), database, database_size);
|
||||
overridden |= FN_copy_env_int((prefix + "_PORT").c_str(), port);
|
||||
|
||||
if (overridden)
|
||||
sys_log(0, "CONFIG: %s overridden from environment", label);
|
||||
|
||||
return overridden;
|
||||
}
|
||||
|
||||
static void FN_apply_db_runtime_env()
|
||||
{
|
||||
const char* env_addr = std::getenv("METIN2_DB_ADDR");
|
||||
if (env_addr)
|
||||
{
|
||||
strlcpy(db_addr, env_addr, sizeof(db_addr));
|
||||
for (int n = 0; n < ADDRESS_MAX_LEN; ++n)
|
||||
{
|
||||
if (db_addr[n] == ' ')
|
||||
db_addr[n] = '\0';
|
||||
}
|
||||
sys_log(0, "CONFIG: DB_ADDR overridden from environment");
|
||||
}
|
||||
|
||||
int env_port = 0;
|
||||
if (FN_copy_env_int("METIN2_DB_PORT", &env_port))
|
||||
{
|
||||
db_port = static_cast<WORD>(env_port);
|
||||
sys_log(0, "CONFIG: DB_PORT overridden from environment");
|
||||
}
|
||||
return !g_stAdminPagePassword.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -457,37 +504,14 @@ void config_init(const string& st_localeServiceName)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
char db_host[3][64], db_user[3][64], db_pwd[3][64], db_db[3][64];
|
||||
// ... 아... db_port는 이미 있는데... 네이밍 어찌해야함...
|
||||
int mysql_db_port[3];
|
||||
|
||||
for (int n = 0; n < 3; ++n)
|
||||
{
|
||||
*db_host[n] = '\0';
|
||||
*db_user[n] = '\0';
|
||||
*db_pwd[n]= '\0';
|
||||
*db_db[n]= '\0';
|
||||
mysql_db_port[n] = 0;
|
||||
}
|
||||
|
||||
char log_host[64], log_user[64], log_pwd[64], log_db[64];
|
||||
int log_port = 0;
|
||||
|
||||
*log_host = '\0';
|
||||
*log_user = '\0';
|
||||
*log_pwd = '\0';
|
||||
*log_db = '\0';
|
||||
std::array<SqlConnectionConfig, static_cast<size_t>(ESqlConfigIndex::Count)> dbConfig;
|
||||
SqlConnectionConfig logConfig;
|
||||
|
||||
|
||||
// DB에서 로케일정보를 세팅하기위해서는 다른 세팅값보다 선행되어서
|
||||
// DB정보만 읽어와 로케일 세팅을 한후 다른 세팅을 적용시켜야한다.
|
||||
// 이유는 로케일관련된 초기화 루틴이 곳곳에 존재하기 때문.
|
||||
|
||||
bool isAccountSQL = false;
|
||||
bool isCommonSQL = false;
|
||||
bool isPlayerSQL = false;
|
||||
bool isLogSQL = false;
|
||||
|
||||
FILE* fp_common;
|
||||
if (!(fp_common = fopen("conf/game.txt", "r")))
|
||||
{
|
||||
@@ -500,69 +524,25 @@ void config_init(const string& st_localeServiceName)
|
||||
|
||||
TOKEN("account_sql")
|
||||
{
|
||||
if (!FN_parse_game_sql_config(value_string,
|
||||
db_host[0], sizeof(db_host[0]),
|
||||
db_user[0], sizeof(db_user[0]),
|
||||
db_pwd[0], sizeof(db_pwd[0]),
|
||||
db_db[0], sizeof(db_db[0]),
|
||||
&mysql_db_port[0],
|
||||
"ACCOUNT_SQL"))
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
isAccountSQL = true;
|
||||
ParseSqlConfigOrExit("account_sql", value_string, dbConfig[static_cast<size_t>(ESqlConfigIndex::Account)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
TOKEN("player_sql")
|
||||
{
|
||||
if (!FN_parse_game_sql_config(value_string,
|
||||
db_host[1], sizeof(db_host[1]),
|
||||
db_user[1], sizeof(db_user[1]),
|
||||
db_pwd[1], sizeof(db_pwd[1]),
|
||||
db_db[1], sizeof(db_db[1]),
|
||||
&mysql_db_port[1],
|
||||
"PLAYER_SQL"))
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
isPlayerSQL = true;
|
||||
ParseSqlConfigOrExit("player_sql", value_string, dbConfig[static_cast<size_t>(ESqlConfigIndex::Player)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
TOKEN("common_sql")
|
||||
{
|
||||
if (!FN_parse_game_sql_config(value_string,
|
||||
db_host[2], sizeof(db_host[2]),
|
||||
db_user[2], sizeof(db_user[2]),
|
||||
db_pwd[2], sizeof(db_pwd[2]),
|
||||
db_db[2], sizeof(db_db[2]),
|
||||
&mysql_db_port[2],
|
||||
"COMMON_SQL"))
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
isCommonSQL = true;
|
||||
ParseSqlConfigOrExit("common_sql", value_string, dbConfig[static_cast<size_t>(ESqlConfigIndex::Common)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
TOKEN("log_sql")
|
||||
{
|
||||
if (!FN_parse_game_sql_config(value_string,
|
||||
log_host, sizeof(log_host),
|
||||
log_user, sizeof(log_user),
|
||||
log_pwd, sizeof(log_pwd),
|
||||
log_db, sizeof(log_db),
|
||||
&log_port,
|
||||
"LOG_SQL"))
|
||||
{
|
||||
exit(1);
|
||||
}
|
||||
|
||||
isLogSQL = true;
|
||||
ParseSqlConfigOrExit("log_sql", value_string, logConfig);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -845,43 +825,11 @@ void config_init(const string& st_localeServiceName)
|
||||
}
|
||||
fclose(fp_common);
|
||||
|
||||
const bool account_env_override = FN_apply_game_sql_env(
|
||||
"METIN2_ACCOUNT_SQL",
|
||||
db_host[0], sizeof(db_host[0]),
|
||||
db_user[0], sizeof(db_user[0]),
|
||||
db_pwd[0], sizeof(db_pwd[0]),
|
||||
db_db[0], sizeof(db_db[0]),
|
||||
&mysql_db_port[0],
|
||||
"ACCOUNT_SQL");
|
||||
const bool player_env_override = FN_apply_game_sql_env(
|
||||
"METIN2_PLAYER_SQL",
|
||||
db_host[1], sizeof(db_host[1]),
|
||||
db_user[1], sizeof(db_user[1]),
|
||||
db_pwd[1], sizeof(db_pwd[1]),
|
||||
db_db[1], sizeof(db_db[1]),
|
||||
&mysql_db_port[1],
|
||||
"PLAYER_SQL");
|
||||
const bool common_env_override = FN_apply_game_sql_env(
|
||||
"METIN2_COMMON_SQL",
|
||||
db_host[2], sizeof(db_host[2]),
|
||||
db_user[2], sizeof(db_user[2]),
|
||||
db_pwd[2], sizeof(db_pwd[2]),
|
||||
db_db[2], sizeof(db_db[2]),
|
||||
&mysql_db_port[2],
|
||||
"COMMON_SQL");
|
||||
const bool log_env_override = FN_apply_game_sql_env(
|
||||
"METIN2_LOG_SQL",
|
||||
log_host, sizeof(log_host),
|
||||
log_user, sizeof(log_user),
|
||||
log_pwd, sizeof(log_pwd),
|
||||
log_db, sizeof(log_db),
|
||||
&log_port,
|
||||
"LOG_SQL");
|
||||
|
||||
isAccountSQL = isAccountSQL || account_env_override || FN_has_sql_config(db_host[0], db_user[0], db_pwd[0], db_db[0]);
|
||||
isPlayerSQL = isPlayerSQL || player_env_override || FN_has_sql_config(db_host[1], db_user[1], db_pwd[1], db_db[1]);
|
||||
isCommonSQL = isCommonSQL || common_env_override || FN_has_sql_config(db_host[2], db_user[2], db_pwd[2], db_db[2]);
|
||||
isLogSQL = isLogSQL || log_env_override || FN_has_sql_config(log_host, log_user, log_pwd, log_db);
|
||||
ApplySqlEnvOverrides("ACCOUNT_SQL", dbConfig[static_cast<size_t>(ESqlConfigIndex::Account)]);
|
||||
ApplySqlEnvOverrides("PLAYER_SQL", dbConfig[static_cast<size_t>(ESqlConfigIndex::Player)]);
|
||||
ApplySqlEnvOverrides("COMMON_SQL", dbConfig[static_cast<size_t>(ESqlConfigIndex::Common)]);
|
||||
ApplySqlEnvOverrides("LOG_SQL", logConfig);
|
||||
ApplyAdminPagePasswordEnvOverride();
|
||||
|
||||
FILE* fpOnlyForDB;
|
||||
|
||||
@@ -944,55 +892,15 @@ void config_init(const string& st_localeServiceName)
|
||||
//처리가 끝났으니 파일을 닫자.
|
||||
fclose(fpOnlyForDB);
|
||||
|
||||
// CONFIG_SQL_INFO_ERROR
|
||||
if (!isCommonSQL)
|
||||
{
|
||||
puts("LOAD_COMMON_SQL_INFO_FAILURE:");
|
||||
puts("");
|
||||
puts("CONFIG:");
|
||||
puts("------------------------------------------------");
|
||||
puts("COMMON_SQL: HOST USER PASSWORD DATABASE");
|
||||
puts("");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (g_bAuthServer && !isAccountSQL)
|
||||
{
|
||||
puts("LOAD_ACCOUNT_SQL_INFO_FAILURE:");
|
||||
puts("");
|
||||
puts("CONFIG:");
|
||||
puts("------------------------------------------------");
|
||||
puts("ACCOUNT_SQL: HOST USER PASSWORD DATABASE [PORT]");
|
||||
puts("");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!g_bAuthServer && !isPlayerSQL)
|
||||
{
|
||||
puts("LOAD_PLAYER_SQL_INFO_FAILURE:");
|
||||
puts("");
|
||||
puts("CONFIG:");
|
||||
puts("------------------------------------------------");
|
||||
puts("PLAYER_SQL: HOST USER PASSWORD DATABASE [PORT]");
|
||||
puts("");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!g_bAuthServer && !isLogSQL)
|
||||
{
|
||||
puts("LOAD_LOG_SQL_INFO_FAILURE:");
|
||||
puts("");
|
||||
puts("CONFIG:");
|
||||
puts("------------------------------------------------");
|
||||
puts("LOG_SQL: HOST USER PASSWORD DATABASE [PORT]");
|
||||
puts("");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
FN_apply_db_runtime_env();
|
||||
ValidateSqlConfigOrExit("COMMON_SQL", dbConfig[static_cast<size_t>(ESqlConfigIndex::Common)]);
|
||||
ValidateSqlConfigOrExit(g_bAuthServer ? "ACCOUNT_SQL" : "PLAYER_SQL", dbConfig[static_cast<size_t>(g_bAuthServer ? ESqlConfigIndex::Account : ESqlConfigIndex::Player)]);
|
||||
if (!g_bAuthServer)
|
||||
ValidateSqlConfigOrExit("LOG_SQL", logConfig);
|
||||
ValidateAdminPageConfigOrExit();
|
||||
|
||||
// Common DB 가 Locale 정보를 가지고 있기 때문에 가장 먼저 접속해야 한다.
|
||||
AccountDB::instance().Connect(db_host[2], mysql_db_port[2], db_user[2], db_pwd[2], db_db[2]);
|
||||
const SqlConnectionConfig& commonDb = dbConfig[static_cast<size_t>(ESqlConfigIndex::Common)];
|
||||
AccountDB::instance().Connect(commonDb.host.c_str(), commonDb.port, commonDb.user.c_str(), commonDb.password.c_str(), commonDb.database.c_str());
|
||||
|
||||
if (false == AccountDB::instance().IsConnected())
|
||||
{
|
||||
@@ -1038,13 +946,14 @@ void config_init(const string& st_localeServiceName)
|
||||
|
||||
AccountDB::instance().SetLocale(g_stLocale);
|
||||
|
||||
AccountDB::instance().ConnectAsync(db_host[2], mysql_db_port[2], db_user[2], db_pwd[2], db_db[2], g_stLocale.c_str());
|
||||
AccountDB::instance().ConnectAsync(commonDb.host.c_str(), commonDb.port, commonDb.user.c_str(), commonDb.password.c_str(), commonDb.database.c_str(), g_stLocale.c_str());
|
||||
|
||||
// Player DB 접속
|
||||
const SqlConnectionConfig& playerDb = dbConfig[static_cast<size_t>(g_bAuthServer ? ESqlConfigIndex::Account : ESqlConfigIndex::Player)];
|
||||
if (g_bAuthServer)
|
||||
DBManager::instance().Connect(db_host[0], mysql_db_port[0], db_user[0], db_pwd[0], db_db[0]);
|
||||
DBManager::instance().Connect(playerDb.host.c_str(), playerDb.port, playerDb.user.c_str(), playerDb.password.c_str(), playerDb.database.c_str());
|
||||
else
|
||||
DBManager::instance().Connect(db_host[1], mysql_db_port[1], db_user[1], db_pwd[1], db_db[1]);
|
||||
DBManager::instance().Connect(playerDb.host.c_str(), playerDb.port, playerDb.user.c_str(), playerDb.password.c_str(), playerDb.database.c_str());
|
||||
|
||||
if (!DBManager::instance().IsConnected())
|
||||
{
|
||||
@@ -1057,7 +966,7 @@ void config_init(const string& st_localeServiceName)
|
||||
if (false == g_bAuthServer) // 인증 서버가 아닐 경우
|
||||
{
|
||||
// Log DB 접속
|
||||
LogManager::instance().Connect(log_host, log_port, log_user, log_pwd, log_db);
|
||||
LogManager::instance().Connect(logConfig.host.c_str(), logConfig.port, logConfig.user.c_str(), logConfig.password.c_str(), logConfig.database.c_str());
|
||||
|
||||
if (!LogManager::instance().IsConnected())
|
||||
{
|
||||
@@ -1282,7 +1191,6 @@ void config_init(const string& st_localeServiceName)
|
||||
LoadStateUserCount();
|
||||
|
||||
CWarMapManager::instance().LoadWarMapInfo(NULL);
|
||||
FN_apply_adminpage_password_env();
|
||||
|
||||
FN_log_adminpage();
|
||||
}
|
||||
@@ -1392,3 +1300,4 @@ bool IsValidFileCRC(DWORD dwCRC)
|
||||
{
|
||||
return s_set_dwFileCRC.find(dwCRC) != s_set_dwFileCRC.end();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ enum
|
||||
void config_init(const std::string& st_localeServiceName); // default "" is CONFIG
|
||||
|
||||
extern char sql_addr[256];
|
||||
bool IsAdminPageEnabled();
|
||||
|
||||
extern WORD mother_port;
|
||||
extern WORD p2p_port;
|
||||
@@ -106,4 +107,3 @@ extern int gPlayerMaxLevel;
|
||||
extern bool g_BlockCharCreation;
|
||||
|
||||
#endif /* __INC_METIN_II_GAME_CONFIG_H__ */
|
||||
|
||||
|
||||
219
src/game/db.cpp
219
src/game/db.cpp
@@ -54,6 +54,60 @@ bool PrepareGameStmt(CStmt& stmt, const std::string& query)
|
||||
return stmt.Prepare(sql, query.c_str());
|
||||
}
|
||||
|
||||
bool CopyRequiredAuthString(const MYSQL_ROW row, int column, char* dst, size_t dstSize)
|
||||
{
|
||||
if (!row[column])
|
||||
{
|
||||
sys_err("error column %d", column);
|
||||
return false;
|
||||
}
|
||||
|
||||
strlcpy(dst, row[column], dstSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void CopyOptionalAuthNumber(const char* src, T& value)
|
||||
{
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
str_to_number(value, src);
|
||||
}
|
||||
|
||||
bool ParseAuthLoginRow(MYSQL_ROW row, AuthLoginData& auth)
|
||||
{
|
||||
int col = 0;
|
||||
|
||||
if (!CopyRequiredAuthString(row, col++, auth.encryptedPassword, sizeof(auth.encryptedPassword))
|
||||
|| !CopyRequiredAuthString(row, col++, auth.password, sizeof(auth.password))
|
||||
|| !CopyRequiredAuthString(row, col++, auth.socialId, sizeof(auth.socialId)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!row[col])
|
||||
{
|
||||
sys_err("error column %d", col);
|
||||
return false;
|
||||
}
|
||||
str_to_number(auth.accountId, row[col++]);
|
||||
|
||||
if (!CopyRequiredAuthString(row, col++, auth.status, sizeof(auth.status)))
|
||||
return false;
|
||||
|
||||
CopyOptionalAuthNumber(row[col++], auth.notAvailable);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_EXP]);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_ITEM]);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_SAFEBOX]);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_AUTOLOOT]);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_FISH_MIND]);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_MARRIAGE_FAST]);
|
||||
CopyOptionalAuthNumber(row[col++], auth.premiumTimes[PREMIUM_GOLD]);
|
||||
CopyOptionalAuthNumber(row[col], auth.createTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadAuthLoginData(const char* login, const char* passwd, AuthLoginData& auth, bool& found)
|
||||
{
|
||||
CStmt stmt;
|
||||
@@ -166,7 +220,9 @@ void FinalizeAuthLogin(LPDESC d, const char* login, const char* passwd, const Au
|
||||
sys_log(0, "Block Time %d ", strncmp(createDate, g_stBlockDate.c_str(), 8));
|
||||
}
|
||||
|
||||
if (strcmp(auth.encryptedPassword, auth.password))
|
||||
const int passwordDiff = strcmp(auth.encryptedPassword, auth.password);
|
||||
|
||||
if (passwordDiff)
|
||||
{
|
||||
RecordLoginFailure(d->GetHostName());
|
||||
LoginFailure(d, "WRONGPWD");
|
||||
@@ -439,11 +495,12 @@ void DBManager::AuthenticateLogin(LPDESC d, const char* login, const char* passw
|
||||
{
|
||||
AuthLoginData auth;
|
||||
bool found = false;
|
||||
const bool channelServiceLogin = IsChannelServiceLogin(login);
|
||||
|
||||
d->SetLogin(login);
|
||||
sys_log(0, "AUTH_LOGIN_DIRECT: START %u %p", d->GetLoginKey(), get_pointer(d));
|
||||
|
||||
if (IsChannelServiceLogin(login))
|
||||
if (channelServiceLogin)
|
||||
sys_log(0, "ChannelServiceLogin [%s]", login);
|
||||
|
||||
if (!LoadAuthLoginData(login, passwd, auth, found) || !found)
|
||||
@@ -488,162 +545,12 @@ void DBManager::AnalyzeReturnQuery(SQLMsg * pMsg)
|
||||
else
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
|
||||
int col = 0;
|
||||
AuthLoginData auth;
|
||||
|
||||
// PASSWORD('%s'), password, securitycode, social_id, id, status
|
||||
char szEncrytPassword[45 + 1];
|
||||
char szPassword[45 + 1];
|
||||
char szSocialID[SOCIAL_ID_MAX_LEN + 1];
|
||||
char szStatus[ACCOUNT_STATUS_MAX_LEN + 1];
|
||||
DWORD dwID = 0;
|
||||
if (ParseAuthLoginRow(row, auth))
|
||||
FinalizeAuthLogin(d, pinfo->login, pinfo->passwd, auth, "QID_AUTH_LOGIN");
|
||||
|
||||
if (!row[col])
|
||||
{
|
||||
sys_err("error column %d", col);
|
||||
M2_DELETE(pinfo);
|
||||
break;
|
||||
}
|
||||
|
||||
strlcpy(szEncrytPassword, row[col++], sizeof(szEncrytPassword));
|
||||
|
||||
if (!row[col])
|
||||
{
|
||||
sys_err("error column %d", col);
|
||||
M2_DELETE(pinfo);
|
||||
break;
|
||||
}
|
||||
|
||||
strlcpy(szPassword, row[col++], sizeof(szPassword));
|
||||
|
||||
if (!row[col])
|
||||
{
|
||||
sys_err("error column %d", col);
|
||||
M2_DELETE(pinfo);
|
||||
break;
|
||||
}
|
||||
|
||||
strlcpy(szSocialID, row[col++], sizeof(szSocialID));
|
||||
|
||||
if (!row[col])
|
||||
{
|
||||
sys_err("error column %d", col);
|
||||
M2_DELETE(pinfo);
|
||||
break;
|
||||
}
|
||||
|
||||
str_to_number(dwID, row[col++]);
|
||||
|
||||
if (!row[col])
|
||||
{
|
||||
sys_err("error column %d", col);
|
||||
M2_DELETE(pinfo);
|
||||
break;
|
||||
}
|
||||
|
||||
strlcpy(szStatus, row[col++], sizeof(szStatus));
|
||||
|
||||
BYTE bNotAvail = 0;
|
||||
str_to_number(bNotAvail, row[col++]);
|
||||
|
||||
int aiPremiumTimes[PREMIUM_MAX_NUM];
|
||||
memset(&aiPremiumTimes, 0, sizeof(aiPremiumTimes));
|
||||
|
||||
char szCreateDate[256] = "00000000";
|
||||
|
||||
if (!g_iUseLocale)
|
||||
{
|
||||
str_to_number(aiPremiumTimes[PREMIUM_EXP], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_ITEM], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_SAFEBOX], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_AUTOLOOT], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_FISH_MIND], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_MARRIAGE_FAST], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_GOLD], row[col++]);
|
||||
}
|
||||
else
|
||||
{
|
||||
str_to_number(aiPremiumTimes[PREMIUM_EXP], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_ITEM], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_SAFEBOX], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_AUTOLOOT], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_FISH_MIND], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_MARRIAGE_FAST], row[col++]);
|
||||
str_to_number(aiPremiumTimes[PREMIUM_GOLD], row[col++]);
|
||||
|
||||
if (LC_IsEurope() || test_server)
|
||||
{
|
||||
long retValue = 0;
|
||||
str_to_number(retValue, row[col]);
|
||||
|
||||
time_t create_time = retValue;
|
||||
struct tm * tm1;
|
||||
tm1 = localtime(&create_time);
|
||||
strftime(szCreateDate, 255, "%Y%m%d", tm1);
|
||||
|
||||
sys_log(0, "Create_Time %d %s", retValue, szCreateDate);
|
||||
sys_log(0, "Block Time %d ", strncmp(szCreateDate, g_stBlockDate.c_str(), 8));
|
||||
}
|
||||
}
|
||||
|
||||
int nPasswordDiff = strcmp(szEncrytPassword, szPassword);
|
||||
|
||||
if (nPasswordDiff)
|
||||
{
|
||||
RecordLoginFailure(d->GetHostName());
|
||||
LoginFailure(d, "WRONGPWD");
|
||||
sys_log(0, " WRONGPWD");
|
||||
M2_DELETE(pinfo);
|
||||
}
|
||||
else if (bNotAvail)
|
||||
{
|
||||
LoginFailure(d, "NOTAVAIL");
|
||||
sys_log(0, " NOTAVAIL");
|
||||
M2_DELETE(pinfo);
|
||||
}
|
||||
else if (DESC_MANAGER::instance().FindByLoginName(pinfo->login))
|
||||
{
|
||||
LoginFailure(d, "ALREADY");
|
||||
sys_log(0, " ALREADY");
|
||||
M2_DELETE(pinfo);
|
||||
}
|
||||
else if (strcmp(szStatus, "OK"))
|
||||
{
|
||||
LoginFailure(d, szStatus);
|
||||
sys_log(0, " STATUS: %s", szStatus);
|
||||
M2_DELETE(pinfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LC_IsEurope())
|
||||
{
|
||||
//stBlockData >= 0 == 날짜가 BlockDate 보다 미래
|
||||
if (strncmp(szCreateDate, g_stBlockDate.c_str(), 8) >= 0)
|
||||
{
|
||||
LoginFailure(d, "BLKLOGIN");
|
||||
sys_log(0, " BLKLOGIN");
|
||||
M2_DELETE(pinfo);
|
||||
break;
|
||||
}
|
||||
|
||||
char szQuery[1024];
|
||||
snprintf(szQuery, sizeof(szQuery), "UPDATE account SET last_play=NOW() WHERE id=%u", dwID);
|
||||
DBManager::instance().DirectQuery(szQuery);
|
||||
}
|
||||
|
||||
TAccountTable & r = d->GetAccountTable();
|
||||
|
||||
r.id = dwID;
|
||||
trim_and_lower(pinfo->login, r.login, sizeof(r.login));
|
||||
strlcpy(r.passwd, pinfo->passwd, sizeof(r.passwd));
|
||||
strlcpy(r.social_id, szSocialID, sizeof(r.social_id));
|
||||
DESC_MANAGER::instance().ConnectAccount(r.login, d);
|
||||
ClearLoginFailure(d->GetHostName());
|
||||
|
||||
LoginPrepare(d, aiPremiumTimes);
|
||||
M2_DELETE(pinfo);
|
||||
|
||||
sys_log(0, "QID_AUTH_LOGIN: SUCCESS %s", pinfo->login);
|
||||
}
|
||||
M2_DELETE(pinfo);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -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);
|
||||
@@ -85,6 +84,7 @@ class DBManager : public singleton<DBManager>
|
||||
void AuthenticateLogin(LPDESC d, const char* login, const char* passwd);
|
||||
void SendAuthLogin(LPDESC d);
|
||||
void SendLoginPing(const char * c_pszLogin);
|
||||
CAsyncSQL* GetDirectSQL() { return &m_sql_direct; }
|
||||
|
||||
void InsertLoginData(CLoginData * pkLD);
|
||||
void DeleteLoginData(CLoginData * pkLD);
|
||||
|
||||
@@ -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;
|
||||
@@ -289,56 +277,22 @@ int DESC::ProcessOutput()
|
||||
if (bytes_to_write == 0)
|
||||
return 0;
|
||||
|
||||
int bytes_written = send(m_sock, (const char *) m_outputBuffer.ReadPtr(), bytes_to_write, 0);
|
||||
int result = socket_write(m_sock, (const char *) m_outputBuffer.ReadPtr(), bytes_to_write);
|
||||
|
||||
if (bytes_written > 0)
|
||||
if (result == 0)
|
||||
{
|
||||
max_bytes_written = MAX(bytes_written, max_bytes_written);
|
||||
max_bytes_written = MAX(bytes_to_write, max_bytes_written);
|
||||
|
||||
total_bytes_written += bytes_written;
|
||||
current_bytes_written += bytes_written;
|
||||
total_bytes_written += bytes_to_write;
|
||||
current_bytes_written += bytes_to_write;
|
||||
|
||||
m_outputBuffer.Discard(bytes_written);
|
||||
m_outputBuffer.Discard(bytes_to_write);
|
||||
|
||||
if (m_outputBuffer.ReadableBytes() != 0)
|
||||
fdwatch_add_fd(m_lpFdw, m_sock, this, FDW_WRITE, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (bytes_written == 0)
|
||||
return -1;
|
||||
|
||||
#ifdef EINTR
|
||||
if (errno == EINTR)
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef EAGAIN
|
||||
if (errno == EAGAIN)
|
||||
{
|
||||
fdwatch_add_fd(m_lpFdw, m_sock, this, FDW_WRITE, true);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef EWOULDBLOCK
|
||||
if (errno == EWOULDBLOCK)
|
||||
{
|
||||
fdwatch_add_fd(m_lpFdw, m_sock, this, FDW_WRITE, true);
|
||||
return 0;
|
||||
}
|
||||
#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));
|
||||
|
||||
return -1;
|
||||
return (result);
|
||||
}
|
||||
|
||||
void DESC::BufferedPacket(const void * c_pvData, int iSize)
|
||||
@@ -416,9 +370,6 @@ void DESC::Packet(const void * c_pvData, int iSize)
|
||||
|
||||
if (m_iPhase != PHASE_CLOSE)
|
||||
fdwatch_add_fd(m_lpFdw, m_sock, this, FDW_WRITE, true);
|
||||
|
||||
if (m_iPhase != PHASE_CLOSE)
|
||||
ProcessOutput();
|
||||
}
|
||||
|
||||
void DESC::LargePacket(const void * c_pvData, int iSize)
|
||||
@@ -427,23 +378,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;
|
||||
@@ -843,9 +779,6 @@ void DESC::RawPacket(const void * c_pvData, int iSize)
|
||||
|
||||
if (m_iPhase != PHASE_CLOSE)
|
||||
fdwatch_add_fd(m_lpFdw, m_sock, this, FDW_WRITE, true);
|
||||
|
||||
if (m_iPhase != PHASE_CLOSE)
|
||||
ProcessOutput();
|
||||
}
|
||||
|
||||
void DESC::ChatPacket(BYTE type, const char * format, ...)
|
||||
|
||||
@@ -146,7 +146,6 @@ class DESC
|
||||
|
||||
protected:
|
||||
void Initialize();
|
||||
void BeginClosePhase();
|
||||
|
||||
protected:
|
||||
CInputProcessor * m_pInputProcessor;
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
#include "questmanager.h"
|
||||
#include "MarkManager.h"
|
||||
#include "MarkImage.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
SGuildMember::SGuildMember(LPCHARACTER ch, BYTE grade, DWORD offer_exp)
|
||||
: pid(ch->GetPlayerID()), grade(grade), is_general(0), job(ch->GetJob()), level(ch->GetLevel()), offer_exp(offer_exp), name(ch->GetName())
|
||||
@@ -28,6 +31,148 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
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 InsertGuildComment(uint32_t guildId, const char* name, uint8_t notice, const char* content)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("INSERT INTO guild_comment") + get_table_postfix()
|
||||
+ "(guild_id, name, notice, content, time) VALUES(?, ?, ?, ?, NOW())";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &guildId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(name))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_TINY, ¬ice)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(content)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool DeleteGuildComment(uint32_t guildId, uint32_t commentId, const char* nameFilter, unsigned long long& affectedRows)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = nameFilter
|
||||
? std::string("DELETE FROM guild_comment") + get_table_postfix() + " WHERE id=? AND guild_id=? AND name=?"
|
||||
: std::string("DELETE FROM guild_comment") + get_table_postfix() + " WHERE id=? AND guild_id=?";
|
||||
|
||||
affectedRows = 0;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &commentId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &guildId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nameFilter && !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(nameFilter)))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
affectedRows = stmt.GetAffectedRows();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PrepareGuildCommentList(CStmt& stmt, uint32_t guildId)
|
||||
{
|
||||
const std::string query = std::string("SELECT id, name, content FROM guild_comment") + get_table_postfix()
|
||||
+ " WHERE guild_id=? ORDER BY notice DESC, id DESC LIMIT " + std::to_string(GUILD_COMMENT_MAX_COUNT);
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
return stmt.BindParam(MYSQL_TYPE_LONG, &guildId);
|
||||
}
|
||||
|
||||
bool InsertGuildRecord(const char* name, uint32_t masterPid, uint32_t& guildId)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("INSERT INTO guild") + get_table_postfix()
|
||||
+ "(name, master, sp, level, exp, skill_point, skill) "
|
||||
+ "VALUES(?, ?, 1000, 1, 0, 0, '\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0')";
|
||||
|
||||
guildId = 0;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(name))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &masterPid)
|
||||
|| !stmt.Execute())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
guildId = static_cast<uint32_t>(stmt.GetInsertId());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertGuildGrade(uint32_t guildId, int grade, const char* gradeName, int authFlag)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("INSERT INTO guild_grade") + get_table_postfix() + " VALUES(?, ?, ?, ?)";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &guildId)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &grade)
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(gradeName))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &authFlag))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool LoadGuildInviteLimit(uint32_t guildId, uint32_t& inviteLimit, bool& found)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = "SELECT value FROM guild_invite_limit WHERE id=?";
|
||||
|
||||
inviteLimit = 0;
|
||||
found = false;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_LONG, &guildId)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &inviteLimit)
|
||||
|| !stmt.Execute())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct FGuildNameSender
|
||||
{
|
||||
FGuildNameSender(uint32_t id, const char* guild_name) : id(id), name(guild_name)
|
||||
@@ -74,22 +219,15 @@ CGuild::CGuild(TGuildCreateParameter & cp)
|
||||
m_data.grade_array[i].auth_flag = 0;
|
||||
}
|
||||
|
||||
auto pmsg = DBManager::instance().DirectQuery(
|
||||
"INSERT INTO guild%s(name, master, sp, level, exp, skill_point, skill) "
|
||||
"VALUES('%s', %u, 1000, 1, 0, 0, '\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0')",
|
||||
get_table_postfix(), m_data.name, m_data.master_pid);
|
||||
|
||||
// TODO if error occur?
|
||||
m_data.guild_id = pmsg->Get()->uiInsertID;
|
||||
InsertGuildRecord(m_data.name, m_data.master_pid, m_data.guild_id);
|
||||
|
||||
for (int i = 0; i < GUILD_GRADE_COUNT; ++i)
|
||||
{
|
||||
DBManager::instance().Query("INSERT INTO guild_grade%s VALUES(%u, %d, '%s', %d)",
|
||||
get_table_postfix(),
|
||||
m_data.guild_id,
|
||||
i + 1,
|
||||
m_data.grade_array[i].grade_name,
|
||||
m_data.grade_array[i].auth_flag);
|
||||
InsertGuildGrade(
|
||||
m_data.guild_id,
|
||||
i + 1,
|
||||
m_data.grade_array[i].grade_name,
|
||||
m_data.grade_array[i].auth_flag);
|
||||
}
|
||||
|
||||
ComputeGuildPoints();
|
||||
@@ -1042,25 +1180,18 @@ void CGuild::AddComment(LPCHARACTER ch, const std::string& str)
|
||||
return;
|
||||
}
|
||||
|
||||
char text[GUILD_COMMENT_MAX_LEN * 2 + 1];
|
||||
DBManager::instance().EscapeString(text, sizeof(text), str.c_str(), str.length());
|
||||
const uint8_t notice = (str[0] == '!') ? 1 : 0;
|
||||
|
||||
DBManager::instance().FuncAfterQuery(std::bind(&CGuild::RefreshCommentForce,this, ch->GetPlayerID()),
|
||||
"INSERT INTO guild_comment%s(guild_id, name, notice, content, time) VALUES(%u, '%s', %d, '%s', NOW())",
|
||||
get_table_postfix(), m_data.guild_id, ch->GetName(), (str[0] == '!') ? 1 : 0, text);
|
||||
if (InsertGuildComment(m_data.guild_id, ch->GetName(), notice, str.c_str()))
|
||||
RefreshCommentForce(ch->GetPlayerID());
|
||||
}
|
||||
|
||||
void CGuild::DeleteComment(LPCHARACTER ch, DWORD comment_id)
|
||||
{
|
||||
std::unique_ptr<SQLMsg> pmsg{};
|
||||
unsigned long long affectedRows = 0;
|
||||
const char* nameFilter = GetMember(ch->GetPlayerID())->grade == GUILD_LEADER_GRADE ? NULL : ch->GetName();
|
||||
|
||||
if (GetMember(ch->GetPlayerID())->grade == GUILD_LEADER_GRADE)
|
||||
pmsg = DBManager::instance().DirectQuery("DELETE FROM guild_comment%s WHERE id = %u AND guild_id = %u",get_table_postfix(), comment_id, m_data.guild_id);
|
||||
else
|
||||
pmsg = DBManager::instance().DirectQuery("DELETE FROM guild_comment%s WHERE id = %u AND guild_id = %u AND name = '%s'",get_table_postfix(), comment_id, m_data.guild_id, ch->GetName());
|
||||
|
||||
auto* res = pmsg ? pmsg->Get() : nullptr;
|
||||
if (!res || res->uiAffectedRows == 0 || res->uiAffectedRows == (uint32_t)-1)
|
||||
if (!DeleteGuildComment(m_data.guild_id, comment_id, nameFilter, affectedRows) || affectedRows == 0)
|
||||
ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<길드> 삭제할 수 없는 글입니다."));
|
||||
else
|
||||
RefreshCommentForce(ch->GetPlayerID());
|
||||
@@ -1074,45 +1205,55 @@ void CGuild::RefreshComment(LPCHARACTER ch)
|
||||
void CGuild::RefreshCommentForce(DWORD player_id)
|
||||
{
|
||||
LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(player_id);
|
||||
if (ch == NULL) {
|
||||
if (ch == NULL)
|
||||
return;
|
||||
}
|
||||
|
||||
auto pmsg = DBManager::instance().DirectQuery("SELECT id, name, content FROM guild_comment%s WHERE guild_id = %u ORDER BY notice DESC, id DESC LIMIT %d", get_table_postfix(), m_data.guild_id, GUILD_COMMENT_MAX_COUNT);
|
||||
|
||||
TPacketGCGuild pack;
|
||||
pack.header = GC::GUILD;
|
||||
pack.length = sizeof(pack)+1;
|
||||
pack.length = sizeof(pack) + 1;
|
||||
pack.subheader = GuildSub::GC::COMMENTS;
|
||||
|
||||
BYTE count = pmsg->Get()->uiNumRows;
|
||||
|
||||
LPDESC d = ch->GetDesc();
|
||||
|
||||
if (!d)
|
||||
return;
|
||||
|
||||
pack.length += (sizeof(DWORD)+CHARACTER_NAME_MAX_LEN+1+GUILD_COMMENT_MAX_LEN+1)*(WORD)count;
|
||||
d->BufferedPacket(&pack,sizeof(pack));
|
||||
d->BufferedPacket(&count, 1);
|
||||
CStmt stmt;
|
||||
uint32_t id = 0;
|
||||
char szName[CHARACTER_NAME_MAX_LEN + 1];
|
||||
char szContent[GUILD_COMMENT_MAX_LEN + 1];
|
||||
|
||||
memset(szName, 0, sizeof(szName));
|
||||
memset(szContent, 0, sizeof(szContent));
|
||||
|
||||
for (uint i = 0; i < pmsg->Get()->uiNumRows; i++)
|
||||
if (!PrepareGuildCommentList(stmt, m_data.guild_id)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &id)
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, szName, sizeof(szName))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, szContent, sizeof(szContent))
|
||||
|| !stmt.Execute())
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(pmsg->Get()->pSQLResult);
|
||||
DWORD id = strtoul(row[0], NULL, 10);
|
||||
return;
|
||||
}
|
||||
|
||||
strlcpy(szName, row[1], sizeof(szName));
|
||||
strlcpy(szContent, row[2], sizeof(szContent));
|
||||
BYTE count = static_cast<BYTE>(stmt.iRows);
|
||||
|
||||
pack.length += (sizeof(DWORD) + CHARACTER_NAME_MAX_LEN + 1 + GUILD_COMMENT_MAX_LEN + 1) * static_cast<WORD>(count);
|
||||
d->BufferedPacket(&pack, sizeof(pack));
|
||||
d->BufferedPacket(&count, 1);
|
||||
|
||||
for (BYTE i = 0; i < count; ++i)
|
||||
{
|
||||
memset(szName, 0, sizeof(szName));
|
||||
memset(szContent, 0, sizeof(szContent));
|
||||
|
||||
if (!stmt.Fetch())
|
||||
break;
|
||||
|
||||
d->BufferedPacket(&id, sizeof(id));
|
||||
d->BufferedPacket(szName, sizeof(szName));
|
||||
|
||||
if (i == pmsg->Get()->uiNumRows - 1)
|
||||
d->Packet(szContent, sizeof(szContent)); // 마지막 줄이면 보내기
|
||||
if (i == count - 1)
|
||||
d->Packet(szContent, sizeof(szContent));
|
||||
else
|
||||
d->BufferedPacket(szContent, sizeof(szContent));
|
||||
}
|
||||
@@ -2122,13 +2263,13 @@ CGuild::GuildJoinErrCode CGuild::VerifyGuildJoinableCondition( const LPCHARACTER
|
||||
}
|
||||
else if ( LC_IsBrazil() == true )
|
||||
{
|
||||
auto pMsg = DBManager::instance().DirectQuery("SELECT value FROM guild_invite_limit WHERE id=%d", GetID());
|
||||
uint32_t inviteLimit = 0;
|
||||
bool hasInviteLimit = false;
|
||||
|
||||
if ( pMsg->Get()->uiNumRows > 0 )
|
||||
if (LoadGuildInviteLimit(GetID(), inviteLimit, hasInviteLimit) && hasInviteLimit)
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(pMsg->Get()->pSQLResult);
|
||||
time_t limit_time=0;
|
||||
str_to_number( limit_time, row[0] );
|
||||
limit_time = inviteLimit;
|
||||
|
||||
if (test_server)
|
||||
{
|
||||
@@ -2171,4 +2312,3 @@ void CGuild::SendGuildDataUpdateToAllMember(SQLMsg* pmsg)
|
||||
SendAllGradePacket(*iter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,29 +17,54 @@
|
||||
#include "MarkManager.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool CountGuildsByName(const char* guild_name, unsigned long long* count)
|
||||
bool PrepareGameStmt(CStmt& stmt, const std::string& query)
|
||||
{
|
||||
char query[256];
|
||||
snprintf(query, sizeof(query), "SELECT COUNT(*) FROM guild%s WHERE name = ?", get_table_postfix());
|
||||
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 GuildNameExists(const char* guildName, bool& exists)
|
||||
{
|
||||
CStmt stmt;
|
||||
if (!stmt.Prepare(DBManager::instance().GetDirectSQL(), query))
|
||||
const std::string query = std::string("SELECT COUNT(*) FROM guild") + get_table_postfix() + " WHERE name=?";
|
||||
uint32_t count = 0;
|
||||
|
||||
exists = false;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, (void*) guild_name, GUILD_NAME_MAX_LEN))
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(guildName))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (!stmt.BindResult(MYSQL_TYPE_LONGLONG, count))
|
||||
return false;
|
||||
|
||||
if (!stmt.Execute() || !stmt.Fetch())
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
exists = count != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
struct FGuildNameSender
|
||||
{
|
||||
FGuildNameSender(DWORD id, const char* guild_name) : id(id), name(guild_name)
|
||||
@@ -102,14 +127,15 @@ DWORD CGuildManager::CreateGuild(TGuildCreateParameter& gcp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long long guild_count = 0;
|
||||
if (!CountGuildsByName(gcp.name, &guild_count))
|
||||
bool guildNameExists = false;
|
||||
|
||||
if (!GuildNameExists(gcp.name, guildNameExists))
|
||||
{
|
||||
gcp.master->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<길드> 길드를 생성할 수 없습니다."));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (guild_count != 0)
|
||||
if (guildNameExists)
|
||||
{
|
||||
gcp.master->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("<길드> 이미 같은 이름의 길드가 있습니다."));
|
||||
return 0;
|
||||
|
||||
@@ -18,19 +18,35 @@
|
||||
|
||||
extern time_t get_global_time();
|
||||
|
||||
bool IsEmptyAdminPage()
|
||||
namespace
|
||||
{
|
||||
return g_stAdminPageIP.empty();
|
||||
}
|
||||
|
||||
bool IsAdminPage(const char * ip)
|
||||
{
|
||||
for (size_t n = 0; n < g_stAdminPageIP.size(); ++n)
|
||||
bool IsEmptyAdminPage()
|
||||
{
|
||||
if (g_stAdminPageIP[n] == ip)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return g_stAdminPageIP.empty();
|
||||
}
|
||||
|
||||
bool IsAdminPage(const char * ip)
|
||||
{
|
||||
for (size_t n = 0; n < g_stAdminPageIP.size(); ++n)
|
||||
{
|
||||
if (g_stAdminPageIP[n] == ip)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool HasAdminPageIpAccess(const char* ip)
|
||||
{
|
||||
if (!IsAdminPageEnabled())
|
||||
return false;
|
||||
|
||||
return IsEmptyAdminPage() || IsAdminPage(ip);
|
||||
}
|
||||
|
||||
bool IsAdminCommandAuthorized(LPDESC d)
|
||||
{
|
||||
return d->IsAdminMode();
|
||||
}
|
||||
}
|
||||
|
||||
CInputProcessor::CInputProcessor() : m_pPacketInfo(NULL), m_iBufferLeft(0)
|
||||
@@ -245,14 +261,15 @@ int CInputHandshake::HandleText(LPDESC d, const char * c_pData)
|
||||
stResult = "YES";
|
||||
}
|
||||
//else if (!stBuf.compare("SHOWMETHEMONEY"))
|
||||
else if (!g_stAdminPagePassword.empty() && stBuf == g_stAdminPagePassword)
|
||||
else if (IsAdminPageEnabled() && stBuf == g_stAdminPagePassword)
|
||||
{
|
||||
const char* hostIp = inet_ntoa(d->GetAddr().sin_addr);
|
||||
if (!IsEmptyAdminPage())
|
||||
{
|
||||
if (!IsAdminPage(inet_ntoa(d->GetAddr().sin_addr)))
|
||||
if (!IsAdminPage(hostIp))
|
||||
{
|
||||
char szTmp[64];
|
||||
snprintf(szTmp, sizeof(szTmp), "WEBADMIN : Wrong Connector : %s", inet_ntoa(d->GetAddr().sin_addr));
|
||||
snprintf(szTmp, sizeof(szTmp), "WEBADMIN : Wrong Connector : %s", hostIp);
|
||||
stResult += szTmp;
|
||||
}
|
||||
else
|
||||
@@ -270,21 +287,14 @@ int CInputHandshake::HandleText(LPDESC d, const char * c_pData)
|
||||
else if (!stBuf.compare("USER_COUNT"))
|
||||
{
|
||||
char szTmp[64];
|
||||
const char* hostIp = inet_ntoa(d->GetAddr().sin_addr);
|
||||
|
||||
if (!IsEmptyAdminPage())
|
||||
if (!HasAdminPageIpAccess(hostIp))
|
||||
{
|
||||
if (!IsAdminPage(inet_ntoa(d->GetAddr().sin_addr)))
|
||||
{
|
||||
snprintf(szTmp, sizeof(szTmp), "WEBADMIN : Wrong Connector : %s", inet_ntoa(d->GetAddr().sin_addr));
|
||||
}
|
||||
if (IsAdminPageEnabled())
|
||||
snprintf(szTmp, sizeof(szTmp), "WEBADMIN : Wrong Connector : %s", hostIp);
|
||||
else
|
||||
{
|
||||
int iTotal;
|
||||
int * paiEmpireUserCount;
|
||||
int iLocal;
|
||||
DESC_MANAGER::instance().GetUserCount(iTotal, &paiEmpireUserCount, iLocal);
|
||||
snprintf(szTmp, sizeof(szTmp), "%d %d %d %d %d", iTotal, paiEmpireUserCount[1], paiEmpireUserCount[2], paiEmpireUserCount[3], iLocal);
|
||||
}
|
||||
strlcpy(szTmp, "WEBADMIN : Disabled", sizeof(szTmp));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -298,48 +308,68 @@ int CInputHandshake::HandleText(LPDESC d, const char * c_pData)
|
||||
}
|
||||
else if (!stBuf.compare("CHECK_P2P_CONNECTIONS"))
|
||||
{
|
||||
std::ostringstream oss(std::ostringstream::out);
|
||||
|
||||
oss << "P2P CONNECTION NUMBER : " << P2P_MANAGER::instance().GetDescCount() << "\n";
|
||||
std::string hostNames;
|
||||
P2P_MANAGER::Instance().GetP2PHostNames(hostNames);
|
||||
oss << hostNames;
|
||||
stResult = oss.str();
|
||||
TPacketGGCheckAwakeness packet;
|
||||
packet.header = GG::CHECK_AWAKENESS;
|
||||
packet.length = sizeof(packet);
|
||||
if (!IsAdminCommandAuthorized(d))
|
||||
stResult = "UNKNOWN";
|
||||
else
|
||||
{
|
||||
std::ostringstream oss(std::ostringstream::out);
|
||||
|
||||
P2P_MANAGER::instance().Send(&packet, sizeof(packet));
|
||||
oss << "P2P CONNECTION NUMBER : " << P2P_MANAGER::instance().GetDescCount() << "\n";
|
||||
std::string hostNames;
|
||||
P2P_MANAGER::Instance().GetP2PHostNames(hostNames);
|
||||
oss << hostNames;
|
||||
stResult = oss.str();
|
||||
TPacketGGCheckAwakeness packet;
|
||||
packet.header = GG::CHECK_AWAKENESS;
|
||||
packet.length = sizeof(packet);
|
||||
|
||||
P2P_MANAGER::instance().Send(&packet, sizeof(packet));
|
||||
}
|
||||
}
|
||||
else if (!stBuf.compare("PACKET_INFO"))
|
||||
{
|
||||
m_pMainPacketInfo->Log("packet_info.txt");
|
||||
stResult = "OK";
|
||||
if (!IsAdminCommandAuthorized(d))
|
||||
stResult = "UNKNOWN";
|
||||
else
|
||||
{
|
||||
m_pMainPacketInfo->Log("packet_info.txt");
|
||||
stResult = "OK";
|
||||
}
|
||||
}
|
||||
else if (!stBuf.compare("PROFILE"))
|
||||
{
|
||||
CProfiler::instance().Log("profile.txt");
|
||||
stResult = "OK";
|
||||
if (!IsAdminCommandAuthorized(d))
|
||||
stResult = "UNKNOWN";
|
||||
else
|
||||
{
|
||||
CProfiler::instance().Log("profile.txt");
|
||||
stResult = "OK";
|
||||
}
|
||||
}
|
||||
//gift notify delete command
|
||||
else if (!stBuf.compare(0,15,"DELETE_AWARDID "))
|
||||
{
|
||||
char szTmp[64];
|
||||
std::string msg = stBuf.substr(15,26); // item_award의 id범위?
|
||||
|
||||
TPacketDeleteAwardID p;
|
||||
p.dwID = (DWORD)(atoi(msg.c_str()));
|
||||
snprintf(szTmp,sizeof(szTmp),"Sent to DB cache to delete ItemAward, id: %d",p.dwID);
|
||||
//sys_log(0,"%d",p.dwID);
|
||||
// strlcpy(p.login, msg.c_str(), sizeof(p.login));
|
||||
db_clientdesc->DBPacket(GD::DELETE_AWARDID, 0, &p, sizeof(p));
|
||||
stResult += szTmp;
|
||||
if (!IsAdminCommandAuthorized(d))
|
||||
stResult = "UNKNOWN";
|
||||
else
|
||||
{
|
||||
char szTmp[64];
|
||||
std::string msg = stBuf.substr(15,26); // item_award의 id범위?
|
||||
|
||||
TPacketDeleteAwardID p;
|
||||
p.dwID = (DWORD)(atoi(msg.c_str()));
|
||||
snprintf(szTmp,sizeof(szTmp),"Sent to DB cache to delete ItemAward, id: %d",p.dwID);
|
||||
//sys_log(0,"%d",p.dwID);
|
||||
// strlcpy(p.login, msg.c_str(), sizeof(p.login));
|
||||
db_clientdesc->DBPacket(GD::DELETE_AWARDID, 0, &p, sizeof(p));
|
||||
stResult += szTmp;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stResult = "UNKNOWN";
|
||||
|
||||
if (d->IsAdminMode())
|
||||
else
|
||||
{
|
||||
stResult = "UNKNOWN";
|
||||
|
||||
if (d->IsAdminMode())
|
||||
{
|
||||
// 어드민 명령들
|
||||
if (!stBuf.compare(0, 7, "NOTICE "))
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -167,64 +167,6 @@ void ShutdownOnFatalError()
|
||||
|
||||
namespace
|
||||
{
|
||||
const char* BoolState(bool value)
|
||||
{
|
||||
return value ? "on" : "off";
|
||||
}
|
||||
|
||||
const char* EmptyToLabel(const char* value, const char* fallback)
|
||||
{
|
||||
return (value && *value) ? value : fallback;
|
||||
}
|
||||
|
||||
const char* EmptyToLabel(const std::string& value, const char* fallback)
|
||||
{
|
||||
return value.empty() ? fallback : value.c_str();
|
||||
}
|
||||
|
||||
void LogStartupSummary()
|
||||
{
|
||||
#ifdef ENABLE_PROXY_IP
|
||||
const char* proxy_ip = EmptyToLabel(g_stProxyIP, "<disabled>");
|
||||
#else
|
||||
const char* proxy_ip = "<disabled>";
|
||||
#endif
|
||||
|
||||
sys_log(0,
|
||||
"[STARTUP] mode=%s channel=%u bind=%s:%u p2p=%s:%u db=%s:%u locale=%s quest_dir=%s",
|
||||
g_bAuthServer ? "auth" : "game",
|
||||
g_bChannel,
|
||||
EmptyToLabel(g_szPublicIP, "0.0.0.0"), mother_port,
|
||||
EmptyToLabel(g_szPublicIP, "0.0.0.0"), p2p_port,
|
||||
EmptyToLabel(db_addr, "<unset>"), db_port,
|
||||
EmptyToLabel(g_stLocale, "<unset>"),
|
||||
EmptyToLabel(g_stQuestDir, "<unset>")
|
||||
);
|
||||
|
||||
sys_log(0,
|
||||
"[STARTUP] users limit=%d full=%d busy=%d local=%u p2p_peers=%d regen=%s admin_page=%s proxy=%s",
|
||||
g_iUserLimit,
|
||||
g_iFullUserCount,
|
||||
g_iBusyUserCount,
|
||||
DESC_MANAGER::instance().GetLocalUserCount(),
|
||||
P2P_MANAGER::instance().GetDescCount(),
|
||||
BoolState(!g_bNoRegen),
|
||||
BoolState(!g_stAdminPagePassword.empty()),
|
||||
proxy_ip
|
||||
);
|
||||
|
||||
sys_log(0,
|
||||
"[STARTUP] features client_version_check=%s guild_mark_server=%s mark_min_level=%u empire_whisper=%s auth_master=%s:%u test_server=%d",
|
||||
BoolState(g_bCheckClientVersion),
|
||||
BoolState(guild_mark_server),
|
||||
guild_mark_min_level,
|
||||
BoolState(g_bEmpireWhisper),
|
||||
EmptyToLabel(g_stAuthMasterIP, "<disabled>"),
|
||||
g_wAuthMasterPort,
|
||||
test_server
|
||||
);
|
||||
}
|
||||
|
||||
struct SendDisconnectFunc
|
||||
{
|
||||
void operator () (LPDESC d)
|
||||
@@ -402,14 +344,14 @@ int main(int argc, char **argv)
|
||||
|
||||
if (!start(argc, argv)) {
|
||||
CleanUpForEarlyExit();
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
quest::CQuestManager quest_manager;
|
||||
|
||||
if (!quest_manager.Initialize()) {
|
||||
CleanUpForEarlyExit();
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
MessengerManager::instance().Initialize();
|
||||
@@ -486,7 +428,7 @@ int main(int argc, char **argv)
|
||||
#endif
|
||||
|
||||
log_destroy();
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void usage()
|
||||
@@ -602,16 +544,6 @@ int start(int argc, char **argv)
|
||||
|
||||
main_fdw = fdwatch_new(4096);
|
||||
|
||||
if (!main_fdw)
|
||||
{
|
||||
sys_err("fdwatch_new failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
sys_log(0, "[STARTUP] fdwatch backend=%s descriptor_limit=%d",
|
||||
fdwatch_backend_name(fdwatch_get_backend(main_fdw)),
|
||||
fdwatch_get_descriptor_limit(main_fdw));
|
||||
|
||||
if ((tcp_socket = socket_tcp_bind(g_szPublicIP, mother_port)) == INVALID_SOCKET)
|
||||
{
|
||||
perror("socket_tcp_bind: tcp_socket");
|
||||
@@ -662,8 +594,6 @@ int start(int argc, char **argv)
|
||||
LoadSpamDB();
|
||||
}
|
||||
|
||||
LogStartupSummary();
|
||||
|
||||
signal_timer_enable(30);
|
||||
return 1;
|
||||
}
|
||||
@@ -848,3 +778,4 @@ int io_loop(LPFDWATCH fdw)
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,113 @@
|
||||
#include "char.h"
|
||||
#include "char_manager.h"
|
||||
#include "questmanager.h"
|
||||
#include "libsql/Statement.h"
|
||||
|
||||
static char __account[CHARACTER_NAME_MAX_LEN * 2 + 1];
|
||||
static char __companion[CHARACTER_NAME_MAX_LEN * 2 + 1];
|
||||
|
||||
namespace
|
||||
{
|
||||
bool PrepareMessengerStmt(CStmt& stmt, const std::string& query)
|
||||
{
|
||||
CAsyncSQL* sql = DBManager::instance().GetDirectSQL();
|
||||
|
||||
if (!sql)
|
||||
{
|
||||
sys_err("messenger direct SQL handle is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Prepare(sql, query.c_str());
|
||||
}
|
||||
|
||||
bool LoadMessengerCompanions(MessengerManager::keyA account, std::vector<std::string>& companions)
|
||||
{
|
||||
CStmt stmt;
|
||||
char companion[CHARACTER_NAME_MAX_LEN + 1];
|
||||
const std::string query = std::string("SELECT companion FROM messenger_list") + get_table_postfix() + " WHERE account=?";
|
||||
|
||||
memset(companion, 0, sizeof(companion));
|
||||
companions.clear();
|
||||
|
||||
if (!PrepareMessengerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(account.c_str()))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_STRING, companion, sizeof(companion))
|
||||
|| !stmt.Execute())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < stmt.iRows; ++i)
|
||||
{
|
||||
memset(companion, 0, sizeof(companion));
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
companions.emplace_back(companion);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InsertMessengerRelation(MessengerManager::keyA account, MessengerManager::keyA companion)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("INSERT INTO messenger_list") + get_table_postfix() + " VALUES (?, ?)";
|
||||
|
||||
if (!PrepareMessengerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(account.c_str()))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(companion.c_str())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool DeleteMessengerRelation(MessengerManager::keyA account, MessengerManager::keyA companion)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("DELETE FROM messenger_list") + get_table_postfix()
|
||||
+ " WHERE (account=? AND companion=?) OR (account=? AND companion=?)";
|
||||
|
||||
if (!PrepareMessengerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(account.c_str()))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(companion.c_str()))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(companion.c_str()))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(account.c_str())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
|
||||
bool DeleteAllMessengerRelations(MessengerManager::keyA account)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("DELETE FROM messenger_list") + get_table_postfix() + " WHERE account=? OR companion=?";
|
||||
|
||||
if (!PrepareMessengerStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(account.c_str()))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(account.c_str())))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
MessengerManager::MessengerManager()
|
||||
{
|
||||
}
|
||||
@@ -51,39 +154,26 @@ void MessengerManager::Login(MessengerManager::keyA account)
|
||||
if (account.compare(__account))
|
||||
return;
|
||||
|
||||
DBManager::instance().FuncQuery(std::bind(&MessengerManager::LoadList, this, std::placeholders::_1),
|
||||
"SELECT account, companion FROM messenger_list%s WHERE account='%s'", get_table_postfix(), account.c_str());
|
||||
|
||||
m_set_loginAccount.insert(account);
|
||||
LoadList(account);
|
||||
}
|
||||
|
||||
void MessengerManager::LoadList(SQLMsg * msg)
|
||||
void MessengerManager::LoadList(MessengerManager::keyA account)
|
||||
{
|
||||
if (NULL == msg)
|
||||
std::vector<std::string> companions;
|
||||
|
||||
if (!LoadMessengerCompanions(account, companions))
|
||||
return;
|
||||
|
||||
if (NULL == msg->Get())
|
||||
if (companions.empty())
|
||||
return;
|
||||
|
||||
if (msg->Get()->uiNumRows == 0)
|
||||
return;
|
||||
|
||||
std::string account;
|
||||
|
||||
sys_log(1, "Messenger::LoadList");
|
||||
|
||||
for (uint i = 0; i < msg->Get()->uiNumRows; ++i)
|
||||
for (const auto& companion : companions)
|
||||
{
|
||||
MYSQL_ROW row = mysql_fetch_row(msg->Get()->pSQLResult);
|
||||
|
||||
if (row[0] && row[1])
|
||||
{
|
||||
if (account.length() == 0)
|
||||
account = row[0];
|
||||
|
||||
m_Relation[row[0]].insert(row[1]);
|
||||
m_InverseRelation[row[1]].insert(row[0]);
|
||||
}
|
||||
m_Relation[account].insert(companion);
|
||||
m_InverseRelation[companion].insert(account);
|
||||
}
|
||||
|
||||
SendList(account);
|
||||
@@ -501,8 +591,7 @@ void MessengerManager::AddToList(MessengerManager::keyA account, MessengerManage
|
||||
|
||||
sys_log(0, "Messenger Add %s %s", account.c_str(), companion.c_str());
|
||||
|
||||
DBManager::instance().Query("INSERT INTO messenger_list%s VALUES ('%s', '%s')",
|
||||
get_table_postfix(), account.c_str(), companion.c_str());
|
||||
InsertMessengerRelation(account, companion);
|
||||
|
||||
__AddToList(account, companion);
|
||||
|
||||
@@ -568,9 +657,7 @@ void MessengerManager::RemoveFromList(MessengerManager::keyA account, MessengerM
|
||||
|
||||
sys_log(1, "Messenger Remove %s %s", account.c_str(), companion.c_str());
|
||||
|
||||
// Fix
|
||||
DBManager::instance().Query("DELETE FROM messenger_list%s WHERE (account='%s' AND companion = '%s') OR (account = '%s' AND companion = '%s')",
|
||||
get_table_postfix(), account.c_str(), companion.c_str(), companion.c_str(), account.c_str());
|
||||
DeleteMessengerRelation(account, companion);
|
||||
|
||||
// MR-3: Remove from messenger Fix
|
||||
LPCHARACTER ch = CHARACTER_MANAGER::instance().FindPC(account.c_str());
|
||||
@@ -601,8 +688,7 @@ void MessengerManager::RemoveAllList(keyA account)
|
||||
return;
|
||||
|
||||
/* SQL Data 삭제 */
|
||||
DBManager::instance().Query("DELETE FROM messenger_list%s WHERE account='%s' OR companion='%s'",
|
||||
get_table_postfix(), account.c_str(), account.c_str());
|
||||
DeleteAllMessengerRelations(account);
|
||||
|
||||
/* 내가 가지고있는 리스트 삭제 */
|
||||
for (std::set<keyT>::iterator iter = company.begin();
|
||||
@@ -739,4 +825,3 @@ void MessengerManager::SendLogout(MessengerManager::keyA account, MessengerManag
|
||||
d->BufferedPacket(&bLen, sizeof(BYTE));
|
||||
d->Packet(companion.c_str(), companion.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class MessengerManager : public singleton<MessengerManager>
|
||||
void SendLogin(keyA account, keyA companion);
|
||||
void SendLogout(keyA account, keyA companion);
|
||||
|
||||
void LoadList(SQLMsg * pmsg);
|
||||
void LoadList(keyA account);
|
||||
|
||||
void Destroy();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "desc_client.h"
|
||||
#include "messenger_manager.h"
|
||||
#include "log.h"
|
||||
#include "db.h"
|
||||
#include "utils.h"
|
||||
#include "unique_item.h"
|
||||
#include "mob_manager.h"
|
||||
@@ -32,50 +33,71 @@ extern int g_nPortalLimitTime;
|
||||
extern LPCLIENT_DESC db_clientdesc;
|
||||
const int ITEM_BROKEN_METIN_VNUM = 28960;
|
||||
|
||||
namespace quest
|
||||
namespace
|
||||
{
|
||||
namespace
|
||||
bool PrepareGameStmt(CStmt& stmt, const std::string& query)
|
||||
{
|
||||
bool CountPlayersByName(const char* player_name, unsigned long long* count)
|
||||
CAsyncSQL* sql = DBManager::instance().GetDirectSQL();
|
||||
|
||||
if (!sql)
|
||||
{
|
||||
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;
|
||||
sys_err("game direct SQL handle is not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return stmt.Prepare(sql, query.c_str());
|
||||
}
|
||||
|
||||
bool CharacterNameExists(const char* name, bool& exists)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("SELECT COUNT(*) FROM player") + get_table_postfix() + " WHERE name=?";
|
||||
uint32_t count = 0;
|
||||
|
||||
exists = false;
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(name))
|
||||
|| !stmt.BindResult(MYSQL_TYPE_LONG, &count))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!stmt.Execute())
|
||||
return false;
|
||||
|
||||
if (stmt.iRows == 0)
|
||||
return true;
|
||||
|
||||
if (!stmt.Fetch())
|
||||
return false;
|
||||
|
||||
exists = count != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateCharacterName(uint32_t playerId, const char* name)
|
||||
{
|
||||
CStmt stmt;
|
||||
const std::string query = std::string("UPDATE player") + get_table_postfix() + " SET name=? WHERE id=?";
|
||||
|
||||
if (!PrepareGameStmt(stmt, query))
|
||||
return false;
|
||||
|
||||
if (!stmt.BindParam(MYSQL_TYPE_STRING, const_cast<char*>(name))
|
||||
|| !stmt.BindParam(MYSQL_TYPE_LONG, &playerId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return stmt.Execute();
|
||||
}
|
||||
}
|
||||
|
||||
namespace quest
|
||||
{
|
||||
//
|
||||
// "pc" Lua functions
|
||||
//
|
||||
@@ -2157,15 +2179,16 @@ teleport_area:
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned long long count = 0;
|
||||
if (!CountPlayersByName(szName, &count))
|
||||
bool characterNameExists = false;
|
||||
|
||||
if (!CharacterNameExists(szName, characterNameExists))
|
||||
{
|
||||
lua_pushnumber(L, 5);
|
||||
lua_pushnumber(L, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// 이미 해당 이름을 가진 캐릭터가 있음
|
||||
if (count != 0)
|
||||
if (characterNameExists)
|
||||
{
|
||||
lua_pushnumber(L, 3);
|
||||
return 1;
|
||||
@@ -2182,9 +2205,9 @@ teleport_area:
|
||||
/* change_name_log */
|
||||
LogManager::instance().ChangeNameLog(pid, ch->GetName(), szName, ch->GetDesc()->GetHostName());
|
||||
|
||||
if (!UpdatePlayerName(pid, szName))
|
||||
if (!UpdateCharacterName(pid, szName))
|
||||
{
|
||||
lua_pushnumber(L, 5);
|
||||
lua_pushnumber(L, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,17 @@ void CStmt::Destroy()
|
||||
m_pkStmt = NULL;
|
||||
}
|
||||
|
||||
if (m_puiParamLen)
|
||||
{
|
||||
free(m_puiParamLen);
|
||||
m_puiParamLen = NULL;
|
||||
}
|
||||
|
||||
m_vec_param.clear();
|
||||
m_vecParamLen.clear();
|
||||
m_vec_result.clear();
|
||||
m_vecResultLen.clear();
|
||||
m_vec_result_len.clear();
|
||||
m_uiParamCount = 0;
|
||||
m_uiResultCount = 0;
|
||||
iRows = 0;
|
||||
}
|
||||
|
||||
void CStmt::Error(const char * c_pszMsg)
|
||||
@@ -40,9 +45,10 @@ 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;
|
||||
m_uiParamCount = 0;
|
||||
m_uiResultCount = 0;
|
||||
|
||||
if (mysql_stmt_prepare(m_pkStmt, m_stQuery.c_str(), m_stQuery.length()))
|
||||
{
|
||||
@@ -50,20 +56,22 @@ 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)
|
||||
const auto iParamCount = mysql_stmt_param_count(m_pkStmt);
|
||||
|
||||
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)
|
||||
const auto iFieldCount = mysql_stmt_field_count(m_pkStmt);
|
||||
if (iFieldCount)
|
||||
{
|
||||
m_vec_result.resize(result_count);
|
||||
memset(&m_vec_result[0], 0, sizeof(MYSQL_BIND) * result_count);
|
||||
m_vecResultLen.resize(result_count, 0);
|
||||
m_vec_result.resize(iFieldCount);
|
||||
memset(&m_vec_result[0], 0, sizeof(MYSQL_BIND) * iFieldCount);
|
||||
m_vec_result_len.resize(iFieldCount, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -82,8 +90,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 +117,7 @@ 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];
|
||||
bind->length = &m_vec_result_len[m_uiResultCount - 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -118,32 +134,14 @@ int CStmt::Execute()
|
||||
MYSQL_BIND * bind = &m_vec_param[i];
|
||||
|
||||
if (bind->buffer_type == MYSQL_TYPE_STRING)
|
||||
{
|
||||
m_vecParamLen[i] = strlen((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))
|
||||
{
|
||||
Error("mysql_stmt_execute");
|
||||
return 0;
|
||||
*(m_puiParamLen + i) = strlen((const char *) bind->buffer);
|
||||
}
|
||||
|
||||
if (!m_vec_result.empty())
|
||||
{
|
||||
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());
|
||||
sys_log(0, "Result binding not enough %d, expected %d query: %s", m_uiResultCount, m_vec_result.size(), m_stQuery.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -152,18 +150,21 @@ int CStmt::Execute()
|
||||
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;
|
||||
}
|
||||
|
||||
iRows = 0;
|
||||
if (mysql_stmt_execute(m_pkStmt))
|
||||
{
|
||||
Error("mysql_stmt_execute");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mysql_stmt_store_result(m_pkStmt))
|
||||
{
|
||||
Error("mysql_store_result");
|
||||
return 0;
|
||||
}
|
||||
|
||||
iRows = mysql_stmt_num_rows(m_pkStmt);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -172,12 +173,12 @@ bool CStmt::Fetch()
|
||||
return !mysql_stmt_fetch(m_pkStmt);
|
||||
}
|
||||
|
||||
unsigned long long CStmt::GetInsertId() const
|
||||
unsigned long CStmt::GetResultLength(unsigned int index) const
|
||||
{
|
||||
if (!m_pkStmt)
|
||||
if (index >= m_vec_result_len.size())
|
||||
return 0;
|
||||
|
||||
return mysql_stmt_insert_id(m_pkStmt);
|
||||
return m_vec_result_len[index];
|
||||
}
|
||||
|
||||
unsigned long long CStmt::GetAffectedRows() const
|
||||
@@ -187,3 +188,11 @@ unsigned long long CStmt::GetAffectedRows() const
|
||||
|
||||
return mysql_stmt_affected_rows(m_pkStmt);
|
||||
}
|
||||
|
||||
unsigned long long CStmt::GetInsertId() const
|
||||
{
|
||||
if (!m_pkStmt)
|
||||
return 0;
|
||||
|
||||
return mysql_stmt_insert_id(m_pkStmt);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ class CStmt
|
||||
bool BindResult(enum_field_types type, void * p, int iMaxLen=0);
|
||||
int Execute();
|
||||
bool Fetch();
|
||||
unsigned long long GetInsertId() const;
|
||||
unsigned long GetResultLength(unsigned int index) const;
|
||||
unsigned long long GetAffectedRows() const;
|
||||
unsigned long long GetInsertId() const;
|
||||
|
||||
void Error(const char * c_pszMsg);
|
||||
|
||||
@@ -34,11 +35,11 @@ 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;
|
||||
std::vector<unsigned long> m_vec_result_len;
|
||||
unsigned int m_uiResultCount;
|
||||
std::vector<unsigned long> m_vecResultLen;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,25 +12,76 @@ enum EFdwatch
|
||||
FDW_EOF = 8,
|
||||
};
|
||||
|
||||
enum EFdwatchBackend
|
||||
#if defined(OS_FREEBSD)
|
||||
|
||||
typedef struct kevent KEVENT;
|
||||
typedef struct kevent * LPKEVENT;
|
||||
typedef int KQUEUE;
|
||||
|
||||
struct fdwatch
|
||||
{
|
||||
FDWATCH_BACKEND_KQUEUE = 0,
|
||||
FDWATCH_BACKEND_SELECT = 1,
|
||||
FDWATCH_BACKEND_EPOLL = 2,
|
||||
KQUEUE kq;
|
||||
|
||||
int nfiles;
|
||||
|
||||
LPKEVENT kqevents;
|
||||
int nkqevents;
|
||||
|
||||
LPKEVENT kqrevents;
|
||||
int * fd_event_idx;
|
||||
|
||||
void ** fd_data;
|
||||
int * fd_rw;
|
||||
};
|
||||
|
||||
LPFDWATCH fdwatch_new(int nfiles);
|
||||
void fdwatch_clear_fd(LPFDWATCH fdw, socket_t fd);
|
||||
void fdwatch_delete(LPFDWATCH fdw);
|
||||
int fdwatch_check_fd(LPFDWATCH fdw, socket_t fd);
|
||||
int fdwatch_check_event(LPFDWATCH fdw, socket_t fd, unsigned int event_idx);
|
||||
void fdwatch_clear_event(LPFDWATCH fdw, socket_t fd, unsigned int event_idx);
|
||||
void fdwatch_add_fd(LPFDWATCH fdw, socket_t fd, void* client_data, int rw, int oneshot);
|
||||
int fdwatch(LPFDWATCH fdw, struct timeval *timeout);
|
||||
void * fdwatch_get_client_data(LPFDWATCH fdw, unsigned int event_idx);
|
||||
void fdwatch_del_fd(LPFDWATCH fdw, socket_t fd);
|
||||
int fdwatch_get_buffer_size(LPFDWATCH fdw, socket_t fd);
|
||||
int fdwatch_get_ident(LPFDWATCH fdw, unsigned int event_idx);
|
||||
EFdwatchBackend fdwatch_get_backend(LPFDWATCH fdw);
|
||||
const char * fdwatch_backend_name(EFdwatchBackend backend);
|
||||
int fdwatch_get_descriptor_limit(LPFDWATCH fdw);
|
||||
#elif defined(__linux__)
|
||||
|
||||
typedef struct epoll_event EPOLL_EVENT;
|
||||
typedef struct epoll_event * LPEPOLL_EVENT;
|
||||
|
||||
struct fdwatch
|
||||
{
|
||||
int epfd;
|
||||
int nfiles;
|
||||
LPEPOLL_EVENT ep_events;
|
||||
LPEPOLL_EVENT ready_events;
|
||||
void ** fd_data;
|
||||
int * fd_rw;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
struct fdwatch
|
||||
{
|
||||
fd_set rfd_set;
|
||||
fd_set wfd_set;
|
||||
|
||||
socket_t* select_fds;
|
||||
int* select_rfdidx;
|
||||
|
||||
int nselect_fds;
|
||||
|
||||
fd_set working_rfd_set;
|
||||
fd_set working_wfd_set;
|
||||
|
||||
int nfiles;
|
||||
|
||||
void** fd_data;
|
||||
int* fd_rw;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
LPFDWATCH fdwatch_new(int nfiles);
|
||||
void fdwatch_clear_fd(LPFDWATCH fdw, socket_t fd);
|
||||
void fdwatch_delete(LPFDWATCH fdw);
|
||||
int fdwatch_check_fd(LPFDWATCH fdw, socket_t fd);
|
||||
int fdwatch_check_event(LPFDWATCH fdw, socket_t fd, unsigned int event_idx);
|
||||
void fdwatch_clear_event(LPFDWATCH fdw, socket_t fd, unsigned int event_idx);
|
||||
void fdwatch_add_fd(LPFDWATCH fdw, socket_t fd, void* client_data, int rw, int oneshot);
|
||||
int fdwatch(LPFDWATCH fdw, struct timeval *timeout);
|
||||
void * fdwatch_get_client_data(LPFDWATCH fdw, unsigned int event_idx);
|
||||
void fdwatch_del_fd(LPFDWATCH fdw, socket_t fd);
|
||||
int fdwatch_get_buffer_size(LPFDWATCH fdw, socket_t fd);
|
||||
int fdwatch_get_ident(LPFDWATCH fdw, unsigned int event_idx);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
LPHEART thecore_heart = NULL;
|
||||
|
||||
std::atomic<int> shutdowned = FALSE;
|
||||
std::atomic<int> tics = 0;
|
||||
unsigned int thecore_profiler[NUM_PF];
|
||||
|
||||
static int pid_init(void)
|
||||
@@ -18,12 +19,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 +37,7 @@ static void pid_deinit(void)
|
||||
return;
|
||||
#else
|
||||
remove("./pid");
|
||||
sys_log(0, "End of pid");
|
||||
sys_err("\nEnd of pid\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -88,7 +89,6 @@ int thecore_idle(void)
|
||||
|
||||
void thecore_destroy(void)
|
||||
{
|
||||
signal_destroy();
|
||||
pid_deinit();
|
||||
log_destroy();
|
||||
}
|
||||
@@ -115,5 +115,5 @@ int thecore_is_shutdowned(void)
|
||||
|
||||
void thecore_tick(void)
|
||||
{
|
||||
signal_mark_progress();
|
||||
++tics;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <atomic>
|
||||
|
||||
extern std::atomic<int> tics;
|
||||
extern std::atomic<int> shutdowned;
|
||||
|
||||
#include "heart.h"
|
||||
@@ -26,4 +27,5 @@ float thecore_time(void);
|
||||
float thecore_pulse_per_second(void);
|
||||
int thecore_is_shutdowned(void);
|
||||
|
||||
void thecore_tick(void); // checkpoint progress 증가
|
||||
void thecore_tick(void); // tics 증가
|
||||
|
||||
|
||||
@@ -1,125 +1,9 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::atomic<uint64_t> s_checkpoint_progress { 0 };
|
||||
|
||||
#ifndef OS_WINDOWS
|
||||
std::mutex s_checkpoint_mutex;
|
||||
std::condition_variable s_checkpoint_cv;
|
||||
std::thread s_checkpoint_thread;
|
||||
bool s_checkpoint_shutdown = false;
|
||||
bool s_checkpoint_enabled = false;
|
||||
int s_checkpoint_timeout_seconds = 0;
|
||||
uint64_t s_checkpoint_generation = 0;
|
||||
|
||||
void checkpoint_watchdog_loop()
|
||||
{
|
||||
uint64_t last_progress = s_checkpoint_progress.load(std::memory_order_relaxed);
|
||||
auto last_change = std::chrono::steady_clock::now();
|
||||
uint64_t observed_generation = 0;
|
||||
|
||||
std::unique_lock<std::mutex> lock(s_checkpoint_mutex);
|
||||
|
||||
while (!s_checkpoint_shutdown)
|
||||
{
|
||||
if (!s_checkpoint_enabled || s_checkpoint_timeout_seconds <= 0)
|
||||
{
|
||||
s_checkpoint_cv.wait(lock, []()
|
||||
{
|
||||
return s_checkpoint_shutdown || (s_checkpoint_enabled && s_checkpoint_timeout_seconds > 0);
|
||||
});
|
||||
|
||||
last_progress = s_checkpoint_progress.load(std::memory_order_relaxed);
|
||||
last_change = std::chrono::steady_clock::now();
|
||||
observed_generation = s_checkpoint_generation;
|
||||
continue;
|
||||
}
|
||||
|
||||
const int timeout_seconds = s_checkpoint_timeout_seconds;
|
||||
const uint64_t generation = s_checkpoint_generation;
|
||||
const auto poll_interval = std::chrono::seconds(1);
|
||||
|
||||
const bool reconfigured = s_checkpoint_cv.wait_for(lock, poll_interval, [generation]()
|
||||
{
|
||||
return s_checkpoint_shutdown || s_checkpoint_generation != generation;
|
||||
});
|
||||
|
||||
if (s_checkpoint_shutdown)
|
||||
break;
|
||||
|
||||
if (reconfigured || observed_generation != s_checkpoint_generation)
|
||||
{
|
||||
last_progress = s_checkpoint_progress.load(std::memory_order_relaxed);
|
||||
last_change = std::chrono::steady_clock::now();
|
||||
observed_generation = s_checkpoint_generation;
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint64_t current_progress = s_checkpoint_progress.load(std::memory_order_relaxed);
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (current_progress != last_progress)
|
||||
{
|
||||
last_progress = current_progress;
|
||||
last_change = now;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (now - last_change >= std::chrono::seconds(timeout_seconds))
|
||||
{
|
||||
lock.unlock();
|
||||
sys_err("CHECKPOINT shutdown: no progress observed for %d seconds.", timeout_seconds);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkpoint_ensure_started()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_checkpoint_mutex);
|
||||
|
||||
if (s_checkpoint_thread.joinable())
|
||||
return;
|
||||
|
||||
s_checkpoint_shutdown = false;
|
||||
s_checkpoint_enabled = false;
|
||||
s_checkpoint_timeout_seconds = 0;
|
||||
s_checkpoint_generation = 0;
|
||||
s_checkpoint_thread = std::thread(checkpoint_watchdog_loop);
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* signal_checkpoint_backend_name_impl(ECheckpointBackend backend)
|
||||
{
|
||||
switch (backend)
|
||||
{
|
||||
case CHECKPOINT_BACKEND_NONE:
|
||||
return "none";
|
||||
case CHECKPOINT_BACKEND_VIRTUAL_TIMER:
|
||||
return "virtual-timer";
|
||||
case CHECKPOINT_BACKEND_WATCHDOG_THREAD:
|
||||
return "watchdog-thread";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef OS_WINDOWS
|
||||
void signal_setup() {}
|
||||
void signal_destroy() {}
|
||||
void signal_timer_disable() {}
|
||||
void signal_timer_enable(int timeout_seconds) {}
|
||||
void signal_mark_progress() {}
|
||||
ECheckpointBackend signal_checkpoint_backend() { return CHECKPOINT_BACKEND_NONE; }
|
||||
const char* signal_checkpoint_backend_name(ECheckpointBackend backend) { return signal_checkpoint_backend_name_impl(backend); }
|
||||
#else
|
||||
#define RETSIGTYPE void
|
||||
|
||||
@@ -130,6 +14,18 @@ RETSIGTYPE reap(int sig)
|
||||
}
|
||||
|
||||
|
||||
RETSIGTYPE checkpointing(int sig)
|
||||
{
|
||||
if (!tics.load())
|
||||
{
|
||||
sys_err("CHECKPOINT shutdown: tics did not updated.");
|
||||
abort();
|
||||
}
|
||||
else
|
||||
tics.store(0);
|
||||
}
|
||||
|
||||
|
||||
RETSIGTYPE hupsig(int sig)
|
||||
{
|
||||
shutdowned = TRUE;
|
||||
@@ -143,84 +39,46 @@ RETSIGTYPE usrsig(int sig)
|
||||
|
||||
void signal_timer_disable(void)
|
||||
{
|
||||
checkpoint_ensure_started();
|
||||
struct itimerval itime;
|
||||
struct timeval interval;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_checkpoint_mutex);
|
||||
s_checkpoint_enabled = false;
|
||||
s_checkpoint_timeout_seconds = 0;
|
||||
++s_checkpoint_generation;
|
||||
}
|
||||
interval.tv_sec = 0;
|
||||
interval.tv_usec = 0;
|
||||
|
||||
s_checkpoint_cv.notify_all();
|
||||
itime.it_interval = interval;
|
||||
itime.it_value = interval;
|
||||
|
||||
setitimer(ITIMER_VIRTUAL, &itime, NULL);
|
||||
}
|
||||
|
||||
void signal_timer_enable(int sec)
|
||||
{
|
||||
checkpoint_ensure_started();
|
||||
struct itimerval itime;
|
||||
struct timeval interval;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_checkpoint_mutex);
|
||||
s_checkpoint_enabled = sec > 0;
|
||||
s_checkpoint_timeout_seconds = sec;
|
||||
++s_checkpoint_generation;
|
||||
}
|
||||
interval.tv_sec = sec;
|
||||
interval.tv_usec = 0;
|
||||
|
||||
s_checkpoint_cv.notify_all();
|
||||
itime.it_interval = interval;
|
||||
itime.it_value = interval;
|
||||
|
||||
setitimer(ITIMER_VIRTUAL, &itime, NULL);
|
||||
}
|
||||
|
||||
void signal_setup(void)
|
||||
{
|
||||
checkpoint_ensure_started();
|
||||
signal_timer_enable(30);
|
||||
signal_timer_enable(30);
|
||||
|
||||
/* just to be on the safe side: */
|
||||
signal(SIGHUP, hupsig);
|
||||
signal(SIGCHLD, reap);
|
||||
signal(SIGINT, hupsig);
|
||||
signal(SIGTERM, hupsig);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGALRM, SIG_IGN);
|
||||
signal(SIGUSR1, usrsig);
|
||||
signal(SIGVTALRM, checkpointing);
|
||||
|
||||
sys_log(0, "[STARTUP] checkpoint backend=%s", signal_checkpoint_backend_name(signal_checkpoint_backend()));
|
||||
}
|
||||
|
||||
void signal_destroy()
|
||||
{
|
||||
std::thread checkpoint_thread;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_checkpoint_mutex);
|
||||
|
||||
if (!s_checkpoint_thread.joinable())
|
||||
return;
|
||||
|
||||
s_checkpoint_shutdown = true;
|
||||
s_checkpoint_enabled = false;
|
||||
s_checkpoint_timeout_seconds = 0;
|
||||
++s_checkpoint_generation;
|
||||
checkpoint_thread = std::move(s_checkpoint_thread);
|
||||
}
|
||||
|
||||
s_checkpoint_cv.notify_all();
|
||||
checkpoint_thread.join();
|
||||
s_checkpoint_progress.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void signal_mark_progress()
|
||||
{
|
||||
s_checkpoint_progress.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
ECheckpointBackend signal_checkpoint_backend()
|
||||
{
|
||||
return CHECKPOINT_BACKEND_WATCHDOG_THREAD;
|
||||
}
|
||||
|
||||
const char* signal_checkpoint_backend_name(ECheckpointBackend backend)
|
||||
{
|
||||
return signal_checkpoint_backend_name_impl(backend);
|
||||
/* just to be on the safe side: */
|
||||
signal(SIGHUP, hupsig);
|
||||
signal(SIGCHLD, reap);
|
||||
signal(SIGINT, hupsig);
|
||||
signal(SIGTERM, hupsig);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
signal(SIGALRM, SIG_IGN);
|
||||
signal(SIGUSR1, usrsig);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
enum ECheckpointBackend
|
||||
{
|
||||
CHECKPOINT_BACKEND_NONE = 0,
|
||||
CHECKPOINT_BACKEND_VIRTUAL_TIMER = 1,
|
||||
CHECKPOINT_BACKEND_WATCHDOG_THREAD = 2,
|
||||
};
|
||||
|
||||
void signal_setup();
|
||||
void signal_destroy();
|
||||
void signal_timer_disable();
|
||||
void signal_timer_enable(int timeout_seconds);
|
||||
void signal_mark_progress();
|
||||
ECheckpointBackend signal_checkpoint_backend();
|
||||
const char* signal_checkpoint_backend_name(ECheckpointBackend backend);
|
||||
void signal_timer_enable(int timeout_seconds);
|
||||
@@ -10,31 +10,6 @@ void socket_timeout(socket_t s, long sec, long usec);
|
||||
void socket_reuse(socket_t s);
|
||||
void socket_keepalive(socket_t s);
|
||||
|
||||
namespace
|
||||
{
|
||||
bool socket_accept_should_retry()
|
||||
{
|
||||
#ifdef OS_WINDOWS
|
||||
const int wsa_error = WSAGetLastError();
|
||||
return wsa_error == WSAEWOULDBLOCK || wsa_error == WSAEINTR;
|
||||
#else
|
||||
#ifdef EINTR
|
||||
if (errno == EINTR)
|
||||
return true;
|
||||
#endif
|
||||
#ifdef EAGAIN
|
||||
if (errno == EAGAIN)
|
||||
return true;
|
||||
#endif
|
||||
#ifdef EWOULDBLOCK
|
||||
if (errno == EWOULDBLOCK)
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
int socket_read(socket_t desc, char* read_point, size_t space_left)
|
||||
{
|
||||
int ret;
|
||||
@@ -45,10 +20,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)
|
||||
@@ -234,8 +206,6 @@ socket_t socket_accept(socket_t s, struct sockaddr_in *peer)
|
||||
|
||||
if ((desc = accept(s, (struct sockaddr *) peer, &i)) == -1)
|
||||
{
|
||||
if (socket_accept_should_retry())
|
||||
return -1;
|
||||
sys_err("accept: %s (fd %d)", strerror(errno), s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ inline double rint(double x)
|
||||
|
||||
#else
|
||||
|
||||
#ifndef OS_FREEBSD
|
||||
#if !defined(OS_FREEBSD) && !defined(__linux__)
|
||||
#define __USE_SELECT__
|
||||
#ifdef __CYGWIN__
|
||||
#define _POSIX_SOURCE 1
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
if(WIN32)
|
||||
return()
|
||||
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
|
||||
login_smoke.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/game/SecureCipher.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(metin_smoke_tests
|
||||
libthecore
|
||||
sodium
|
||||
pthread
|
||||
)
|
||||
|
||||
target_link_libraries(metin_login_smoke
|
||||
libthecore
|
||||
sodium
|
||||
pthread
|
||||
)
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
|
||||
target_link_libraries(metin_smoke_tests md)
|
||||
target_link_libraries(metin_login_smoke md)
|
||||
endif()
|
||||
|
||||
add_test(NAME metin_smoke_tests COMMAND metin_smoke_tests)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,446 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#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/SecureCipher.h"
|
||||
#include "libthecore/fdwatch.h"
|
||||
#include "libthecore/signal.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct WirePhasePacket
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t phase;
|
||||
};
|
||||
|
||||
struct WireKeyChallengePacket
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t server_pk[SecureCipher::PK_SIZE];
|
||||
uint8_t challenge[SecureCipher::CHALLENGE_SIZE];
|
||||
uint32_t server_time;
|
||||
};
|
||||
|
||||
struct WireKeyResponsePacket
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t client_pk[SecureCipher::PK_SIZE];
|
||||
uint8_t challenge_response[SecureCipher::HMAC_SIZE];
|
||||
};
|
||||
|
||||
struct WireKeyCompletePacket
|
||||
{
|
||||
uint16_t header;
|
||||
uint16_t length;
|
||||
uint8_t encrypted_token[SecureCipher::SESSION_TOKEN_SIZE + SecureCipher::TAG_SIZE];
|
||||
uint8_t nonce[SecureCipher::NONCE_SIZE];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
void Expect(bool condition, const char* message)
|
||||
{
|
||||
if (!condition)
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
|
||||
void WriteExact(int fd, const void* data, size_t length, const char* message)
|
||||
{
|
||||
const uint8_t* cursor = static_cast<const uint8_t*>(data);
|
||||
size_t remaining = length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
const ssize_t written = write(fd, cursor, remaining);
|
||||
Expect(written > 0, message);
|
||||
cursor += written;
|
||||
remaining -= static_cast<size_t>(written);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadExact(int fd, void* data, size_t length, const char* message)
|
||||
{
|
||||
uint8_t* cursor = static_cast<uint8_t*>(data);
|
||||
size_t remaining = length;
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
const ssize_t bytes_read = read(fd, cursor, remaining);
|
||||
Expect(bytes_read > 0, message);
|
||||
cursor += bytes_read;
|
||||
remaining -= static_cast<size_t>(bytes_read);
|
||||
}
|
||||
}
|
||||
|
||||
void TestPacketLayouts()
|
||||
{
|
||||
Expect(sizeof(WirePhasePacket) == 5, "Unexpected phase wire size");
|
||||
Expect(sizeof(WireKeyChallengePacket) == 72, "Unexpected key challenge wire size");
|
||||
Expect(sizeof(WireKeyResponsePacket) == 68, "Unexpected key response wire size");
|
||||
Expect(sizeof(WireKeyCompletePacket) == 76, "Unexpected key complete wire size");
|
||||
}
|
||||
|
||||
void TestSecureCipherRoundTrip()
|
||||
{
|
||||
SecureCipher server;
|
||||
SecureCipher client;
|
||||
|
||||
Expect(server.Initialize(), "Server SecureCipher init failed");
|
||||
Expect(client.Initialize(), "Client SecureCipher init failed");
|
||||
|
||||
std::array<uint8_t, SecureCipher::PK_SIZE> server_pk {};
|
||||
std::array<uint8_t, SecureCipher::PK_SIZE> client_pk {};
|
||||
server.GetPublicKey(server_pk.data());
|
||||
client.GetPublicKey(client_pk.data());
|
||||
|
||||
Expect(client.ComputeClientKeys(server_pk.data()), "Client session key derivation failed");
|
||||
Expect(server.ComputeServerKeys(client_pk.data()), "Server session key derivation failed");
|
||||
|
||||
std::array<uint8_t, SecureCipher::CHALLENGE_SIZE> challenge {};
|
||||
std::array<uint8_t, SecureCipher::HMAC_SIZE> response {};
|
||||
server.GenerateChallenge(challenge.data());
|
||||
client.ComputeChallengeResponse(challenge.data(), response.data());
|
||||
Expect(server.VerifyChallengeResponse(challenge.data(), response.data()), "Challenge verification failed");
|
||||
|
||||
std::array<uint8_t, SecureCipher::SESSION_TOKEN_SIZE> token {};
|
||||
for (size_t i = 0; i < token.size(); ++i)
|
||||
token[i] = static_cast<uint8_t>(i);
|
||||
|
||||
std::array<uint8_t, SecureCipher::SESSION_TOKEN_SIZE + SecureCipher::TAG_SIZE> ciphertext {};
|
||||
std::array<uint8_t, SecureCipher::NONCE_SIZE> nonce {};
|
||||
std::array<uint8_t, SecureCipher::SESSION_TOKEN_SIZE> plaintext {};
|
||||
|
||||
Expect(server.EncryptToken(token.data(), token.size(), ciphertext.data(), nonce.data()), "Token encryption failed");
|
||||
Expect(client.DecryptToken(ciphertext.data(), ciphertext.size(), nonce.data(), plaintext.data()), "Token decryption failed");
|
||||
Expect(std::memcmp(token.data(), plaintext.data(), token.size()) == 0, "Token round-trip mismatch");
|
||||
|
||||
server.SetActivated(true);
|
||||
client.SetActivated(true);
|
||||
|
||||
std::array<uint8_t, 96> payload {};
|
||||
for (size_t i = 0; i < payload.size(); ++i)
|
||||
payload[i] = static_cast<uint8_t>(0xA0 + (i % 31));
|
||||
|
||||
auto encrypted = payload;
|
||||
server.EncryptInPlace(encrypted.data(), encrypted.size());
|
||||
client.DecryptInPlace(encrypted.data(), encrypted.size());
|
||||
Expect(encrypted == payload, "Server to client stream cipher round-trip failed");
|
||||
|
||||
auto reverse = payload;
|
||||
client.EncryptInPlace(reverse.data(), reverse.size());
|
||||
server.DecryptInPlace(reverse.data(), reverse.size());
|
||||
Expect(reverse == payload, "Client to server stream cipher round-trip failed");
|
||||
}
|
||||
|
||||
void TestSocketAuthWireFlow()
|
||||
{
|
||||
SecureCipher server;
|
||||
SecureCipher client;
|
||||
|
||||
Expect(server.Initialize(), "Server auth cipher init failed");
|
||||
Expect(client.Initialize(), "Client auth cipher init failed");
|
||||
|
||||
int sockets[2] = { -1, -1 };
|
||||
Expect(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0, "socketpair for auth flow failed");
|
||||
|
||||
WirePhasePacket phase_packet {};
|
||||
phase_packet.header = GC::PHASE;
|
||||
phase_packet.length = sizeof(phase_packet);
|
||||
phase_packet.phase = PHASE_HANDSHAKE;
|
||||
|
||||
WireKeyChallengePacket key_challenge {};
|
||||
key_challenge.header = GC::KEY_CHALLENGE;
|
||||
key_challenge.length = sizeof(key_challenge);
|
||||
server.GetPublicKey(key_challenge.server_pk);
|
||||
server.GenerateChallenge(key_challenge.challenge);
|
||||
key_challenge.server_time = 0x12345678;
|
||||
|
||||
WriteExact(sockets[0], &phase_packet, sizeof(phase_packet), "Failed to write phase packet");
|
||||
WriteExact(sockets[0], &key_challenge, sizeof(key_challenge), "Failed to write key challenge");
|
||||
|
||||
WirePhasePacket client_phase {};
|
||||
WireKeyChallengePacket client_challenge {};
|
||||
ReadExact(sockets[1], &client_phase, sizeof(client_phase), "Failed to read phase packet");
|
||||
ReadExact(sockets[1], &client_challenge, sizeof(client_challenge), "Failed to read key challenge");
|
||||
|
||||
Expect(client_phase.header == GC::PHASE, "Unexpected phase header");
|
||||
Expect(client_phase.length == sizeof(client_phase), "Unexpected phase packet length");
|
||||
Expect(client_phase.phase == PHASE_HANDSHAKE, "Unexpected phase value");
|
||||
Expect(client_challenge.header == GC::KEY_CHALLENGE, "Unexpected key challenge header");
|
||||
Expect(client_challenge.length == sizeof(client_challenge), "Unexpected key challenge length");
|
||||
Expect(std::memcmp(client_challenge.server_pk, key_challenge.server_pk, sizeof(key_challenge.server_pk)) == 0,
|
||||
"Server public key changed on the wire");
|
||||
Expect(std::memcmp(client_challenge.challenge, key_challenge.challenge, sizeof(key_challenge.challenge)) == 0,
|
||||
"Challenge bytes changed on the wire");
|
||||
|
||||
Expect(client.ComputeClientKeys(client_challenge.server_pk), "Client auth key derivation failed");
|
||||
|
||||
WireKeyResponsePacket key_response {};
|
||||
key_response.header = CG::KEY_RESPONSE;
|
||||
key_response.length = sizeof(key_response);
|
||||
client.GetPublicKey(key_response.client_pk);
|
||||
client.ComputeChallengeResponse(client_challenge.challenge, key_response.challenge_response);
|
||||
|
||||
WriteExact(sockets[1], &key_response, sizeof(key_response), "Failed to write key response");
|
||||
|
||||
WireKeyResponsePacket server_response {};
|
||||
ReadExact(sockets[0], &server_response, sizeof(server_response), "Failed to read key response");
|
||||
|
||||
Expect(server_response.header == CG::KEY_RESPONSE, "Unexpected key response header");
|
||||
Expect(server_response.length == sizeof(server_response), "Unexpected key response length");
|
||||
Expect(server.ComputeServerKeys(server_response.client_pk), "Server auth key derivation failed");
|
||||
Expect(server.VerifyChallengeResponse(key_challenge.challenge, server_response.challenge_response),
|
||||
"Server rejected challenge response");
|
||||
|
||||
std::array<uint8_t, SecureCipher::SESSION_TOKEN_SIZE> session_token {};
|
||||
for (size_t i = 0; i < session_token.size(); ++i)
|
||||
session_token[i] = static_cast<uint8_t>(0x30 + i);
|
||||
|
||||
server.SetSessionToken(session_token.data());
|
||||
|
||||
WireKeyCompletePacket key_complete {};
|
||||
key_complete.header = GC::KEY_COMPLETE;
|
||||
key_complete.length = sizeof(key_complete);
|
||||
Expect(server.EncryptToken(session_token.data(), session_token.size(), key_complete.encrypted_token, key_complete.nonce),
|
||||
"Failed to encrypt key complete token");
|
||||
|
||||
WriteExact(sockets[0], &key_complete, sizeof(key_complete), "Failed to write key complete");
|
||||
|
||||
WireKeyCompletePacket client_complete {};
|
||||
ReadExact(sockets[1], &client_complete, sizeof(client_complete), "Failed to read key complete");
|
||||
|
||||
Expect(client_complete.header == GC::KEY_COMPLETE, "Unexpected key complete header");
|
||||
Expect(client_complete.length == sizeof(client_complete), "Unexpected key complete length");
|
||||
|
||||
std::array<uint8_t, SecureCipher::SESSION_TOKEN_SIZE> decrypted_token {};
|
||||
Expect(client.DecryptToken(client_complete.encrypted_token, sizeof(client_complete.encrypted_token),
|
||||
client_complete.nonce, decrypted_token.data()),
|
||||
"Failed to decrypt key complete token");
|
||||
Expect(decrypted_token == session_token, "Session token changed on the wire");
|
||||
|
||||
server.SetActivated(true);
|
||||
client.SetSessionToken(decrypted_token.data());
|
||||
client.SetActivated(true);
|
||||
|
||||
std::array<uint8_t, 32> payload {};
|
||||
for (size_t i = 0; i < payload.size(); ++i)
|
||||
payload[i] = static_cast<uint8_t>(0x41 + i);
|
||||
|
||||
auto encrypted_payload = payload;
|
||||
server.EncryptInPlace(encrypted_payload.data(), encrypted_payload.size());
|
||||
WriteExact(sockets[0], encrypted_payload.data(), encrypted_payload.size(), "Failed to write encrypted payload");
|
||||
|
||||
std::array<uint8_t, 32> received_payload {};
|
||||
ReadExact(sockets[1], received_payload.data(), received_payload.size(), "Failed to read encrypted payload");
|
||||
client.DecryptInPlace(received_payload.data(), received_payload.size());
|
||||
Expect(received_payload == payload, "Encrypted payload round-trip mismatch");
|
||||
|
||||
close(sockets[0]);
|
||||
close(sockets[1]);
|
||||
}
|
||||
|
||||
void TestFdwatchReadAndOneshotWrite()
|
||||
{
|
||||
int sockets[2] = { -1, -1 };
|
||||
Expect(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == 0, "socketpair failed");
|
||||
|
||||
LPFDWATCH fdw = fdwatch_new(64);
|
||||
Expect(fdw != nullptr, "fdwatch_new failed");
|
||||
|
||||
int marker = 42;
|
||||
fdwatch_add_fd(fdw, sockets[1], &marker, FDW_READ, false);
|
||||
|
||||
const uint8_t byte = 0x7F;
|
||||
Expect(write(sockets[0], &byte, sizeof(byte)) == sizeof(byte), "socketpair write failed");
|
||||
|
||||
timeval timeout {};
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 200000;
|
||||
|
||||
int num_events = fdwatch(fdw, &timeout);
|
||||
Expect(num_events == 1, "Expected one read event");
|
||||
Expect(fdwatch_get_client_data(fdw, 0) == &marker, "Unexpected client data");
|
||||
Expect(fdwatch_check_event(fdw, sockets[1], 0) == FDW_READ, "Expected FDW_READ event");
|
||||
|
||||
uint8_t read_back = 0;
|
||||
Expect(read(sockets[1], &read_back, sizeof(read_back)) == sizeof(read_back), "socketpair read failed");
|
||||
Expect(read_back == byte, "Read payload mismatch");
|
||||
|
||||
fdwatch_add_fd(fdw, sockets[1], &marker, FDW_WRITE, true);
|
||||
num_events = fdwatch(fdw, &timeout);
|
||||
Expect(num_events >= 1, "Expected at least one write event");
|
||||
Expect(fdwatch_check_event(fdw, sockets[1], 0) == FDW_WRITE, "Expected FDW_WRITE event");
|
||||
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
num_events = fdwatch(fdw, &timeout);
|
||||
Expect(num_events == 0, "FDW_WRITE oneshot was not cleared");
|
||||
|
||||
fdwatch_del_fd(fdw, sockets[1]);
|
||||
fdwatch_delete(fdw);
|
||||
close(sockets[0]);
|
||||
close(sockets[1]);
|
||||
}
|
||||
|
||||
void TestFdwatchBackendMetadata()
|
||||
{
|
||||
LPFDWATCH fdw = fdwatch_new(4096);
|
||||
Expect(fdw != nullptr, "fdwatch_new for backend metadata failed");
|
||||
|
||||
#ifdef __linux__
|
||||
Expect(fdwatch_get_backend(fdw) == FDWATCH_BACKEND_EPOLL, "Expected epoll backend");
|
||||
Expect(std::strcmp(fdwatch_backend_name(fdwatch_get_backend(fdw)), "epoll") == 0, "Unexpected epoll backend name");
|
||||
Expect(fdwatch_get_descriptor_limit(fdw) == 4096, "Unexpected epoll descriptor limit");
|
||||
#elif defined(__USE_SELECT__)
|
||||
Expect(fdwatch_get_backend(fdw) == FDWATCH_BACKEND_SELECT, "Expected select backend");
|
||||
Expect(std::strcmp(fdwatch_backend_name(fdwatch_get_backend(fdw)), "select") == 0, "Unexpected select backend name");
|
||||
Expect(fdwatch_get_descriptor_limit(fdw) == std::min(4096, static_cast<int>(FD_SETSIZE)), "Unexpected select descriptor limit");
|
||||
#else
|
||||
Expect(fdwatch_get_backend(fdw) == FDWATCH_BACKEND_KQUEUE, "Expected kqueue backend");
|
||||
Expect(std::strcmp(fdwatch_backend_name(fdwatch_get_backend(fdw)), "kqueue") == 0, "Unexpected kqueue backend name");
|
||||
Expect(fdwatch_get_descriptor_limit(fdw) == 4096, "Unexpected kqueue descriptor limit");
|
||||
#endif
|
||||
|
||||
fdwatch_delete(fdw);
|
||||
}
|
||||
|
||||
void TestCheckpointBackendMetadata()
|
||||
{
|
||||
#ifdef OS_WINDOWS
|
||||
Expect(signal_checkpoint_backend() == CHECKPOINT_BACKEND_NONE, "Expected no checkpoint backend on Windows");
|
||||
Expect(std::strcmp(signal_checkpoint_backend_name(signal_checkpoint_backend()), "none") == 0,
|
||||
"Unexpected checkpoint backend name on Windows");
|
||||
#else
|
||||
Expect(signal_checkpoint_backend() == CHECKPOINT_BACKEND_WATCHDOG_THREAD, "Expected watchdog thread checkpoint backend");
|
||||
Expect(std::strcmp(signal_checkpoint_backend_name(signal_checkpoint_backend()), "watchdog-thread") == 0,
|
||||
"Unexpected checkpoint backend name");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestFdwatchSlotReuseAfterDelete()
|
||||
{
|
||||
int sockets_a[2] = { -1, -1 };
|
||||
int sockets_b[2] = { -1, -1 };
|
||||
Expect(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets_a) == 0, "socketpair A failed");
|
||||
Expect(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets_b) == 0, "socketpair B failed");
|
||||
|
||||
LPFDWATCH fdw = fdwatch_new(64);
|
||||
Expect(fdw != nullptr, "fdwatch_new for slot reuse failed");
|
||||
|
||||
int marker_a = 11;
|
||||
int marker_b = 22;
|
||||
|
||||
fdwatch_add_fd(fdw, sockets_a[1], &marker_a, FDW_READ, false);
|
||||
fdwatch_add_fd(fdw, sockets_b[1], &marker_b, FDW_READ, false);
|
||||
fdwatch_del_fd(fdw, sockets_a[1]);
|
||||
|
||||
const uint8_t byte = 0x51;
|
||||
Expect(write(sockets_b[0], &byte, sizeof(byte)) == sizeof(byte), "socketpair B write failed");
|
||||
|
||||
timeval timeout {};
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 200000;
|
||||
|
||||
const int num_events = fdwatch(fdw, &timeout);
|
||||
Expect(num_events == 1, "Expected one read event after slot reuse");
|
||||
Expect(fdwatch_get_client_data(fdw, 0) == &marker_b, "Unexpected client data after slot reuse");
|
||||
Expect(fdwatch_check_event(fdw, sockets_b[1], 0) == FDW_READ, "Expected FDW_READ after slot reuse");
|
||||
|
||||
uint8_t read_back = 0;
|
||||
Expect(read(sockets_b[1], &read_back, sizeof(read_back)) == sizeof(read_back), "socketpair B read failed");
|
||||
Expect(read_back == byte, "Read payload mismatch after slot reuse");
|
||||
|
||||
fdwatch_delete(fdw);
|
||||
close(sockets_a[0]);
|
||||
close(sockets_a[1]);
|
||||
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()
|
||||
{
|
||||
try
|
||||
{
|
||||
TestPacketLayouts();
|
||||
TestSecureCipherRoundTrip();
|
||||
TestSocketAuthWireFlow();
|
||||
TestFdwatchBackendMetadata();
|
||||
TestCheckpointBackendMetadata();
|
||||
TestFdwatchReadAndOneshotWrite();
|
||||
TestFdwatchSlotReuseAfterDelete();
|
||||
TestQuestInfoPacketFraming();
|
||||
TestRequestCooldownGuard();
|
||||
std::cout << "metin smoke tests passed\n";
|
||||
return 0;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "metin smoke tests failed: " << e.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user