mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-02 09:57:15 +00:00
fix(capture): replay keeps its records, skips RakNet's own messages and waits for transfers
- the expected answers pointed into a copy of the records that was gone by the diff - the client's recorded connect and disconnect messages are RakNet's, not sent again - a recorded move to another world is waited for as long as a zone takes to start - the handshake sends the client net version; reports are written after every bundle - a crash or Ctrl-C of the tool stops the sandbox stack too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -254,6 +254,9 @@ namespace {
|
|||||||
entry["sandbox"] = stack->Dir().string();
|
entry["sandbox"] = stack->Dir().string();
|
||||||
report.push_back(entry);
|
report.push_back(entry);
|
||||||
PrintResult(fs::path(path).filename().string(), result);
|
PrintResult(fs::path(path).filename().string(), result);
|
||||||
|
// Written after every bundle, so a long run's results survive it being stopped
|
||||||
|
if (!Arg(args, "--report").empty()) std::ofstream(Arg(args, "--report")) << report.dump(1);
|
||||||
|
std::cout.flush();
|
||||||
const bool failed = !result.stoppedAt.empty();
|
const bool failed = !result.stoppedAt.empty();
|
||||||
if (failed) failures++;
|
if (failed) failures++;
|
||||||
if (options.keep || (failed && Flag(args, "--keep-on-failure"))) {
|
if (options.keep || (failed && Flag(args, "--keep-on-failure"))) {
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ namespace {
|
|||||||
for (const auto& record : records) {
|
for (const auto& record : records) {
|
||||||
const auto source = static_cast<eCaptureSource>(record.header.source);
|
const auto source = static_cast<eCaptureSource>(record.header.source);
|
||||||
if ((source != eCaptureSource::AUTH && source != eCaptureSource::WORLD) || (record.header.flags & (PacketRecordFlags::MASTER_LINK | PacketRecordFlags::GAP))) continue;
|
if ((source != eCaptureSource::AUTH && source != eCaptureSource::WORLD) || (record.header.flags & (PacketRecordFlags::MASTER_LINK | PacketRecordFlags::GAP))) continue;
|
||||||
|
// RakNet's own connection messages (connected, disconnected) aren't sent: the fake client's RakNet makes its own
|
||||||
|
if (CaptureTools::FromClient(record.header) && (record.bytes.empty() || static_cast<uint8_t>(record.bytes[0]) != ID_USER_PACKET_ENUM)) continue;
|
||||||
const bool handshake = CaptureTools::FromClient(record.header) && NameOf(record) == "VERSION_CONFIRM";
|
const bool handshake = CaptureTools::FromClient(record.header) && NameOf(record) == "VERSION_CONFIRM";
|
||||||
if (segments.empty() || handshake || segments.back().source != source) segments.push_back({ source, {}, false });
|
if (segments.empty() || handshake || segments.back().source != source) segments.push_back({ source, {}, false });
|
||||||
segments.back().records.push_back(&record);
|
segments.back().records.push_back(&record);
|
||||||
@@ -79,9 +81,10 @@ namespace {
|
|||||||
Run(const CaptureBundle::Bundle& bundle, const Replayer::Options& options, Replayer::Result& result) : m_Bundle(bundle), m_Options(options), m_Result(result) {}
|
Run(const CaptureBundle::Bundle& bundle, const Replayer::Options& options, Replayer::Result& result) : m_Bundle(bundle), m_Options(options), m_Result(result) {}
|
||||||
|
|
||||||
void Go() {
|
void Go() {
|
||||||
auto records = m_Bundle.records;
|
// Kept for the whole run: the segments and the expected answers point into it
|
||||||
CaptureTools::SortTimeline(records);
|
m_Records = m_Bundle.records;
|
||||||
const auto segments = Split(records);
|
CaptureTools::SortTimeline(m_Records);
|
||||||
|
const auto segments = Split(m_Records);
|
||||||
m_Result.connections = segments.size();
|
m_Result.connections = segments.size();
|
||||||
if (segments.empty()) return Stop("The bundle has no client connections");
|
if (segments.empty()) return Stop("The bundle has no client connections");
|
||||||
|
|
||||||
@@ -112,6 +115,7 @@ namespace {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
const CaptureBundle::Bundle& m_Bundle;
|
const CaptureBundle::Bundle& m_Bundle;
|
||||||
|
std::vector<Record> m_Records;
|
||||||
const Replayer::Options& m_Options;
|
const Replayer::Options& m_Options;
|
||||||
Replayer::Result& m_Result;
|
Replayer::Result& m_Result;
|
||||||
std::vector<const Record*> m_Expected;
|
std::vector<const Record*> m_Expected;
|
||||||
@@ -132,6 +136,7 @@ namespace {
|
|||||||
|
|
||||||
void Handshake(FakeClient& client) {
|
void Handshake(FakeClient& client) {
|
||||||
CommonPackets::ClientVersionConfirm version;
|
CommonPackets::ClientVersionConfirm version;
|
||||||
|
version.netVersion = CommonPackets::ServerVersionConfirm::DEFAULT_NET_VERSION;
|
||||||
version.serviceType = ServiceType::CLIENT;
|
version.serviceType = ServiceType::CLIENT;
|
||||||
version.processID = 1;
|
version.processID = 1;
|
||||||
version.port = client.GetLocalPort();
|
version.port = client.GetLocalPort();
|
||||||
@@ -345,7 +350,9 @@ namespace {
|
|||||||
if (client.GetReceived().size() == count && rounds >= 3) break;
|
if (client.GetReceived().size() == count && rounds >= 3) break;
|
||||||
count = client.GetReceived().size();
|
count = client.GetReceived().size();
|
||||||
}
|
}
|
||||||
const auto at = client.WaitFor([](const auto& r) { return NameOf(r.bytes) == "TRANSFER_TO_WORLD"; }, 0, 1s);
|
// A recorded move to another world is waited for as long as a zone takes to start
|
||||||
|
const auto at = client.WaitFor([](const auto& r) { return NameOf(r.bytes) == "TRANSFER_TO_WORLD"; }, 0,
|
||||||
|
recordedCounts.contains("TRANSFER_TO_WORLD") ? std::chrono::seconds(180) : std::chrono::seconds(1));
|
||||||
if (at >= 0) {
|
if (at >= 0) {
|
||||||
ClientPackets::TransferToWorld transfer;
|
ClientPackets::TransferToWorld transfer;
|
||||||
if (Read(client.GetReceived()[at].bytes, transfer)) {
|
if (Read(client.GetReceived()[at].bytes, transfer)) {
|
||||||
|
|||||||
@@ -28,6 +28,15 @@ namespace fs = std::filesystem;
|
|||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
// The running stack's process group: stopped too if the tool is interrupted or crashes
|
||||||
|
volatile sig_atomic_t g_Running = 0;
|
||||||
|
|
||||||
|
void StopOnSignal(int signal) {
|
||||||
|
if (g_Running > 0) kill(-g_Running, SIGKILL);
|
||||||
|
std::signal(signal, SIG_DFL);
|
||||||
|
std::raise(signal);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings every sandbox gets over the server's own files. The first value of a key counts, so each key's line
|
* Settings every sandbox gets over the server's own files. The first value of a key counts, so each key's line
|
||||||
* is replaced where it is, and the ones the file doesn't have are added.
|
* is replaced where it is, and the ones the file doesn't have are added.
|
||||||
@@ -181,7 +190,9 @@ namespace Sandbox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Stack::Start(std::string& error) {
|
bool Stack::Start(std::string& error) {
|
||||||
|
for (const int signal : { SIGINT, SIGTERM, SIGSEGV, SIGABRT }) std::signal(signal, StopOnSignal);
|
||||||
m_Master = Launch(m_Dir, m_Dir / "MasterServer", m_Dir / "logs" / "master.out");
|
m_Master = Launch(m_Dir, m_Dir / "MasterServer", m_Dir / "logs" / "master.out");
|
||||||
|
g_Running = m_Master;
|
||||||
// Auth answering is the sign the stack is up (master starts it after chat and the character select world)
|
// Auth answering is the sign the stack is up (master starts it after chat and the character select world)
|
||||||
const auto until = std::chrono::steady_clock::now() + std::chrono::seconds(120);
|
const auto until = std::chrono::steady_clock::now() + std::chrono::seconds(120);
|
||||||
while (std::chrono::steady_clock::now() < until) {
|
while (std::chrono::steady_clock::now() < until) {
|
||||||
@@ -213,6 +224,7 @@ namespace Sandbox {
|
|||||||
kill(-m_Master, SIGKILL);
|
kill(-m_Master, SIGKILL);
|
||||||
waitpid(m_Master, &status, 0);
|
waitpid(m_Master, &status, 0);
|
||||||
m_Master = 0;
|
m_Master = 0;
|
||||||
|
g_Running = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int SetupCommand(const fs::path& bundlePath, const std::string& username, const std::string& password, bool characters, const fs::path& out) {
|
int SetupCommand(const fs::path& bundlePath, const std::string& username, const std::string& password, bool characters, const fs::path& out) {
|
||||||
|
|||||||
Reference in New Issue
Block a user