Files
DarkflameServer/dCaptureTool/Sandbox.h
Aaron Kimbrell 3a8838d463 feat(capture): replay bundles against a sandbox stack with a headless client
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>
2026-09-29 22:34:34 -05:00

58 lines
2.3 KiB
C++

#pragma once
#include <cstdint>
#include <filesystem>
#include <memory>
#include <string>
#include <sys/types.h>
#include "json.hpp"
/**
* A replay sandbox (docs/CaptureReplay.md): a throwaway server stack for one replay, in a folder of its own. The
* server binaries are copied in (they read their settings and database from their own folder), the settings point
* at a fresh SQLite database inside that folder, ports that don't collide with a normal server, and
* replay_sandbox=1, with which every server refuses to start on any other database. The client files are shared
* read-only; CDServer.sqlite is copied. The folder is deleted afterwards unless it is kept.
*/
namespace Sandbox {
struct Options {
std::filesystem::path serverDir; // built servers (MasterServer, AuthServer, ChatServer, WorldServer, migrations, ...)
std::filesystem::path root; // sandboxes are made in here
std::filesystem::path clientLocation; // the game client's files (read only)
std::filesystem::path cdServer; // CDServer.sqlite to copy
std::filesystem::path liveDatabase; // the live SQLite database, which a sandbox must never be (optional)
uint16_t basePort{ 41000 };
bool keep{};
};
class Stack {
public:
static std::unique_ptr<Stack> Create(const Options& options, std::string& error);
~Stack();
// Makes the database (migrations), the replay's account and, from the bundle's setup section, its characters.
// `ids`: symbol -> {placeholder, id} of the characters made.
bool Setup(const std::filesystem::path& bundle, const std::string& username, const std::string& password, bool characters,
nlohmann::json& ids, std::string& error);
// Starts master (which starts auth, chat and the character select world) and waits until auth answers
bool Start(std::string& error);
void Stop();
void Keep() { m_Keep = true; }
uint16_t AuthPort() const { return m_Options.basePort + 10; }
const std::filesystem::path& Dir() const { return m_Dir; }
private:
Options m_Options;
std::filesystem::path m_Dir;
pid_t m_Master{};
bool m_Keep{};
};
// The sandbox-setup command, run by Stack::Setup inside the sandbox folder
int SetupCommand(const std::filesystem::path& bundle, const std::string& username, const std::string& password, bool characters,
const std::filesystem::path& out);
}