1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2026-01-18 09:52:44 +01:00

Fix 3ac1a2f1e4: Game crash due to invalid vehicle type information. (#14628)

Use std::variant instead of union for vehicle info.

RailVehicleInfo is now non-POD so using in a union causes undefined behaviour.
This commit is contained in:
Peter Nelson
2025-09-24 22:44:41 +01:00
committed by GitHub
parent ecb761fc69
commit 42c9f84d74
26 changed files with 208 additions and 198 deletions

View File

@@ -78,24 +78,19 @@ Engine::Engine(VehicleType type, uint16_t local_id)
/* Check if this base engine is within the original engine data range */
if (local_id >= _engine_counts[type]) {
/* 'power' defaults to zero, so we also have to default to 'wagon' */
if (type == VEH_TRAIN) this->u.rail.railveh_type = RAILVEH_WAGON;
/* Initialise default type-specific information. */
switch (type) {
case VEH_TRAIN: this->vehicle_info.emplace<RailVehicleInfo>(); break;
case VEH_ROAD: this->vehicle_info.emplace<RoadVehicleInfo>(); break;
case VEH_SHIP: this->vehicle_info.emplace<ShipVehicleInfo>(); break;
case VEH_AIRCRAFT: this->vehicle_info.emplace<AircraftVehicleInfo>(); break;
default: break;
}
/* Set model life to maximum to make wagons available */
this->info.base_life = TimerGameCalendar::Year{0xFF};
/* Set road vehicle tractive effort to the default value */
if (type == VEH_ROAD) this->u.road.tractive_effort = 0x4C;
/* Aircraft must have CT_INVALID as default, as there is no property */
this->info.cargo_type = INVALID_CARGO;
this->info.cargo_label = (type == VEH_AIRCRAFT) ? CT_INVALID : CT_PASSENGERS;
/* Ships must have a non-zero acceleration. */
if (type == VEH_SHIP) this->u.ship.acceleration = 1;
/* Set visual effect to the default value */
switch (type) {
case VEH_TRAIN: this->u.rail.visual_effect = VE_DEFAULT; break;
case VEH_ROAD: this->u.road.visual_effect = VE_DEFAULT; break;
case VEH_SHIP: this->u.ship.visual_effect = VE_DEFAULT; break;
default: break; // The aircraft, disasters and especially visual effects have no NewGRF configured visual effects
}
/* Set cargo aging period to the default value. */
this->info.cargo_age_period = Ticks::CARGO_AGING_TICKS;
/* Not a variant */
@@ -110,33 +105,37 @@ Engine::Engine(VehicleType type, uint16_t local_id)
switch (type) {
default: NOT_REACHED();
case VEH_TRAIN:
this->u.rail = _orig_rail_vehicle_info[local_id];
this->original_image_index = this->u.rail.image_index;
case VEH_TRAIN: {
RailVehicleInfo &rvi = this->vehicle_info.emplace<RailVehicleInfo>(_orig_rail_vehicle_info[local_id]);
this->original_image_index = rvi.image_index;
this->info.string_id = STR_VEHICLE_NAME_TRAIN_ENGINE_RAIL_KIRBY_PAUL_TANK_STEAM + local_id;
/* Set the default model life of original wagons to "infinite" */
if (this->u.rail.railveh_type == RAILVEH_WAGON) this->info.base_life = TimerGameCalendar::Year{0xFF};
if (rvi.railveh_type == RAILVEH_WAGON) this->info.base_life = TimerGameCalendar::Year{0xFF};
break;
}
case VEH_ROAD:
this->u.road = _orig_road_vehicle_info[local_id];
this->original_image_index = this->u.road.image_index;
case VEH_ROAD: {
RoadVehicleInfo &rvi = this->vehicle_info.emplace<RoadVehicleInfo>(_orig_road_vehicle_info[local_id]);
this->original_image_index = rvi.image_index;
this->info.string_id = STR_VEHICLE_NAME_ROAD_VEHICLE_MPS_REGAL_BUS + local_id;
break;
}
case VEH_SHIP:
this->u.ship = _orig_ship_vehicle_info[local_id];
this->original_image_index = this->u.ship.image_index;
case VEH_SHIP: {
ShipVehicleInfo &svi = this->vehicle_info.emplace<ShipVehicleInfo>(_orig_ship_vehicle_info[local_id]);
this->original_image_index = svi.image_index;
this->info.string_id = STR_VEHICLE_NAME_SHIP_MPS_OIL_TANKER + local_id;
break;
}
case VEH_AIRCRAFT:
this->u.air = _orig_aircraft_vehicle_info[local_id];
this->original_image_index = this->u.air.image_index;
case VEH_AIRCRAFT: {
AircraftVehicleInfo &avi = this->vehicle_info.emplace<AircraftVehicleInfo>(_orig_aircraft_vehicle_info[local_id]);
this->original_image_index = avi.image_index;
this->info.string_id = STR_VEHICLE_NAME_AIRCRAFT_SAMPSON_U52 + local_id;
break;
}
}
}
@@ -174,11 +173,11 @@ bool Engine::CanCarryCargo() const
*/
switch (this->type) {
case VEH_TRAIN:
if (this->u.rail.capacity == 0) return false;
if (this->VehInfo<RailVehicleInfo>().capacity == 0) return false;
break;
case VEH_ROAD:
if (this->u.road.capacity == 0) return false;
if (this->VehInfo<RoadVehicleInfo>().capacity == 0) return false;
break;
case VEH_SHIP:
@@ -210,7 +209,7 @@ uint Engine::DetermineCapacity(const Vehicle *v, uint16_t *mail_capacity) const
CargoType cargo_type = (v != nullptr) ? v->cargo_type : default_cargo;
if (mail_capacity != nullptr && this->type == VEH_AIRCRAFT && IsCargoInClass(cargo_type, CargoClass::Passengers)) {
*mail_capacity = GetEngineProperty(this->index, PROP_AIRCRAFT_MAIL_CAPACITY, this->u.air.mail_capacity, v);
*mail_capacity = GetEngineProperty(this->index, PROP_AIRCRAFT_MAIL_CAPACITY, this->VehInfo<AircraftVehicleInfo>().mail_capacity, v);
}
/* Check the refit capacity callback if we are not in the default configuration, or if we are using the new multiplier algorithm. */
@@ -225,24 +224,24 @@ uint Engine::DetermineCapacity(const Vehicle *v, uint16_t *mail_capacity) const
uint extra_mail_cap = 0;
switch (this->type) {
case VEH_TRAIN:
capacity = GetEngineProperty(this->index, PROP_TRAIN_CARGO_CAPACITY, this->u.rail.capacity, v);
capacity = GetEngineProperty(this->index, PROP_TRAIN_CARGO_CAPACITY, this->VehInfo<RailVehicleInfo>().capacity, v);
/* In purchase list add the capacity of the second head. Always use the plain property for this. */
if (v == nullptr && this->u.rail.railveh_type == RAILVEH_MULTIHEAD) capacity += this->u.rail.capacity;
if (v == nullptr && this->VehInfo<RailVehicleInfo>().railveh_type == RAILVEH_MULTIHEAD) capacity += this->VehInfo<RailVehicleInfo>().capacity;
break;
case VEH_ROAD:
capacity = GetEngineProperty(this->index, PROP_ROADVEH_CARGO_CAPACITY, this->u.road.capacity, v);
capacity = GetEngineProperty(this->index, PROP_ROADVEH_CARGO_CAPACITY, this->VehInfo<RoadVehicleInfo>().capacity, v);
break;
case VEH_SHIP:
capacity = GetEngineProperty(this->index, PROP_SHIP_CARGO_CAPACITY, this->u.ship.capacity, v);
capacity = GetEngineProperty(this->index, PROP_SHIP_CARGO_CAPACITY, this->VehInfo<ShipVehicleInfo>().capacity, v);
break;
case VEH_AIRCRAFT:
capacity = GetEngineProperty(this->index, PROP_AIRCRAFT_PASSENGER_CAPACITY, this->u.air.passenger_capacity, v);
capacity = GetEngineProperty(this->index, PROP_AIRCRAFT_PASSENGER_CAPACITY, this->VehInfo<AircraftVehicleInfo>().passenger_capacity, v);
if (!IsCargoInClass(cargo_type, CargoClass::Passengers)) {
extra_mail_cap = GetEngineProperty(this->index, PROP_AIRCRAFT_MAIL_CAPACITY, this->u.air.mail_capacity, v);
extra_mail_cap = GetEngineProperty(this->index, PROP_AIRCRAFT_MAIL_CAPACITY, this->VehInfo<AircraftVehicleInfo>().mail_capacity, v);
}
if (IsValidCargoType(GetCargoTypeByLabel(CT_MAIL))) {
if (!new_multipliers && cargo_type == GetCargoTypeByLabel(CT_MAIL)) return capacity + extra_mail_cap;
@@ -284,25 +283,25 @@ Money Engine::GetRunningCost() const
uint cost_factor;
switch (this->type) {
case VEH_ROAD:
base_price = this->u.road.running_cost_class;
base_price = this->VehInfo<RoadVehicleInfo>().running_cost_class;
if (base_price == INVALID_PRICE) return 0;
cost_factor = GetEngineProperty(this->index, PROP_ROADVEH_RUNNING_COST_FACTOR, this->u.road.running_cost);
cost_factor = GetEngineProperty(this->index, PROP_ROADVEH_RUNNING_COST_FACTOR, this->VehInfo<RoadVehicleInfo>().running_cost);
break;
case VEH_TRAIN:
base_price = this->u.rail.running_cost_class;
base_price = this->VehInfo<RailVehicleInfo>().running_cost_class;
if (base_price == INVALID_PRICE) return 0;
cost_factor = GetEngineProperty(this->index, PROP_TRAIN_RUNNING_COST_FACTOR, this->u.rail.running_cost);
cost_factor = GetEngineProperty(this->index, PROP_TRAIN_RUNNING_COST_FACTOR, this->VehInfo<RailVehicleInfo>().running_cost);
break;
case VEH_SHIP:
base_price = PR_RUNNING_SHIP;
cost_factor = GetEngineProperty(this->index, PROP_SHIP_RUNNING_COST_FACTOR, this->u.ship.running_cost);
cost_factor = GetEngineProperty(this->index, PROP_SHIP_RUNNING_COST_FACTOR, this->VehInfo<ShipVehicleInfo>().running_cost);
break;
case VEH_AIRCRAFT:
base_price = PR_RUNNING_AIRCRAFT;
cost_factor = GetEngineProperty(this->index, PROP_AIRCRAFT_RUNNING_COST_FACTOR, this->u.air.running_cost);
cost_factor = GetEngineProperty(this->index, PROP_AIRCRAFT_RUNNING_COST_FACTOR, this->VehInfo<AircraftVehicleInfo>().running_cost);
break;
default: NOT_REACHED();
@@ -322,27 +321,27 @@ Money Engine::GetCost() const
switch (this->type) {
case VEH_ROAD:
base_price = PR_BUILD_VEHICLE_ROAD;
cost_factor = GetEngineProperty(this->index, PROP_ROADVEH_COST_FACTOR, this->u.road.cost_factor);
cost_factor = GetEngineProperty(this->index, PROP_ROADVEH_COST_FACTOR, this->VehInfo<RoadVehicleInfo>().cost_factor);
break;
case VEH_TRAIN:
if (this->u.rail.railveh_type == RAILVEH_WAGON) {
if (this->VehInfo<RailVehicleInfo>().railveh_type == RAILVEH_WAGON) {
base_price = PR_BUILD_VEHICLE_WAGON;
cost_factor = GetEngineProperty(this->index, PROP_TRAIN_COST_FACTOR, this->u.rail.cost_factor);
cost_factor = GetEngineProperty(this->index, PROP_TRAIN_COST_FACTOR, this->VehInfo<RailVehicleInfo>().cost_factor);
} else {
base_price = PR_BUILD_VEHICLE_TRAIN;
cost_factor = GetEngineProperty(this->index, PROP_TRAIN_COST_FACTOR, this->u.rail.cost_factor);
cost_factor = GetEngineProperty(this->index, PROP_TRAIN_COST_FACTOR, this->VehInfo<RailVehicleInfo>().cost_factor);
}
break;
case VEH_SHIP:
base_price = PR_BUILD_VEHICLE_SHIP;
cost_factor = GetEngineProperty(this->index, PROP_SHIP_COST_FACTOR, this->u.ship.cost_factor);
cost_factor = GetEngineProperty(this->index, PROP_SHIP_COST_FACTOR, this->VehInfo<ShipVehicleInfo>().cost_factor);
break;
case VEH_AIRCRAFT:
base_price = PR_BUILD_VEHICLE_AIRCRAFT;
cost_factor = GetEngineProperty(this->index, PROP_AIRCRAFT_COST_FACTOR, this->u.air.cost_factor);
cost_factor = GetEngineProperty(this->index, PROP_AIRCRAFT_COST_FACTOR, this->VehInfo<AircraftVehicleInfo>().cost_factor);
break;
default: NOT_REACHED();
@@ -359,22 +358,22 @@ uint Engine::GetDisplayMaxSpeed() const
{
switch (this->type) {
case VEH_TRAIN:
return GetEngineProperty(this->index, PROP_TRAIN_SPEED, this->u.rail.max_speed);
return GetEngineProperty(this->index, PROP_TRAIN_SPEED, this->VehInfo<RailVehicleInfo>().max_speed);
case VEH_ROAD: {
uint max_speed = GetEngineProperty(this->index, PROP_ROADVEH_SPEED, 0);
return (max_speed != 0) ? max_speed * 2 : this->u.road.max_speed / 2;
return (max_speed != 0) ? max_speed * 2 : this->VehInfo<RoadVehicleInfo>().max_speed / 2;
}
case VEH_SHIP:
return GetEngineProperty(this->index, PROP_SHIP_SPEED, this->u.ship.max_speed) / 2;
return GetEngineProperty(this->index, PROP_SHIP_SPEED, this->VehInfo<ShipVehicleInfo>().max_speed) / 2;
case VEH_AIRCRAFT: {
uint max_speed = GetEngineProperty(this->index, PROP_AIRCRAFT_SPEED, 0);
if (max_speed != 0) {
return (max_speed * 128) / 10;
}
return this->u.air.max_speed;
return this->VehInfo<AircraftVehicleInfo>().max_speed;
}
default: NOT_REACHED();
@@ -392,9 +391,9 @@ uint Engine::GetPower() const
/* Only trains and road vehicles have 'power'. */
switch (this->type) {
case VEH_TRAIN:
return GetEngineProperty(this->index, PROP_TRAIN_POWER, this->u.rail.power);
return GetEngineProperty(this->index, PROP_TRAIN_POWER, this->VehInfo<RailVehicleInfo>().power);
case VEH_ROAD:
return GetEngineProperty(this->index, PROP_ROADVEH_POWER, this->u.road.power) * 10;
return GetEngineProperty(this->index, PROP_ROADVEH_POWER, this->VehInfo<RoadVehicleInfo>().power) * 10;
default: NOT_REACHED();
}
@@ -410,9 +409,9 @@ uint Engine::GetDisplayWeight() const
/* Only trains and road vehicles have 'weight'. */
switch (this->type) {
case VEH_TRAIN:
return GetEngineProperty(this->index, PROP_TRAIN_WEIGHT, this->u.rail.weight) << (this->u.rail.railveh_type == RAILVEH_MULTIHEAD ? 1 : 0);
return GetEngineProperty(this->index, PROP_TRAIN_WEIGHT, this->VehInfo<RailVehicleInfo>().weight) << (this->VehInfo<RailVehicleInfo>().railveh_type == RAILVEH_MULTIHEAD ? 1 : 0);
case VEH_ROAD:
return GetEngineProperty(this->index, PROP_ROADVEH_WEIGHT, this->u.road.weight) / 4;
return GetEngineProperty(this->index, PROP_ROADVEH_WEIGHT, this->VehInfo<RoadVehicleInfo>().weight) / 4;
default: NOT_REACHED();
}
@@ -428,9 +427,9 @@ uint Engine::GetDisplayMaxTractiveEffort() const
/* Only trains and road vehicles have 'tractive effort'. */
switch (this->type) {
case VEH_TRAIN:
return (GROUND_ACCELERATION * this->GetDisplayWeight() * GetEngineProperty(this->index, PROP_TRAIN_TRACTIVE_EFFORT, this->u.rail.tractive_effort)) / 256;
return (GROUND_ACCELERATION * this->GetDisplayWeight() * GetEngineProperty(this->index, PROP_TRAIN_TRACTIVE_EFFORT, this->VehInfo<RailVehicleInfo>().tractive_effort)) / 256;
case VEH_ROAD:
return (GROUND_ACCELERATION * this->GetDisplayWeight() * GetEngineProperty(this->index, PROP_ROADVEH_TRACTIVE_EFFORT, this->u.road.tractive_effort)) / 256;
return (GROUND_ACCELERATION * this->GetDisplayWeight() * GetEngineProperty(this->index, PROP_ROADVEH_TRACTIVE_EFFORT, this->VehInfo<RoadVehicleInfo>().tractive_effort)) / 256;
default: NOT_REACHED();
}
@@ -454,7 +453,7 @@ uint16_t Engine::GetRange() const
{
switch (this->type) {
case VEH_AIRCRAFT:
return GetEngineProperty(this->index, PROP_AIRCRAFT_RANGE, this->u.air.max_range);
return GetEngineProperty(this->index, PROP_AIRCRAFT_RANGE, this->VehInfo<AircraftVehicleInfo>().max_range);
default: NOT_REACHED();
}
@@ -468,7 +467,7 @@ StringID Engine::GetAircraftTypeText() const
{
switch (this->type) {
case VEH_AIRCRAFT:
switch (this->u.air.subtype) {
switch (this->VehInfo<AircraftVehicleInfo>().subtype) {
case AIR_HELI: return STR_LIVERY_HELICOPTER;
case AIR_CTOL: return STR_LIVERY_SMALL_PLANE;
case AIR_CTOL | AIR_FAST: return STR_LIVERY_LARGE_PLANE;
@@ -625,7 +624,7 @@ void ShowEnginePreviewWindow(EngineID engine);
static bool IsWagon(EngineID index)
{
const Engine *e = Engine::Get(index);
return e->type == VEH_TRAIN && e->u.rail.railveh_type == RAILVEH_WAGON;
return e->type == VEH_TRAIN && e->VehInfo<RailVehicleInfo>().railveh_type == RAILVEH_WAGON;
}
/**
@@ -700,7 +699,7 @@ void SetYearEngineAgingStops()
/* Exclude certain engines */
if (!ei->climates.Test(_settings_game.game_creation.landscape)) continue;
if (e->type == VEH_TRAIN && e->u.rail.railveh_type == RAILVEH_WAGON) continue;
if (e->type == VEH_TRAIN && e->VehInfo<RailVehicleInfo>().railveh_type == RAILVEH_WAGON) continue;
/* Base year ending date on half the model life */
TimerGameCalendar::YearMonthDay ymd = TimerGameCalendar::ConvertDateToYMD(ei->base_intro + (ei->lifelength.base() * CalendarTime::DAYS_IN_LEAP_YEAR) / 2);
@@ -1107,13 +1106,13 @@ static void NewVehicleAvailable(Engine *e)
if (e->type == VEH_TRAIN) {
/* maybe make another rail type available */
assert(e->u.rail.railtypes != RailTypes{});
RailTypes introduced = GetAllIntroducesRailTypes(e->u.rail.railtypes);
assert(e->VehInfo<RailVehicleInfo>().railtypes != RailTypes{});
RailTypes introduced = GetAllIntroducesRailTypes(e->VehInfo<RailVehicleInfo>().railtypes);
for (Company *c : Company::Iterate()) c->avail_railtypes = AddDateIntroducedRailTypes(c->avail_railtypes | introduced, TimerGameCalendar::date);
} else if (e->type == VEH_ROAD) {
/* maybe make another road type available */
assert(e->u.road.roadtype < ROADTYPE_END);
for (Company *c : Company::Iterate()) c->avail_roadtypes = AddDateIntroducedRoadTypes(c->avail_roadtypes | GetRoadTypeInfo(e->u.road.roadtype)->introduces_roadtypes, TimerGameCalendar::date);
assert(e->VehInfo<RoadVehicleInfo>().roadtype < ROADTYPE_END);
for (Company *c : Company::Iterate()) c->avail_roadtypes = AddDateIntroducedRoadTypes(c->avail_roadtypes | GetRoadTypeInfo(e->VehInfo<RoadVehicleInfo>().roadtype)->introduces_roadtypes, TimerGameCalendar::date);
}
/* Only broadcast event if AIs are able to build this vehicle type. */
@@ -1268,12 +1267,12 @@ bool IsEngineBuildable(EngineID engine, VehicleType type, CompanyID company)
if (type == VEH_TRAIN && company != OWNER_DEITY) {
/* Check if the rail type is available to this company */
const Company *c = Company::Get(company);
if (!GetAllCompatibleRailTypes(e->u.rail.railtypes).Any(c->avail_railtypes)) return false;
if (!GetAllCompatibleRailTypes(e->VehInfo<RailVehicleInfo>().railtypes).Any(c->avail_railtypes)) return false;
}
if (type == VEH_ROAD && company != OWNER_DEITY) {
/* Check if the road type is available to this company */
const Company *c = Company::Get(company);
if (!GetRoadTypeInfo(e->u.road.roadtype)->powered_roadtypes.Any(c->avail_roadtypes)) return false;
if (!GetRoadTypeInfo(e->VehInfo<RoadVehicleInfo>().roadtype)->powered_roadtypes.Any(c->avail_roadtypes)) return false;
}
return true;
@@ -1319,7 +1318,7 @@ void CheckEngines()
if (!e->IsEnabled()) continue;
/* Don't consider train wagons, we need a powered engine available. */
if (e->type == VEH_TRAIN && e->u.rail.railveh_type == RAILVEH_WAGON) continue;
if (e->type == VEH_TRAIN && e->VehInfo<RailVehicleInfo>().railveh_type == RAILVEH_WAGON) continue;
/* We have an available engine... yay! */
if (e->flags.Test(EngineFlag::Available) && e->company_avail.Any()) return;