From 5c4688e4e463ae147bb685ecbff715d0cc45367a Mon Sep 17 00:00:00 2001 From: TckTcm <258429854+TckTcm@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:34:36 +0200 Subject: [PATCH] feat: add native inventory upgrade support --- include/py_Inventory.h | 59 +++- include/py_party.h | 2 + src/py_Inventory.cpp | 7 +- src/py_party.cpp | 139 +++++++- vendor/gwca/Include/GWCA/Managers/ItemMgr.h | 16 + vendor/gwca/Source/ItemMgr.cpp | 360 +++++++++++++++++++- 6 files changed, 576 insertions(+), 7 deletions(-) diff --git a/include/py_Inventory.h b/include/py_Inventory.h index 634da40..8e82242 100644 --- a/include/py_Inventory.h +++ b/include/py_Inventory.h @@ -113,6 +113,62 @@ class Inventory { return result; } + int GetInventoryIDFromAgent(int agent_id) { + return static_cast(GW::Items::GetInventoryIDFromAgent(static_cast(agent_id))); + } + + bool IsInventoryIDValid(int inventory_id) { + return GW::Items::IsInventoryIDValid(static_cast(inventory_id)); + } + + int GetEquippedItemID(int inventory_id, int equip_slot) { + return static_cast(GW::Items::GetEquippedItemID( + static_cast(inventory_id), + static_cast(equip_slot))); + } + + int GetUpgradeSlot(int upgrade_item_id) { + GW::Item* item = GW::Items::GetItemById(static_cast(upgrade_item_id)); + return static_cast(GW::Items::GetUpgradeSlot(item)); + } + + bool ValidateUpgrade(int target_item_id, int upgrade_item_id) { + if (target_item_id == upgrade_item_id) + return false; + return GW::Items::ValidateUpgrade( + static_cast(target_item_id), + static_cast(upgrade_item_id)); + } + + bool ApplyUpgrade(int inventory_id, int target_item_id, int upgrade_item_id, int upgrade_slot = -1) { + const uint32_t target_inventory_id = static_cast(inventory_id); + const uint32_t target_item_id_u32 = static_cast(target_item_id); + const uint32_t upgrade_item_id_u32 = static_cast(upgrade_item_id); + const bool derive_upgrade_slot = upgrade_slot < 0; + uint32_t upgrade_slot_u32 = derive_upgrade_slot ? 0xffffffff : static_cast(upgrade_slot); + + if (!(target_inventory_id && target_item_id_u32 && upgrade_item_id_u32)) + return false; + if (target_item_id_u32 == upgrade_item_id_u32) + return false; + if (!GW::Items::IsInventoryIDValid(target_inventory_id)) + return false; + if (derive_upgrade_slot) { + const auto upgrade_item = GW::Items::GetItemById(upgrade_item_id_u32); + upgrade_slot_u32 = GW::Items::GetUpgradeSlot(upgrade_item); + if (!upgrade_slot_u32) + return false; + } + if (!GW::Items::ValidateUpgrade(target_item_id_u32, upgrade_item_id_u32)) + return false; + + return GW::Items::ApplyUpgrade( + target_inventory_id, + target_item_id_u32, + upgrade_item_id_u32, + upgrade_slot_u32); + } + int GetGoldAmount() { return GW::Items::GetGoldAmountOnCharacter(); @@ -139,6 +195,3 @@ class Inventory { }; - - - diff --git a/include/py_party.h b/include/py_party.h index 9d5d741..4055a8c 100644 --- a/include/py_party.h +++ b/include/py_party.h @@ -223,6 +223,8 @@ class PyParty { float GetAllFlagY(); int GetHeroAgentID(int hero_index); int GetAgentHeroID(int agent_id); + int GetInventorySelectedAgentID(); + int GetInventoryEquipmentFrameID(); int GetAgentIDByLoginNumber(int login_number); std::string GetPlayerNameByLoginNumber(int login_number); bool SearchParty(uint32_t search_type, std::string advertisement); diff --git a/src/py_Inventory.cpp b/src/py_Inventory.cpp index 6841bd5..92ac6c6 100644 --- a/src/py_Inventory.cpp +++ b/src/py_Inventory.cpp @@ -114,6 +114,12 @@ void bind_Inventory(py::module_& m) { .def("UseItem", &Inventory::UseItem, py::arg("item_id")) // Use an item .def("DestroyItem", &Inventory::DestroyItem, py::arg("item_id")) // Destroy an item .def("IdentifyItem", &Inventory::IdentifyItem, py::arg("id_kit_id"), py::arg("item_id")) // Identify an item + .def("GetInventoryIDFromAgent", &Inventory::GetInventoryIDFromAgent, py::arg("agent_id")) + .def("IsInventoryIDValid", &Inventory::IsInventoryIDValid, py::arg("inventory_id")) + .def("GetEquippedItemID", &Inventory::GetEquippedItemID, py::arg("inventory_id"), py::arg("equip_slot")) + .def("GetUpgradeSlot", &Inventory::GetUpgradeSlot, py::arg("upgrade_item_id")) + .def("ValidateUpgrade", &Inventory::ValidateUpgrade, py::arg("target_item_id"), py::arg("upgrade_item_id")) + .def("ApplyUpgrade", &Inventory::ApplyUpgrade, py::arg("inventory_id"), py::arg("target_item_id"), py::arg("upgrade_item_id"), py::arg("upgrade_slot") = -1) // Get item info .def("GetHoveredItemID", &Inventory::GetHoveredItemId) // Get hovered item ID @@ -139,4 +145,3 @@ PYBIND11_EMBEDDED_MODULE(PyInventory, m) { bind_SafeBag(m); bind_Inventory(m); } - diff --git a/src/py_party.cpp b/src/py_party.cpp index b53dbf5..a81b41e 100644 --- a/src/py_party.cpp +++ b/src/py_party.cpp @@ -3,6 +3,133 @@ namespace py = pybind11; +namespace { + // Gw_05072026.exe FUN_008ea160 handles 0x100001A8 as the equipment selected-agent getter. + // Older GWCA enums and Gw.wasm still label the same local query as 0x100001A7, so keep fallback probing. + constexpr uint32_t INVENTORY_GET_AGENT_FRAME_MESSAGE_CURRENT = 0x100001A8; + constexpr uint32_t INVENTORY_GET_AGENT_FRAME_MESSAGE_LEGACY = 0x100001A7; + constexpr uint32_t INVENTORY_AGENT_SENTINEL = 0xFFFFFFFF; + constexpr const wchar_t* INVENTORY_EQUIPMENT_FRAME_LABEL = L"Inventory-Equipment"; + + bool CanProbeInventoryEquipmentFrame() { + const auto instance_type = GW::Map::GetInstanceType(); + if (!GW::Map::GetIsMapLoaded() + || GW::Map::GetIsObserving() + || instance_type == GW::Constants::InstanceType::Loading) { + return false; + } + + const GW::Inventory* inventory = GW::Items::GetInventory(); + return inventory && inventory->equipped_items; + } + + bool TryReadInventorySelectedAgent( + GW::UI::Frame* frame, + uint32_t get_message_id, + uint32_t* selected_agent_id) { + if (!frame || !selected_agent_id) { + return false; + } + + uint32_t candidate_agent_id = INVENTORY_AGENT_SENTINEL; + GW::UI::SendFrameUIMessage( + frame, + static_cast(get_message_id), + nullptr, + &candidate_agent_id); + + if (candidate_agent_id == INVENTORY_AGENT_SENTINEL) { + return false; + } + + *selected_agent_id = candidate_agent_id; + return true; + } + + GW::UI::Frame* ProbeInventoryEquipmentFrame( + uint32_t* selected_agent_id = nullptr, + uint32_t* selected_frame_id = nullptr) { + if (!CanProbeInventoryEquipmentFrame()) { + return nullptr; + } + + const auto try_frame = [&]( + GW::UI::Frame* frame, + uint32_t get_message_id, + bool accept_zero_agent) -> bool { + if (!frame || !frame->IsCreated()) { + return false; + } + + uint32_t candidate_agent_id = INVENTORY_AGENT_SENTINEL; + if (!TryReadInventorySelectedAgent(frame, get_message_id, &candidate_agent_id)) { + return false; + } + if (!accept_zero_agent && candidate_agent_id == 0) { + return false; + } + + if (selected_agent_id) { + *selected_agent_id = candidate_agent_id; + } + if (selected_frame_id) { + *selected_frame_id = frame->frame_id; + } + return true; + }; + + GW::UI::Frame* labeled_frame = GW::UI::GetFrameByLabel(INVENTORY_EQUIPMENT_FRAME_LABEL); + if (try_frame(labeled_frame, INVENTORY_GET_AGENT_FRAME_MESSAGE_CURRENT, true) + || try_frame(labeled_frame, INVENTORY_GET_AGENT_FRAME_MESSAGE_LEGACY, true)) { + return labeled_frame; + } + + const std::vector frame_ids = GW::UI::GetFrameArray(); + const auto scan_message = [&](uint32_t get_message_id, bool accept_zero_agent) -> GW::UI::Frame* { + for (const uint32_t frame_id : frame_ids) { + GW::UI::Frame* frame = GW::UI::GetFrameById(frame_id); + if (try_frame(frame, get_message_id, accept_zero_agent)) { + if (selected_frame_id) { + *selected_frame_id = frame_id; + } + return frame; + } + } + return nullptr; + }; + + GW::UI::Frame* frame = scan_message(INVENTORY_GET_AGENT_FRAME_MESSAGE_CURRENT, true); + if (frame) { + return frame; + } + + // Legacy getter can return zero from unrelated root frames; require a real agent there. + return scan_message(INVENTORY_GET_AGENT_FRAME_MESSAGE_LEGACY, false); + } + + uint32_t ReadInventorySelectedAgent() { + if (!CanProbeInventoryEquipmentFrame()) { + return 0; + } + + uint32_t selected_agent_id = INVENTORY_AGENT_SENTINEL; + if (ProbeInventoryEquipmentFrame(&selected_agent_id) && selected_agent_id != INVENTORY_AGENT_SENTINEL) { + return selected_agent_id; + } + return 0; + } + + uint32_t ResolveInventoryEquipmentFrameID() { + if (!CanProbeInventoryEquipmentFrame()) { + return 0; + } + + uint32_t frame_id = 0; + ProbeInventoryEquipmentFrame(nullptr, &frame_id); + return frame_id; + } +} + const std::unordered_map> Hero::hero_data = { {0, {"None", static_cast(ProfessionType::None)}}, {1, {"Norgu", static_cast(ProfessionType::Mesmer)}}, @@ -370,6 +497,14 @@ int PyParty::GetHeroAgentID(int hero_index) { return GW::PartyMgr::GetHeroAgentID(hero_index); } +int PyParty::GetInventorySelectedAgentID() { + return static_cast(ReadInventorySelectedAgent()); +} + +int PyParty::GetInventoryEquipmentFrameID() { + return static_cast(ResolveInventoryEquipmentFrameID()); +} + int PyParty::GetAgentHeroID(int agent_id) { return GW::PartyMgr::GetAgentHeroID(agent_id); } @@ -586,6 +721,8 @@ void bind_PyParty(py::module_& m) { .def("GetAllFlagX", &PyParty::GetAllFlagX) // Bind GetAllFlagX method .def("GetAllFlagY", &PyParty::GetAllFlagY) // Bind GetAllFlagY method .def("GetHeroAgentID", &PyParty::GetHeroAgentID, py::arg("hero_index")) // Bind GetHeroAgentID method + .def("GetInventorySelectedAgentID", &PyParty::GetInventorySelectedAgentID) + .def("GetInventoryEquipmentFrameID", &PyParty::GetInventoryEquipmentFrameID) .def("GetAgentHeroID", &PyParty::GetAgentHeroID, py::arg("agent_id")) // Bind GetAgentHeroID method .def("GetAgentIDByLoginNumber", &PyParty::GetAgentIDByLoginNumber, py::arg("login_number")) .def("GetPlayerNameByLoginNumber", &PyParty::GetPlayerNameByLoginNumber, py::arg("login_number")) // Bind GetPlayerNameByLoginNumber method @@ -628,5 +765,3 @@ PYBIND11_EMBEDDED_MODULE(PyParty, m) { BindPetInfo(m); bind_PyParty(m); } - - diff --git a/vendor/gwca/Include/GWCA/Managers/ItemMgr.h b/vendor/gwca/Include/GWCA/Managers/ItemMgr.h index 3703243..9c873d1 100644 --- a/vendor/gwca/Include/GWCA/Managers/ItemMgr.h +++ b/vendor/gwca/Include/GWCA/Managers/ItemMgr.h @@ -90,6 +90,22 @@ namespace GW { // Identify an item GWCA_API bool IdentifyItem(uint32_t identification_kit_id, uint32_t item_id); + // Resolve the player or hero inventory id backing an agent's equipment panel. + GWCA_API uint32_t GetInventoryIDFromAgent(uint32_t agent_id); + + // Returns whether a native inventory id is present in the client's inventory table. + GWCA_API bool IsInventoryIDValid(uint32_t inventory_id); + + // Returns the item id equipped in a native equipment slot for an inventory id. + GWCA_API uint32_t GetEquippedItemID(uint32_t inventory_id, uint32_t equip_slot, bool* active = nullptr); + + // Returns the native upgrade slot encoded by an upgrade item's interaction flags. + GWCA_API uint32_t GetUpgradeSlot(const Item* upgrade_item); + + // Validate and apply a rune/upgrade component to a target item. + GWCA_API bool ValidateUpgrade(uint32_t target_item_id, uint32_t upgrade_item_id); + GWCA_API bool ApplyUpgrade(uint32_t inventory_id, uint32_t target_item_id, uint32_t upgrade_item_id, uint32_t upgrade_slot = 0xffffffff); + // Cancel the current salvage session GWCA_API bool SalvageSessionCancel(); diff --git a/vendor/gwca/Source/ItemMgr.cpp b/vendor/gwca/Source/ItemMgr.cpp index b970a7c..10f5e1f 100644 --- a/vendor/gwca/Source/ItemMgr.cpp +++ b/vendor/gwca/Source/ItemMgr.cpp @@ -76,6 +76,52 @@ namespace { typedef void(__cdecl* IdentifyItem_pt)(uint32_t identification_kit_id, uint32_t item_id); IdentifyItem_pt IdentifyItem_Func = 0; + typedef uint32_t(__cdecl* ValidateUpgrade_pt)(uint32_t target_item_id, uint32_t upgrade_item_id); + ValidateUpgrade_pt ValidateUpgrade_Func = 0; + + typedef void(__cdecl* OrderUpgradeBegin_pt)(uint32_t target_inventory_id, uint32_t target_item_id); + OrderUpgradeBegin_pt OrderUpgradeBegin_Func = 0; + + typedef void(__cdecl* OrderUpgradeAdd_pt)(uint32_t upgrade_slot, uint32_t upgrade_item_id); + OrderUpgradeAdd_pt OrderUpgradeAdd_Func = 0; + + typedef void(__cdecl* OrderUpgradeEnd_pt)(); + OrderUpgradeEnd_pt OrderUpgradeEnd_Func = 0; + + typedef uint32_t(__cdecl* GetInventoryIDFromAgent_pt)(uint32_t agent_id); + GetInventoryIDFromAgent_pt GetInventoryIDFromAgent_Func = 0; + + typedef uint32_t(__cdecl* GetEquippedItemID_pt)(uint32_t inventory_id, uint32_t equip_slot, uint32_t* active); + GetEquippedItemID_pt GetEquippedItemID_Func = 0; + + typedef void*(__thiscall* InventoryTableFind_pt)(void* inventory_table, uint32_t inventory_id); + InventoryTableFind_pt InventoryTableFind_Func = 0; + + constexpr uint32_t INVENTORY_SET_AGENT_FRAME_MESSAGE_ID = + static_cast(GW::UI::UIMessage::kNone) + 0x56; + constexpr GW::UI::UIMessage INVENTORY_SET_AGENT_FRAME_MESSAGE = + static_cast(INVENTORY_SET_AGENT_FRAME_MESSAGE_ID); + constexpr uint32_t INVENTORY_GET_AGENT_FRAME_MESSAGE = 0x100001A8; + constexpr uint32_t INVENTORY_AGENT_SENTINEL = 0xFFFFFFFF; + constexpr size_t INVENTORY_EQUIPMENT_SELECTED_AGENT_OFFSET = 0x08; + constexpr size_t INVENTORY_EQUIPMENT_SELECTED_INVENTORY_OFFSET = 0x0C; + constexpr const wchar_t* INVENTORY_EQUIPMENT_FRAME_LABEL = L"Inventory-Equipment"; + constexpr uint32_t UPGRADE_ADD_UI_MESSAGE_ID = 0x10000108; + constexpr uint32_t UPGRADE_REFRESH_UI_MESSAGE_ID = 0x10000109; + constexpr GW::UI::UIMessage UPGRADE_ADD_UI_MESSAGE = + static_cast(UPGRADE_ADD_UI_MESSAGE_ID); + constexpr GW::UI::UIMessage UPGRADE_REFRESH_UI_MESSAGE = + static_cast(UPGRADE_REFRESH_UI_MESSAGE_ID); + constexpr uint32_t UPGRADE_SLOT_DERIVE = 0xffffffff; + constexpr uint32_t UPGRADE_UI_MESSAGE_GUARD_MS = 3000; + constexpr uint32_t UPGRADE_UI_MESSAGE_GUARD_BLOCKS = 4; + + HookEntry InventoryEquipmentSetAgent_Entry; + HookEntry InventoryEquipmentRefresh_Entry; + HookEntry ItemUpdated_Entry; + HookEntry UpgradeUiMessageGuard_Entry; + uint32_t upgrade_ui_message_guard_until = 0; + uint32_t upgrade_ui_message_guard_remaining = 0; HookEntry OnUseItem_Entry; DoAction_pt UseItem_Func = 0; DoAction_pt UseItem_Ret = 0; @@ -94,6 +140,37 @@ namespace { return item && IsStorageBag(item->bag); } + void ClearUpgradeUiMessageGuard() { + upgrade_ui_message_guard_until = 0; + upgrade_ui_message_guard_remaining = 0; + } + + void ArmUpgradeUiMessageGuard() { + upgrade_ui_message_guard_until = GetTickCount() + UPGRADE_UI_MESSAGE_GUARD_MS; + upgrade_ui_message_guard_remaining = UPGRADE_UI_MESSAGE_GUARD_BLOCKS; + } + + void OnUpgradeUiMessage(GW::HookStatus* status, UI::UIMessage message_id, void*, void*) { + if (!(status && !status->blocked)) + return; + if (message_id != UPGRADE_ADD_UI_MESSAGE && message_id != UPGRADE_REFRESH_UI_MESSAGE) + return; + + const uint32_t guard_until = upgrade_ui_message_guard_until; + if (!(guard_until && upgrade_ui_message_guard_remaining)) + return; + if (static_cast(guard_until - GetTickCount()) < 0) { + ClearUpgradeUiMessageGuard(); + return; + } + + status->blocked = true; + --upgrade_ui_message_guard_remaining; + if (!upgrade_ui_message_guard_remaining) + upgrade_ui_message_guard_until = 0; + GWCA_INFO("[Item Module] Blocked stale upgrade UI message 0x%08x after native ApplyUpgrade", static_cast(message_id)); + } + void OnUseItem(uint32_t item_id) { GW::Hook::EnterHook(); @@ -137,6 +214,15 @@ namespace { uint32_t bag_index; // equal to bag_id() - 1 uint32_t slot; }; + + bool IsItemStillInSourceBag(const GW::Item* item) { + if (!(item && item->bag && item->bag->items.valid())) + return false; + if (!item->slot || item->slot > item->bag->items.size()) + return false; + return item->bag->items[item->slot - 1] == item; + } + void OnMoveItem(uint32_t item_id, uint32_t quantity, uint32_t bag_index, uint32_t slot) { GW::Hook::EnterHook(); MoveItem_UIMessage packet = { item_id, quantity, bag_index, slot }; @@ -158,6 +244,130 @@ namespace { } } + bool IsInventoryEquipmentFrame(const GW::UI::Frame* frame) { + if (!(frame && frame->IsCreated())) + return false; + + const auto labeled_frame = GW::UI::GetFrameByLabel(INVENTORY_EQUIPMENT_FRAME_LABEL); + if (labeled_frame == frame) + return true; + + uint32_t selected_agent_id = INVENTORY_AGENT_SENTINEL; + GW::UI::SendFrameUIMessage( + const_cast(frame), + static_cast(INVENTORY_GET_AGENT_FRAME_MESSAGE), + nullptr, + &selected_agent_id); + return selected_agent_id != INVENTORY_AGENT_SENTINEL; + } + + uint32_t ReadContextUInt32(const void* context, size_t offset) { + return *reinterpret_cast(reinterpret_cast(context) + offset); + } + + bool ReadInventoryEquipmentFrameSelection( + const GW::UI::Frame* frame, + uint32_t* selected_agent_id, + uint32_t* selected_inventory_id) { + const void* context = GW::UI::GetFrameContext(const_cast(frame)); + if (!context) + return false; + + if (selected_agent_id) { + *selected_agent_id = ReadContextUInt32( + context, + INVENTORY_EQUIPMENT_SELECTED_AGENT_OFFSET); + } + if (selected_inventory_id) { + *selected_inventory_id = ReadContextUInt32( + context, + INVENTORY_EQUIPMENT_SELECTED_INVENTORY_OFFSET); + } + return true; + } + + bool IsInventoryEquipmentInventoryValid(uint32_t agent_id, uint32_t inventory_id) { + if (!inventory_id) + return false; + + const bool stale_hero_inventory_id = + inventory_id == agent_id && agent_id != GW::Agents::GetControlledCharacterId(); + return !stale_hero_inventory_id && Items::IsInventoryIDValid(inventory_id); + } + + bool IsInventoryEquipmentSelectionStale() { + const auto frame = GW::UI::GetFrameByLabel(INVENTORY_EQUIPMENT_FRAME_LABEL); + if (!(frame && frame->IsCreated())) + return false; + + uint32_t selected_agent_id = 0; + uint32_t selected_inventory_id = 0; + if (!ReadInventoryEquipmentFrameSelection(frame, &selected_agent_id, &selected_inventory_id)) + return false; + return selected_inventory_id && !IsInventoryEquipmentInventoryValid(selected_agent_id, selected_inventory_id); + } + + void OnItemUpdated_UIMessage(GW::HookStatus* status, UI::UIMessage message_id, void* wparam, void*) { + if (!(status && !status->blocked && message_id == UI::UIMessage::kItemUpdated && wparam)) + return; + + const auto packet = static_cast(wparam); + if (IsInventoryEquipmentSelectionStale()) { + status->blocked = true; + GWCA_INFO( + "[Item Module] Blocked stale ItemUpdated item=%u", + packet->item_id); + } + } + + void OnInventoryEquipmentSetAgent_UIMessage( + GW::HookStatus* status, + const GW::UI::Frame* frame, + GW::UI::UIMessage message_id, + void* wparam, + void*) { + if (!(status && !status->blocked && message_id == INVENTORY_SET_AGENT_FRAME_MESSAGE)) + return; + if (!IsInventoryEquipmentFrame(frame)) + return; + + const uint32_t agent_id = static_cast(reinterpret_cast(wparam)); + const uint32_t inventory_id = Items::GetInventoryIDFromAgent(agent_id); + if (IsInventoryEquipmentInventoryValid(agent_id, inventory_id)) + return; + + status->blocked = true; + GWCA_INFO( + "[Item Module] Blocked Inventory-Equipment 0x56 for agent=%u inventory=%u", + agent_id, + inventory_id); + } + + void OnInventoryEquipmentRefresh_UIMessage( + GW::HookStatus* status, + const GW::UI::Frame* frame, + GW::UI::UIMessage message_id, + void*, + void*) { + if (!(status && !status->blocked && message_id == UI::UIMessage::kItemUpdated)) + return; + if (!IsInventoryEquipmentFrame(frame)) + return; + + uint32_t selected_agent_id = 0; + uint32_t selected_inventory_id = 0; + if (!ReadInventoryEquipmentFrameSelection(frame, &selected_agent_id, &selected_inventory_id)) + return; + if (!selected_inventory_id || !IsInventoryEquipmentSelectionStale()) + return; + + status->blocked = true; + GWCA_INFO( + "[Item Module] Blocked stale Inventory-Equipment refresh for agent=%u inventory=%u", + selected_agent_id, + selected_inventory_id); + } + typedef void(__cdecl* EquipItem_pt)(uint32_t item_id, uint32_t agent_id); EquipItem_pt EquipItem_Func = 0; @@ -282,6 +492,39 @@ namespace { IdentifyItem_Func = (IdentifyItem_pt)Scanner::ToFunctionStart(Scanner::FindAssertion("ItCliApi.cpp", "context->itemTable.Get(srcItemId)", 0, 0)); Logger::AssertAddress("IdentifyItem_Func", (uintptr_t)IdentifyItem_Func, "Item Module"); + ValidateUpgrade_Func = (ValidateUpgrade_pt)Scanner::ToFunctionStart(Scanner::FindAssertion("ItCliApi.cpp", "baseItem", 0, 0), 0x420); + OrderUpgradeAdd_Func = (OrderUpgradeAdd_pt)Scanner::ToFunctionStart(Scanner::FindAssertion("ItCliApi.cpp", "upgradeItemId", 0x67b, 0), 0x260); + OrderUpgradeBegin_Func = (OrderUpgradeBegin_pt)Scanner::ToFunctionStart(Scanner::FindAssertion("ItCliApi.cpp", "targetInventoryId", 0x685, 0), 0x260); + GetEquippedItemID_Func = (GetEquippedItemID_pt)Scanner::ToFunctionStart(Scanner::FindAssertion("ItCliApi.cpp", "slot < ITEM_EQUIP_SLOTS", 0x1e5, 0), 0x260); + GetInventoryIDFromAgent_Func = (GetInventoryIDFromAgent_pt)Scanner::ToFunctionStart(Scanner::FindAssertion("\\Code\\Gw\\Ui\\Game\\GmItemHelpers.cpp", "agentId", 0x69, 0), 0x260); + if (GetEquippedItemID_Func) { + const auto start = reinterpret_cast(GetEquippedItemID_Func); + const auto call_site = Scanner::FindInRange( + "\x81\xc1\xd4\x00\x00\x00\xe8", + "xxxxxxx", + 6, + start, + start + 0x80); + if (call_site) + InventoryTableFind_Func = reinterpret_cast(Scanner::FunctionFromNearCall(call_site)); + } + if (OrderUpgradeBegin_Func) { + const auto candidate = reinterpret_cast(OrderUpgradeBegin_Func) + 0xb0; + if (Scanner::IsValidPtr(candidate, ScannerSection::Section_TEXT)) { + const auto first_byte = *reinterpret_cast(candidate); + if (first_byte == 0xe9 || first_byte == 0x55) { + OrderUpgradeEnd_Func = reinterpret_cast(candidate); + } + } + } + Logger::AssertAddress("ValidateUpgrade_Func", (uintptr_t)ValidateUpgrade_Func, "Item Module"); + Logger::AssertAddress("OrderUpgradeBegin_Func", (uintptr_t)OrderUpgradeBegin_Func, "Item Module"); + Logger::AssertAddress("OrderUpgradeAdd_Func", (uintptr_t)OrderUpgradeAdd_Func, "Item Module"); + Logger::AssertAddress("OrderUpgradeEnd_Func", (uintptr_t)OrderUpgradeEnd_Func, "Item Module"); + Logger::AssertAddress("GetInventoryIDFromAgent_Func", (uintptr_t)GetInventoryIDFromAgent_Func, "Item Module"); + Logger::AssertAddress("GetEquippedItemID_Func", (uintptr_t)GetEquippedItemID_Func, "Item Module"); + Logger::AssertAddress("InventoryTableFind_Func", (uintptr_t)InventoryTableFind_Func, "Item Module"); + address = Scanner::Find("\x83\xc4\x40\x6a\x00\x6a\x19", "xxxxxxx", -0x4e); DropItem_Func = (DropItem_pt)Scanner::FunctionFromNearCall(address); @@ -398,6 +641,31 @@ namespace { Logger::AssertHook("UseItem_Func", HookBase::CreateHook((void**)&UseItem_Func, OnUseItem, (void**)&UseItem_Ret), "Item Module"); UI::RegisterUIMessageCallback(&OnUseItem_Entry, UI::UIMessage::kSendUseItem, OnUseItem_UIMessage, 0x1); } + UI::RegisterFrameUIMessageCallback( + &InventoryEquipmentSetAgent_Entry, + INVENTORY_SET_AGENT_FRAME_MESSAGE, + OnInventoryEquipmentSetAgent_UIMessage, + -0x8000); + UI::RegisterFrameUIMessageCallback( + &InventoryEquipmentRefresh_Entry, + UI::UIMessage::kItemUpdated, + OnInventoryEquipmentRefresh_UIMessage, + -0x8000); + UI::RegisterUIMessageCallback( + &ItemUpdated_Entry, + UI::UIMessage::kItemUpdated, + OnItemUpdated_UIMessage, + -0x8000); + UI::RegisterUIMessageCallback( + &UpgradeUiMessageGuard_Entry, + UPGRADE_ADD_UI_MESSAGE, + OnUpgradeUiMessage, + -0x8000); + UI::RegisterUIMessageCallback( + &UpgradeUiMessageGuard_Entry, + UPGRADE_REFRESH_UI_MESSAGE, + OnUpgradeUiMessage, + -0x8000); if (ChangeGold_Func) { Logger::AssertHook("ChangeGold_Func", HookBase::CreateHook((void**)&ChangeGold_Func, OnChangeGold, (void**)&ChangeGold_Ret), "Item Module"); } @@ -441,6 +709,10 @@ namespace { HookBase::RemoveHook(MoveItem_Func); HookBase::RemoveHook(UseItem_Func); HookBase::RemoveHook(ChangeGold_Func); + UI::RemoveFrameUIMessageCallback(&InventoryEquipmentSetAgent_Entry); + UI::RemoveFrameUIMessageCallback(&InventoryEquipmentRefresh_Entry); + UI::RemoveUIMessageCallback(&ItemUpdated_Entry, UI::UIMessage::kItemUpdated); + UI::RemoveUIMessageCallback(&UpgradeUiMessageGuard_Entry); } } @@ -494,7 +766,7 @@ namespace GW { StoC::EmulatePacket(&pack); } bool CanInteractWithItem(const GW::Item* item) { - return item && !IsStorageItem(item) || CanAccessXunlaiChest(); + return item && (!IsStorageItem(item) || CanAccessXunlaiChest()); } bool PickUpItem(const Item* item, uint32_t call_target /*= 0*/) { auto packet = UI::UIPacket::kInteractAgent{ item->agent_id, call_target == 1 }; @@ -672,6 +944,90 @@ namespace GW { return IdentifyItem_Func ? IdentifyItem_Func(identification_kit_id, item_id), true : false; } + uint32_t GetInventoryIDFromAgent(uint32_t agent_id) { + if (!(GetInventoryIDFromAgent_Func && agent_id)) + return 0; + return GetInventoryIDFromAgent_Func(agent_id); + } + + bool IsInventoryIDValid(uint32_t inventory_id) { + if (!(InventoryTableFind_Func && inventory_id)) + return false; + const auto item_context = GetItemContext(); + if (!item_context) + return false; + void* inventory_table = reinterpret_cast(reinterpret_cast(item_context) + 0xd4); + return InventoryTableFind_Func(inventory_table, inventory_id) != nullptr; + } + + uint32_t GetEquippedItemID(uint32_t inventory_id, uint32_t equip_slot, bool* active) { + if (!(GetEquippedItemID_Func && inventory_id && equip_slot <= 8 && IsInventoryIDValid(inventory_id))) + return 0; + uint32_t active_value = 0; + const uint32_t item_id = GetEquippedItemID_Func(inventory_id, equip_slot, &active_value); + if (active) + *active = active_value != 0; + return item_id; + } + + uint32_t GetUpgradeSlot(const Item* upgrade_item) { + if (!(upgrade_item && upgrade_item->type == Constants::ItemType::Rune_Mod)) + return 0; + + const uint32_t interaction = upgrade_item->interaction; + const bool weapon_upgrade = (interaction & 0x04000000) != 0; + const bool armor_insignia = (interaction & 0x00010000) != 0; + const bool armor_rune = (interaction & 0x00100000) != 0; + const uint32_t category_count = + (weapon_upgrade ? 1u : 0u) + + (armor_insignia ? 1u : 0u) + + (armor_rune ? 1u : 0u); + if (category_count != 1) + return 0; + if (weapon_upgrade) + return 6; + if (armor_insignia) + return 7; + if (armor_rune) + return 1; + return 0; + } + + bool ValidateUpgrade(uint32_t target_item_id, uint32_t upgrade_item_id) { + const auto target_item = GetItemById(target_item_id); + const auto upgrade_item = GetItemById(upgrade_item_id); + if (!(ValidateUpgrade_Func && CanInteractWithItem(target_item) && CanInteractWithItem(upgrade_item))) + return false; + return ValidateUpgrade_Func(target_item_id, upgrade_item_id) != 0; + } + + bool ApplyUpgrade(uint32_t inventory_id, uint32_t target_item_id, uint32_t upgrade_item_id, uint32_t upgrade_slot) { + if (target_item_id == upgrade_item_id) + return false; + + const auto target_item = GetItemById(target_item_id); + const auto upgrade_item = GetItemById(upgrade_item_id); + if (!(OrderUpgradeBegin_Func && OrderUpgradeAdd_Func && OrderUpgradeEnd_Func)) + return false; + if (!(inventory_id && IsInventoryIDValid(inventory_id) && CanInteractWithItem(target_item) && CanInteractWithItem(upgrade_item))) + return false; + + const bool derive_upgrade_slot = upgrade_slot == UPGRADE_SLOT_DERIVE; + if (derive_upgrade_slot) { + upgrade_slot = GetUpgradeSlot(upgrade_item); + if (!upgrade_slot) + return false; + } + if (!ValidateUpgrade(target_item_id, upgrade_item_id)) + return false; + + ArmUpgradeUiMessageGuard(); + OrderUpgradeBegin_Func(inventory_id, target_item_id); + OrderUpgradeAdd_Func(upgrade_slot, upgrade_item_id); + OrderUpgradeEnd_Func(); + return true; + } + uint32_t GetGoldAmountOnCharacter() { auto* i = GetInventory(); return i ? i->gold_character : 0; @@ -729,6 +1085,8 @@ namespace GW { bool MoveItem(const Item* from, const Bag* bag, uint32_t slot, uint32_t quantity) { if (!(MoveItem_Func && from && bag)) return false; + if (!(CanInteractWithItem(from) && IsItemStillInSourceBag(from))) return false; + if (!bag->items.valid()) return false; if (bag->items.size() < (unsigned)slot) return false; if (quantity <= 0) quantity = from->quantity; if (quantity > from->quantity) quantity = from->quantity;