From e28da50c4f4a0408731293519a56ad65e6d426a2 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 30 Jul 2019 19:09:08 +0100 Subject: [PATCH 01/28] Fix order destination refcount accounting for shared orders --- src/order_cmd.cpp | 1 + 1 file changed, 1 insertion(+) 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)) { From f5badfefd1c12baa9816facff79fff4ced05dee1 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 30 Jul 2019 19:09:22 +0100 Subject: [PATCH 02/28] Check order destination refcount maps in CheckCaches --- src/openttd.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/openttd.cpp b/src/openttd.cpp index 0d8cabc2ca..88a100ae5b 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -1634,6 +1634,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 } From bc0de9b6ef02b4d76835421c5bb01332b83d26c1 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 1 Aug 2019 23:05:31 +0100 Subject: [PATCH 03/28] Rebuild total cargo acceptance on load even for empty acceptance area --- src/saveload/town_sl.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) 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); } } } From 385960af4e58cb0c0a51cfd9b2f4faa3fe689561 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 5 Aug 2019 18:21:14 +0100 Subject: [PATCH 04/28] Network: Decrease sync frame period when desync occurs --- src/network/network_server.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 9da604ab84..1265bb6cf1 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -1173,6 +1173,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p if (errorno == NETWORK_ERROR_DESYNC) { CrashLog::DesyncCrashLog(&(this->desync_log), nullptr); + // 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); } From 6d2efd0837b7d2da1ca9c17e8447808c23403a2e Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 5 Aug 2019 19:12:01 +0100 Subject: [PATCH 05/28] Crashlog: Log pathfinders and day length factor --- src/crashlog.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/crashlog.cpp b/src/crashlog.cpp index abf13f5383..1ade5a5d42 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))); @@ -449,7 +458,7 @@ char *CrashLog::FillDesyncCrashLog(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))); From c941d2d2a6ec55043fdda9371ba038cbec01c9e7 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 5 Aug 2019 19:14:06 +0100 Subject: [PATCH 06/28] Desync: Change sync event logging to desync debug level 2 --- src/network/network.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/network.cpp b/src/network/network.cpp index 8c7f4e6ce9..e705d74ba5 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -893,7 +893,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; } } From a1d36b02f0e770c024a613289ac610190bcb0a38 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 5 Aug 2019 19:16:51 +0100 Subject: [PATCH 07/28] Network: Add state checksum which is check in network sync --- source.list | 1 + src/aircraft_cmd.cpp | 2 ++ src/core/checksum_func.hpp | 33 ++++++++++++++++++++++++++++++++ src/core/random_func.hpp | 2 ++ src/disaster_vehicle.cpp | 2 ++ src/effectvehicle.cpp | 2 ++ src/network/network.cpp | 3 +++ src/network/network_client.cpp | 7 +++++-- src/network/network_internal.h | 1 + src/network/network_server.cpp | 2 ++ src/openttd.cpp | 8 ++++++++ src/roadveh_cmd.cpp | 4 ++++ src/saveload/extended_ver_sl.cpp | 1 + src/saveload/extended_ver_sl.h | 1 + src/saveload/misc_sl.cpp | 3 +++ src/ship_cmd.cpp | 3 +++ src/train_cmd.cpp | 3 +++ 17 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 src/core/checksum_func.hpp diff --git a/source.list b/source.list index 2dc1659aec..a530151757 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/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/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/network.cpp b/src/network/network.cpp index e705d74ba5..43e1922a3d 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" @@ -74,6 +75,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? @@ -1028,6 +1030,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_client.cpp b/src/network/network_client.cpp index f7d779b9d7..dc03be2aff 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -32,6 +32,7 @@ #include "../core/backup_type.hpp" #include "../thread.h" #include "../crashlog.h" +#include "../core/checksum_func.hpp" #include "table/strings.h" @@ -282,9 +283,9 @@ 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 NetworkError(STR_NETWORK_ERROR_DESYNC); DEBUG(desync, 1, "sync_err: date{%08x; %02x; %02x}", _date, _date_fract, _tick_skip_counter); @@ -945,6 +946,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. */ @@ -972,6 +974,7 @@ 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; } 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 1265bb6cf1..0ff9f53e95 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -689,6 +689,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 +712,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; } diff --git a/src/openttd.cpp b/src/openttd.cpp index 88a100ae5b..2be27d8e82 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -77,6 +77,7 @@ #include "string_func_extra.h" #include "industry.h" #include "cargopacket.h" +#include "core/checksum_func.hpp" #include "linkgraph/linkgraphschedule.h" #include "tracerestrict.h" @@ -103,6 +104,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. @@ -1749,6 +1752,11 @@ void StateGameLoop() CallWindowGameTickEvent(); NewsLoop(); cur_company.Restore(); + + Company *c; + FOR_ALL_COMPANIES(c) { + UpdateStateChecksum(c->money); + } } assert(IsLocalCompany()); diff --git a/src/roadveh_cmd.cpp b/src/roadveh_cmd.cpp index 746b5786ce..d26e5217ca 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" @@ -1061,6 +1062,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:; @@ -1726,6 +1728,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 afdab325fb..377f507635 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/ship_cmd.cpp b/src/ship_cmd.cpp index 2ecde98843..45c33414e3 100644 --- a/src/ship_cmd.cpp +++ b/src/ship_cmd.cpp @@ -36,6 +36,7 @@ #include "tunnelbridge_map.h" #include "zoom_func.h" #include "framerate_type.h" +#include "core/checksum_func.hpp" #include "table/strings.h" @@ -523,6 +524,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; @@ -948,6 +950,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/train_cmd.cpp b/src/train_cmd.cpp index 2a146dc2a6..071d78acd3 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" @@ -3127,6 +3128,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); } @@ -4971,6 +4973,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++; From 0aa366a24bb12a9e5826085d912bed09730e85a7 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 5 Aug 2019 20:56:42 +0100 Subject: [PATCH 08/28] Desync: Log seed and state checksum on desync --- src/network/network_client.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index dc03be2aff..f35c13bc06 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -288,7 +288,8 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) if (_sync_seed_1 != _random.state[0] || _sync_state_checksum != _state_checksum.state) { #endif 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; From 70ef0f562b6544d48f24864aa5b3ffb91df43a0a Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sun, 18 Aug 2019 14:40:24 +0100 Subject: [PATCH 09/28] Fix train reverse inside signalled tunnel/bridge entrance clearing reservation of incoming train --- src/train_cmd.cpp | 3 +++ src/tunnelbridge_map.h | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 071d78acd3..625e5b6174 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2725,6 +2725,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; 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. From 5bc9c8ed6e40da5712ec27ed206e7aca36620ac5 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sun, 18 Aug 2019 16:52:57 +0100 Subject: [PATCH 10/28] Unreserve signalled tunnel/bridge head when entering from opposite to v->tile --- src/train_cmd.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 625e5b6174..ee6a1a0ffc 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -4222,7 +4222,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; From dae041601d505ebbf431d6291eb442c046bde235 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sun, 18 Aug 2019 16:54:00 +0100 Subject: [PATCH 11/28] Unreserve signalled tunnel/bridge head when reversing on approach to exit --- src/train_cmd.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index ee6a1a0ffc..0a5b3f8c10 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2176,6 +2176,27 @@ void ReverseTrainDirection(Train *v) /* Clear path reservation in front if train is not stuck. */ if (!HasBit(v->flags, VRF_TRAIN_STUCK)) FreeTrainTrackReservation(v); + 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); From d0ee8971357e4b71e61e56d79fc55c453a2dbfbf Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sun, 18 Aug 2019 17:05:44 +0100 Subject: [PATCH 12/28] Unreserve and re-reserve reservations to bidi bridge/tunnel entrances when reversing train inside --- src/train_cmd.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 0a5b3f8c10..0dbbbc616a 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -2176,6 +2176,32 @@ 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)); @@ -2236,6 +2262,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. */ From c8a37d8292b5ae284edb662b1ef525bae6b5215f Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 19 Aug 2019 20:01:49 +0100 Subject: [PATCH 13/28] Add 64 bit FindFirstBit function --- src/core/bitmath_func.cpp | 7 +++++++ src/core/bitmath_func.hpp | 7 +++++++ 2 files changed, 14 insertions(+) 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..5bbb4ca016 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 */ From ccdc75e2bf9ceccf84f178196d028a364be72a40 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 19 Aug 2019 20:02:24 +0100 Subject: [PATCH 14/28] Tracerestrict: Fix non-deterministic use of _sorted_cargo_specs --- src/core/bitmath_func.cpp | 7 +++++++ src/tracerestrict.cpp | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/bitmath_func.cpp b/src/core/bitmath_func.cpp index 7763227315..5f3484f964 100644 --- a/src/core/bitmath_func.cpp +++ b/src/core/bitmath_func.cpp @@ -53,6 +53,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; +} + /** * Search the last set bit in a 64 bit variable. * diff --git a/src/tracerestrict.cpp b/src/tracerestrict.cpp index 86512e6141..9aa1639e35 100644 --- a/src/tracerestrict.cpp +++ b/src/tracerestrict.cpp @@ -630,8 +630,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; From a8786632f748cecad5f1357206727a7ec6bc0a7a Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 19 Aug 2019 20:15:05 +0100 Subject: [PATCH 15/28] Always call UpdateTownCargoTotal in UpdateTownCargoes --- src/town_cmd.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/town_cmd.cpp b/src/town_cmd.cpp index 99bb0aacfc..eecf047869 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -1009,11 +1009,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. */ From 4b9a4ae6953c29fb4fb46a048ce9736f92db3e25 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Mon, 19 Aug 2019 20:25:53 +0100 Subject: [PATCH 16/28] Use consistent casts/types for byte swap functions --- src/core/bitmath_func.hpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/core/bitmath_func.hpp b/src/core/bitmath_func.hpp index 5bbb4ca016..90688d0c05 100644 --- a/src/core/bitmath_func.hpp +++ b/src/core/bitmath_func.hpp @@ -402,14 +402,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. @@ -437,7 +437,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__ */ @@ -450,7 +450,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__ */ From 515187058f8339c70fc36e9ef1938099b5e6ff3e Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 20 Aug 2019 01:45:35 +0100 Subject: [PATCH 17/28] Add nearby stations to town debug window --- src/table/newgrf_debug_data.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/table/newgrf_debug_data.h b/src/table/newgrf_debug_data.h index 7a75acd701..72415f46d5 100644 --- a/src/table/newgrf_debug_data.h +++ b/src/table/newgrf_debug_data.h @@ -615,6 +615,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); + } } }; From 5155afee9bf44933f7794208c170170272d48f22 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 20 Aug 2019 01:47:07 +0100 Subject: [PATCH 18/28] Ensure nearby stations list updated in CBID_HOUSE_DESTRUCTION callback --- src/newgrf_house.cpp | 7 +++++-- src/town_cmd.cpp | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) 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/town_cmd.cpp b/src/town_cmd.cpp index eecf047869..b65bbed84f 100644 --- a/src/town_cmd.cpp +++ b/src/town_cmd.cpp @@ -572,7 +572,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; From 0299eaa45f22ba80d10be4d18845e7f8fb4629c2 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 20 Aug 2019 19:26:19 +0100 Subject: [PATCH 19/28] Clear order backup clone when train head is moved to non-head position --- src/train_cmd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/train_cmd.cpp b/src/train_cmd.cpp index 0dbbbc616a..3a623312da 100644 --- a/src/train_cmd.cpp +++ b/src/train_cmd.cpp @@ -1550,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 From 17c054a650e913b328946212ff7f118ae059b77b Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 20 Aug 2019 20:04:34 +0100 Subject: [PATCH 20/28] Use password hash for network server and rcon passwords --- src/network/network_client.cpp | 28 +++++++++++++++++++--------- src/network/network_server.cpp | 10 ++++++++-- src/network/network_server.h | 2 ++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index f35c13bc06..67d33abb59 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -325,8 +325,12 @@ 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 char _password_server_id[NETWORK_SERVER_ID_LENGTH]; /** Maximum number of companies of the currently joined server. */ @@ -394,7 +398,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; } @@ -406,7 +410,7 @@ 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; } @@ -504,7 +508,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; } @@ -541,7 +545,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; @@ -556,7 +560,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; } @@ -773,6 +777,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); @@ -788,7 +796,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; @@ -810,7 +818,9 @@ 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(); p->Recv_string(_password_server_id, sizeof(_password_server_id)); /* Start receiving the map */ diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 0ff9f53e95..1297a9c536 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -216,6 +216,8 @@ 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(); /* 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 @@ -492,6 +494,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 +535,8 @@ 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_string(_settings_client.network.network_id); this->SendPacket(p); @@ -972,7 +978,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); } @@ -1481,7 +1487,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; } diff --git a/src/network/network_server.h b/src/network/network_server.h index 89de84d06b..2ea4441bd4 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -72,6 +72,8 @@ 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 struct PacketWriter *savegame; ///< Writer used to write the savegame. NetworkAddress client_address; ///< IP-address of the client (so he can be banned) From ec892879f469f5fefb572389745525f459819235 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 20 Aug 2019 20:42:17 +0100 Subject: [PATCH 21/28] Add passworded mechanism to change server game settings from client --- src/build_vehicle_gui.cpp | 6 ++--- src/console_cmds.cpp | 18 +++++++++++++++ src/network/core/tcp_game.cpp | 4 ++++ src/network/core/tcp_game.h | 17 ++++++++++++++ src/network/network.cpp | 1 + src/network/network.h | 1 + src/network/network_client.cpp | 37 ++++++++++++++++++++++++++++++ src/network/network_client.h | 2 ++ src/network/network_func.h | 1 + src/network/network_server.cpp | 41 +++++++++++++++++++++++++++++++--- src/network/network_server.h | 4 ++++ src/settings.cpp | 17 ++++++++++---- src/settings_gui.cpp | 2 +- src/settings_type.h | 1 + src/table/settings.ini | 10 +++++++++ src/toolbar_gui.cpp | 5 +++-- src/town_gui.cpp | 4 +--- 17 files changed, 155 insertions(+), 16 deletions(-) diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 4d3b920909..72d3199aa3 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1093,9 +1093,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; @@ -1564,6 +1561,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/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/network/core/tcp_game.cpp b/src/network/core/tcp_game.cpp index ef3a382219..fe7bce467c 100644 --- a/src/network/core/tcp_game.cpp +++ b/src/network/core/tcp_game.cpp @@ -79,6 +79,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); @@ -166,6 +168,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); } diff --git a/src/network/core/tcp_game.h b/src/network/core/tcp_game.h index 03eb5ded44..4ae96edb1c 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. @@ -262,6 +264,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. diff --git a/src/network/network.cpp b/src/network/network.cpp index 43e1922a3d..dbd45e4d0a 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -57,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. 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 67d33abb59..a031fb2f37 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -159,6 +159,7 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler() { assert(ClientNetworkGameSocketHandler::my_client == this); ClientNetworkGameSocketHandler::my_client = nullptr; + _network_settings_access = false; delete this->savegame; } @@ -331,6 +332,8 @@ 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. */ @@ -415,6 +418,18 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const char 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; +} + /** Request the map from the server. */ NetworkRecvStatus ClientNetworkGameSocketHandler::SendGetMap() { @@ -821,6 +836,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet _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 */ @@ -1209,6 +1225,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? */ @@ -1263,6 +1290,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..c173408619 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -49,6 +49,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; @@ -92,6 +93,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); 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_server.cpp b/src/network/network_server.cpp index 1297a9c536..e1e8cd750a 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -218,6 +218,7 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo 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 @@ -537,6 +538,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome() 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); @@ -860,6 +862,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 @@ -1014,6 +1025,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; @@ -1108,12 +1142,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); } @@ -1123,7 +1157,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); diff --git a/src/network/network_server.h b/src/network/network_server.h index 2ea4441bd4..f73232f9a6 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; @@ -74,6 +75,8 @@ public: 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) @@ -104,6 +107,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/settings.cpp b/src/settings.cpp index 9d842f6568..9a9561154f 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -806,7 +806,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) && @@ -1260,7 +1260,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); } @@ -1495,6 +1495,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(); @@ -2112,7 +2121,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; @@ -2272,7 +2281,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 9435a1b5c3..765952d28d 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 7f4dfdd0df..f8181a461f 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/table/settings.ini b/src/table/settings.ini index 5dcd56d638..e5b49c429d 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); @@ -5077,6 +5078,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 457962388e..1eb818f079 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -275,7 +275,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); @@ -2013,7 +2013,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); @@ -2041,6 +2040,8 @@ struct MainToolbarWindow : Window { this->SetWidgetDisabledState(WID_TN_RAILS, !CanBuildVehicleInfrastructure(VEH_TRAIN)); 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_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(); } From 459a49cb24277b50989d1b20b6ba6fa8d510c964 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 20 Aug 2019 20:47:17 +0100 Subject: [PATCH 22/28] Add flags for mismatch type to client desync log --- src/crashlog.cpp | 16 +++++++++++++--- src/crashlog.h | 20 +++++++++++++++++--- src/network/network_client.cpp | 10 +++++++++- src/network/network_server.cpp | 2 +- src/os/macosx/crashlog_osx.cpp | 4 ++-- src/os/unix/crashlog_unix.cpp | 4 ++-- src/os/windows/crashlog_win.cpp | 4 ++-- 7 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/crashlog.cpp b/src/crashlog.cpp index 1ade5a5d42..fc692a7a37 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -449,12 +449,22 @@ 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); @@ -627,7 +637,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]; @@ -641,7 +651,7 @@ 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_in && !log_in->empty()) { b = strecpy(b, "\n", lastof(buffer), true); diff --git a/src/crashlog.h b/src/crashlog.h index f44000baee..eb25607fad 100644 --- a/src/crashlog.h +++ b/src/crashlog.h @@ -12,8 +12,22 @@ #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; +}; +DECLARE_ENUM_AS_BIT_SET(DesyncExtraInfo::Flags) + /** * Helper class for creating crash logs. */ @@ -113,7 +127,7 @@ public: virtual ~CrashLog() {} char *FillCrashLog(char *buffer, const char *last) const; - char *FillDesyncCrashLog(char *buffer, const char *last) 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") const; /** @@ -130,7 +144,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 +154,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/network/network_client.cpp b/src/network/network_client.cpp index a031fb2f37..e9f2b71617 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -288,13 +288,21 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) #else 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} {%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); + CrashLog::DesyncCrashLog(nullptr, &desync_log, info); my_client->SendDesyncLog(desync_log); my_client->ClientError(NETWORK_RECV_STATUS_DESYNC); return false; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index e1e8cd750a..0a796ba33d 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -1214,7 +1214,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p NetworkAdminClientError(this->client_id, errorno); if (errorno == NETWORK_ERROR_DESYNC) { - CrashLog::DesyncCrashLog(&(this->desync_log), nullptr); + CrashLog::DesyncCrashLog(&(this->desync_log), nullptr, DesyncExtraInfo{}); // decrease the sync frequency for this point onwards _settings_client.network.sync_freq = min(_settings_client.network.sync_freq, 16); 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 */ From 6ce7f22fe6e50b8c23061054238544ef27ec3e13 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Wed, 21 Aug 2019 19:15:02 +0100 Subject: [PATCH 23/28] Send server desync log to client --- src/crashlog.cpp | 14 +++++++----- src/crashlog.h | 3 ++- src/network/core/tcp_game.cpp | 4 ++++ src/network/core/tcp_game.h | 3 +++ src/network/network_client.cpp | 32 +++++++++++++++++++++++++++ src/network/network_client.h | 5 +++++ src/network/network_server.cpp | 40 +++++++++++++++++++++++++++++----- src/network/network_server.h | 2 ++ 8 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/crashlog.cpp b/src/crashlog.cpp index fc692a7a37..b95a26ab1c 100644 --- a/src/crashlog.cpp +++ b/src/crashlog.cpp @@ -509,7 +509,7 @@ char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last, const DesyncE * @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); @@ -519,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; } @@ -653,14 +657,14 @@ bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_ou printf("Desync encountered (%s), generating desync log...\n", mode); 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 eb25607fad..3d9c5c26eb 100644 --- a/src/crashlog.h +++ b/src/crashlog.h @@ -25,6 +25,7 @@ struct DesyncExtraInfo { }; Flags flags = DEIF_NONE; + FILE **log_file = nullptr; ///< save unclosed log file handle here }; DECLARE_ENUM_AS_BIT_SET(DesyncExtraInfo::Flags) @@ -128,7 +129,7 @@ public: char *FillCrashLog(char *buffer, const char *last) 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") 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. diff --git a/src/network/core/tcp_game.cpp b/src/network/core/tcp_game.cpp index fe7bce467c..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 @@ -102,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); @@ -191,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 4ae96edb1c..2fcc503912 100644 --- a/src/network/core/tcp_game.h +++ b/src/network/core/tcp_game.h @@ -124,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) }; @@ -160,6 +161,7 @@ private: NetworkClientInfo *info; ///< Client info related to this socket protected: + bool ignore_close = false; NetworkRecvStatus ReceiveInvalidPacket(PacketGameType type); /** @@ -445,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_client.cpp b/src/network/network_client.cpp index e9f2b71617..94aa83c0b4 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -33,6 +33,7 @@ #include "../thread.h" #include "../crashlog.h" #include "../core/checksum_func.hpp" +#include "../fileio_func.h" #include "table/strings.h" @@ -162,6 +163,15 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler() _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) @@ -175,6 +185,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); @@ -192,6 +203,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; @@ -302,6 +319,7 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res) DEBUG(net, 0, "Sync error detected!"); std::string 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); @@ -968,6 +986,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(); @@ -1002,6 +1021,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(); @@ -1016,6 +1036,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SYNC(Packet *p) 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; @@ -1035,6 +1056,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]; @@ -1092,6 +1114,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) { @@ -1104,6 +1127,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; diff --git a/src/network/network_client.h b/src/network/network_client.h index c173408619..707b2cd2cc 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -32,11 +32,15 @@ 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; + protected: friend void NetworkExecuteLocalCommandQueue(); friend void NetworkClose(bool close_admins); @@ -63,6 +67,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; diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp index 0a796ba33d..ae3f407bdc 100644 --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -323,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(); } @@ -464,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() { @@ -1214,15 +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, DesyncExtraInfo{}); + 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); } @@ -1956,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; @@ -1965,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); @@ -2027,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 f73232f9a6..2e8573d5a9 100644 --- a/src/network/network_server.h +++ b/src/network/network_server.h @@ -64,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). }; @@ -100,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(); From 2e4b1d73d8b1c5ab26de1ba576acb3d20868724c Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 22 Aug 2019 01:40:28 +0100 Subject: [PATCH 24/28] Avoid performing emergency network save more than once --- src/network/network_client.cpp | 9 +++++++++ src/network/network_client.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/src/network/network_client.cpp b/src/network/network_client.cpp index 94aa83c0b4..319f9c5277 100644 --- a/src/network/network_client.cpp +++ b/src/network/network_client.cpp @@ -136,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); @@ -344,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; diff --git a/src/network/network_client.h b/src/network/network_client.h index 707b2cd2cc..91c0b4e6d6 100644 --- a/src/network/network_client.h +++ b/src/network/network_client.h @@ -40,6 +40,7 @@ private: FILE *desync_log_file = nullptr; std::string server_desync_log; + bool emergency_save_done = false; protected: friend void NetworkExecuteLocalCommandQueue(); @@ -111,6 +112,8 @@ public: static void Send(); static bool Receive(); static bool GameLoop(); + + static bool EmergencySavePossible(); }; /** Helper to make the code look somewhat nicer. */ From 942da75dd4792438f2a7cbe0cd5912d1c93c194a Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 22 Aug 2019 02:28:29 +0100 Subject: [PATCH 25/28] Fix commit 4cf60613 suppressing critical error message windows --- src/openttd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/openttd.cpp b/src/openttd.cpp index 2be27d8e82..ec71b057e0 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -406,6 +406,7 @@ static void ShutdownGame() */ static void LoadIntroGame(bool load_newgrfs = true) { + UnshowCriticalError(); Window *v; FOR_ALL_WINDOWS_FROM_FRONT(v) delete v; From f1df81bdd81aceffd3e338b71b7716ba0797468c Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Thu, 22 Aug 2019 07:41:25 +0100 Subject: [PATCH 26/28] Increase company ID field widths in command log dump --- src/command.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/command.cpp b/src/command.cpp index ff95d3777b..07eac8dd9a 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -553,7 +553,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; From 62713ad106c15af7b422161f09e9efe39be37990 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sat, 24 Aug 2019 03:41:08 +0100 Subject: [PATCH 27/28] Fix missing declaration in c8a37d82 --- src/core/bitmath_func.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/bitmath_func.hpp b/src/core/bitmath_func.hpp index 90688d0c05..10a951d36a 100644 --- a/src/core/bitmath_func.hpp +++ b/src/core/bitmath_func.hpp @@ -223,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 From 12be6fd81ff3c826139a95fa8965f85c41a50da3 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Sat, 24 Aug 2019 10:21:17 +0100 Subject: [PATCH 28/28] Version: Committing version data for tag: jgrpp-0.31.4 --- .ottdrev-vc | 4 ++-- README.md | 4 +++- jgrpp-changelog.md | 9 +++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.ottdrev-vc b/.ottdrev-vc index 40abd5e45e..70ea975518 100644 --- a/.ottdrev-vc +++ b/.ottdrev-vc @@ -1,2 +1,2 @@ -jgrpp-0.31.3 20190711 0 eab5db97692c82afc11465887c9a16f698461ed4 1 0 -316e7b69e51a6cd40c766278bf4d4ffb0c833ac5466e13b1abce2f14c68ad124 - +jgrpp-0.31.4 20190824 0 62713ad106c15af7b422161f09e9efe39be37990 1 0 +2c60c3d02c7646220ce330c28881b0b0b0d8d9bb5c573483b24d387cfe8179bb - diff --git a/README.md b/README.md index b22b77f685..7cfe4c18c6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## JGR's Patchpack version 0.31.3 +## JGR's Patchpack version 0.31.4 This is a collection of patches applied to [OpenTTD](http://www.openttd.org/) @@ -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 9776d9be68..5ce89a83f0 100644 --- a/jgrpp-changelog.md +++ b/jgrpp-changelog.md @@ -2,6 +2,15 @@ * * * +### 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.