Files
ClassicAPI/src/cache/Query.cpp
T
Brues a53a08be95 fix(item): resend item queries the server dropped behind a loading screen
The server drops CMSG_ITEM_QUERY_SINGLE while the player is not in the
world, and the item cache sends its query only when it creates the
pending node, so a query sent before login finished or during a transfer
left that item nil for the rest of the session. The settle gate was a
one-way latch, so every loading screen after the first could lose
queries.

Queries now go out only in the world, after a settle delay per entry.
Queries in flight across a transition, or unanswered for 5 s, are sent
again with Cache::Query::Send, which builds the packet as the cache's
GetRecord does. Timeouts are wall-clock instead of frame counts.
2026-10-20 05:34:23 -05:00

60 lines
2.3 KiB
C++

// This file is part of ClassicAPI.
//
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
#include "cache/Query.h"
#include "Game.h"
#include "Offsets.h"
namespace Cache::Query {
namespace {
// The engine's stack CDataStore (see Offsets::VTABLE_CDATASTORE).
struct DataStore {
uintptr_t vtable;
void *buffer;
uint32_t base;
uint32_t alloc;
uint32_t size;
uint32_t read;
};
static_assert(sizeof(DataStore) == 0x18, "CDataStore layout");
using PutU32_t = void(__thiscall *)(DataStore *self, uint32_t value);
using PutGuid_t = void(__thiscall *)(DataStore *self, uint32_t lo, uint32_t hi);
using Send_t = void(__thiscall *)(DataStore *self);
using Free_t = void(__thiscall *)(DataStore *self, void **buffer, uint32_t *base, uint32_t *alloc);
} // namespace
// The send of FUN_DBCACHE_ITEMSTATS_GET_RECORD (0x0055BB18..0x0055BB89),
// step for step. Every DBCache<T> clone builds the same packet.
void Send(void *cache, uint32_t key) {
DataStore packet{Offsets::VTABLE_CDATASTORE, nullptr, 0, 0, 0, 0xFFFFFFFF};
const auto putU32 = reinterpret_cast<PutU32_t>(Offsets::FUN_DATASTORE_PUT_U32);
putU32(&packet, Game::Read<uint32_t>(cache, Offsets::OFF_DBCACHE_QUERY_OPCODE));
putU32(&packet, key);
if (Game::Read<uint8_t>(cache, Offsets::OFF_DBCACHE_OWNER_FLAG) != 0)
reinterpret_cast<PutGuid_t>(Offsets::FUN_DATASTORE_PUT_GUID)(&packet, 0, 0);
packet.read = 0;
reinterpret_cast<Send_t>(Offsets::FUN_CLIENT_SERVICES_SEND)(&packet);
if (packet.alloc != 0xFFFFFFFF) {
const auto release =
*reinterpret_cast<const Free_t *>(Offsets::VTABLE_CDATASTORE + sizeof(uintptr_t));
release(&packet, &packet.buffer, &packet.base, &packet.alloc);
}
}
} // namespace Cache::Query