summaryrefslogtreecommitdiff
path: root/grfspecial.c
blob: 7991ab6a3bd76a8dfa983454dfe9259bbfc4063c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
#include "stdafx.h"

#include <stdarg.h>

#include "ttd.h"
#include "gfx.h"
#include "fileio.h"
#include "engine.h"

/* TTDPatch extended GRF format codec
 * (c) Petr Baudis 2004 (GPL'd)
 * Contains portions of documentation by TTDPatch team.
 * Thanks especially to Josef Drexler for the documentation as well as a lot
 * of help at #tycoon. Also thanks to Michael Blunck for is GRF files which
 * served as subject to the initial testing of this codec. */

extern int _skip_sprites;

static const char *_cur_grffile;
static int _cur_spriteid;

typedef void (*SpecialSpriteHandler)(byte *buf, int len);

static const int _vehshifts[4] = {
	0,
	ROAD_ENGINES_INDEX,
	SHIP_ENGINES_INDEX,
	AIRCRAFT_ENGINES_INDEX,
};


enum grfmsg_severity {
	GMS_NOTICE,
	GMS_WARN,
	GMS_ERROR,
	GMS_FATAL,
};

static void CDECL grfmsg(enum grfmsg_severity severity, const char *str, ...)
{
	static const char * const severitystr[4] = { "Notice", "Warning", "Error", "Fatal" };
	char buf[1024];
	va_list va;

	va_start(va, str);
	vsprintf(buf, str, va);
	va_end(va);
	DEBUG(grf, 2) ("[%s][%s] %s", _cur_grffile, severitystr[severity], buf);
}

static byte INLINE grf_load_byte(byte **buf) {
	return *(*buf)++;
}


static uint16 grf_load_word(byte **buf)
{
	uint16 val;
	byte *p = *buf;
	val = p[0];
	val |= p[1] << 8;
	*buf = p + 2;
	return val;
}

static uint16 grf_load_dword(byte **buf)
{
	uint32 val;
	byte *p = *buf;
	val = p[0];
	val |= p[1] << 8;
	val |= p[2] << 16;
	val |= p[3] << 24;
	*buf = p + 4;
	return val;
}

typedef int (*VCI_Handler)(uint engine, int numinfo, int prop, byte **buf, int len);

#define foreach_engine for (i = 0; i < numinfo; i++)

static void dewagonize(int condition, int engine)
{
	EngineInfo *ei = &_engine_info[engine];
	RailVehicleInfo *rvi = &_rail_vehicle_info[engine];

	if (condition) {
		ei->unk2 &= ~0x80;
		rvi->flags &= ~2;
	} else {
		ei->unk2 |= 0x80;
		rvi->flags |= 2;
	}
}

static int RailVehicleChangeInfo(uint engine, int numinfo, int prop, byte **bufp, int len)
{
	EngineInfo *ei = &_engine_info[engine];
	RailVehicleInfo *rvi = &_rail_vehicle_info[engine];
	byte *buf = *bufp;
	int i;
	int ret = 0;

	switch (prop) {
		case 0x05:
		{	/* Track type */
			foreach_engine {
				uint8 tracktype = grf_load_byte(&buf);

				ei[i].railtype_climates &= 0xf;
				ei[i].railtype_climates |= tracktype << 4;
			}
			break;
		}
		case 0x08:
		{	/* AI passenger service */
			/* TODO */
			foreach_engine {
				grf_load_byte(&buf);
			}
			ret = 1;
			break;
		}
		case 0x09:
		{	/* Speed */
			foreach_engine {
				uint16 speed = grf_load_word(&buf);

				rvi[i].max_speed = speed;
				dewagonize(speed, engine + i);
			}
			break;
		}
		case 0x0b:
		{	/* Power */
			foreach_engine {
				uint16 power = grf_load_word(&buf);

				rvi[i].power = power;
				dewagonize(power, engine + i);
			}
			break;
		}
		case 0x0d:
		{	/* Running cost factor */
			foreach_engine {
				uint8 runcostfact = grf_load_byte(&buf);

				rvi[i].running_cost_base = runcostfact;
				dewagonize(runcostfact, engine + i);
			}
			break;
		}
		case 0x0e:
		{	/* Running cost base */
			foreach_engine {
				uint32 base = grf_load_dword(&buf);

				switch (base) {
				case 0x4c30:
					rvi[i].engclass = 0;
					break;
				case 0x4c36:
					rvi[i].engclass = 1;
					break;
				case 0x4c3c:
					rvi[i].engclass = 2;
					break;
				}
				dewagonize(base, engine + i);
			}
			break;
		}
		case 0x12:
		{	/* Sprite ID */
			foreach_engine {
				uint8 spriteid = grf_load_byte(&buf);

				if (spriteid == 0xfd && rvi[i].image_index != 0xfd)
					_engine_original_sprites[engine + i] = rvi[i].image_index;
				rvi[i].image_index = spriteid;
			}
			break;
		}
		case 0x13:
		{	/* Dual-headed */
			foreach_engine {
				uint8 dual = grf_load_byte(&buf);

				if (dual) {
					rvi[i].flags |= 1;
				} else {
					rvi[i].flags &= ~1;
				}
			}
			break;
		}
		case 0x14:
		{	/* Cargo capacity */
			foreach_engine {
				uint8 capacity = grf_load_byte(&buf);

				rvi[i].capacity = capacity;
			}
			break;
		}
		case 0x15:
		{	/* Cargo type */
			foreach_engine {
				uint8 ctype = grf_load_byte(&buf);

				rvi[i].cargo_type = ctype;
			}
			break;
		}
		case 0x16:
		{	/* Weight */
			foreach_engine {
				uint8 weight = grf_load_byte(&buf);

				rvi[i].weight = weight;
			}
			break;
		}
		case 0x17:
		{	/* Cost factor */
			foreach_engine {
				uint8 cfactor = grf_load_byte(&buf);

				rvi[i].base_cost = cfactor;
			}
			break;
		}
		case 0x18:
		{	/* AI rank */
			/* TODO: _railveh_score should be merged to _rail_vehicle_info. */
			foreach_engine {
				grf_load_byte(&buf);
			}
			ret = 1;
			break;
		}
		case 0x19:
		{	/* Engine traction type */
			/* TODO: What do the individual numbers mean?
			 * XXX: And in what base are they, in fact? --pasky */
			foreach_engine {
				uint8 traction = grf_load_byte(&buf);
				int engclass;

				if (traction <= 0x07)
					engclass = 0;
				else if (traction <= 0x27)
					engclass = 1;
				else if (traction <= 0x31)
					engclass = 2;
				else
					break;

				rvi[i].engclass = engclass;
			}
			break;
		}
		/* TODO */
		/* Fall-through for unimplemented four bytes long properties. */
		case 0x1d:	/* Refit cargo */
			foreach_engine {
				grf_load_word(&buf);
			}
		/* Fall-through for unimplemented two bytes long properties. */
		case 0x1b:	/* Powered wagons power bonus */
			foreach_engine {
				grf_load_byte(&buf);
			}
		/* Fall-through for unimplemented one byte long properties. */
		case 0x1a:	/* Sort order */
		case 0x1c:	/* Refit cost */
		case 0x1e:	/* Callback */
		case 0x1f:	/* Tractive effort */
		case 0x21:	/* Shorter tenders */
		case 0x22:	/* Visual */
		case 0x23:	/* Powered wagons weight bonus */
			/* TODO */
			foreach_engine {
				grf_load_byte(&buf);
			}
			ret = 1;
			break;
		default:
			ret = 1;
			break;
	}
	*bufp = buf;
	return ret;
}

static int ShipVehicleChangeInfo(uint engine, int numinfo, int prop, byte **bufp, int len)
{
	ShipVehicleInfo *svi = &_ship_vehicle_info[engine];
	byte *buf = *bufp;
	int i;
	int ret = 0;

	//printf("e %x prop %x?\n", engine, prop);
	switch (prop) {
		case 0x08:
		{	/* Sprite ID */
			foreach_engine {
				uint8 spriteid = grf_load_byte(&buf);

				if (spriteid == 0xff)
					spriteid = 0xfd; // ships have different custom id in the GRF file

				// This is currently not used but there's no reason
				// in not having it here for the future.
				if (spriteid == 0xfd
				    && svi[i].image_index != 0xfd)
					_engine_original_sprites[SHIP_ENGINES_INDEX
					                         + engine + i]
						= svi[i].image_index;
				svi[i].image_index = spriteid;
			}
			break;
		}
		case 0x09:
		{	/* Refittable */
			foreach_engine {
				uint8 refittable = grf_load_byte(&buf);

				svi[i].refittable = refittable;
			}
			break;
		}
		case 0x0a:
		{	/* Cost factor */
			foreach_engine {
				uint8 cost_factor = grf_load_byte(&buf);

				svi[i].base_cost = cost_factor; // ?? is it base_cost?
			}
			break;
		}
		case 0x0b:
		{	/* Speed */
			foreach_engine {
				uint8 speed = grf_load_byte(&buf);

				svi[i].max_speed = speed; // ?? units
			}
			break;
		}
		case 0x0c:
		{	/* Cargo type */

			foreach_engine {
				uint8 cargo = grf_load_byte(&buf);

				// XXX: Need to consult this with patchman yet.
#if 0
				// Documentation claims this is already the
				// per-landscape cargo type id, but newships.grf
				// assume otherwise.
				cargo = local_cargo_id_ctype[cargo];
#endif
				svi[i].cargo_type = cargo;
			}
			break;
		}
		case 0x0d:
		{	/* Cargo capacity */
			foreach_engine {
				uint16 capacity = grf_load_word(&buf);

				svi[i].capacity = capacity;
			}
			break;
		}
		case 0x0f:
		{	/* Running cost factor */
			foreach_engine {
				uint8 runcost = grf_load_byte(&buf);

				svi[i].running_cost = runcost;
			}
			break;
		}
		case 0x10:
		{	/* SFX */
			foreach_engine {
				uint8 sfx = grf_load_byte(&buf);

				svi[i].sfx = sfx;
			}
			break;
		}
		case 0x11:
		{	/* Cargos available for refitting */
			foreach_engine {
				uint32 refit_mask = grf_load_dword(&buf);

				_engine_refit_masks[SHIP_ENGINES_INDEX + engine + i] = refit_mask;
			}
			break;
		}
		case 0x12:
		{	/* Callback */
			/* TODO */
			ret = 1;
			break;
		}
		default:
			ret = 1;
			break;
	}

	*bufp = buf;
	return ret;
}

#undef shift_buf

static void VehicleChangeInfo(byte *buf, int len)
{
	byte *bufend = buf + len;
	int i;

	/* <00> <feature> <num-props> <num-info> <id> (<property <new-info>)...
	 *
	 * B feature       0, 1, 2 or 3 for trains, road vehicles, ships or planes
	 *                 4 for defining new train station sets
	 * B num-props     how many properties to change per vehicle/station
	 * B num-info      how many vehicles/stations to change
	 * B 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) */
	/* TODO: Only trains and ships are supported for now. */

	static const VCI_Handler handler[5] = {
		RailVehicleChangeInfo,
		NULL,
		ShipVehicleChangeInfo,
		NULL,
		NULL,
	};

	if (len > 5) {
		uint8 feature = buf[1];
		uint8 numprops = buf[2];
		uint8 numinfo = buf[3];
		byte engine = buf[4];
		EngineInfo *ei;

		if (feature != 0 && feature != 2) {
			grfmsg(GMS_WARN, "VehicleChangeInfo: Unsupported vehicle type %x, skipping.", feature);
			return;
		}

		ei = &_engine_info[engine + _vehshifts[feature]];

		buf += 5;

		while (numprops-- && buf < bufend) {
			uint8 prop = grf_load_byte(&buf);

			switch (prop) {
			case 0x00: {
				/* Introduction date */
				foreach_engine {
					uint16 date = grf_load_word(&buf);

					ei[i].base_intro = date;
				}
				break;
			}
			case 0x02: {
				/* Decay speed */
				foreach_engine {
					uint8 decay = grf_load_byte(&buf);

					ei[i].unk2 &= 0x80;
					ei[i].unk2 |= decay & 0x7f;
				}
				break;
			}
			case 0x03: {
				/* Vehicle life */
				foreach_engine {
					uint8 life = grf_load_byte(&buf);

					ei[i].lifelength = life;
				}
				break;
			}
			case 0x04: {
				/* Model life */
				foreach_engine {
					uint8 life = grf_load_byte(&buf);

					ei[i].base_life = life;
				}
				break;
			}
			case 0x06: {
				/* Climates available */
				foreach_engine {
					uint8 climates = grf_load_byte(&buf);

					ei[i].railtype_climates &= 0xf0;
					ei[i].railtype_climates |= climates;
				}
				break;
			}
			case 0x07: {
				/* Loading speed */
				/* Hyronymus explained me what does
				 * this mean and insists on having a
				 * credit ;-). --pasky */
				/* TODO: This needs to be supported by
				 * LoadUnloadVehicle() first. */
				foreach_engine {
					grf_load_byte(&buf);
				}
				goto ignoring;
			}
			default:
			{
				if (handler[feature](engine, numinfo, prop, &buf, bufend - buf)) {
ignoring:
					grfmsg(GMS_WARN, "VehicleChangeInfo: Ignoring property %x.", prop);
				}
				break;
			}
			}
		}
	}
#undef shift_buf
}


/* A sprite superset contains all sprites of a given vehicle (or multiple
 * vehicles) when carrying given cargo. It consists of several sprite sets.
 * Superset ids are refered as "cargo id"s by TTDPatch documentation,
 * contributing to the global confusion.
 *
 * A sprite set contains all sprites of a given vehicle carrying given cargo at
 * a given *stage* - that is usually its load stage. Ie. you can have a
 * spriteset for an empty wagon, wagon full of coal, half-filled wagon etc.
 * Each spriteset contains eight sprites (one per direction) or four sprites if
 * the vehicle is symmetric. */

static int _spriteset_start;
static int _spriteset_numsets;
static int _spriteset_numents;
static int _spriteset_feature;

static int _spritesset_count;
static struct SpriteSuperSet *_spritesset;

static void SpriteNewSet(byte *buf, int len)
{
	/* <01> <feature> <num-sets> <num-ent>
	 *
	 * B feature       feature to define sprites for
	 *                 0, 1, 2, 3: veh-type, 4: train stations
	 * B num-sets      number of sprite sets
	 * B 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.
	 *                 For stations, must be 12 (hex) for the eighteen
	 *                         different sprites that make up a station */
	/* TODO: No stations support. */

	if (len == 4) {
		uint8 feature = buf[1];

		if (feature == 4) {
			_spriteset_start = 0;
			grfmsg(GMS_WARN, "SpriteNewSet: Stations unsupported, skipping.");
			return;
		}

		_spriteset_start = _cur_spriteid + 1;
		_spriteset_numsets = buf[2];
		_spriteset_numents = buf[3];
		_spriteset_feature = feature;
	}
}

static void SpriteNewSuperset(byte *buf, int len)
{
	byte *bufend = buf + len;

	/* <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) */
	/* TODO: Only trains supported now. No 0x80-types (ugh). */
	/* TODO: Also, empty sprites aren't handled for now. Need to investigate
	 * the "opacity" rules for these, that is which sprite to fall back to
	 * when. --pasky */

	if (bufend - buf > 4) {
		uint8 feature = buf[1];
		uint8 setid = buf[2];
		uint8 numloaded = buf[3];
		uint8 numloading = buf[4];
		struct SpriteSuperSet *superset;
		int i;

		if (feature == 4) {
			grfmsg(GMS_WARN, "SpriteNewSuperset: Stations unsupported, skipping.");
			return;
		}

		if (numloaded == 0x81) {
			// XXX: This is _VERY_ ad hoc just to handle Dm3. And that is
			// a semi-futile ask because the great Patchman himself says
			// this is just buggy. It dereferences last (first) byte of
			// a schedule list pointer of the vehicle and if it's 0xff
			// it uses superset 01, otherwise it uses superset 00. Now
			// if _you_ understand _that_... We just assume it is never
			// 0xff and therefore go for superset 00. --pasky
			uint8 var = buf[4];
			//uint8 shiftnum = buf[5];
			//uint8 andmask = buf[6];
			uint8 nvar = buf[7];
			//uint32 val;
			uint16 def;

			grfmsg(GMS_WARN, "SpriteNewSuperset(0x81): Unsupported variable %x. Using default cid.", var);

			//val = (0xff << shiftnum) & andmask;

			//Go for the default.
			if (setid >= _spritesset_count) {
				_spritesset_count = setid + 1;
				_spritesset = realloc(_spritesset, _spritesset_count * sizeof(struct SpriteSuperSet));
			}
			buf += 8 + nvar * 4;
			def = grf_load_word(&buf);
			_spritesset[setid] = _spritesset[def];
			return;

		} else if (numloaded & 0x80) {
			grfmsg(GMS_WARN, "SpriteNewSuperset(0x%x): Unsupported special superset.", numloaded);
			return;
		}

		if (!_spriteset_start) {
			grfmsg(GMS_WARN, "SpriteNewSuperset: No sprite set to work on! Skipping.");
			return;
		}

		if (_spriteset_feature != feature) {
			grfmsg(GMS_WARN, "SpriteNewSuperset: Superset feature %x doesn't match set feature %x! Skipping.", feature, _spriteset_feature);
			return;
		}

		if (setid >= _spritesset_count) {
			_spritesset_count = setid + 1;
			_spritesset = realloc(_spritesset, _spritesset_count * sizeof(struct SpriteSuperSet));
		}
		superset = &_spritesset[setid];
		memset(superset, 0, sizeof(struct SpriteSuperSet));
		superset->sprites_per_set = _spriteset_numents;

		buf += 5;

		for (i = 0; buf < bufend && i < numloaded; i++) {
			uint16 spriteset_id = grf_load_word(&buf);

			if (_spritesset[setid].loaded_count > 16) {
				grfmsg(GMS_WARN, "SpriteNewSuperset: More than 16 sprites in superset %x, skipping.", setid);
				return;
			}
			superset->loaded[superset->loaded_count++]
				= _spriteset_start + spriteset_id * _spriteset_numents;
		}

		for (i = 0; buf < bufend && i < numloading; i++) {
			uint16 spriteset_id = grf_load_word(&buf);

			if (_spritesset[setid].loading_count > 16) {
				grfmsg(GMS_WARN, "SpriteNewSuperset: More than 16 sprites in superset %x, skipping.", setid);
				return;
			}
			superset->loading[superset->loading_count++] = _spriteset_start + spriteset_id * _spriteset_numents;
		}
	}
}

static void VehicleMapSpriteSuperset(byte *buf, int len)
{
	byte *bufend = buf + len;
	/* <03> <feature> <n-id> <ids>... <num-cid> [<cargo-type> <cid>]... <def-cid>
	 *
	 * 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 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 for this type of cargo
	 * W def-cid       default cargo ID */
	/* TODO: Only trains supported now. */
	/* TODO: Multiple cargo support could be useful even for trains/cars -
	 * cargo id 0xff is used for showing images in the build train list. */

	static byte *last_engines;
	static int last_engines_count;

	if (bufend - buf > 6) {
		uint8 feature = buf[1];
		uint8 idcount = buf[2] & 0x7f;
		int wagover = buf[2] & 0x80;
		uint8 cidcount = buf[3 + idcount];
		int c, i;

		if (feature == 4) {
			grfmsg(GMS_WARN, "VehicleMapSpriteSuperset: Stations unsupported, skipping.");
			return;
		}

		// FIXME: Tropicset contains things like:
		// 03 00 01 19 01 00 00 00 00 - this is missing one 00 at the end,
		// what should we exactly do with that? --pasky

		if (!_spriteset_start || !_spritesset) {
			grfmsg(GMS_WARN, "VehicleMapSpriteSuperset: No sprite set to work on! Skipping.");
			return;
		}

		if (!wagover && last_engines_count != idcount) {
			last_engines = realloc(last_engines, idcount);
			last_engines_count = idcount;
		}

		for (i = 0; i < idcount; i++) {
			uint8 engine = buf[3 + i] + _vehshifts[feature];
			byte *bp = &buf[4 + idcount];

			for (c = 0; c < cidcount; c++) {
				uint8 ctype = grf_load_byte(&bp);
				uint16 supersetid = grf_load_word(&bp);

				if (supersetid >= _spritesset_count) {
					grfmsg(GMS_WARN, "VehicleMapSpriteSuperset: Spriteset %x out of range %x, skipping.", supersetid, _spritesset_count);
					return;
				}

				if (ctype == 0xff)
					ctype = CID_PURCHASE;

				if (wagover) {
					// TODO: No multiple cargo types per vehicle yet. --pasky
					SetWagonOverrideSprites(engine, &_spritesset[supersetid], last_engines, last_engines_count);
				} else {
					SetCustomEngineSprites(engine, ctype, &_spritesset[supersetid]);
					last_engines[i] = engine;
				}
			}
		}

		{
			byte *bp = buf + 4 + idcount + cidcount * 3;
			uint16 supersetid = grf_load_word(&bp);

			for (i = 0; i < idcount; i++) {
				uint8 engine = buf[3 + i] + _vehshifts[feature];

				// Don't tell me you don't love duplicated code!
				if (supersetid >= _spritesset_count) {
					grfmsg(GMS_WARN, "VehicleMapSpriteSuperset: Spriteset %x out of range %x, skipping.", supersetid, _spritesset_count);
					return;
				}

				if (wagover) {
					// TODO: No multiple cargo types per vehicle yet. --pasky
					SetWagonOverrideSprites(engine, &_spritesset[supersetid], last_engines, last_engines_count);
				} else {
					SetCustomEngineSprites(engine, CID_DEFAULT, &_spritesset[supersetid]);
					last_engines[i] = engine;
				}
			}
		}
	}
}

static void VehicleNewName(byte *buf, int len)
{
	/* <04> <veh-type> <language-id> <num-veh> <offset> <data...>
	 *
	 * B veh-type      see action 0
	 * B language-id   language ID with bit 7 cleared (see below)
	 * B num-veh       number of vehicles which are getting a new name
	 * B offset        number of the first vehicle that gets a new name
	 * S data          new texts, each of them zero-terminated, after
	 *                 which the next name begins. */
	/* TODO: No support for changing non-vehicle text. Perhaps we shouldn't
	 * implement it at all, but it could be useful for some "modpacks"
	 * (completely new scenarios changing all graphics and logically also
	 * factory names etc). We should then also support all languages (by
	 * name), not only the original four ones. --pasky */

	if (len > 5) {
		uint8 feature = buf[1];
		uint8 lang = buf[2];
		uint8 id = buf[4] + _vehshifts[feature];
		uint8 endid = id + buf[3];

		if (lang & 0x80) {
			grfmsg(GMS_WARN, "VehicleNewName: No support for changing in-game texts. Skipping.");
			return;
		}

		if (!(lang & 3)) {
			/* XXX: If non-English name, silently skip it. */
			return;
		}

		buf += 5, len -= 5;
		for (; id < endid && len > 0; id++) {
			int ofs = strlen(buf) + 1;

			if (ofs < 128)
				SetCustomEngineName(id, buf);
			buf += ofs, len -= ofs;
		}
	}
}

static void GraphicsNew(byte *buf, int len)
{
	/* <05> <graphics-type> <num-sprites> <other data...>
	 *
	 * B graphics-type What set of graphics the sprites define.
	 * B num-sprites   How many sprites are in this set?
	 * V other data    Graphics type specific data.  Currently unused. */
	/* TODO */
}

static void CfgApply(byte *buf, int len)
{
	/* <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. */
	/* TODO */
}

static void SkipIf(byte *buf, int len)
{
	/* <07/09> <param-num> <param-size> <condition-type> <value> <num-sprites>
	 *
	 * B param-num
	 * B param-size
	 * B condition-type
	 * V value
	 * B num-sprites */
	/* TODO: We only support few tests. */
	/* TODO: More params. More condition types. */

	if (len > 5) {
		uint8 param = buf[1];
		uint8 paramsize = buf[2];
		uint8 condtype = buf[3];
		uint8 numsprites = buf[4 + paramsize];
		int val, result;

		if (param == 0x83) {
			val = _opt.landscape;
		} else {
			grfmsg(GMS_WARN, "Unsupported param %x. Ignoring test.", param);
			return;
		}

		switch (condtype) {
			case 2: result = (buf[4] == val);
				break;
			case 3: result = (buf[4] != val);
				break;
			default:
				grfmsg(GMS_WARN, "Unsupported test %d. Ignoring.", condtype);
				return;
		}

		if (!result)
			return;

		_skip_sprites = numsprites;
		if (_skip_sprites == 0) {
			/* Zero means there are no sprites to skip, so
			 * we use -1 to indicate that all further
			 * sprites should be skipped. */
			_skip_sprites = -1;
		}
	}
}

static void GRFInfo(byte *buf, int len)
{
	/* <08> <version> <grf-id> <name> <info>
	 *
	 * B version       newgrf version, currently 06
	 * 4*B grf-id      globally unique ID of this .grf file
	 * S name          name of this .grf set
	 * S info          string describing the set, and e.g. author and copyright */
	/* TODO: Check version. (We should have own versioning done somehow.) */

	if (len > 8) {
		uint8 version = buf[1];
		// this is de facto big endian - grf_load_dword() unsuitable
		uint32 grfid = buf[2] << 24 | buf[3] << 16 | buf[4] << 8 | buf[5];
		DEBUG(grf, 1) ("[%s] Loaded GRFv%d set %08lx - %s:\n%s\n", _cur_grffile, version, grfid, buf+6, buf+6+strlen(buf+6)+1);
	}
}

static void SpriteReplace(byte *buf, int len)
{
	/* <0A> <num-sets> <set1> [<set2> ...]
	 * <set>: <num-sprites> <first-sprite>
	 *
	 * B num-sets      How many sets of sprites to replace.
	 * Each set:
	 * B num-sprites   How many sprites are in this set
	 * W first-sprite  First sprite number to replace */
	/* TODO */
}

static void GRFError(byte *buf, int len)
{
	/* <0B> <severity> <language-id> <message-id> [<message...> 00] [<data...>] 00 [<parnum>]
	 *
	 * B severity      00: notice, contine loading grf file
	 *                 01: warning, continue loading grf file
	 *                 02: error, but continue loading grf file, and attempt
	 *                     loading grf again when loading or starting next game
	 *                 03: error, abort loading and prevent loading again in
	 *                     the future (only when restarting the patch)
	 * B language-id   see action 4, use 1F for built-in error messages
	 * B message-id    message to show, see below
	 * S message       for custom messages (message-id FF), text of the message
	 *                 not present for built-in messages.
	 * V data          additional data for built-in (or custom) messages
	 * B parnum        see action 6, only used with built-in message 03 */
	/* TODO: For now we just show the message, sometimes incomplete and never translated. */

	static const char * const msgstr[4] = {
		"Requires at least pseudo-TTDPatch version %s.",
		"This file is for %s version of TTD.",
		"Designed to be used with %s.",
		"Invalid parameter %s.",
	};

	if (len > 5) {
		uint8 severity = buf[1];
		uint8 msgid = buf[3];

		if (msgid == 0xff) {
			grfmsg(severity, "%s", buf+4);
		} else {
			grfmsg(severity, msgstr[msgid], buf+4);
		}
	}
}

static void GRFComment(byte *buf, int len)
{
	/* <0C> [<ignored...>]
	 *
	 * V ignored       Anything following the 0C is ignored */
}

static void ParamSet(byte *buf, int len)
{
	/* <0D> <target> <operation> <source1> <source2> [<data>]
	 *
	 * B target        parameter number where result is stored
	 * B operation     operation to perform, see below
	 * B source1       first source operand
	 * B source2       second source operand
	 * D data          data to use in the calculation, not necessary
	 *                 if both source1 and source2 refer to actual parameters
	 *
	 * Operations
	 * 00      Set parameter equal to source1
	 * 01      Addition, source1 + source2
	 * 02      Subtraction, source1 - source2
	 * 03      Unsigned multiplication, source1 * source2 (both unsigned)
	 * 04      Signed multiplication, source1 * source2 (both signed)
	 * 05      Unsigned bit shift, source1 by source2 (source2 taken to be a
	 *         signed quantity; left shift if positive and right shift if
	 *         negative, source1 is unsigned)
	 * 06      Signed bit shift, source1 by source2
	 *         (source2 like in 05, and source1 as well)
	 */
	/* TODO */
}

static void GRFInhibit(byte *buf, int len)
{
	/* <0E> <num> <grfids...>
	 *
	 * B num           Number of GRFIDs that follow
	 * D grfids        GRFIDs of the files to deactivate */
	/* TODO */
}

/* Here we perform initial decoding of some special sprites (as are they
 * described at http://www.ttdpatch.net/src/newgrf.txt, but this is only a very
 * partial implementation yet; also, we ignore the stages stuff). */
/* XXX: We consider GRF files trusted. It would be trivial to exploit OTTD by
 * a crafted invalid GRF file. We should tell that to the user somehow, or
 * better make this more robust in the future. */

void DecodeSpecialSprite(const char *filename, int num, int spriteid)
{
#define NUM_ACTIONS 0xf
	static const SpecialSpriteHandler handlers[NUM_ACTIONS] = {
		/* 0x0 */ VehicleChangeInfo,
		/* 0x1 */ SpriteNewSet,
		/* 0x2 */ SpriteNewSuperset,
		/* 0x3 */ VehicleMapSpriteSuperset,
		/* 0x4 */ VehicleNewName,
		/* 0x5 */ GraphicsNew,
		/* 0x6 */ CfgApply,
		/* 0x7 */ SkipIf,
		/* 0x8 */ GRFInfo,
		/* 0x9 */ SkipIf,
		/* 0xa */ SpriteReplace,
		/* 0xb */ GRFError,
		/* 0xc */ GRFComment,
		/* 0xd */ ParamSet,
		/* 0xe */ GRFInhibit,
	};
	byte action;
	byte *buf = malloc(num);
	int i;

	_cur_grffile = filename;
	_cur_spriteid = spriteid;

	for (i = 0; i != num; i++)
		buf[i] = FioReadByte();

	action = buf[0];
	if (action < NUM_ACTIONS) {
		handlers[action](buf, num);
	} else {
		grfmsg(GMS_WARN, "Unknown special sprite action %x, skipping.", action);
	}

	free(buf);
#undef NUM_ACTIONS
}