#include "Client/Mode/CGameMode.h" #include "Client/Session.h" #include "Contents/CashEmotionMgr.h" #include "Contents/ClanInfo.h" #include "Contents/Actors/Pc.h" #include "Contents/Actors/RenderObject.h" #include "Contents/Configuration/ServiceConfiguration.h" #include "Contents/Configuration/ServiceOptions.h" #include "Contents/Items/RuneSystem.h" #include "Contents/MessageTable.h" #include "Contents/Sound/SoundMgr.h" #include "Contents/World/World.h" #include "UI/Window/UIWindow.h" #include "UI/Window/UIWindowMgr.h" #include "Utility/Text/FormatString.h" #include #include #include #include #include #include #pragma pack(push, 1) struct ClanInfoPacket { /* 0000 */ std::int16_t id; /* 0002 */ std::int16_t length; /* 0004 */ int clanId; /* 0008 */ char clanName[24]; /* 0020 */ char masterName[24]; /* 0038 */ char mapName[16]; /* 0048 */ unsigned char allyCount; /* 0049 */ unsigned char hostileCount; /* 004A */ char relationNames[1][24]; }; struct ActorClanNamePacket { /* 0000 */ std::int16_t id; /* 0002 */ std::uint16_t length; /* 0004 */ std::uint32_t aid; /* 0008 */ char name[1]; }; struct ClanMemberCountPacket { /* 0000 */ std::int16_t id; /* 0002 */ std::uint16_t connectedMembers; /* 0004 */ std::uint16_t totalMembers; }; struct RuneSetGradeEntry { /* 0000 */ std::uint32_t id; /* 0004 */ std::uint16_t grade; /* 0006 */ std::uint16_t gradeLevel; }; struct RuneRewardStepEntry { /* 0000 */ std::uint32_t id; /* 0004 */ unsigned char step; }; struct RuneListPacket { /* 0000 */ std::int16_t id; /* 0002 */ std::uint16_t length; /* 0004 */ unsigned char unknown_0004; /* 0005 */ std::uint16_t tagId; /* 0007 */ std::uint16_t count; }; struct RuneActivationPacket : RuneListPacket { /* 0009 */ std::uint32_t ids[1]; }; struct RuneSetGradesPacket : RuneListPacket { /* 0009 */ RuneSetGradeEntry entries[1]; }; struct RuneRewardStepsPacket : RuneListPacket { /* 0009 */ RuneRewardStepEntry entries[1]; }; #pragma pack(pop) struct GameOptionEntry { /* 0000 */ int id; /* 0004 */ int value; }; // GLOBAL: gl_24865 bool g_gameOptionFlag; // OFFSET: 0x00D3CC10 // FUNCTION: fn_21796 void CGameMode::ReceivePetAct(unsigned char* packet) { CRenderObject* actor = this->scene->GetGameActorByAID(*reinterpret_cast(packet + 2)); if (!actor) { return; } int* data = reinterpret_cast(packet + 6); const int kind = *data % 10; const int value = *data / 10; if (kind == 2) { const std::int64_t emotion = value; const std::int64_t basePack = g_cashEmotionMgr->BasePackId(); actor->SendMsg( nullptr, 160, 0, static_cast(basePack), static_cast(basePack >> 32), static_cast(emotion), static_cast(emotion >> 32), 0, 0, 0, 0, 0, 0, 0, 0 ); return; } const int talkId = value / 10; *data = talkId; char talk[256]; std::memset(talk, 0, sizeof(talk)); g_session.GetPetTalkText(talkId / 10, value % 10, talkId % 10, talk); if (talk[0]) { char speech[256]; g_session.FormatMonsterSpeech(talk, speech, *reinterpret_cast(packet + 2), 1 - kind); if (std::strcmp(speech, "") != 0) { const std::int64_t text = reinterpret_cast(speech); actor->SendMsg(nullptr, 7, 0, static_cast(text), static_cast(text >> 32), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (!g_serviceOptions.Get(114) && !g_serviceOptions.Get(148)) { g_windowMgr.SendNotice(1, reinterpret_cast(speech), 0xFAFAFA, 0, 0, 0); } } } } // OFFSET: 0x00D3CEC0 // FUNCTION: fn_21798 void CGameMode::PlayBgm(unsigned char type, std::string name) { if (!g_serviceOptions.Get(7)) { return; } g_soundMgr->StopStreams(); switch (type) { case 0: g_soundMgr->PlayStream(FormatOwnedString("bgm\\%s.mp3", name.c_str()).c_str(), 0, false); g_soundMgr->secondaryEnabled = 0; break; case 1: g_soundMgr->PlayStream(FormatOwnedString("bgm\\%s.mp3", name.c_str()).c_str(), 1, false); g_soundMgr->secondaryEnabled = 1; break; case 2: g_soundMgr->SetDefaultName(g_session.GetBgmFileName(std::string(this->mapName).c_str())); g_soundMgr->secondaryEnabled = 0; break; } } // OFFSET: 0x00D3D0F0 // FUNCTION: fn_21799 void CGameMode::ReceiveNpcBgm(const unsigned char* packet) { if (g_serviceOptions.Get(7)) { const std::string path = std::string("bgm\\") + reinterpret_cast(packet + 2) + std::string(".mp3"); g_soundMgr->PlayStream(path.c_str(), 1, true); g_soundMgr->secondaryEnabled = 1; } } // OFFSET: 0x00D3D2E0 // FUNCTION: fn_21800 void CGameMode::IgnoreRemovedPacket1(const unsigned char*) { } // OFFSET: 0x00D3E0B0 // FUNCTION: fn_21808 void CGameMode::IgnoreRemovedPacket2(const unsigned char*) { } // OFFSET: 0x00D3E0C0 // FUNCTION: fn_21809 void CGameMode::ReceiveBillingInfo(const unsigned char* packet) { char text[512]; std::memset(text, 0, sizeof(text)); if (g_serviceType == 3) { switch (*reinterpret_cast(packet + 2)) { case 1: std::sprintf(text, LookupMessage(1124), *reinterpret_cast(packet + 6) / 86400 + 1); break; case 2: { const int seconds = *reinterpret_cast(packet + 10); std::sprintf(text, LookupMessage(1125), seconds / 3600, seconds % 3600 / 60, seconds % 60); break; } } } else { __time32_t now; _time32(&now); _localtime32(&now); const __time32_t* expireTime = reinterpret_cast(packet + 6); const tm* expire = _localtime32(expireTime); const int type = *reinterpret_cast(packet + 2); if (type >= 0x8000 && type <= 0x80FF) { if (type & 0x20) { std::strcat(text, "(PC방-실버) "); } if (type & 0x40) { std::strcat(text, "(PC방-골드) "); } if (type & 0x80) { std::strcat(text, "(PC방-스페셜) "); } if (type & 0x01) { std::strcat(text, "프리미엄 정액 요금제"); if (type & 0x10) { std::strcat(text, " + Advanced Package"); } else { if (type & 0x02) { std::strcat(text, " + Exp UP"); } if (type & 0x04) { std::strcat(text, " + Item UP"); } if (type & 0x08) { std::strcat(text, " + Death Down"); } char period[256]; std::memset(period, 0, sizeof(period)); const int remaining = *reinterpret_cast(packet + 10); if (*expireTime > now) { if (remaining > 0) { std::sprintf( period, " (종료일시 : %04d/%02d/%02d , 남은시간 : %d 시간 %d 분 %d 초)", expire->tm_year + 1900, expire->tm_mon + 1, expire->tm_mday, remaining / 3600, remaining / 60 % 60, remaining % 60 ); } else if (remaining == 0) { std::sprintf(period, " (종료일시 : %04d/%02d/%02d )", expire->tm_year + 1900, expire->tm_mon + 1, expire->tm_mday); } } else if (remaining > 0) { std::sprintf(period, " (남은시간 : %d 시간 %d 분 %d 초)", remaining / 3600, remaining / 60 % 60, remaining % 60); } std::strcat(text, period); } } } else { switch (type) { case 100: { const int remaining = *reinterpret_cast(packet + 10); if (*expireTime > now) { if (remaining > 0) { std::sprintf( text, "귀하는 혼합요금제 사용자 입니다. (종료일시 : %04d/%02d/%02d , 남은시간 : %d 시간 %d 분 %d 초)", expire->tm_year + 1900, expire->tm_mon + 1, expire->tm_mday, remaining / 3600, remaining / 60 % 60, remaining % 60 ); } else if (remaining == 0) { std::sprintf( text, "귀하는 정액요금제 사용자 입니다. (종료일시 : %04d 년 %02d 월 %02d 일 %02d 시 %02d 분 %02d 초)", expire->tm_year + 1900, expire->tm_mon + 1, expire->tm_mday, expire->tm_hour, expire->tm_min, expire->tm_sec ); } } else if (remaining > 0) { std::sprintf( text, "귀하는 정량요금제 사용자 입니다. (남은시간 : %d 시간 %d 분 %d 초)", remaining / 3600, remaining / 60 % 60, remaining % 60 ); } break; } case 1000: std::sprintf(text, "PC방의 요금제 정보는 요청할 수 없습니다."); break; case 10000: std::sprintf(text, "귀하는 사크라이 2시간 무료요금제 사용자 입니다."); break; default: std::sprintf(text, "과금정보 요청에 실패 했습니다."); break; } } } g_windowMgr.SendNotice(1, reinterpret_cast(text), 0xFFFFFF, 0, 0, 0); } // OFFSET: 0x00D65730 // FUNCTION: fn_22001 void CGameMode::ReceiveClanLeave(const unsigned char* packet) { g_clanInfo->Clear(); this->nameCard.SetValue3(g_session.m_aid, g_clanInfo->m_clanName); if (this->scene->player) { this->scene->player->SetUnknown0305(0, 0); } g_windowMgr.RefreshClanWnd(); } // OFFSET: 0x00D658C0 // FUNCTION: fn_22005 void CGameMode::ReceiveClanInfo(const unsigned char* data) { const auto* packet = reinterpret_cast(data); g_clanInfo->Clear(); g_clanInfo->m_hasClan = true; g_clanInfo->m_clanId = packet->clanId; char clanName[24] = {}; strcpy_s(clanName, sizeof(clanName), packet->clanName); g_clanInfo->m_clanName = clanName; this->nameCard.SetValue3(g_session.m_aid, g_clanInfo->m_clanName); char masterName[24] = {}; strcpy_s(masterName, sizeof(masterName), packet->masterName); g_clanInfo->m_masterName = masterName; char mapName[16] = {}; strcpy_s(mapName, sizeof(mapName), packet->mapName); g_clanInfo->m_mapName = mapName; const char* relationName = packet->relationNames[0]; for (int i = 0; i < packet->allyCount; ++i) { char name[24] = {}; StringFormat(name, sizeof(name), static_cast(-1), "%s", relationName); std::pair relation(0, name); g_clanInfo->m_relations.push_back(relation); relationName += sizeof(name); } for (int i = 0; i < packet->hostileCount; ++i) { char name[24] = {}; StringFormat(name, sizeof(name), static_cast(-1), "%s", relationName); std::pair relation(1, name); g_clanInfo->m_relations.push_back(relation); relationName += sizeof(name); } g_windowMgr.RefreshClanWnd(); } // OFFSET: 0x00D66200 // FUNCTION: fn_22009 void CGameMode::ReceiveGameOptions(int count, const GameOptionEntry* entries) { for (int i = 0; i < count; ++i) { if (entries[i].id == 1) { g_gameOptionFlag = entries[i].value != 0; } } } // OFFSET: 0x00D66370 // FUNCTION: fn_22011 void CGameMode::ReceiveActorClanName(const unsigned char* data) { const auto* packet = reinterpret_cast(data); this->nameCard.SetValue3(packet->aid, std::string(packet->name, packet->name + (packet->length - 8))); } // OFFSET: 0x00D66A00 // FUNCTION: fn_22017 void CGameMode::ReceiveClanMemberCount(const unsigned char* data) { const auto* packet = reinterpret_cast(data); g_clanInfo->m_connectedMembers = packet->connectedMembers; g_clanInfo->m_totalMembers = packet->totalMembers; } // OFFSET: 0x00D67040 // FUNCTION: fn_22023 void CGameMode::ReceiveRuneActivation(const unsigned char* data) { const auto* packet = reinterpret_cast(data); if (!packet) { return; } unsigned int count = packet->count; const unsigned int size = packet->length - 9; if (size != count * sizeof(std::uint32_t)) { count = size / sizeof(std::uint32_t); } for (unsigned int i = 0; i < count; ++i) { g_runeSystemMgr->SetRuneGrade(packet->tagId, 1, packet->ids[i], 0, 0); } CTagData* tag = g_runeSystemMgr->GetTag(packet->tagId); if (!tag) { return; } tag->UpdateActiveCount(1); UIWindow* window = g_windowMgr.LookupWindow(361); if (window) { window->SendMsg(window, 60, 0, 0, 0, 0); } } // OFFSET: 0x00D670F0 // FUNCTION: fn_22024 void CGameMode::ReceiveRuneSetGrades(const unsigned char* data) { const auto* packet = reinterpret_cast(data); if (!packet) { return; } unsigned int count = packet->count; const unsigned int size = packet->length - 9; if (size != count * sizeof(RuneSetGradeEntry)) { count = size / sizeof(RuneSetGradeEntry); } for (unsigned int i = 0; i < count; ++i) { const RuneSetGradeEntry& entry = packet->entries[i]; g_runeSystemMgr->SetRuneGrade(packet->tagId, 0, entry.id, entry.grade, entry.gradeLevel); } CTagData* tag = g_runeSystemMgr->GetTag(packet->tagId); if (!tag) { return; } tag->UpdateActiveCount(0); tag->m_locked = true; UIWindow* window = g_windowMgr.LookupWindow(361); if (window) { window->SendMsg(window, 60, 0, 0, 0, 0); } } // OFFSET: 0x00D671A0 // FUNCTION: fn_22025 void CGameMode::ReceiveRuneRewardResult(int result, int tagId, std::int64_t id, int step) { switch (result) { case 0: { g_runeSystemMgr->SetRewardStep(tagId, id, step); UIWindow* window = g_windowMgr.LookupWindow(361); if (window) { if (step > 0) { window->SendMsg(window, 178, 224, step - 1, 0, 0); } window->SendMsg(window, 60, 0, 0, 0, 0); } break; } case 1: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4202)), 0x64FFFF, 0, 0, 0); break; case 2: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4210)), 0x64FFFF, 0, 0, 0); break; case 3: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4211)), 0x64FFFF, 0, 0, 0); break; case 4: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4212)), 0x64FFFF, 0, 0, 0); break; case 5: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4213)), 0x64FFFF, 0, 0, 0); break; case 6: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4214)), 0x64FFFF, 0, 0, 0); break; case 7: g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(4215)), 0x64FFFF, 0, 0, 0); break; } } // OFFSET: 0x00D672E0 // FUNCTION: fn_22026 void CGameMode::ReceiveRuneRewardSteps(const unsigned char* data) { const auto* packet = reinterpret_cast(data); if (!packet) { return; } for (int i = 0; i < packet->count; ++i) { const RuneRewardStepEntry& entry = packet->entries[i]; g_runeSystemMgr->SetRewardStep(packet->tagId, entry.id, entry.step); } UIWindow* window = g_windowMgr.LookupWindow(361); if (window) { window->SendMsg(window, 60, 0, 0, 0, 0); } }