Files
DarkflameServer/dDatabase/GameDatabase/ITables/IMessageCaptures.h
Aaron Kimbrell 332bc04ce8 feat(capture): record whole packets of an account, a character or everything on every server
Staff arm a packet capture on the dashboard; master passes MESSAGE_CAPTURE_CONTROL ARM to
every world, auth and chat and arms its own. Each server's PacketCapture tap (dServer receive,
and a send hook in RakPeer::Send so replica constructions are seen too) records into one
preallocated chunk per server and ships sealed chunks through master on the main loop when
capture_flush_bytes or capture_flush_interval_ms is reached; past capture_buffer_max_mb the
oldest chunks are dropped and the dashboard records a gap. Nothing is armed: one flag check.

- targets: an account (from its login; packets before the login are kept per connection
  and added once auth or the world knows whose they are), a character (from when it is
  picked), or everything; up to 8 at once (a bit each in the record mask)
- worlds and auth record their clients' packets and the master link messages of a captured
  player (session keys by name, zone transfers by request, player added/removed, migration);
  chat finds the player in each packet; master records server traffic for everything
- secrets are never recorded: structs that carry them (login request, login response user
  key, world validation session key, session key messages between servers) are read,
  blanked and written again before recording; auth keeps only the handshake and login
- PacketDecoder: a registry by service and message id names every packet and decodes the
  registered structs; CaptureBundle is the file format (DLUBNDL1, metadata, records);
  CaptureTools orders records on one timeline, pulls movement out, makes bundles portable
  or anonymous and diffs replays
- the dashboard keeps packet captures in message_capture_sessions (capture_kind 1) and
  their packets in a file under capture_dir, one write per batch; arming is audited
- MESSAGE_CAPTURE_CONTROL/DATA only gain appended enum values and trailing fields

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 22:34:34 -05:00

105 lines
3.9 KiB
C++

#ifndef __IMESSAGECAPTURES__H__
#define __IMESSAGECAPTURES__H__
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "dCommonVars.h"
/**
* Saved captures of the dashboard's game message inspector (see dDashboardServer/routes/Inspector.h): each capture's
* details and the messages it caught, with their raw bytes, so staff can look at them again after it ended. The
* dashboard writes them while the capture runs and deletes old ones (the Message capture pruning task).
*/
class IMessageCaptures {
public:
struct MessageCaptureSession {
uint64_t id{};
LWOOBJID characterId{};
std::string characterName;
uint32_t accountId{};
std::string accountName;
uint32_t startedById{}; // the staff account
std::string startedBy;
int64_t startedAt{}; // Unix seconds
int64_t endsAt{}; // its time limit
int64_t endedAt{}; // 0 while it runs
std::string endReason;
bool toServer{ true };
bool toClient{ true };
std::string onlyMessages; // message IDs, comma separated
std::string skipMessages;
uint32_t zoneId{}; // where it was last captured
uint32_t instanceId{};
uint32_t cloneId{};
std::string zones; // every world it was captured in, in order: "zone:instance:clone" separated by spaces
uint64_t messageCount{};
uint64_t byteCount{}; // stored bytes (raw bytes plus decoded fields)
uint64_t dropped{}; // messages the worlds left out (too many at once)
uint8_t kind{}; // 0: game messages (message_capture_entries); 1: packets (a capture file, docs/CaptureReplay.md)
std::string target; // packets: "character", "account" or "everything"
};
// One captured message
struct MessageCaptureRecord {
uint64_t sessionId{};
uint32_t seq{}; // from 1 in its session
int64_t timeMs{}; // Unix milliseconds
uint8_t direction{}; // eMessageDirection
uint16_t messageId{}; // MessageType::Game
LWOOBJID objectId{};
uint32_t bits{}; // the message's full size
uint32_t droppedBefore{}; // messages left out just before this one
uint32_t zoneId{};
uint32_t instanceId{};
uint32_t cloneId{};
std::string payload; // raw bytes after the object and message ID (may be cut short; see bits)
std::string decoded; // fields as JSON, or empty
};
// How GetMessageCaptureSessions sorts
enum class eSessionOrder : uint8_t {
STARTED,
CHARACTER,
STARTED_BY,
MESSAGES,
BYTES,
};
struct SessionQuery {
LWOOBJID characterId{}; // 0: any
uint32_t accountId{}; // the captured player's account (0: any)
std::string startedBy; // staff account name, any case (empty: anyone)
int64_t since{}; // started at or after (0: any)
int64_t until{}; // started before (0: any)
bool unfinishedOnly{}; // still running (ended_at 0)
eSessionOrder order{ eSessionOrder::STARTED };
bool ascending{};
uint32_t limit{ 50 };
uint32_t offset{};
};
// Returns the new id
virtual uint64_t InsertMessageCaptureSession(const MessageCaptureSession& session) = 0;
// Everything that changes while it runs: ends_at, ended_at, end_reason, where, zones and the counts
virtual void UpdateMessageCaptureSession(const MessageCaptureSession& session) = 0;
// In one transaction
virtual void InsertMessageCaptureEntries(const std::vector<MessageCaptureRecord>& entries) = 0;
virtual std::optional<MessageCaptureSession> GetMessageCaptureSession(uint64_t id) = 0;
virtual std::vector<MessageCaptureSession> GetMessageCaptureSessions(const SessionQuery& query) = 0;
virtual uint64_t CountMessageCaptureSessions(const SessionQuery& query) = 0;
// A session's messages with seq above afterSeq, in order
virtual std::vector<MessageCaptureRecord> GetMessageCaptureEntries(uint64_t sessionId, uint32_t afterSeq, uint32_t limit) = 0;
// The session and its messages
virtual void DeleteMessageCaptureSession(uint64_t id) = 0;
};
#endif //!__IMESSAGECAPTURES__H__