diff --git a/src/citymania/cm_command_log.cpp b/src/citymania/cm_command_log.cpp new file mode 100644 index 0000000000..f4c0e79a25 --- /dev/null +++ b/src/citymania/cm_command_log.cpp @@ -0,0 +1,213 @@ +#include "../stdafx.h" + +#include "cm_command_log.hpp" + +#include "cm_bitstream.hpp" + +#include "../command_type.h" +#include "../network/network_internal.h" +#include "../network/network_server.h" +#include "../console_func.h" +#include "../rev.h" +#include "../strings_func.h" + +#include + +#include +#include + +namespace citymania { + + +struct FakeCommand { + Date date; + DateFract date_fract; + uint res; + uint8 seed; + uint16 client_id; + CommandPacket cp; +}; + +static std::queue _fake_commands; +bool _replay_started = false; + +bool DatePredate(Date date1, DateFract date_fract1, Date date2, DateFract date_fract2) { + return date1 < date2 || (date1 == date2 && date_fract1 < date_fract2); +} + +void SkipFakeCommands(Date date, DateFract date_fract) { + uint commands_skipped = 0; + + while (!_fake_commands.empty() && DatePredate(_fake_commands.front().date, _fake_commands.front().date_fract, date, date_fract)) { + _fake_commands.pop(); + commands_skipped++; + } + + if (commands_skipped) { + fprintf(stderr, "Skipped %u commands that predate the current date (%d/%hu)\n", commands_skipped, date, date_fract); + } +} + +extern CommandCost ExecuteCommand(CommandPacket *cp); + +void ExecuteFakeCommands(Date date, DateFract date_fract) { + if (!_replay_started) { + SkipFakeCommands(_date, _date_fract); + _replay_started = true; + } + + auto backup_company = _current_company; + while (!_fake_commands.empty() && !DatePredate(date, date_fract, _fake_commands.front().date, _fake_commands.front().date_fract)) { + auto &x = _fake_commands.front(); + + fprintf(stderr, "Executing command: %s(%u) company=%u tile=%u ... ", GetCommandName(x.cp.cmd), x.cp.cmd, x.cp.company, (uint)x.cp.tile); + if (x.res == 0) { + fprintf(stderr, "REJECTED\n"); + _fake_commands.pop(); + continue; + } + + if (_networking) { + x.cp.frame = _frame_counter_max + 1; + x.cp.callback = nullptr; + x.cp.my_cmd = false; + + for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) { + if (cs->status >= NetworkClientSocket::STATUS_MAP) { + cs->outgoing_queue.Append(&x.cp); + } + } + } + + _current_company = (CompanyID)x.cp.company; + auto res = ExecuteCommand(&x.cp); + if (res.Failed() != (x.res != 1)) { + if (!res.Failed()) { + fprintf(stderr, "FAIL (Failing command succeeded)\n"); + } else if (res.GetErrorMessage() != INVALID_STRING_ID) { + char buf[DRAW_STRING_BUFFER]; + GetString(buf, res.GetErrorMessage(), lastof(buf)); + fprintf(stderr, "FAIL (Successful command failed: %s)\n", buf); + } else { + fprintf(stderr, "FAIL (Successful command failed)\n"); + } + } else { + fprintf(stderr, "OK\n"); + } + if (x.seed != (_random.state[0] & 255)) { + fprintf(stderr, "*** DESYNC expected seed %u vs current %u ***\n", x.seed, _random.state[0] & 255); + } + _fake_commands.pop(); + } + _current_company = backup_company; +} + + +void load_replay_commands(const std::string &filename, std::function error_func) { + _fake_commands = {}; + + FILE *f = std::fopen(filename.c_str(), "rb"); + + if (f == nullptr) { + error_func(fmt::format("Cannot open file `{}`: {}", filename, std::strerror(errno))); + return; + } + + lzma_stream lzma = LZMA_STREAM_INIT; + lzma_ret ret = lzma_auto_decoder(&lzma, 1 << 28, 0); + if (ret != LZMA_OK) { + error_func(fmt::format("Cannot initialize LZMA decompressor (code {})", ret)); + return; + } + + static const size_t CHUNK_SIZE = 128 * 1024; + static byte inbuf[CHUNK_SIZE]; + + lzma.next_in = NULL; + lzma.avail_in = 0; + + lzma_action action = LZMA_RUN; + + u8vector data(CHUNK_SIZE); + size_t bytes_read = 0; + lzma.next_out = &data[0]; + lzma.avail_out = data.size(); + do { + fprintf(stderr, "LZMA in %d\n", (int)lzma.avail_in); + if (lzma.avail_in == 0 && !std::feof(f)) { + lzma.next_in = inbuf; + lzma.avail_in = std::fread((char *)inbuf, 1, sizeof(inbuf), f); + + if (std::ferror(f)) { + error_func(fmt::format("Error reading {}: {}", filename, std::strerror(errno))); + return; + } + + if (std::feof(f)) action = LZMA_FINISH; + } + + ret = lzma_code(&lzma, action); + + if (lzma.avail_out == 0 || ret == LZMA_STREAM_END) { + bytes_read += CHUNK_SIZE - lzma.avail_out; + data.resize(bytes_read + CHUNK_SIZE); + lzma.next_out = &data[bytes_read]; + lzma.avail_out = CHUNK_SIZE; + } + } while (ret == LZMA_OK); + + std::fclose(f); + + data.resize(bytes_read); + + if (ret != LZMA_STREAM_END) { + error_func(fmt::format("LZMA decompressor returned error code {}", ret)); + // return; + } + + auto bs = BitIStream(data); + auto version = bs.ReadBytes(2); + if (version != 1) { + error_func(fmt::format("Unsupported log file version {}", version)); + return; + } + + // auto openttd_version = bs.ReadBytes(4); + auto openttd_version = _openttd_newgrf_version; + + if (_openttd_newgrf_version != openttd_version) { + error_func(fmt::format("OpenTTD version doesn't match: current {}, log file {}", + _openttd_newgrf_version, openttd_version)); + return; + } + try { + while (!bs.eof()) { + FakeCommand fk; + auto date_full = bs.ReadBytes(4); + fk.date = date_full / DAY_TICKS; + fk.date_fract = date_full % DAY_TICKS; + fk.res = bs.ReadBytes(1); + fk.seed = bs.ReadBytes(1); + fk.cp.company = (Owner)bs.ReadBytes(1); + fk.client_id = bs.ReadBytes(2); + fk.cp.cmd = (Commands)bs.ReadBytes(2); + fk.cp.tile = bs.ReadBytes(4); + fk.cp.data = bs.ReadData(); + fk.cp.callback = nullptr; + _fake_commands.push(fk); + error_func(fmt::format("Command {}({}) company={} client={} tile={}", GetCommandName(fk.cp.cmd), fk.cp.cmd, fk.cp.company, fk.client_id, fk.cp.tile)); + } + } + catch (BitIStreamUnexpectedEnd &) { + error_func("Unexpected end of command data"); + } + + _replay_started = false; +} + +bool IsReplayingCommands() { + return !_fake_commands.empty(); +} + + +}; // namespace citymania diff --git a/src/citymania/cm_command_log.hpp b/src/citymania/cm_command_log.hpp new file mode 100644 index 0000000000..a4700b7c83 --- /dev/null +++ b/src/citymania/cm_command_log.hpp @@ -0,0 +1,12 @@ +#ifndef CM_COMMAND_LOG_HPP +#define CM_COMMAND_LOG_HPP + +#include + +namespace citymania { + +void load_replay_commands(const std::string &filename, std::function); + +}; // namespace citymania + +#endif