File diff r27123:c9e33a591e8d → r27124:237b340de564
src/newgrf.cpp
Show inline comments
 
@@ -564,49 +564,49 @@ StringID MapGRFStringID(uint32 grfid, St
 
		 *
 
		 * Due to how TTDP implements the GRF-local- to global-textid translation
 
		 * texts included via 0x80 or 0x81 control codes have to add 0x400 to the textid.
 
		 * We do not care about that difference and just mask out the 0x400 bit.
 
		 */
 
		str &= ~0x400;
 
		return GetGRFStringID(grfid, str);
 
	} else {
 
		/* The NewGRF wants to include/reference an original TTD string.
 
		 * Try our best to find an equivalent one. */
 
		return TTDPStringIDToOTTDStringIDMapping(str);
 
	}
 
}
 

	
 
static std::map<uint32, uint32> _grf_id_overrides;
 

	
 
/**
 
 * Set the override for a NewGRF
 
 * @param source_grfid The grfID which wants to override another NewGRF.
 
 * @param target_grfid The grfID which is being overridden.
 
 */
 
static void SetNewGRFOverride(uint32 source_grfid, uint32 target_grfid)
 
{
 
	_grf_id_overrides[source_grfid] = target_grfid;
 
	GrfMsg(5, "SetNewGRFOverride: Added override of {:#X} to {:#X}", BSWAP32(source_grfid), BSWAP32(target_grfid));
 
	GrfMsg(5, "SetNewGRFOverride: Added override of 0x{:X} to 0x{:X}", BSWAP32(source_grfid), BSWAP32(target_grfid));
 
}
 

	
 
/**
 
 * Returns the engine associated to a certain internal_id, resp. allocates it.
 
 * @param file NewGRF that wants to change the engine.
 
 * @param type Vehicle type.
 
 * @param internal_id Engine ID inside the NewGRF.
 
 * @param static_access If the engine is not present, return nullptr instead of allocating a new engine. (Used for static Action 0x04).
 
 * @return The requested engine.
 
 */
 
static Engine *GetNewEngine(const GRFFile *file, VehicleType type, uint16 internal_id, bool static_access = false)
 
{
 
	/* Hack for add-on GRFs that need to modify another GRF's engines. This lets
 
	 * them use the same engine slots. */
 
	uint32 scope_grfid = INVALID_GRFID; // If not using dynamic_engines, all newgrfs share their ID range
 
	if (_settings_game.vehicle.dynamic_engines) {
 
		/* If dynamic_engies is enabled, there can be multiple independent ID ranges. */
 
		scope_grfid = file->grfid;
 
		uint32 override = _grf_id_overrides[file->grfid];
 
		if (override != 0) {
 
			scope_grfid = override;
 
			const GRFFile *grf_match = GetFileByGRFID(override);
 
			if (grf_match == nullptr) {
 
				GrfMsg(5, "Tried mapping from GRFID {:x} to {:x} but target is not loaded", BSWAP32(file->grfid), BSWAP32(override));
 
@@ -841,64 +841,64 @@ static void ReadSpriteLayoutRegisters(By
 
 * @param num_building_sprites Number of building sprites to read
 
 * @param use_cur_spritesets   Whether to use currently referenceable action 1 sets.
 
 * @param feature              GrfSpecFeature to use spritesets from.
 
 * @param allow_var10          Whether the spritelayout may specify var10 values for resolving multiple action-1-2-3 chains
 
 * @param no_z_position        Whether bounding boxes have no Z offset
 
 * @param dts                  Layout container to output into
 
 * @return True on error (GRF was disabled).
 
 */
 
static bool ReadSpriteLayout(ByteReader *buf, uint num_building_sprites, bool use_cur_spritesets, byte feature, bool allow_var10, bool no_z_position, NewGRFSpriteLayout *dts)
 
{
 
	bool has_flags = HasBit(num_building_sprites, 6);
 
	ClrBit(num_building_sprites, 6);
 
	TileLayoutFlags valid_flags = TLF_KNOWN_FLAGS;
 
	if (!allow_var10) valid_flags &= ~TLF_VAR10_FLAGS;
 
	dts->Allocate(num_building_sprites); // allocate before reading groundsprite flags
 

	
 
	std::vector<uint16> max_sprite_offset(num_building_sprites + 1, 0);
 
	std::vector<uint16> max_palette_offset(num_building_sprites + 1, 0);
 

	
 
	/* Groundsprite */
 
	TileLayoutFlags flags = ReadSpriteLayoutSprite(buf, has_flags, false, use_cur_spritesets, feature, &dts->ground, max_sprite_offset.data(), max_palette_offset.data());
 
	if (_cur.skip_sprites < 0) return true;
 

	
 
	if (flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS)) {
 
		GrfMsg(1, "ReadSpriteLayout: Spritelayout uses invalid flag {:#X} for ground sprite", flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS));
 
		GrfMsg(1, "ReadSpriteLayout: Spritelayout uses invalid flag 0x{:X} for ground sprite", flags & ~(valid_flags & ~TLF_NON_GROUND_FLAGS));
 
		DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
 
		return true;
 
	}
 

	
 
	ReadSpriteLayoutRegisters(buf, flags, false, dts, 0);
 
	if (_cur.skip_sprites < 0) return true;
 

	
 
	for (uint i = 0; i < num_building_sprites; i++) {
 
		DrawTileSeqStruct *seq = const_cast<DrawTileSeqStruct*>(&dts->seq[i]);
 

	
 
		flags = ReadSpriteLayoutSprite(buf, has_flags, false, use_cur_spritesets, feature, &seq->image, max_sprite_offset.data() + i + 1, max_palette_offset.data() + i + 1);
 
		if (_cur.skip_sprites < 0) return true;
 

	
 
		if (flags & ~valid_flags) {
 
			GrfMsg(1, "ReadSpriteLayout: Spritelayout uses unknown flag {:#X}", flags & ~valid_flags);
 
			GrfMsg(1, "ReadSpriteLayout: Spritelayout uses unknown flag 0x{:X}", flags & ~valid_flags);
 
			DisableGrf(STR_NEWGRF_ERROR_INVALID_SPRITE_LAYOUT);
 
			return true;
 
		}
 

	
 
		seq->delta_x = buf->ReadByte();
 
		seq->delta_y = buf->ReadByte();
 

	
 
		if (!no_z_position) seq->delta_z = buf->ReadByte();
 

	
 
		if (seq->IsParentSprite()) {
 
			seq->size_x = buf->ReadByte();
 
			seq->size_y = buf->ReadByte();
 
			seq->size_z = buf->ReadByte();
 
		}
 

	
 
		ReadSpriteLayoutRegisters(buf, flags, seq->IsParentSprite(), dts, i + 1);
 
		if (_cur.skip_sprites < 0) return true;
 
	}
 

	
 
	/* Check if the number of sprites per spriteset is consistent */
 
	bool is_consistent = true;
 
	dts->consistent_max_offset = 0;
 
	for (uint i = 0; i < num_building_sprites + 1; i++) {
 
		if (max_sprite_offset[i] > 0) {
 
@@ -947,49 +947,49 @@ static CargoTypes TranslateRefitMask(uin
 
		if (cargo != CT_INVALID) SetBit(result, cargo);
 
	}
 
	return result;
 
}
 

	
 
/**
 
 * Converts TTD(P) Base Price pointers into the enum used by OTTD
 
 * See http://wiki.ttdpatch.net/tiki-index.php?page=BaseCosts
 
 * @param base_pointer TTD(P) Base Price Pointer
 
 * @param error_location Function name for grf error messages
 
 * @param[out] index If \a base_pointer is valid, \a index is assigned to the matching price; else it is left unchanged
 
 */
 
static void ConvertTTDBasePrice(uint32 base_pointer, const char *error_location, Price *index)
 
{
 
	/* Special value for 'none' */
 
	if (base_pointer == 0) {
 
		*index = INVALID_PRICE;
 
		return;
 
	}
 

	
 
	static const uint32 start = 0x4B34; ///< Position of first base price
 
	static const uint32 size  = 6;      ///< Size of each base price record
 

	
 
	if (base_pointer < start || (base_pointer - start) % size != 0 || (base_pointer - start) / size >= PR_END) {
 
		GrfMsg(1, "{}: Unsupported running cost base {:#04X}, ignoring", error_location, base_pointer);
 
		GrfMsg(1, "{}: Unsupported running cost base 0x{:04X}, ignoring", error_location, base_pointer);
 
		return;
 
	}
 

	
 
	*index = (Price)((base_pointer - start) / size);
 
}
 

	
 
/** Possible return values for the FeatureChangeInfo functions */
 
enum ChangeInfoResult {
 
	CIR_SUCCESS,    ///< Variable was parsed and read
 
	CIR_DISABLED,   ///< GRF was disabled due to error
 
	CIR_UNHANDLED,  ///< Variable was parsed but unread
 
	CIR_UNKNOWN,    ///< Variable is unknown
 
	CIR_INVALID_ID, ///< Attempt to modify an invalid ID
 
};
 

	
 
typedef ChangeInfoResult (*VCI_Handler)(uint engine, int numinfo, int prop, ByteReader *buf);
 

	
 
/**
 
 * Define properties common to all vehicles
 
 * @param ei Engine info.
 
 * @param prop The property to change.
 
 * @param buf The property value.
 
 * @return ChangeInfoResult.
 
 */
 
@@ -2115,49 +2115,49 @@ static ChangeInfoResult StationChangeInf
 
			}
 

	
 
			default:
 
				ret = CIR_UNKNOWN;
 
				break;
 
		}
 
	}
 

	
 
	return ret;
 
}
 

	
 
/**
 
 * Define properties for water features
 
 * @param id Type of the first water feature.
 
 * @param numinfo Number of subsequent water feature ids to change the property for.
 
 * @param prop The property to change.
 
 * @param buf The property value.
 
 * @return ChangeInfoResult.
 
 */
 
static ChangeInfoResult CanalChangeInfo(uint id, int numinfo, int prop, ByteReader *buf)
 
{
 
	ChangeInfoResult ret = CIR_SUCCESS;
 

	
 
	if (id + numinfo > CF_END) {
 
		GrfMsg(1, "CanalChangeInfo: Canal feature {:#02X} is invalid, max {}, ignoring", id + numinfo, CF_END);
 
		GrfMsg(1, "CanalChangeInfo: Canal feature 0x{:02X} is invalid, max {}, ignoring", id + numinfo, CF_END);
 
		return CIR_INVALID_ID;
 
	}
 

	
 
	for (int i = 0; i < numinfo; i++) {
 
		CanalProperties *cp = &_cur.grffile->canal_local_properties[id + i];
 

	
 
		switch (prop) {
 
			case 0x08:
 
				cp->callback_mask = buf->ReadByte();
 
				break;
 

	
 
			case 0x09:
 
				cp->flags = buf->ReadByte();
 
				break;
 

	
 
			default:
 
				ret = CIR_UNKNOWN;
 
				break;
 
		}
 
	}
 

	
 
	return ret;
 
}
 

	
 
@@ -4096,49 +4096,49 @@ static ChangeInfoResult ObjectChangeInfo
 

	
 
				/* Swap classid because we read it in BE. */
 
				uint32 classid = buf->ReadDWord();
 
				spec->cls_id = ObjectClass::Allocate(BSWAP32(classid));
 
				break;
 
			}
 

	
 
			case 0x09: { // Class name
 
				ObjectClass *objclass = ObjectClass::Get(spec->cls_id);
 
				AddStringForMapping(buf->ReadWord(), &objclass->name);
 
				break;
 
			}
 

	
 
			case 0x0A: // Object name
 
				AddStringForMapping(buf->ReadWord(), &spec->name);
 
				break;
 

	
 
			case 0x0B: // Climate mask
 
				spec->climate = buf->ReadByte();
 
				break;
 

	
 
			case 0x0C: // Size
 
				spec->size = buf->ReadByte();
 
				if (GB(spec->size, 0, 4) == 0 || GB(spec->size, 4, 4) == 0) {
 
					GrfMsg(0, "ObjectChangeInfo: Invalid object size requested ({:#X}) for object id {}. Ignoring.", spec->size, id + i);
 
					GrfMsg(0, "ObjectChangeInfo: Invalid object size requested (0x{:X}) for object id {}. Ignoring.", spec->size, id + i);
 
					spec->size = OBJECT_SIZE_1X1;
 
				}
 
				break;
 

	
 
			case 0x0D: // Build cost multipler
 
				spec->build_cost_multiplier = buf->ReadByte();
 
				spec->clear_cost_multiplier = spec->build_cost_multiplier;
 
				break;
 

	
 
			case 0x0E: // Introduction date
 
				spec->introduction_date = buf->ReadDWord();
 
				break;
 

	
 
			case 0x0F: // End of life
 
				spec->end_of_life_date = buf->ReadDWord();
 
				break;
 

	
 
			case 0x10: // Flags
 
				spec->flags = (ObjectFlags)buf->ReadWord();
 
				_loaded_newgrf_features.has_2CC |= (spec->flags & OBJECT_FLAG_2CC_COLOUR) != 0;
 
				break;
 

	
 
			case 0x11: // Animation info
 
				spec->animation.frames = buf->ReadByte();
 
@@ -4830,53 +4830,53 @@ static ChangeInfoResult RoadStopChangeIn
 
				break;
 

	
 
			default:
 
				ret = CIR_UNKNOWN;
 
				break;
 
		}
 
	}
 

	
 
	return ret;
 
}
 

	
 
static bool HandleChangeInfoResult(const char *caller, ChangeInfoResult cir, uint8 feature, uint8 property)
 
{
 
	switch (cir) {
 
		default: NOT_REACHED();
 

	
 
		case CIR_DISABLED:
 
			/* Error has already been printed; just stop parsing */
 
			return true;
 

	
 
		case CIR_SUCCESS:
 
			return false;
 

	
 
		case CIR_UNHANDLED:
 
			GrfMsg(1, "{}: Ignoring property {:#02X} of feature {:#02X} (not implemented)", caller, property, feature);
 
			GrfMsg(1, "{}: Ignoring property 0x{:02X} of feature 0x{:02X} (not implemented)", caller, property, feature);
 
			return false;
 

	
 
		case CIR_UNKNOWN:
 
			GrfMsg(0, "{}: Unknown property {:#02X} of feature {:#02X}, disabling", caller, property, feature);
 
			GrfMsg(0, "{}: Unknown property 0x{:02X} of feature 0x{:02X}, disabling", caller, property, feature);
 
			FALLTHROUGH;
 

	
 
		case CIR_INVALID_ID: {
 
			/* No debug message for an invalid ID, as it has already been output */
 
			GRFError *error = DisableGrf(cir == CIR_INVALID_ID ? STR_NEWGRF_ERROR_INVALID_ID : STR_NEWGRF_ERROR_UNKNOWN_PROPERTY);
 
			if (cir != CIR_INVALID_ID) error->param_value[1] = property;
 
			return true;
 
		}
 
	}
 
}
 

	
 
/* Action 0x00 */
 
static void FeatureChangeInfo(ByteReader *buf)
 
{
 
	/* <00> <feature> <num-props> <num-info> <id> (<property <new-info>)...
 
	 *
 
	 * B feature
 
	 * B num-props     how many properties to change per vehicle/station
 
	 * B num-info      how many vehicles/stations to change
 
	 * E id            ID of first vehicle/station to change, if num-info is
 
	 *                 greater than one, this one and the following
 
	 *                 vehicles/stations will be changed
 
	 * B property      what property to change, depends on the feature
 
	 * V new-info      new bytes of info (variable size; depends on properties) */
 
@@ -4891,57 +4891,57 @@ static void FeatureChangeInfo(ByteReader
 
		/* GSF_BRIDGES */       BridgeChangeInfo,
 
		/* GSF_HOUSES */        TownHouseChangeInfo,
 
		/* GSF_GLOBALVAR */     GlobalVarChangeInfo,
 
		/* GSF_INDUSTRYTILES */ IndustrytilesChangeInfo,
 
		/* GSF_INDUSTRIES */    IndustriesChangeInfo,
 
		/* GSF_CARGOES */       nullptr, // Cargo is handled during reservation
 
		/* GSF_SOUNDFX */       SoundEffectChangeInfo,
 
		/* GSF_AIRPORTS */      AirportChangeInfo,
 
		/* GSF_SIGNALS */       nullptr,
 
		/* GSF_OBJECTS */       ObjectChangeInfo,
 
		/* GSF_RAILTYPES */     RailTypeChangeInfo,
 
		/* GSF_AIRPORTTILES */  AirportTilesChangeInfo,
 
		/* GSF_ROADTYPES */     RoadTypeChangeInfo,
 
		/* GSF_TRAMTYPES */     TramTypeChangeInfo,
 
		/* GSF_ROADSTOPS */     RoadStopChangeInfo,
 
	};
 
	static_assert(GSF_END == lengthof(handler));
 

	
 
	uint8 feature  = buf->ReadByte();
 
	uint8 numprops = buf->ReadByte();
 
	uint numinfo  = buf->ReadByte();
 
	uint engine   = buf->ReadExtendedByte();
 

	
 
	if (feature >= GSF_END) {
 
		GrfMsg(1, "FeatureChangeInfo: Unsupported feature {:#02X}, skipping", feature);
 
		GrfMsg(1, "FeatureChangeInfo: Unsupported feature 0x{:02X}, skipping", feature);
 
		return;
 
	}
 

	
 
	GrfMsg(6, "FeatureChangeInfo: Feature {:#02X}, {} properties, to apply to {}+{}",
 
	GrfMsg(6, "FeatureChangeInfo: Feature 0x{:02X}, {} properties, to apply to {}+{}",
 
	               feature, numprops, engine, numinfo);
 

	
 
	if (handler[feature] == nullptr) {
 
		if (feature != GSF_CARGOES) GrfMsg(1, "FeatureChangeInfo: Unsupported feature {:#02X}, skipping", feature);
 
		if (feature != GSF_CARGOES) GrfMsg(1, "FeatureChangeInfo: Unsupported feature 0x{:02X}, skipping", feature);
 
		return;
 
	}
 

	
 
	/* Mark the feature as used by the grf */
 
	SetBit(_cur.grffile->grf_features, feature);
 

	
 
	while (numprops-- && buf->HasData()) {
 
		uint8 prop = buf->ReadByte();
 

	
 
		ChangeInfoResult cir = handler[feature](engine, numinfo, prop, buf);
 
		if (HandleChangeInfoResult("FeatureChangeInfo", cir, feature, prop)) return;
 
	}
 
}
 

	
 
/* Action 0x00 (GLS_SAFETYSCAN) */
 
static void SafeChangeInfo(ByteReader *buf)
 
{
 
	uint8 feature  = buf->ReadByte();
 
	uint8 numprops = buf->ReadByte();
 
	uint numinfo = buf->ReadByte();
 
	buf->ReadExtendedByte(); // id
 

	
 
	if (feature == GSF_BRIDGES && numprops == 1) {
 
		uint8 prop = buf->ReadByte();
 
@@ -5024,148 +5024,148 @@ static void NewSpriteSet(ByteReader *buf
 
	 *                 0, 1, 2, 3: veh-type, 4: train stations
 
	 * E first-set     first sprite set to define
 
	 * B num-sets      number of sprite sets (extended byte in extended format)
 
	 * E num-ent       how many entries per sprite set
 
	 *                 For vehicles, this is the number of different
 
	 *                         vehicle directions in each sprite set
 
	 *                         Set num-dirs=8, unless your sprites are symmetric.
 
	 *                         In that case, use num-dirs=4.
 
	 */
 

	
 
	uint8  feature   = buf->ReadByte();
 
	uint16 num_sets  = buf->ReadByte();
 
	uint16 first_set = 0;
 

	
 
	if (num_sets == 0 && buf->HasData(3)) {
 
		/* Extended Action1 format.
 
		 * Some GRFs define zero sets of zero sprites, though there is actually no use in that. Ignore them. */
 
		first_set = buf->ReadExtendedByte();
 
		num_sets = buf->ReadExtendedByte();
 
	}
 
	uint16 num_ents = buf->ReadExtendedByte();
 

	
 
	if (feature >= GSF_END) {
 
		_cur.skip_sprites = num_sets * num_ents;
 
		GrfMsg(1, "NewSpriteSet: Unsupported feature {:#02X}, skipping {} sprites", feature, _cur.skip_sprites);
 
		GrfMsg(1, "NewSpriteSet: Unsupported feature 0x{:02X}, skipping {} sprites", feature, _cur.skip_sprites);
 
		return;
 
	}
 

	
 
	_cur.AddSpriteSets(feature, _cur.spriteid, first_set, num_sets, num_ents);
 

	
 
	GrfMsg(7, "New sprite set at {} of feature {:#02X}, consisting of {} sets with {} views each (total {})",
 
	GrfMsg(7, "New sprite set at {} of feature 0x{:02X}, consisting of {} sets with {} views each (total {})",
 
		_cur.spriteid, feature, num_sets, num_ents, num_sets * num_ents
 
	);
 

	
 
	for (int i = 0; i < num_sets * num_ents; i++) {
 
		_cur.nfo_line++;
 
		LoadNextSprite(_cur.spriteid++, *_cur.file, _cur.nfo_line);
 
	}
 
}
 

	
 
/* Action 0x01 (SKIP) */
 
static void SkipAct1(ByteReader *buf)
 
{
 
	buf->ReadByte();
 
	uint16 num_sets  = buf->ReadByte();
 

	
 
	if (num_sets == 0 && buf->HasData(3)) {
 
		/* Extended Action1 format.
 
		 * Some GRFs define zero sets of zero sprites, though there is actually no use in that. Ignore them. */
 
		buf->ReadExtendedByte(); // first_set
 
		num_sets = buf->ReadExtendedByte();
 
	}
 
	uint16 num_ents = buf->ReadExtendedByte();
 

	
 
	_cur.skip_sprites = num_sets * num_ents;
 

	
 
	GrfMsg(3, "SkipAct1: Skipping {} sprites", _cur.skip_sprites);
 
}
 

	
 
/* Helper function to either create a callback or link to a previously
 
 * defined spritegroup. */
 
static const SpriteGroup *GetGroupFromGroupID(byte setid, byte type, uint16 groupid)
 
{
 
	if (HasBit(groupid, 15)) {
 
		assert(CallbackResultSpriteGroup::CanAllocateItem());
 
		return new CallbackResultSpriteGroup(groupid, _cur.grffile->grf_version >= 8);
 
	}
 

	
 
	if (groupid > MAX_SPRITEGROUP || _cur.spritegroups[groupid] == nullptr) {
 
		GrfMsg(1, "GetGroupFromGroupID({:#02X}:{:#02X}): Groupid {:#04X} does not exist, leaving empty", setid, type, groupid);
 
		GrfMsg(1, "GetGroupFromGroupID(0x{:02X}:0x{:02X}): Groupid 0x{:04X} does not exist, leaving empty", setid, type, groupid);
 
		return nullptr;
 
	}
 

	
 
	return _cur.spritegroups[groupid];
 
}
 

	
 
/**
 
 * Helper function to either create a callback or a result sprite group.
 
 * @param feature GrfSpecFeature to define spritegroup for.
 
 * @param setid SetID of the currently being parsed Action2. (only for debug output)
 
 * @param type Type of the currently being parsed Action2. (only for debug output)
 
 * @param spriteid Raw value from the GRF for the new spritegroup; describes either the return value or the referenced spritegroup.
 
 * @return Created spritegroup.
 
 */
 
static const SpriteGroup *CreateGroupFromGroupID(byte feature, byte setid, byte type, uint16 spriteid)
 
{
 
	if (HasBit(spriteid, 15)) {
 
		assert(CallbackResultSpriteGroup::CanAllocateItem());
 
		return new CallbackResultSpriteGroup(spriteid, _cur.grffile->grf_version >= 8);
 
	}
 

	
 
	if (!_cur.IsValidSpriteSet(feature, spriteid)) {
 
		GrfMsg(1, "CreateGroupFromGroupID({:#02X}:{:#02X}): Sprite set {} invalid", setid, type, spriteid);
 
		GrfMsg(1, "CreateGroupFromGroupID(0x{:02X}:0x{:02X}): Sprite set {} invalid", setid, type, spriteid);
 
		return nullptr;
 
	}
 

	
 
	SpriteID spriteset_start = _cur.GetSprite(feature, spriteid);
 
	uint num_sprites = _cur.GetNumEnts(feature, spriteid);
 

	
 
	/* Ensure that the sprites are loeded */
 
	assert(spriteset_start + num_sprites <= _cur.spriteid);
 

	
 
	assert(ResultSpriteGroup::CanAllocateItem());
 
	return new ResultSpriteGroup(spriteset_start, num_sprites);
 
}
 

	
 
/* Action 0x02 */
 
static void NewSpriteGroup(ByteReader *buf)
 
{
 
	/* <02> <feature> <set-id> <type/num-entries> <feature-specific-data...>
 
	 *
 
	 * B feature       see action 1
 
	 * B set-id        ID of this particular definition
 
	 * B type/num-entries
 
	 *                 if 80 or greater, this is a randomized or variational
 
	 *                 list definition, see below
 
	 *                 otherwise it specifies a number of entries, the exact
 
	 *                 meaning depends on the feature
 
	 * V feature-specific-data (huge mess, don't even look it up --pasky) */
 
	const SpriteGroup *act_group = nullptr;
 

	
 
	uint8 feature = buf->ReadByte();
 
	if (feature >= GSF_END) {
 
		GrfMsg(1, "NewSpriteGroup: Unsupported feature {:#02X}, skipping", feature);
 
		GrfMsg(1, "NewSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
 
		return;
 
	}
 

	
 
	uint8 setid   = buf->ReadByte();
 
	uint8 type    = buf->ReadByte();
 

	
 
	/* Sprite Groups are created here but they are allocated from a pool, so
 
	 * we do not need to delete anything if there is an exception from the
 
	 * ByteReader. */
 

	
 
	switch (type) {
 
		/* Deterministic Sprite Group */
 
		case 0x81: // Self scope, byte
 
		case 0x82: // Parent scope, byte
 
		case 0x85: // Self scope, word
 
		case 0x86: // Parent scope, word
 
		case 0x89: // Self scope, dword
 
		case 0x8A: // Parent scope, dword
 
		{
 
			byte varadjust;
 
			byte varsize;
 

	
 
			assert(DeterministicSpriteGroup::CanAllocateItem());
 
			DeterministicSpriteGroup *group = new DeterministicSpriteGroup();
 
@@ -5300,49 +5300,49 @@ static void NewSpriteGroup(ByteReader *b
 
		/* Neither a variable or randomized sprite group... must be a real group */
 
		default:
 
		{
 
			switch (feature) {
 
				case GSF_TRAINS:
 
				case GSF_ROADVEHICLES:
 
				case GSF_SHIPS:
 
				case GSF_AIRCRAFT:
 
				case GSF_STATIONS:
 
				case GSF_CANALS:
 
				case GSF_CARGOES:
 
				case GSF_AIRPORTS:
 
				case GSF_RAILTYPES:
 
				case GSF_ROADTYPES:
 
				case GSF_TRAMTYPES:
 
				{
 
					byte num_loaded  = type;
 
					byte num_loading = buf->ReadByte();
 

	
 
					if (!_cur.HasValidSpriteSets(feature)) {
 
						GrfMsg(0, "NewSpriteGroup: No sprite set to work on! Skipping");
 
						return;
 
					}
 

	
 
					GrfMsg(6, "NewSpriteGroup: New SpriteGroup {:#02X}, {} loaded, {} loading",
 
					GrfMsg(6, "NewSpriteGroup: New SpriteGroup 0x{:02X}, {} loaded, {} loading",
 
							setid, num_loaded, num_loading);
 

	
 
					if (num_loaded + num_loading == 0) {
 
						GrfMsg(1, "NewSpriteGroup: no result, skipping invalid RealSpriteGroup");
 
						break;
 
					}
 

	
 
					if (num_loaded + num_loading == 1) {
 
						/* Avoid creating 'Real' sprite group if only one option. */
 
						uint16 spriteid = buf->ReadWord();
 
						act_group = CreateGroupFromGroupID(feature, setid, type, spriteid);
 
						GrfMsg(8, "NewSpriteGroup: one result, skipping RealSpriteGroup = subset {}", spriteid);
 
						break;
 
					}
 

	
 
					std::vector<uint16> loaded;
 
					std::vector<uint16> loading;
 

	
 
					for (uint i = 0; i < num_loaded; i++) {
 
						loaded.push_back(buf->ReadWord());
 
						GrfMsg(8, "NewSpriteGroup: + rg->loaded[{}]  = subset {}", i, loaded[i]);
 
					}
 

	
 
					for (uint i = 0; i < num_loading; i++) {
 
@@ -5456,49 +5456,49 @@ static void NewSpriteGroup(ByteReader *b
 
							return;
 
						}
 
						for (uint i = 0; i < group->num_output; i++) {
 
							byte rawcargo = buf->ReadByte();
 
							CargoID cargo = GetCargoTranslation(rawcargo, _cur.grffile);
 
							if (cargo == CT_INVALID) {
 
								/* Mark this result as invalid to use */
 
								group->version = 0xFF;
 
							} else if (std::find(group->cargo_output, group->cargo_output + i, cargo) != group->cargo_output + i) {
 
								GRFError *error = DisableGrf(STR_NEWGRF_ERROR_INDPROD_CALLBACK);
 
								error->data = "duplicate output cargo";
 
								return;
 
							}
 
							group->cargo_output[i] = cargo;
 
							group->add_output[i] = buf->ReadByte();
 
						}
 
						group->again = buf->ReadByte();
 
					} else {
 
						NOT_REACHED();
 
					}
 
					break;
 
				}
 

	
 
				/* Loading of Tile Layout and Production Callback groups would happen here */
 
				default: GrfMsg(1, "NewSpriteGroup: Unsupported feature {:#02X}, skipping", feature);
 
				default: GrfMsg(1, "NewSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
 
			}
 
		}
 
	}
 

	
 
	_cur.spritegroups[setid] = act_group;
 
}
 

	
 
static CargoID TranslateCargo(uint8 feature, uint8 ctype)
 
{
 
	if (feature == GSF_OBJECTS) {
 
		switch (ctype) {
 
			case 0:    return 0;
 
			case 0xFF: return CT_PURCHASE_OBJECT;
 
			default:
 
				GrfMsg(1, "TranslateCargo: Invalid cargo bitnum {} for objects, skipping.", ctype);
 
				return CT_INVALID;
 
		}
 
	}
 
	/* Special cargo types for purchase list and stations */
 
	if ((feature == GSF_STATIONS || feature == GSF_ROADSTOPS) && ctype == 0xFE) return CT_DEFAULT_NA;
 
	if (ctype == 0xFF) return CT_PURCHASE;
 

	
 
	if (_cur.grffile->cargo_list.size() == 0) {
 
		/* No cargo table, so use bitnum values */
 
@@ -5524,49 +5524,49 @@ static CargoID TranslateCargo(uint8 feat
 
		return CT_INVALID;
 
	}
 

	
 
	/* Look up the cargo label from the translation table */
 
	CargoLabel cl = _cur.grffile->cargo_list[ctype];
 
	if (cl == 0) {
 
		GrfMsg(5, "TranslateCargo: Cargo type {} not available in this climate, skipping.", ctype);
 
		return CT_INVALID;
 
	}
 

	
 
	ctype = GetCargoIDByLabel(cl);
 
	if (ctype == CT_INVALID) {
 
		GrfMsg(5, "TranslateCargo: Cargo '{:c}{:c}{:c}{:c}' unsupported, skipping.", GB(cl, 24, 8), GB(cl, 16, 8), GB(cl, 8, 8), GB(cl, 0, 8));
 
		return CT_INVALID;
 
	}
 

	
 
	GrfMsg(6, "TranslateCargo: Cargo '{:c}{:c}{:c}{:c}' mapped to cargo type {}.", GB(cl, 24, 8), GB(cl, 16, 8), GB(cl, 8, 8), GB(cl, 0, 8), ctype);
 
	return ctype;
 
}
 

	
 

	
 
static bool IsValidGroupID(uint16 groupid, const char *function)
 
{
 
	if (groupid > MAX_SPRITEGROUP || _cur.spritegroups[groupid] == nullptr) {
 
		GrfMsg(1, "{}: Spritegroup {:#04X} out of range or empty, skipping.", function, groupid);
 
		GrfMsg(1, "{}: Spritegroup 0x{:04X} out of range or empty, skipping.", function, groupid);
 
		return false;
 
	}
 

	
 
	return true;
 
}
 

	
 
static void VehicleMapSpriteGroup(ByteReader *buf, byte feature, uint8 idcount)
 
{
 
	static EngineID *last_engines;
 
	static uint last_engines_count;
 
	bool wagover = false;
 

	
 
	/* Test for 'wagon override' flag */
 
	if (HasBit(idcount, 7)) {
 
		wagover = true;
 
		/* Strip off the flag */
 
		idcount = GB(idcount, 0, 7);
 

	
 
		if (last_engines_count == 0) {
 
			GrfMsg(0, "VehicleMapSpriteGroup: WagonOverride: No engine to do override with");
 
			return;
 
		}
 

	
 
		GrfMsg(6, "VehicleMapSpriteGroup: WagonOverride: {} engines, {} wagons",
 
@@ -5578,70 +5578,70 @@ static void VehicleMapSpriteGroup(ByteRe
 
		}
 
	}
 

	
 
	std::vector<EngineID> engines;
 
	for (uint i = 0; i < idcount; i++) {
 
		Engine *e = GetNewEngine(_cur.grffile, (VehicleType)feature, buf->ReadExtendedByte());
 
		if (e == nullptr) {
 
			/* No engine could be allocated?!? Deal with it. Okay,
 
			 * this might look bad. Also make sure this NewGRF
 
			 * gets disabled, as a half loaded one is bad. */
 
			HandleChangeInfoResult("VehicleMapSpriteGroup", CIR_INVALID_ID, 0, 0);
 
			return;
 
		}
 

	
 
		engines.push_back(e->index);
 
		if (!wagover) last_engines[i] = engines[i];
 
	}
 

	
 
	uint8 cidcount = buf->ReadByte();
 
	for (uint c = 0; c < cidcount; c++) {
 
		uint8 ctype = buf->ReadByte();
 
		uint16 groupid = buf->ReadWord();
 
		if (!IsValidGroupID(groupid, "VehicleMapSpriteGroup")) continue;
 

	
 
		GrfMsg(8, "VehicleMapSpriteGroup: * [{}] Cargo type {:#X}, group id {:#02X}", c, ctype, groupid);
 
		GrfMsg(8, "VehicleMapSpriteGroup: * [{}] Cargo type 0x{:X}, group id 0x{:02X}", c, ctype, groupid);
 

	
 
		ctype = TranslateCargo(feature, ctype);
 
		if (ctype == CT_INVALID) continue;
 

	
 
		for (uint i = 0; i < idcount; i++) {
 
			EngineID engine = engines[i];
 

	
 
			GrfMsg(7, "VehicleMapSpriteGroup: [{}] Engine {}...", i, engine);
 

	
 
			if (wagover) {
 
				SetWagonOverrideSprites(engine, ctype, _cur.spritegroups[groupid], last_engines, last_engines_count);
 
			} else {
 
				SetCustomEngineSprites(engine, ctype, _cur.spritegroups[groupid]);
 
			}
 
		}
 
	}
 

	
 
	uint16 groupid = buf->ReadWord();
 
	if (!IsValidGroupID(groupid, "VehicleMapSpriteGroup")) return;
 

	
 
	GrfMsg(8, "-- Default group id {:#04X}", groupid);
 
	GrfMsg(8, "-- Default group id 0x{:04X}", groupid);
 

	
 
	for (uint i = 0; i < idcount; i++) {
 
		EngineID engine = engines[i];
 

	
 
		if (wagover) {
 
			SetWagonOverrideSprites(engine, CT_DEFAULT, _cur.spritegroups[groupid], last_engines, last_engines_count);
 
		} else {
 
			SetCustomEngineSprites(engine, CT_DEFAULT, _cur.spritegroups[groupid]);
 
			SetEngineGRF(engine, _cur.grffile);
 
		}
 
	}
 
}
 

	
 

	
 
static void CanalMapSpriteGroup(ByteReader *buf, uint8 idcount)
 
{
 
	std::vector<CanalFeature> cfs;
 
	cfs.reserve(idcount);
 
	for (uint i = 0; i < idcount; i++) {
 
		cfs.push_back((CanalFeature)buf->ReadByte());
 
	}
 

	
 
	uint8 cidcount = buf->ReadByte();
 
	buf->Skip(cidcount * 3);
 
@@ -5666,69 +5666,69 @@ static void StationMapSpriteGroup(ByteRe
 
	if (_cur.grffile->stations.empty()) {
 
		GrfMsg(1, "StationMapSpriteGroup: No stations defined, skipping");
 
		return;
 
	}
 

	
 
	std::vector<uint8> stations;
 
	stations.reserve(idcount);
 
	for (uint i = 0; i < idcount; i++) {
 
		stations.push_back(buf->ReadByte());
 
	}
 

	
 
	uint8 cidcount = buf->ReadByte();
 
	for (uint c = 0; c < cidcount; c++) {
 
		uint8 ctype = buf->ReadByte();
 
		uint16 groupid = buf->ReadWord();
 
		if (!IsValidGroupID(groupid, "StationMapSpriteGroup")) continue;
 

	
 
		ctype = TranslateCargo(GSF_STATIONS, ctype);
 
		if (ctype == CT_INVALID) continue;
 

	
 
		for (auto &station : stations) {
 
			StationSpec *statspec = station >= _cur.grffile->stations.size() ? nullptr : _cur.grffile->stations[station].get();
 

	
 
			if (statspec == nullptr) {
 
				GrfMsg(1, "StationMapSpriteGroup: Station with ID {:#02X} does not exist, skipping", station);
 
				GrfMsg(1, "StationMapSpriteGroup: Station with ID 0x{:02X} does not exist, skipping", station);
 
				continue;
 
			}
 

	
 
			statspec->grf_prop.spritegroup[ctype] = _cur.spritegroups[groupid];
 
		}
 
	}
 

	
 
	uint16 groupid = buf->ReadWord();
 
	if (!IsValidGroupID(groupid, "StationMapSpriteGroup")) return;
 

	
 
	for (auto &station : stations) {
 
		StationSpec *statspec = station >= _cur.grffile->stations.size() ? nullptr : _cur.grffile->stations[station].get();
 

	
 
		if (statspec == nullptr) {
 
			GrfMsg(1, "StationMapSpriteGroup: Station with ID {:#02X} does not exist, skipping", station);
 
			GrfMsg(1, "StationMapSpriteGroup: Station with ID 0x{:02X} does not exist, skipping", station);
 
			continue;
 
		}
 

	
 
		if (statspec->grf_prop.grffile != nullptr) {
 
			GrfMsg(1, "StationMapSpriteGroup: Station with ID {:#02X} mapped multiple times, skipping", station);
 
			GrfMsg(1, "StationMapSpriteGroup: Station with ID 0x{:02X} mapped multiple times, skipping", station);
 
			continue;
 
		}
 

	
 
		statspec->grf_prop.spritegroup[CT_DEFAULT] = _cur.spritegroups[groupid];
 
		statspec->grf_prop.grffile = _cur.grffile;
 
		statspec->grf_prop.local_id = station;
 
		StationClass::Assign(statspec);
 
	}
 
}
 

	
 

	
 
static void TownHouseMapSpriteGroup(ByteReader *buf, uint8 idcount)
 
{
 
	if (_cur.grffile->housespec.empty()) {
 
		GrfMsg(1, "TownHouseMapSpriteGroup: No houses defined, skipping");
 
		return;
 
	}
 

	
 
	std::vector<uint8> houses;
 
	houses.reserve(idcount);
 
	for (uint i = 0; i < idcount; i++) {
 
		houses.push_back(buf->ReadByte());
 
	}
 

	
 
@@ -5847,69 +5847,69 @@ static void ObjectMapSpriteGroup(ByteRea
 
	if (_cur.grffile->objectspec.empty()) {
 
		GrfMsg(1, "ObjectMapSpriteGroup: No object tiles defined, skipping");
 
		return;
 
	}
 

	
 
	std::vector<uint8> objects;
 
	objects.reserve(idcount);
 
	for (uint i = 0; i < idcount; i++) {
 
		objects.push_back(buf->ReadByte());
 
	}
 

	
 
	uint8 cidcount = buf->ReadByte();
 
	for (uint c = 0; c < cidcount; c++) {
 
		uint8 ctype = buf->ReadByte();
 
		uint16 groupid = buf->ReadWord();
 
		if (!IsValidGroupID(groupid, "ObjectMapSpriteGroup")) continue;
 

	
 
		ctype = TranslateCargo(GSF_OBJECTS, ctype);
 
		if (ctype == CT_INVALID) continue;
 

	
 
		for (auto &object : objects) {
 
			ObjectSpec *spec = object >= _cur.grffile->objectspec.size() ? nullptr : _cur.grffile->objectspec[object].get();
 

	
 
			if (spec == nullptr) {
 
				GrfMsg(1, "ObjectMapSpriteGroup: Object with ID {:#02X} undefined, skipping", object);
 
				GrfMsg(1, "ObjectMapSpriteGroup: Object with ID 0x{:02X} undefined, skipping", object);
 
				continue;
 
			}
 

	
 
			spec->grf_prop.spritegroup[ctype] = _cur.spritegroups[groupid];
 
		}
 
	}
 

	
 
	uint16 groupid = buf->ReadWord();
 
	if (!IsValidGroupID(groupid, "ObjectMapSpriteGroup")) return;
 

	
 
	for (auto &object : objects) {
 
		ObjectSpec *spec = object >= _cur.grffile->objectspec.size() ? nullptr : _cur.grffile->objectspec[object].get();
 

	
 
		if (spec == nullptr) {
 
			GrfMsg(1, "ObjectMapSpriteGroup: Object with ID {:#02X} undefined, skipping", object);
 
			GrfMsg(1, "ObjectMapSpriteGroup: Object with ID 0x{:02X} undefined, skipping", object);
 
			continue;
 
		}
 

	
 
		if (spec->grf_prop.grffile != nullptr) {
 
			GrfMsg(1, "ObjectMapSpriteGroup: Object with ID {:#02X} mapped multiple times, skipping", object);
 
			GrfMsg(1, "ObjectMapSpriteGroup: Object with ID 0x{:02X} mapped multiple times, skipping", object);
 
			continue;
 
		}
 

	
 
		spec->grf_prop.spritegroup[0] = _cur.spritegroups[groupid];
 
		spec->grf_prop.grffile        = _cur.grffile;
 
		spec->grf_prop.local_id       = object;
 
	}
 
}
 

	
 
static void RailTypeMapSpriteGroup(ByteReader *buf, uint8 idcount)
 
{
 
	std::vector<uint8> railtypes;
 
	railtypes.reserve(idcount);
 
	for (uint i = 0; i < idcount; i++) {
 
		uint8 id = buf->ReadByte();
 
		railtypes.push_back(id < RAILTYPE_END ? _cur.grffile->railtype_map[id] : INVALID_RAILTYPE);
 
	}
 

	
 
	uint8 cidcount = buf->ReadByte();
 
	for (uint c = 0; c < cidcount; c++) {
 
		uint8 ctype = buf->ReadByte();
 
		uint16 groupid = buf->ReadWord();
 
		if (!IsValidGroupID(groupid, "RailTypeMapSpriteGroup")) continue;
 

	
 
@@ -6033,121 +6033,121 @@ static void RoadStopMapSpriteGroup(ByteR
 
	if (_cur.grffile->roadstops.empty()) {
 
		GrfMsg(1, "RoadStopMapSpriteGroup: No roadstops defined, skipping");
 
		return;
 
	}
 

	
 
	std::vector<uint8> roadstops;
 
	roadstops.reserve(idcount);
 
	for (uint i = 0; i < idcount; i++) {
 
		roadstops.push_back(buf->ReadByte());
 
	}
 

	
 
	uint8 cidcount = buf->ReadByte();
 
	for (uint c = 0; c < cidcount; c++) {
 
		uint8 ctype = buf->ReadByte();
 
		uint16 groupid = buf->ReadWord();
 
		if (!IsValidGroupID(groupid, "RoadStopMapSpriteGroup")) continue;
 

	
 
		ctype = TranslateCargo(GSF_ROADSTOPS, ctype);
 
		if (ctype == CT_INVALID) continue;
 

	
 
		for (auto &roadstop : roadstops) {
 
			RoadStopSpec *roadstopspec = roadstop >= _cur.grffile->roadstops.size() ? nullptr : _cur.grffile->roadstops[roadstop].get();
 

	
 
			if (roadstopspec == nullptr) {
 
				GrfMsg(1, "RoadStopMapSpriteGroup: Road stop with ID {:#02X} does not exist, skipping", roadstop);
 
				GrfMsg(1, "RoadStopMapSpriteGroup: Road stop with ID 0x{:02X} does not exist, skipping", roadstop);
 
				continue;
 
			}
 

	
 
			roadstopspec->grf_prop.spritegroup[ctype] = _cur.spritegroups[groupid];
 
		}
 
	}
 

	
 
	uint16 groupid = buf->ReadWord();
 
	if (!IsValidGroupID(groupid, "RoadStopMapSpriteGroup")) return;
 

	
 
	for (auto &roadstop : roadstops) {
 
		RoadStopSpec *roadstopspec = roadstop >= _cur.grffile->roadstops.size() ? nullptr : _cur.grffile->roadstops[roadstop].get();
 

	
 
		if (roadstopspec == nullptr) {
 
			GrfMsg(1, "RoadStopMapSpriteGroup: Road stop with ID {:#02X} does not exist, skipping.", roadstop);
 
			GrfMsg(1, "RoadStopMapSpriteGroup: Road stop with ID 0x{:02X} does not exist, skipping.", roadstop);
 
			continue;
 
		}
 

	
 
		if (roadstopspec->grf_prop.grffile != nullptr) {
 
			GrfMsg(1, "RoadStopMapSpriteGroup: Road stop with ID {:#02X} mapped multiple times, skipping", roadstop);
 
			GrfMsg(1, "RoadStopMapSpriteGroup: Road stop with ID 0x{:02X} mapped multiple times, skipping", roadstop);
 
			continue;
 
		}
 

	
 
		roadstopspec->grf_prop.spritegroup[CT_DEFAULT] = _cur.spritegroups[groupid];
 
		roadstopspec->grf_prop.grffile = _cur.grffile;
 
		roadstopspec->grf_prop.local_id = roadstop;
 
		RoadStopClass::Assign(roadstopspec);
 
	}
 
}
 

	
 
/* Action 0x03 */
 
static void FeatureMapSpriteGroup(ByteReader *buf)
 
{
 
	/* <03> <feature> <n-id> <ids>... <num-cid> [<cargo-type> <cid>]... <def-cid>
 
	 * id-list    := [<id>] [id-list]
 
	 * cargo-list := <cargo-type> <cid> [cargo-list]
 
	 *
 
	 * B feature       see action 0
 
	 * B n-id          bits 0-6: how many IDs this definition applies to
 
	 *                 bit 7: if set, this is a wagon override definition (see below)
 
	 * B ids           the IDs for which this definition applies
 
	 * B num-cid       number of cargo IDs (sprite group IDs) in this definition
 
	 *                 can be zero, in that case the def-cid is used always
 
	 * B cargo-type    type of this cargo type (e.g. mail=2, wood=7, see below)
 
	 * W cid           cargo ID (sprite group ID) for this type of cargo
 
	 * W def-cid       default cargo ID (sprite group ID) */
 

	
 
	uint8 feature = buf->ReadByte();
 
	uint8 idcount = buf->ReadByte();
 

	
 
	if (feature >= GSF_END) {
 
		GrfMsg(1, "FeatureMapSpriteGroup: Unsupported feature {:#02X}, skipping", feature);
 
		GrfMsg(1, "FeatureMapSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
 
		return;
 
	}
 

	
 
	/* If idcount is zero, this is a feature callback */
 
	if (idcount == 0) {
 
		/* Skip number of cargo ids? */
 
		buf->ReadByte();
 
		uint16 groupid = buf->ReadWord();
 
		if (!IsValidGroupID(groupid, "FeatureMapSpriteGroup")) return;
 

	
 
		GrfMsg(6, "FeatureMapSpriteGroup: Adding generic feature callback for feature {:#02X}", feature);
 
		GrfMsg(6, "FeatureMapSpriteGroup: Adding generic feature callback for feature 0x{:02X}", feature);
 

	
 
		AddGenericCallback(feature, _cur.grffile, _cur.spritegroups[groupid]);
 
		return;
 
	}
 

	
 
	/* Mark the feature as used by the grf (generic callbacks do not count) */
 
	SetBit(_cur.grffile->grf_features, feature);
 

	
 
	GrfMsg(6, "FeatureMapSpriteGroup: Feature {:#02X}, {} ids", feature, idcount);
 
	GrfMsg(6, "FeatureMapSpriteGroup: Feature 0x{:02X}, {} ids", feature, idcount);
 

	
 
	switch (feature) {
 
		case GSF_TRAINS:
 
		case GSF_ROADVEHICLES:
 
		case GSF_SHIPS:
 
		case GSF_AIRCRAFT:
 
			VehicleMapSpriteGroup(buf, feature, idcount);
 
			return;
 

	
 
		case GSF_CANALS:
 
			CanalMapSpriteGroup(buf, idcount);
 
			return;
 

	
 
		case GSF_STATIONS:
 
			StationMapSpriteGroup(buf, idcount);
 
			return;
 

	
 
		case GSF_HOUSES:
 
			TownHouseMapSpriteGroup(buf, idcount);
 
			return;
 

	
 
		case GSF_INDUSTRIES:
 
			IndustryMapSpriteGroup(buf, idcount);
 
			return;
 
@@ -6168,160 +6168,160 @@ static void FeatureMapSpriteGroup(ByteRe
 
			ObjectMapSpriteGroup(buf, idcount);
 
			break;
 

	
 
		case GSF_RAILTYPES:
 
			RailTypeMapSpriteGroup(buf, idcount);
 
			break;
 

	
 
		case GSF_ROADTYPES:
 
			RoadTypeMapSpriteGroup(buf, idcount, RTT_ROAD);
 
			break;
 

	
 
		case GSF_TRAMTYPES:
 
			RoadTypeMapSpriteGroup(buf, idcount, RTT_TRAM);
 
			break;
 

	
 
		case GSF_AIRPORTTILES:
 
			AirportTileMapSpriteGroup(buf, idcount);
 
			return;
 

	
 
		case GSF_ROADSTOPS:
 
			RoadStopMapSpriteGroup(buf, idcount);
 
			return;
 

	
 
		default:
 
			GrfMsg(1, "FeatureMapSpriteGroup: Unsupported feature {:#02X}, skipping", feature);
 
			GrfMsg(1, "FeatureMapSpriteGroup: Unsupported feature 0x{:02X}, skipping", feature);
 
			return;
 
	}
 
}
 

	
 
/* Action 0x04 */
 
static void FeatureNewName(ByteReader *buf)
 
{
 
	/* <04> <veh-type> <language-id> <num-veh> <offset> <data...>
 
	 *
 
	 * B veh-type      see action 0 (as 00..07, + 0A
 
	 *                 But IF veh-type = 48, then generic text
 
	 * B language-id   If bit 6 is set, This is the extended language scheme,
 
	 *                 with up to 64 language.
 
	 *                 Otherwise, it is a mapping where set bits have meaning
 
	 *                 0 = american, 1 = english, 2 = german, 3 = french, 4 = spanish
 
	 *                 Bit 7 set means this is a generic text, not a vehicle one (or else)
 
	 * B num-veh       number of vehicles which are getting a new name
 
	 * B/W offset      number of the first vehicle that gets a new name
 
	 *                 Byte : ID of vehicle to change
 
	 *                 Word : ID of string to change/add
 
	 * S data          new texts, each of them zero-terminated, after
 
	 *                 which the next name begins. */
 

	
 
	bool new_scheme = _cur.grffile->grf_version >= 7;
 

	
 
	uint8 feature  = buf->ReadByte();
 
	if (feature >= GSF_END && feature != 0x48) {
 
		GrfMsg(1, "FeatureNewName: Unsupported feature {:#02X}, skipping", feature);
 
		GrfMsg(1, "FeatureNewName: Unsupported feature 0x{:02X}, skipping", feature);
 
		return;
 
	}
 

	
 
	uint8 lang     = buf->ReadByte();
 
	uint8 num      = buf->ReadByte();
 
	bool generic   = HasBit(lang, 7);
 
	uint16 id;
 
	if (generic) {
 
		id = buf->ReadWord();
 
	} else if (feature <= GSF_AIRCRAFT) {
 
		id = buf->ReadExtendedByte();
 
	} else {
 
		id = buf->ReadByte();
 
	}
 

	
 
	ClrBit(lang, 7);
 

	
 
	uint16 endid = id + num;
 

	
 
	GrfMsg(6, "FeatureNewName: About to rename engines {}..{} (feature {:#02X}) in language {:#02X}",
 
	GrfMsg(6, "FeatureNewName: About to rename engines {}..{} (feature 0x{:02X}) in language 0x{:02X}",
 
	               id, endid, feature, lang);
 

	
 
	for (; id < endid && buf->HasData(); id++) {
 
		const char *name = buf->ReadString();
 
		GrfMsg(8, "FeatureNewName: {:#04X} <- {}", id, name);
 
		GrfMsg(8, "FeatureNewName: 0x{:04X} <- {}", id, name);
 

	
 
		switch (feature) {
 
			case GSF_TRAINS:
 
			case GSF_ROADVEHICLES:
 
			case GSF_SHIPS:
 
			case GSF_AIRCRAFT:
 
				if (!generic) {
 
					Engine *e = GetNewEngine(_cur.grffile, (VehicleType)feature, id, HasBit(_cur.grfconfig->flags, GCF_STATIC));
 
					if (e == nullptr) break;
 
					StringID string = AddGRFString(_cur.grffile->grfid, e->index, lang, new_scheme, false, name, e->info.string_id);
 
					e->info.string_id = string;
 
				} else {
 
					AddGRFString(_cur.grffile->grfid, id, lang, new_scheme, true, name, STR_UNDEFINED);
 
				}
 
				break;
 

	
 
			default:
 
				if (IsInsideMM(id, 0xD000, 0xD400) || IsInsideMM(id, 0xD800, 0x10000)) {
 
					AddGRFString(_cur.grffile->grfid, id, lang, new_scheme, true, name, STR_UNDEFINED);
 
					break;
 
				}
 

	
 
				switch (GB(id, 8, 8)) {
 
					case 0xC4: // Station class name
 
						if (GB(id, 0, 8) >= _cur.grffile->stations.size() || _cur.grffile->stations[GB(id, 0, 8)] == nullptr) {
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined station {:#X}, ignoring", GB(id, 0, 8));
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined station 0x{:X}, ignoring", GB(id, 0, 8));
 
						} else {
 
							StationClassID cls_id = _cur.grffile->stations[GB(id, 0, 8)]->cls_id;
 
							StationClass::Get(cls_id)->name = AddGRFString(_cur.grffile->grfid, id, lang, new_scheme, false, name, STR_UNDEFINED);
 
						}
 
						break;
 

	
 
					case 0xC5: // Station name
 
						if (GB(id, 0, 8) >= _cur.grffile->stations.size() || _cur.grffile->stations[GB(id, 0, 8)] == nullptr) {
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined station {:#X}, ignoring", GB(id, 0, 8));
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined station 0x{:X}, ignoring", GB(id, 0, 8));
 
						} else {
 
							_cur.grffile->stations[GB(id, 0, 8)]->name = AddGRFString(_cur.grffile->grfid, id, lang, new_scheme, false, name, STR_UNDEFINED);
 
						}
 
						break;
 

	
 
					case 0xC7: // Airporttile name
 
						if (GB(id, 0, 8) >= _cur.grffile->airtspec.size() || _cur.grffile->airtspec[GB(id, 0, 8)] == nullptr) {
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined airport tile {:#X}, ignoring", GB(id, 0, 8));
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined airport tile 0x{:X}, ignoring", GB(id, 0, 8));
 
						} else {
 
							_cur.grffile->airtspec[GB(id, 0, 8)]->name = AddGRFString(_cur.grffile->grfid, id, lang, new_scheme, false, name, STR_UNDEFINED);
 
						}
 
						break;
 

	
 
					case 0xC9: // House name
 
						if (GB(id, 0, 8) >= _cur.grffile->housespec.size() || _cur.grffile->housespec[GB(id, 0, 8)] == nullptr) {
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined house {:#X}, ignoring.", GB(id, 0, 8));
 
							GrfMsg(1, "FeatureNewName: Attempt to name undefined house 0x{:X}, ignoring.", GB(id, 0, 8));
 
						} else {
 
							_cur.grffile->housespec[GB(id, 0, 8)]->building_name = AddGRFString(_cur.grffile->grfid, id, lang, new_scheme, false, name, STR_UNDEFINED);
 
						}
 
						break;
 

	
 
					default:
 
						GrfMsg(7, "FeatureNewName: Unsupported ID ({:#04X})", id);
 
						GrfMsg(7, "FeatureNewName: Unsupported ID (0x{:04X})", id);
 
						break;
 
				}
 
				break;
 
		}
 
	}
 
}
 

	
 
/**
 
 * Sanitize incoming sprite offsets for Action 5 graphics replacements.
 
 * @param num         The number of sprites to load.
 
 * @param offset      Offset from the base.
 
 * @param max_sprites The maximum number of sprites that can be loaded in this action 5.
 
 * @param name        Used for error warnings.
 
 * @return The number of sprites that is going to be skipped.
 
 */
 
static uint16 SanitizeSpriteOffset(uint16& num, uint16 offset, int max_sprites, const char *name)
 
{
 

	
 
	if (offset >= max_sprites) {
 
		GrfMsg(1, "GraphicsNew: {} sprite offset must be less than {}, skipping", name, max_sprites);
 
		uint orig_num = num;
 
		num = 0;
 
		return orig_num;
 
	}
 
@@ -6395,77 +6395,77 @@ static void GraphicsNew(ByteReader *buf)
 
	uint16 num = buf->ReadExtendedByte();
 
	uint16 offset = HasBit(type, 7) ? buf->ReadExtendedByte() : 0;
 
	ClrBit(type, 7); // Clear the high bit as that only indicates whether there is an offset.
 

	
 
	if ((type == 0x0D) && (num == 10) && HasBit(_cur.grfconfig->flags, GCF_SYSTEM)) {
 
		/* Special not-TTDP-compatible case used in openttd.grf
 
		 * Missing shore sprites and initialisation of SPR_SHORE_BASE */
 
		GrfMsg(2, "GraphicsNew: Loading 10 missing shore sprites from extra grf.");
 
		LoadNextSprite(SPR_SHORE_BASE +  0, *_cur.file, _cur.nfo_line++); // SLOPE_STEEP_S
 
		LoadNextSprite(SPR_SHORE_BASE +  5, *_cur.file, _cur.nfo_line++); // SLOPE_STEEP_W
 
		LoadNextSprite(SPR_SHORE_BASE +  7, *_cur.file, _cur.nfo_line++); // SLOPE_WSE
 
		LoadNextSprite(SPR_SHORE_BASE + 10, *_cur.file, _cur.nfo_line++); // SLOPE_STEEP_N
 
		LoadNextSprite(SPR_SHORE_BASE + 11, *_cur.file, _cur.nfo_line++); // SLOPE_NWS
 
		LoadNextSprite(SPR_SHORE_BASE + 13, *_cur.file, _cur.nfo_line++); // SLOPE_ENW
 
		LoadNextSprite(SPR_SHORE_BASE + 14, *_cur.file, _cur.nfo_line++); // SLOPE_SEN
 
		LoadNextSprite(SPR_SHORE_BASE + 15, *_cur.file, _cur.nfo_line++); // SLOPE_STEEP_E
 
		LoadNextSprite(SPR_SHORE_BASE + 16, *_cur.file, _cur.nfo_line++); // SLOPE_EW
 
		LoadNextSprite(SPR_SHORE_BASE + 17, *_cur.file, _cur.nfo_line++); // SLOPE_NS
 
		if (_loaded_newgrf_features.shore == SHORE_REPLACE_NONE) _loaded_newgrf_features.shore = SHORE_REPLACE_ONLY_NEW;
 
		return;
 
	}
 

	
 
	/* Supported type? */
 
	if ((type >= lengthof(_action5_types)) || (_action5_types[type].block_type == A5BLOCK_INVALID)) {
 
		GrfMsg(2, "GraphicsNew: Custom graphics (type {:#02X}) sprite block of length {} (unimplemented, ignoring)", type, num);
 
		GrfMsg(2, "GraphicsNew: Custom graphics (type 0x{:02X}) sprite block of length {} (unimplemented, ignoring)", type, num);
 
		_cur.skip_sprites = num;
 
		return;
 
	}
 

	
 
	const Action5Type *action5_type = &_action5_types[type];
 

	
 
	/* Contrary to TTDP we allow always to specify too few sprites as we allow always an offset,
 
	 * except for the long version of the shore type:
 
	 * Ignore offset if not allowed */
 
	if ((action5_type->block_type != A5BLOCK_ALLOW_OFFSET) && (offset != 0)) {
 
		GrfMsg(1, "GraphicsNew: {} (type {:#02X}) do not allow an <offset> field. Ignoring offset.", action5_type->name, type);
 
		GrfMsg(1, "GraphicsNew: {} (type 0x{:02X}) do not allow an <offset> field. Ignoring offset.", action5_type->name, type);
 
		offset = 0;
 
	}
 

	
 
	/* Ignore action5 if too few sprites are specified. (for TTDP compatibility)
 
	 * This does not make sense, if <offset> is allowed */
 
	if ((action5_type->block_type == A5BLOCK_FIXED) && (num < action5_type->min_sprites)) {
 
		GrfMsg(1, "GraphicsNew: {} (type {:#02X}) count must be at least {}. Only {} were specified. Skipping.", action5_type->name, type, action5_type->min_sprites, num);
 
		GrfMsg(1, "GraphicsNew: {} (type 0x{:02X}) count must be at least {}. Only {} were specified. Skipping.", action5_type->name, type, action5_type->min_sprites, num);
 
		_cur.skip_sprites = num;
 
		return;
 
	}
 

	
 
	/* Load at most max_sprites sprites. Skip remaining sprites. (for compatibility with TTDP and future extensions) */
 
	uint16 skip_num = SanitizeSpriteOffset(num, offset, action5_type->max_sprites, action5_type->name);
 
	SpriteID replace = action5_type->sprite_base + offset;
 

	
 
	/* Load <num> sprites starting from <replace>, then skip <skip_num> sprites. */
 
	GrfMsg(2, "GraphicsNew: Replacing sprites {} to {} of {} (type {:#02X}) at SpriteID {:#04X}", offset, offset + num - 1, action5_type->name, type, replace);
 
	GrfMsg(2, "GraphicsNew: Replacing sprites {} to {} of {} (type 0x{:02X}) at SpriteID 0x{:04X}", offset, offset + num - 1, action5_type->name, type, replace);
 

	
 
	if (type == 0x0D) _loaded_newgrf_features.shore = SHORE_REPLACE_ACTION_5;
 

	
 
	if (type == 0x0B) {
 
		static const SpriteID depot_with_track_offset = SPR_TRAMWAY_DEPOT_WITH_TRACK - SPR_TRAMWAY_BASE;
 
		static const SpriteID depot_no_track_offset = SPR_TRAMWAY_DEPOT_NO_TRACK - SPR_TRAMWAY_BASE;
 
		if (offset <= depot_with_track_offset && offset + num > depot_with_track_offset) _loaded_newgrf_features.tram = TRAMWAY_REPLACE_DEPOT_WITH_TRACK;
 
		if (offset <= depot_no_track_offset && offset + num > depot_no_track_offset) _loaded_newgrf_features.tram = TRAMWAY_REPLACE_DEPOT_NO_TRACK;
 
	}
 

	
 
	/* If the baseset or grf only provides sprites for flat tiles (pre #10282), duplicate those for use on slopes. */
 
	bool dup_oneway_sprites = ((type == 0x09) && (offset + num <= SPR_ONEWAY_SLOPE_N_OFFSET));
 

	
 
	for (; num > 0; num--) {
 
		_cur.nfo_line++;
 
		int load_index = (replace == 0 ? _cur.spriteid++ : replace++);
 
		LoadNextSprite(load_index, *_cur.file, _cur.nfo_line);
 
		if (dup_oneway_sprites) {
 
			DupSprite(load_index, load_index + SPR_ONEWAY_SLOPE_N_OFFSET);
 
			DupSprite(load_index, load_index + SPR_ONEWAY_SLOPE_S_OFFSET);
 
		}
 
	}
 

	
 
	_cur.skip_sprites = skip_num;
 
@@ -6643,49 +6643,49 @@ static uint32 GetParamVal(byte param, ui
 
			return res;
 
		}
 

	
 
		case 0x85: // TTDPatch flags, only for bit tests
 
			if (cond_val == nullptr) {
 
				/* Supported in Action 0x07 and 0x09, not 0x0D */
 
				return 0;
 
			} else {
 
				uint32 index = *cond_val / 0x20;
 
				uint32 param_val = index < lengthof(_ttdpatch_flags) ? _ttdpatch_flags[index] : 0;
 
				*cond_val %= 0x20;
 
				return param_val;
 
			}
 

	
 
		case 0x88: // GRF ID check
 
			return 0;
 

	
 
		/* case 0x99: Global ID offset not implemented */
 

	
 
		default:
 
			/* GRF Parameter */
 
			if (param < 0x80) return _cur.grffile->GetParam(param);
 

	
 
			/* In-game variable. */
 
			GrfMsg(1, "Unsupported in-game variable {:#02X}", param);
 
			GrfMsg(1, "Unsupported in-game variable 0x{:02X}", param);
 
			return UINT_MAX;
 
	}
 
}
 

	
 
/* Action 0x06 */
 
static void CfgApply(ByteReader *buf)
 
{
 
	/* <06> <param-num> <param-size> <offset> ... <FF>
 
	 *
 
	 * B param-num     Number of parameter to substitute (First = "zero")
 
	 *                 Ignored if that parameter was not specified in newgrf.cfg
 
	 * B param-size    How many bytes to replace.  If larger than 4, the
 
	 *                 bytes of the following parameter are used.  In that
 
	 *                 case, nothing is applied unless *all* parameters
 
	 *                 were specified.
 
	 * B offset        Offset into data from beginning of next sprite
 
	 *                 to place where parameter is to be stored. */
 

	
 
	/* Preload the next sprite */
 
	SpriteFile &file = *_cur.file;
 
	size_t pos = file.GetPos();
 
	uint32 num = file.GetContainerVersion() >= 2 ? file.ReadDword() : file.ReadWord();
 
	uint8 type = file.ReadByte();
 
	byte *preload_sprite = nullptr;
 
@@ -6725,49 +6725,49 @@ static void CfgApply(ByteReader *buf)
 

	
 
		/* Read the parameter to apply. 0xFF indicates no more data to change. */
 
		param_num = buf->ReadByte();
 
		if (param_num == 0xFF) break;
 

	
 
		/* Get the size of the parameter to use. If the size covers multiple
 
		 * double words, sequential parameter values are used. */
 
		param_size = buf->ReadByte();
 

	
 
		/* Bit 7 of param_size indicates we should add to the original value
 
		 * instead of replacing it. */
 
		add_value  = HasBit(param_size, 7);
 
		param_size = GB(param_size, 0, 7);
 

	
 
		/* Where to apply the data to within the pseudo sprite data. */
 
		offset     = buf->ReadExtendedByte();
 

	
 
		/* If the parameter is a GRF parameter (not an internal variable) check
 
		 * if it (and all further sequential parameters) has been defined. */
 
		if (param_num < 0x80 && (param_num + (param_size - 1) / 4) >= _cur.grffile->param_end) {
 
			GrfMsg(2, "CfgApply: Ignoring (param {} not set)", (param_num + (param_size - 1) / 4));
 
			break;
 
		}
 

	
 
		GrfMsg(8, "CfgApply: Applying {} bytes from parameter {:#02X} at offset {:#04X}", param_size, param_num, offset);
 
		GrfMsg(8, "CfgApply: Applying {} bytes from parameter 0x{:02X} at offset 0x{:04X}", param_size, param_num, offset);
 

	
 
		bool carry = false;
 
		for (i = 0; i < param_size && offset + i < num; i++) {
 
			uint32 value = GetParamVal(param_num + i / 4, nullptr);
 
			/* Reset carry flag for each iteration of the variable (only really
 
			 * matters if param_size is greater than 4) */
 
			if (i % 4 == 0) carry = false;
 

	
 
			if (add_value) {
 
				uint new_value = preload_sprite[offset + i] + GB(value, (i % 4) * 8, 8) + (carry ? 1 : 0);
 
				preload_sprite[offset + i] = GB(new_value, 0, 8);
 
				/* Check if the addition overflowed */
 
				carry = new_value >= 256;
 
			} else {
 
				preload_sprite[offset + i] = GB(value, (i % 4) * 8, 8);
 
			}
 
		}
 
	}
 
}
 

	
 
/**
 
 * Disable a static NewGRF when it is influencing another (non-static)
 
 * NewGRF as this could cause desyncs.
 
 *
 
@@ -6798,98 +6798,98 @@ static void SkipIf(ByteReader *buf)
 
	bool result;
 

	
 
	uint8 param     = buf->ReadByte();
 
	uint8 paramsize = buf->ReadByte();
 
	uint8 condtype  = buf->ReadByte();
 

	
 
	if (condtype < 2) {
 
		/* Always 1 for bit tests, the given value should be ignored. */
 
		paramsize = 1;
 
	}
 

	
 
	switch (paramsize) {
 
		case 8: cond_val = buf->ReadDWord(); mask = buf->ReadDWord(); break;
 
		case 4: cond_val = buf->ReadDWord(); mask = 0xFFFFFFFF; break;
 
		case 2: cond_val = buf->ReadWord();  mask = 0x0000FFFF; break;
 
		case 1: cond_val = buf->ReadByte();  mask = 0x000000FF; break;
 
		default: break;
 
	}
 

	
 
	if (param < 0x80 && _cur.grffile->param_end <= param) {
 
		GrfMsg(7, "SkipIf: Param {} undefined, skipping test", param);
 
		return;
 
	}
 

	
 
	GrfMsg(7, "SkipIf: Test condtype {}, param {:#02X}, condval {:#08X}", condtype, param, cond_val);
 
	GrfMsg(7, "SkipIf: Test condtype {}, param 0x{:02X}, condval 0x{:08X}", condtype, param, cond_val);
 

	
 
	/* condtypes that do not use 'param' are always valid.
 
	 * condtypes that use 'param' are either not valid for param 0x88, or they are only valid for param 0x88.
 
	 */
 
	if (condtype >= 0x0B) {
 
		/* Tests that ignore 'param' */
 
		switch (condtype) {
 
			case 0x0B: result = GetCargoIDByLabel(BSWAP32(cond_val)) == CT_INVALID;
 
				break;
 
			case 0x0C: result = GetCargoIDByLabel(BSWAP32(cond_val)) != CT_INVALID;
 
				break;
 
			case 0x0D: result = GetRailTypeByLabel(BSWAP32(cond_val)) == INVALID_RAILTYPE;
 
				break;
 
			case 0x0E: result = GetRailTypeByLabel(BSWAP32(cond_val)) != INVALID_RAILTYPE;
 
				break;
 
			case 0x0F: {
 
				RoadType rt = GetRoadTypeByLabel(BSWAP32(cond_val));
 
				result = rt == INVALID_ROADTYPE || !RoadTypeIsRoad(rt);
 
				break;
 
			}
 
			case 0x10: {
 
				RoadType rt = GetRoadTypeByLabel(BSWAP32(cond_val));
 
				result = rt != INVALID_ROADTYPE && RoadTypeIsRoad(rt);
 
				break;
 
			}
 
			case 0x11: {
 
				RoadType rt = GetRoadTypeByLabel(BSWAP32(cond_val));
 
				result = rt == INVALID_ROADTYPE || !RoadTypeIsTram(rt);
 
				break;
 
			}
 
			case 0x12: {
 
				RoadType rt = GetRoadTypeByLabel(BSWAP32(cond_val));
 
				result = rt != INVALID_ROADTYPE && RoadTypeIsTram(rt);
 
				break;
 
			}
 
			default: GrfMsg(1, "SkipIf: Unsupported condition type {:02X}. Ignoring", condtype); return;
 
		}
 
	} else if (param == 0x88) {
 
		/* GRF ID checks */
 

	
 
		GRFConfig *c = GetGRFConfig(cond_val, mask);
 

	
 
		if (c != nullptr && HasBit(c->flags, GCF_STATIC) && !HasBit(_cur.grfconfig->flags, GCF_STATIC) && _networking) {
 
			DisableStaticNewGRFInfluencingNonStaticNewGRFs(c);
 
			c = nullptr;
 
		}
 

	
 
		if (condtype != 10 && c == nullptr) {
 
			GrfMsg(7, "SkipIf: GRFID {:#08X} unknown, skipping test", BSWAP32(cond_val));
 
			GrfMsg(7, "SkipIf: GRFID 0x{:08X} unknown, skipping test", BSWAP32(cond_val));
 
			return;
 
		}
 

	
 
		switch (condtype) {
 
			/* Tests 0x06 to 0x0A are only for param 0x88, GRFID checks */
 
			case 0x06: // Is GRFID active?
 
				result = c->status == GCS_ACTIVATED;
 
				break;
 

	
 
			case 0x07: // Is GRFID non-active?
 
				result = c->status != GCS_ACTIVATED;
 
				break;
 

	
 
			case 0x08: // GRFID is not but will be active?
 
				result = c->status == GCS_INITIALISED;
 
				break;
 

	
 
			case 0x09: // GRFID is or will be active?
 
				result = c->status == GCS_ACTIVATED || c->status == GCS_INITIALISED;
 
				break;
 

	
 
			case 0x0A: // GRFID is not nor will be active
 
				/* This is the only condtype that doesn't get ignored if the GRFID is not found */
 
				result = c == nullptr || c->status == GCS_DISABLED || c->status == GCS_NOT_FOUND;
 
@@ -6921,49 +6921,49 @@ static void SkipIf(ByteReader *buf)
 
		GrfMsg(2, "SkipIf: Not skipping sprites, test was false");
 
		return;
 
	}
 

	
 
	uint8 numsprites = buf->ReadByte();
 

	
 
	/* numsprites can be a GOTO label if it has been defined in the GRF
 
	 * file. The jump will always be the first matching label that follows
 
	 * the current nfo_line. If no matching label is found, the first matching
 
	 * label in the file is used. */
 
	const GRFLabel *choice = nullptr;
 
	for (const auto &label : _cur.grffile->labels) {
 
		if (label.label != numsprites) continue;
 

	
 
		/* Remember a goto before the current line */
 
		if (choice == nullptr) choice = &label;
 
		/* If we find a label here, this is definitely good */
 
		if (label.nfo_line > _cur.nfo_line) {
 
			choice = &label;
 
			break;
 
		}
 
	}
 

	
 
	if (choice != nullptr) {
 
		GrfMsg(2, "SkipIf: Jumping to label {:#X} at line {}, test was true", choice->label, choice->nfo_line);
 
		GrfMsg(2, "SkipIf: Jumping to label 0x{:X} at line {}, test was true", choice->label, choice->nfo_line);
 
		_cur.file->SeekTo(choice->pos, SEEK_SET);
 
		_cur.nfo_line = choice->nfo_line;
 
		return;
 
	}
 

	
 
	GrfMsg(2, "SkipIf: Skipping {} sprites, test was true", numsprites);
 
	_cur.skip_sprites = numsprites;
 
	if (_cur.skip_sprites == 0) {
 
		/* Zero means there are no sprites to skip, so
 
		 * we use -1 to indicate that all further
 
		 * sprites should be skipped. */
 
		_cur.skip_sprites = -1;
 

	
 
		/* If an action 8 hasn't been encountered yet, disable the grf. */
 
		if (_cur.grfconfig->status != (_cur.stage < GLS_RESERVE ? GCS_INITIALISED : GCS_ACTIVATED)) {
 
			DisableGrf();
 
		}
 
	}
 
}
 

	
 

	
 
/* Action 0x08 (GLS_FILESCAN) */
 
static void ScanInfo(ByteReader *buf)
 
{
 
@@ -7267,49 +7267,49 @@ static uint32 GetPatchVariable(uint8 par
 
				if (max_edge == log_Y) SetBit(map_bits, 1); // edge Y been the biggest, mark it
 
			}
 

	
 
			return (map_bits << 24) | (std::min(log_X, log_Y) << 20) | (max_edge << 16) |
 
				(log_X << 12) | (log_Y << 8) | (log_X + log_Y);
 
		}
 

	
 
		/* The maximum height of the map. */
 
		case 0x14:
 
			return _settings_game.construction.map_height_limit;
 

	
 
		/* Extra foundations base sprite */
 
		case 0x15:
 
			return SPR_SLOPES_BASE;
 

	
 
		/* Shore base sprite */
 
		case 0x16:
 
			return SPR_SHORE_BASE;
 

	
 
		/* Game map seed */
 
		case 0x17:
 
			return _settings_game.game_creation.generation_seed;
 

	
 
		default:
 
			GrfMsg(2, "ParamSet: Unknown Patch variable {:#02X}.", param);
 
			GrfMsg(2, "ParamSet: Unknown Patch variable 0x{:02X}.", param);
 
			return 0;
 
	}
 
}
 

	
 

	
 
static uint32 PerformGRM(uint32 *grm, uint16 num_ids, uint16 count, uint8 op, uint8 target, const char *type)
 
{
 
	uint start = 0;
 
	uint size  = 0;
 

	
 
	if (op == 6) {
 
		/* Return GRFID of set that reserved ID */
 
		return grm[_cur.grffile->GetParam(target)];
 
	}
 

	
 
	/* With an operation of 2 or 3, we want to reserve a specific block of IDs */
 
	if (op == 2 || op == 3) start = _cur.grffile->GetParam(target);
 

	
 
	for (uint i = start; i < num_ids; i++) {
 
		if (grm[i] == 0) {
 
			size++;
 
		} else {
 
			if (op == 2 || op == 3) break;
 
			start = i + 1;
 
@@ -7446,49 +7446,49 @@ static void ParamSet(ByteReader *buf)
 
						case 0x08: // General sprites
 
							switch (op) {
 
								case 0:
 
									/* Return space reserved during reservation stage */
 
									src1 = _grm_sprites[GRFLocation(_cur.grffile->grfid, _cur.nfo_line)];
 
									GrfMsg(4, "ParamSet: GRM: Using pre-allocated sprites at {}", src1);
 
									break;
 

	
 
								case 1:
 
									src1 = _cur.spriteid;
 
									break;
 

	
 
								default:
 
									GrfMsg(1, "ParamSet: GRM: Unsupported operation {} for general sprites", op);
 
									return;
 
							}
 
							break;
 

	
 
						case 0x0B: // Cargo
 
							/* There are two ranges: one for cargo IDs and one for cargo bitmasks */
 
							src1 = PerformGRM(_grm_cargoes, NUM_CARGO * 2, count, op, target, "cargoes");
 
							if (_cur.skip_sprites == -1) return;
 
							break;
 

	
 
						default: GrfMsg(1, "ParamSet: GRM: Unsupported feature {:#X}", feature); return;
 
						default: GrfMsg(1, "ParamSet: GRM: Unsupported feature 0x{:X}", feature); return;
 
					}
 
				} else {
 
					/* Ignore GRM during initialization */
 
					src1 = 0;
 
				}
 
			}
 
		} else {
 
			/* Read another GRF File's parameter */
 
			const GRFFile *file = GetFileByGRFID(data);
 
			GRFConfig *c = GetGRFConfig(data);
 
			if (c != nullptr && HasBit(c->flags, GCF_STATIC) && !HasBit(_cur.grfconfig->flags, GCF_STATIC) && _networking) {
 
				/* Disable the read GRF if it is a static NewGRF. */
 
				DisableStaticNewGRFInfluencingNonStaticNewGRFs(c);
 
				src1 = 0;
 
			} else if (file == nullptr || c == nullptr || c->status == GCS_DISABLED) {
 
				src1 = 0;
 
			} else if (src1 == 0xFE) {
 
				src1 = c->version;
 
			} else {
 
				src1 = file->GetParam(src1);
 
			}
 
		}
 
	} else {
 
		/* The source1 and source2 operands refer to the grf parameter number
 
@@ -7586,79 +7586,79 @@ static void ParamSet(ByteReader *buf)
 
			_cur.grffile->traininfo_vehicle_pitch = res;
 
			break;
 

	
 
		case 0x8F: { // Rail track type cost factors
 
			extern RailtypeInfo _railtypes[RAILTYPE_END];
 
			_railtypes[RAILTYPE_RAIL].cost_multiplier = GB(res, 0, 8);
 
			if (_settings_game.vehicle.disable_elrails) {
 
				_railtypes[RAILTYPE_ELECTRIC].cost_multiplier = GB(res, 0, 8);
 
				_railtypes[RAILTYPE_MONO].cost_multiplier = GB(res, 8, 8);
 
			} else {
 
				_railtypes[RAILTYPE_ELECTRIC].cost_multiplier = GB(res, 8, 8);
 
				_railtypes[RAILTYPE_MONO].cost_multiplier = GB(res, 16, 8);
 
			}
 
			_railtypes[RAILTYPE_MAGLEV].cost_multiplier = GB(res, 16, 8);
 
			break;
 
		}
 

	
 
		/* not implemented */
 
		case 0x93: // Tile refresh offset to left -- Intended to allow support for larger sprites, not necessary for OTTD
 
		case 0x94: // Tile refresh offset to right
 
		case 0x95: // Tile refresh offset upwards
 
		case 0x96: // Tile refresh offset downwards
 
		case 0x97: // Snow line height -- Better supported by feature 8 property 10h (snow line table) TODO: implement by filling the entire snow line table with the given value
 
		case 0x99: // Global ID offset -- Not necessary since IDs are remapped automatically
 
			GrfMsg(7, "ParamSet: Skipping unimplemented target {:#02X}", target);
 
			GrfMsg(7, "ParamSet: Skipping unimplemented target 0x{:02X}", target);
 
			break;
 

	
 
		case 0x9E: // Miscellaneous GRF features
 
			/* Set train list engine width */
 
			_cur.grffile->traininfo_vehicle_width = HasBit(res, GMB_TRAIN_WIDTH_32_PIXELS) ? VEHICLEINFO_FULL_VEHICLE_WIDTH : TRAININFO_DEFAULT_VEHICLE_WIDTH;
 
			/* Remove the local flags from the global flags */
 
			ClrBit(res, GMB_TRAIN_WIDTH_32_PIXELS);
 

	
 
			/* Only copy safe bits for static grfs */
 
			if (HasBit(_cur.grfconfig->flags, GCF_STATIC)) {
 
				uint32 safe_bits = 0;
 
				SetBit(safe_bits, GMB_SECOND_ROCKY_TILE_SET);
 

	
 
				_misc_grf_features = (_misc_grf_features & ~safe_bits) | (res & safe_bits);
 
			} else {
 
				_misc_grf_features = res;
 
			}
 
			break;
 

	
 
		case 0x9F: // locale-dependent settings
 
			GrfMsg(7, "ParamSet: Skipping unimplemented target {:#02X}", target);
 
			GrfMsg(7, "ParamSet: Skipping unimplemented target 0x{:02X}", target);
 
			break;
 

	
 
		default:
 
			if (target < 0x80) {
 
				_cur.grffile->param[target] = res;
 
				/* param is zeroed by default */
 
				if (target + 1U > _cur.grffile->param_end) _cur.grffile->param_end = target + 1;
 
			} else {
 
				GrfMsg(7, "ParamSet: Skipping unknown target {:#02X}", target);
 
				GrfMsg(7, "ParamSet: Skipping unknown target 0x{:02X}", target);
 
			}
 
			break;
 
	}
 
}
 

	
 
/* Action 0x0E (GLS_SAFETYSCAN) */
 
static void SafeGRFInhibit(ByteReader *buf)
 
{
 
	/* <0E> <num> <grfids...>
 
	 *
 
	 * B num           Number of GRFIDs that follow
 
	 * D grfids        GRFIDs of the files to deactivate */
 

	
 
	uint8 num = buf->ReadByte();
 

	
 
	for (uint i = 0; i < num; i++) {
 
		uint32 grfid = buf->ReadDWord();
 

	
 
		/* GRF is unsafe it if tries to deactivate other GRFs */
 
		if (grfid != _cur.grfconfig->ident.grfid) {
 
			SetBit(_cur.grfconfig->flags, GCF_UNSAFE);
 

	
 
			/* Skip remainder of GRF */
 
			_cur.skip_sprites = -1;
 
@@ -7685,124 +7685,124 @@ static void GRFInhibit(ByteReader *buf)
 
		/* Unset activation flag */
 
		if (file != nullptr && file != _cur.grfconfig) {
 
			GrfMsg(2, "GRFInhibit: Deactivating file '{}'", file->filename);
 
			GRFError *error = DisableGrf(STR_NEWGRF_ERROR_FORCEFULLY_DISABLED, file);
 
			error->data = _cur.grfconfig->GetName();
 
		}
 
	}
 
}
 

	
 
/** Action 0x0F - Define Town names */
 
static void FeatureTownName(ByteReader *buf)
 
{
 
	/* <0F> <id> <style-name> <num-parts> <parts>
 
	 *
 
	 * B id          ID of this definition in bottom 7 bits (final definition if bit 7 set)
 
	 * V style-name  Name of the style (only for final definition)
 
	 * B num-parts   Number of parts in this definition
 
	 * V parts       The parts */
 

	
 
	uint32 grfid = _cur.grffile->grfid;
 

	
 
	GRFTownName *townname = AddGRFTownName(grfid);
 

	
 
	byte id = buf->ReadByte();
 
	GrfMsg(6, "FeatureTownName: definition {:#02X}", id & 0x7F);
 
	GrfMsg(6, "FeatureTownName: definition 0x{:02X}", id & 0x7F);
 

	
 
	if (HasBit(id, 7)) {
 
		/* Final definition */
 
		ClrBit(id, 7);
 
		bool new_scheme = _cur.grffile->grf_version >= 7;
 

	
 
		byte lang = buf->ReadByte();
 
		StringID style = STR_UNDEFINED;
 

	
 
		do {
 
			ClrBit(lang, 7);
 

	
 
			const char *name = buf->ReadString();
 

	
 
			std::string lang_name = TranslateTTDPatchCodes(grfid, lang, false, name);
 
			GrfMsg(6, "FeatureTownName: lang {:#X} -> '{}'", lang, lang_name.c_str());
 
			GrfMsg(6, "FeatureTownName: lang 0x{:X} -> '{}'", lang, lang_name.c_str());
 

	
 
			style = AddGRFString(grfid, id, lang, new_scheme, false, name, STR_UNDEFINED);
 

	
 
			lang = buf->ReadByte();
 
		} while (lang != 0);
 
		townname->styles.emplace_back(style, id);
 
	}
 

	
 
	uint8 parts = buf->ReadByte();
 
	GrfMsg(6, "FeatureTownName: {} parts", parts);
 

	
 
	townname->partlists[id].reserve(parts);
 
	for (uint partnum = 0; partnum < parts; partnum++) {
 
		NamePartList &partlist = townname->partlists[id].emplace_back();
 
		uint8 texts = buf->ReadByte();
 
		partlist.bitstart = buf->ReadByte();
 
		partlist.bitcount = buf->ReadByte();
 
		partlist.maxprob  = 0;
 
		GrfMsg(6, "FeatureTownName: part {} contains {} texts and will use GB(seed, {}, {})", partnum, texts, partlist.bitstart, partlist.bitcount);
 

	
 
		partlist.parts.reserve(texts);
 
		for (uint textnum = 0; textnum < texts; textnum++) {
 
			NamePart &part = partlist.parts.emplace_back();
 
			part.prob = buf->ReadByte();
 

	
 
			if (HasBit(part.prob, 7)) {
 
				byte ref_id = buf->ReadByte();
 
				if (ref_id >= GRFTownName::MAX_LISTS || townname->partlists[ref_id].empty()) {
 
					GrfMsg(0, "FeatureTownName: definition {:#02X} doesn't exist, deactivating", ref_id);
 
					GrfMsg(0, "FeatureTownName: definition 0x{:02X} doesn't exist, deactivating", ref_id);
 
					DelGRFTownName(grfid);
 
					DisableGrf(STR_NEWGRF_ERROR_INVALID_ID);
 
					return;
 
				}
 
				part.id = ref_id;
 
				GrfMsg(6, "FeatureTownName: part {}, text {}, uses intermediate definition {:#02X} (with probability {})", partnum, textnum, ref_id, part.prob & 0x7F);
 
				GrfMsg(6, "FeatureTownName: part {}, text {}, uses intermediate definition 0x{:02X} (with probability {})", partnum, textnum, ref_id, part.prob & 0x7F);
 
			} else {
 
				const char *text = buf->ReadString();
 
				part.text = TranslateTTDPatchCodes(grfid, 0, false, text);
 
				GrfMsg(6, "FeatureTownName: part {}, text {}, '{}' (with probability {})", partnum, textnum, part.text.c_str(), part.prob);
 
			}
 
			partlist.maxprob += GB(part.prob, 0, 7);
 
		}
 
		GrfMsg(6, "FeatureTownName: part {}, total probability {}", partnum, partlist.maxprob);
 
	}
 
}
 

	
 
/** Action 0x10 - Define goto label */
 
static void DefineGotoLabel(ByteReader *buf)
 
{
 
	/* <10> <label> [<comment>]
 
	 *
 
	 * B label      The label to define
 
	 * V comment    Optional comment - ignored */
 

	
 
	byte nfo_label = buf->ReadByte();
 

	
 
	_cur.grffile->labels.emplace_back(nfo_label, _cur.nfo_line, _cur.file->GetPos());
 

	
 
	GrfMsg(2, "DefineGotoLabel: GOTO target with label {:#02X}", nfo_label);
 
	GrfMsg(2, "DefineGotoLabel: GOTO target with label 0x{:02X}", nfo_label);
 
}
 

	
 
/**
 
 * Process a sound import from another GRF file.
 
 * @param sound Destination for sound.
 
 */
 
static void ImportGRFSound(SoundEntry *sound)
 
{
 
	const GRFFile *file;
 
	uint32 grfid = _cur.file->ReadDword();
 
	SoundID sound_id = _cur.file->ReadWord();
 

	
 
	file = GetFileByGRFID(grfid);
 
	if (file == nullptr || file->sound_offset == 0) {
 
		GrfMsg(1, "ImportGRFSound: Source file not available");
 
		return;
 
	}
 

	
 
	if (sound_id >= file->num_sounds) {
 
		GrfMsg(1, "ImportGRFSound: Sound effect {} is invalid", sound_id);
 
		return;
 
	}
 

	
 
	GrfMsg(2, "ImportGRFSound: Copying sound {} ({}) from file {:x}", sound_id, file->sound_offset + sound_id, grfid);
 
@@ -7938,122 +7938,122 @@ static void SkipAct11(ByteReader *buf)
 
	GrfMsg(3, "SkipAct11: Skipping {} sprites", _cur.skip_sprites);
 
}
 

	
 
/** Action 0x12 */
 
static void LoadFontGlyph(ByteReader *buf)
 
{
 
	/* <12> <num_def> <font_size> <num_char> <base_char>
 
	 *
 
	 * B num_def      Number of definitions
 
	 * B font_size    Size of font (0 = normal, 1 = small, 2 = large, 3 = mono)
 
	 * B num_char     Number of consecutive glyphs
 
	 * W base_char    First character index */
 

	
 
	uint8 num_def = buf->ReadByte();
 

	
 
	for (uint i = 0; i < num_def; i++) {
 
		FontSize size    = (FontSize)buf->ReadByte();
 
		uint8  num_char  = buf->ReadByte();
 
		uint16 base_char = buf->ReadWord();
 

	
 
		if (size >= FS_END) {
 
			GrfMsg(1, "LoadFontGlyph: Size {} is not supported, ignoring", size);
 
		}
 

	
 
		GrfMsg(7, "LoadFontGlyph: Loading {} glyph(s) at {:#04X} for size {}", num_char, base_char, size);
 
		GrfMsg(7, "LoadFontGlyph: Loading {} glyph(s) at 0x{:04X} for size {}", num_char, base_char, size);
 

	
 
		for (uint c = 0; c < num_char; c++) {
 
			if (size < FS_END) SetUnicodeGlyph(size, base_char + c, _cur.spriteid);
 
			_cur.nfo_line++;
 
			LoadNextSprite(_cur.spriteid++, *_cur.file, _cur.nfo_line);
 
		}
 
	}
 
}
 

	
 
/** Action 0x12 (SKIP) */
 
static void SkipAct12(ByteReader *buf)
 
{
 
	/* <12> <num_def> <font_size> <num_char> <base_char>
 
	 *
 
	 * B num_def      Number of definitions
 
	 * B font_size    Size of font (0 = normal, 1 = small, 2 = large)
 
	 * B num_char     Number of consecutive glyphs
 
	 * W base_char    First character index */
 

	
 
	uint8 num_def = buf->ReadByte();
 

	
 
	for (uint i = 0; i < num_def; i++) {
 
		/* Ignore 'size' byte */
 
		buf->ReadByte();
 

	
 
		/* Sum up number of characters */
 
		_cur.skip_sprites += buf->ReadByte();
 

	
 
		/* Ignore 'base_char' word */
 
		buf->ReadWord();
 
	}
 

	
 
	GrfMsg(3, "SkipAct12: Skipping {} sprites", _cur.skip_sprites);
 
}
 

	
 
/** Action 0x13 */
 
static void TranslateGRFStrings(ByteReader *buf)
 
{
 
	/* <13> <grfid> <num-ent> <offset> <text...>
 
	 *
 
	 * 4*B grfid     The GRFID of the file whose texts are to be translated
 
	 * B   num-ent   Number of strings
 
	 * W   offset    First text ID
 
	 * S   text...   Zero-terminated strings */
 

	
 
	uint32 grfid = buf->ReadDWord();
 
	const GRFConfig *c = GetGRFConfig(grfid);
 
	if (c == nullptr || (c->status != GCS_INITIALISED && c->status != GCS_ACTIVATED)) {
 
		GrfMsg(7, "TranslateGRFStrings: GRFID {:#08X} unknown, skipping action 13", BSWAP32(grfid));
 
		GrfMsg(7, "TranslateGRFStrings: GRFID 0x{:08X} unknown, skipping action 13", BSWAP32(grfid));
 
		return;
 
	}
 

	
 
	if (c->status == GCS_INITIALISED) {
 
		/* If the file is not active but will be activated later, give an error
 
		 * and disable this file. */
 
		GRFError *error = DisableGrf(STR_NEWGRF_ERROR_LOAD_AFTER);
 

	
 
		error->data = GetString(STR_NEWGRF_ERROR_AFTER_TRANSLATED_FILE);
 

	
 
		return;
 
	}
 

	
 
	/* Since no language id is supplied for with version 7 and lower NewGRFs, this string has
 
	 * to be added as a generic string, thus the language id of 0x7F. For this to work
 
	 * new_scheme has to be true as well, which will also be implicitly the case for version 8
 
	 * and higher. A language id of 0x7F will be overridden by a non-generic id, so this will
 
	 * not change anything if a string has been provided specifically for this language. */
 
	byte language = _cur.grffile->grf_version >= 8 ? buf->ReadByte() : 0x7F;
 
	byte num_strings = buf->ReadByte();
 
	uint16 first_id  = buf->ReadWord();
 

	
 
	if (!((first_id >= 0xD000 && first_id + num_strings <= 0xD400) || (first_id >= 0xD800 && first_id + num_strings <= 0xE000))) {
 
		GrfMsg(7, "TranslateGRFStrings: Attempting to set out-of-range string IDs in action 13 (first: {:#04X}, number: {:#02X})", first_id, num_strings);
 
		GrfMsg(7, "TranslateGRFStrings: Attempting to set out-of-range string IDs in action 13 (first: 0x{:04X}, number: 0x{:02X})", first_id, num_strings);
 
		return;
 
	}
 

	
 
	for (uint i = 0; i < num_strings && buf->HasData(); i++) {
 
		const char *string = buf->ReadString();
 

	
 
		if (StrEmpty(string)) {
 
			GrfMsg(7, "TranslateGRFString: Ignoring empty string.");
 
			continue;
 
		}
 

	
 
		AddGRFString(grfid, first_id + i, language, true, true, string, STR_UNDEFINED);
 
	}
 
}
 

	
 
/** Callback function for 'INFO'->'NAME' to add a translation to the newgrf name. */
 
static bool ChangeGRFName(byte langid, const char *str)
 
{
 
	AddGRFTextToList(_cur.grfconfig->name, langid, _cur.grfconfig->ident.grfid, false, str);
 
	return true;
 
}
 

	
 
/** Callback function for 'INFO'->'DESC' to add a translation to the newgrf description. */
 
static bool ChangeGRFDescription(byte langid, const char *str)
 
@@ -9479,53 +9479,53 @@ static void DecodeSpecialSprite(byte *bu
 
	if (it == _grf_line_to_action6_sprite_override.end()) {
 
		/* No preloaded sprite to work with; read the
 
		 * pseudo sprite content. */
 
		_cur.file->ReadBlock(buf, num);
 
	} else {
 
		/* Use the preloaded sprite data. */
 
		buf = _grf_line_to_action6_sprite_override[location];
 
		GrfMsg(7, "DecodeSpecialSprite: Using preloaded pseudo sprite data");
 

	
 
		/* Skip the real (original) content of this action. */
 
		_cur.file->SeekTo(num, SEEK_CUR);
 
	}
 

	
 
	ByteReader br(buf, buf + num);
 
	ByteReader *bufp = &br;
 

	
 
	try {
 
		byte action = bufp->ReadByte();
 

	
 
		if (action == 0xFF) {
 
			GrfMsg(2, "DecodeSpecialSprite: Unexpected data block, skipping");
 
		} else if (action == 0xFE) {
 
			GrfMsg(2, "DecodeSpecialSprite: Unexpected import block, skipping");
 
		} else if (action >= lengthof(handlers)) {
 
			GrfMsg(7, "DecodeSpecialSprite: Skipping unknown action {:#02X}", action);
 
			GrfMsg(7, "DecodeSpecialSprite: Skipping unknown action 0x{:02X}", action);
 
		} else if (handlers[action][stage] == nullptr) {
 
			GrfMsg(7, "DecodeSpecialSprite: Skipping action {:#02X} in stage {}", action, stage);
 
			GrfMsg(7, "DecodeSpecialSprite: Skipping action 0x{:02X} in stage {}", action, stage);
 
		} else {
 
			GrfMsg(7, "DecodeSpecialSprite: Handling action {:#02X} in stage {}", action, stage);
 
			GrfMsg(7, "DecodeSpecialSprite: Handling action 0x{:02X} in stage {}", action, stage);
 
			handlers[action][stage](bufp);
 
		}
 
	} catch (...) {
 
		GrfMsg(1, "DecodeSpecialSprite: Tried to read past end of pseudo-sprite data");
 
		DisableGrf(STR_NEWGRF_ERROR_READ_BOUNDS);
 
	}
 
}
 

	
 
/**
 
 * Load a particular NewGRF from a SpriteFile.
 
 * @param config The configuration of the to be loaded NewGRF.
 
 * @param stage  The loading stage of the NewGRF.
 
 * @param file   The file to load the GRF data from.
 
 */
 
static void LoadNewGRFFileFromFile(GRFConfig *config, GrfLoadingStage stage, SpriteFile &file)
 
{
 
	_cur.file = &file;
 
	_cur.grfconfig = config;
 

	
 
	Debug(grf, 2, "LoadNewGRFFile: Reading NewGRF-file '{}'", config->filename);
 

	
 
	byte grf_container_version = file.GetContainerVersion();
 
	if (grf_container_version == 0) {
 
		Debug(grf, 7, "LoadNewGRFFile: Custom .grf has invalid format");