OpenTTD Source 20260421-master-gc2fbc6fdeb
newgrf_act14.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
11#include "../debug.h"
12#include "../newgrf_text.h"
13#include "newgrf_bytereader.h"
14#include "newgrf_internal.h"
15
16#include "../safeguards.h"
17
19static bool ChangeGRFName(uint8_t langid, std::string_view str)
20{
21 AddGRFTextToList(_cur_gps.grfconfig->name, langid, _cur_gps.grfconfig->ident.grfid, false, str);
22 return true;
23}
24
26static bool ChangeGRFDescription(uint8_t langid, std::string_view str)
27{
28 AddGRFTextToList(_cur_gps.grfconfig->info, langid, _cur_gps.grfconfig->ident.grfid, true, str);
29 return true;
30}
31
33static bool ChangeGRFURL(uint8_t langid, std::string_view str)
34{
35 AddGRFTextToList(_cur_gps.grfconfig->url, langid, _cur_gps.grfconfig->ident.grfid, false, str);
36 return true;
37}
38
40static bool ChangeGRFNumUsedParams(size_t len, ByteReader &buf)
41{
42 if (len != 1) {
43 GrfMsg(2, "StaticGRFInfo: expected only 1 byte for 'INFO'->'NPAR' but got {}, ignoring this field", len);
44 buf.Skip(len);
45 } else {
46 _cur_gps.grfconfig->num_valid_params = std::min(buf.ReadByte(), GRFConfig::MAX_NUM_PARAMS);
47 }
48 return true;
49}
50
52static bool ChangeGRFPalette(size_t len, ByteReader &buf)
53{
54 if (len != 1) {
55 GrfMsg(2, "StaticGRFInfo: expected only 1 byte for 'INFO'->'PALS' but got {}, ignoring this field", len);
56 buf.Skip(len);
57 } else {
58 char data = buf.ReadByte();
60 switch (data) {
61 case '*':
62 case 'A': pal = GRFP_GRF_ANY; break;
63 case 'W': pal = GRFP_GRF_WINDOWS; break;
64 case 'D': pal = GRFP_GRF_DOS; break;
65 default:
66 GrfMsg(2, "StaticGRFInfo: unexpected value '{:02X}' for 'INFO'->'PALS', ignoring this field", data);
67 break;
68 }
69 if (pal != GRFP_GRF_UNSET) {
70 _cur_gps.grfconfig->palette &= ~GRFP_GRF_MASK;
71 _cur_gps.grfconfig->palette |= pal;
72 }
73 }
74 return true;
75}
76
78static bool ChangeGRFBlitter(size_t len, ByteReader &buf)
79{
80 if (len != 1) {
81 GrfMsg(2, "StaticGRFInfo: expected only 1 byte for 'INFO'->'BLTR' but got {}, ignoring this field", len);
82 buf.Skip(len);
83 } else {
84 char data = buf.ReadByte();
86 switch (data) {
87 case '8': pal = GRFP_BLT_UNSET; break;
88 case '3': pal = GRFP_BLT_32BPP; break;
89 default:
90 GrfMsg(2, "StaticGRFInfo: unexpected value '{:02X}' for 'INFO'->'BLTR', ignoring this field", data);
91 return true;
92 }
93 _cur_gps.grfconfig->palette &= ~GRFP_BLT_MASK;
94 _cur_gps.grfconfig->palette |= pal;
95 }
96 return true;
97}
98
100static bool ChangeGRFVersion(size_t len, ByteReader &buf)
101{
102 if (len != 4) {
103 GrfMsg(2, "StaticGRFInfo: expected 4 bytes for 'INFO'->'VRSN' but got {}, ignoring this field", len);
104 buf.Skip(len);
105 } else {
106 /* Set min_loadable_version as well (default to minimal compatibility) */
107 _cur_gps.grfconfig->version = _cur_gps.grfconfig->min_loadable_version = buf.ReadDWord();
108 }
109 return true;
110}
111
113static bool ChangeGRFMinVersion(size_t len, ByteReader &buf)
114{
115 if (len != 4) {
116 GrfMsg(2, "StaticGRFInfo: expected 4 bytes for 'INFO'->'MINV' but got {}, ignoring this field", len);
117 buf.Skip(len);
118 } else {
119 _cur_gps.grfconfig->min_loadable_version = buf.ReadDWord();
120 if (_cur_gps.grfconfig->version == 0) {
121 GrfMsg(2, "StaticGRFInfo: 'MINV' defined before 'VRSN' or 'VRSN' set to 0, ignoring this field");
122 _cur_gps.grfconfig->min_loadable_version = 0;
123 }
124 if (_cur_gps.grfconfig->version < _cur_gps.grfconfig->min_loadable_version) {
125 GrfMsg(2, "StaticGRFInfo: 'MINV' defined as {}, limiting it to 'VRSN'", _cur_gps.grfconfig->min_loadable_version);
126 _cur_gps.grfconfig->min_loadable_version = _cur_gps.grfconfig->version;
127 }
128 }
129 return true;
130}
131
133
135static bool ChangeGRFParamName(uint8_t langid, std::string_view str)
136{
137 AddGRFTextToList(_cur_parameter->name, langid, _cur_gps.grfconfig->ident.grfid, false, str);
138 return true;
139}
140
142static bool ChangeGRFParamDescription(uint8_t langid, std::string_view str)
143{
144 AddGRFTextToList(_cur_parameter->desc, langid, _cur_gps.grfconfig->ident.grfid, true, str);
145 return true;
146}
147
149static bool ChangeGRFParamType(size_t len, ByteReader &buf)
150{
151 if (len != 1) {
152 GrfMsg(2, "StaticGRFInfo: expected 1 byte for 'INFO'->'PARA'->'TYPE' but got {}, ignoring this field", len);
153 buf.Skip(len);
154 } else {
155 GRFParameterType type = static_cast<GRFParameterType>(buf.ReadByte());
156 switch (type) {
159 _cur_parameter->type = type;
160 break;
161
162 default:
163 GrfMsg(3, "StaticGRFInfo: unknown parameter type {}, ignoring this field", type);
164 break;
165 }
166 }
167 return true;
168}
169
171static bool ChangeGRFParamLimits(size_t len, ByteReader &buf)
172{
174 GrfMsg(2, "StaticGRFInfo: 'INFO'->'PARA'->'LIMI' is only valid for parameters with type uint/enum, ignoring this field");
175 buf.Skip(len);
176 } else if (len != 8) {
177 GrfMsg(2, "StaticGRFInfo: expected 8 bytes for 'INFO'->'PARA'->'LIMI' but got {}, ignoring this field", len);
178 buf.Skip(len);
179 } else {
180 uint32_t min_value = buf.ReadDWord();
181 uint32_t max_value = buf.ReadDWord();
182 if (min_value <= max_value) {
183 _cur_parameter->min_value = min_value;
184 _cur_parameter->max_value = max_value;
185 } else {
186 GrfMsg(2, "StaticGRFInfo: 'INFO'->'PARA'->'LIMI' values are incoherent, ignoring this field");
187 }
188 }
189 return true;
190}
191
193static bool ChangeGRFParamMask(size_t len, ByteReader &buf)
194{
195 if (len < 1 || len > 3) {
196 GrfMsg(2, "StaticGRFInfo: expected 1 to 3 bytes for 'INFO'->'PARA'->'MASK' but got {}, ignoring this field", len);
197 buf.Skip(len);
198 } else {
199 uint8_t param_nr = buf.ReadByte();
200 if (param_nr >= GRFConfig::MAX_NUM_PARAMS) {
201 GrfMsg(2, "StaticGRFInfo: invalid parameter number in 'INFO'->'PARA'->'MASK', param {}, ignoring this field", param_nr);
202 buf.Skip(len - 1);
203 } else {
204 _cur_parameter->param_nr = param_nr;
205 if (len >= 2) _cur_parameter->first_bit = std::min<uint8_t>(buf.ReadByte(), 31);
206 if (len >= 3) _cur_parameter->num_bit = std::min<uint8_t>(buf.ReadByte(), 32 - _cur_parameter->first_bit);
207 }
208 }
209
210 return true;
211}
212
214static bool ChangeGRFParamDefault(size_t len, ByteReader &buf)
215{
216 if (len != 4) {
217 GrfMsg(2, "StaticGRFInfo: expected 4 bytes for 'INFO'->'PARA'->'DEFA' but got {}, ignoring this field", len);
218 buf.Skip(len);
219 } else {
220 _cur_parameter->def_value = buf.ReadDWord();
221 }
222 _cur_gps.grfconfig->has_param_defaults = true;
223 return true;
224}
225
232using DataHandler = bool(*)(size_t len, ByteReader &buf);
233
240using TextHandler = bool(*)(uint8_t langid, std::string_view str);
241
247using BranchHandler = bool(*)(ByteReader &buf);
248
258 using Span = std::pair<const AllowedSubtags *, const AllowedSubtags *>;
259
260 uint32_t id;
261 std::variant<DataHandler, TextHandler, BranchHandler, Span> handler;
262};
263
264static bool SkipUnknownInfo(ByteReader &buf, uint8_t type);
265static bool HandleNodes(ByteReader &buf, std::span<const AllowedSubtags> tags);
266
275{
276 uint8_t type = buf.ReadByte();
277 while (type != 0) {
278 uint32_t id = buf.ReadDWord();
279 if (type != 'T' || id > _cur_parameter->max_value) {
280 GrfMsg(2, "StaticGRFInfo: all child nodes of 'INFO'->'PARA'->param_num->'VALU' should have type 't' and the value/bit number as id");
281 if (!SkipUnknownInfo(buf, type)) return false;
282 type = buf.ReadByte();
283 continue;
284 }
285
286 uint8_t langid = buf.ReadByte();
287 std::string_view name_string = buf.ReadString();
288
289 auto it = std::ranges::lower_bound(_cur_parameter->value_names, id, std::less{}, &GRFParameterInfo::ValueName::first);
290 if (it == std::end(_cur_parameter->value_names) || it->first != id) {
291 it = _cur_parameter->value_names.emplace(it, id, GRFTextList{});
292 }
293 AddGRFTextToList(it->second, langid, _cur_gps.grfconfig->ident.grfid, false, name_string);
294
295 type = buf.ReadByte();
296 }
297 return true;
298}
299
310
319{
320 uint8_t type = buf.ReadByte();
321 while (type != 0) {
322 uint32_t id = buf.ReadDWord();
323 if (type != 'C' || id >= _cur_gps.grfconfig->num_valid_params) {
324 GrfMsg(2, "StaticGRFInfo: all child nodes of 'INFO'->'PARA' should have type 'C' and their parameter number as id");
325 if (!SkipUnknownInfo(buf, type)) return false;
326 type = buf.ReadByte();
327 continue;
328 }
329
330 if (id >= _cur_gps.grfconfig->param_info.size()) {
331 _cur_gps.grfconfig->param_info.resize(id + 1);
332 }
333 if (!_cur_gps.grfconfig->param_info[id].has_value()) {
334 _cur_gps.grfconfig->param_info[id] = GRFParameterInfo(id);
335 }
336 _cur_parameter = &_cur_gps.grfconfig->param_info[id].value();
337 /* Read all parameter-data and process each node. */
338 if (!HandleNodes(buf, _tags_parameters)) return false;
339 type = buf.ReadByte();
340 }
341 return true;
342}
343
356
358static constexpr AllowedSubtags _tags_root[] = {
359 AllowedSubtags{'INFO', std::make_pair(std::begin(_tags_info), std::end(_tags_info))},
360};
361
362
369static bool SkipUnknownInfo(ByteReader &buf, uint8_t type)
370{
371 /* type and id are already read */
372 switch (type) {
373 case 'C': {
374 uint8_t new_type = buf.ReadByte();
375 while (new_type != 0) {
376 buf.ReadDWord(); // skip the id
377 if (!SkipUnknownInfo(buf, new_type)) return false;
378 new_type = buf.ReadByte();
379 }
380 break;
381 }
382
383 case 'T':
384 buf.ReadByte(); // lang
385 buf.ReadString(); // actual text
386 break;
387
388 case 'B': {
389 uint16_t size = buf.ReadWord();
390 buf.Skip(size);
391 break;
392 }
393
394 default:
395 return false;
396 }
397
398 return true;
399}
400
409static bool HandleNode(uint8_t type, uint32_t id, ByteReader &buf, std::span<const AllowedSubtags> subtags)
410{
411 /* Visitor to get a subtag handler's type. */
412 struct type_visitor {
413 char operator()(const DataHandler &) { return 'B'; }
414 char operator()(const TextHandler &) { return 'T'; }
415 char operator()(const BranchHandler &) { return 'C'; }
416 char operator()(const AllowedSubtags::Span &) { return 'C'; }
417 };
418
419 /* Visitor to evaluate a subtag handler. */
420 struct evaluate_visitor {
421 ByteReader &buf;
422
423 bool operator()(const DataHandler &handler)
424 {
425 size_t len = buf.ReadWord();
426 if (buf.Remaining() < len) return false;
427 return handler(len, buf);
428 }
429
430 bool operator()(const TextHandler &handler)
431 {
432 uint8_t langid = buf.ReadByte();
433 return handler(langid, buf.ReadString());
434 }
435
436 bool operator()(const BranchHandler &handler)
437 {
438 return handler(buf);
439 }
440
441 bool operator()(const AllowedSubtags::Span &subtags)
442 {
443 return HandleNodes(buf, {subtags.first, subtags.second});
444 }
445 };
446
447 for (const auto &tag : subtags) {
448 if (tag.id != std::byteswap(id) || std::visit(type_visitor{}, tag.handler) != type) continue;
449 return std::visit(evaluate_visitor{buf}, tag.handler);
450 }
451
452 GrfMsg(2, "StaticGRFInfo: unknown type/id combination found, type={:c}, id={:x}", type, id);
453 return SkipUnknownInfo(buf, type);
454}
455
462static bool HandleNodes(ByteReader &buf, std::span<const AllowedSubtags> subtags)
463{
464 uint8_t type = buf.ReadByte();
465 while (type != 0) {
466 uint32_t id = buf.ReadDWord();
467 if (!HandleNode(type, id, buf, subtags)) return false;
468 type = buf.ReadByte();
469 }
470 return true;
471}
472
477static void StaticGRFInfo(ByteReader &buf)
478{
479 /* <14> <type> <id> <text/data...> */
481}
482
constexpr enable_if_t< is_integral_v< T >, T > byteswap(T x) noexcept
Custom implementation of std::byteswap; remove once we build with C++23.
Class to read from a NewGRF file.
uint32_t ReadDWord()
Read a single DWord (32 bits).
uint16_t ReadWord()
Read a single Word (16 bits).
std::string_view ReadString()
Read a NUL-terminated string.
uint8_t ReadByte()
Read a single byte (8 bits).
Functions related to debugging.
static bool ChangeGRFParamType(size_t len, ByteReader &buf)
Callback function for 'INFO'->'PARAM'->param_num->'TYPE' to set the typeof a parameter.
static bool ChangeGRFParamLimits(size_t len, ByteReader &buf)
Callback function for 'INFO'->'PARAM'->param_num->'LIMI' to set the min/max value of a parameter.
static bool ChangeGRFNumUsedParams(size_t len, ByteReader &buf)
Callback function for 'INFO'->'NPAR' to set the number of valid parameters.
static constexpr AllowedSubtags _tags_info[]
Action14 tags for the INFO node.
static bool ChangeGRFURL(uint8_t langid, std::string_view str)
Callback function for 'INFO'->'URL_' to set the newgrf url.
static bool ChangeGRFParamDescription(uint8_t langid, std::string_view str)
Callback function for 'INFO'->'PARAM'->param_num->'DESC' to set the description of a parameter.
static bool ChangeGRFMinVersion(size_t len, ByteReader &buf)
Callback function for 'INFO'->'MINV' to the minimum compatible version of the NewGRF.
static bool ChangeGRFParamDefault(size_t len, ByteReader &buf)
Callback function for 'INFO'->'PARAM'->param_num->'DFLT' to set the default value.
static bool ChangeGRFParamValueNames(ByteReader &buf)
Callback function for 'INFO'->'PARA'->param_num->'VALU' to set the names of some parameter values (ty...
static bool HandleParameterInfo(ByteReader &buf)
Callback function for 'INFO'->'PARA' to set extra information about the parameters.
static GRFParameterInfo * _cur_parameter
The parameter which info is currently changed by the newgrf.
static bool HandleNodes(ByteReader &buf, std::span< const AllowedSubtags > tags)
Handle the contents of a 'C' choice of an Action14.
bool(*)(uint8_t langid, std::string_view str) TextHandler
Callback to read text data.
static bool ChangeGRFName(uint8_t langid, std::string_view str)
Callback function for 'INFO'->'NAME' to add a translation to the newgrf name.
bool(*)(size_t len, ByteReader &buf) DataHandler
Callback to read binary data.
bool(*)(ByteReader &buf) BranchHandler
Callback for parsing branch nodes.
static constexpr AllowedSubtags _tags_root[]
Action14 root tags.
static void StaticGRFInfo(ByteReader &buf)
Handle Action 0x14.
static bool ChangeGRFParamMask(size_t len, ByteReader &buf)
Callback function for 'INFO'->'PARAM'->param_num->'MASK' to set the parameter and bits to use.
static bool SkipUnknownInfo(ByteReader &buf, uint8_t type)
Try to skip the current node and all subnodes (if it's a branch node).
static bool ChangeGRFDescription(uint8_t langid, std::string_view str)
Callback function for 'INFO'->'DESC' to add a translation to the newgrf description.
static bool ChangeGRFPalette(size_t len, ByteReader &buf)
Callback function for 'INFO'->'PALS' to set the number of valid parameters.
static bool ChangeGRFVersion(size_t len, ByteReader &buf)
Callback function for 'INFO'->'VRSN' to the version of the NewGRF.
static bool ChangeGRFParamName(uint8_t langid, std::string_view str)
Callback function for 'INFO'->'PARAM'->param_num->'NAME' to set the name of a parameter.
static bool HandleNode(uint8_t type, uint32_t id, ByteReader &buf, std::span< const AllowedSubtags > subtags)
Handle the nodes of an Action14.
static bool ChangeGRFBlitter(size_t len, ByteReader &buf)
Callback function for 'INFO'->'BLTR' to set the blitter info.
static constexpr AllowedSubtags _tags_parameters[]
Action14 parameter tags.
NewGRF buffer reader definition.
GRFParameterType
The possible types of a newgrf parameter.
@ UintEnum
The parameter allows a range of numbers, each of which can have a special name.
@ Bool
The parameter is either 0 or 1.
GRFPalette
Information that can/has to be stored about a GRF's palette.
@ GRFP_GRF_UNSET
The NewGRF provided no information.
@ GRFP_BLT_UNSET
The NewGRF provided no information or doesn't care about a 32 bpp blitter.
@ GRFP_GRF_WINDOWS
The NewGRF says the Windows palette can be used.
@ GRFP_GRF_DOS
The NewGRF says the DOS palette can be used.
@ GRFP_GRF_ANY
The NewGRF says any palette can be used.
@ GRFP_BLT_MASK
Bitmask to only get the blitter information.
@ GRFP_BLT_32BPP
The NewGRF prefers a 32 bpp blitter.
@ GRFP_GRF_MASK
Bitmask to get only the NewGRF supplied information.
NewGRF internal processing state.
static void AddGRFTextToList(GRFTextList &list, uint8_t langid, std::string_view text_to_add)
Add a new text to a GRFText list.
Header of Action 04 "universal holder" structure and functions.
std::vector< GRFText > GRFTextList
A GRF text with a list of translations.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
Data structure to store the allowed id/type combinations for action 14.
uint32_t id
The identifier for this node.
std::variant< DataHandler, TextHandler, BranchHandler, Span > handler
The handler for this node.
std::pair< const AllowedSubtags *, const AllowedSubtags * > Span
Custom 'span' of subtags.
Information about one grf parameter.
static void FileScan(ByteReader &buf)
Implementation of the GrfLoadingStage::FileScan stage of this action.
static void SafetyScan(ByteReader &buf)
Implementation of the GrfLoadingStage::SafetyScan stage of this action.
static void Reserve(ByteReader &buf)
Implementation of the GrfLoadingStage::Reserve stage of this action.
static void Activation(ByteReader &buf)
Implementation of the GrfLoadingStage::Activation stage of this action.
static void Init(ByteReader &buf)
Implementation of the GrfLoadingStage::Init stage of this action.
static void LabelScan(ByteReader &buf)
Implementation of the GrfLoadingStage::LabelScan stage of this action.