Send vehicle caches to network clients to prevent desyncs due to bad GRFs
This commit is contained in:
@@ -617,7 +617,7 @@ NetworkRecvStatus ServerNetworkGameSocketHandler::SendMap()
|
||||
sent_packets = 4; // We start with trying 4 packets
|
||||
|
||||
/* Make a dump of the current game */
|
||||
if (SaveWithFilter(this->savegame, true) != SL_OK) usererror("network savedump failed");
|
||||
if (SaveWithFilter(this->savegame, true, true) != SL_OK) usererror("network savedump failed");
|
||||
}
|
||||
|
||||
if (this->status == STATUS_MAP) {
|
||||
|
@@ -1331,6 +1331,22 @@ void SwitchToMode(SwitchMode new_mode)
|
||||
SmallMapWindow::RebuildColourIndexIfNecessary();
|
||||
}
|
||||
|
||||
void WriteVehicleInfo(char *&p, const char *last, const Vehicle *u, const Vehicle *v, uint length)
|
||||
{
|
||||
p += seprintf(p, last, ": type %i, vehicle %i (%i), company %i, unit number %i, wagon %i, engine: ",
|
||||
(int)u->type, u->index, v->index, (int)u->owner, v->unitnumber, length);
|
||||
SetDParam(0, u->engine_type);
|
||||
p = GetString(p, STR_ENGINE_NAME, last);
|
||||
uint32 grfid = u->GetGRFID();
|
||||
if (grfid) {
|
||||
p += seprintf(p, last, ", GRF: %08X", BSWAP32(grfid));
|
||||
GRFConfig *grfconfig = GetGRFConfig(grfid);
|
||||
if (grfconfig) {
|
||||
p += seprintf(p, last, ", %s, %s", grfconfig->GetName(), grfconfig->filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the validity of some of the caches.
|
||||
* Especially in the sense of desyncs between
|
||||
@@ -1359,18 +1375,7 @@ void CheckCaches(bool force_check, std::function<void(const char *)> log)
|
||||
}
|
||||
|
||||
auto output_veh_info = [&](char *&p, const Vehicle *u, const Vehicle *v, uint length) {
|
||||
p += seprintf(p, lastof(cclog_buffer), ": type %i, vehicle %i (%i), company %i, unit number %i, wagon %i, engine: ",
|
||||
(int)u->type, u->index, v->index, (int)u->owner, v->unitnumber, length);
|
||||
SetDParam(0, u->engine_type);
|
||||
p = GetString(p, STR_ENGINE_NAME, lastof(cclog_buffer));
|
||||
uint32 grfid = u->GetGRFID();
|
||||
if (grfid) {
|
||||
p += seprintf(p, lastof(cclog_buffer), ", GRF: %08X", BSWAP32(grfid));
|
||||
GRFConfig *grfconfig = GetGRFConfig(grfid);
|
||||
if (grfconfig) {
|
||||
p += seprintf(p, lastof(cclog_buffer), ", %s, %s", grfconfig->GetName(), grfconfig->filename);
|
||||
}
|
||||
}
|
||||
WriteVehicleInfo(p, lastof(cclog_buffer), u, v, length);
|
||||
};
|
||||
|
||||
#define CCLOGV(...) { \
|
||||
|
@@ -3840,6 +3840,10 @@ bool AfterLoadGame()
|
||||
extern void YapfCheckRailSignalPenalties();
|
||||
YapfCheckRailSignalPenalties();
|
||||
|
||||
if (_networking && !_network_server) {
|
||||
SlProcessVENC();
|
||||
}
|
||||
|
||||
/* Show this message last to avoid covering up an error message if we bail out part way */
|
||||
switch (gcf_res) {
|
||||
case GLC_COMPATIBLE: ShowErrorMessage(STR_NEWGRF_COMPATIBLE_LOAD_WARNING, INVALID_STRING_ID, WL_CRITICAL); break;
|
||||
|
@@ -138,6 +138,7 @@ const SlxiSubChunkInfo _sl_xv_sub_chunk_infos[] = {
|
||||
{ XSLFI_ORDER_FLAGS_EXTRA, XSCF_NULL, 1, 1, "order_flags_extra", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_ONE_WAY_DT_ROAD_STOP, XSCF_NULL, 1, 1, "one_way_dt_road_stop", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_ONE_WAY_ROAD_STATE, XSCF_NULL, 1, 1, "one_way_road_state", nullptr, nullptr, nullptr },
|
||||
{ XSLFI_VENC_CHUNK, XSCF_IGNORABLE_ALL, 1, 1, "venc_chunk", nullptr, nullptr, "VENC" },
|
||||
{ XSLFI_NULL, XSCF_NULL, 0, 0, nullptr, nullptr, nullptr, nullptr },// This is the end marker
|
||||
};
|
||||
|
||||
|
@@ -92,6 +92,7 @@ enum SlXvFeatureIndex {
|
||||
XSLFI_ORDER_FLAGS_EXTRA, ///< Order flags field extra size
|
||||
XSLFI_ONE_WAY_DT_ROAD_STOP, ///< One-way drive-through road stops
|
||||
XSLFI_ONE_WAY_ROAD_STATE, ///< One-way road state cache
|
||||
XSLFI_VENC_CHUNK, ///< VENC chunk
|
||||
|
||||
XSLFI_RIFF_HEADER_60_BIT, ///< Size field in RIFF chunk header is 60 bit
|
||||
XSLFI_HEIGHT_8_BIT, ///< Map tile height is 8 bit instead of 4 bit, but savegame version may be before this became true in trunk
|
||||
|
@@ -43,6 +43,7 @@
|
||||
#include "../string_func_extra.h"
|
||||
#include "../fios.h"
|
||||
#include "../error.h"
|
||||
#include "../scope.h"
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
@@ -226,6 +227,7 @@ struct SaveLoadParams {
|
||||
|
||||
byte ff_state; ///< The state of fast-forward when saving started.
|
||||
bool saveinprogress; ///< Whether there is currently a save in progress.
|
||||
bool networkserversave; ///< Whether this save is being sent to a network client
|
||||
};
|
||||
|
||||
static SaveLoadParams _sl; ///< Parameters used for/at saveload.
|
||||
@@ -2896,6 +2898,8 @@ static inline void ClearSaveLoadState()
|
||||
delete _sl.lf;
|
||||
_sl.lf = nullptr;
|
||||
|
||||
_sl.networkserversave = false;
|
||||
|
||||
GamelogStopAnyAction();
|
||||
}
|
||||
|
||||
@@ -3042,12 +3046,14 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded)
|
||||
* Save the game using a (writer) filter.
|
||||
* @param writer The filter to write the savegame to.
|
||||
* @param threaded Whether to try to perform the saving asynchronously.
|
||||
* @param networkserversave Whether this is a network server save.
|
||||
* @return Return the result of the action. #SL_OK or #SL_ERROR
|
||||
*/
|
||||
SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded)
|
||||
SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded, bool networkserversave)
|
||||
{
|
||||
try {
|
||||
_sl.action = SLA_SAVE;
|
||||
_sl.networkserversave = networkserversave;
|
||||
return DoSave(writer, threaded);
|
||||
} catch (...) {
|
||||
ClearSaveLoadState();
|
||||
@@ -3055,6 +3061,11 @@ SaveOrLoadResult SaveWithFilter(SaveFilter *writer, bool threaded)
|
||||
}
|
||||
}
|
||||
|
||||
bool IsNetworkServerSave()
|
||||
{
|
||||
return _sl.networkserversave;
|
||||
}
|
||||
|
||||
struct ThreadedLoadFilter : LoadFilter {
|
||||
static const size_t BUFFER_COUNT = 4;
|
||||
|
||||
@@ -3179,6 +3190,10 @@ static SaveOrLoadResult DoLoad(LoadFilter *reader, bool load_check)
|
||||
}
|
||||
|
||||
SlXvResetState();
|
||||
SlResetVENC();
|
||||
auto guard = scope_guard([&]() {
|
||||
SlResetVENC();
|
||||
});
|
||||
|
||||
uint32 hdr[2];
|
||||
if (_sl.lf->Read((byte*)hdr, sizeof(hdr)) != sizeof(hdr)) SlError(STR_GAME_SAVELOAD_ERROR_FILE_NOT_READABLE);
|
||||
@@ -3396,6 +3411,7 @@ SaveOrLoadResult SaveOrLoad(const char *filename, SaveLoadOperation fop, Detaile
|
||||
|
||||
default: NOT_REACHED();
|
||||
}
|
||||
_sl.networkserversave = false;
|
||||
|
||||
FILE *fh = (fop == SLO_SAVE) ? FioFOpenFile(filename, "wb", sb) : FioFOpenFile(filename, "rb", sb);
|
||||
|
||||
|
@@ -376,8 +376,9 @@ void WaitTillSaved();
|
||||
void ProcessAsyncSaveFinish();
|
||||
void DoExitSave();
|
||||
|
||||
SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded);
|
||||
SaveOrLoadResult SaveWithFilter(struct SaveFilter *writer, bool threaded, bool networkserversave);
|
||||
SaveOrLoadResult LoadWithFilter(struct LoadFilter *reader);
|
||||
bool IsNetworkServerSave();
|
||||
|
||||
typedef void ChunkSaveLoadProc();
|
||||
typedef void AutolengthProc(void *arg);
|
||||
@@ -1085,6 +1086,9 @@ void NORETURN CDECL SlErrorCorruptFmt(const char *format, ...) WARN_FORMAT(1, 2)
|
||||
|
||||
bool SaveloadCrashWithMissingNewGRFs();
|
||||
|
||||
void SlResetVENC();
|
||||
void SlProcessVENC();
|
||||
|
||||
extern char _savegame_format[8];
|
||||
extern bool _do_autosave;
|
||||
|
||||
|
@@ -1137,8 +1137,244 @@ void Load_VESR()
|
||||
}
|
||||
}
|
||||
|
||||
struct vehicle_venc {
|
||||
VehicleID id;
|
||||
VehicleCache vcache;
|
||||
};
|
||||
|
||||
struct train_venc {
|
||||
VehicleID id;
|
||||
GroundVehicleCache gvcache;
|
||||
bool cached_tilt;
|
||||
uint8 cached_num_engines;
|
||||
byte user_def_data;
|
||||
int cached_max_curve_speed;
|
||||
};
|
||||
|
||||
struct roadvehicle_venc {
|
||||
VehicleID id;
|
||||
GroundVehicleCache gvcache;
|
||||
};
|
||||
|
||||
struct aircraft_venc {
|
||||
VehicleID id;
|
||||
uint16 cached_max_range;
|
||||
};
|
||||
|
||||
static std::vector<vehicle_venc> _vehicle_vencs;
|
||||
static std::vector<train_venc> _train_vencs;
|
||||
static std::vector<roadvehicle_venc> _roadvehicle_vencs;
|
||||
static std::vector<aircraft_venc> _aircraft_vencs;
|
||||
|
||||
void Save_VENC()
|
||||
{
|
||||
if (!IsNetworkServerSave()) {
|
||||
SlSetLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
SlAutolength([](void *) {
|
||||
int types[4] = {};
|
||||
int total = 0;
|
||||
for (Vehicle *v : Vehicle::Iterate()) {
|
||||
total++;
|
||||
if (v->type < VEH_COMPANY_END) types[v->type]++;
|
||||
}
|
||||
|
||||
/* vehicle cache */
|
||||
SlWriteUint32(total);
|
||||
for (Vehicle *v : Vehicle::Iterate()) {
|
||||
SlWriteUint32(v->index);
|
||||
SlWriteUint16(v->vcache.cached_max_speed);
|
||||
SlWriteUint16(v->vcache.cached_cargo_age_period);
|
||||
SlWriteByte(v->vcache.cached_vis_effect);
|
||||
SlWriteByte(v->vcache.cached_veh_flags);
|
||||
}
|
||||
|
||||
auto write_gv_cache = [&](const GroundVehicleCache &cache) {
|
||||
SlWriteUint32(cache.cached_weight);
|
||||
SlWriteUint32(cache.cached_slope_resistance);
|
||||
SlWriteUint32(cache.cached_max_te);
|
||||
SlWriteUint32(cache.cached_axle_resistance);
|
||||
SlWriteUint32(cache.cached_max_track_speed);
|
||||
SlWriteUint32(cache.cached_power);
|
||||
SlWriteUint32(cache.cached_air_drag);
|
||||
SlWriteUint16(cache.cached_total_length);
|
||||
SlWriteUint16(cache.first_engine);
|
||||
SlWriteByte(cache.cached_veh_length);
|
||||
};
|
||||
|
||||
/* train */
|
||||
SlWriteUint32(types[VEH_TRAIN]);
|
||||
for (Train *t : Train::Iterate()) {
|
||||
SlWriteUint32(t->index);
|
||||
write_gv_cache(t->gcache);
|
||||
SlWriteByte(t->tcache.cached_tilt);
|
||||
SlWriteByte(t->tcache.cached_num_engines);
|
||||
SlWriteByte(t->tcache.user_def_data);
|
||||
SlWriteUint32(t->tcache.cached_max_curve_speed);
|
||||
}
|
||||
|
||||
/* road vehicle */
|
||||
SlWriteUint32(types[VEH_ROAD]);
|
||||
for (RoadVehicle *rv : RoadVehicle::Iterate()) {
|
||||
SlWriteUint32(rv->index);
|
||||
write_gv_cache(rv->gcache);
|
||||
}
|
||||
|
||||
/* aircraft */
|
||||
SlWriteUint32(types[VEH_AIRCRAFT]);
|
||||
for (Aircraft *a : Aircraft::Iterate()) {
|
||||
SlWriteUint32(a->index);
|
||||
SlWriteUint16(a->acache.cached_max_range);
|
||||
}
|
||||
}, nullptr);
|
||||
}
|
||||
|
||||
void Load_VENC()
|
||||
{
|
||||
if (SlGetFieldLength() == 0) return;
|
||||
|
||||
if (!_networking || _network_server) {
|
||||
SlSkipBytes(SlGetFieldLength());
|
||||
return;
|
||||
}
|
||||
|
||||
_vehicle_vencs.resize(SlReadUint32());
|
||||
for (vehicle_venc &venc : _vehicle_vencs) {
|
||||
venc.id = SlReadUint32();
|
||||
venc.vcache.cached_max_speed = SlReadUint16();
|
||||
venc.vcache.cached_cargo_age_period = SlReadUint16();
|
||||
venc.vcache.cached_vis_effect = SlReadByte();
|
||||
venc.vcache.cached_veh_flags = SlReadByte();
|
||||
}
|
||||
|
||||
auto read_gv_cache = [&](GroundVehicleCache &cache) {
|
||||
cache.cached_weight = SlReadUint32();
|
||||
cache.cached_slope_resistance = SlReadUint32();
|
||||
cache.cached_max_te = SlReadUint32();
|
||||
cache.cached_axle_resistance = SlReadUint32();
|
||||
cache.cached_max_track_speed = SlReadUint32();
|
||||
cache.cached_power = SlReadUint32();
|
||||
cache.cached_air_drag = SlReadUint32();
|
||||
cache.cached_total_length = SlReadUint16();
|
||||
cache.first_engine = SlReadUint16();
|
||||
cache.cached_veh_length = SlReadByte();
|
||||
};
|
||||
|
||||
_train_vencs.resize(SlReadUint32());
|
||||
for (train_venc &venc : _train_vencs) {
|
||||
venc.id = SlReadUint32();
|
||||
read_gv_cache(venc.gvcache);
|
||||
venc.cached_tilt = SlReadByte();
|
||||
venc.cached_num_engines = SlReadByte();
|
||||
venc.user_def_data = SlReadByte();
|
||||
venc.cached_max_curve_speed = SlReadUint32();
|
||||
}
|
||||
|
||||
_roadvehicle_vencs.resize(SlReadUint32());
|
||||
for (roadvehicle_venc &venc : _roadvehicle_vencs) {
|
||||
venc.id = SlReadUint32();
|
||||
read_gv_cache(venc.gvcache);
|
||||
}
|
||||
|
||||
_aircraft_vencs.resize(SlReadUint32());
|
||||
for (aircraft_venc &venc : _aircraft_vencs) {
|
||||
venc.id = SlReadUint32();
|
||||
venc.cached_max_range = SlReadUint16();
|
||||
}
|
||||
}
|
||||
|
||||
void SlResetVENC()
|
||||
{
|
||||
_vehicle_vencs.clear();
|
||||
_train_vencs.clear();
|
||||
_roadvehicle_vencs.clear();
|
||||
_aircraft_vencs.clear();
|
||||
}
|
||||
|
||||
static void LogVehicleVENCMessage(const Vehicle *v, const char *var)
|
||||
{
|
||||
char log_buffer[1024];
|
||||
|
||||
char *p = log_buffer + seprintf(log_buffer, lastof(log_buffer), "[load]: vehicle cache mismatch: %s", var);
|
||||
|
||||
extern void WriteVehicleInfo(char *&p, const char *last, const Vehicle *u, const Vehicle *v, uint length);
|
||||
uint length = 0;
|
||||
for (const Vehicle *u = v->First(); u != v; u = u->Next()) {
|
||||
length++;
|
||||
}
|
||||
WriteVehicleInfo(p, lastof(log_buffer), v, v->First(), length);
|
||||
DEBUG(desync, 0, "%s", log_buffer);
|
||||
LogDesyncMsg(log_buffer);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void CheckVehicleVENCProp(T &v_prop, T venc_prop, const Vehicle *v, const char *var)
|
||||
{
|
||||
if (v_prop != venc_prop) {
|
||||
v_prop = venc_prop;
|
||||
LogVehicleVENCMessage(v, var);
|
||||
}
|
||||
}
|
||||
|
||||
void SlProcessVENC()
|
||||
{
|
||||
for (const vehicle_venc &venc : _vehicle_vencs) {
|
||||
Vehicle *v = Vehicle::GetIfValid(venc.id);
|
||||
if (v == nullptr) continue;
|
||||
CheckVehicleVENCProp(v->vcache.cached_max_speed, venc.vcache.cached_max_speed, v, "cached_max_speed");
|
||||
CheckVehicleVENCProp(v->vcache.cached_cargo_age_period, venc.vcache.cached_cargo_age_period, v, "cached_cargo_age_period");
|
||||
CheckVehicleVENCProp(v->vcache.cached_vis_effect, venc.vcache.cached_vis_effect, v, "cached_vis_effect");
|
||||
if (HasBit(v->vcache.cached_veh_flags ^ venc.vcache.cached_veh_flags, VCF_LAST_VISUAL_EFFECT)) {
|
||||
SB(v->vcache.cached_veh_flags, VCF_LAST_VISUAL_EFFECT, 1, HasBit(venc.vcache.cached_veh_flags, VCF_LAST_VISUAL_EFFECT) ? 1 : 0);
|
||||
LogVehicleVENCMessage(v, "VCF_LAST_VISUAL_EFFECT");
|
||||
}
|
||||
}
|
||||
|
||||
auto check_gv_cache = [&](GroundVehicleCache &v_gvcache, const GroundVehicleCache &venc_gvcache, const Vehicle *v) {
|
||||
CheckVehicleVENCProp(v_gvcache.cached_weight, venc_gvcache.cached_weight, v, "cached_weight");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_slope_resistance, venc_gvcache.cached_slope_resistance, v, "cached_slope_resistance");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_max_te, venc_gvcache.cached_max_te, v, "cached_max_te");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_axle_resistance, venc_gvcache.cached_axle_resistance, v, "cached_axle_resistance");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_max_track_speed, venc_gvcache.cached_max_track_speed, v, "cached_max_track_speed");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_power, venc_gvcache.cached_power, v, "cached_power");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_air_drag, venc_gvcache.cached_air_drag, v, "cached_air_drag");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_total_length, venc_gvcache.cached_total_length, v, "cached_total_length");
|
||||
CheckVehicleVENCProp(v_gvcache.first_engine, venc_gvcache.first_engine, v, "first_engine");
|
||||
CheckVehicleVENCProp(v_gvcache.cached_veh_length, venc_gvcache.cached_veh_length, v, "cached_veh_length");
|
||||
};
|
||||
|
||||
for (const train_venc &venc : _train_vencs) {
|
||||
Train *t = Train::GetIfValid(venc.id);
|
||||
if (t == nullptr) continue;
|
||||
check_gv_cache(t->gcache, venc.gvcache, t);
|
||||
CheckVehicleVENCProp(t->tcache.cached_tilt, venc.cached_tilt, t, "cached_tilt");
|
||||
CheckVehicleVENCProp(t->tcache.cached_num_engines, venc.cached_num_engines, t, "cached_num_engines");
|
||||
CheckVehicleVENCProp(t->tcache.user_def_data, venc.user_def_data, t, "user_def_data");
|
||||
CheckVehicleVENCProp(t->tcache.cached_max_curve_speed, venc.cached_max_curve_speed, t, "cached_max_curve_speed");
|
||||
}
|
||||
|
||||
for (const roadvehicle_venc &venc : _roadvehicle_vencs) {
|
||||
RoadVehicle *rv = RoadVehicle::GetIfValid(venc.id);
|
||||
if (rv == nullptr) continue;
|
||||
check_gv_cache(rv->gcache, venc.gvcache, rv);
|
||||
}
|
||||
|
||||
for (const aircraft_venc &venc : _aircraft_vencs) {
|
||||
Aircraft *a = Aircraft::GetIfValid(venc.id);
|
||||
if (a == nullptr) continue;
|
||||
if (a->acache.cached_max_range != venc.cached_max_range) {
|
||||
a->acache.cached_max_range = venc.cached_max_range;
|
||||
a->acache.cached_max_range_sqr = venc.cached_max_range * venc.cached_max_range;
|
||||
LogVehicleVENCMessage(a, "cached_max_range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern const ChunkHandler _veh_chunk_handlers[] = {
|
||||
{ 'VEHS', Save_VEHS, Load_VEHS, Ptrs_VEHS, nullptr, CH_SPARSE_ARRAY},
|
||||
{ 'VEOX', Save_VEOX, Load_VEOX, nullptr, nullptr, CH_SPARSE_ARRAY},
|
||||
{ 'VESR', Save_VESR, Load_VESR, nullptr, nullptr, CH_SPARSE_ARRAY | CH_LAST},
|
||||
{ 'VESR', Save_VESR, Load_VESR, nullptr, nullptr, CH_SPARSE_ARRAY},
|
||||
{ 'VENC', Save_VENC, Load_VENC, nullptr, nullptr, CH_RIFF | CH_LAST},
|
||||
};
|
||||
|
Reference in New Issue
Block a user