#include "ReplayReader.h" #include #include #include #include "Command.h" #include "CommandSerializer.h" namespace { void stripCarriageReturn(std::string& line) { if (!line.empty() && line.back() == '\r') { line.pop_back(); } } // Splits "key value with spaces" into (key, remainder). Remainder may be empty. void splitKeyValue(const std::string& line, std::string& key, std::string& value) { const std::string::size_type space = line.find(' '); if (space == std::string::npos) { key = line; value.clear(); } else { key = line.substr(0, space); value = line.substr(space + 1); } } } // namespace std::optional readReplayFile(const std::string& path) { std::ifstream stream(path, std::ios::in); if (!stream.is_open()) { return std::nullopt; } ParsedReplay replay; std::string line; // --- Header (up to the "---" separator) --- bool sawSeparator = false; while (std::getline(stream, line)) { stripCarriageReturn(line); if (line == "---") { sawSeparator = true; break; } if (line.empty() || line[0] == '#') { continue; // banner / blank } std::string key; std::string value; splitKeyValue(line, key, value); try { if (key == "version") { replay.header.version = std::stoi(value); } else if (key == "build") { replay.header.build = value; } else if (key == "seed") { replay.header.seed = static_cast(std::stoul(value)); } else if (key == "config_hash") { replay.header.configHash = value; } else if (key == "timestamp") { replay.header.timestamp = value; } } catch (const std::exception&) { return std::nullopt; } } if (!sawSeparator) { return std::nullopt; } // --- Command / checksum stream --- while (std::getline(stream, line)) { stripCarriageReturn(line); if (line.empty()) { continue; } if (line[0] == '#') { // "# checksum " std::istringstream in(line); std::string hash; std::string keyword; ReplayEntry entry; in >> hash >> keyword >> entry.tick >> hash; if (keyword != "checksum") { continue; // unknown comment line — ignore } try { entry.fingerprint = std::stoull(hash, nullptr, 16); } catch (const std::exception&) { return std::nullopt; } entry.isCommand = false; replay.entries.push_back(std::move(entry)); continue; } // " " const std::string::size_type space = line.find(' '); if (space == std::string::npos) { return std::nullopt; } ReplayEntry entry; try { entry.tick = std::stoll(line.substr(0, space)); } catch (const std::exception&) { return std::nullopt; } std::shared_ptr command = parseCommand(line.substr(space + 1)); if (!command) { return std::nullopt; } entry.isCommand = true; entry.command = std::move(command); replay.entries.push_back(std::move(entry)); } return replay; }