mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
CaptureTool (built next to the servers) replays packet bundles and compares the answers: - replay: per bundle a fresh sandbox folder with copied server binaries, rewritten settings (replay_sandbox=1, a new SQLite file inside the folder, ports from --port on, no dashboard), read back before anything starts; sandbox-setup runs inside it to apply migrations and make the replay account and the bundle's characters (setup mode); master is started, the stack is stopped as one process group and the folder deleted unless kept - with replay_sandbox=1 every server refuses a database that isn't SQLite, isn't inside its own folder, or is replay_live_sqlite_path (Database::Connect); replay-target against a running server needs --i-know-this-is-not-a-sandbox - the fake client splits the recording into connections, logs in and picks the character itself when the recording doesn't, fills in the target's account, session key and IDs, learns server-made object IDs from replica constructions by LOT, follows the recorded timing and waits for the answers a client waits for; the diff pairs answers by name (and constructions by LOT) and ignores fields that differ between runs - import-live converts the 2014 live captures (folders of *_traffic.zip; pcaps and encrypted captures are left alone) into bundles, with secrets removed and CREATE_CHARACTER as setup - anonymise makes local fixtures; docs/CaptureReplay.md describes capture, the bundle format, portability rules, the sandbox and the replay Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
96 lines
3.4 KiB
C++
96 lines
3.4 KiB
C++
#include "FakeClient.h"
|
|
|
|
#include <cstring>
|
|
#include <thread>
|
|
|
|
#include "dNetCommon.h"
|
|
#include "MessageIdentifiers.h"
|
|
#include "RakNetworkFactory.h"
|
|
#include "RakPeerInterface.h"
|
|
#include "PacketPriority.h"
|
|
|
|
namespace {
|
|
int64_t NowUs() {
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
}
|
|
|
|
FakeClient::FakeClient() = default;
|
|
|
|
FakeClient::~FakeClient() { Disconnect(); }
|
|
|
|
bool FakeClient::Connect(const std::string& host, uint16_t port, std::chrono::milliseconds timeout) {
|
|
Disconnect();
|
|
m_Peer = RakNetworkFactory::GetRakPeerInterface();
|
|
SocketDescriptor socket(0, nullptr);
|
|
if (!m_Peer->Startup(1, 10, &socket, 1)) return false;
|
|
m_LocalPort = m_Peer->GetInternalID().port;
|
|
if (!m_Peer->Connect(host.c_str(), port, NET_PASSWORD_EXTERNAL, static_cast<int>(strnlen(NET_PASSWORD_EXTERNAL, sizeof(NET_PASSWORD_EXTERNAL))))) return false;
|
|
const auto until = std::chrono::steady_clock::now() + timeout;
|
|
while (std::chrono::steady_clock::now() < until) {
|
|
for (Packet* packet = m_Peer->Receive(); packet; packet = m_Peer->Receive()) {
|
|
const auto id = packet->length ? packet->data[0] : 0;
|
|
if (id == ID_CONNECTION_REQUEST_ACCEPTED) {
|
|
m_Server = packet->systemAddress;
|
|
m_Connected = true;
|
|
}
|
|
m_Peer->DeallocatePacket(packet);
|
|
if (id == ID_CONNECTION_ATTEMPT_FAILED || id == ID_NO_FREE_INCOMING_CONNECTIONS || id == ID_INVALID_PASSWORD) return false;
|
|
if (m_Connected) return true;
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FakeClient::Disconnect() {
|
|
if (!m_Peer) return;
|
|
if (m_Connected) m_Peer->CloseConnection(m_Server, true);
|
|
m_Peer->Shutdown(100);
|
|
RakNetworkFactory::DestroyRakPeerInterface(m_Peer);
|
|
m_Peer = nullptr;
|
|
m_Connected = false;
|
|
}
|
|
|
|
void FakeClient::Send(const std::string& bytes) {
|
|
if (!m_Connected || bytes.empty()) return;
|
|
m_Peer->Send(bytes.data(), static_cast<int>(bytes.size()), SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, m_Server, false);
|
|
}
|
|
|
|
bool FakeClient::Pump() {
|
|
if (!m_Peer) return false;
|
|
for (Packet* packet = m_Peer->Receive(); packet; packet = m_Peer->Receive()) {
|
|
if (packet->length) {
|
|
const auto id = packet->data[0];
|
|
if (id == ID_DISCONNECTION_NOTIFICATION || id == ID_CONNECTION_LOST) m_Connected = false;
|
|
// RakNet's own connection messages aren't the server's answers
|
|
if (id == ID_USER_PACKET_ENUM || (id >= ID_REPLICA_MANAGER_CONSTRUCTION && id <= ID_REPLICA_MANAGER_DOWNLOAD_COMPLETE)) {
|
|
m_Received.push_back({ std::string(reinterpret_cast<const char*>(packet->data), packet->length), NowUs() });
|
|
}
|
|
}
|
|
m_Peer->DeallocatePacket(packet);
|
|
}
|
|
return m_Connected;
|
|
}
|
|
|
|
int64_t FakeClient::WaitFor(const std::function<bool(const Received&)>& until, size_t from, std::chrono::milliseconds timeout) {
|
|
const auto end = std::chrono::steady_clock::now() + timeout;
|
|
size_t checked = from;
|
|
while (true) {
|
|
const bool open = Pump();
|
|
for (; checked < m_Received.size(); checked++) {
|
|
if (until(m_Received[checked])) return static_cast<int64_t>(checked);
|
|
}
|
|
if (!open || std::chrono::steady_clock::now() >= end) return -1;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
}
|
|
}
|
|
|
|
void FakeClient::Idle(std::chrono::milliseconds time) {
|
|
const auto end = std::chrono::steady_clock::now() + time;
|
|
while (std::chrono::steady_clock::now() < end) {
|
|
if (!Pump()) return;
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(2));
|
|
}
|
|
}
|