Merge branch 'jgrpp' into jgrpp-nrt

# Conflicts:
#	.ottdrev-vc
#	README.md
#	jgrpp-changelog.md
#	src/ship_cmd.cpp
pull/104/head
Jonathan G Rennison 5 years ago
commit 04ef407ddf

@ -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).

@ -6,6 +6,15 @@
* Include NotRoadTypes (NRT).
* Bump trunk base from commit 21edf67f89c60351d5a0d84625455aa296b6b950 to commit a52bbb72a8a2cbcbefb0ff91b559f33c34094239.
### v0.31.4 (2019-08-24)
* Fix crash when removing signals from tunnel/bridge with trainless reservation.
* Fix various cases where reversing a train inside a signalled tunnel/bridge handled PBS reservations incorrectly.
* Fix error windows being closed when returning to the main menu.
* Add a password mechanism to change network game settings from a network client.
* Change station tile coverage highlight colour to light blue.
* Change network protocol to send server/join and rcon passwords in hashed form instead of in clear text.
* Fix various possible sources of non-determinism which could potentially cause multiplayer desyncs.
### v0.31.3 (2019-07-13)
* Fix the target order number of conditional order jumps being loaded incorrectly from SpringPP savegames.
* Fix order backups not being restored when using buy and refit.

@ -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

@ -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++;

@ -1082,9 +1082,6 @@ struct BuildVehicleWindow : Window {
this->GetWidget<NWidgetStacked>(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<NWidgetCore>(WID_BV_LIST);
widget->tool_tip = STR_BUY_VEHICLE_TRAIN_LIST_TOOLTIP + type;
@ -1589,6 +1586,9 @@ struct BuildVehicleWindow : Window {
this->SetWidgetsDisabledState(this->sel_engine == INVALID_ENGINE, WID_BV_SHOW_HIDE, WID_BV_BUILD, WID_BV_RENAME, WIDGET_LIST_END);
/* disable renaming engines in network games if you are not the server */
this->SetWidgetDisabledState(WID_BV_RENAME, _networking && !(_network_server || _network_settings_access));
this->DrawWidgets();
if (!this->IsShaded()) {

@ -556,7 +556,7 @@ char *DumpCommandLog(char *buffer, const char *last)
buffer += seprintf(buffer, last, "%c%c%c%c%c%c%c%c | ",
fc(CLEF_SCRIPT, 'a'), fc(CLEF_BINARY, 'b'), fc(CLEF_MY_CMD, 'm'), fc(CLEF_ONLY_SENDING, 's'),
fc(CLEF_ESTIMATE_ONLY, 'e'), fc(CLEF_TEXT, 't'), fc(CLEF_GENERATING_WORLD, 'g'), fc(CLEF_CMD_FAILED, 'f'));
buffer += seprintf(buffer, last, " %7d x %7d, p1: 0x%08X, p2: 0x%08X, cc: %2u, lc: %2u, cmd: 0x%08X (%s)\n",
buffer += seprintf(buffer, last, " %7d x %7d, p1: 0x%08X, p2: 0x%08X, cc: %3u, lc: %3u, cmd: 0x%08X (%s)\n",
TileX(entry.tile), TileY(entry.tile), entry.p1, entry.p2, (uint) entry.current_company, (uint) entry.local_company, entry.cmd, GetCommandName(entry.cmd));
}
return buffer;

@ -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 <password>'");
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 %+");

@ -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
/**

@ -198,6 +198,13 @@ inline uint8 FindFirstBit(uint32 x)
return __builtin_ctz(x);
}
inline uint8 FindFirstBit64(uint64 x)
{
if (x == 0) return 0;
return __builtin_ctzll(x);
}
#else
/** Lookup table to check which bit is set in a 6 bit variable */
@ -216,6 +223,7 @@ extern const uint8 _ffb_64[64];
#define FIND_FIRST_BIT(x) _ffb_64[(x)]
uint8 FindFirstBit(uint32 x);
uint8 FindFirstBit64(uint64 x);
#endif
@ -395,14 +403,14 @@ static inline T ROR(const T x, const uint8 n)
* (since it will use hardware swapping if available).
* Even though they should return uint16 and uint32, we get
* warnings if we don't cast those (why?) */
#define BSWAP64(x) ((uint64)CFSwapInt64(x))
#define BSWAP32(x) ((uint32)CFSwapInt32(x))
#define BSWAP16(x) ((uint16)CFSwapInt16(x))
#define BSWAP64(x) ((uint64)CFSwapInt64((uint64)x))
#define BSWAP32(x) ((uint32)CFSwapInt32((uint32)x))
#define BSWAP16(x) ((uint16)CFSwapInt16((uint16)x))
#elif defined(_MSC_VER)
/* MSVC has intrinsics for swapping, resulting in faster code */
#define BSWAP64(x) (_byteswap_uint64(x))
#define BSWAP32(x) (_byteswap_ulong(x))
#define BSWAP16(x) (_byteswap_ushort(x))
#define BSWAP64(x) ((uint64)_byteswap_uint64((uint64)x))
#define BSWAP32(x) ((uint32)_byteswap_ulong((uint32)x))
#define BSWAP16(x) ((uint16)_byteswap_ushort((uint16)x))
#else
/**
* Perform a 64 bits endianness bitswap on x.
@ -430,7 +438,7 @@ static inline T ROR(const T x, const uint8 n)
{
#if !defined(__ICC) && (defined(__GNUC__) || defined(__clang__))
/* GCC >= 4.3 provides a builtin, resulting in faster code */
return (uint32)__builtin_bswap32((int32)x);
return (uint32)__builtin_bswap32((uint32)x);
#else
return ((x >> 24) & 0xFF) | ((x >> 8) & 0xFF00) | ((x << 8) & 0xFF0000) | ((x << 24) & 0xFF000000);
#endif /* __GNUC__ || __clang__ */
@ -443,7 +451,12 @@ static inline T ROR(const T x, const uint8 n)
*/
static inline uint16 BSWAP16(uint16 x)
{
#if !defined(__ICC) && (defined(__GNUC__) || defined(__clang__))
/* GCC >= 4.3 provides a builtin, resulting in faster code */
return (uint16)__builtin_bswap16((uint16)x);
#else
return (x >> 8) | (x << 8);
#endif /* __GNUC__ || __clang__ */
}
#endif /* __APPLE__ */

@ -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 <http://www.gnu.org/licenses/>.
*/
/** @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 */

@ -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

@ -179,6 +179,13 @@ char *CrashLog::LogOpenTTDVersion(char *buffer, const char *last) const
*/
char *CrashLog::LogConfiguration(char *buffer, const char *last) const
{
auto pathfinder_name = [](uint8 pf) -> const char * {
switch (pf) {
case VPF_NPF: return "NPF";
case VPF_YAPF: return "YAPF";
default: return "-";
};
};
buffer += seprintf(buffer, last,
"Configuration:\n"
" Blitter: %s\n"
@ -189,7 +196,8 @@ char *CrashLog::LogConfiguration(char *buffer, const char *last) const
" Network: %s\n"
" Sound driver: %s\n"
" Sound set: %s (%u)\n"
" Video driver: %s\n\n",
" Video driver: %s\n"
" Pathfinder: %s %s %s\n\n",
BlitterFactory::GetCurrentBlitter() == nullptr ? "none" : BlitterFactory::GetCurrentBlitter()->GetName(),
BaseGraphics::GetUsedSet() == nullptr ? "none" : BaseGraphics::GetUsedSet()->name,
BaseGraphics::GetUsedSet() == nullptr ? UINT32_MAX : BaseGraphics::GetUsedSet()->version,
@ -201,7 +209,8 @@ char *CrashLog::LogConfiguration(char *buffer, const char *last) const
SoundDriver::GetInstance() == nullptr ? "none" : SoundDriver::GetInstance()->GetName(),
BaseSounds::GetUsedSet() == nullptr ? "none" : BaseSounds::GetUsedSet()->name,
BaseSounds::GetUsedSet() == nullptr ? UINT32_MAX : BaseSounds::GetUsedSet()->version,
VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetName()
VideoDriver::GetInstance() == nullptr ? "none" : VideoDriver::GetInstance()->GetName(),
pathfinder_name(_settings_game.pf.pathfinder_for_trains), pathfinder_name(_settings_game.pf.pathfinder_for_roadvehs), pathfinder_name(_settings_game.pf.pathfinder_for_ships)
);
buffer += seprintf(buffer, last,
@ -396,7 +405,7 @@ char *CrashLog::FillCrashLog(char *buffer, const char *last) const
YearMonthDay ymd;
ConvertDateToYMD(_date, &ymd);
buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter);
buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i) (DL: %u)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter, _settings_game.economy.day_length_factor);
if (_game_load_time != 0) {
buffer += seprintf(buffer, last, "Game loaded at: %i-%02i-%02i (%i, %i), %s",
_game_load_cur_date_ymd.year, _game_load_cur_date_ymd.month + 1, _game_load_cur_date_ymd.day, _game_load_date_fract, _game_load_tick_skip_counter, asctime(gmtime(&_game_load_time)));
@ -440,16 +449,26 @@ char *CrashLog::FillCrashLog(char *buffer, const char *last) const
* @param last The last position in the buffer to write to.
* @return the position of the \c '\0' character after the buffer.
*/
char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last) const
char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last, const DesyncExtraInfo &info) const
{
time_t cur_time = time(nullptr);
buffer += seprintf(buffer, last, "*** OpenTTD Multiplayer %s Desync Report ***\n\n", _network_server ? "Server" : "Client");
buffer += seprintf(buffer, last, "Desync at: %s", asctime(gmtime(&cur_time)));
if (!_network_server && info.flags) {
auto flag_check = [&](DesyncExtraInfo::Flags flag, const char *str) {
return info.flags & flag ? str : "";
};
buffer += seprintf(buffer, last, "Flags: %s%s%s%s\n",
flag_check(DesyncExtraInfo::DEIF_RAND1, "R"),
flag_check(DesyncExtraInfo::DEIF_RAND2, "Z"),
flag_check(DesyncExtraInfo::DEIF_STATE, "S"),
flag_check(DesyncExtraInfo::DEIF_DBL_RAND, "D"));
}
YearMonthDay ymd;
ConvertDateToYMD(_date, &ymd);
buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter);
buffer += seprintf(buffer, last, "In game date: %i-%02i-%02i (%i, %i) (DL: %u)\n", _cur_date_ymd.year, _cur_date_ymd.month + 1, _cur_date_ymd.day, _date_fract, _tick_skip_counter, _settings_game.economy.day_length_factor);
if (_game_load_time != 0) {
buffer += seprintf(buffer, last, "Game loaded at: %i-%02i-%02i (%i, %i), %s",
_game_load_cur_date_ymd.year, _game_load_cur_date_ymd.month + 1, _game_load_cur_date_ymd.day, _game_load_date_fract, _game_load_tick_skip_counter, asctime(gmtime(&_game_load_time)));
@ -490,7 +509,7 @@ char *CrashLog::FillDesyncCrashLog(char *buffer, const char *last) const
* @param filename_last The last position in the filename buffer.
* @return true when the crash log was successfully written.
*/
bool CrashLog::WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name) const
bool CrashLog::WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name, FILE **crashlog_file) const
{
seprintf(filename, filename_last, "%s%s.log", _personal_dir, name);
@ -500,7 +519,11 @@ bool CrashLog::WriteCrashLog(const char *buffer, char *filename, const char *fil
size_t len = strlen(buffer);
size_t written = fwrite(buffer, 1, len, file);
FioFCloseFile(file);
if (crashlog_file) {
*crashlog_file = file;
} else {
FioFCloseFile(file);
}
return len == written;
}
@ -618,7 +641,7 @@ bool CrashLog::MakeCrashLog() const
* information like paths to the console.
* @return true when everything is made successfully.
*/
bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_out) const
bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) const
{
char filename[MAX_PATH];
char buffer[65536 * 2];
@ -632,16 +655,16 @@ bool CrashLog::MakeDesyncCrashLog(const std::string *log_in, std::string *log_ou
strftime(name_buffer_date, lastof(name_buffer) - name_buffer_date, "%Y%m%dT%H%M%SZ", gmtime(&cur_time));
printf("Desync encountered (%s), generating desync log...\n", mode);
char *b = this->FillDesyncCrashLog(buffer, lastof(buffer));
char *b = this->FillDesyncCrashLog(buffer, lastof(buffer), info);
if (log_out) log_out->assign(buffer);
if (log_in && !log_in->empty()) {
b = strecpy(b, "\n", lastof(buffer), true);
b = strecpy(b, log_in->c_str(), lastof(buffer), true);
}
if (log_out) log_out->assign(buffer);
bool bret = this->WriteCrashLog(buffer, filename, lastof(filename), name_buffer);
bool bret = this->WriteCrashLog(buffer, filename, lastof(filename), name_buffer, info.log_file);
if (bret) {
printf("Desync log written to %s. Please add this file to any bug reports.\n\n", filename);
} else {

@ -12,8 +12,23 @@
#ifndef CRASHLOG_H
#define CRASHLOG_H
#include "core/enum_type.hpp"
#include <string>
struct DesyncExtraInfo {
enum Flags {
DEIF_NONE = 0, ///< no flags
DEIF_RAND1 = 1 << 0, ///< random 1 mismatch
DEIF_RAND2 = 1 << 1, ///< random 2 mismatch
DEIF_STATE = 1 << 2, ///< state mismatch
DEIF_DBL_RAND = 1 << 3, ///< double-seed sent
};
Flags flags = DEIF_NONE;
FILE **log_file = nullptr; ///< save unclosed log file handle here
};
DECLARE_ENUM_AS_BIT_SET(DesyncExtraInfo::Flags)
/**
* Helper class for creating crash logs.
*/
@ -113,8 +128,8 @@ public:
virtual ~CrashLog() {}
char *FillCrashLog(char *buffer, const char *last) const;
char *FillDesyncCrashLog(char *buffer, const char *last) const;
bool WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name = "crash") const;
char *FillDesyncCrashLog(char *buffer, const char *last, const DesyncExtraInfo &info) const;
bool WriteCrashLog(const char *buffer, char *filename, const char *filename_last, const char *name = "crash", FILE **crashlog_file = nullptr) const;
/**
* Write the (crash) dump to a file.
@ -130,7 +145,7 @@ public:
bool WriteScreenshot(char *filename, const char *filename_last, const char *name = "crash") const;
bool MakeCrashLog() const;
bool MakeDesyncCrashLog(const std::string *log_in, std::string *log_out) const;
bool MakeDesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info) const;
bool MakeCrashSavegameAndScreenshot() const;
/**
@ -140,7 +155,7 @@ public:
*/
static void InitialiseCrashLog();
static void DesyncCrashLog(const std::string *log_in, std::string *log_out);
static void DesyncCrashLog(const std::string *log_in, std::string *log_out, const DesyncExtraInfo &info);
static void SetErrorMessage(const char *message);
static void AfterCrashLogCleanup();

@ -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);
}

@ -18,6 +18,7 @@
#include "animated_tile_func.h"
#include "effectvehicle_func.h"
#include "effectvehicle_base.h"
#include "core/checksum_func.hpp"
#include <algorithm>
@ -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);
}

@ -41,6 +41,8 @@ NetworkGameSocketHandler::NetworkGameSocketHandler(SOCKET s) : info(nullptr), cl
*/
NetworkRecvStatus NetworkGameSocketHandler::CloseConnection(bool error)
{
if (this->ignore_close) return NETWORK_RECV_STATUS_CONN_LOST;
/* Clients drop back to the main menu */
if (!_network_server && _networking) {
extern void ClientNetworkEmergencySave(); // from network_client.cpp
@ -79,6 +81,8 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p)
case PACKET_SERVER_NEED_COMPANY_PASSWORD: return this->Receive_SERVER_NEED_COMPANY_PASSWORD(p);
case PACKET_CLIENT_GAME_PASSWORD: return this->Receive_CLIENT_GAME_PASSWORD(p);
case PACKET_CLIENT_COMPANY_PASSWORD: return this->Receive_CLIENT_COMPANY_PASSWORD(p);
case PACKET_CLIENT_SETTINGS_PASSWORD: return this->Receive_CLIENT_SETTINGS_PASSWORD(p);
case PACKET_SERVER_SETTINGS_ACCESS: return this->Receive_SERVER_SETTINGS_ACCESS(p);
case PACKET_SERVER_WELCOME: return this->Receive_SERVER_WELCOME(p);
case PACKET_CLIENT_GETMAP: return this->Receive_CLIENT_GETMAP(p);
case PACKET_SERVER_WAIT: return this->Receive_SERVER_WAIT(p);
@ -100,6 +104,7 @@ NetworkRecvStatus NetworkGameSocketHandler::HandlePacket(Packet *p)
case PACKET_CLIENT_QUIT: return this->Receive_CLIENT_QUIT(p);
case PACKET_CLIENT_ERROR: return this->Receive_CLIENT_ERROR(p);
case PACKET_CLIENT_DESYNC_LOG: return this->Receive_CLIENT_DESYNC_LOG(p);
case PACKET_SERVER_DESYNC_LOG: return this->Receive_SERVER_DESYNC_LOG(p);
case PACKET_SERVER_QUIT: return this->Receive_SERVER_QUIT(p);
case PACKET_SERVER_ERROR_QUIT: return this->Receive_SERVER_ERROR_QUIT(p);
case PACKET_SERVER_SHUTDOWN: return this->Receive_SERVER_SHUTDOWN(p);
@ -166,6 +171,8 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSWORD(Pa
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_NEED_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GAME_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_COMPANY_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWORD(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_SETTINGS_PASSWORD); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_SETTINGS_ACCESS(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_SETTINGS_ACCESS); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_WELCOME); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_GETMAP); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_WAIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_WAIT); }
@ -187,6 +194,7 @@ NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_SET_NAME(Packet *p) {
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_QUIT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_ERROR); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_CLIENT_DESYNC_LOG(Packet *p) { return this->ReceiveInvalidPacket(PACKET_CLIENT_DESYNC_LOG); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_DESYNC_LOG(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_DESYNC_LOG); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_QUIT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_ERROR_QUIT); }
NetworkRecvStatus NetworkGameSocketHandler::Receive_SERVER_SHUTDOWN(Packet *p) { return this->ReceiveInvalidPacket(PACKET_SERVER_SHUTDOWN); }

@ -63,6 +63,8 @@ enum PacketGameType {
PACKET_CLIENT_GAME_PASSWORD, ///< Clients sends the (hashed) game password.
PACKET_SERVER_NEED_COMPANY_PASSWORD, ///< Server requests the (hashed) company password.
PACKET_CLIENT_COMPANY_PASSWORD, ///< Client sends the (hashed) company password.
PACKET_CLIENT_SETTINGS_PASSWORD, ///< Client sends the (hashed) settings password.
PACKET_SERVER_SETTINGS_ACCESS, ///< Server sends the settings access state.
/* The server welcomes the authenticated client and sends information of other clients. */
PACKET_SERVER_WELCOME, ///< Server welcomes you and gives you your #ClientID.
@ -122,6 +124,7 @@ enum PacketGameType {
PACKET_CLIENT_ERROR, ///< A client reports an error to the server.
PACKET_SERVER_ERROR_QUIT, ///< A server tells that a client has hit an error and did quit.
PACKET_CLIENT_DESYNC_LOG, ///< A client reports a desync log
PACKET_SERVER_DESYNC_LOG, ///< A server reports a desync log
PACKET_END, ///< Must ALWAYS be on the end of this list!! (period)
};
@ -158,6 +161,7 @@ private:
NetworkClientInfo *info; ///< Client info related to this socket
protected:
bool ignore_close = false;
NetworkRecvStatus ReceiveInvalidPacket(PacketGameType type);
/**
@ -262,6 +266,21 @@ protected:
*/
virtual NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet *p);
/**
* Send a password to the server to authorize
* uint8 Password type (see NetworkPasswordType).
* string The password.
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_CLIENT_SETTINGS_PASSWORD(Packet *p);
/**
* Indication to the client that the setting access state has changed
* bool setting access state
* @param p The packet that was just received.
*/
virtual NetworkRecvStatus Receive_SERVER_SETTINGS_ACCESS(Packet *p);
/**
* The client is joined and ready to receive his map:
* uint32 Own client ID.
@ -428,6 +447,7 @@ protected:
*/
virtual NetworkRecvStatus Receive_CLIENT_ERROR(Packet *p);
virtual NetworkRecvStatus Receive_CLIENT_DESYNC_LOG(Packet *p);
virtual NetworkRecvStatus Receive_SERVER_DESYNC_LOG(Packet *p);
/**
* Notification that a client left the game:

@ -35,6 +35,7 @@
#include "../core/pool_func.hpp"
#include "../gfx_func.h"
#include "../error.h"
#include "../core/checksum_func.hpp"
#include "../safeguards.h"
@ -56,6 +57,7 @@ bool _network_server; ///< network-server is active
bool _network_available; ///< is network mode available?
bool _network_dedicated; ///< are we a dedicated server?
bool _is_network_server; ///< Does this client wants to be a network-server?
bool _network_settings_access; ///< Can this client change server settings?
NetworkServerGameInfo _network_game_info; ///< Information about our game.
NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies.
ClientID _network_own_client_id; ///< Our client identifier.
@ -74,6 +76,7 @@ uint32 _sync_seed_1; ///< Seed to compare during sync checks.
#ifdef NETWORK_SEND_DOUBLE_SEED
uint32 _sync_seed_2; ///< Second part of the seed.
#endif
uint64 _sync_state_checksum; ///< State checksum to compare during sync checks.
uint32 _sync_frame; ///< The frame to perform the sync check.
bool _network_first_time; ///< Whether we have finished joining or not.
bool _network_udp_server; ///< Is the UDP server started?
@ -893,7 +896,7 @@ void NetworkGameLoop()
/* We don't want to log multiple times if paused. */
static Date last_log;
if (last_log != _date) {
DEBUG(desync, 1, "sync: date{%08x; %02x; %02x}; %08x; %08x", _date, _date_fract, _tick_skip_counter, _random.state[0], _random.state[1]);
DEBUG(desync, 2, "sync: date{%08x; %02x; %02x}; %08x; %08x", _date, _date_fract, _tick_skip_counter, _random.state[0], _random.state[1]);
last_log = _date;
}
}
@ -1028,6 +1031,7 @@ void NetworkGameLoop()
#ifdef NETWORK_SEND_DOUBLE_SEED
_sync_seed_2 = _random.state[1];
#endif
_sync_state_checksum = _state_checksum.state;
NetworkServer_Tick(send_frame);
} else {

@ -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 */

@ -32,6 +32,8 @@
#include "../core/backup_type.hpp"
#include "../thread.h"
#include "../crashlog.h"
#include "../core/checksum_func.hpp"
#include "../fileio_func.h"
#include "table/strings.h"
@ -134,6 +136,7 @@ void ClientNetworkEmergencySave()
{
if (!_settings_client.gui.autosave_on_network_disconnect) return;
if (!_networking) return;
if (!ClientNetworkGameSocketHandler::EmergencySavePossible()) return;
const char *filename = "netsave.sav";
DEBUG(net, 0, "Client: Performing emergency save (%s)", filename);
@ -158,8 +161,18 @@ ClientNetworkGameSocketHandler::~ClientNetworkGameSocketHandler()
{
assert(ClientNetworkGameSocketHandler::my_client == this);
ClientNetworkGameSocketHandler::my_client = nullptr;
_network_settings_access = false;
delete this->savegame;
if (this->desync_log_file) {
if (!this->server_desync_log.empty()) {
fwrite("\n", 1, 1, this->desync_log_file);
fwrite(this->server_desync_log.data(), 1, this->server_desync_log.size(), this->desync_log_file);
}
FioFCloseFile(this->desync_log_file);
this->desync_log_file = nullptr;
}
}
NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvStatus status)
@ -173,6 +186,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
* that code any more complex or more aware of the validity of the socket.
*/
if (this->sock == INVALID_SOCKET) return status;
if (this->status == STATUS_CLOSING) return status;
DEBUG(net, 1, "Shutting down client connection %d", this->client_id);
@ -190,6 +204,12 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
DEBUG(net, 1, "Shutdown client connection %d", this->client_id);
if (status == NETWORK_RECV_STATUS_DESYNC) {
this->status = STATUS_CLOSING;
this->ignore_close = true;
this->ReceivePackets();
}
delete this->GetInfo();
delete this;
@ -282,16 +302,26 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
if (_sync_frame != 0) {
if (_sync_frame == _frame_counter) {
#ifdef NETWORK_SEND_DOUBLE_SEED
if (_sync_seed_1 != _random.state[0] || _sync_seed_2 != _random.state[1]) {
if (_sync_seed_1 != _random.state[0] || _sync_seed_2 != _random.state[1] || _sync_state_checksum != _state_checksum.state) {
#else
if (_sync_seed_1 != _random.state[0]) {
if (_sync_seed_1 != _random.state[0] || _sync_state_checksum != _state_checksum.state) {
#endif
DesyncExtraInfo info;
if (_sync_seed_1 != _random.state[0]) info.flags |= DesyncExtraInfo::DEIF_RAND1;
#ifdef NETWORK_SEND_DOUBLE_SEED
if (_sync_seed_2 != _random.state[1]) info.flags |= DesyncExtraInfo::DEIF_RAND2;
info.flags |= DesyncExtraInfo::DEIF_DBL_RAND;
#endif
if (_sync_state_checksum != _state_checksum.state) info.flags |= DesyncExtraInfo::DEIF_STATE;
NetworkError(STR_NETWORK_ERROR_DESYNC);
DEBUG(desync, 1, "sync_err: date{%08x; %02x; %02x}", _date, _date_fract, _tick_skip_counter);
DEBUG(desync, 1, "sync_err: date{%08x; %02x; %02x} {%x, " OTTD_PRINTFHEX64 "} != {%x, " OTTD_PRINTFHEX64 "}"
, _date, _date_fract, _tick_skip_counter, _sync_seed_1, _sync_state_checksum, _random.state[0], _state_checksum.state);
DEBUG(net, 0, "Sync error detected!");
std::string desync_log;
CrashLog::DesyncCrashLog(nullptr, &desync_log);
info.log_file = &(my_client->desync_log_file);
CrashLog::DesyncCrashLog(nullptr, &desync_log, info);
my_client->SendDesyncLog(desync_log);
my_client->ClientError(NETWORK_RECV_STATUS_DESYNC);
return false;
@ -315,6 +345,14 @@ void ClientNetworkGameSocketHandler::ClientError(NetworkRecvStatus res)
return true;
}
/* static */ bool ClientNetworkGameSocketHandler::EmergencySavePossible()
{
if (!my_client) return false;
if (my_client->emergency_save_done) return false;
my_client->emergency_save_done = true;
return true;
}
/** Our client's connection. */
ClientNetworkGameSocketHandler * ClientNetworkGameSocketHandler::my_client = nullptr;
@ -323,8 +361,14 @@ ClientNetworkGameSocketHandler * ClientNetworkGameSocketHandler::my_client = nul
static uint32 last_ack_frame;
/** One bit of 'entropy' used to generate a salt for the company passwords. */
static uint32 _password_game_seed;
/** The other bit of 'entropy' used to generate a salt for the company passwords. */
static uint32 _company_password_game_seed;
/** One bit of 'entropy' used to generate a salt for the server passwords. */
static uint32 _server_password_game_seed;
/** One bit of 'entropy' used to generate a salt for the rcon passwords. */
static uint32 _rcon_password_game_seed;
/** One bit of 'entropy' used to generate a salt for the settings passwords. */
static uint32 _settings_password_game_seed;
/** The other bit of 'entropy' used to generate a salt for the company, server, rcon, and settings passwords. */
static char _password_server_id[NETWORK_SERVER_ID_LENGTH];
/** Maximum number of companies of the currently joined server. */
@ -392,7 +436,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendNewGRFsOk()
NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const char *password)
{
Packet *p = new Packet(PACKET_CLIENT_GAME_PASSWORD);
p->Send_string(password);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _server_password_game_seed));
my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
@ -404,7 +448,19 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendGamePassword(const char *p
NetworkRecvStatus ClientNetworkGameSocketHandler::SendCompanyPassword(const char *password)
{
Packet *p = new Packet(PACKET_CLIENT_COMPANY_PASSWORD);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _company_password_game_seed));
my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Set the game password as requested.
* @param password The game password.
*/
NetworkRecvStatus ClientNetworkGameSocketHandler::SendSettingsPassword(const char *password)
{
Packet *p = new Packet(PACKET_CLIENT_SETTINGS_PASSWORD);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _settings_password_game_seed));
my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
@ -502,7 +558,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendSetPassword(const char *pa
{
Packet *p = new Packet(PACKET_CLIENT_SET_PASSWORD);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _company_password_game_seed));
my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
@ -539,7 +595,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendQuit()
NetworkRecvStatus ClientNetworkGameSocketHandler::SendRCon(const char *pass, const char *command)
{
Packet *p = new Packet(PACKET_CLIENT_RCON);
p->Send_string(pass);
p->Send_string(GenerateCompanyPasswordHash(pass, _password_server_id, _rcon_password_game_seed));
p->Send_string(command);
my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
@ -554,7 +610,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::SendMove(CompanyID company, co
{
Packet *p = new Packet(PACKET_CLIENT_MOVE);
p->Send_uint8(company);
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _password_game_seed));
p->Send_string(GenerateCompanyPasswordHash(password, _password_server_id, _company_password_game_seed));
my_client->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
@ -771,6 +827,10 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_GAME_PASSW
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_GAME) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
this->status = STATUS_AUTH_GAME;
_server_password_game_seed = p->Recv_uint32();
p->Recv_string(_password_server_id, sizeof(_password_server_id));
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
const char *password = _network_join_server_password;
if (!StrEmpty(password)) {
return SendGamePassword(password);
@ -786,7 +846,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_NEED_COMPANY_PA
if (this->status < STATUS_JOIN || this->status >= STATUS_AUTH_COMPANY) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
this->status = STATUS_AUTH_COMPANY;
_password_game_seed = p->Recv_uint32();
_company_password_game_seed = p->Recv_uint32();
p->Recv_string(_password_server_id, sizeof(_password_server_id));
if (this->HasClientQuit()) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
@ -808,7 +868,10 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_WELCOME(Packet
_network_own_client_id = (ClientID)p->Recv_uint32();
/* Initialize the password hash salting variables, even if they were previously. */
_password_game_seed = p->Recv_uint32();
_company_password_game_seed = p->Recv_uint32();
_server_password_game_seed = p->Recv_uint32();
_rcon_password_game_seed = p->Recv_uint32();
_settings_password_game_seed = p->Recv_uint32();
p->Recv_string(_password_server_id, sizeof(_password_server_id));
/* Start receiving the map */
@ -932,6 +995,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_MAP_DONE(Packet
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p)
{
if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY;
if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
_frame_counter_server = p->Recv_uint32();
@ -945,6 +1009,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p
#ifdef NETWORK_SEND_DOUBLE_SEED
_sync_seed_2 = p->Recv_uint32();
#endif
_sync_state_checksum = p->Recv_uint64();
}
#endif
/* Receive the token. */
@ -965,6 +1030,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_FRAME(Packet *p
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SYNC(Packet *p)
{
if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY;
if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
_sync_frame = p->Recv_uint32();
@ -972,12 +1038,14 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SYNC(Packet *p)
#ifdef NETWORK_SEND_DOUBLE_SEED
_sync_seed_2 = p->Recv_uint32();
#endif
_sync_state_checksum = p->Recv_uint64();
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet *p)
{
if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY;
if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
CommandPacket cp;
@ -997,6 +1065,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMMAND(Packet
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_CHAT(Packet *p)
{
if (this->status == STATUS_CLOSING) return NETWORK_RECV_STATUS_OKAY;
if (this->status != STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
char name[NETWORK_NAME_LENGTH], msg[NETWORK_CHAT_LENGTH];
@ -1054,6 +1123,7 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Pack
if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
ClientID client_id = (ClientID)p->Recv_uint32();
if (client_id == _network_own_client_id) return NETWORK_RECV_STATUS_OKAY; // do not try to clear our own client info
NetworkClientInfo *ci = NetworkClientInfo::GetByClientID(client_id);
if (ci != nullptr) {
@ -1066,6 +1136,15 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_ERROR_QUIT(Pack
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_DESYNC_LOG(Packet *p)
{
uint size = p->Recv_uint16();
this->server_desync_log.resize(this->server_desync_log.size() + size);
p->Recv_binary(const_cast<char *>(this->server_desync_log.data() + this->server_desync_log.size() - size), size);
DEBUG(net, 2, "Received %u bytes of server desync log", size);
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_QUIT(Packet *p)
{
if (this->status < STATUS_AUTHORIZED) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
@ -1195,6 +1274,17 @@ NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_COMPANY_UPDATE(
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ClientNetworkGameSocketHandler::Receive_SERVER_SETTINGS_ACCESS(Packet *p)
{
if (this->status < STATUS_ACTIVE) return NETWORK_RECV_STATUS_MALFORMED_PACKET;
_network_settings_access = p->Recv_bool();
ReInitAllWindows();
return NETWORK_RECV_STATUS_OKAY;
}
/**
* Check the connection's state, i.e. is the connection still up?
*/
@ -1249,6 +1339,16 @@ void NetworkClientSendRcon(const char *password, const char *command)
MyClient::SendRCon(password, command);
}
/**
* Send settings password.
* @param password The password.
* @param command The command to execute.
*/
void NetworkClientSendSettingsPassword(const char *password)
{
MyClient::SendSettingsPassword(password);
}
/**
* Notify the server of this client wanting to be moved to another company.
* @param company_id id of the company the client wishes to be moved to.

@ -32,11 +32,16 @@ private:
STATUS_MAP_WAIT, ///< The client is waiting as someone else is downloading the map.
STATUS_MAP, ///< The client is downloading the map.
STATUS_ACTIVE, ///< The client is active within in the game.
STATUS_CLOSING, ///< The client connection is in the process of being closed.
STATUS_END, ///< Must ALWAYS be on the end of this list!! (period)
};
ServerStatus status; ///< Status of the connection with the server.
FILE *desync_log_file = nullptr;
std::string server_desync_log;
bool emergency_save_done = false;
protected:
friend void NetworkExecuteLocalCommandQueue();
friend void NetworkClose(bool close_admins);
@ -49,6 +54,7 @@ protected:
NetworkRecvStatus Receive_SERVER_CLIENT_INFO(Packet *p) override;
NetworkRecvStatus Receive_SERVER_NEED_GAME_PASSWORD(Packet *p) override;
NetworkRecvStatus Receive_SERVER_NEED_COMPANY_PASSWORD(Packet *p) override;
NetworkRecvStatus Receive_SERVER_SETTINGS_ACCESS(Packet *p) override;
NetworkRecvStatus Receive_SERVER_WELCOME(Packet *p) override;
NetworkRecvStatus Receive_SERVER_WAIT(Packet *p) override;
NetworkRecvStatus Receive_SERVER_MAP_BEGIN(Packet *p) override;
@ -62,6 +68,7 @@ protected:
NetworkRecvStatus Receive_SERVER_CHAT(Packet *p) override;
NetworkRecvStatus Receive_SERVER_QUIT(Packet *p) override;
NetworkRecvStatus Receive_SERVER_ERROR_QUIT(Packet *p) override;
NetworkRecvStatus Receive_SERVER_DESYNC_LOG(Packet *p) override;
NetworkRecvStatus Receive_SERVER_SHUTDOWN(Packet *p) override;
NetworkRecvStatus Receive_SERVER_NEWGAME(Packet *p) override;
NetworkRecvStatus Receive_SERVER_RCON(Packet *p) override;
@ -92,6 +99,7 @@ public:
static NetworkRecvStatus SendGamePassword(const char *password);
static NetworkRecvStatus SendCompanyPassword(const char *password);
static NetworkRecvStatus SendSettingsPassword(const char *password);
static NetworkRecvStatus SendChat(NetworkAction action, DestType type, int dest, const char *msg, NetworkTextMessageData data);
static NetworkRecvStatus SendSetPassword(const char *password);
@ -104,6 +112,8 @@ public:
static void Send();
static bool Receive();
static bool GameLoop();
static bool EmergencySavePossible();
};
/** Helper to make the code look somewhat nicer. */

@ -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);

@ -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 */

@ -216,6 +216,9 @@ ServerNetworkGameSocketHandler::ServerNetworkGameSocketHandler(SOCKET s) : Netwo
this->status = STATUS_INACTIVE;
this->client_id = _network_client_id++;
this->receive_limit = _settings_client.network.bytes_per_frame_burst;
this->server_hash_bits = InteractiveRandom();
this->rcon_hash_bits = InteractiveRandom();
this->settings_hash_bits = InteractiveRandom();
/* The Socket and Info pools need to be the same in size. After all,
* each Socket will be associated with at most one Info object. As
@ -320,7 +323,14 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::CloseConnection(NetworkRecvSta
NetworkClientSocket *cs;
FOR_ALL_CLIENT_SOCKETS(cs) {
if (cs->writable) {
if (cs->SendPackets() != SPS_CLOSED && cs->status == STATUS_MAP) {
if (cs->status == STATUS_CLOSE_PENDING) {
SendPacketsState send_state = cs->SendPackets(true);
if (send_state == SPS_CLOSED) {
cs->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
} else if (send_state != SPS_PARTLY_SENT && send_state != SPS_NONE_SENT) {
ShutdownSocket(cs->sock, true, false, 2);
}
} else if (cs->SendPackets() != SPS_CLOSED && cs->status == STATUS_MAP) {
/* This client is in the middle of a map-send, call the function for that */
cs->SendMap();
}
@ -461,6 +471,20 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendError(NetworkErrorCode err
return this->CloseConnection(NETWORK_RECV_STATUS_SERVER_ERROR);
}
NetworkRecvStatus ServerNetworkGameSocketHandler::SendDesyncLog(const std::string &log)
{
for (size_t offset = 0; offset < log.size();) {
Packet *p = new Packet(PACKET_SERVER_DESYNC_LOG);
size_t size = min<size_t>(log.size() - offset, SHRT_MAX - 2 - p->size);
p->Send_uint16(size);
p->Send_binary(log.data() + offset, size);
this->SendPacket(p);
offset += size;
}
return NETWORK_RECV_STATUS_OKAY;
}
/** Send the check for the NewGRFs. */
NetworkRecvStatus ServerNetworkGameSocketHandler::SendNewGRFCheck()
{
@ -492,6 +516,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendNeedGamePassword()
this->last_frame = this->last_frame_server = _frame_counter;
Packet *p = new Packet(PACKET_SERVER_NEED_GAME_PASSWORD);
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits);
p->Send_string(_settings_client.network.network_id);
this->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
@ -531,6 +557,9 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendWelcome()
p = new Packet(PACKET_SERVER_WELCOME);
p->Send_uint32(this->client_id);
p->Send_uint32(_settings_game.game_creation.generation_seed);
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->server_hash_bits);
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->rcon_hash_bits);
p->Send_uint32(_settings_game.game_creation.generation_seed ^ this->settings_hash_bits);
p->Send_string(_settings_client.network.network_id);
this->SendPacket(p);
@ -689,6 +718,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendFrame()
#ifdef NETWORK_SEND_DOUBLE_SEED
p->Send_uint32(_sync_seed_2);
#endif
p->Send_uint64(_sync_state_checksum);
#endif
/* If token equals 0, we need to make a new token and send that. */
@ -711,6 +741,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendSync()
#ifdef NETWORK_SEND_DOUBLE_SEED
p->Send_uint32(_sync_seed_2);
#endif
p->Send_uint64(_sync_state_checksum);
this->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
@ -852,6 +883,15 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendConfigUpdate()
return NETWORK_RECV_STATUS_OKAY;
}
NetworkRecvStatus ServerNetworkGameSocketHandler::SendSettingsAccessUpdate(bool ok)
{
Packet *p = new Packet(PACKET_SERVER_SETTINGS_ACCESS);
p->Send_bool(ok);
this->SendPacket(p);
return NETWORK_RECV_STATUS_OKAY;
}
/***********
* Receiving functions
* DEF_SERVER_RECEIVE_COMMAND has parameter: NetworkClientSocket *cs, Packet *p
@ -970,7 +1010,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GAME_PASSWORD(P
/* Check game password. Allow joining if we cleared the password meanwhile */
if (!StrEmpty(_settings_client.network.server_password) &&
strcmp(password, _settings_client.network.server_password) != 0) {
strcmp(password, GenerateCompanyPasswordHash(_settings_client.network.server_password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed ^ this->server_hash_bits)) != 0) {
/* Password is invalid */
return this->SendError(NETWORK_ERROR_WRONG_PASSWORD);
}
@ -1006,6 +1046,29 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMPANY_PASSWOR
return this->SendWelcome();
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_SETTINGS_PASSWORD(Packet *p)
{
if (this->status != STATUS_ACTIVE) {
/* Illegal call, return error and ignore the packet */
return this->SendError(NETWORK_ERROR_NOT_EXPECTED);
}
char password[NETWORK_PASSWORD_LENGTH];
p->Recv_string(password, sizeof(password));
/* Check settings password. Deny if no password is set */
if (StrEmpty(_settings_client.network.settings_password) ||
strcmp(password, GenerateCompanyPasswordHash(_settings_client.network.settings_password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed ^ this->settings_hash_bits)) != 0) {
DEBUG(net, 0, "[settings-ctrl] wrong password from client-id %d", this->client_id);
this->settings_authed = false;
} else {
DEBUG(net, 0, "[settings-ctrl] client-id %d", this->client_id);
this->settings_authed = true;
}
return this->SendSettingsAccessUpdate(this->settings_authed);
}
NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_GETMAP(Packet *p)
{
NetworkClientSocket *new_cs;
@ -1100,12 +1163,12 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet
}
if ((GetCommandFlags(cp.cmd) & CMD_SERVER) && ci->client_id != CLIENT_ID_SERVER) {
if ((GetCommandFlags(cp.cmd) & CMD_SERVER) && ci->client_id != CLIENT_ID_SERVER && !this->settings_authed) {
IConsolePrintF(CC_ERROR, "WARNING: server only command from: client %d (IP: %s), kicking...", ci->client_id, this->GetClientIP());
return this->SendError(NETWORK_ERROR_KICKED);
}
if ((GetCommandFlags(cp.cmd) & CMD_SPECTATOR) == 0 && !Company::IsValidID(cp.company) && ci->client_id != CLIENT_ID_SERVER) {
if ((GetCommandFlags(cp.cmd) & CMD_SPECTATOR) == 0 && !Company::IsValidID(cp.company) && ci->client_id != CLIENT_ID_SERVER && !this->settings_authed) {
IConsolePrintF(CC_ERROR, "WARNING: spectator issuing command from client %d (IP: %s), kicking...", ci->client_id, this->GetClientIP());
return this->SendError(NETWORK_ERROR_KICKED);
}
@ -1115,7 +1178,8 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_COMMAND(Packet
* to match the company in the packet. If it doesn't, the client has done
* something pretty naughty (or a bug), and will be kicked
*/
if (!(cp.cmd == CMD_COMPANY_CTRL && cp.p1 == 0 && ci->client_playas == COMPANY_NEW_COMPANY) && ci->client_playas != cp.company) {
if (!(cp.cmd == CMD_COMPANY_CTRL && cp.p1 == 0 && ci->client_playas == COMPANY_NEW_COMPANY) && ci->client_playas != cp.company &&
!((GetCommandFlags(cp.cmd) & CMD_SERVER) && this->settings_authed)) {
IConsolePrintF(CC_ERROR, "WARNING: client %d (IP: %s) tried to execute a command as company %d, kicking...",
ci->client_playas + 1, this->GetClientIP(), cp.company + 1);
return this->SendError(NETWORK_ERROR_COMPANY_MISMATCH);
@ -1171,12 +1235,22 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_ERROR(Packet *p
NetworkAdminClientError(this->client_id, errorno);
if (errorno == NETWORK_ERROR_DESYNC) {
CrashLog::DesyncCrashLog(&(this->desync_log), nullptr);
std::string server_desync_log;
CrashLog::DesyncCrashLog(&(this->desync_log), &server_desync_log, DesyncExtraInfo{});
this->SendDesyncLog(server_desync_log);
// decrease the sync frequency for this point onwards
_settings_client.network.sync_freq = min<uint16>(_settings_client.network.sync_freq, 16);
// have the server and all clients run some sanity checks
NetworkSendCommand(0, 0, 0, CMD_DESYNC_CHECK, nullptr, nullptr, _local_company, 0);
}
SendPacketsState send_state = this->SendPackets(true);
if (send_state != SPS_CLOSED) {
this->status = STATUS_CLOSE_PENDING;
return NETWORK_RECV_STATUS_OKAY;
}
}
return this->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
}
@ -1476,7 +1550,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::Receive_CLIENT_RCON(Packet *p)
p->Recv_string(pass, sizeof(pass));
p->Recv_string(command, sizeof(command));
if (strcmp(pass, _settings_client.network.rcon_password) != 0) {
if (strcmp(pass, GenerateCompanyPasswordHash(_settings_client.network.rcon_password, _settings_client.network.network_id, _settings_game.game_creation.generation_seed ^ this->rcon_hash_bits)) != 0) {
DEBUG(net, 0, "[rcon] wrong password from client-id %d", this->client_id);
return NETWORK_RECV_STATUS_OKAY;
}
@ -1910,6 +1984,7 @@ void NetworkServer_Tick(bool send_frame)
break;
case NetworkClientSocket::STATUS_MAP_WAIT:
case NetworkClientSocket::STATUS_CLOSE_PENDING:
/* This is an internal state where we do not wait
* on the client to move to a different state. */
break;
@ -1919,7 +1994,7 @@ void NetworkServer_Tick(bool send_frame)
NOT_REACHED();
}
if (cs->status >= NetworkClientSocket::STATUS_PRE_ACTIVE) {
if (cs->status >= NetworkClientSocket::STATUS_PRE_ACTIVE && cs->status != NetworkClientSocket::STATUS_CLOSE_PENDING) {
/* Check if we can send command, and if we have anything in the queue */
NetworkHandleCommandQueue(cs);
@ -1981,7 +2056,8 @@ void NetworkServerShowStatusToConsole()
"loading map",
"map done",
"ready",
"active"
"active",
"close pending"
};
assert_compile(lengthof(stat_str) == NetworkClientSocket::STATUS_END);

@ -29,6 +29,7 @@ protected:
NetworkRecvStatus Receive_CLIENT_COMPANY_INFO(Packet *p) override;
NetworkRecvStatus Receive_CLIENT_GAME_PASSWORD(Packet *p) override;
NetworkRecvStatus Receive_CLIENT_COMPANY_PASSWORD(Packet *p) override;
NetworkRecvStatus Receive_CLIENT_SETTINGS_PASSWORD(Packet *p) override;
NetworkRecvStatus Receive_CLIENT_GETMAP(Packet *p) override;
NetworkRecvStatus Receive_CLIENT_MAP_OK(Packet *p) override;
NetworkRecvStatus Receive_CLIENT_ACK(Packet *p) override;
@ -63,6 +64,7 @@ public:
STATUS_DONE_MAP, ///< The client has downloaded the map.
STATUS_PRE_ACTIVE, ///< The client is catching up the delayed frames.
STATUS_ACTIVE, ///< The client is active within in the game.
STATUS_CLOSE_PENDING, ///< The client connection is pending closure.
STATUS_END, ///< Must ALWAYS be on the end of this list!! (period).
};
@ -72,6 +74,10 @@ public:
ClientStatus status; ///< Status of this client
CommandQueue outgoing_queue; ///< The command-queue awaiting delivery
int receive_limit; ///< Amount of bytes that we can receive at this moment
uint32 server_hash_bits; ///< Server password hash entropy bits
uint32 rcon_hash_bits; ///< Rcon password hash entropy bits
uint32 settings_hash_bits; ///< Settings password hash entropy bits
bool settings_authed = false;///< Authorised to control all game settings
struct PacketWriter *savegame; ///< Writer used to write the savegame.
NetworkAddress client_address; ///< IP-address of the client (so he can be banned)
@ -95,6 +101,7 @@ public:
NetworkRecvStatus SendClientInfo(NetworkClientInfo *ci);
NetworkRecvStatus SendError(NetworkErrorCode error);
NetworkRecvStatus SendDesyncLog(const std::string &log);
NetworkRecvStatus SendChat(NetworkAction action, ClientID client_id, bool self_send, const char *msg, NetworkTextMessageData data);
NetworkRecvStatus SendJoin(ClientID client_id);
NetworkRecvStatus SendFrame();
@ -102,6 +109,7 @@ public:
NetworkRecvStatus SendCommand(const CommandPacket *cp);
NetworkRecvStatus SendCompanyUpdate();
NetworkRecvStatus SendConfigUpdate();
NetworkRecvStatus SendSettingsAccessUpdate(bool ok);
static void Send();
static void AcceptConnection(SOCKET s, const NetworkAddress &address);

@ -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;
}
}

@ -78,6 +78,7 @@
#include "string_func_extra.h"
#include "industry.h"
#include "cargopacket.h"
#include "core/checksum_func.hpp"
#include "linkgraph/linkgraphschedule.h"
#include "tracerestrict.h"
@ -104,6 +105,8 @@ GameEventFlags _game_events_overall;
time_t _game_load_time;
SimpleChecksum64 _state_checksum;
/**
* Error handling for fatal user errors.
* @param s the string to print.
@ -404,6 +407,7 @@ static void ShutdownGame()
*/
static void LoadIntroGame(bool load_newgrfs = true)
{
UnshowCriticalError();
Window *v;
FOR_ALL_WINDOWS_FROM_FRONT(v) delete v;
@ -1636,6 +1640,12 @@ void CheckCaches(bool force_check, std::function<void(const char *)> log)
if (!CargoPacket::ValidateDeferredCargoPayments()) CCLOG("Cargo packets deferred payments validation failed");
if (_order_destination_refcount_map_valid) {
btree::btree_map<uint32, uint32> saved_order_destination_refcount_map = std::move(_order_destination_refcount_map);
IntialiseOrderDestinationRefcountMap();
if (saved_order_destination_refcount_map != _order_destination_refcount_map) CCLOG("Order destination refcount map mismatch");
}
#undef CCLOG
}
@ -1745,6 +1755,11 @@ void StateGameLoop()
CallWindowGameTickEvent();
NewsLoop();
cur_company.Restore();
Company *c;
FOR_ALL_COMPANIES(c) {
UpdateStateChecksum(c->money);
}
}
assert(IsLocalCompany());

@ -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)) {

@ -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);
}

@ -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);
}

@ -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 */

@ -39,6 +39,7 @@
#include "framerate_type.h"
#include "scope_info.h"
#include "string_func.h"
#include "core/checksum_func.hpp"
#include "table/strings.h"
@ -1067,6 +1068,7 @@ static Trackdir RoadFindPathToDest(RoadVehicle *v, TileIndex tile, DiagDirection
default: NOT_REACHED();
}
UpdateStateChecksum((((uint64) v->index) << 32) | (path_found << 16) | best_track);
v->HandlePathfindingResult(path_found);
found_best_track:;
@ -1746,6 +1748,8 @@ Money RoadVehicle::GetRunningCost() const
bool RoadVehicle::Tick()
{
UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos);
UpdateStateChecksum((((uint64) this->state) << 32) | this->frame);
if (this->IsFrontEngine()) {
if (!(this->IsRoadVehicleStopped())) this->running_ticks++;
return RoadVehController(this);

@ -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
};

@ -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

@ -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

@ -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<CargoTypes>(arr_len);
SlArray(t->cargo_accepted.data, arr_len, SLE_UINT64);
/* Rebuild total cargo acceptance. */
UpdateTownCargoTotal(t);
}
/* Rebuild total cargo acceptance. */
UpdateTownCargoTotal(t);
}
}
}

@ -810,7 +810,7 @@ void IniSaveWindowSettings(IniFile *ini, const char *grpname, void *desc)
*/
bool SettingDesc::IsEditable(bool do_command) const
{
if (!do_command && !(this->save.conv & SLF_NO_NETWORK_SYNC) && _networking && !_network_server && !(this->desc.flags & SGF_PER_COMPANY)) return false;
if (!do_command && !(this->save.conv & SLF_NO_NETWORK_SYNC) && _networking && !(_network_server || _network_settings_access) && !(this->desc.flags & SGF_PER_COMPANY)) return false;
if ((this->desc.flags & SGF_NETWORK_ONLY) && !_networking && _game_mode != GM_MENU) return false;
if ((this->desc.flags & SGF_NO_NETWORK) && _networking) return false;
if ((this->desc.flags & SGF_NEWGAME_ONLY) &&
@ -1264,7 +1264,7 @@ static bool MaxNoAIsChange(int32 i)
{
if (GetGameSettings().difficulty.max_no_competitors != 0 &&
AI::GetInfoList()->size() == 0 &&
(!_networking || _network_server)) {
(!_networking || (_network_server || _network_settings_access))) {
ShowErrorMessage(STR_WARNING_NO_SUITABLE_AI, INVALID_STRING_ID, WL_CRITICAL);
}
@ -1499,6 +1499,15 @@ static bool UpdateRconPassword(int32 p1)
return true;
}
static bool UpdateSettingsPassword(int32 p1)
{
if (strcmp(_settings_client.network.settings_password, "*") == 0) {
_settings_client.network.settings_password[0] = '\0';
}
return true;
}
static bool UpdateClientConfigValues(int32 p1)
{
if (_network_server) NetworkServerSendConfigUpdate();
@ -2116,7 +2125,7 @@ bool SetSettingValue(uint index, int32 value, bool force_newgame)
}
/* send non-company-based settings over the network */
if (!_networking || (_networking && _network_server)) {
if (!_networking || (_networking && (_network_server || _network_settings_access))) {
return DoCommandP(0, index, value, CMD_CHANGE_SETTING);
}
return false;
@ -2276,7 +2285,7 @@ void IConsoleSetSetting(const char *name, const char *value, bool force_newgame)
}
if (!success) {
if (_network_server) {
if ((_network_server || _network_settings_access)) {
IConsoleError("This command/variable is not available during network games.");
} else {
IConsoleError("This command/variable is only available to a network server.");

@ -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
}

@ -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)

@ -37,6 +37,7 @@
#include "framerate_type.h"
#include "industry.h"
#include "industry_map.h"
#include "core/checksum_func.hpp"
#include "table/strings.h"
@ -524,6 +525,7 @@ static Track ChooseShipTrack(Ship *v, TileIndex tile, DiagDirection enterdir, Tr
default: NOT_REACHED();
}
}
UpdateStateChecksum((((uint64) v->index) << 32) | (path_found << 16) | track);
v->HandlePathfindingResult(path_found);
return track;
@ -972,6 +974,7 @@ reverse_direction:
bool Ship::Tick()
{
UpdateStateChecksum((((uint64) this->x_pos) << 32) | this->y_pos);
if (!(this->vehstatus & VS_STOPPED)) this->running_ticks++;
ShipController(this);

@ -616,6 +616,13 @@ class NIHTown : public NIHelper {
SetDParam(0, t->cargo_accepted_total);
b = GetString(b, STR_JUST_CARGO_LIST, lastof(buffer));
print(buffer);
seprintf(buffer, lastof(buffer), " Nearby stations: %u", (uint) t->stations_near.size());
print(buffer);
for (const Station *st : t->stations_near) {
seprintf(buffer, lastof(buffer), " %u: %s", st->index, st->GetCachedName());
print(buffer);
}
}
};

@ -53,6 +53,7 @@ static bool EnableSingleVehSharedOrderGuiChanged(int32 p1);
static bool UpdateClientName(int32 p1);
static bool UpdateServerPassword(int32 p1);
static bool UpdateRconPassword(int32 p1);
static bool UpdateSettingsPassword(int32 p1);
static bool UpdateClientConfigValues(int32 p1);
static bool CheckSharingRail(int32 p1);
static bool CheckSharingRoad(int32 p1);
@ -5093,6 +5094,15 @@ guiflags = SGF_NETWORK_ONLY
def = nullptr
cat = SC_BASIC
[SDTC_STR]
var = network.settings_password
type = SLE_STRB
flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC
guiflags = SGF_NETWORK_ONLY
def = nullptr
proc = UpdateSettingsPassword
cat = SC_EXPERT
[SDTC_STR]
var = network.default_company_pass
type = SLE_STRB

@ -277,7 +277,7 @@ static CallBackFunction SelectSignTool()
static CallBackFunction ToolbarPauseClick(Window *w)
{
if (_networking && !_network_server) return CBF_NONE; // only server can pause the game
if (_networking && !(_network_server || _network_settings_access)) return CBF_NONE; // only server can pause the game
if (DoCommandP(0, PM_PAUSED_NORMAL, _pause_mode == PM_UNPAUSED, CMD_PAUSE)) {
if (_settings_client.sound.confirm) SndPlayFx(SND_15_BEEP);
@ -2061,7 +2061,6 @@ struct MainToolbarWindow : Window {
_last_started_action = CBF_NONE;
CLRBITS(this->flags, WF_WHITE_BORDER);
this->SetWidgetDisabledState(WID_TN_PAUSE, _networking && !_network_server); // if not server, disable pause button
this->SetWidgetDisabledState(WID_TN_FAST_FORWARD, _networking); // if networking, disable fast-forward button
PositionMainToolbar(this);
DoZoomInOutWindow(ZOOM_NONE, this);
@ -2091,6 +2090,8 @@ struct MainToolbarWindow : Window {
this->SetWidgetDisabledState(WID_TN_TRAMS, !CanBuildVehicleInfrastructure(VEH_ROAD, RTT_TRAM));
this->SetWidgetDisabledState(WID_TN_AIR, !CanBuildVehicleInfrastructure(VEH_AIRCRAFT));
this->SetWidgetDisabledState(WID_TN_PAUSE, _networking && !(_network_server || _network_settings_access)); // if not server, disable pause button
this->DrawWidgets();
}

@ -573,7 +573,7 @@ uint32 GetWorldPopulation()
* Remove stations from nearby station list if a town is no longer in the catchment area of each.
* @param t Town to work on
*/
static void RemoveNearbyStations(Town *t)
void RemoveNearbyStations(Town *t)
{
for (StationList::iterator it = t->stations_near.begin(); it != t->stations_near.end(); /* incremented inside loop */) {
const Station *st = *it;
@ -1010,11 +1010,11 @@ void UpdateTownCargoes(Town *t)
t->cargo_produced = 0;
const TileArea &area = t->cargo_accepted.GetArea();
if (area.tile == INVALID_TILE) return;
/* Update acceptance for each grid square. */
TILE_AREA_LOOP_STEP(tile, area, AcceptanceMatrix::GRID) {
UpdateTownCargoesSingleGridArea(t, tile, false);
if (area.tile != INVALID_TILE) {
/* Update acceptance for each grid square. */
TILE_AREA_LOOP_STEP(tile, area, AcceptanceMatrix::GRID) {
UpdateTownCargoesSingleGridArea(t, tile, false);
}
}
/* Update the total acceptance. */

@ -322,9 +322,6 @@ public:
this->flags |= WF_DISABLE_VP_SCROLL;
NWidgetViewport *nvp = this->GetWidget<NWidgetViewport>(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();
}

@ -870,8 +870,8 @@ void SetTraceRestrictValueDefault(TraceRestrictItem &item, TraceRestrictValueTyp
break;
case TRVT_CARGO_ID:
assert(_sorted_standard_cargo_specs_size > 0);
SetTraceRestrictValue(item, _sorted_cargo_specs[0]->Index());
assert(_standard_cargo_mask != 0);
SetTraceRestrictValue(item, FindFirstBit64(_standard_cargo_mask));
SetTraceRestrictAuxField(item, 0);
break;

@ -43,6 +43,7 @@
#include "engine_func.h"
#include "bridge_signal_map.h"
#include "scope_info.h"
#include "core/checksum_func.hpp"
#include "table/strings.h"
#include "table/train_cmd.h"
@ -1549,6 +1550,7 @@ CommandCost CmdMoveRailVehicle(TileIndex tile, DoCommandFlag flags, uint32 p1, u
TraceRestrictRemoveVehicleFromAllSlots(src->index);
ClrBit(src->flags, VRF_HAVE_SLOT);
}
OrderBackup::ClearVehicle(src);
}
/* We weren't a front engine but are becoming one. So
@ -2175,6 +2177,53 @@ void ReverseTrainDirection(Train *v)
/* Clear path reservation in front if train is not stuck. */
if (!HasBit(v->flags, VRF_TRAIN_STUCK)) FreeTrainTrackReservation(v);
std::vector<Train *> re_reserve_trains;
{
/* Temporarily clear and restore reservations to bidi tunnel/bridge entrances when reversing train inside,
* to avoid outgoing and incoming reservations becoming merged */
auto find_train_reservations = [&re_reserve_trains, &v](TileIndex tile) {
TrackBits reserved = GetAcrossTunnelBridgeReservationTrackBits(tile);
Track track;
while ((track = RemoveFirstTrack(&reserved)) != INVALID_TRACK) {
Train *res_train = GetTrainForReservation(tile, track);
if (res_train != nullptr && res_train != v) {
FreeTrainTrackReservation(res_train);
re_reserve_trains.push_back(res_train);
}
}
};
if (IsTunnelBridgeWithSignalSimulation(v->tile) && IsTunnelBridgeSignalSimulationBidirectional(v->tile)) {
find_train_reservations(v->tile);
find_train_reservations(GetOtherTunnelBridgeEnd(v->tile));
}
Train *last = v->Last();
if (IsTunnelBridgeWithSignalSimulation(last->tile) && IsTunnelBridgeSignalSimulationBidirectional(last->tile)) {
find_train_reservations(last->tile);
find_train_reservations(GetOtherTunnelBridgeEnd(last->tile));
}
}
if ((v->track & TRACK_BIT_WORMHOLE) && IsTunnelBridgeWithSignalSimulation(v->tile)) {
/* Clear exit tile reservation if train was on approach to exit and had reserved it */
Axis axis = DiagDirToAxis(GetTunnelBridgeDirection(v->tile));
DiagDirection axial_dir = DirToDiagDirAlongAxis(v->direction, axis);
TileIndex next_tile = TileVirtXY(v->x_pos, v->y_pos) + TileOffsByDiagDir(axial_dir);
if (next_tile == v->tile || next_tile == GetOtherTunnelBridgeEnd(v->tile)) {
Trackdir exit_td = TrackEnterdirToTrackdir(FindFirstTrack(GetAcrossTunnelBridgeTrackBits(next_tile)), ReverseDiagDir(GetTunnelBridgeDirection(next_tile)));
CFollowTrackRail ft(GetTileOwner(next_tile), GetRailTypeInfo(v->railtype)->compatible_railtypes);
if (ft.Follow(next_tile, exit_td)) {
TrackdirBits reserved = ft.m_new_td_bits & TrackBitsToTrackdirBits(GetReservedTrackbits(ft.m_new_tile));
if (reserved == TRACKDIR_BIT_NONE) {
UnreserveAcrossRailTunnelBridge(next_tile);
MarkTileDirtyByTile(next_tile, ZOOM_LVL_DRAW_MAP);
}
} else {
UnreserveAcrossRailTunnelBridge(next_tile);
MarkTileDirtyByTile(next_tile, ZOOM_LVL_DRAW_MAP);
}
}
}
/* Check if we were approaching a rail/road-crossing */
TileIndex crossing = TrainApproachingCrossingTile(v);
@ -2214,6 +2263,10 @@ void ReverseTrainDirection(Train *v)
crossing = TrainApproachingCrossingTile(v);
if (crossing != INVALID_TILE) MaybeBarCrossingWithSound(crossing);
for (uint i = 0; i < re_reserve_trains.size(); ++i) {
TryPathReserve(re_reserve_trains[i], true);
}
/* If we are inside a depot after reversing, don't bother with path reserving. */
if (v->track == TRACK_BIT_DEPOT) {
/* Can't be stuck here as inside a depot is always a safe tile. */
@ -2724,6 +2777,9 @@ void FreeTrainTrackReservation(const Train *v, TileIndex origin, Trackdir orig_t
/* Don't free reservation if it's not ours. */
if (TracksOverlap(GetReservedTrackbits(tile) | TrackToTrackBits(TrackdirToTrack(td)))) return;
/* Do not attempt to unreserve out of a signalled tunnel/bridge entrance, as this would unreserve the reservations of another train coming in */
if (IsTunnelBridgeWithSignalSimulation(tile) && TrackdirExitsTunnelBridge(tile, td) && IsTunnelBridgeSignalSimulationEntranceOnly(tile)) return;
CFollowTrackRail ft(v, GetRailTypeInfo(v->railtype)->compatible_railtypes);
while (ft.Follow(tile, td)) {
tile = ft.m_new_tile;
@ -3127,6 +3183,7 @@ static Track ChooseTrainTrack(Train *v, TileIndex tile, DiagDirection enterdir,
TileIndex new_tile = res_dest.tile;
Track next_track = DoTrainPathfind(v, new_tile, dest_enterdir, tracks, path_found, do_track_reservation, &res_dest);
UpdateStateChecksum((((uint64) v->index) << 32) | (path_found << 16) | next_track);
if (new_tile == tile) best_track = next_track;
v->HandlePathfindingResult(path_found);
}
@ -4217,7 +4274,10 @@ bool TrainController(Train *v, Vehicle *nomove, bool reverse)
}
if (v->Next() == nullptr) {
if (v->tunnel_bridge_signal_num > 0 && distance == (TILE_SIZE * _settings_game.construction.simulated_wormhole_signals) - TILE_SIZE) HandleSignalBehindTrain(v, v->tunnel_bridge_signal_num - 2);
if (old_tile == v->tile) {
DiagDirection tunnel_bridge_dir = GetTunnelBridgeDirection(v->tile);
Axis axis = DiagDirToAxis(tunnel_bridge_dir);
DiagDirection axial_dir = DirToDiagDirAlongAxis(v->direction, axis);
if (old_tile == ((axial_dir == tunnel_bridge_dir) ? v->tile : GetOtherTunnelBridgeEnd(v->tile))) {
/* We left ramp into wormhole. */
v->x_pos = gp.x;
v->y_pos = gp.y;
@ -4971,6 +5031,7 @@ Money Train::GetRunningCost() const
*/
bool Train::Tick()
{
UpdateStateChecksum((((uint64) this->x_pos) << 32) | (this->y_pos << 16) | this->track );
if (this->IsFrontEngine()) {
if (!(this->vehstatus & VS_STOPPED) || this->cur_speed > 0) this->running_ticks++;

@ -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.

Loading…
Cancel
Save