From 7f2c842f6d3177b4f82fe85d50a10df5cc6be547 Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Wed, 21 Jan 2026 08:34:42 +0000 Subject: [PATCH 1/2] QoL: Server-Side mark broadcast --- src/game/input_login.cpp | 27 ++++++++++++++++++++++++++- src/game/packet.h | 8 ++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/game/input_login.cpp b/src/game/input_login.cpp index fc2b3d3..ea9c46f 100644 --- a/src/game/input_login.cpp +++ b/src/game/input_login.cpp @@ -924,10 +924,35 @@ void CInputLogin::GuildMarkUpload(LPDESC d, const char* c_pData) if (*((DWORD *) p->image + iPixel) != 0x00000000) isEmpty = false; + DWORD markID = CGuildMarkManager::INVALID_MARK_ID; + if (isEmpty) rkMarkMgr.DeleteMark(p->gid); else - rkMarkMgr.SaveMark(p->gid, p->image); + markID = rkMarkMgr.SaveMark(p->gid, p->image); + + // Broadcast mark update to all connected game clients + if (markID != CGuildMarkManager::INVALID_MARK_ID) + { + WORD imgIdx = static_cast(markID / CGuildMarkImage::MARK_TOTAL_COUNT); + + TPacketGCMarkUpdate packet; + packet.header = HEADER_GC_MARK_UPDATE; + packet.guildID = p->gid; + packet.imgIdx = imgIdx; + + const DESC_MANAGER::DESC_SET & c_set_desc = DESC_MANAGER::instance().GetClientSet(); + for (DESC_MANAGER::DESC_SET::const_iterator it = c_set_desc.begin(); it != c_set_desc.end(); ++it) + { + LPDESC pkDesc = *it; + if (pkDesc && pkDesc->GetCharacter()) + { + pkDesc->Packet(&packet, sizeof(packet)); + } + } + + sys_log(0, "MARK_SERVER: GuildMarkUpload: Broadcast mark update for guild %u, imgIdx %u", p->gid, imgIdx); + } } void CInputLogin::GuildMarkIDXList(LPDESC d, const char* c_pData) diff --git a/src/game/packet.h b/src/game/packet.h index 989d5c3..78b3ef3 100644 --- a/src/game/packet.h +++ b/src/game/packet.h @@ -212,6 +212,7 @@ enum HEADER_GC_MARK_BLOCK = 100, HEADER_GC_MARK_IDXLIST = 102, + HEADER_GC_MARK_UPDATE = 103, HEADER_GC_TIME = 106, HEADER_GC_CHANGE_NAME = 107, @@ -1814,6 +1815,13 @@ typedef struct packet_mark_block // 뒤에 64 x 48 x 픽셀크기(4바이트) = 12288만큼 데이터 붙음 } TPacketGCMarkBlock; +typedef struct packet_mark_update +{ + uint8_t header; + uint32_t guildID; + uint16_t imgIdx; +} TPacketGCMarkUpdate; + typedef struct command_symbol_upload { uint8_t header; From 7c615a7a8465c7ce1780f5b7ef154346fe4beb91 Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Wed, 21 Jan 2026 18:31:58 +0000 Subject: [PATCH 2/2] QoL: Server-Side mark broadcast P2P --- src/game/MarkManager.cpp | 25 ++++++++++++++++++++ src/game/MarkManager.h | 1 + src/game/guild.cpp | 50 ++++++++++++++++++++++------------------ src/game/guild.h | 4 ++-- src/game/input.h | 2 ++ src/game/input_login.cpp | 26 +++++++++------------ src/game/input_p2p.cpp | 32 +++++++++++++++++++++++++ src/game/packet.h | 8 +++++++ src/game/packet_info.cpp | 1 + 9 files changed, 110 insertions(+), 39 deletions(-) diff --git a/src/game/MarkManager.cpp b/src/game/MarkManager.cpp index 7420f9d..79c5161 100644 --- a/src/game/MarkManager.cpp +++ b/src/game/MarkManager.cpp @@ -242,6 +242,31 @@ DWORD CGuildMarkManager::SaveMark(DWORD guildID, BYTE * pbMarkImage) return idMark; } +// SERVER - Allocate a mark slot with default (empty) image for a new guild +DWORD CGuildMarkManager::AllocMark(DWORD guildID) +{ + // Check if guild already has a mark + if (GetMarkID(guildID) != INVALID_MARK_ID) + { + sys_log(0, "AllocMark: guild %u already has a mark", guildID); + return GetMarkID(guildID); + } + + DWORD idMark = __AllocMarkID(guildID); + if (idMark == INVALID_MARK_ID) + { + sys_err("CGuildMarkManager::AllocMark: cannot alloc mark id for guild %u", guildID); + return INVALID_MARK_ID; + } + + sys_log(0, "AllocMark: allocated mark id %u for guild %u", idMark, guildID); + + // Save the index so the mark slot is persisted + SaveMarkIndex(); + + return idMark; +} + // SERVER void CGuildMarkManager::DeleteMark(DWORD guildID) { diff --git a/src/game/MarkManager.h b/src/game/MarkManager.h index 8a307ae..f8879e9 100644 --- a/src/game/MarkManager.h +++ b/src/game/MarkManager.h @@ -45,6 +45,7 @@ class CGuildMarkManager : public singleton // SERVER void CopyMarkIdx(char * pcBuf) const; DWORD SaveMark(DWORD guildID, BYTE * pbMarkImage); + DWORD AllocMark(DWORD guildID); // Allocate a mark slot with default (empty) image void DeleteMark(DWORD guildID); void GetDiffBlocks(DWORD imgIdx, const uint32_t* crcList, std::map & mapDiffBlocks); diff --git a/src/game/guild.cpp b/src/game/guild.cpp index 167de97..d2d0f33 100644 --- a/src/game/guild.cpp +++ b/src/game/guild.cpp @@ -16,6 +16,7 @@ #include "locale_service.h" #include "log.h" #include "questmanager.h" +#include "MarkManager.h" 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()) @@ -113,6 +114,9 @@ CGuild::CGuild(TGuildCreateParameter & cp) AddMember(&p); */ RequestAddMember(cp.master, GUILD_LEADER_GRADE); + + // Allocate a mark slot for the new guild + CGuildMarkManager::instance().AllocMark(GetID()); } void CGuild::Initialize() @@ -375,25 +379,23 @@ void CGuild::SendListOneToAll(LPCHARACTER ch) void CGuild::SendListOneToAll(DWORD pid) { - - TPacketGCGuild pack; - pack.header = HEADER_GC_GUILD; - pack.size = sizeof(TPacketGCGuild); - pack.subheader = GUILD_SUBHEADER_GC_LIST; - - pack.size += sizeof(TGuildMemberPacketData); - - char c[CHARACTER_NAME_MAX_LEN+1]; - memset(c, 0, sizeof(c)); - TGuildMemberContainer::iterator cit = m_member.find(pid); if (cit == m_member.end()) return; + TPacketGCGuild pack; + pack.header = HEADER_GC_GUILD; + pack.size = sizeof(TPacketGCGuild) + sizeof(TGuildMemberPacketData) + CHARACTER_NAME_MAX_LEN + 1; + pack.subheader = GUILD_SUBHEADER_GC_LIST; + + char szName[CHARACTER_NAME_MAX_LEN + 1]; + memset(szName, 0, sizeof(szName)); + strlcpy(szName, cit->second.name.c_str(), sizeof(szName)); + for (TGuildMemberOnlineContainer::iterator it = m_memberOnline.begin(); it!= m_memberOnline.end(); ++it) { LPDESC d = (*it)->GetDesc(); - if (!d) + if (!d) continue; TGuildMemberPacketData p; @@ -404,12 +406,11 @@ void CGuild::SendListOneToAll(DWORD pid) p.level = cit->second.level; p.offer = cit->second.offer_exp; p.name_flag = 1; - strlcpy(p.name, cit->second.name.c_str(), sizeof(p.name)); - TEMP_BUFFER buf; buf.write(&pack, sizeof(pack)); buf.write(&p, sizeof(p)); + buf.write(szName, CHARACTER_NAME_MAX_LEN + 1); d->Packet(buf.read_peek(), buf.size()); } } @@ -424,7 +425,7 @@ void CGuild::SendListPacket(LPCHARACTER ch) [ ... name_flag 1 - 이름을 보내느냐 안보내느냐 - name CHARACTER_NAME_MAX_LEN+1 + name CHARACTER_NAME_MAX_LEN+1 (only if name_flag is 1) ] * Count */ @@ -437,10 +438,11 @@ void CGuild::SendListPacket(LPCHARACTER ch) pack.size = sizeof(TPacketGCGuild); pack.subheader = GUILD_SUBHEADER_GC_LIST; - pack.size += sizeof(TGuildMemberPacketData) * m_member.size(); + // Each member: struct + name (always sent with name_flag=1) + pack.size += (sizeof(TGuildMemberPacketData) + CHARACTER_NAME_MAX_LEN + 1) * m_member.size(); TEMP_BUFFER buf; - buf.write(&pack,sizeof(pack)); + buf.write(&pack, sizeof(pack)); for (TGuildMemberContainer::iterator it = m_member.begin(); it != m_member.end(); ++it) { @@ -452,11 +454,16 @@ void CGuild::SendListPacket(LPCHARACTER ch) p.level = it->second.level; p.offer = it->second.offer_exp; p.name_flag = 1; - strlcpy(p.name, it->second.name.c_str(), sizeof(p.name)); buf.write(&p, sizeof(p)); - if ( test_server ) - sys_log(0 ,"name %s job %d ", it->second.name.c_str(), it->second.job ); + // Send name separately after the struct + char szName[CHARACTER_NAME_MAX_LEN + 1]; + memset(szName, 0, sizeof(szName)); + strlcpy(szName, it->second.name.c_str(), sizeof(szName)); + buf.write(szName, sizeof(szName)); + + if (test_server) + sys_log(0, "name %s job %d", it->second.name.c_str(), it->second.job); } d->Packet(buf.read_peek(), buf.size()); @@ -470,7 +477,6 @@ void CGuild::SendListPacket(LPCHARACTER ch) { SendLoginPacket(ch, *it); } - } void CGuild::SendLoginPacket(LPCHARACTER ch, LPCHARACTER chLogin) @@ -1407,7 +1413,7 @@ void CGuild::SendSkillInfoPacket(LPCHARACTER ch) const TPacketGCGuild pack; pack.header = HEADER_GC_GUILD; - pack.size = sizeof(pack) + 6 + GUILD_SKILL_COUNT; + pack.size = sizeof(pack) + 5 + GUILD_SKILL_COUNT; // 1 (skill_point) + GUILD_SKILL_COUNT (abySkill) + 2 (power) + 2 (max_power) pack.subheader = GUILD_SUBHEADER_GC_SKILL_INFO; d->BufferedPacket(&pack, sizeof(pack)); diff --git a/src/game/guild.h b/src/game/guild.h index 024cab7..46bc2f1 100644 --- a/src/game/guild.h +++ b/src/game/guild.h @@ -48,7 +48,7 @@ typedef struct SGuildMember #pragma pack(1) typedef struct SGuildMemberPacketData -{ +{ uint32_t pid; uint8_t grade; uint8_t is_general; @@ -56,7 +56,7 @@ typedef struct SGuildMemberPacketData uint8_t level; uint32_t offer; uint8_t name_flag; - char name[CHARACTER_NAME_MAX_LEN+1]; + // Note: name is sent separately after this struct if name_flag is set } TGuildMemberPacketData; typedef struct packet_guild_sub_info diff --git a/src/game/input.h b/src/game/input.h index f0ca4f7..62ff72a 100644 --- a/src/game/input.h +++ b/src/game/input.h @@ -17,6 +17,7 @@ enum }; void LoginFailure(LPDESC d, const char * c_pszStatus); +void BroadcastGuildMarkUpdate(DWORD dwGuildID, WORD wImgIdx); class CInputProcessor @@ -345,6 +346,7 @@ class CInputP2P : public CInputProcessor void IamAwake(LPDESC d, const char * c_pData); void MessengerRequestAdd(const char* c_pData); void MessengerResponse(const char* c_pData); + void GuildMarkUpdate(const char * c_pData); protected: CPacketInfoGG m_packetInfoGG; diff --git a/src/game/input_login.cpp b/src/game/input_login.cpp index ea9c46f..c24ee2b 100644 --- a/src/game/input_login.cpp +++ b/src/game/input_login.cpp @@ -27,6 +27,7 @@ #include "log.h" #include "horsename_manager.h" #include "MarkManager.h" +#include "p2p.h" static void _send_bonus_info(LPCHARACTER ch) { @@ -931,27 +932,22 @@ void CInputLogin::GuildMarkUpload(LPDESC d, const char* c_pData) else markID = rkMarkMgr.SaveMark(p->gid, p->image); - // Broadcast mark update to all connected game clients + // Broadcast mark update to all game cores via P2P, which will then broadcast to their clients if (markID != CGuildMarkManager::INVALID_MARK_ID) { WORD imgIdx = static_cast(markID / CGuildMarkImage::MARK_TOTAL_COUNT); - TPacketGCMarkUpdate packet; - packet.header = HEADER_GC_MARK_UPDATE; - packet.guildID = p->gid; - packet.imgIdx = imgIdx; + // Send P2P packet to all other game cores + TPacketGGMarkUpdate p2pPacket; + p2pPacket.bHeader = HEADER_GG_MARK_UPDATE; + p2pPacket.dwGuildID = p->gid; + p2pPacket.wImgIdx = imgIdx; + P2P_MANAGER::instance().Send(&p2pPacket, sizeof(p2pPacket)); - const DESC_MANAGER::DESC_SET & c_set_desc = DESC_MANAGER::instance().GetClientSet(); - for (DESC_MANAGER::DESC_SET::const_iterator it = c_set_desc.begin(); it != c_set_desc.end(); ++it) - { - LPDESC pkDesc = *it; - if (pkDesc && pkDesc->GetCharacter()) - { - pkDesc->Packet(&packet, sizeof(packet)); - } - } + // Also broadcast to clients connected to this core (mark server) using the same logic + BroadcastGuildMarkUpdate(p->gid, imgIdx); - sys_log(0, "MARK_SERVER: GuildMarkUpload: Broadcast mark update for guild %u, imgIdx %u", p->gid, imgIdx); + sys_log(0, "MARK_SERVER: GuildMarkUpload: Broadcast mark update for guild %u, imgIdx %u via P2P", p->gid, imgIdx); } } diff --git a/src/game/input_p2p.cpp b/src/game/input_p2p.cpp index 051a98d..8837a1f 100644 --- a/src/game/input_p2p.cpp +++ b/src/game/input_p2p.cpp @@ -457,6 +457,34 @@ void CInputP2P::IamAwake(LPDESC d, const char * c_pData) sys_log(0, "P2P Awakeness check from %s. My P2P connection number is %d. and details...\n%s", d->GetHostName(), P2P_MANAGER::instance().GetDescCount(), hostNames.c_str()); } +// Shared function to broadcast mark update to all clients on this core +void BroadcastGuildMarkUpdate(DWORD dwGuildID, WORD wImgIdx) +{ + TPacketGCMarkUpdate packet; + packet.header = HEADER_GC_MARK_UPDATE; + packet.guildID = dwGuildID; + packet.imgIdx = wImgIdx; + + const DESC_MANAGER::DESC_SET & c_set_desc = DESC_MANAGER::instance().GetClientSet(); + for (DESC_MANAGER::DESC_SET::const_iterator it = c_set_desc.begin(); it != c_set_desc.end(); ++it) + { + LPDESC pkDesc = *it; + if (pkDesc && pkDesc->GetCharacter()) + { + pkDesc->Packet(&packet, sizeof(packet)); + } + } + + sys_log(0, "BroadcastGuildMarkUpdate: guild %u, imgIdx %u, sent to %zu clients", + dwGuildID, wImgIdx, c_set_desc.size()); +} + +void CInputP2P::GuildMarkUpdate(const char * c_pData) +{ + TPacketGGMarkUpdate * p = (TPacketGGMarkUpdate *) c_pData; + BroadcastGuildMarkUpdate(p->dwGuildID, p->wImgIdx); +} + int CInputP2P::Analyze(LPDESC d, BYTE bHeader, const char * c_pData) { if (test_server) @@ -581,6 +609,10 @@ int CInputP2P::Analyze(LPDESC d, BYTE bHeader, const char * c_pData) case HEADER_GG_CHECK_AWAKENESS: IamAwake(d, c_pData); break; + + case HEADER_GG_MARK_UPDATE: + GuildMarkUpdate(c_pData); + break; } return (iExtraLen); diff --git a/src/game/packet.h b/src/game/packet.h index 78b3ef3..19719e1 100644 --- a/src/game/packet.h +++ b/src/game/packet.h @@ -315,6 +315,7 @@ enum HEADER_GG_MONARCH_TRANSFER = 27, HEADER_GG_CHECK_AWAKENESS = 29, + HEADER_GG_MARK_UPDATE = 30, }; #pragma pack(1) @@ -516,6 +517,13 @@ typedef struct SPacketGGBlockChat int32_t lBlockDuration; } TPacketGGBlockChat; +typedef struct packet_gg_mark_update +{ + uint8_t bHeader; + uint32_t dwGuildID; + uint16_t wImgIdx; +} TPacketGGMarkUpdate; + /* 클라이언트 측에서 보내는 패킷 */ typedef struct command_text diff --git a/src/game/packet_info.cpp b/src/game/packet_info.cpp index 1fcae85..afa1278 100644 --- a/src/game/packet_info.cpp +++ b/src/game/packet_info.cpp @@ -267,6 +267,7 @@ CPacketInfoGG::CPacketInfoGG() Set(HEADER_GG_MONARCH_NOTICE, sizeof(TPacketGGMonarchNotice), "MonarchNotice", false); Set(HEADER_GG_MONARCH_TRANSFER, sizeof(TPacketMonarchGGTransfer), "MonarchTransfer", false); Set(HEADER_GG_CHECK_AWAKENESS, sizeof(TPacketGGCheckAwakeness), "CheckAwakeness", false); + Set(HEADER_GG_MARK_UPDATE, sizeof(TPacketGGMarkUpdate), "MarkUpdate", false); } CPacketInfoGG::~CPacketInfoGG()