Files
LGram16 dd97ddec92 Restructure repository to include all source folders
Move git root from Client/ to src/ to track all source code:
- Client: Game client source (moved to Client/Client/)
- Server: Game server source
- GameTools: Development tools
- CryptoSource: Encryption utilities
- database: Database scripts
- Script: Game scripts
- rylCoder_16.02.2008_src: Legacy coder tools
- GMFont, Game: Additional resources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 20:17:20 +09:00

704 lines
21 KiB
C++

#include "stdafx.h"
#include "ChatCharData.h"
#include "BroadcastCharacterData.h"
#include <DB/DBComponent.h>
#include <DB/AdminDBComponent.h>
#include <Community/FriendList.h>
#include <Community/BanList.h>
#include <Network/Dispatch/GameClient/SendCharCommunity.h>
#include <Network/Dispatch/MultiDispatchStorage.h>
using Broadcast2nd::COPY_AND_ADVANCE_SRC;
using Broadcast2nd::COPY_AND_ADVANCE_DST;
ChatData::CCharInfo::CCharInfo(unsigned long dwUID, unsigned long dwCID,
unsigned long dwServerID, CXRefFriends* lpXRefFriends, CXRefBans* lpXRefBans)
: m_dwUID(dwUID), m_dwServerID(dwServerID), m_bSendLoginData(false),
m_cAdminLevel(0), m_FriendList(dwCID, lpXRefFriends), m_BanList(dwCID, lpXRefBans)
{
memset(m_szName, 0, sizeof(char) * CHAR_INFOST::MAX_NAME_LEN);
memset(m_szAccountName, 0, sizeof(char) * CHAR_INFOST::MAX_ACCOUNT_LEN);
memset(&m_CharData, 0, sizeof(BattleInclination::CharData));
memset(&m_RejectOption, 0, sizeof(RejectOption));
m_CharData.m_dwCID = dwCID;
}
bool ChatData::CCharInfo::UpdateData(const char* szData, int& nDataLen)
{
// 업데이트되어야 할 데이터 종류
const char* szDataPos = szData;
if (nDataLen < sizeof(unsigned short))
{
return false;
}
unsigned short usUpdateFlag = 0;
COPY_AND_ADVANCE_SRC(&usUpdateFlag, szDataPos, sizeof(unsigned short));
int nEstimateSize = EstimateBufferSize(usUpdateFlag) + sizeof(unsigned short);
if (nDataLen < nEstimateSize)
{
return false;
}
if (usUpdateFlag & DELTA_NAME)
{
COPY_AND_ADVANCE_SRC(m_szName,
szDataPos, sizeof(char) * CHAR_INFOST::MAX_NAME_LEN);
}
if (usUpdateFlag & DELTA_PID)
{
COPY_AND_ADVANCE_SRC(&m_CharData.m_dwPID,
szDataPos, sizeof(unsigned long));
}
if (usUpdateFlag & DELTA_GID)
{
COPY_AND_ADVANCE_SRC(&m_CharData.m_dwGID,
szDataPos, sizeof(unsigned long));
COPY_AND_ADVANCE_SRC(&m_CharData.m_cTacticsFlag,
szDataPos, sizeof(unsigned char));
}
if (usUpdateFlag & DELTA_WARFLAG)
{
COPY_AND_ADVANCE_SRC(&m_CharData.m_cRealmWarFlag,
szDataPos, sizeof(unsigned char));
COPY_AND_ADVANCE_SRC(&m_CharData.m_cGuildWarFlag,
szDataPos, sizeof(unsigned char));
}
if (usUpdateFlag & DELTA_CASTLEID)
{
COPY_AND_ADVANCE_SRC(&m_CharData.m_dwCastleID,
szDataPos, sizeof(unsigned long));
}
if (usUpdateFlag & DELTA_SIEGESTATE)
{
m_CharData.m_cSiegeState = *szDataPos;
++szDataPos;
}
if (usUpdateFlag & DELTA_NATION)
{
m_CharData.m_cNation = *szDataPos;
++szDataPos;
}
if (usUpdateFlag & DELTA_ETCFLAGS)
{
m_CharData.m_cFlags = *szDataPos;
++szDataPos;
}
if (usUpdateFlag & DELTA_REJECTOPTION)
{
COPY_AND_ADVANCE_SRC(&m_RejectOption,
szDataPos, sizeof(RejectOption));
}
if (usUpdateFlag & DELTA_ADMINLEVEL)
{
m_cAdminLevel = *szDataPos;
++szDataPos;
}
if (usUpdateFlag & DELTA_ACCOUNTNAME)
{
COPY_AND_ADVANCE_SRC(m_szAccountName,
szDataPos, sizeof(char) * CHAR_INFOST::MAX_ACCOUNT_LEN);
}
if (usUpdateFlag & DELTA_CREATURETYPE)
{
m_CharData.m_cCreatureType = *szDataPos;
++szDataPos;
}
if (usUpdateFlag & DELTA_OBJECTTYPE)
{
COPY_AND_ADVANCE_SRC(&m_CharData.m_wObjectType,
szDataPos, sizeof(unsigned short));
}
if (usUpdateFlag & DELTA_KID)
{
COPY_AND_ADVANCE_SRC(&m_CharData.m_wObjectType,
szDataPos, sizeof(unsigned short));
}
nDataLen = static_cast<int>(szDataPos - szData);
return true;
}
int ChatData::CCharInfo::EstimateBufferSize(unsigned short usUpdateFlag)
{
int nBufferSize = 0;
if (usUpdateFlag & DELTA_NAME) { nBufferSize += sizeof(char) * CHAR_INFOST::MAX_NAME_LEN; }
if (usUpdateFlag & DELTA_PID) { nBufferSize += sizeof(unsigned long); }
if (usUpdateFlag & DELTA_GID) { nBufferSize += sizeof(unsigned long); }
if (usUpdateFlag & DELTA_GID) { nBufferSize += sizeof(unsigned char); }
if (usUpdateFlag & DELTA_WARFLAG) { nBufferSize += sizeof(unsigned char)*2; }
if (usUpdateFlag & DELTA_CASTLEID) { nBufferSize += sizeof(unsigned long); }
if (usUpdateFlag & DELTA_SIEGESTATE) { nBufferSize += sizeof(unsigned char); }
if (usUpdateFlag & DELTA_NATION) { nBufferSize += sizeof(unsigned char); }
if (usUpdateFlag & DELTA_ETCFLAGS) { nBufferSize += sizeof(unsigned char); }
if (usUpdateFlag & DELTA_REJECTOPTION) { nBufferSize += sizeof(RejectOption); }
if (usUpdateFlag & DELTA_ADMINLEVEL) { nBufferSize += sizeof(unsigned char); }
if (usUpdateFlag & DELTA_ACCOUNTNAME) { nBufferSize += sizeof(char) * CHAR_INFOST::MAX_ACCOUNT_LEN; }
if (usUpdateFlag & DELTA_CREATURETYPE) { nBufferSize += sizeof(unsigned char); }
if (usUpdateFlag & DELTA_OBJECTTYPE) { nBufferSize += sizeof(unsigned short); }
if (usUpdateFlag & DELTA_KID) { nBufferSize += sizeof(unsigned short); }
return nBufferSize;
}
ChatData::CCharInfoMap& ChatData::CCharInfoMap::GetInstance()
{
static CCharInfoMap charInfoMap;
return charInfoMap;
}
ChatData::CCharInfoMap::~CCharInfoMap()
{
Destroy();
}
void ChatData::CCharInfoMap::Destroy()
{
CharInfoMap::iterator pos = m_cidMap.begin();
CharInfoMap::iterator end = m_cidMap.end();
for(;pos != end; ++pos)
{
delete pos->second;
}
m_cidMap.clear();
m_pidMap.clear();
m_gidMap.clear();
m_raceMap.clear();
m_nameMap.clear();
}
inline unsigned long nameHash(const unsigned char *str, int length)
{
unsigned long hash = 0;
int c;
while (c = *str && 0 < length)
{
++str;
--length;
hash = c + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
bool ChatData::CCharInfoMap::AddCharInfo(CCharInfo* lpCharInfo)
{
if (0 != lpCharInfo)
{
// 이미 있는 캐릭터를 제거하고, 신규 캐릭터를 넣는다.
CharInfoMap::iterator insertPos = m_cidMap.lower_bound(lpCharInfo->GetCID());
if (insertPos != m_cidMap.end() && insertPos->first == lpCharInfo->GetCID())
{
// 기존 데이터를 찾았다. 기존 데이터는 삭제한다.
CCharInfo* lpDelCharInfo = insertPos->second;
CCharInfo* lpRemovedCharInfo = RemoveCharInfo(lpDelCharInfo->GetUID(),
lpDelCharInfo->GetCID(), lpDelCharInfo->GetServerID());
if (0 != lpRemovedCharInfo)
{
delete lpRemovedCharInfo;
}
}
// 신규 데이터이다.
CharInfoMap::iterator result = m_cidMap.insert(insertPos,
CharInfoMap::value_type(lpCharInfo->GetCID(), lpCharInfo));
if (result != m_cidMap.end())
{
// GID
if (0 != lpCharInfo->GetGID())
{
m_gidMap.insert(CharInfoMap::value_type(
lpCharInfo->GetGID(), lpCharInfo));
}
// PID
if (0 != lpCharInfo->GetPID())
{
m_pidMap.insert(CharInfoMap::value_type(
lpCharInfo->GetPID(), lpCharInfo));
}
// Race
m_raceMap.insert(CharInfoMap::value_type(
lpCharInfo->GetRace(), lpCharInfo));
// 캐릭터 이름
unsigned long dwNameHash = nameHash(
reinterpret_cast<const unsigned char*>(lpCharInfo->GetName()),
CHAR_INFOST::MAX_NAME_LEN);
m_nameMap.insert(CharInfoMultiMap::value_type(dwNameHash, lpCharInfo));
// 계정명
unsigned long dwAccountHash = nameHash(
reinterpret_cast<const unsigned char*>(lpCharInfo->GetAccountName()),
CHAR_INFOST::MAX_ACCOUNT_LEN);
m_accountMap.insert(CharInfoMultiMap::value_type(dwAccountHash, lpCharInfo));
// 채팅 금지
unsigned long dwMinutes = 0;
if (DBComponent::AdminDB::GetCharChatBan(
CDBSingleObject::GetInstance(), lpCharInfo->GetCID(), dwMinutes) && dwMinutes)
{
AddCharChatBan(lpCharInfo->GetCID(), dwMinutes);
}
DETLOG3(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/캐릭터 정보 추가 성공",
lpCharInfo->GetUID(), lpCharInfo->GetCID(), lpCharInfo->GetServerID());
return true;
}
else
{
ERRLOG1(g_Log, "CID:0x%08x/데이터를 삽입할 수 없습니다.", lpCharInfo->GetCID());
}
}
return false;
}
ChatData::CCharInfo* ChatData::CCharInfoMap::GetCharInfoByUID(unsigned long dwUID)
{
CharInfoMap::iterator pos = m_cidMap.begin();
CharInfoMap::iterator end = m_cidMap.end();
for(; pos != end; ++pos)
{
CCharInfo* lpCharInfo = pos->second;
if (lpCharInfo->GetUID() == dwUID)
{
return lpCharInfo;
}
}
return 0;
}
ChatData::CCharInfo* ChatData::CCharInfoMap::GetCharInfoByCID(unsigned long dwCID)
{
CharInfoMap::iterator pos = m_cidMap.find(dwCID);
CharInfoMap::iterator end = m_cidMap.end();
return pos != end ? pos->second : 0;
}
ChatData::CCharInfo* ChatData::CCharInfoMap::GetCharInfoByName(const char* szName)
{
std::pair<CharInfoMultiMap::iterator, CharInfoMultiMap::iterator>
result = m_nameMap.equal_range(nameHash(
reinterpret_cast<const unsigned char*>(szName), CHAR_INFOST::MAX_NAME_LEN));
for(; result.first != result.second; ++result.first)
{
CCharInfo* lpCharInfo = result.first->second;
if (0 == strncmp(lpCharInfo->GetName(), szName, CHAR_INFOST::MAX_NAME_LEN))
{
return lpCharInfo;
}
}
return 0;
}
ChatData::CCharInfo* ChatData::CCharInfoMap::GetCharInfoByAccountName(const char* szAccountName)
{
std::pair<CharInfoMultiMap::iterator, CharInfoMultiMap::iterator>
result = m_accountMap.equal_range(nameHash(
reinterpret_cast<const unsigned char*>(szAccountName), CHAR_INFOST::MAX_ACCOUNT_LEN));
for(; result.first != result.second; ++result.first)
{
CCharInfo* lpCharInfo = result.first->second;
if (0 == strncmp(lpCharInfo->GetAccountName(), szAccountName, CHAR_INFOST::MAX_ACCOUNT_LEN))
{
return lpCharInfo;
}
}
return 0;
}
namespace ChatData
{
inline bool EraseByKey(CCharInfoMap::CharInfoMultiMap& map,
unsigned long dwUID, unsigned long dwCID, unsigned long dwServerID, unsigned long dwKey)
{
std::pair<CCharInfoMap::CharInfoMultiMap::iterator,
CCharInfoMap::CharInfoMultiMap::iterator> result = map.equal_range(dwKey);
for(; result.first != result.second; ++result.first)
{
CCharInfo* lpDelCharInfo = result.first->second;
if(!lpDelCharInfo)
{
return false;
}
if (lpDelCharInfo->GetUID() == dwUID &&
lpDelCharInfo->GetCID() == dwCID &&
lpDelCharInfo->GetServerID() == dwServerID)
{
map.erase(result.first);
return true;
}
}
return false;
}
};
ChatData::CCharInfo* ChatData::CCharInfoMap::RemoveCharInfo(unsigned long dwUID,
unsigned long dwCID, unsigned long dwServerID)
{
CharInfoMap::iterator pos = m_cidMap.find(dwCID);
if (pos != m_cidMap.end())
{
CCharInfo* lpCharInfo = pos->second;
CCharInfo* lpDelCharInfo = 0;
if (dwUID == lpCharInfo->GetUID() &&
dwCID == lpCharInfo->GetCID() &&
dwServerID == lpCharInfo->GetServerID())
{
// UID맵에서 삭제
if (0 != lpCharInfo->GetGID())
{
EraseByKey(m_gidMap, dwUID, dwCID,
dwServerID, lpCharInfo->GetGID());
}
// PID맵에서 삭제
if (0 != lpCharInfo->GetPID())
{
EraseByKey(m_pidMap, dwUID, dwCID,
dwServerID, lpCharInfo->GetPID());
}
// Race맵에서 삭제
EraseByKey(m_raceMap, dwUID, dwCID,
dwServerID, lpCharInfo->GetRace());
// 캐릭터 이름 맵에서 삭제
EraseByKey(m_nameMap, dwUID, dwCID, dwServerID,
nameHash(reinterpret_cast<const unsigned char*>(lpCharInfo->GetName()),
CHAR_INFOST::MAX_NAME_LEN));
// 계정 맵에서 삭제
EraseByKey(m_accountMap, dwUID, dwCID, dwServerID,
nameHash(reinterpret_cast<const unsigned char*>(lpCharInfo->GetAccountName()),
CHAR_INFOST::MAX_ACCOUNT_LEN));
// 채팅 금지 제거
const unsigned long dwRemainTime = GetCharChatBan(dwCID);
if (0 != dwRemainTime)
{
DBComponent::AdminDB::UpdateCharChatBanTime(
CDBSingleObject::GetInstance(), dwCID, dwRemainTime);
DeleteCharChatBan(dwCID);
}
m_cidMap.erase(pos);
DETLOG3(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/캐릭터 정보 제거 성공",
dwUID, dwCID, dwServerID);
return lpCharInfo;
}
else
{
ERRLOG6(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/"
"CurrentUID:%10u/CurrentCID:0x%08x/CurrentServerID:0x%08x/"
"제거 실패 : 현재 데이터와 새로 들어온 데이터의 값이 다릅니다.", dwUID, dwCID, dwServerID,
lpCharInfo->GetUID(), lpCharInfo->GetCID(), lpCharInfo->GetServerID());
}
}
else
{
ERRLOG3(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/"
"업데이트 실패 : 해당하는 데이터를 찾을 수 없습니다", dwUID, dwCID, dwServerID);
}
return 0;
}
void ChatData::CCharInfoMap::RemoveCharInfo(unsigned long dwServerID)
{
CharInfoMap::iterator pos = m_cidMap.begin();
CharInfoMap::iterator end = m_cidMap.end();
for(; pos != end;)
{
CCharInfo* lpCharInfo = pos->second;
if(lpCharInfo->GetServerID() == dwServerID)
{
unsigned long dwUID = lpCharInfo->GetUID();
unsigned long dwCID = lpCharInfo->GetCID();
if (0 != lpCharInfo->GetGID())
{
EraseByKey(m_gidMap, dwUID, dwCID,
dwServerID, lpCharInfo->GetGID());
}
if (0 != lpCharInfo->GetPID())
{
EraseByKey(m_pidMap, dwUID, dwCID,
dwServerID, lpCharInfo->GetPID());
}
// Race맵에서 삭제
EraseByKey(m_raceMap, dwUID, dwCID,
dwServerID, lpCharInfo->GetRace());
unsigned long dwNameHash = nameHash(
reinterpret_cast<const unsigned char*>(lpCharInfo->GetName()), CHAR_INFOST::MAX_NAME_LEN);
EraseByKey(m_nameMap, dwUID, dwCID, dwServerID, dwNameHash);
// 채팅 금지 제거
const unsigned long dwRemainTime = GetCharChatBan(dwCID);
if (0 != dwRemainTime)
{
DBComponent::AdminDB::UpdateCharChatBanTime(
CDBSingleObject::GetInstance(), dwCID, dwRemainTime);
DeleteCharChatBan(dwCID);
}
pos = m_cidMap.erase(pos);
delete lpCharInfo;
}
else
{
++pos;
}
}
}
bool ChatData::CCharInfoMap::AddCharChatBan(unsigned long dwTargetCID, unsigned long dwMinutes)
{
CharBanMap::iterator pos = m_banMap.find(dwTargetCID);
if (pos != m_banMap.end())
{
// 이미 존재하면 종료시킨 후 다시 추가시킨다. (로긴시에는 관계없다.)
m_banMap.erase(pos);
//DBComponent::AdminDB::EndCharChatBan(CDBSingleObject::GetInstance(), dwTargetCID);
}
m_banMap.insert(std::make_pair(dwTargetCID, dwMinutes));
return true;
}
bool ChatData::CCharInfoMap::GetCharChatBanInfo(unsigned long dwTargetCID, unsigned long& dwMinutes)
{
DBComponent::AdminDB::GetCharChatBan(CDBSingleObject::GetInstance(), dwTargetCID, dwMinutes);
return true;
}
bool ChatData::CCharInfoMap::DeleteCharChatBan(unsigned long dwCID)
{
CharBanMap::iterator pos = m_banMap.find(dwCID);
if (pos != m_banMap.end())
{
if(pos->second)
{
DBComponent::AdminDB::UpdateCharChatBanTime(CDBSingleObject::GetInstance(), pos->first, pos->second);
}
m_banMap.erase(pos);
return true;
}
return false;
}
unsigned long ChatData::CCharInfoMap::GetCharChatBan(unsigned long dwCID)
{
CharBanMap::iterator pos = m_banMap.find(dwCID);
if (pos != m_banMap.end())
{
return pos->second;
}
return 0;
}
void ChatData::CCharInfoMap::ProcessCharChatBan(CMultiDispatch& dispatch)
{
for (CharBanMap::iterator pos = m_banMap.begin(); pos != m_banMap.end(); )
{
if (0 < pos->second)
{
--(pos->second);
}
if (0 == pos->second)
{
DWORD dwCID = pos->first;
DWORD dwMinute = pos->second;
dispatch.Process(CSendCharChatBanAllGameServer(dwCID, dwMinute));
pos = m_banMap.erase(pos);
DBComponent::AdminDB::EndCharChatBan(CDBSingleObject::GetInstance(), dwCID);
}
else
{
// 10분 간격으로 DB에 남은 시간을 업데이트 해준다.
if (0 == pos->second % 10)
{
DBComponent::AdminDB::UpdateCharChatBanTime(
CDBSingleObject::GetInstance(), pos->first, pos->second);
}
++pos;
}
}
}
bool ChatData::CCharInfoMap::Update(unsigned long dwUID, unsigned long dwCID,
unsigned long dwServerID, const char* lpData, int& nDataLen)
{
CharInfoMap::iterator pos = m_cidMap.find(dwCID);
if (pos != m_cidMap.end())
{
CCharInfo* lpCharInfo = pos->second;
if (dwUID == lpCharInfo->GetUID() &&
dwCID == lpCharInfo->GetCID() &&
dwServerID == lpCharInfo->GetServerID())
{
unsigned long dwGID = lpCharInfo->GetGID();
unsigned long dwPID = lpCharInfo->GetPID();
char szName[CHAR_INFOST::MAX_NAME_LEN];
strncpy(szName, lpCharInfo->GetName(), CHAR_INFOST::MAX_NAME_LEN);
szName[CHAR_INFOST::MAX_NAME_LEN - 1] = 0;
if (lpCharInfo->UpdateData(lpData, nDataLen))
{
// 업데이트 성공.
// GID변경시 맵 다시 세팅
if (dwGID != lpCharInfo->GetGID())
{
if (0 != dwGID) { EraseByKey(m_gidMap, dwUID, dwCID, dwServerID, dwGID); }
if (0 != lpCharInfo->GetGID())
{
m_gidMap.insert(CharInfoMultiMap::value_type(
lpCharInfo->GetGID(), lpCharInfo));
}
}
// PID변경시 맵 다시 세팅
if (dwPID != lpCharInfo->GetPID())
{
if (0 != dwPID) { EraseByKey(m_pidMap, dwUID, dwCID, dwServerID, dwPID); }
if (0 != lpCharInfo->GetPID())
{
m_pidMap.insert(CharInfoMultiMap::value_type(
lpCharInfo->GetPID(), lpCharInfo));
}
}
// 이름 변경시 이름 맵 다시 세팅
if (0 != strncmp(szName, lpCharInfo->GetName(), CHAR_INFOST::MAX_NAME_LEN))
{
EraseByKey(m_nameMap, dwUID, dwCID, dwServerID, nameHash(
reinterpret_cast<const unsigned char*>(szName), CHAR_INFOST::MAX_NAME_LEN));
m_nameMap.insert(CharInfoMultiMap::value_type(
nameHash(reinterpret_cast<const unsigned char*>(
lpCharInfo->GetName()), CHAR_INFOST::MAX_NAME_LEN), lpCharInfo));
}
return true;
}
else
{
ERRLOG3(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/채팅 데이터 업데이트에 실패했습니다",
dwUID, dwCID, dwServerID);
}
}
else
{
ERRLOG6(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/"
"CurrentUID:%10u/CurrentCID:0x%08x/CurrentServerID:0x%08x/"
"업데이트 실패 : 현재 데이터와 새로 들어온 데이터의 값이 다릅니다.", dwUID, dwCID, dwServerID,
lpCharInfo->GetUID(), lpCharInfo->GetCID(), lpCharInfo->GetServerID());
}
}
else
{
ERRLOG3(g_Log, "UID:%10u/CID:0x%08x/ServerID:0x%08x/"
"업데이트 실패 : 해당하는 데이터를 찾을 수 없습니다", dwUID, dwCID, dwServerID);
}
return false;
}
bool CSendCharChatBanAllGameServer::operator () (unsigned long dwServerID, CPacketDispatch& dispatch)
{
CRylServerDispatch& rylServerDispatch = static_cast<CRylServerDispatch&>(dispatch);
// 게임서버에는 채금을 건 운영자 정보가 필요없다.
return GameClientSendPacket::SendCharChatBan(rylServerDispatch.GetSendStream(), 0, m_dwCID, m_dwMinutes);
}