diff --git a/README.md b/README.md index bc64537b8e..cef821adc2 100644 --- a/README.md +++ b/README.md @@ -246,6 +246,8 @@ See [jgrpp-changelog.md](jgrpp-changelog.md) for changelog. * Add news/advice setting to warn if no depot order in vehicle schedule. (added in v0.31.1). * Enable vehicle list buttons in station window when the list would be non-empty. (added in v0.31.1). * Enable vehicle group management actions on other companies' stations. (added in v0.31.1). + * Add a password mechanism to change network game settings from a network client. (added in v0.31.4). + * Change network protocol to send server/join and rcon passwords in hashed form instead of in clear text. (added in v0.31.4). * Various minor fixes, see changelog. * [NewGRF specification additions](docs/newgrf-additions.html) ([online copy](https://htmlpreview.github.io/?https://github.com/JGRennison/OpenTTD-patches/blob/jgrpp/docs/newgrf-additions.html)). * [Low-level code/performance changes](docs/jgrpp-low-level-changes.md). diff --git a/jgrpp-changelog.md b/jgrpp-changelog.md index c3d94b3a9b..ace070b74d 100644 --- a/jgrpp-changelog.md +++ b/jgrpp-changelog.md @@ -6,6 +6,15 @@ * Include NotRoadTypes (NRT). * Bump trunk base from commit 21edf67f89c60351d5a0d84625455aa296b6b950 to commit a52bbb72a8a2cbcbefb0ff91b559f33c34094239. +### v0.31.4 (2019-08-24) +* Fix crash when removing signals from tunnel/bridge with trainless reservation. +* Fix various cases where reversing a train inside a signalled tunnel/bridge handled PBS reservations incorrectly. +* Fix error windows being closed when returning to the main menu. +* Add a password mechanism to change network game settings from a network client. +* Change station tile coverage highlight colour to light blue. +* Change network protocol to send server/join and rcon passwords in hashed form instead of in clear text. +* Fix various possible sources of non-determinism which could potentially cause multiplayer desyncs. + ### v0.31.3 (2019-07-13) * Fix the target order number of conditional order jumps being loaded incorrectly from SpringPP savegames. * Fix order backups not being restored when using buy and refit. diff --git a/source.list b/source.list index 1784fa379a..6bf55ec306 100644 --- a/source.list +++ b/source.list @@ -460,6 +460,7 @@ core/alloc_type.hpp core/backup_type.hpp core/bitmath_func.cpp core/bitmath_func.hpp +core/checksum_func.hpp core/container_func.hpp core/dyn_arena_alloc.hpp core/endian_func.hpp diff --git a/src/aircraft_cmd.cpp b/src/aircraft_cmd.cpp index 00d4ddad04..2ca4b84e16 100644 --- a/src/aircraft_cmd.cpp +++ b/src/aircraft_cmd.cpp @@ -40,6 +40,7 @@ #include "disaster_vehicle.h" #include "newgrf_airporttiles.h" #include "framerate_type.h" +#include "core/checksum_func.hpp" #include "table/strings.h" @@ -2127,6 +2128,7 @@ static bool AircraftEventHandler(Aircraft *v, int loop) bool Aircraft::Tick() { + UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos); if (!this->IsNormalAircraft()) return true; this->tick_counter++; diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 3781f2d0ac..8041100858 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1082,9 +1082,6 @@ struct BuildVehicleWindow : Window { this->GetWidget(WID_BV_BUILD_SEL)->SetDisplayedPlane(SZSP_NONE); } - /* disable renaming engines in network games if you are not the server */ - this->SetWidgetDisabledState(WID_BV_RENAME, _networking && !_network_server); - NWidgetCore *widget = this->GetWidget(WID_BV_LIST); widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type; @@ -1589,6 +1586,9 @@ struct BuildVehicleWindow : Window { this->SetWidgetsDisabledState(this->sel_engine == INVALID_ENGINE, WID_BV_SHOW_HIDE, WID_BV_BUILD, WID_BV_RENAME, WIDGET_LIST_END); + /* disable renaming engines in network games if you are not the server */ + this->SetWidgetDisabledState(WID_BV_RENAME, _networking && !(_network_server || _network_settings_access)); + this->DrawWidgets(); if (!this->IsShaded()) { diff --git a/src/command.cpp b/src/command.cpp index f768dee597..9faa2cb7a5 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -556,7 +556,7 @@ char *DumpCommandLog(char *buffer, const char *last) buffer += seprintf(buffer, last, "%c%c%c%c%c%c%c%c | ", fc(CLEF_SCRIPT, 'a'), fc(CLEF_BINARY, 'b'), fc(CLEF_MY_CMD, 'm'), fc(CLEF_ONLY_SENDING, 's'), fc(CLEF_ESTIMATE_ONLY, 'e'), fc(CLEF_TEXT, 't'), fc(CLEF_GENERATING_WORLD, 'g'), fc(CLEF_CMD_FAILED, 'f')); - buffer += seprintf(buffer, last, " %7d x %7d, p1: 0x%08X, p2: 0x%08X, cc: %2u, lc: %2u, cmd: 0x%08X (%s)\n", + buffer += seprintf(buffer, last, " %7d x %7d, p1: 0x%08X, p2: 0x%08X, cc: %3u, lc: %3u, cmd: 0x%08X (%s)\n", TileX(entry.tile), TileY(entry.tile), entry.p1, entry.p2, (uint) entry.current_company, (uint) entry.local_company, entry.cmd, GetCommandName(entry.cmd)); } return buffer; diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index dfbf88345c..dfbcf41fa2 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -703,6 +703,21 @@ DEF_CONSOLE_CMD(ConRcon) return true; } +DEF_CONSOLE_CMD(ConSettingsAccess) +{ + if (argc == 0) { + IConsoleHelp("Enable changing game settings from this client. Usage: 'settings_access '"); + return true; + } + + if (argc < 2) return false; + + if (!_network_server) { + NetworkClientSendSettingsPassword(argv[1]); + } + return true; +} + DEF_CONSOLE_CMD(ConStatus) { if (argc == 0) { @@ -2356,6 +2371,7 @@ void IConsoleStdLibRegister() IConsoleAliasRegister("info", "server_info"); IConsoleCmdRegister("reconnect", ConNetworkReconnect, ConHookClientOnly); IConsoleCmdRegister("rcon", ConRcon, ConHookNeedNetwork); + IConsoleCmdRegister("settings_access", ConSettingsAccess, ConHookNeedNetwork); IConsoleCmdRegister("join", ConJoinCompany, ConHookNeedNetwork); IConsoleAliasRegister("spectate", "join 255"); @@ -2380,6 +2396,8 @@ void IConsoleStdLibRegister() IConsoleAliasRegister("server_password", "setting server_password %+"); IConsoleAliasRegister("rcon_pw", "setting rcon_password %+"); IConsoleAliasRegister("rcon_password", "setting rcon_password %+"); + IConsoleAliasRegister("settings_pw", "setting settings_password %+"); + IConsoleAliasRegister("settings_password", "setting settings_password %+"); IConsoleAliasRegister("name", "setting client_name %+"); IConsoleAliasRegister("server_name", "setting server_name %+"); IConsoleAliasRegister("server_port", "setting server_port %+"); diff --git a/src/core/bitmath_func.cpp b/src/core/bitmath_func.cpp index 65188c9fd8..04c2e35b10 100644 --- a/src/core/bitmath_func.cpp +++ b/src/core/bitmath_func.cpp @@ -55,6 +55,13 @@ uint8 FindFirstBit(uint32 x) return pos; } +uint8 FindFirstBit64(uint64 x) +{ + if (x == 0) return 0; + if ((x & 0x00000000ffffffffULL) != 0) return FindFirstBit(x); + return FindFirstBit(x >> 32) + 32; +} + #endif /** diff --git a/src/core/bitmath_func.hpp b/src/core/bitmath_func.hpp index 2d908130b8..10a951d36a 100644 --- a/src/core/bitmath_func.hpp +++ b/src/core/bitmath_func.hpp @@ -198,6 +198,13 @@ inline uint8 FindFirstBit(uint32 x) return __builtin_ctz(x); } +inline uint8 FindFirstBit64(uint64 x) +{ + if (x == 0) return 0; + + return __builtin_ctzll(x); +} + #else /** Lookup table to check which bit is set in a 6 bit variable */ @@ -216,6 +223,7 @@ extern const uint8 _ffb_64[64]; #define FIND_FIRST_BIT(x) _ffb_64[(x)] uint8 FindFirstBit(uint32 x); +uint8 FindFirstBit64(uint64 x); #endif @@ -395,14 +403,14 @@ static inline T ROR(const T x, const uint8 n) * (since it will use hardware swapping if available). * Even though they should return uint16 and uint32, we get * warnings if we don't cast those (why?) */ - #define BSWAP64(x) ((uint64)CFSwapInt64(x)) - #define BSWAP32(x) ((uint32)CFSwapInt32(x)) - #define BSWAP16(x) ((uint16)CFSwapInt16(x)) + #define BSWAP64(x) ((uint64)CFSwapInt64((uint64)x)) + #define BSWAP32(x) ((uint32)CFSwapInt32((uint32)x)) + #define BSWAP16(x) ((uint16)CFSwapInt16((uint16)x)) #elif defined(_MSC_VER) /* MSVC has intrinsics for swapping, resulting in faster code */ - #define BSWAP64(x) (_byteswap_uint64(x)) - #define BSWAP32(x) (_byteswap_ulong(x)) - #define BSWAP16(x) (_byteswap_ushort(x)) + #define BSWAP64(x) ((uint64)_byteswap_uint64((uint64)x)) + #define BSWAP32(x) ((uint32)_byteswap_ulong((uint32)x)) + #define BSWAP16(x) ((uint16)_byteswap_ushort((uint16)x)) #else /** * Perform a 64 bits endianness bitswap on x. @@ -430,7 +438,7 @@ static inline T ROR(const T x, const uint8 n) { #if !defined(__ICC) && (defined(__GNUC__) || defined(__clang__)) /* GCC >= 4.3 provides a builtin, resulting in faster code */ - return (uint32)__builtin_bswap32((int32)x); + return (uint32)__builtin_bswap32((uint32)x); #else return ((x >> 24) & 0xFF) | ((x >> 8) & 0xFF00) | ((x << 8) & 0xFF0000) | ((x << 24) & 0xFF000000); #endif /* __GNUC__ || __clang__ */ @@ -443,7 +451,12 @@ static inline T ROR(const T x, const uint8 n) */ static inline uint16 BSWAP16(uint16 x) { +#if !defined(__ICC) && (defined(__GNUC__) || defined(__clang__)) + /* GCC >= 4.3 provides a builtin, resulting in faster code */ + return (uint16)__builtin_bswap16((uint16)x); +#else return (x >> 8) | (x << 8); +#endif /* __GNUC__ || __clang__ */ } #endif /* __APPLE__ */ diff --git a/src/core/checksum_func.hpp b/src/core/checksum_func.hpp new file mode 100644 index 0000000000..6845e14dae --- /dev/null +++ b/src/core/checksum_func.hpp @@ -0,0 +1,33 @@ +/* $Id$ */ + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** @file checksum_func.hpp Checksum utility functions. */ + +#ifndef CHECKSUM_FUNC_HPP +#define CHECKSUM_FUNC_HPP + +#include "bitmath_func.hpp" + +struct SimpleChecksum64 { + uint64 state = 0; + + void Update(uint64 input) + { + this->state = ROL(this->state, 1) ^ input ^ 0x123456789ABCDEF7ULL; + } +}; + +extern SimpleChecksum64 _state_checksum; + +inline void UpdateStateChecksum(uint64 input) +{ + _state_checksum.Update(input); +} + +#endif /* CHECKSUM_FUNC_HPP */ diff --git a/src/core/random_func.hpp b/src/core/random_func.hpp index 934703d6fb..d65060ccb7 100644 --- a/src/core/random_func.hpp +++ b/src/core/random_func.hpp @@ -12,6 +12,8 @@ #ifndef RANDOM_FUNC_HPP #define RANDOM_FUNC_HPP +#include "bitmath_func.hpp" + #if defined(__APPLE__) /* Apple already has Random declared */ #define Random OTTD_Random diff --git a/src/crashlog.cpp b/src/crashlog.cpp index abf13f5383..b95a26ab1c 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -179,6 +179,13 @@ char *CrashLog::LogOpenTTDVersion(char *buffer, const char *last) const */ char *CrashLog::LogConfiguration(char *buffer, const char *last) const { + auto pathfinder_name = [](uint8 pf) -> const char * { + switch (pf) { + case VPF_NPF: return "NPF"; + case VPF_YAPF: return "YAPF"; + default: return "-"; + }; + }; buffer += seprintf(buffer, last, "Configuration:\n" " Blitter: %s\n" @@ -189,7 +196,8 @@ char *CrashLog::LogConfiguration(char *buffer, const char *last) const " Network: %s\n" " Sound driver: %s\n" " Sound set: %s (%u)\n" - " Video driver: %s\n\n", + " Video driver: %s\n" + " Pathfinder: %s %s %s\n\n", BlitterFactory::GetCurrentBlitter() == nullptr ? "none" : BlitterFactory::GetCurrentBlitter()->GetName(), BaseGraphics::GetUsedSet() == nullptr ? "none" : BaseGraphics::GetUsedSet()->name, BaseGraphics::GetUsedSet() == nullptr ? UINT32_MAX : BaseGraphics::GetUsedSet()->version, @@ -201,7 +209,8 @@ char *CrashLog::LogConfiguration(char *buffer, const char *last) const SoundDriver::GetInstance() == nullptr ? "none" : SoundDriver::GetInstance()->GetName(), BaseSounds::GetUsedSet() == nullptr ? "none" : BaseSounds::GetUsedSet()->name, BaseSounds::GetUsedSet() == nullptr ? UINT32_MAX : BaseSounds::GetUsedSet()->version, - VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetName() + VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetName(), + pathfinder_name(_settings_game.pf.pathfinder_for_trains), pathfinder_name(_settings_game.pf.pathfinder_for_roadvehs), pathfinder_name(_settings_game.pf.pathfinder_for_ships) ); buffer += seprintf(buffer, last, @@ -396,7 +405,7 @@ char *CrashLog::FillCrashLog(char *buffer, const char *last) const YearMonthDay ymd; ConvertDateToYMD(_date, &ymd); - buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter); + buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i) (DL: %u)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter, _settings_game.economy.day_length_factor); if (_game_load_time != 0) { buffer += seprintf(buffer, last, "Game loaded at: %i-%02i-%02i (%i, %i), %s", _game_load_cur_date_ymd.year, _game_load_cur_date_ymd.month + 1, _game_load_cur_date_ymd.day, _game_load_date_fract, _game_load_tick_skip_counter, asctime(gmtime(&_game_load_time))); @@ -440,16 +449,26 @@ char *CrashLog::FillCrashLog(char *buffer, const char *last) const * @param last The last position in the buffer to write to. * @return the position of the \c '\0' character after the buffer. */ -char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last) const +char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last, const DesyncExtraInfo &info) const { time_t cur_time = time(nullptr); buffer += seprintf(buffer, last, "*** OpenTTD Multiplayer %s Desync Report ***\n\n", _network_server ? "Server" : "Client"); buffer += seprintf(buffer, last, "Desync at: %s", asctime(gmtime(&cur_time))); + if (!_network_server && info.flags) { + auto flag_check = [&](DesyncExtraInfo::Flags flag, const char *str) { + return info.flags & flag ? str : ""; + }; + buffer += seprintf(buffer, last, "Flags: %s%s%s%s\n", + flag_check(DesyncExtraInfo::DEIF_RAND1, "R"), + flag_check(DesyncExtraInfo::DEIF_RAND2, "Z"), + flag_check(DesyncExtraInfo::DEIF_STATE, "S"), + flag_check(DesyncExtraInfo::DEIF_DBL_RAND, "D")); + } YearMonthDay ymd; ConvertDateToYMD(_date, &ymd); - buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter); + buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i) (DL: %u)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter, _settings_game.economy.day_length_factor); if (_game_load_time != 0) { buffer += seprintf(buffer, last, "Game loaded at: %i-%02i-%02i (%i, %i), %s", _game_load_cur_date_ymd.year, _game_load_cur_date_ymd.month + 1, _game_load_cur_date_ymd.day, _game_load_date_fract, _game_load_tick_skip_counter, asctime(gmtime(&_game_load_time))); @@ -490,7 +509,7 @@ char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last) const * @param filename_last The last position in the filename buffer. * @return true when the crash log was successfully written. */ -bool CrashLog::WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name) const +bool CrashLog::WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name, FILE **crashlog_file) const { seprintf(filename, filename_last, "%s%s.log", _personal_dir, name); @@ -500,7 +519,11 @@ bool CrashLog::WriteCrashLog(const char *buffer, char *filename, const char *fil size_t len = strlen(buffer); size_t written = fwrite(buffer, 1, len, file); - FioFCloseFile(file); + if (crashlog_file) { + *crashlog_file = file; + } else { + FioFCloseFile(file); + } return len == written; } @@ -618,7 +641,7 @@ bool CrashLog::MakeCrashLog() const * information like paths to the console. * @return true when everything is made successfully. */ -bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_out) const +bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) const { char filename[MAX_PATH]; char buffer[65536 * 2]; @@ -632,16 +655,16 @@ bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_ou strftime(name_buffer_date, lastof(name_buffer) - name_buffer_date, "%Y%m%dT%H%M%SZ", gmtime(&cur_time)); printf("Desync encountered (%s), generating desync log...\n", mode); - char *b = this->FillDesyncCrashLog(buffer, lastof(buffer)); + char *b = this->FillDesyncCrashLog(buffer, lastof(buffer), info); + + if (log_out) log_out->assign(buffer); if (log_in && !log_in->empty()) { b = strecpy(b, "\n", lastof(buffer), true); b = strecpy(b, log_in->c_str(), lastof(buffer), true); } - if (log_out) log_out->assign(buffer); - - bool bret = this->WriteCrashLog(buffer, filename, lastof(filename), name_buffer); + bool bret = this->WriteCrashLog(buffer, filename, lastof(filename), name_buffer, info.log_file); if (bret) { printf("Desync log written to %s. Please add this file to any bug reports.\n\n", filename); } else { diff --git a/src/crashlog.h b/src/crashlog.h index f44000baee..3d9c5c26eb 100644 --- a/src/crashlog.h +++ b/src/crashlog.h @@ -12,8 +12,23 @@ #ifndef CRASHLOG_H #define CRASHLOG_H +#include "core/enum_type.hpp" #include +struct DesyncExtraInfo { + enum Flags { + DEIF_NONE = 0, ///< no flags + DEIF_RAND1 = 1 << 0, ///< random 1 mismatch + DEIF_RAND2 = 1 << 1, ///< random 2 mismatch + DEIF_STATE = 1 << 2, ///< state mismatch + DEIF_DBL_RAND = 1 << 3, ///< double-seed sent + }; + + Flags flags = DEIF_NONE; + FILE **log_file = nullptr; ///< save unclosed log file handle here +}; +DECLARE_ENUM_AS_BIT_SET(DesyncExtraInfo::Flags) + /** * Helper class for creating crash logs. */ @@ -113,8 +128,8 @@ public: virtual ~CrashLog() {} char *FillCrashLog(char *buffer, const char *last) const; - char *FillDesyncCrashLog(char *buffer, const char *last) const; - bool WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name = "crash") const; + char *FillDesyncCrashLog(char *buffer, const char *last, const DesyncExtraInfo &info) const; + bool WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name = "crash", FILE **crashlog_file = nullptr) const; /** * Write the (crash) dump to a file. @@ -130,7 +145,7 @@ public: bool WriteScreenshot(char *filename, const char *filename_last, const char *name = "crash") const; bool MakeCrashLog() const; - bool MakeDesyncCrashLog(const std::string *log_in, std::string *log_out) const; + bool MakeDesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) const; bool MakeCrashSavegameAndScreenshot() const; /** @@ -140,7 +155,7 @@ public: */ static void InitialiseCrashLog(); - static void DesyncCrashLog(const std::string *log_in, std::string *log_out); + static void DesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info); static void SetErrorMessage(const char *message); static void AfterCrashLogCleanup(); diff --git a/src/disaster_vehicle.cpp b/src/disaster_vehicle.cpp index 2d52609dd3..ba5f30428d 100644 --- a/src/disaster_vehicle.cpp +++ b/src/disaster_vehicle.cpp @@ -47,6 +47,7 @@ #include "company_base.h" #include "core/random_func.hpp" #include "core/backup_type.hpp" +#include "core/checksum_func.hpp" #include "table/strings.h" @@ -707,6 +708,7 @@ static DisasterVehicleTickProc * const _disastervehicle_tick_procs[] = { bool DisasterVehicle::Tick() { + UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos); return _disastervehicle_tick_procs[this->subtype](this); } diff --git a/src/effectvehicle.cpp b/src/effectvehicle.cpp index 5ea274db69..bee23ac373 100644 --- a/src/effectvehicle.cpp +++ b/src/effectvehicle.cpp @@ -18,6 +18,7 @@ #include "animated_tile_func.h" #include "effectvehicle_func.h" #include "effectvehicle_base.h" +#include "core/checksum_func.hpp" #include @@ -670,6 +671,7 @@ EffectVehicle *CreateEffectVehicleRel(const Vehicle *v, int x, int y, int z, Eff bool EffectVehicle::Tick() { + UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos); return _effect_tick_procs[this->subtype](this); } diff --git a/src/network/core/tcp_game.cpp b/src/network/core/tcp_game.cpp index ef3a382219..c74b02e6fe 100644 --- a/src/network/core/tcp_game.cpp +++ b/src/network/core/tcp_game.cpp @@ -41,6 +41,8 @@ NetworkGameSocketHandler::NetworkGameSocketHandler(SOCKET s) : info(nullptr), cl */ NetworkRecvStatus NetworkGameSocketHandler::CloseConnection(bool error) { + if (this->ignore_close) return NETWORK_RECV_STATUS_CONN_LOST; + /* Clients drop back to the main menu */ if (!_network_server && _networking) { extern void ClientNetworkEmergencySave(); // from network_client.cpp @@ -79,6 +81,8 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p) case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p); case PACKET_CLIENT_GAME_PASSWORD: return this->Receive_CLIENT_GAME_PASSWORD(p); case PACKET_CLIENT_COMPANY_PASSWORD: return this->Receive_CLIENT_COMPANY_PASSWORD(p); + case PACKET_CLIENT_SETTINGS_PASSWORD: return this->Receive_CLIENT_SETTINGS_PASSWORD(p); + case PACKET_SERVER_SETTINGS_ACCESS: return this->Receive_SERVER_SETTINGS_ACCESS(p); case PACKET_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(p); case PACKET_CLIENT_GETMAP: return this->Receive_CLIENT_GETMAP(p); case PACKET_SERVER_WAIT: return this->Receive_SERVER_WAIT(p); @@ -100,6 +104,7 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p) case PACKET_CLIENT_QUIT: return this->Receive_CLIENT_QUIT(p); case PACKET_CLIENT_ERROR: return this->Receive_CLIENT_ERROR(p); case PACKET_CLIENT_DESYNC_LOG: return this->Receive_CLIENT_DESYNC_LOG(p); + case PACKET_SERVER_DESYNC_LOG: return this->Receive_SERVER_DESYNC_LOG(p); case PACKET_SERVER_QUIT: return this->Receive_SERVER_QUIT(p); case PACKET_SERVER_ERROR_QUIT: return this->Receive_SERVER_ERROR_QUIT(p); case PACKET_SERVER_SHUTDOWN: return this->Receive_SERVER_SHUTDOWN(p); @@ -166,6 +171,8 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Pa NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_PASSWORD); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_PASSWORD); } +NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SETTINGS_PASSWORD); } +NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_SETTINGS_ACCESS(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_SETTINGS_ACCESS); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_WELCOME); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GETMAP); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WAIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_WAIT); } @@ -187,6 +194,7 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet *p) { NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_QUIT); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_ERROR); } NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_DESYNC_LOG(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_DESYNC_LOG); } +NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_DESYNC_LOG(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_DESYNC_LOG); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_QUIT); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_ERROR_QUIT); } NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_SHUTDOWN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_SHUTDOWN); } diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index 03eb5ded44..2fcc503912 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -63,6 +63,8 @@ enum PacketGameType { PACKET_CLIENT_GAME_PASSWORD, ///< Clients sends the (hashed) game password. PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests the (hashed) company password. PACKET_CLIENT_COMPANY_PASSWORD, ///< Client sends the (hashed) company password. + PACKET_CLIENT_SETTINGS_PASSWORD, ///< Client sends the (hashed) settings password. + PACKET_SERVER_SETTINGS_ACCESS, ///< Server sends the settings access state. /* The server welcomes the authenticated client and sends information of other clients. */ PACKET_SERVER_WELCOME, ///< Server welcomes you and gives you your #ClientID. @@ -122,6 +124,7 @@ enum PacketGameType { PACKET_CLIENT_ERROR, ///< A client reports an error to the server. PACKET_SERVER_ERROR_QUIT, ///< A server tells that a client has hit an error and did quit. PACKET_CLIENT_DESYNC_LOG, ///< A client reports a desync log + PACKET_SERVER_DESYNC_LOG, ///< A server reports a desync log PACKET_END, ///< Must ALWAYS be on the end of this list!! (period) }; @@ -158,6 +161,7 @@ private: NetworkClientInfo *info; ///< Client info related to this socket protected: + bool ignore_close = false; NetworkRecvStatus ReceiveInvalidPacket(PacketGameType type); /** @@ -262,6 +266,21 @@ protected: */ virtual NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet *p); + /** + * Send a password to the server to authorize + * uint8 Password type (see NetworkPasswordType). + * string The password. + * @param p The packet that was just received. + */ + virtual NetworkRecvStatus Receive_CLIENT_SETTINGS_PASSWORD(Packet *p); + + /** + * Indication to the client that the setting access state has changed + * bool setting access state + * @param p The packet that was just received. + */ + virtual NetworkRecvStatus Receive_SERVER_SETTINGS_ACCESS(Packet *p); + /** * The client is joined and ready to receive his map: * uint32 Own client ID. @@ -428,6 +447,7 @@ protected: */ virtual NetworkRecvStatus Receive_CLIENT_ERROR(Packet *p); virtual NetworkRecvStatus Receive_CLIENT_DESYNC_LOG(Packet *p); + virtual NetworkRecvStatus Receive_SERVER_DESYNC_LOG(Packet *p); /** * Notification that a client left the game: diff --git a/src/network/network.cpp b/src/network/network.cpp index 8c7f4e6ce9..dbd45e4d0a 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -35,6 +35,7 @@ #include "../core/pool_func.hpp" #include "../gfx_func.h" #include "../error.h" +#include "../core/checksum_func.hpp" #include "../safeguards.h" @@ -56,6 +57,7 @@ bool _network_server; ///< network-server is active bool _network_available; ///< is network mode available? bool _network_dedicated; ///< are we a dedicated server? bool _is_network_server; ///< Does this client wants to be a network-server? +bool _network_settings_access; ///< Can this client change server settings? NetworkServerGameInfo _network_game_info; ///< Information about our game. NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies. ClientID _network_own_client_id; ///< Our client identifier. @@ -74,6 +76,7 @@ uint32 _sync_seed_1; ///< Seed to compare during sync checks. #ifdef NETWORK_SEND_DOUBLE_SEED uint32 _sync_seed_2; ///< Second part of the seed. #endif +uint64 _sync_state_checksum; ///< State checksum to compare during sync checks. uint32 _sync_frame; ///< The frame to perform the sync check. bool _network_first_time; ///< Whether we have finished joining or not. bool _network_udp_server; ///< Is the UDP server started? @@ -893,7 +896,7 @@ void NetworkGameLoop() /* We don't want to log multiple times if paused. */ static Date last_log; if (last_log != _date) { - DEBUG(desync, 1, "sync: date{%08x; %02x; %02x}; %08x; %08x", _date, _date_fract, _tick_skip_counter, _random.state[0], _random.state[1]); + DEBUG(desync, 2, "sync: date{%08x; %02x; %02x}; %08x; %08x", _date, _date_fract, _tick_skip_counter, _random.state[0], _random.state[1]); last_log = _date; } } @@ -1028,6 +1031,7 @@ void NetworkGameLoop() #ifdef NETWORK_SEND_DOUBLE_SEED _sync_seed_2 = _random.state[1]; #endif + _sync_state_checksum = _state_checksum.state; NetworkServer_Tick(send_frame); } else { diff --git a/src/network/network.h b/src/network/network.h index 9f8e3b790b..1b9bb741cb 100644 --- a/src/network/network.h +++ b/src/network/network.h @@ -22,5 +22,6 @@ extern bool _network_server; ///< network-server is active extern bool _network_available; ///< is network mode available? extern bool _network_dedicated; ///< are we a dedicated server? extern bool _is_network_server; ///< Does this client wants to be a network-server? +extern bool _network_settings_access; ///< Can this client change server settings? #endif /* NETWORK_H */ diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index f7d779b9d7..319f9c5277 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -32,6 +32,8 @@ #include "../core/backup_type.hpp" #include "../thread.h" #include "../crashlog.h" +#include "../core/checksum_func.hpp" +#include "../fileio_func.h" #include "table/strings.h" @@ -134,6 +136,7 @@ void ClientNetworkEmergencySave() { if (!_settings_client.gui.autosave_on_network_disconnect) return; if (!_networking) return; + if (!ClientNetworkGameSocketHandler::EmergencySavePossible()) return; const char *filename = "netsave.sav"; DEBUG(net, 0, "Client: Performing emergency save (%s)", filename); @@ -158,8 +161,18 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler() { assert(ClientNetworkGameSocketHandler::my_client == this); ClientNetworkGameSocketHandler::my_client = nullptr; + _network_settings_access = false; delete this->savegame; + + if (this->desync_log_file) { + if (!this->server_desync_log.empty()) { + fwrite("\n", 1, 1, this->desync_log_file); + fwrite(this->server_desync_log.data(), 1, this->server_desync_log.size(), this->desync_log_file); + } + FioFCloseFile(this->desync_log_file); + this->desync_log_file = nullptr; + } } NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status) @@ -173,6 +186,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta * that code any more complex or more aware of the validity of the socket. */ if (this->sock == INVALID_SOCKET) return status; + if (this->status == STATUS_CLOSING) return status; DEBUG(net, 1, "Shutting down client connection %d", this->client_id); @@ -190,6 +204,12 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta DEBUG(net, 1, "Shutdown client connection %d", this->client_id); + if (status == NETWORK_RECV_STATUS_DESYNC) { + this->status = STATUS_CLOSING; + this->ignore_close = true; + this->ReceivePackets(); + } + delete this->GetInfo(); delete this; @@ -282,16 +302,26 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) if (_sync_frame != 0) { if (_sync_frame == _frame_counter) { #ifdef NETWORK_SEND_DOUBLE_SEED - if (_sync_seed_1 != _random.state[0] || _sync_seed_2 != _random.state[1]) { + if (_sync_seed_1 != _random.state[0] || _sync_seed_2 != _random.state[1] || _sync_state_checksum != _state_checksum.state) { #else - if (_sync_seed_1 != _random.state[0]) { + if (_sync_seed_1 != _random.state[0] || _sync_state_checksum != _state_checksum.state) { #endif + DesyncExtraInfo info; + if (_sync_seed_1 != _random.state[0]) info.flags |= DesyncExtraInfo::DEIF_RAND1; +#ifdef NETWORK_SEND_DOUBLE_SEED + if (_sync_seed_2 != _random.state[1]) info.flags |= DesyncExtraInfo::DEIF_RAND2; + info.flags |= DesyncExtraInfo::DEIF_DBL_RAND; +#endif + if (_sync_state_checksum != _state_checksum.state) info.flags |= DesyncExtraInfo::DEIF_STATE; + NetworkError(STR_NETWORK_ERROR_DESYNC); - DEBUG(desync, 1, "sync_err: date{%08x; %02x; %02x}", _date, _date_fract, _tick_skip_counter); + DEBUG(desync, 1, "sync_err: date{%08x; %02x; %02x} {%x, " OTTD_PRINTFHEX64 "} != {%x, " OTTD_PRINTFHEX64 "}" + , _date, _date_fract, _tick_skip_counter, _sync_seed_1, _sync_state_checksum, _random.state[0], _state_checksum.state); DEBUG(net, 0, "Sync error detected!"); std::string desync_log; - CrashLog::DesyncCrashLog(nullptr, &desync_log); + info.log_file = &(my_client->desync_log_file); + CrashLog::DesyncCrashLog(nullptr, &desync_log, info); my_client->SendDesyncLog(desync_log); my_client->ClientError(NETWORK_RECV_STATUS_DESYNC); return false; @@ -315,6 +345,14 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) return true; } +/* static */ bool ClientNetworkGameSocketHandler::EmergencySavePossible() +{ + if (!my_client) return false; + if (my_client->emergency_save_done) return false; + my_client->emergency_save_done = true; + return true; +} + /** Our client's connection. */ ClientNetworkGameSocketHandler * ClientNetworkGameSocketHandler::my_client = nullptr; @@ -323,8 +361,14 @@ ClientNetworkGameSocketHandler * ClientNetworkGameSocketHandler::my_client = nul static uint32 last_ack_frame; /** One bit of 'entropy' used to generate a salt for the company passwords. */ -static uint32 _password_game_seed; -/** The other bit of 'entropy' used to generate a salt for the company passwords. */ +static uint32 _company_password_game_seed; +/** One bit of 'entropy' used to generate a salt for the server passwords. */ +static uint32 _server_password_game_seed; +/** One bit of 'entropy' used to generate a salt for the rcon passwords. */ +static uint32 _rcon_password_game_seed; +/** One bit of 'entropy' used to generate a salt for the settings passwords. */ +static uint32 _settings_password_game_seed; +/** The other bit of 'entropy' used to generate a salt for the company, server, rcon, and settings passwords. */ static char _password_server_id[NETWORK_SERVER_ID_LENGTH]; /** Maximum number of companies of the currently joined server. */ @@ -392,7 +436,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk() NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const char *password) { Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD); - p->Send_string(password); + p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _server_password_game_seed)); my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -404,7 +448,19 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const char *p NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const char *password) { Packet *p = new Packet(PACKET_CLIENT_COMPANY_PASSWORD); - p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed)); + p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _company_password_game_seed)); + my_client->SendPacket(p); + return NETWORK_RECV_STATUS_OKAY; +} + +/** + * Set the game password as requested. + * @param password The game password. + */ +NetworkRecvStatus ClientNetworkGameSocketHandler::SendSettingsPassword(const char *password) +{ + Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD); + p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _settings_password_game_seed)); my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -502,7 +558,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetPassword(const char *pa { Packet *p = new Packet(PACKET_CLIENT_SET_PASSWORD); - p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed)); + p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _company_password_game_seed)); my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -539,7 +595,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendQuit() NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const char *pass, const char *command) { Packet *p = new Packet(PACKET_CLIENT_RCON); - p->Send_string(pass); + p->Send_string(GenerateCompanyPasswordHash(pass, _password_server_id, _rcon_password_game_seed)); p->Send_string(command); my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; @@ -554,7 +610,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendMove(CompanyID company, co { Packet *p = new Packet(PACKET_CLIENT_MOVE); p->Send_uint8(company); - p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed)); + p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _company_password_game_seed)); my_client->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -771,6 +827,10 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSW if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET; this->status = STATUS_AUTH_GAME; + _server_password_game_seed = p->Recv_uint32(); + p->Recv_string(_password_server_id, sizeof(_password_server_id)); + if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET; + const char *password = _network_join_server_password; if (!StrEmpty(password)) { return SendGamePassword(password); @@ -786,7 +846,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PA if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET; this->status = STATUS_AUTH_COMPANY; - _password_game_seed = p->Recv_uint32(); + _company_password_game_seed = p->Recv_uint32(); p->Recv_string(_password_server_id, sizeof(_password_server_id)); if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET; @@ -808,7 +868,10 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet _network_own_client_id = (ClientID)p->Recv_uint32(); /* Initialize the password hash salting variables, even if they were previously. */ - _password_game_seed = p->Recv_uint32(); + _company_password_game_seed = p->Recv_uint32(); + _server_password_game_seed = p->Recv_uint32(); + _rcon_password_game_seed = p->Recv_uint32(); + _settings_password_game_seed = p->Recv_uint32(); p->Recv_string(_password_server_id, sizeof(_password_server_id)); /* Start receiving the map */ @@ -932,6 +995,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p) { + if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY; if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET; _frame_counter_server = p->Recv_uint32(); @@ -945,6 +1009,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p #ifdef NETWORK_SEND_DOUBLE_SEED _sync_seed_2 = p->Recv_uint32(); #endif + _sync_state_checksum = p->Recv_uint64(); } #endif /* Receive the token. */ @@ -965,6 +1030,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SYNC(Packet *p) { + if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY; if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET; _sync_frame = p->Recv_uint32(); @@ -972,12 +1038,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SYNC(Packet *p) #ifdef NETWORK_SEND_DOUBLE_SEED _sync_seed_2 = p->Recv_uint32(); #endif + _sync_state_checksum = p->Recv_uint64(); return NETWORK_RECV_STATUS_OKAY; } NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet *p) { + if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY; if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET; CommandPacket cp; @@ -997,6 +1065,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p) { + if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY; if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET; char name[NETWORK_NAME_LENGTH], msg[NETWORK_CHAT_LENGTH]; @@ -1054,6 +1123,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Pack if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET; ClientID client_id = (ClientID)p->Recv_uint32(); + if (client_id == _network_own_client_id) return NETWORK_RECV_STATUS_OKAY; // do not try to clear our own client info NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id); if (ci != nullptr) { @@ -1066,6 +1136,15 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Pack return NETWORK_RECV_STATUS_OKAY; } +NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_DESYNC_LOG(Packet *p) +{ + uint size = p->Recv_uint16(); + this->server_desync_log.resize(this->server_desync_log.size() + size); + p->Recv_binary(const_cast(this->server_desync_log.data() + this->server_desync_log.size() - size), size); + DEBUG(net, 2, "Received %u bytes of server desync log", size); + return NETWORK_RECV_STATUS_OKAY; +} + NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p) { if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET; @@ -1195,6 +1274,17 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMPANY_UPDATE( return NETWORK_RECV_STATUS_OKAY; } +NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SETTINGS_ACCESS(Packet *p) +{ + if (this->status < STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET; + + _network_settings_access = p->Recv_bool(); + + ReInitAllWindows(); + + return NETWORK_RECV_STATUS_OKAY; +} + /** * Check the connection's state, i.e. is the connection still up? */ @@ -1249,6 +1339,16 @@ void NetworkClientSendRcon(const char *password, const char *command) MyClient::SendRCon(password, command); } +/** + * Send settings password. + * @param password The password. + * @param command The command to execute. + */ +void NetworkClientSendSettingsPassword(const char *password) +{ + MyClient::SendSettingsPassword(password); +} + /** * Notify the server of this client wanting to be moved to another company. * @param company_id id of the company the client wishes to be moved to. diff --git a/src/network/network_client.h b/src/network/network_client.h index 25ee42d426..91c0b4e6d6 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -32,11 +32,16 @@ private: STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map. STATUS_MAP, ///< The client is downloading the map. STATUS_ACTIVE, ///< The client is active within in the game. + STATUS_CLOSING, ///< The client connection is in the process of being closed. STATUS_END, ///< Must ALWAYS be on the end of this list!! (period) }; ServerStatus status; ///< Status of the connection with the server. + FILE *desync_log_file = nullptr; + std::string server_desync_log; + bool emergency_save_done = false; + protected: friend void NetworkExecuteLocalCommandQueue(); friend void NetworkClose(bool close_admins); @@ -49,6 +54,7 @@ protected: NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet *p) override; NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet *p) override; NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet *p) override; + NetworkRecvStatus Receive_SERVER_SETTINGS_ACCESS(Packet *p) override; NetworkRecvStatus Receive_SERVER_WELCOME(Packet *p) override; NetworkRecvStatus Receive_SERVER_WAIT(Packet *p) override; NetworkRecvStatus Receive_SERVER_MAP_BEGIN(Packet *p) override; @@ -62,6 +68,7 @@ protected: NetworkRecvStatus Receive_SERVER_CHAT(Packet *p) override; NetworkRecvStatus Receive_SERVER_QUIT(Packet *p) override; NetworkRecvStatus Receive_SERVER_ERROR_QUIT(Packet *p) override; + NetworkRecvStatus Receive_SERVER_DESYNC_LOG(Packet *p) override; NetworkRecvStatus Receive_SERVER_SHUTDOWN(Packet *p) override; NetworkRecvStatus Receive_SERVER_NEWGAME(Packet *p) override; NetworkRecvStatus Receive_SERVER_RCON(Packet *p) override; @@ -92,6 +99,7 @@ public: static NetworkRecvStatus SendGamePassword(const char *password); static NetworkRecvStatus SendCompanyPassword(const char *password); + static NetworkRecvStatus SendSettingsPassword(const char *password); static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const char *msg, NetworkTextMessageData data); static NetworkRecvStatus SendSetPassword(const char *password); @@ -104,6 +112,8 @@ public: static void Send(); static bool Receive(); static bool GameLoop(); + + static bool EmergencySavePossible(); }; /** Helper to make the code look somewhat nicer. */ diff --git a/src/network/network_func.h b/src/network/network_func.h index 781a5ce7d6..04b8178a60 100644 --- a/src/network/network_func.h +++ b/src/network/network_func.h @@ -54,6 +54,7 @@ void NetworkClientsToSpectators(CompanyID cid); void NetworkClientConnectGame(NetworkAddress address, CompanyID join_as, const char *join_server_password = nullptr, const char *join_company_password = nullptr); void NetworkClientRequestMove(CompanyID company, const char *pass = ""); void NetworkClientSendRcon(const char *password, const char *command); +void NetworkClientSendSettingsPassword(const char *password); void NetworkClientSendChat(NetworkAction action, DestType type, int dest, const char *msg, NetworkTextMessageData data = NetworkTextMessageData()); bool NetworkClientPreferTeamChat(const NetworkClientInfo *cio); bool NetworkCompanyIsPassworded(CompanyID company_id); diff --git a/src/network/network_internal.h b/src/network/network_internal.h index 73a8a56947..85b16923c7 100644 --- a/src/network/network_internal.h +++ b/src/network/network_internal.h @@ -118,6 +118,7 @@ extern uint32 _sync_seed_1; #ifdef NETWORK_SEND_DOUBLE_SEED extern uint32 _sync_seed_2; #endif +extern uint64 _sync_state_checksum; extern uint32 _sync_frame; extern bool _network_first_time; /* Vars needed for the join-GUI */ diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 9da604ab84..ae3f407bdc 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -216,6 +216,9 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo this->status = STATUS_INACTIVE; this->client_id = _network_client_id++; this->receive_limit = _settings_client.network.bytes_per_frame_burst; + this->server_hash_bits = InteractiveRandom(); + this->rcon_hash_bits = InteractiveRandom(); + this->settings_hash_bits = InteractiveRandom(); /* The Socket and Info pools need to be the same in size. After all, * each Socket will be associated with at most one Info object. As @@ -320,7 +323,14 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta NetworkClientSocket *cs; FOR_ALL_CLIENT_SOCKETS(cs) { if (cs->writable) { - if (cs->SendPackets() != SPS_CLOSED && cs->status == STATUS_MAP) { + if (cs->status == STATUS_CLOSE_PENDING) { + SendPacketsState send_state = cs->SendPackets(true); + if (send_state == SPS_CLOSED) { + cs->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST); + } else if (send_state != SPS_PARTLY_SENT && send_state != SPS_NONE_SENT) { + ShutdownSocket(cs->sock, true, false, 2); + } + } else if (cs->SendPackets() != SPS_CLOSED && cs->status == STATUS_MAP) { /* This client is in the middle of a map-send, call the function for that */ cs->SendMap(); } @@ -461,6 +471,20 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendError(NetworkErrorCode err return this->CloseConnection(NETWORK_RECV_STATUS_SERVER_ERROR); } +NetworkRecvStatus ServerNetworkGameSocketHandler::SendDesyncLog(const std::string &log) +{ + for (size_t offset = 0; offset < log.size();) { + Packet *p = new Packet(PACKET_SERVER_DESYNC_LOG); + size_t size = min(log.size() - offset, SHRT_MAX - 2 - p->size); + p->Send_uint16(size); + p->Send_binary(log.data() + offset, size); + this->SendPacket(p); + + offset += size; + } + return NETWORK_RECV_STATUS_OKAY; +} + /** Send the check for the NewGRFs. */ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck() { @@ -492,6 +516,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword() this->last_frame = this->last_frame_server = _frame_counter; Packet *p = new Packet(PACKET_SERVER_NEED_GAME_PASSWORD); + p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits); + p->Send_string(_settings_client.network.network_id); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -531,6 +557,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome() p = new Packet(PACKET_SERVER_WELCOME); p->Send_uint32(this->client_id); p->Send_uint32(_settings_game.game_creation.generation_seed); + p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits); + p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->rcon_hash_bits); + p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->settings_hash_bits); p->Send_string(_settings_client.network.network_id); this->SendPacket(p); @@ -689,6 +718,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendFrame() #ifdef NETWORK_SEND_DOUBLE_SEED p->Send_uint32(_sync_seed_2); #endif + p->Send_uint64(_sync_state_checksum); #endif /* If token equals 0, we need to make a new token and send that. */ @@ -711,6 +741,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendSync() #ifdef NETWORK_SEND_DOUBLE_SEED p->Send_uint32(_sync_seed_2); #endif + p->Send_uint64(_sync_state_checksum); this->SendPacket(p); return NETWORK_RECV_STATUS_OKAY; } @@ -852,6 +883,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendConfigUpdate() return NETWORK_RECV_STATUS_OKAY; } +NetworkRecvStatus ServerNetworkGameSocketHandler::SendSettingsAccessUpdate(bool ok) +{ + Packet *p = new Packet(PACKET_SERVER_SETTINGS_ACCESS); + p->Send_bool(ok); + this->SendPacket(p); + return NETWORK_RECV_STATUS_OKAY; +} + + /*********** * Receiving functions * DEF_SERVER_RECEIVE_COMMAND has parameter: NetworkClientSocket *cs, Packet *p @@ -970,7 +1010,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(P /* Check game password. Allow joining if we cleared the password meanwhile */ if (!StrEmpty(_settings_client.network.server_password) && - strcmp(password, _settings_client.network.server_password) != 0) { + strcmp(password, GenerateCompanyPasswordHash(_settings_client.network.server_password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed ^ this->server_hash_bits)) != 0) { /* Password is invalid */ return this->SendError(NETWORK_ERROR_WRONG_PASSWORD); } @@ -1006,6 +1046,29 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWOR return this->SendWelcome(); } +NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWORD(Packet *p) +{ + if (this->status != STATUS_ACTIVE) { + /* Illegal call, return error and ignore the packet */ + return this->SendError(NETWORK_ERROR_NOT_EXPECTED); + } + + char password[NETWORK_PASSWORD_LENGTH]; + p->Recv_string(password, sizeof(password)); + + /* Check settings password. Deny if no password is set */ + if (StrEmpty(_settings_client.network.settings_password) || + strcmp(password, GenerateCompanyPasswordHash(_settings_client.network.settings_password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed ^ this->settings_hash_bits)) != 0) { + DEBUG(net, 0, "[settings-ctrl] wrong password from client-id %d", this->client_id); + this->settings_authed = false; + } else { + DEBUG(net, 0, "[settings-ctrl] client-id %d", this->client_id); + this->settings_authed = true; + } + + return this->SendSettingsAccessUpdate(this->settings_authed); +} + NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet *p) { NetworkClientSocket *new_cs; @@ -1100,12 +1163,12 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet } - if ((GetCommandFlags(cp.cmd) & CMD_SERVER) && ci->client_id != CLIENT_ID_SERVER) { + if ((GetCommandFlags(cp.cmd) & CMD_SERVER) && ci->client_id != CLIENT_ID_SERVER && !this->settings_authed) { IConsolePrintF(CC_ERROR, "WARNING: server only command from: client %d (IP: %s), kicking...", ci->client_id, this->GetClientIP()); return this->SendError(NETWORK_ERROR_KICKED); } - if ((GetCommandFlags(cp.cmd) & CMD_SPECTATOR) == 0 && !Company::IsValidID(cp.company) && ci->client_id != CLIENT_ID_SERVER) { + if ((GetCommandFlags(cp.cmd) & CMD_SPECTATOR) == 0 && !Company::IsValidID(cp.company) && ci->client_id != CLIENT_ID_SERVER && !this->settings_authed) { IConsolePrintF(CC_ERROR, "WARNING: spectator issuing command from client %d (IP: %s), kicking...", ci->client_id, this->GetClientIP()); return this->SendError(NETWORK_ERROR_KICKED); } @@ -1115,7 +1178,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet * to match the company in the packet. If it doesn't, the client has done * something pretty naughty (or a bug), and will be kicked */ - if (!(cp.cmd == CMD_COMPANY_CTRL && cp.p1 == 0 && ci->client_playas == COMPANY_NEW_COMPANY) && ci->client_playas != cp.company) { + if (!(cp.cmd == CMD_COMPANY_CTRL && cp.p1 == 0 && ci->client_playas == COMPANY_NEW_COMPANY) && ci->client_playas != cp.company && + !((GetCommandFlags(cp.cmd) & CMD_SERVER) && this->settings_authed)) { IConsolePrintF(CC_ERROR, "WARNING: client %d (IP: %s) tried to execute a command as company %d, kicking...", ci->client_playas + 1, this->GetClientIP(), cp.company + 1); return this->SendError(NETWORK_ERROR_COMPANY_MISMATCH); @@ -1171,12 +1235,22 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p NetworkAdminClientError(this->client_id, errorno); if (errorno == NETWORK_ERROR_DESYNC) { - CrashLog::DesyncCrashLog(&(this->desync_log), nullptr); + std::string server_desync_log; + CrashLog::DesyncCrashLog(&(this->desync_log), &server_desync_log, DesyncExtraInfo{}); + this->SendDesyncLog(server_desync_log); + + // decrease the sync frequency for this point onwards + _settings_client.network.sync_freq = min(_settings_client.network.sync_freq, 16); // have the server and all clients run some sanity checks NetworkSendCommand(0, 0, 0, CMD_DESYNC_CHECK, nullptr, nullptr, _local_company, 0); - } + SendPacketsState send_state = this->SendPackets(true); + if (send_state != SPS_CLOSED) { + this->status = STATUS_CLOSE_PENDING; + return NETWORK_RECV_STATUS_OKAY; + } + } return this->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST); } @@ -1476,7 +1550,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_RCON(Packet *p) p->Recv_string(pass, sizeof(pass)); p->Recv_string(command, sizeof(command)); - if (strcmp(pass, _settings_client.network.rcon_password) != 0) { + if (strcmp(pass, GenerateCompanyPasswordHash(_settings_client.network.rcon_password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed ^ this->rcon_hash_bits)) != 0) { DEBUG(net, 0, "[rcon] wrong password from client-id %d", this->client_id); return NETWORK_RECV_STATUS_OKAY; } @@ -1910,6 +1984,7 @@ void NetworkServer_Tick(bool send_frame) break; case NetworkClientSocket::STATUS_MAP_WAIT: + case NetworkClientSocket::STATUS_CLOSE_PENDING: /* This is an internal state where we do not wait * on the client to move to a different state. */ break; @@ -1919,7 +1994,7 @@ void NetworkServer_Tick(bool send_frame) NOT_REACHED(); } - if (cs->status >= NetworkClientSocket::STATUS_PRE_ACTIVE) { + if (cs->status >= NetworkClientSocket::STATUS_PRE_ACTIVE && cs->status != NetworkClientSocket::STATUS_CLOSE_PENDING) { /* Check if we can send command, and if we have anything in the queue */ NetworkHandleCommandQueue(cs); @@ -1981,7 +2056,8 @@ void NetworkServerShowStatusToConsole() "loading map", "map done", "ready", - "active" + "active", + "close pending" }; assert_compile(lengthof(stat_str) == NetworkClientSocket::STATUS_END); diff --git a/src/network/network_server.h b/src/network/network_server.h index 89de84d06b..2e8573d5a9 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -29,6 +29,7 @@ protected: NetworkRecvStatus Receive_CLIENT_COMPANY_INFO(Packet *p) override; NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet *p) override; NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet *p) override; + NetworkRecvStatus Receive_CLIENT_SETTINGS_PASSWORD(Packet *p) override; NetworkRecvStatus Receive_CLIENT_GETMAP(Packet *p) override; NetworkRecvStatus Receive_CLIENT_MAP_OK(Packet *p) override; NetworkRecvStatus Receive_CLIENT_ACK(Packet *p) override; @@ -63,6 +64,7 @@ public: STATUS_DONE_MAP, ///< The client has downloaded the map. STATUS_PRE_ACTIVE, ///< The client is catching up the delayed frames. STATUS_ACTIVE, ///< The client is active within in the game. + STATUS_CLOSE_PENDING, ///< The client connection is pending closure. STATUS_END, ///< Must ALWAYS be on the end of this list!! (period). }; @@ -72,6 +74,10 @@ public: ClientStatus status; ///< Status of this client CommandQueue outgoing_queue; ///< The command-queue awaiting delivery int receive_limit; ///< Amount of bytes that we can receive at this moment + uint32 server_hash_bits; ///< Server password hash entropy bits + uint32 rcon_hash_bits; ///< Rcon password hash entropy bits + uint32 settings_hash_bits; ///< Settings password hash entropy bits + bool settings_authed = false;///< Authorised to control all game settings struct PacketWriter *savegame; ///< Writer used to write the savegame. NetworkAddress client_address; ///< IP-address of the client (so he can be banned) @@ -95,6 +101,7 @@ public: NetworkRecvStatus SendClientInfo(NetworkClientInfo *ci); NetworkRecvStatus SendError(NetworkErrorCode error); + NetworkRecvStatus SendDesyncLog(const std::string &log); NetworkRecvStatus SendChat(NetworkAction action, ClientID client_id, bool self_send, const char *msg, NetworkTextMessageData data); NetworkRecvStatus SendJoin(ClientID client_id); NetworkRecvStatus SendFrame(); @@ -102,6 +109,7 @@ public: NetworkRecvStatus SendCommand(const CommandPacket *cp); NetworkRecvStatus SendCompanyUpdate(); NetworkRecvStatus SendConfigUpdate(); + NetworkRecvStatus SendSettingsAccessUpdate(bool ok); static void Send(); static void AcceptConnection(SOCKET s, const NetworkAddress &address); diff --git a/src/newgrf_house.cpp b/src/newgrf_house.cpp index cf87027f1a..b265f4efc0 100644 --- a/src/newgrf_house.cpp +++ b/src/newgrf_house.cpp @@ -719,9 +719,12 @@ bool NewHouseTileLoop(TileIndex tile) /* Check callback 21, which determines if a house should be destroyed. */ if (HasBit(hs->callback_mask, CBM_HOUSE_DESTRUCTION)) { - uint16 callback_res = GetHouseCallback(CBID_HOUSE_DESTRUCTION, 0, 0, GetHouseType(tile), Town::GetByTile(tile), tile); + Town *t = Town::GetByTile(tile); + uint16 callback_res = GetHouseCallback(CBID_HOUSE_DESTRUCTION, 0, 0, GetHouseType(tile), t, tile); if (callback_res != CALLBACK_FAILED && Convert8bitBooleanCallback(hs->grf_prop.grffile, CBID_HOUSE_DESTRUCTION, callback_res)) { - ClearTownHouse(Town::GetByTile(tile), tile); + ClearTownHouse(t, tile); + extern void RemoveNearbyStations(Town *t); + RemoveNearbyStations(t); return false; } } diff --git a/src/openttd.cpp b/src/openttd.cpp index ac3b495d85..04672c3fe8 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -78,6 +78,7 @@ #include "string_func_extra.h" #include "industry.h" #include "cargopacket.h" +#include "core/checksum_func.hpp" #include "linkgraph/linkgraphschedule.h" #include "tracerestrict.h" @@ -104,6 +105,8 @@ GameEventFlags _game_events_overall; time_t _game_load_time; +SimpleChecksum64 _state_checksum; + /** * Error handling for fatal user errors. * @param s the string to print. @@ -404,6 +407,7 @@ static void ShutdownGame() */ static void LoadIntroGame(bool load_newgrfs = true) { + UnshowCriticalError(); Window *v; FOR_ALL_WINDOWS_FROM_FRONT(v) delete v; @@ -1636,6 +1640,12 @@ void CheckCaches(bool force_check, std::function log) if (!CargoPacket::ValidateDeferredCargoPayments()) CCLOG("Cargo packets deferred payments validation failed"); + if (_order_destination_refcount_map_valid) { + btree::btree_map saved_order_destination_refcount_map = std::move(_order_destination_refcount_map); + IntialiseOrderDestinationRefcountMap(); + if (saved_order_destination_refcount_map != _order_destination_refcount_map) CCLOG("Order destination refcount map mismatch"); + } + #undef CCLOG } @@ -1745,6 +1755,11 @@ void StateGameLoop() CallWindowGameTickEvent(); NewsLoop(); cur_company.Restore(); + + Company *c; + FOR_ALL_COMPANIES(c) { + UpdateStateChecksum(c->money); + } } assert(IsLocalCompany()); diff --git a/src/order_cmd.cpp b/src/order_cmd.cpp index b371eba52c..86a533452e 100644 --- a/src/order_cmd.cpp +++ b/src/order_cmd.cpp @@ -63,6 +63,7 @@ void IntialiseOrderDestinationRefcountMap() ClearOrderDestinationRefcountMap(); const Vehicle *v; FOR_ALL_VEHICLES(v) { + if (v != v->FirstShared()) continue; const Order *order; FOR_VEHICLE_ORDERS(v, order) { if (order->IsType(OT_GOTO_STATION) || order->IsType(OT_GOTO_WAYPOINT) || order->IsType(OT_IMPLICIT)) { diff --git a/src/os/macosx/crashlog_osx.cpp b/src/os/macosx/crashlog_osx.cpp index 329f7c50a1..9a420ad16e 100644 --- a/src/os/macosx/crashlog_osx.cpp +++ b/src/os/macosx/crashlog_osx.cpp @@ -516,8 +516,8 @@ void CDECL HandleCrash(int signum, siginfo_t *si, void *context) } } -/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out) +/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) { CrashLogOSX log(CrashLogOSX::DesyncTag{}); - log.MakeDesyncCrashLog(log_in, log_out); + log.MakeDesyncCrashLog(log_in, log_out, info); } diff --git a/src/os/unix/crashlog_unix.cpp b/src/os/unix/crashlog_unix.cpp index 7883f74b29..ca1e5de7d0 100644 --- a/src/os/unix/crashlog_unix.cpp +++ b/src/os/unix/crashlog_unix.cpp @@ -620,8 +620,8 @@ static void CDECL HandleCrash(int signum) } } -/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out) +/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) { CrashLogUnix log(CrashLogUnix::DesyncTag{}); - log.MakeDesyncCrashLog(log_in, log_out); + log.MakeDesyncCrashLog(log_in, log_out, info); } diff --git a/src/os/windows/crashlog_win.cpp b/src/os/windows/crashlog_win.cpp index 8f82cb46d0..fcd31e7094 100644 --- a/src/os/windows/crashlog_win.cpp +++ b/src/os/windows/crashlog_win.cpp @@ -612,10 +612,10 @@ static void CDECL CustomAbort(int signal) SetUnhandledExceptionFilter(ExceptionHandler); } -/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out) +/* static */ void CrashLog::DesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) { CrashLogWindows log(nullptr); - log.MakeDesyncCrashLog(log_in, log_out); + log.MakeDesyncCrashLog(log_in, log_out, info); } /* The crash log GUI */ diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 7846f56ac5..ee0a08802f 100644 --- a/src/roadveh_cmd.cpp +++ b/src/roadveh_cmd.cpp @@ -39,6 +39,7 @@ #include "framerate_type.h" #include "scope_info.h" #include "string_func.h" +#include "core/checksum_func.hpp" #include "table/strings.h" @@ -1067,6 +1068,7 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection default: NOT_REACHED(); } + UpdateStateChecksum((((uint64) v->index) << 32) | (path_found << 16) | best_track); v->HandlePathfindingResult(path_found); found_best_track:; @@ -1746,6 +1748,8 @@ Money RoadVehicle::GetRunningCost() const bool RoadVehicle::Tick() { + UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos); + UpdateStateChecksum((((uint64) this->state) << 32) | this->frame); if (this->IsFrontEngine()) { if (!(this->IsRoadVehicleStopped())) this->running_ticks++; return RoadVehController(this); diff --git a/src/saveload/extended_ver_sl.cpp b/src/saveload/extended_ver_sl.cpp index 76531c04fb..bec8580489 100644 --- a/src/saveload/extended_ver_sl.cpp +++ b/src/saveload/extended_ver_sl.cpp @@ -108,6 +108,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = { { XSLFI_GAME_EVENTS, XSCF_NULL, 1, 1, "game_events", nullptr, nullptr, nullptr }, { XSLFI_ROAD_LAYOUT_CHANGE_CTR, XSCF_NULL, 1, 1, "road_layout_change_ctr", nullptr, nullptr, nullptr }, { XSLFI_TOWN_CARGO_MATRIX, XSCF_NULL, 1, 1, "town_cargo_matrix", nullptr, nullptr, nullptr }, + { XSLFI_STATE_CHECKSUM, XSCF_NULL, 1, 1, "state_checksum", nullptr, nullptr, nullptr }, { XSLFI_DEBUG, XSCF_IGNORABLE_ALL, 1, 1, "debug", nullptr, nullptr, "DBGL" }, { XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker }; diff --git a/src/saveload/extended_ver_sl.h b/src/saveload/extended_ver_sl.h index 2011fab96a..a52e0c955a 100644 --- a/src/saveload/extended_ver_sl.h +++ b/src/saveload/extended_ver_sl.h @@ -75,6 +75,7 @@ enum SlXvFeatureIndex { XSLFI_GAME_EVENTS, ///< Game event flags XSLFI_ROAD_LAYOUT_CHANGE_CTR, ///< Road layout change counter XSLFI_TOWN_CARGO_MATRIX, ///< Town cargo matrix savegame format changes + XSLFI_STATE_CHECKSUM, ///< State checksum XSLFI_DEBUG, ///< Debugging info XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit diff --git a/src/saveload/misc_sl.cpp b/src/saveload/misc_sl.cpp index 77167d08d6..a6eee84661 100644 --- a/src/saveload/misc_sl.cpp +++ b/src/saveload/misc_sl.cpp @@ -19,6 +19,7 @@ #include "../core/random_func.hpp" #include "../fios.h" #include "../road_type.h" +#include "../core/checksum_func.hpp" #include "saveload.h" @@ -86,6 +87,7 @@ static const SaveLoadGlobVarList _date_desc[] = { SLE_CONDNULL(2, SL_MIN_VERSION, SLV_120), SLEG_VAR(_random.state[0], SLE_UINT32), SLEG_VAR(_random.state[1], SLE_UINT32), + SLEG_CONDVAR_X(_state_checksum.state, SLE_UINT64, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_STATE_CHECKSUM)), SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10), SLE_CONDNULL(4, SLV_10, SLV_120), SLEG_VAR(_cur_company_tick_index, SLE_FILE_U8 | SLE_VAR_U32), @@ -114,6 +116,7 @@ static const SaveLoadGlobVarList _date_check_desc[] = { SLE_CONDNULL(2, SL_MIN_VERSION, SLV_120), SLE_NULL(4), // _random.state[0] SLE_NULL(4), // _random.state[1] + SLE_CONDNULL_X(8, SL_MIN_VERSION, SL_MAX_VERSION, SlXvFeatureTest(XSLFTO_AND, XSLFI_STATE_CHECKSUM)), // _state_checksum.state SLE_CONDNULL(1, SL_MIN_VERSION, SLV_10), SLE_CONDNULL(4, SLV_10, SLV_120), SLE_NULL(1), // _cur_company_tick_index diff --git a/src/saveload/town_sl.cpp b/src/saveload/town_sl.cpp index b73ed30555..fc3dbe6065 100644 --- a/src/saveload/town_sl.cpp +++ b/src/saveload/town_sl.cpp @@ -336,10 +336,9 @@ static void Load_TOWN() uint arr_len = t->cargo_accepted.area.w / AcceptanceMatrix::GRID * t->cargo_accepted.area.h / AcceptanceMatrix::GRID; t->cargo_accepted.data = MallocT(arr_len); SlArray(t->cargo_accepted.data, arr_len, SLE_UINT64); - - /* Rebuild total cargo acceptance. */ - UpdateTownCargoTotal(t); } + /* Rebuild total cargo acceptance. */ + UpdateTownCargoTotal(t); } } } diff --git a/src/settings.cpp b/src/settings.cpp index 91641cf24a..8699fd70ed 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -810,7 +810,7 @@ void IniSaveWindowSettings(IniFile *ini, const char *grpname, void *desc) */ bool SettingDesc::IsEditable(bool do_command) const { - if (!do_command && !(this->save.conv & SLF_NO_NETWORK_SYNC) && _networking && !_network_server && !(this->desc.flags & SGF_PER_COMPANY)) return false; + if (!do_command && !(this->save.conv & SLF_NO_NETWORK_SYNC) && _networking && !(_network_server || _network_settings_access) && !(this->desc.flags & SGF_PER_COMPANY)) return false; if ((this->desc.flags & SGF_NETWORK_ONLY) && !_networking && _game_mode != GM_MENU) return false; if ((this->desc.flags & SGF_NO_NETWORK) && _networking) return false; if ((this->desc.flags & SGF_NEWGAME_ONLY) && @@ -1264,7 +1264,7 @@ static bool MaxNoAIsChange(int32 i) { if (GetGameSettings().difficulty.max_no_competitors != 0 && AI::GetInfoList()->size() == 0 && - (!_networking || _network_server)) { + (!_networking || (_network_server || _network_settings_access))) { ShowErrorMessage(STR_WARNING_NO_SUITABLE_AI, INVALID_STRING_ID, WL_CRITICAL); } @@ -1499,6 +1499,15 @@ static bool UpdateRconPassword(int32 p1) return true; } +static bool UpdateSettingsPassword(int32 p1) +{ + if (strcmp(_settings_client.network.settings_password, "*") == 0) { + _settings_client.network.settings_password[0] = '\0'; + } + + return true; +} + static bool UpdateClientConfigValues(int32 p1) { if (_network_server) NetworkServerSendConfigUpdate(); @@ -2116,7 +2125,7 @@ bool SetSettingValue(uint index, int32 value, bool force_newgame) } /* send non-company-based settings over the network */ - if (!_networking || (_networking && _network_server)) { + if (!_networking || (_networking && (_network_server || _network_settings_access))) { return DoCommandP(0, index, value, CMD_CHANGE_SETTING); } return false; @@ -2276,7 +2285,7 @@ void IConsoleSetSetting(const char *name, const char *value, bool force_newgame) } if (!success) { - if (_network_server) { + if ((_network_server || _network_settings_access)) { IConsoleError("This command/variable is not available during network games."); } else { IConsoleError("This command/variable is only available to a network server."); diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index c300a3491c..2e0f52aa2c 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -228,7 +228,7 @@ struct GameOptionsWindow : Window { /* You can only change the drive side if you are in the menu or ingame with * no vehicles present. In a networking game only the server can change it */ extern bool RoadVehiclesAreBuilt(); - if ((_game_mode != GM_MENU && RoadVehiclesAreBuilt()) || (_networking && !_network_server)) { + if ((_game_mode != GM_MENU && RoadVehiclesAreBuilt()) || (_networking && !(_network_server || _network_settings_access))) { disabled = ~(1 << this->opt->vehicle.road_side); // disable the other value } diff --git a/src/settings_type.h b/src/settings_type.h index 8d8706d14c..5aff422722 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -303,6 +303,7 @@ struct NetworkSettings { char server_password[NETWORK_PASSWORD_LENGTH]; ///< password for joining this server char rcon_password[NETWORK_PASSWORD_LENGTH]; ///< password for rconsole (server side) char admin_password[NETWORK_PASSWORD_LENGTH]; ///< password for the admin network + char settings_password[NETWORK_PASSWORD_LENGTH]; ///< password for game settings (server side) bool server_advertise; ///< advertise the server to the masterserver uint8 lan_internet; ///< search on the LAN or internet for servers char client_name[NETWORK_CLIENT_NAME_LENGTH]; ///< name of the player (as client) diff --git a/src/ship_cmd.cpp b/src/ship_cmd.cpp index c4e9fd010b..5cb21952e7 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -37,6 +37,7 @@ #include "framerate_type.h" #include "industry.h" #include "industry_map.h" +#include "core/checksum_func.hpp" #include "table/strings.h" @@ -524,6 +525,7 @@ static Track ChooseShipTrack(Ship *v, TileIndex tile, DiagDirection enterdir, Tr default: NOT_REACHED(); } } + UpdateStateChecksum((((uint64) v->index) << 32) | (path_found << 16) | track); v->HandlePathfindingResult(path_found); return track; @@ -972,6 +974,7 @@ reverse_direction: bool Ship::Tick() { + UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos); if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++; ShipController(this); diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 69d0e6187d..6cbdf757ef 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -616,6 +616,13 @@ class NIHTown : public NIHelper { SetDParam(0, t->cargo_accepted_total); b = GetString(b, STR_JUST_CARGO_LIST, lastof(buffer)); print(buffer); + + seprintf(buffer, lastof(buffer), " Nearby stations: %u", (uint) t->stations_near.size()); + print(buffer); + for (const Station *st : t->stations_near) { + seprintf(buffer, lastof(buffer), " %u: %s", st->index, st->GetCachedName()); + print(buffer); + } } }; diff --git a/src/table/settings.ini b/src/table/settings.ini index f5c82485f4..87e96e251c 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -53,6 +53,7 @@ static bool EnableSingleVehSharedOrderGuiChanged(int32 p1); static bool UpdateClientName(int32 p1); static bool UpdateServerPassword(int32 p1); static bool UpdateRconPassword(int32 p1); +static bool UpdateSettingsPassword(int32 p1); static bool UpdateClientConfigValues(int32 p1); static bool CheckSharingRail(int32 p1); static bool CheckSharingRoad(int32 p1); @@ -5093,6 +5094,15 @@ guiflags = SGF_NETWORK_ONLY def = nullptr cat = SC_BASIC +[SDTC_STR] +var = network.settings_password +type = SLE_STRB +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_NETWORK_ONLY +def = nullptr +proc = UpdateSettingsPassword +cat = SC_EXPERT + [SDTC_STR] var = network.default_company_pass type = SLE_STRB diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index aded838763..f56f74d213 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -277,7 +277,7 @@ static CallBackFunction SelectSignTool() static CallBackFunction ToolbarPauseClick(Window *w) { - if (_networking && !_network_server) return CBF_NONE; // only server can pause the game + if (_networking && !(_network_server || _network_settings_access)) return CBF_NONE; // only server can pause the game if (DoCommandP(0, PM_PAUSED_NORMAL, _pause_mode == PM_UNPAUSED, CMD_PAUSE)) { if (_settings_client.sound.confirm) SndPlayFx(SND_15_BEEP); @@ -2061,7 +2061,6 @@ struct MainToolbarWindow : Window { _last_started_action = CBF_NONE; CLRBITS(this->flags, WF_WHITE_BORDER); - this->SetWidgetDisabledState(WID_TN_PAUSE, _networking && !_network_server); // if not server, disable pause button this->SetWidgetDisabledState(WID_TN_FAST_FORWARD, _networking); // if networking, disable fast-forward button PositionMainToolbar(this); DoZoomInOutWindow(ZOOM_NONE, this); @@ -2091,6 +2090,8 @@ struct MainToolbarWindow : Window { this->SetWidgetDisabledState(WID_TN_TRAMS, !CanBuildVehicleInfrastructure(VEH_ROAD, RTT_TRAM)); this->SetWidgetDisabledState(WID_TN_AIR, !CanBuildVehicleInfrastructure(VEH_AIRCRAFT)); + this->SetWidgetDisabledState(WID_TN_PAUSE, _networking && !(_network_server || _network_settings_access)); // if not server, disable pause button + this->DrawWidgets(); } diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index a7396b2a5e..33f95cd349 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -573,7 +573,7 @@ uint32 GetWorldPopulation() * Remove stations from nearby station list if a town is no longer in the catchment area of each. * @param t Town to work on */ -static void RemoveNearbyStations(Town *t) +void RemoveNearbyStations(Town *t) { for (StationList::iterator it = t->stations_near.begin(); it != t->stations_near.end(); /* incremented inside loop */) { const Station *st = *it; @@ -1010,11 +1010,11 @@ void UpdateTownCargoes(Town *t) t->cargo_produced = 0; const TileArea &area = t->cargo_accepted.GetArea(); - if (area.tile == INVALID_TILE) return; - - /* Update acceptance for each grid square. */ - TILE_AREA_LOOP_STEP(tile, area, AcceptanceMatrix::GRID) { - UpdateTownCargoesSingleGridArea(t, tile, false); + if (area.tile != INVALID_TILE) { + /* Update acceptance for each grid square. */ + TILE_AREA_LOOP_STEP(tile, area, AcceptanceMatrix::GRID) { + UpdateTownCargoesSingleGridArea(t, tile, false); + } } /* Update the total acceptance. */ diff --git a/src/town_gui.cpp b/src/town_gui.cpp index 557e7b36dc..33b6bb0730 100644 --- a/src/town_gui.cpp +++ b/src/town_gui.cpp @@ -322,9 +322,6 @@ public: this->flags |= WF_DISABLE_VP_SCROLL; NWidgetViewport *nvp = this->GetWidget(WID_TV_VIEWPORT); nvp->InitializeViewport(this, this->town->xy, ZOOM_LVL_NEWS); - - /* disable renaming town in network games if you are not the server */ - this->SetWidgetDisabledState(WID_TV_CHANGE_NAME, _networking && !_network_server); } ~TownViewWindow() @@ -341,6 +338,7 @@ public: { extern const Town *_viewport_highlight_town; this->SetWidgetLoweredState(WID_TV_CATCHMENT, _viewport_highlight_town == this->town); + this->SetWidgetDisabledState(WID_TV_CHANGE_NAME, _networking && !(_network_server || _network_settings_access)); this->DrawWidgets(); } diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp index b631831acb..f7ccf5ccd5 100644 --- a/src/tracerestrict.cpp +++ b/src/tracerestrict.cpp @@ -870,8 +870,8 @@ void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueTyp break; case TRVT_CARGO_ID: - assert(_sorted_standard_cargo_specs_size > 0); - SetTraceRestrictValue(item, _sorted_cargo_specs[0]->Index()); + assert(_standard_cargo_mask != 0); + SetTraceRestrictValue(item, FindFirstBit64(_standard_cargo_mask)); SetTraceRestrictAuxField(item, 0); break; diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 2a146dc2a6..3a623312da 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -43,6 +43,7 @@ #include "engine_func.h" #include "bridge_signal_map.h" #include "scope_info.h" +#include "core/checksum_func.hpp" #include "table/strings.h" #include "table/train_cmd.h" @@ -1549,6 +1550,7 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u TraceRestrictRemoveVehicleFromAllSlots(src->index); ClrBit(src->flags, VRF_HAVE_SLOT); } + OrderBackup::ClearVehicle(src); } /* We weren't a front engine but are becoming one. So @@ -2175,6 +2177,53 @@ void ReverseTrainDirection(Train *v) /* Clear path reservation in front if train is not stuck. */ if (!HasBit(v->flags, VRF_TRAIN_STUCK)) FreeTrainTrackReservation(v); + std::vector re_reserve_trains; + { + /* Temporarily clear and restore reservations to bidi tunnel/bridge entrances when reversing train inside, + * to avoid outgoing and incoming reservations becoming merged */ + auto find_train_reservations = [&re_reserve_trains, &v](TileIndex tile) { + TrackBits reserved = GetAcrossTunnelBridgeReservationTrackBits(tile); + Track track; + while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) { + Train *res_train = GetTrainForReservation(tile, track); + if (res_train != nullptr && res_train != v) { + FreeTrainTrackReservation(res_train); + re_reserve_trains.push_back(res_train); + } + } + }; + if (IsTunnelBridgeWithSignalSimulation(v->tile) && IsTunnelBridgeSignalSimulationBidirectional(v->tile)) { + find_train_reservations(v->tile); + find_train_reservations(GetOtherTunnelBridgeEnd(v->tile)); + } + Train *last = v->Last(); + if (IsTunnelBridgeWithSignalSimulation(last->tile) && IsTunnelBridgeSignalSimulationBidirectional(last->tile)) { + find_train_reservations(last->tile); + find_train_reservations(GetOtherTunnelBridgeEnd(last->tile)); + } + } + + if ((v->track & TRACK_BIT_WORMHOLE) && IsTunnelBridgeWithSignalSimulation(v->tile)) { + /* Clear exit tile reservation if train was on approach to exit and had reserved it */ + Axis axis = DiagDirToAxis(GetTunnelBridgeDirection(v->tile)); + DiagDirection axial_dir = DirToDiagDirAlongAxis(v->direction, axis); + TileIndex next_tile = TileVirtXY(v->x_pos, v->y_pos) + TileOffsByDiagDir(axial_dir); + if (next_tile == v->tile || next_tile == GetOtherTunnelBridgeEnd(v->tile)) { + Trackdir exit_td = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(next_tile)), ReverseDiagDir(GetTunnelBridgeDirection(next_tile))); + CFollowTrackRail ft(GetTileOwner(next_tile), GetRailTypeInfo(v->railtype)->compatible_railtypes); + if (ft.Follow(next_tile, exit_td)) { + TrackdirBits reserved = ft.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedTrackbits(ft.m_new_tile)); + if (reserved == TRACKDIR_BIT_NONE) { + UnreserveAcrossRailTunnelBridge(next_tile); + MarkTileDirtyByTile(next_tile, ZOOM_LVL_DRAW_MAP); + } + } else { + UnreserveAcrossRailTunnelBridge(next_tile); + MarkTileDirtyByTile(next_tile, ZOOM_LVL_DRAW_MAP); + } + } + } + /* Check if we were approaching a rail/road-crossing */ TileIndex crossing = TrainApproachingCrossingTile(v); @@ -2214,6 +2263,10 @@ void ReverseTrainDirection(Train *v) crossing = TrainApproachingCrossingTile(v); if (crossing != INVALID_TILE) MaybeBarCrossingWithSound(crossing); + for (uint i = 0; i < re_reserve_trains.size(); ++i) { + TryPathReserve(re_reserve_trains[i], true); + } + /* If we are inside a depot after reversing, don't bother with path reserving. */ if (v->track == TRACK_BIT_DEPOT) { /* Can't be stuck here as inside a depot is always a safe tile. */ @@ -2724,6 +2777,9 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_t /* Don't free reservation if it's not ours. */ if (TracksOverlap(GetReservedTrackbits(tile) | TrackToTrackBits(TrackdirToTrack(td)))) return; + /* Do not attempt to unreserve out of a signalled tunnel/bridge entrance, as this would unreserve the reservations of another train coming in */ + if (IsTunnelBridgeWithSignalSimulation(tile) && TrackdirExitsTunnelBridge(tile, td) && IsTunnelBridgeSignalSimulationEntranceOnly(tile)) return; + CFollowTrackRail ft(v, GetRailTypeInfo(v->railtype)->compatible_railtypes); while (ft.Follow(tile, td)) { tile = ft.m_new_tile; @@ -3127,6 +3183,7 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir, TileIndex new_tile = res_dest.tile; Track next_track = DoTrainPathfind(v, new_tile, dest_enterdir, tracks, path_found, do_track_reservation, &res_dest); + UpdateStateChecksum((((uint64) v->index) << 32) | (path_found << 16) | next_track); if (new_tile == tile) best_track = next_track; v->HandlePathfindingResult(path_found); } @@ -4217,7 +4274,10 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse) } if (v->Next() == nullptr) { if (v->tunnel_bridge_signal_num > 0 && distance == (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals) - TILE_SIZE) HandleSignalBehindTrain(v, v->tunnel_bridge_signal_num - 2); - if (old_tile == v->tile) { + DiagDirection tunnel_bridge_dir = GetTunnelBridgeDirection(v->tile); + Axis axis = DiagDirToAxis(tunnel_bridge_dir); + DiagDirection axial_dir = DirToDiagDirAlongAxis(v->direction, axis); + if (old_tile == ((axial_dir == tunnel_bridge_dir) ? v->tile : GetOtherTunnelBridgeEnd(v->tile))) { /* We left ramp into wormhole. */ v->x_pos = gp.x; v->y_pos = gp.y; @@ -4971,6 +5031,7 @@ Money Train::GetRunningCost() const */ bool Train::Tick() { + UpdateStateChecksum((((uint64) this->x_pos) << 32) | (this->y_pos << 16) | this->track ); if (this->IsFrontEngine()) { if (!(this->vehstatus & VS_STOPPED) || this->cur_speed > 0) this->running_ticks++; diff --git a/src/tunnelbridge_map.h b/src/tunnelbridge_map.h index 4b2202dc10..775c2f307b 100644 --- a/src/tunnelbridge_map.h +++ b/src/tunnelbridge_map.h @@ -372,6 +372,18 @@ static inline bool IsTunnelBridgeSignalSimulationEntrance(TileIndex t) return HasBit(_m[t].m5, 5); } +/** + * Is this a tunnel/bridge entrance tile with signal only? + * @param t the tile that might be a tunnel/bridge. + * @pre IsTileType(t, MP_TUNNELBRIDGE) + * @return true if and only if this tile is a tunnel/bridge entrance only. + */ +static inline bool IsTunnelBridgeSignalSimulationEntranceOnly(TileIndex t) +{ + assert_tile(IsTileType(t, MP_TUNNELBRIDGE), t); + return HasBit(_m[t].m5, 5) && !HasBit(_m[t].m5, 6); +} + /** * Is this a tunnel/bridge exit? * @param t the tile that might be a tunnel/bridge.