From 5a7ebf063a8a4aecded96e60f9ca2731c0daded8 Mon Sep 17 00:00:00 2001 From: ragnarok-clanker Date: Sat, 31 Oct 2026 11:51:01 +0000 Subject: [PATCH] Decompile 3 functions in Source/Client/Mode/GameModeNotices.cpp, Source/Client/Mode/GameModePackets.cpp, ... Batch #7800 by Yommy: 0 byte-identical, 3 confirmed by peer review. - fn_21606 CGameMode::ReceiveBankDepositAck: logic confirmed by peer review - fn_21626 CGameMode::ReceiveExchangeRequestAck: logic confirmed by peer review - fn_22143 CGameMode::ReceiveNotifyEffect: logic confirmed by peer review Peer-reviewed-by: eAthena, Snow, willerthal, SapitoSucio, ragnarok-clanker, ntrl, KWDev, Tanke Accepted without human review: peers confirmed the logic ("same" ahead by at least 3, with 70% of the votes). --- Source/Client/Mode/CGameMode.h | 27 ++ Source/Client/Mode/GameModeNotices.cpp | 419 +++++++++++++++++++++++++ Source/Client/Mode/GameModePackets.cpp | 4 + 3 files changed, 450 insertions(+) diff --git a/Source/Client/Mode/CGameMode.h b/Source/Client/Mode/CGameMode.h index b9230447..38a0fe92 100644 --- a/Source/Client/Mode/CGameMode.h +++ b/Source/Client/Mode/CGameMode.h @@ -30,6 +30,8 @@ struct CWorld; struct RenewQuestOngoingInfo; struct TextureRegion; struct vector3d; +struct ItemCardSlots; +struct ItemSlotOptions; struct MiniMapMember { /* 0000 */ int x; @@ -559,6 +561,31 @@ public: void ReceiveWindow348Result(unsigned char result, std::int16_t index); void ReceiveRodexItemReturn(const unsigned char* packet); void ReceiveExpGainNotice(const unsigned char* packet); + void ReceiveBankDepositAck(const unsigned char* packet); + void ReceiveBankWithdrawAck(const unsigned char* packet); + void ReceiveBankCheck(const unsigned char* packet); + void ReceiveExchangeItemAddAck(const unsigned char* packet); + void ReceiveExchangeRequestAck2(const unsigned char* packet); + void ReceiveExchangeRequestAck(const unsigned char* packet); + void ReceiveOpenStore(const unsigned char* packet); + void ReceiveSearchStoreOpen(const unsigned char* packet); + void ReceiveStorageEquipList(const unsigned char* packet); + void ReceiveStorageEquipList2(const unsigned char* packet); + void ReceiveStorageEquipList3(const unsigned char* packet); + void ReceiveStorageEquipList4(const unsigned char* packet); + void ReceiveStorageEquipList5(const unsigned char* packet); + void ReceiveStorageNormalList(const unsigned char* packet); + void ReceiveStorageNormalList2(const unsigned char* packet); + void ReceiveItemUpgrade(std::uint16_t index, std::uint16_t refine, ItemCardSlots cards, ItemSlotOptions options); + void ReceiveFeedConfig(unsigned char state, unsigned char option); + void ReceiveFeedConfig2(unsigned char state, unsigned char option, bool petAutoFeed); + void ReceiveFeedConfig3(unsigned char state, unsigned char option, bool petAutoFeed, bool homunculusAutoFeed); + void ReceiveFeedConfig4(unsigned char state, unsigned char option, bool petAutoFeed, bool homunculusAutoFeed, int value); + void ReceiveLongParChange(const unsigned char* packet); + void ReceiveNotifyEffect(const unsigned char* packet); + void ReceiveTypedItemList(const unsigned char* packet); + void ReceiveVendingItemList(int aid, int storeId, char own, int count, std::uint32_t duration, const unsigned char* data, int value, int unused); + void ReceiveVendingItemList2(int aid, int storeId, char own, int count, std::uint32_t duration, const unsigned char* data); }; void LaunchRollingCutterEffect(CGameActor* actor); diff --git a/Source/Client/Mode/GameModeNotices.cpp b/Source/Client/Mode/GameModeNotices.cpp index 8c3ed6c6..635f0c4e 100644 --- a/Source/Client/Mode/GameModeNotices.cpp +++ b/Source/Client/Mode/GameModeNotices.cpp @@ -1193,6 +1193,15 @@ struct ClientItemLists { void ClearItems1738(); void ClearItems1784(int index); void RemoveItem1710(std::uint32_t id, int quantity); + bool AddItem1738(const ITEM_INFO& item, unsigned char refresh); + bool AddExchangeItem(ITEM_INFO item); + void ClearItemSlotQuantities(); + void ClearItems1768(); + void ClearItems1758(); + void ClearItems1748(); + void ClearItems1760(); + void AddItem1758(const ITEM_INFO& item); + void AddItem1748(const ITEM_INFO& item); }; struct AchievementStatePacket { @@ -2649,3 +2658,413 @@ void CGameMode::ReceiveExpGainNotice(const unsigned char* data) { } g_windowMgr.SendNotice(1, reinterpret_cast(text.c_str()), color, 18, 0, 0); } + +#include "UI/Item/UIItemStoreWnd.h" +#include "UI/Shop/UIMerchantShopMakeWnd.h" +#include "UI/Text/UIStaticText.h" +#include "UI/UIBarGraph.h" +#include "UI/Window/UIBasicInfoWnd.h" +#include "Contents/Effects/EmitterEffect.h" + +#pragma pack(push, 1) +struct BankDepositAckPacket { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int16_t reason; + /* 0004 */ std::int64_t money; + /* 000C */ int zeny; +}; + +struct BankCheckPacket { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int64_t money; + /* 000A */ std::int16_t reason; +}; + +struct ExchangeItemAddAckPacket { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int16_t index; + /* 0004 */ unsigned char result; +}; + +struct ExchangeRequestAckPacket { + /* 0000 */ std::int16_t id; + /* 0002 */ unsigned char result; + /* 0003 */ std::uint32_t charId; + /* 0007 */ std::int16_t level; +}; + +struct SearchStoreOpenPacket { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int16_t openType; + /* 0004 */ unsigned char count; +}; + +struct StorageListHeader { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int16_t length; +}; + +struct NamedStorageListHeader { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int16_t length; + /* 0004 */ char title[24]; +}; + +struct StorageEquipRecord { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ unsigned char identified; + /* 0008 */ std::uint16_t location; + /* 000A */ std::uint16_t wearState; + /* 000C */ unsigned char damaged; + /* 000D */ unsigned char refine; + /* 000E */ std::uint32_t cards[4]; + /* 001E */ int expireTime; +}; + +struct StorageEquipRecord2 { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ unsigned char identified; + /* 0008 */ std::uint16_t location; + /* 000A */ std::uint16_t wearState; + /* 000C */ unsigned char damaged; + /* 000D */ unsigned char refine; + /* 000E */ std::uint32_t cards[4]; + /* 001E */ int expireTime; + /* 0022 */ std::uint16_t bindOnEquip; + /* 0024 */ std::uint16_t sprite; +}; + +struct StorageEquipRecord3 { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ std::uint16_t location; + /* 0009 */ std::uint16_t wearState; + /* 000B */ unsigned char refine; + /* 000C */ std::uint32_t cards[4]; + /* 001C */ int expireTime; + /* 0020 */ std::uint16_t bindOnEquip; + /* 0022 */ std::uint16_t sprite; + /* 0024 */ unsigned char flags; +}; + +struct StorageEquipRecord4 { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ int location; + /* 000B */ int wearState; + /* 000F */ unsigned char refine; + /* 0010 */ std::uint32_t cards[4]; + /* 0020 */ int expireTime; + /* 0024 */ std::uint16_t bindOnEquip; + /* 0026 */ std::uint16_t sprite; + /* 0028 */ unsigned char flags; +}; + +struct StorageEquipRecord5 { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ int location; + /* 000B */ int wearState; + /* 000F */ unsigned char refine; + /* 0010 */ std::uint32_t cards[4]; + /* 0020 */ int expireTime; + /* 0024 */ std::uint16_t bindOnEquip; + /* 0026 */ std::uint16_t sprite; + /* 0028 */ unsigned char optionCount; + /* 0029 */ ItemSlotOption options[5]; + /* 0042 */ unsigned char flags; +}; + +struct StorageNormalRecord { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ std::int16_t amount; + /* 0009 */ std::uint16_t wearState; + /* 000B */ std::uint32_t cards[4]; + /* 001B */ int expireTime; + /* 001F */ unsigned char flags; +}; + +struct StorageNormalRecord2 { + /* 0000 */ std::int16_t index; + /* 0002 */ int itemId; + /* 0006 */ unsigned char type; + /* 0007 */ std::int16_t amount; + /* 0009 */ int wearState; + /* 000D */ std::uint32_t cards[4]; + /* 001D */ int expireTime; + /* 0021 */ unsigned char flags; +}; + +struct ItemCardSlots { + /* 0000 */ std::uint32_t cards[4]; +}; + +struct ItemSlotOptions { + /* 0000 */ ItemSlotOption options[5]; +}; + +struct LongParChangePacket { + /* 0000 */ std::int16_t id; + /* 0002 */ std::uint16_t type; + /* 0004 */ std::int64_t value; +}; +#pragma pack(pop) + +struct SessionMessengerContext { + void ResetSlots(); +}; + +// OFFSET: 0x00D195D0 +// FUNCTION: fn_21606 +void CGameMode::ReceiveBankDepositAck(const unsigned char* data) { + const auto* packet = reinterpret_cast(data); + if (packet->reason == 0) { + g_session.m_bankBalance = packet->money; + g_session.m_zeny = packet->zeny; + g_session.RefreshHpSpWindows(); + g_session.InvalidateWindowFrames(); + return; + } + + char text[256]; + if (packet->reason == 2) { + std::memset(text, 0, 250); + std::sprintf(text, LookupMessage(2456)); + } else if (packet->reason == 3) { + std::memset(text, 0, 250); + std::sprintf(text, LookupMessage(2465)); + } else if (packet->reason == 4) { + std::memset(text, 0, 250); + std::sprintf(text, LookupMessage(2489)); + } else { + std::memset(text, 0, 250); + std::sprintf(text, LookupMessage(2454)); + } + g_windowMgr.SendNotice(1, reinterpret_cast(text), 0xFF, 0, 0, 0); +} + +// OFFSET: 0x00D196E0 + +// OFFSET: 0x00D197F0 + +// OFFSET: 0x00D1BE10 + +// OFFSET: 0x00D1C2B0 + +// OFFSET: 0x00D1C4E0 +// FUNCTION: fn_21626 +void CGameMode::ReceiveExchangeRequestAck(const unsigned char* data) { + const auto* packet = reinterpret_cast(data); + switch (packet->result) { + case 3: { + this->SendMsg(40, 0, 0, 0, 0); + *reinterpret_cast(&this->unknown_0100[0x14]) = 1; + reinterpret_cast(&g_session)->ResetSlots(); + reinterpret_cast(&g_session)->ClearItemSlotQuantities(); + g_windowMgr.CloseWindow(10011); + UIWindow* window = g_windowMgr.EnsureWindow(10011); + if (window) { + const std::string& exchangeName = *reinterpret_cast(&this->unknown_0130[0x18]); + window->SendMsg(nullptr, 50, reinterpret_cast(exchangeName.c_str()), 0, 0, 0); + } + break; + } + case 4: + g_windowMgr.SendNotice(3, 0, 0, 0, 0, 0); + g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(69)), 0xFF, 0, 0, 0); + break; + case 0: + g_windowMgr.SendNotice(3, 0, 0, 0, 0, 0); + g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(70)), 0xFF, 0, 0, 0); + break; + case 1: + g_windowMgr.SendNotice(3, 0, 0, 0, 0, 0); + g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(71)), 0xFF, 0, 0, 0); + break; + case 2: + g_windowMgr.SendNotice(3, 0, 0, 0, 0, 0); + g_windowMgr.SendNotice(1, reinterpret_cast(LookupMessage(72)), 0xFF, 0, 0, 0); + break; + } +} + +// OFFSET: 0x00D45E90 + +// OFFSET: 0x00D46000 + +// OFFSET: 0x00D46460 + +// OFFSET: 0x00D46680 + +// OFFSET: 0x00D46A20 + +// OFFSET: 0x00D46EE0 + +// OFFSET: 0x00D470E0 + +// OFFSET: 0x00D6DEF0 + +// OFFSET: 0x00D6E640 + +// OFFSET: 0x00D6E6F0 + +// OFFSET: 0x00D6E7A0 + +// OFFSET: 0x00D6E860 + +// OFFSET: 0x00D713E0 + +// OFFSET: 0x00D73AF0 +// FUNCTION: fn_22143 +void CGameMode::ReceiveNotifyEffect(const unsigned char* packet) { + CRenderObject* actor = this->scene->GetGameActorByAidIncludeMe(*reinterpret_cast(packet + 2)); + if (!actor) { + return; + } + + int effectId = *reinterpret_cast(packet + 6); + if (effectId == 0x3FD) { + CEmitterEffect* emitter = static_cast(g_effectMgr->Spawn( + 0, 0x3CE, actor->render.position.x, actor->render.position.y, actor->render.position.z, 0, -1, 0, 0 + )); + emitter->velocityMin.x = -2.0f; + emitter->velocityMin.y = 2.0f; + emitter->velocityMin.z = -2.0f; + emitter->velocityMax.x = 0.0f; + emitter->velocityMax.y = -1.0f; + emitter->velocityMax.z = 0.0f; + emitter->acceleration.x = 0.0f; + emitter->acceleration.y = -7.5f; + emitter->acceleration.z = 0.0f; + emitter->spread.x = -1.0f; + emitter->spread.y = 2.0f; + emitter->spread.z = -1.0f; + emitter->red = 255; + emitter->green = 255; + emitter->blue = 100; + emitter->alpha = 100; + emitter->rateMin = 20.0f; + emitter->rateMax = 100.0f; + emitter->scaleMin = 30.0f; + emitter->scaleMax = 30.0f; + emitter->lifeMin = 5.0f; + emitter->lifeMax = 10.0f; + emitter->sourceBlend = 5; + emitter->destinationBlend = 2; + emitter->SetParticleSpeed(3.0f); + emitter->SetCount(30); + emitter->SetTexture("smoke2.bmp"); + emitter->duration = 1250; + effectId = *reinterpret_cast(packet + 6); + } + + if (g_serviceOptions.Get(0xE5)) { + switch (*reinterpret_cast(packet + 6)) { + case 0x5A: + effectId = 0x48A; + break; + case 0x5C: + effectId = 0x48B; + break; + case 0x5F: + effectId = 0x489; + break; + } + } + + CGameActor* gameActor = static_cast(actor); + if (!g_effectMgr->Spawn( + actor->render.accountId, effectId, actor->render.position.x, actor->render.position.y, actor->render.position.z, 0, -1, 0, 0 + )) { + gameActor->LaunchEffect(*reinterpret_cast(packet + 6), vector3d{0.0f, 0.0f, 0.0f}, 0.0f); + } + + if (g_session.IsMercenaryJob(gameActor->GetEffectiveJob(-1)) && *reinterpret_cast(packet + 6) == 0x238) { + ++gameActor->unknown_0210; + gameActor->ResetJobEffects(); + gameActor->sub_CAA4D0(); + } +} + +#include "Client/Application/Launcher.h" + +#pragma pack(push, 1) +struct TypedItemEntry { + /* 0000 */ int itemId; + /* 0004 */ std::uint32_t unknown_0004; + /* 0008 */ std::uint16_t unknown_0008; + /* 000A */ std::uint32_t unknown_000A; +}; + +struct TypedItemListPacket { + /* 0000 */ std::int16_t id; + /* 0002 */ std::int16_t length; + /* 0004 */ std::int16_t count; + /* 0006 */ std::int16_t type; + /* 0008 */ TypedItemEntry entries[1]; +}; +#pragma pack(pop) + +// OFFSET: 0x00D1B690 + +#include "UI/Shop/UIMerchantItemMyShopWnd.h" +#include "UI/Shop/UIMerchantItemShopWnd.h" + +#pragma pack(push, 1) +struct VendingItemRecord { + /* 0000 */ int price; + /* 0004 */ std::int16_t amount; + /* 0006 */ unsigned char type; + /* 0007 */ int itemId; +}; +#pragma pack(pop) + +// OFFSET: 0x00D70010 + +#pragma pack(push, 1) +struct VendingItemRecord2 { + /* 0000 */ int price; + /* 0004 */ std::int16_t amount; + /* 0006 */ std::int16_t index; + /* 0008 */ unsigned char type; + /* 0009 */ int itemId; + /* 000D */ unsigned char identified; + /* 000E */ unsigned char damaged; + /* 000F */ unsigned char refine; + /* 0010 */ std::uint32_t cards[4]; + /* 0020 */ ItemSlotOption options[5]; + /* 0039 */ int location; + /* 003D */ std::uint16_t sprite; +}; +#pragma pack(pop) + +// INLINED +static void FillVendingItem(ITEM_INFO& item, const VendingItemRecord2& record) { + item.unknown_0000[1] = record.index; + item.unknown_0014[0] = record.price; + item.unknown_0014[1] = record.price; + item.unknown_0000[0] = record.type; + item.quantity = record.amount; + item.identified = record.identified; + item.unknown_005D = record.damaged; + item.unknown_0060 = record.refine; + item.SetItemId(record.itemId); + std::memcpy(item.unknown_001C, record.cards, sizeof(record.cards)); + item.unknown_0098 = CountItemSlotOptions(record.options); + for (int index = 0; index < item.unknown_0098; ++index) { + reinterpret_cast(item.unknown_009C)[index] = record.options[index]; + } + item.unknown_0000[2] = record.location; + item.unknown_0070 = record.sprite; +} + +// OFFSET: 0x00D70790 diff --git a/Source/Client/Mode/GameModePackets.cpp b/Source/Client/Mode/GameModePackets.cpp index c166f13b..b0c0b96d 100644 --- a/Source/Client/Mode/GameModePackets.cpp +++ b/Source/Client/Mode/GameModePackets.cpp @@ -4121,3 +4121,7 @@ void CGameMode::ReceiveGuildMemberMemoList(const unsigned char* data) { guildInfo->UpdateMemberExpRatios(); g_windowMgr.RefreshGuildWindows(); } + +// OFFSET: 0x00D3A630 + +// OFFSET: 0x00D3A770