1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/adrift/scare.h"
24 #include "glk/adrift/scprotos.h"
25 #include "glk/jumps.h"
26 
27 namespace Glk {
28 namespace Adrift {
29 
30 /*
31  * Module notes:
32  *
33  * o Adds new "types" to jAsea's property descriptor: 'M' for multiline
34  *   strings, 'Z', 'F'/'T', and 'E' for defaulted integers, booleans, and
35  *   strings, 'i', 'b', and 's' for ignored integers, booleans, and strings,
36  *   and '{...}' and '|...|' for "special" descriptions and version fixups
37  *   that can't be described as things stand.
38  *
39  * o Adds new 'G' expression test, to check Global boolean.
40  *
41  * o The stack "adjustment" stuff is a bit of a bother.
42  */
43 
44 /* Assorted definitions and constants. */
45 static const sc_char NUL = '\0';
46 enum {
47 	PARSE_TEMP_LENGTH = 256,
48 	PARSE_MAX_DEPTH = 32
49 };
50 
51 /* Multiline separator sequences for the various versions supported. */
52 enum { SEPARATOR_SIZE = 3 };
53 static const sc_byte V400_SEPARATOR[SEPARATOR_SIZE] = {0xbd, 0xd0, 0x00};
54 static const sc_byte V390_SEPARATOR[SEPARATOR_SIZE] = {0x2a, 0x2a, 0x00};
55 static const sc_byte V380_SEPARATOR[SEPARATOR_SIZE] = {0x2a, 0x2a, 0x00};
56 
57 
58 /*
59  * Tables of properties descriptors.  These strings define the structure of
60  * a TAF file.  Field keys are:
61  *
62  * $,#,B,M   - string, integer, boolean, and multiline properties
63  * E,F,T,Z   - string, integer, and boolean properties not in the TAF;
64  *             set to "", FALSE, TRUE and zero on parsing (version < 4)
65  * i,s,b     - string, integer, and boolean in the TAF, but not stored
66  * [num]     - arrays of property, fixed size to num
67  * V         - variable sized array of property, size in input file
68  * W         - like V, but size - 1 in input file (version < 4)
69  * <class>   - class of property, separate parse target (recurse)
70  * ?[!]expr: - conditional property based on expr
71  * G[!]expr: - conditional property based on expr using globals
72  * |...|     - fixup specials for versions < 4
73  * {special} - because some things just defy description
74  */
75 struct sc_parse_schema_t {
76 	const sc_char *const class_name;
77 	const sc_char *const descriptor;
78 };
79 
80 /* Version 4.0 TAF file properties descriptor table. */
81 static const sc_parse_schema_t V400_PARSE_SCHEMA[] = {
82 	{
83 		"_GAME_",
84 		"<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
85 		" V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
86 		" V<VARIABLE>Variables V<ALR>ALRs BCustomFont ?BCustomFont:$FontNameSize"
87 		" $CompileDate"
88 	},
89 	{
90 		"HEADER",
91 		"MStartupText #StartRoom MWinText"
92 	},
93 	{
94 		"GLOBAL",
95 		"$GameName $GameAuthor $DontUnderstand #Perspective BShowExits #WaitTurns"
96 		" BDispFirstRoom BBattleSystem #MaxScore $PlayerName BPromptName $PlayerDesc"
97 		" #Task ?!#Task=0:$AltDesc #Position #ParentObject #PlayerGender"
98 		" #MaxSize #MaxWt ?GBattleSystem:<BATTLE>Battle BEightPointCompass bNoDebug"
99 		" BNoScoreNotify BNoMap bNoAutoComplete bNoControlPanel bNoMouse BSound"
100 		" BGraphics <RESOURCE>IntroRes <RESOURCE>WinRes BStatusBox $StatusBoxText"
101 		" iUnk1 iUnk2 BEmbedded"
102 	},
103 	{
104 		"BATTLE",
105 		"iStaminaLo iStaminaHi iStrengthLo iStrengthHi iAccuracyLo iAccuracyHi"
106 		" iDefenseLo iDefenseHi iAgilityLo iAgilityHi iRecovery"
107 	},
108 	{
109 		"ROOM",
110 		"$Short $Long ?GEightPointCompass:[12]<ROOM_EXIT>Exits"
111 		" ?!GEightPointCompass:[8]<ROOM_EXIT>Exits <RESOURCE>Res V<ROOM_ALT>Alts"
112 		" ?!GNoMap:bHideOnMap"
113 	},
114 	{
115 		"ROOM_EXIT",
116 		"{V400_ROOM_EXIT:#Dest_#Var1_#Var2_#Var3}"
117 	},
118 	{
119 		"ROOM_ALT",
120 		"$M1 #Type <RESOURCE>Res1 $M2 #Var2 <RESOURCE>Res2 #HideObjects $Changed"
121 		" #Var3 #DisplayRoom"
122 	},
123 	{
124 		"RESOURCE",
125 		"?GSound:$SoundFile,#SoundLen,ZSoundOffset"
126 		" ?GGraphics:$GraphicFile,#GraphicLen,ZGraphicOffset {V400_RESOURCE}"
127 	},
128 	{
129 		"OBJECT",
130 		"$Prefix $Short V$Alias BStatic $Description #InitialPosition #Task"
131 		" BTaskNotDone $AltDesc ?BStatic:<ROOM_LIST1>Where BContainer BSurface"
132 		" #Capacity ?!BStatic:BWearable,#SizeWeight,#Parent"
133 		" ?BStatic:{OBJECT:#Parent} #Openable ?#Openable=5:#Key ?#Openable=6:#Key"
134 		" ?#Openable=7:#Key #SitLie ?!BStatic:BEdible BReadable ?BReadable:$ReadText"
135 		" ?!BStatic:BWeapon #CurrentState ?!#CurrentState=0:$States,BStateListed"
136 		" BListFlag <RESOURCE>Res1 <RESOURCE>Res2 ?GBattleSystem:<OBJ_BATTLE>Battle"
137 		" $InRoomDesc #OnlyWhenNotMoved"
138 	},
139 	{
140 		"OBJ_BATTLE",
141 		"iProtectionValue iHitValue iMethod iAccuracy"
142 	},
143 	{
144 		"ROOM_LIST1",
145 		"#Type {ROOM_LIST1}"
146 	},
147 	{
148 		"TASK",
149 		"V$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
150 		" #ShowRoomDesc BRepeatable BReversible V$ReverseCommand <ROOM_LIST0>Where"
151 		" $Question ?$Question:$Hint1,$Hint2 V<TASK_RESTR>Restrictions"
152 		" V<TASK_ACTION>Actions $RestrMask <RESOURCE>Res"
153 	},
154 	{
155 		"TASK_RESTR",
156 		"#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2 ?#Type=2:#Var1,#Var2"
157 		" ?#Type=3:#Var1,#Var2,#Var3 ?#Type=4:#Var1,#Var2,#Var3,$Var4 $FailMessage"
158 	},
159 	{
160 		"TASK_ACTION",
161 		"#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2,#Var3"
162 		" ?#Type=2:#Var1,#Var2 ?#Type=3:#Var1,#Var2,#Var3,$Expr,#Var5"
163 		" ?#Type=4:#Var1 ?#Type=5:#Var1,#Var2 ?#Type=6:#Var1,#Var2,#Var3"
164 		" ?#Type=7:iVar1,iVar2,iVar3"
165 	},
166 	{
167 		"ROOM_LIST0",
168 		"#Type {ROOM_LIST0}"
169 	},
170 	{
171 		"EVENT",
172 		"$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
173 		" ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
174 		" $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
175 		" BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
176 		" #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
177 		" #TaskAffected [5]<RESOURCE>Res"
178 	},
179 	{
180 		"NPC",
181 		"$Name $Prefix V$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
182 		" V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
183 		" $InRoomText #Gender [4]<RESOURCE>Res ?GBattleSystem:<NPC_BATTLE>Battle"
184 	},
185 	{
186 		"NPC_BATTLE",
187 		"iAttitude iStaminaLo iStaminaHi iStrengthLo iStrengthHi iAccuracyLo"
188 		" iAccuracyHi iDefenseLo iDefenseHi iAgilityLo iAgilityHi iSpeed"
189 		" iKilledTask iRecovery iStaminaTask"
190 	},
191 	{
192 		"TOPIC",
193 		"$Subject $Reply #Task $AltReply"
194 	},
195 	{
196 		"WALK",
197 		"#NumStops BLoop #StartTask #CharTask #MeetObject #ObjectTask #StoppingTask"
198 		" #MeetChar $ChangedDesc {WALK:#Rooms_#Times}"
199 	},
200 	{
201 		"ROOM_GROUP",
202 		"$Name {ROOM_GROUP:[]BList}"
203 	},
204 	{
205 		"SYNONYM",
206 		"$Replacement $Original"
207 	},
208 	{
209 		"VARIABLE",
210 		"$Name #Type $Value"
211 	},
212 	{
213 		"ALR",
214 		"$Original $Replacement"
215 	},
216 	{NULL, NULL}
217 };
218 
219 /* Version 3.9 TAF file properties descriptor table. */
220 static const sc_parse_schema_t V390_PARSE_SCHEMA[] = {
221 	{
222 		"_GAME_",
223 		"<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
224 		" V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
225 		" V<VARIABLE>Variables V<ALR>ALRs BCustomFont ?BCustomFont:$FontNameSize"
226 		" $CompileDate sPassword"
227 	},
228 	{
229 		"HEADER",
230 		"MStartupText #StartRoom MWinText"
231 	},
232 	{
233 		"GLOBAL",
234 		"$GameName $GameAuthor $DontUnderstand #Perspective BShowExits #WaitTurns"
235 		" BDispFirstRoom BBattleSystem #MaxScore $PlayerName BPromptName $PlayerDesc"
236 		" #Task ?!#Task=0:$AltDesc #Position #ParentObject #PlayerGender"
237 		" #MaxSize #MaxWt ?GBattleSystem:<BATTLE>Battle BEightPointCompass bNoDebug"
238 		" BNoScoreNotify BNoMap bNoAutoComplete bNoControlPanel bNoMouse"
239 		" BSound BGraphics <RESOURCE>IntroRes <RESOURCE>WinRes FStatusBox"
240 		" EStatusBoxText iUnk1 iUnk2 FEmbedded"
241 	},
242 	{
243 		"BATTLE",
244 		"iStamina iStrength iDefense"
245 	},
246 	{
247 		"ROOM",
248 		"$Short $Long $LastDesc ?GEightPointCompass:[12]<ROOM_EXIT>Exits"
249 		" ?!GEightPointCompass:[8]<ROOM_EXIT>Exits $AddDesc1 #Task1 $AddDesc2 #Task2"
250 		" #Obj $AltDesc #TypeHideObjects <RESOURCE>Res <RESOURCE>LastRes"
251 		" <RESOURCE>Task1Res <RESOURCE>Task2Res <RESOURCE>AltRes"
252 		" ?!GNoMap:bHideOnMap |V390_ROOM:_Alts_|"
253 	},
254 	{
255 		"ROOM_EXIT",
256 		"{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}"
257 	},
258 	{
259 		"RESOURCE",
260 		"?GSound:$SoundFile,ZSoundLen,ZSoundOffset"
261 		" ?GGraphics:$GraphicFile,ZGraphicLen,ZGraphicOffset"
262 	},
263 	{
264 		"OBJECT",
265 		"$Prefix $Short"
266 		" [1]$Alias BStatic $Description #InitialPosition #Task BTaskNotDone"
267 		" $AltDesc ?BStatic:<ROOM_LIST1>Where BContainer BSurface #Capacity"
268 		" ?!BStatic:BWearable,#SizeWeight,#Parent ?BStatic:{OBJECT:#Parent}"
269 		" #Openable |V390_OBJECT:_Openable_,Key| #SitLie ?!BStatic:BEdible BReadable"
270 		" ?BReadable:$ReadText ?!BStatic:BWeapon ZCurrentState FListFlag"
271 		" <RESOURCE>Res1 <RESOURCE>Res2 ?GBattleSystem:<OBJ_BATTLE>Battle"
272 		" EInRoomDesc ZOnlyWhenNotMoved"
273 	},
274 	{
275 		"OBJ_BATTLE",
276 		"iProtectionValue iHitValue iMethod"
277 	},
278 	{
279 		"ROOM_LIST1",
280 		"#Type {ROOM_LIST1}"
281 	},
282 	{
283 		"TASK",
284 		"W$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
285 		" #ShowRoomDesc BRepeatable BReversible W$ReverseCommand <ROOM_LIST0>Where"
286 		" $Question ?$Question:$Hint1,$Hint2 V<TASK_RESTR>Restrictions"
287 		" V<TASK_ACTION>Actions |V390_TASK:$RestrMask| <RESOURCE>Res"
288 	},
289 	{
290 		"TASK_RESTR",
291 		"#Type ?#Type=0:#Var1,#Var2,#Var3 ?#Type=1:#Var1,#Var2 ?#Type=2:#Var1,#Var2"
292 		" ?#Type=3:#Var1,#Var2,#Var3 ?#Type=4:#Var1,#Var2,#Var3,EVar4"
293 		",|V390_TASK_RESTR:Var1>0?#Var1++| $FailMessage"
294 	},
295 	{
296 		"TASK_ACTION",
297 		"#Type |V390_TASK_ACTION:Type>4?#Type++| ?#Type=0:#Var1,#Var2,#Var3"
298 		" ?#Type=1:#Var1,#Var2,#Var3 ?#Type=2:#Var1,#Var2"
299 		" ?#Type=3:#Var1,#Var2,#Var3,|V390_TASK_ACTION:$Expr_#Var5|"
300 		" ?#Type=4:#Var1 ?#Type=6:#Var1,ZVar2,ZVar3 ?#Type=7:iVar1,iVar2,iVar3"
301 	},
302 	{
303 		"ROOM_LIST0",
304 		"#Type {ROOM_LIST0}"
305 	},
306 	{
307 		"EVENT",
308 		"$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
309 		" ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
310 		" $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
311 		" BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
312 		" #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
313 		" #TaskAffected [5]<RESOURCE>Res"
314 	},
315 	{
316 		"NPC",
317 		"$Name $Prefix [1]$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
318 		" V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
319 		" $InRoomText #Gender [4]<RESOURCE>Res ?GBattleSystem:<NPC_BATTLE>Battle"
320 	},
321 	{
322 		"NPC_BATTLE",
323 		"iAttitude iStamina iStrength iDefense iSpeed iKilledTask"
324 	},
325 	{
326 		"TOPIC",
327 		"$Subject $Reply #Task $AltReply"
328 	},
329 	{
330 		"WALK",
331 		"#NumStops BLoop #StartTask #CharTask #MeetObject #ObjectTask #StoppingTask"
332 		" ZMeetChar $ChangedDesc {WALK:#Rooms_#Times}"
333 	},
334 	{
335 		"ROOM_GROUP",
336 		"$Name {ROOM_GROUP:[]BList}"
337 	},
338 	{
339 		"SYNONYM",
340 		"$Replacement $Original"
341 	},
342 	{
343 		"VARIABLE",
344 		"$Name ZType $Value"
345 	},
346 	{
347 		"ALR",
348 		"$Original $Replacement"
349 	},
350 	{NULL, NULL}
351 };
352 
353 /* Version 3.8 TAF file properties descriptor table. */
354 static const sc_parse_schema_t V380_PARSE_SCHEMA[] = {
355 	{
356 		"_GAME_",
357 		"<HEADER>Header <GLOBAL>Globals V<ROOM>Rooms V<OBJECT>Objects V<TASK>Tasks"
358 		" V<EVENT>Events V<NPC>NPCs V<ROOM_GROUP>RoomGroups V<SYNONYM>Synonyms"
359 		" FCustomFont $CompileDate sPassword |V380_GLOBAL:_MaxScore_|"
360 		" |V380_OBJECT:_InitialPositions_|"
361 	},
362 	{
363 		"HEADER",
364 		"MStartupText #StartRoom MWinText"
365 	},
366 	{
367 		"GLOBAL",
368 		"$GameName $GameAuthor #MaxCarried |V380_MaxSize_MaxWt_| $DontUnderstand"
369 		" #Perspective BShowExits #WaitTurns FDispFirstRoom FBattleSystem"
370 		" EPlayerName FPromptName EPlayerDesc ZTask ZPosition ZParentObject"
371 		" ZPlayerGender FEightPointCompass TNoScoreNotify FSound FGraphics"
372 		" FStatusBox EStatusBoxText FEmbedded"
373 	},
374 	{
375 		"ROOM",
376 		"$Short $Long $LastDesc [8]<ROOM_EXIT>Exits $AddDesc1 #Task1 $AddDesc2"
377 		" #Task2 #Obj $AltDesc #TypeHideObjects |V380_ROOM:_Alts_|"
378 	},
379 	{
380 		"ROOM_EXIT",
381 		"{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}"
382 	},
383 	{
384 		"OBJECT",
385 		"$Prefix $Short [1]$Alias BStatic $Description #InitialPosition #Task"
386 		" BTaskNotDone $AltDesc ?BStatic:<ROOM_LIST1>Where #SurfaceContainer"
387 		" FSurface ?#SurfaceContainer=2:TSurface FContainer"
388 		" ?#SurfaceContainer=1:TContainer #Capacity |V380_OBJECT:#Capacity*10+2|"
389 		" ?!BStatic:BWearable,#SizeWeight,#Parent ?BStatic:{OBJECT:#Parent}"
390 		" #Openable |V380_OBJECT:_Openable_,Key| #SitLie ?!BStatic:BEdible BReadable"
391 		" ?BReadable:$ReadText ?!BStatic:BWeapon ZCurrentState FListFlag"
392 		" EInRoomDesc ZOnlyWhenNotMoved"
393 	},
394 	{
395 		"ROOM_LIST1",
396 		"#Type {ROOM_LIST1}"
397 	},
398 	{
399 		"TASK",
400 		"W$Command $CompleteText $ReverseMessage $RepeatText $AdditionalMessage"
401 		" #ShowRoomDesc BRepeatable #Score BSingleScore [6]<TASK_MOVE>Movements"
402 		" BReversible W$ReverseCommand #WearObj1 #WearObj2 #HoldObj1 #HoldObj2"
403 		" #HoldObj3 #Obj1 #Task BTaskNotDone $TaskMsg $HoldMsg $WearMsg $CompanyMsg"
404 		" BNotInSameRoom #NPC $Obj1Msg #Obj1Room <ROOM_LIST0>Where BKillsPlayer"
405 		" BHoldingSameRoom $Question ?$Question:$Hint1,$Hint2 #Obj2"
406 		" ?!#Obj2=0:#Obj2Var1,#Obj2Var2,$Obj2Msg BWinGame |V380_TASK:_Actions_|"
407 		" |V380_TASK:_Restrictions_|"
408 	},
409 	{
410 		"TASK_MOVE",
411 		"#Var1 #Var2 #Var3"
412 	},
413 	{
414 		"ROOM_LIST0",
415 		"#Type {ROOM_LIST0}"
416 	},
417 	{
418 		"EVENT",
419 		"$Short #StarterType ?#StarterType=2:#StartTime,#EndTime"
420 		" ?#StarterType=3:#TaskNum #RestartType BTaskFinished #Time1 #Time2"
421 		" $StartText $LookText $FinishText <ROOM_LIST0>Where #PauseTask"
422 		" BPauserCompleted #PrefTime1 $PrefText1 #ResumeTask BResumerCompleted"
423 		" #PrefTime2 $PrefText2 #Obj2 #Obj2Dest #Obj3 #Obj3Dest #Obj1 #Obj1Dest"
424 		" #TaskAffected"
425 	},
426 	{
427 		"NPC",
428 		"$Name $Prefix [1]$Alias $Descr #StartRoom $AltText #Task V<TOPIC>Topics"
429 		" V<WALK>Walks BShowEnterExit ?BShowEnterExit:$EnterText,$ExitText"
430 		" $InRoomText ZGender"
431 	},
432 	{
433 		"TOPIC",
434 		"$Subject $Reply #Task $AltReply"
435 	},
436 	{
437 		"WALK",
438 		"#NumStops BLoop #StartTask #CharTask #MeetObject"
439 		" ?!#MeetObject=0:|V380_WALK:_MeetObject_| #ObjectTask ZMeetChar"
440 		" {WALK:#Rooms_#Times} ZStoppingTask EChangedDesc"
441 	},
442 	{
443 		"ROOM_GROUP",
444 		"$Name {ROOM_GROUP:[]BList}"
445 	},
446 	{
447 		"SYNONYM",
448 		"$Replacement $Original"
449 	},
450 	{NULL, NULL}
451 };
452 
453 
454 /*
455  * parse_select_schema()
456  *
457  * Select one of the parse schemata based on a TAF file.
458  */
parse_select_schema(sc_tafref_t taf)459 static const sc_parse_schema_t *parse_select_schema(sc_tafref_t taf) {
460 	/* Switch based on the TAF file version. */
461 	switch (taf_get_version(taf)) {
462 	case TAF_VERSION_400:
463 		return V400_PARSE_SCHEMA;
464 	case TAF_VERSION_390:
465 		return V390_PARSE_SCHEMA;
466 	case TAF_VERSION_380:
467 		return V380_PARSE_SCHEMA;
468 	default:
469 		sc_fatal("parse_select_schema: invalid TAF file version\n");
470 		return NULL;
471 	}
472 }
473 
474 
475 /* The uncompressed TAF file from which we get all our data. */
476 static sc_tafref_t parse_taf = NULL;
477 static sc_int parse_tafline = 0;
478 
479 /* The parse schema selected for this TAF file. */
480 static sc_parse_schema_t const *parse_schema = NULL;
481 
482 /* Properties bundle and trace flag, set before parsing. */
483 static sc_prop_setref_t parse_bundle = NULL;
484 static sc_bool parse_trace = FALSE;
485 
486 /*
487  * Stack of property keys.  The stack is filled by parsing, and written
488  * to the property store on parse terminals.
489  */
490 static sc_vartype_t parse_vt_key[PARSE_MAX_DEPTH];
491 static sc_char parse_format[PARSE_MAX_DEPTH];
492 static sc_int parse_depth = 0;
493 
494 
495 /*
496  * parse_push_key()
497  * parse_pop_key()
498  *
499  * Push a key of the given type onto the property key stack, and pop a key
500  * off on unwind.
501  */
parse_push_key(sc_vartype_t vt_key,sc_char type)502 static void parse_push_key(sc_vartype_t vt_key, sc_char type) {
503 	if (parse_depth == PARSE_MAX_DEPTH)
504 		sc_fatal("parse_push_key: stack overrun\n");
505 
506 	/* Push the key, and its associated type. */
507 	parse_vt_key[parse_depth] = vt_key;
508 	parse_format[parse_depth] = type;
509 	parse_depth++;
510 }
511 
parse_pop_key(void)512 static void parse_pop_key(void) {
513 	/* Check the stack has something to pop, then pop it. */
514 	if (parse_depth == 0)
515 		sc_fatal("parse_pop_key: stack underrun\n");
516 	parse_depth--;
517 }
518 
519 
520 /*
521  * parse_retrieve_stack()
522  *
523  * This is ugly.  The parse produces indexes before the things that they
524  * index.  An expedient fix is to switch i-s keys before storing a property
525  * value
526  */
parse_retrieve_stack(sc_char format[],sc_vartype_t vt_key[],sc_int * depth)527 static void parse_retrieve_stack(sc_char format[], sc_vartype_t vt_key[], sc_int *depth) {
528 	sc_int index_;
529 
530 	/* Switch index-string key pairs. */
531 	for (index_ = 0; index_ < parse_depth; index_++) {
532 		if (index_ < parse_depth - 1
533 		        && parse_format[index_] == PROP_KEY_INTEGER
534 		        && parse_format[index_ + 1] == PROP_KEY_STRING) {
535 			/* Swap format and key elements. */
536 			format[index_] = parse_format[index_ + 1];
537 			format[index_ + 1] = parse_format[index_];
538 			vt_key[index_] = parse_vt_key[index_ + 1];
539 			vt_key[index_ + 1] = parse_vt_key[index_];
540 
541 			index_++;
542 		} else {
543 			/* Simple copy of format and key elements. */
544 			format[index_] = parse_format[index_];
545 			vt_key[index_] = parse_vt_key[index_];
546 		}
547 	}
548 
549 	/* Return the parse depth. */
550 	*depth = parse_depth;
551 }
552 
553 
554 /*
555  * parse_stack_backtrace()
556  *
557  * Dump the parse stack.  Used for diagnostics on finding what we think may
558  * be a bad game.
559  */
parse_stack_backtrace(void)560 static void parse_stack_backtrace(void) {
561 	sc_vartype_t vt_key[PARSE_MAX_DEPTH];
562 	sc_char format[PARSE_MAX_DEPTH];
563 	sc_int depth, index_;
564 
565 	parse_retrieve_stack(format, vt_key, &depth);
566 
567 	sc_error("parse_stack_backtrace: version %s schema parsed to depth %ld\n",
568 	         (parse_schema == V400_PARSE_SCHEMA) ? "4.00" :
569 	         (parse_schema == V390_PARSE_SCHEMA) ? "3.90" :
570 	         (parse_schema == V380_PARSE_SCHEMA) ? "3.80" : "[Invalid]",
571 	         depth);
572 
573 	sc_error("parse_stack_backtrace: parse stack backtrace follows...\n");
574 	for (index_ = 0; index_ < depth; index_++) {
575 		sc_char type;
576 
577 		type = format[index_];
578 		if (type == PROP_KEY_INTEGER)
579 			sc_error("%2ld - [%c] %ld\n", index_, type, vt_key[index_].integer);
580 		else if (type == PROP_KEY_STRING)
581 			sc_error("%2ld - [%c] \"%s\"\n", index_, type, vt_key[index_].string);
582 		else
583 			sc_error("%2ld - [%c] %p\n", index_, type, vt_key[index_].voidp);
584 	}
585 }
586 
587 
588 /*
589  * parse_put_property()
590  * parse_get_property()
591  *
592  * Write or read a property based on the keys amassed so far.
593  */
parse_put_property(sc_vartype_t vt_value,sc_char type)594 static void parse_put_property(sc_vartype_t vt_value, sc_char type) {
595 	sc_vartype_t vt_key[PARSE_MAX_DEPTH];
596 	sc_char format[PARSE_MAX_DEPTH + 4];
597 	sc_int depth;
598 
599 	/* Retrieve the adjusted stack. */
600 	parse_retrieve_stack(format + 3, vt_key, &depth);
601 
602 	/* Complete the format for the property put. */
603 	format[0] = type;
604 	format[1] = '-';
605 	format[2] = '>';
606 	format[depth + 3] = NUL;
607 
608 	/* Store the property under the stacked keys. */
609 	assert(parse_bundle);
610 	prop_put(parse_bundle, format, vt_value, vt_key);
611 }
612 
parse_get_property(sc_vartype_t * vt_rvalue,sc_char type)613 static sc_bool parse_get_property(sc_vartype_t *vt_rvalue, sc_char type) {
614 	sc_vartype_t vt_key[PARSE_MAX_DEPTH];
615 	sc_char format[PARSE_MAX_DEPTH + 4];
616 	sc_int depth;
617 	sc_bool status;
618 
619 	/* Retrieve the adjusted stack. */
620 	parse_retrieve_stack(format + 3, vt_key, &depth);
621 
622 	/* Complete the format for the property put. */
623 	format[0] = type;
624 	format[1] = '<';
625 	format[2] = '-';
626 	format[depth + 3] = NUL;
627 
628 	/* Retrieve the property using the stacked keys. */
629 	assert(parse_bundle);
630 	status = prop_get(parse_bundle, format, vt_rvalue, vt_key);
631 
632 	return status;
633 }
634 
635 
636 /*
637  * parse_get_child_count()
638  *
639  * Convenience form of parse_get_property(), retrieve an integer property
640  * indicating the child count of the effectively stacked node, or zero if
641  * no such node exists.
642  */
parse_get_child_count(void)643 static sc_int parse_get_child_count(void) {
644 	sc_vartype_t vt_rvalue;
645 
646 	if (!parse_get_property(&vt_rvalue, PROP_INTEGER))
647 		vt_rvalue.integer = 0;
648 
649 	return vt_rvalue.integer;
650 }
651 
652 
653 /*
654  * parse_get_integer_property()
655  * parse_get_boolean_property()
656  * parse_get_string_property()
657  *
658  * Convenience forms of parse_get_property(), retrieve directly, and report
659  * a fatal error if the property does not exist.
660  */
parse_get_integer_property(void)661 static sc_int parse_get_integer_property(void) {
662 	sc_vartype_t vt_rvalue;
663 
664 	if (!parse_get_property(&vt_rvalue, PROP_INTEGER))
665 		sc_fatal("parse_get_integer_property: missing property\n");
666 
667 	return vt_rvalue.integer;
668 }
669 
parse_get_boolean_property(void)670 static sc_bool parse_get_boolean_property(void) {
671 	sc_vartype_t vt_rvalue;
672 
673 	if (!parse_get_property(&vt_rvalue, PROP_BOOLEAN))
674 		sc_fatal("parse_get_boolean_property: missing property\n");
675 
676 	return vt_rvalue.boolean;
677 }
678 
parse_get_string_property(void)679 static const sc_char *parse_get_string_property(void) {
680 	sc_vartype_t vt_rvalue;
681 
682 	if (!parse_get_property(&vt_rvalue, PROP_STRING))
683 		sc_fatal("parse_get_string_property: missing property\n");
684 
685 	return vt_rvalue.string;
686 }
687 
688 
689 /* Pushback line, and pushback requested flag. */
690 static const sc_char *parse_pushback_line = NULL;
691 static sc_bool parse_use_pushback = FALSE;
692 
693 /*
694  * parse_get_taf_string()
695  * parse_get_taf_integer()
696  * parse_get_taf_boolean()
697  * parse_taf_pushback()
698  *
699  * Wrapper round obtaining the next TAF file line, with variants to convert
700  * the line content into an integer or boolean, and a function for effective
701  * TAF line pushback.
702  */
parse_get_taf_string(CONTEXT)703 static const sc_char *parse_get_taf_string(CONTEXT) {
704 	const sc_char *line;
705 
706 	/* If pushback requested, use that instead of reading. */
707 	if (parse_use_pushback) {
708 		/* Use the pushback line, and clear the request. */
709 		assert(parse_pushback_line);
710 		line = parse_pushback_line;
711 		parse_use_pushback = FALSE;
712 	} else {
713 		/* Get the next line, and complain if absent. */
714 		line = taf_next_line(parse_taf);
715 		if (!line) {
716 			sc_error("parse_get_taf_string:"
717 			         " out of TAF data at line %ld\n", parse_tafline);
718 			parse_stack_backtrace();
719 			LONG_JUMP0;
720 		}
721 
722 		/* Note this line for possible pushback. */
723 		parse_pushback_line = line;
724 	}
725 
726 	/* Print out the line we're parsing if tracing. */
727 	if (parse_trace)
728 		sc_trace("Parse: read in line %ld : %s\n", parse_tafline, line);
729 
730 	parse_tafline++;
731 	return line;
732 }
733 
parse_get_taf_integer(CONTEXT)734 static sc_int parse_get_taf_integer(CONTEXT) {
735 	const sc_char *line;
736 	sc_int integer;
737 
738 	/* Get line, and scan for a single integer; return it. */
739 	R0FUNC0(parse_get_taf_string, line);
740 	if (sscanf(line, "%ld", &integer) != 1) {
741 		sc_error("parse_get_taf_integer:"
742 		         " invalid integer at line %ld\n", parse_tafline - 1);
743 		parse_stack_backtrace();
744 		LONG_JUMP0;
745 	}
746 
747 	return integer;
748 }
749 
parse_get_taf_boolean(CONTEXT)750 static sc_bool parse_get_taf_boolean(CONTEXT) {
751 	const sc_char *line;
752 	sc_uint boolean;
753 
754 	/*
755 	 * Get line, and scan for a single integer; check it's a valid-looking flag,
756 	 * and return it.
757 	 */
758 	R0FUNC0(parse_get_taf_string, line);
759 	if (sscanf(line, "%lu", &boolean) != 1) {
760 		sc_error("parse_get_taf_boolean:"
761 		         " invalid boolean at line %ld\n", parse_tafline - 1);
762 		parse_stack_backtrace();
763 		LONG_JUMP0;
764 	}
765 	if (boolean != 0 && boolean != 1) {
766 		sc_error("parse_get_taf_boolean:"
767 		         " warning: suspect boolean at line %ld\n", parse_tafline - 1);
768 	}
769 
770 	return boolean != 0;
771 }
772 
parse_taf_pushback(void)773 static void parse_taf_pushback(void) {
774 	if (parse_use_pushback || !parse_pushback_line)
775 		sc_fatal("parse_taf_pushback: too much pushback requested\n");
776 
777 	/* Set pushback request, and decrement line counter. */
778 	parse_use_pushback = TRUE;
779 	parse_tafline--;
780 
781 	/* Note pushback for tracing purposes. */
782 	if (parse_trace)
783 		sc_trace("Parse: push back at line %ld\n", parse_tafline);
784 }
785 
786 
787 /* Enumerations of parse types found in the parse schema. */
788 enum {
789 	PARSE_INTEGER = '#',
790 	PARSE_DEFAULT_ZERO = 'Z',
791 	PARSE_BOOLEAN = 'B',
792 	PARSE_DEFAULT_FALSE = 'F',
793 	PARSE_DEFAULT_TRUE = 'T',
794 	PARSE_STRING = '$',
795 	PARSE_DEFAULT_EMPTY = 'E',
796 	PARSE_MULTILINE = 'M',
797 	PARSE_VECTOR = 'V',
798 	PARSE_VECTOR_ALTERNATE = 'W',
799 	PARSE_ARRAY = '[',
800 	PARSE_EXPRESSION = '?',
801 	PARSE_EXPRESSION_NOT = '!',
802 	PARSE_GLOBAL_EXPRESSION = 'G',
803 	PARSE_CLASS = '<',
804 	PARSE_FIXUP = '|',
805 	PARSE_SPECIAL = '{',
806 	PARSE_IGNORE_INTEGER = 'i',
807 	PARSE_IGNORE_BOOLEAN = 'b',
808 	PARSE_IGNORE_STRING = 's'
809 };
810 
811 /* Forward declarations of parse functions for recursion. */
812 static void parse_element(CONTEXT, const sc_char *element);
813 static void parse_class(CONTEXT, const sc_char *class_);
814 static void parse_descriptor(CONTEXT, const sc_char *descriptor);
815 
816 /*
817  * parse_array()
818  *
819  * Parse a descriptor [] array.
820  */
parse_array(CONTEXT,const sc_char * array)821 static void parse_array(CONTEXT, const sc_char *array) {
822 	sc_int count, index_;
823 	sc_char element[PARSE_TEMP_LENGTH];
824 
825 	if (parse_trace)
826 		sc_trace("Parse: entering array %s\n", array);
827 
828 	/* Find the count of elements in the array, and the element itself. */
829 	if (sscanf(array, "[%ld]%[^ ]", &count, element) != 2)
830 		sc_fatal("parse_array: bad array, %s\n", array);
831 
832 	/* Parse the element for array count iterations, each a key. */
833 	for (index_ = 0; index_ < count; index_++) {
834 		sc_vartype_t vt_key;
835 
836 		vt_key.integer = index_;
837 		parse_push_key(vt_key, PROP_KEY_INTEGER);
838 
839 		CALL1(parse_element, element);
840 
841 		parse_pop_key();
842 	}
843 
844 	if (parse_trace)
845 		sc_trace("Parse: leaving array %s\n", array);
846 }
847 
848 
849 /*
850  * parse_vector_common()
851  * parse_vector()
852  * parse_vector_alternate()
853  *
854  * Parse a variable-length vector of properties.
855  */
parse_vector_common(CONTEXT,const sc_char * vector,sc_int count)856 static void parse_vector_common(CONTEXT, const sc_char *vector, sc_int count) {
857 	sc_int index_;
858 
859 	/* Parse the vector property count times, pushing a key on each. */
860 	for (index_ = 0; index_ < count; index_++) {
861 		sc_vartype_t vt_key;
862 
863 		vt_key.integer = index_;
864 		parse_push_key(vt_key, PROP_KEY_INTEGER);
865 
866 		CALL1(parse_element, vector + 1);
867 
868 		parse_pop_key();
869 	}
870 }
871 
parse_vector(CONTEXT,const sc_char * vector)872 static void parse_vector(CONTEXT, const sc_char *vector) {
873 	sc_int count;
874 
875 	if (parse_trace)
876 		sc_trace("Parse: entering vector %s\n", vector);
877 
878 	/* Find the count of elements in the vector, and parse. */
879 	FUNC0(parse_get_taf_integer, count);
880 	CALL2(parse_vector_common, vector, count);
881 
882 	if (parse_trace)
883 		sc_trace("Parse: leaving vector %s\n", vector);
884 }
885 
parse_vector_alternate(CONTEXT,const sc_char * vector)886 static void parse_vector_alternate(CONTEXT, const sc_char *vector) {
887 	sc_int count1;
888 
889 	if (parse_trace)
890 		sc_trace("Parse: entering alternate vector %s\n", vector);
891 
892 	/* Element count, this is a vector described by size - 1. */
893 	FUNC0(parse_get_taf_integer, count1);
894 	CALL2(parse_vector_common, vector, count1 + 1);
895 
896 	if (parse_trace)
897 		sc_trace("Parse: leaving alternate vector %s\n", vector);
898 }
899 
900 
901 /*
902  * parse_test_expression()
903  * parse_expression()
904  *
905  * Parse a conditional field definition, with runtime test.
906  */
parse_test_expression(const sc_char * test_expression)907 static sc_bool parse_test_expression(const sc_char *test_expression) {
908 	sc_vartype_t vt_key;
909 	sc_char plhs[PARSE_TEMP_LENGTH];
910 	sc_int rhs;
911 	sc_bool retval = FALSE;
912 
913 	/* Identify the type of expression to evaluate. */
914 	switch (test_expression[0]) {
915 	case PARSE_BOOLEAN:
916 		/* Read boolean property and return its value. */
917 		vt_key.string = test_expression + 1;
918 		parse_push_key(vt_key, PROP_KEY_STRING);
919 		retval = parse_get_boolean_property();
920 		parse_pop_key();
921 		break;
922 
923 	case PARSE_INTEGER:
924 		/* Get the left and right sides of = comparison. */
925 		if (sscanf(test_expression, "#%[^=]=%ld", plhs, &rhs) != 2) {
926 			sc_fatal("parse_test_expression: bad = compare, %s\n",
927 			         test_expression + 1);
928 		}
929 
930 		/* Read integer property and return comparison. */
931 		vt_key.string = plhs;
932 		parse_push_key(vt_key, PROP_KEY_STRING);
933 		retval = (parse_get_integer_property() == rhs);
934 		parse_pop_key();
935 		break;
936 
937 	case PARSE_STRING:
938 		/* Read property and return TRUE if not an empty string. */
939 		vt_key.string = test_expression + 1;
940 		parse_push_key(vt_key, PROP_KEY_STRING);
941 		retval = !sc_strempty(parse_get_string_property());
942 		parse_pop_key();
943 		break;
944 
945 	case PARSE_GLOBAL_EXPRESSION: {
946 		sc_vartype_t vt_gkey[2];
947 
948 		/* Read the given Global boolean property and return it. */
949 		vt_gkey[0].string = "Globals";
950 		vt_gkey[1].string = test_expression + 1;
951 		retval = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
952 		break;
953 	}
954 
955 	default:
956 		sc_fatal("parse_test_expression:"
957 		         " bad expression, %s\n", test_expression + 1);
958 	}
959 
960 	if (parse_trace)
961 		sc_trace("Parse: expression is %s\n", retval ? "true" : "false");
962 
963 	return retval;
964 }
965 
parse_expression(CONTEXT,const sc_char * expression)966 static void parse_expression(CONTEXT, const sc_char *expression) {
967 	sc_char test_expression[PARSE_TEMP_LENGTH];
968 	sc_bool is_present;
969 
970 	if (parse_trace)
971 		sc_trace("Parse: entering expression %s\n", expression);
972 
973 	/* Isolate the test part of the expression. */
974 	if (sscanf(expression, "?%[^:]", test_expression) != 1)
975 		sc_fatal("parse_expression: bad expression, %s\n", expression);
976 
977 	/* Handle the remainder of the expression only if test passes. */
978 	is_present = (test_expression[0] == PARSE_EXPRESSION_NOT)
979 	             ? !parse_test_expression(test_expression + 1)
980 	             : parse_test_expression(test_expression);
981 	if (is_present) {
982 		sc_int next;
983 
984 		/*
985 		 * Following the ':' may be a single element, or a comma-separated list.
986 		 */
987 		for (next = strlen(test_expression) + 2; expression[next] != NUL;) {
988 			sc_char element[PARSE_TEMP_LENGTH];
989 
990 			/* Get the next individual element to parse. */
991 			if (sscanf(expression + next, "%[^,]", element) != 1)
992 				sc_fatal("parse_expression: bad list, %s\n", expression + next);
993 
994 			/* Parse this isolated element. */
995 			CALL1(parse_element, element);
996 
997 			/* Advance to the start of the next element. */
998 			next += strlen(element);
999 			next += strspn(expression + next, ",");
1000 		}
1001 	}
1002 
1003 	if (parse_trace)
1004 		sc_trace("Parse: leaving expression %s\n", expression);
1005 }
1006 
1007 
1008 /*
1009  * parse_read_multiline()
1010  *
1011  * Helper for parse_terminal(), reads in a multiline string.  The return
1012  * string is malloc'ed, and the caller needs to handle that.
1013  */
parse_read_multiline(CONTEXT)1014 static sc_char *parse_read_multiline(CONTEXT) {
1015 	const sc_byte *separator = NULL;
1016 	const sc_char *line;
1017 	sc_char *multiline;
1018 
1019 	/* Select the appropriate multiline separator. */
1020 	switch (taf_get_version(parse_taf)) {
1021 	case TAF_VERSION_400:
1022 		separator = V400_SEPARATOR;
1023 		break;
1024 	case TAF_VERSION_390:
1025 		separator = V390_SEPARATOR;
1026 		break;
1027 	case TAF_VERSION_380:
1028 		separator = V380_SEPARATOR;
1029 		break;
1030 	default:
1031 		sc_fatal("parse_read_multiline: invalid TAF file version\n");
1032 		break;
1033 	}
1034 
1035 	/* Take a simple copy of the first line. */
1036 	R0FUNC0(parse_get_taf_string, line);
1037 	multiline = (sc_char *)sc_malloc(strlen(line) + 1);
1038 	strcpy(multiline, line);
1039 
1040 	/* Now concatenate until separator found. */
1041 	R0FUNC0(parse_get_taf_string, line);
1042 	while (memcmp(line, separator, SEPARATOR_SIZE) != 0) {
1043 		multiline = (sc_char *)sc_realloc(multiline,
1044 		                                  strlen(multiline) + strlen(line) + 2);
1045 		strcat(multiline, "\n");
1046 		strcat(multiline, line);
1047 		R0FUNC0(parse_get_taf_string, line);
1048 	}
1049 
1050 	return multiline;
1051 }
1052 
1053 
1054 /*
1055  * parse_terminal()
1056  *
1057  * Common handler for string, integer, boolean, and multiline parse terminals.
1058  */
parse_terminal(CONTEXT,const sc_char * terminal)1059 static void parse_terminal(CONTEXT, const sc_char *terminal) {
1060 	sc_vartype_t vt_key, vt_value;
1061 
1062 	if (parse_trace)
1063 		sc_trace("Parse: entering terminal %s\n", terminal);
1064 
1065 	/* Push the key string. */
1066 	vt_key.string = terminal + 1;
1067 	parse_push_key(vt_key, PROP_KEY_STRING);
1068 
1069 	/* Retrieve, or invent, then store the value. */
1070 	switch (terminal[0]) {
1071 	case PARSE_INTEGER:
1072 		FUNC0(parse_get_taf_integer, vt_value.integer);
1073 		parse_put_property(vt_value, PROP_INTEGER);
1074 		break;
1075 	case PARSE_DEFAULT_ZERO:
1076 		vt_value.integer = 0;
1077 		parse_put_property(vt_value, PROP_INTEGER);
1078 		break;
1079 
1080 	case PARSE_BOOLEAN:
1081 		FUNC0(parse_get_taf_boolean, vt_value.boolean);
1082 		parse_put_property(vt_value, PROP_BOOLEAN);
1083 		break;
1084 	case PARSE_DEFAULT_FALSE:
1085 	case PARSE_DEFAULT_TRUE:
1086 		vt_value.boolean = (terminal[0] == PARSE_DEFAULT_TRUE);
1087 		parse_put_property(vt_value, PROP_BOOLEAN);
1088 		break;
1089 
1090 	case PARSE_STRING:
1091 		FUNC0(parse_get_taf_string, vt_value.string);
1092 		parse_put_property(vt_value, PROP_STRING);
1093 		break;
1094 	case PARSE_DEFAULT_EMPTY:
1095 		vt_value.string = "";
1096 		parse_put_property(vt_value, PROP_STRING);
1097 		break;
1098 
1099 	case PARSE_MULTILINE:
1100 		/* Assign to and adopt mutable string rather than const string. */
1101 		FUNC0(parse_read_multiline, vt_value.mutable_string);
1102 		parse_put_property(vt_value, PROP_STRING);
1103 
1104 		assert(parse_bundle);
1105 		prop_adopt(parse_bundle, vt_value.mutable_string);
1106 		break;
1107 
1108 	case PARSE_IGNORE_INTEGER:
1109 		CALL0(parse_get_taf_integer);
1110 		break;
1111 	case PARSE_IGNORE_BOOLEAN:
1112 		CALL0(parse_get_taf_boolean);
1113 		break;
1114 	case PARSE_IGNORE_STRING:
1115 		CALL0(parse_get_taf_string);
1116 		break;
1117 
1118 	default:
1119 		sc_fatal("parse_terminal: bad type, %c\n", terminal[0]);
1120 	}
1121 
1122 	/* Pop terminal key. */
1123 	parse_pop_key();
1124 
1125 	if (parse_trace)
1126 		sc_trace("Parse: leaving terminal %s\n", terminal);
1127 }
1128 
1129 
1130 /*
1131  * Resources table.  This table enables resource offsets to be calculated
1132  * for the various sound and graphic resources encountered on parsing
1133  * version 4.0 games.  It's unused if the version is not 4.0.
1134  */
1135 struct sc_parse_resource_t {
1136 	sc_char *name;
1137 	sc_uint hash;
1138 	sc_int length;
1139 	sc_int offset;
1140 };
1141 
1142 enum { RESOURCE_GROW_INCREMENT = 32 };
1143 static sc_int parse_resources_length = 0;
1144 static sc_int parse_resources_size = 0;
1145 static sc_parse_resource_t *parse_resources = NULL;
1146 
1147 
1148 /*
1149  * parse_clear_v400_resources_table()
1150  *
1151  * Free and clear down the version 4.0 resources table.
1152  */
parse_clear_v400_resources_table(void)1153 static void parse_clear_v400_resources_table(void) {
1154 	/* Free allocated memory and return to initial values. */
1155 	if (parse_resources) {
1156 		sc_int index_;
1157 
1158 		for (index_ = 0; index_ < parse_resources_length; index_++)
1159 			sc_free(parse_resources[index_].name);
1160 
1161 		sc_free(parse_resources);
1162 		parse_resources = NULL;
1163 	}
1164 	parse_resources_length = 0;
1165 	parse_resources_size = 0;
1166 }
1167 
1168 
1169 /*
1170  * parse_get_v400_resource_offset()
1171  *
1172  * Notes version 4.0 resource names encountered in the parse, and their
1173  * lengths, and builds up a list of resources with their data offsets.  The
1174  * function assumes that resources are appended to the TAF file in the
1175  * order in which they are encountered when reading through the TAF file.
1176  *
1177  * A warning -- this function may return a new length.  Resources that
1178  * have been seen once already have non-useful (though apparently non-zero)
1179  * lengths; this function needs to handle that.  The caller needs to compare
1180  * length with real_length to see if that happened.
1181  */
parse_get_v400_resource_offset(const sc_char * name,sc_int length,sc_int * real_length)1182 static sc_int parse_get_v400_resource_offset(const sc_char *name,
1183 		sc_int length, sc_int *real_length) {
1184 	sc_char *clean_name;
1185 	sc_uint hash;
1186 	sc_int index_, offset;
1187 
1188 	/*
1189 	 * Take a copy of the name, and remove any trailing "##" looping sound
1190 	 * indicator flag.  Who thinks this junk up?
1191 	 */
1192 	clean_name = (sc_char *)sc_malloc(strlen(name) + 1);
1193 	strcpy(clean_name, name);
1194 	if (strcmp(clean_name + strlen(clean_name) - 2, "##") == 0)
1195 		clean_name[strlen(clean_name) - 2] = NUL;
1196 
1197 	/*
1198 	 * Scan the current resources list for a matching name, and if the resource
1199 	 * is already known, return its offset.  The hash check is an attempt to
1200 	 * improve the search times, relative to using only string comparisons --
1201 	 * the table's not fully hashed.  If found, we need to also pass back the
1202 	 * corrected length.
1203 	 */
1204 	offset = -1;
1205 	hash = sc_hash(clean_name);
1206 	for (index_ = 0; index_ < parse_resources_length; index_++) {
1207 		if (parse_resources[index_].hash == hash
1208 		        && strcmp(parse_resources[index_].name, clean_name) == 0) {
1209 			offset = parse_resources[index_].offset;
1210 			break;
1211 		}
1212 	}
1213 	if (offset != -1) {
1214 		*real_length = parse_resources[index_].length;
1215 		sc_free(clean_name);
1216 		return offset;
1217 	}
1218 
1219 	/* Resize the resources table if required. */
1220 	if (parse_resources_length == parse_resources_size) {
1221 		parse_resources_size += RESOURCE_GROW_INCREMENT;
1222 		parse_resources = (sc_parse_resource_t *)sc_realloc(parse_resources,
1223 		                  parse_resources_size *
1224 		                  sizeof(parse_resources[0]));
1225 	}
1226 
1227 	/*
1228 	 * Calculate the offset.  For the first resource, it's zero; for others,
1229 	 * it's one after the prior entry's offset and length.
1230 	 */
1231 	if (parse_resources_length == 0)
1232 		offset = 0;
1233 	else {
1234 		offset = parse_resources[parse_resources_length - 1].offset
1235 		         + parse_resources[parse_resources_length - 1].length + 1;
1236 	}
1237 
1238 	/* Add details to the table. */
1239 	parse_resources[parse_resources_length].name = clean_name;
1240 	parse_resources[parse_resources_length].hash = hash;
1241 	parse_resources[parse_resources_length].offset = offset;
1242 	parse_resources[parse_resources_length].length = length;
1243 	parse_resources_length++;
1244 
1245 	*real_length = length;
1246 	return offset;
1247 }
1248 
1249 
1250 /*
1251  * parse_handle_v400_resources()
1252  *
1253  * Extra special handling for version 4.0 resources; extracts details of
1254  * the resource just parsed, and adds an offset property for each defined.
1255  *
1256  * A warning -- Adrift seems to use -ve numbers as lengths for resources
1257  * already parsed, where TAF files include the resource.  It's unclear
1258  * what the -ve values mean, so here we ignore them and work off the
1259  * resource file name given.  This means we have to look for length not
1260  * equal to zero, not just lengths greater than zero.
1261  *
1262  * TODO Work out what this means.  The -ve lengths look like a form of
1263  * 'resource number'; -(length+2) is tantalizingly close to the index into
1264  * our parse_resources table, but not always...
1265  */
parse_handle_v400_resources(sc_bool has_sound,sc_bool has_graphics)1266 static void parse_handle_v400_resources(sc_bool has_sound, sc_bool has_graphics) {
1267 	sc_vartype_t vt_key, vt_value;
1268 	const sc_char *file;
1269 	sc_int length, offset;
1270 
1271 	/*
1272 	 * Retrieve the file and length for the sound just parsed.  If there's a
1273 	 * file of non-zero length, rewrite its offset.
1274 	 */
1275 	if (has_sound) {
1276 		/* Retrieve the file and length information. */
1277 		vt_key.string = "SoundFile";
1278 		parse_push_key(vt_key, PROP_KEY_STRING);
1279 		file = parse_get_string_property();
1280 		parse_pop_key();
1281 
1282 		vt_key.string = "SoundLen";
1283 		parse_push_key(vt_key, PROP_KEY_STRING);
1284 		length = parse_get_integer_property();
1285 		parse_pop_key();
1286 
1287 		/*
1288 		 * If defined and has a length, rewrite the offset, and also the length
1289 		 * in case changed.
1290 		 */
1291 		if (!sc_strempty(file) && length != 0) {
1292 			sc_int real_length;
1293 
1294 			offset = parse_get_v400_resource_offset(file, length, &real_length);
1295 			vt_key.string = "SoundOffset";
1296 			parse_push_key(vt_key, PROP_KEY_STRING);
1297 
1298 			vt_value.integer = offset;
1299 			parse_put_property(vt_value, PROP_INTEGER);
1300 
1301 			parse_pop_key();
1302 
1303 			/* Rewrite length if changed. */
1304 			if (real_length != length) {
1305 				vt_key.string = "SoundLen";
1306 				parse_push_key(vt_key, PROP_KEY_STRING);
1307 
1308 				vt_value.integer = real_length;
1309 				parse_put_property(vt_value, PROP_INTEGER);
1310 
1311 				parse_pop_key();
1312 			}
1313 		}
1314 	}
1315 
1316 	/* Now do the same thing for graphics. */
1317 	if (has_graphics) {
1318 		/* Retrieve the file and length information. */
1319 		vt_key.string = "GraphicFile";
1320 		parse_push_key(vt_key, PROP_KEY_STRING);
1321 		file = parse_get_string_property();
1322 		parse_pop_key();
1323 
1324 		vt_key.string = "GraphicLen";
1325 		parse_push_key(vt_key, PROP_KEY_STRING);
1326 		length = parse_get_integer_property();
1327 		parse_pop_key();
1328 
1329 		/*
1330 		 * If defined and has a length, rewrite the offset, and also the length
1331 		 * in case changed.
1332 		 */
1333 		if (!sc_strempty(file) && length != 0) {
1334 			sc_int real_length;
1335 
1336 			offset = parse_get_v400_resource_offset(file, length, &real_length);
1337 			vt_key.string = "GraphicOffset";
1338 			parse_push_key(vt_key, PROP_KEY_STRING);
1339 
1340 			vt_value.integer = offset;
1341 			parse_put_property(vt_value, PROP_INTEGER);
1342 
1343 			parse_pop_key();
1344 
1345 			/* Rewrite length if changed. */
1346 			if (real_length != length) {
1347 				vt_key.string = "GraphicLen";
1348 				parse_push_key(vt_key, PROP_KEY_STRING);
1349 
1350 				vt_value.integer = real_length;
1351 				parse_put_property(vt_value, PROP_INTEGER);
1352 
1353 				parse_pop_key();
1354 			}
1355 		}
1356 	}
1357 }
1358 
1359 
1360 /*
1361  * parse_special()
1362  *
1363  * Handler for special items that can't be described accurately, and
1364  * therefore need careful treatment.
1365  */
parse_special(CONTEXT,const sc_char * special)1366 static void parse_special(CONTEXT, const sc_char *special) {
1367 	if (parse_trace)
1368 		sc_trace("Parse: entering special %s\n", special);
1369 
1370 	/* Special handling for version 4.0 resources. */
1371 	if (strcmp(special, "{V400_RESOURCE}") == 0) {
1372 		sc_vartype_t vt_key[2];
1373 		sc_bool has_sound, has_graphics;
1374 
1375 		/* Get sound and graphics global flags. */
1376 		vt_key[0].string = "Globals";
1377 		vt_key[1].string = "Sound";
1378 		has_sound = prop_get_boolean(parse_bundle, "B<-ss", vt_key);
1379 
1380 		vt_key[1].string = "Graphics";
1381 		has_graphics = prop_get_boolean(parse_bundle, "B<-ss", vt_key);
1382 
1383 		/* Apply special handling to the resources. */
1384 		parse_handle_v400_resources(has_sound, has_graphics);
1385 	}
1386 
1387 	/* Parse a version 4.0 optional set of room exit information. */
1388 	else if (strcmp(special, "{V400_ROOM_EXIT:#Dest_#Var1_#Var2_#Var3}") == 0) {
1389 		sc_int flag;
1390 
1391 		/* Get next flag, and if true, pushback and parse. */
1392 		FUNC0(parse_get_taf_integer, flag);
1393 		if (flag != 0) {
1394 			parse_taf_pushback();
1395 			CALL1(parse_descriptor, "#Dest #Var1 #Var2 #Var3");
1396 		}
1397 	}
1398 
1399 	/* Parse version 3.9 and version 3.8 optional room exit information. */
1400 	else if (strcmp(special,
1401 	                "{V390_V380_ROOM_EXIT:#Dest_#Var1_#Var2_ZVar3}") == 0) {
1402 		sc_int flag;
1403 
1404 		/* Get next flag, and if true, pushback and parse. */
1405 		FUNC0(parse_get_taf_integer, flag);
1406 		if (flag != 0) {
1407 			parse_taf_pushback();
1408 			CALL1(parse_descriptor, "#Dest #Var1 #Var2 ZVar3");
1409 		}
1410 	}
1411 
1412 	/* Parse room lists, with optional extra room. */
1413 	else if (strcmp(special, "{ROOM_LIST0}") == 0
1414 	         || strcmp(special, "{ROOM_LIST1}") == 0) {
1415 		sc_vartype_t vt_key, vt_value;
1416 		sc_int room_count, num_rooms, type, index_;
1417 
1418 		/* Retrieve the room list type. */
1419 		vt_key.string = "Type";
1420 		parse_push_key(vt_key, PROP_KEY_STRING);
1421 		type = parse_get_integer_property();
1422 		parse_pop_key();
1423 
1424 		/* Write remaining room list depending on the type. */
1425 		switch (type) {
1426 		case ROOMLIST_NO_ROOMS:
1427 		case ROOMLIST_ALL_ROOMS:
1428 		case ROOMLIST_NPC_PART:
1429 			break;
1430 
1431 		case ROOMLIST_ONE_ROOM:
1432 			/* Store this room as the single list entry. */
1433 			CALL1(parse_element, "#Room");
1434 			break;
1435 
1436 		case ROOMLIST_SOME_ROOMS:
1437 			/* Get count of rooms defined, add one if necessary. */
1438 			vt_key.string = "Rooms";
1439 			room_count = prop_get_child_count(parse_bundle, "I<-s", &vt_key);
1440 
1441 			if (strcmp(special, "{ROOM_LIST1}") == 0)
1442 				num_rooms = room_count + 1;
1443 			else
1444 				num_rooms = room_count;
1445 
1446 			/* Store an array of rooms flags for each room. */
1447 			vt_key.string = "Rooms";
1448 			parse_push_key(vt_key, PROP_KEY_STRING);
1449 			for (index_ = 0; index_ < num_rooms; index_++) {
1450 				sc_bool this_room;
1451 
1452 				/* Get flag for this room. */
1453 				FUNC0(parse_get_taf_boolean, this_room);
1454 
1455 				/* Store flag directly. */
1456 				vt_key.integer = index_;
1457 				parse_push_key(vt_key, PROP_KEY_INTEGER);
1458 				vt_value.boolean = this_room;
1459 				parse_put_property(vt_value, PROP_BOOLEAN);
1460 				parse_pop_key();
1461 			}
1462 			parse_pop_key();
1463 			break;
1464 
1465 		default:
1466 			sc_fatal("parse_special: bad type, %ld\n", type);
1467 		}
1468 	}
1469 
1470 	/* Parse Parent number iff this object is an NPC part. */
1471 	else if (strcmp(special, "{OBJECT:#Parent}") == 0) {
1472 		sc_vartype_t vt_key;
1473 		sc_int type;
1474 
1475 		/* Check object's Where room list Type for NPC part. */
1476 		vt_key.string = "Where";
1477 		parse_push_key(vt_key, PROP_KEY_STRING);
1478 		vt_key.string = "Type";
1479 		parse_push_key(vt_key, PROP_KEY_STRING);
1480 		type = parse_get_integer_property();
1481 		parse_pop_key();
1482 		parse_pop_key();
1483 
1484 		/* Get Parent if the object is part of an NPC. */
1485 		if (type == ROOMLIST_NPC_PART) {
1486 			CALL1(parse_element, "#Parent");
1487 		}
1488 	}
1489 
1490 	/* Parse a list of rooms and times for a walk. */
1491 	else if (strcmp(special, "{WALK:#Rooms_#Times}") == 0) {
1492 		sc_vartype_t vt_key, vt_value;
1493 		sc_int num_stops, index_;
1494 
1495 		/* Obtain the count of stops in this walk. */
1496 		vt_key.string = "NumStops";
1497 		parse_push_key(vt_key, PROP_KEY_STRING);
1498 		num_stops = parse_get_integer_property();
1499 		parse_pop_key();
1500 
1501 		/* Look for a room and time for each stop. */
1502 		for (index_ = 0; index_ < num_stops; index_++) {
1503 			sc_int room, time;
1504 
1505 			/* Parse and store Rooms[index_]. */
1506 			vt_key.string = "Rooms";
1507 			parse_push_key(vt_key, PROP_KEY_STRING);
1508 			vt_key.integer = index_;
1509 			parse_push_key(vt_key, PROP_KEY_INTEGER);
1510 
1511 			FUNC0(parse_get_taf_integer, room);
1512 
1513 			vt_value.integer = room;
1514 			parse_put_property(vt_value, PROP_INTEGER);
1515 			parse_pop_key();
1516 			parse_pop_key();
1517 
1518 			/* Parse and store Times[index_]. */
1519 			vt_key.string = "Times";
1520 			parse_push_key(vt_key, PROP_KEY_STRING);
1521 			vt_key.integer = index_;
1522 			parse_push_key(vt_key, PROP_KEY_INTEGER);
1523 
1524 			FUNC0(parse_get_taf_integer, time);
1525 
1526 			vt_value.integer = time;
1527 			parse_put_property(vt_value, PROP_INTEGER);
1528 			parse_pop_key();
1529 			parse_pop_key();
1530 		}
1531 	}
1532 
1533 	/* Parse a room group variable size boolean list. */
1534 	else if (strcmp(special, "{ROOM_GROUP:[]BList}") == 0) {
1535 		sc_vartype_t vt_key, vt_value;
1536 		sc_int num_rooms, index_, l2index_;
1537 		sc_bool in_group;
1538 
1539 		/* Get the count of rooms defined. */
1540 		vt_key.string = "Rooms";
1541 		num_rooms = prop_get_integer(parse_bundle, "I<-s", &vt_key);
1542 
1543 		/* Read a boolean for each room. */
1544 		l2index_ = 0;
1545 		for (index_ = 0; index_ < num_rooms; index_++) {
1546 			FUNC0(parse_get_taf_boolean, in_group);
1547 
1548 			/* Store raw flag as List[index_]. */
1549 			vt_key.string = "List";
1550 			parse_push_key(vt_key, PROP_KEY_STRING);
1551 			vt_key.integer = index_;
1552 			parse_push_key(vt_key, PROP_KEY_INTEGER);
1553 			vt_value.boolean = in_group;
1554 			parse_put_property(vt_value, PROP_BOOLEAN);
1555 
1556 			parse_pop_key();
1557 			parse_pop_key();
1558 
1559 			/* Store in-group index'es as List2[0..n]. */
1560 			if (in_group) {
1561 				vt_key.string = "List2";
1562 				parse_push_key(vt_key, PROP_KEY_STRING);
1563 				vt_key.integer = l2index_;
1564 				parse_push_key(vt_key, PROP_KEY_INTEGER);
1565 				vt_value.integer = index_;
1566 				parse_put_property(vt_value, PROP_INTEGER);
1567 
1568 				parse_pop_key();
1569 				parse_pop_key();
1570 
1571 				l2index_++;
1572 			}
1573 		}
1574 	}
1575 
1576 	/* Error if no special handler available. */
1577 	else {
1578 		sc_fatal("parse_special: no handler for \"%s\"\n", special);
1579 	}
1580 
1581 	if (parse_trace)
1582 		sc_trace("Parse: leaving special %s\n", special);
1583 }
1584 
1585 
1586 /*
1587  * parse_fixup_v390_v380_room_alt()
1588  *
1589  * Helper for parse_fixup_v390_v380_room_alts().  Handles creation of
1590  * version 4.0 room alts for version 3.9 and version 3.8 games.
1591  */
parse_fixup_v390_v380_room_alt(const sc_char * m1,sc_int type,const sc_char * resource1,const sc_char * m2,sc_int var2,const sc_char * resource2,sc_int hide_objects,const sc_char * changed,sc_int var3,sc_int display_room)1592 static void parse_fixup_v390_v380_room_alt(const sc_char *m1, sc_int type,
1593 		const sc_char *resource1, const sc_char *m2, sc_int var2, const sc_char *resource2,
1594 		sc_int hide_objects, const sc_char *changed, sc_int var3, sc_int display_room) {
1595 	sc_vartype_t vt_key, vt_value, vt_gkey[2];
1596 	sc_bool has_sound, has_graphics;
1597 	sc_int alt_count;
1598 	const sc_char *soundfile1, *graphicfile1;
1599 	const sc_char *soundfile2, *graphicfile2;
1600 
1601 	/*
1602 	 * Initialize resource files to empty, for cases where no resource is copied
1603 	 * over from the main room (NULL resource1/2).
1604 	 */
1605 	soundfile1 = "";
1606 	graphicfile1 = "";
1607 	soundfile2 = "";
1608 	graphicfile2 = "";
1609 
1610 	/* Get sound and graphics flags, always FALSE for version 3.8. */
1611 	vt_gkey[0].string = "Globals";
1612 	vt_gkey[1].string = "Sound";
1613 	has_sound = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
1614 
1615 	vt_gkey[1].string = "Graphics";
1616 	has_graphics = prop_get_boolean(parse_bundle, "B<-ss", vt_gkey);
1617 
1618 	/* Get a count of alts so far defined for the room. */
1619 	vt_key.string = "Alts";
1620 	parse_push_key(vt_key, PROP_KEY_STRING);
1621 	alt_count = parse_get_child_count();
1622 	parse_pop_key();
1623 
1624 	/*
1625 	 * Lookup any resource details now, and save them.  Because this is not
1626 	 * version 4.0, we can ignore lengths, and set them to zero when needed.
1627 	 */
1628 	if (has_sound || has_graphics) {
1629 		if (resource1) {
1630 			vt_key.string = resource1;
1631 			parse_push_key(vt_key, PROP_KEY_STRING);
1632 			if (has_sound) {
1633 				vt_key.string = "SoundFile";
1634 				parse_push_key(vt_key, PROP_KEY_STRING);
1635 				soundfile1 = parse_get_string_property();
1636 				parse_pop_key();
1637 			}
1638 			if (has_graphics) {
1639 				vt_key.string = "GraphicFile";
1640 				parse_push_key(vt_key, PROP_KEY_STRING);
1641 				graphicfile1 = parse_get_string_property();
1642 				parse_pop_key();
1643 			}
1644 			parse_pop_key();
1645 		}
1646 
1647 		if (resource2) {
1648 			vt_key.string = resource2;
1649 			parse_push_key(vt_key, PROP_KEY_STRING);
1650 			if (has_sound) {
1651 				vt_key.string = "SoundFile";
1652 				parse_push_key(vt_key, PROP_KEY_STRING);
1653 				soundfile2 = parse_get_string_property();
1654 				parse_pop_key();
1655 			}
1656 			if (has_graphics) {
1657 				vt_key.string = "GraphicFile";
1658 				parse_push_key(vt_key, PROP_KEY_STRING);
1659 				graphicfile2 = parse_get_string_property();
1660 				parse_pop_key();
1661 			}
1662 			parse_pop_key();
1663 		}
1664 	}
1665 
1666 	/*
1667 	 * Create a room alt to match data passed in.  Start with the Alts string
1668 	 * and the index to the alt being written.  To correctly emulate the parse,
1669 	 * we also have to reverse the "Alts" and the index, as parse_put_property()
1670 	 * will swap them.  Madness.
1671 	 */
1672 	vt_key.integer = alt_count;
1673 	parse_push_key(vt_key, PROP_KEY_INTEGER);
1674 	vt_key.string = "Alts";
1675 	parse_push_key(vt_key, PROP_KEY_STRING);
1676 
1677 	/* Write M1 and Type. */
1678 	vt_key.string = "M1";
1679 	parse_push_key(vt_key, PROP_KEY_STRING);
1680 	vt_value.string = m1;
1681 	parse_put_property(vt_value, PROP_STRING);
1682 	parse_pop_key();
1683 	vt_key.string = "Type";
1684 	parse_push_key(vt_key, PROP_KEY_STRING);
1685 	vt_value.integer = type;
1686 	parse_put_property(vt_value, PROP_INTEGER);
1687 	parse_pop_key();
1688 
1689 	/* If resources, add these as retrieved above. */
1690 	if (has_sound || has_graphics) {
1691 		vt_key.string = "Res1";
1692 		parse_push_key(vt_key, PROP_KEY_STRING);
1693 		if (has_sound) {
1694 			vt_key.string = "SoundFile";
1695 			parse_push_key(vt_key, PROP_KEY_STRING);
1696 			vt_value.string = soundfile1;
1697 			parse_put_property(vt_value, PROP_STRING);
1698 			parse_pop_key();
1699 			vt_key.string = "SoundLen";
1700 			parse_push_key(vt_key, PROP_KEY_STRING);
1701 			vt_value.integer = 0;
1702 			parse_put_property(vt_value, PROP_INTEGER);
1703 			parse_pop_key();
1704 		}
1705 		if (has_graphics) {
1706 			vt_key.string = "GraphicFile";
1707 			parse_push_key(vt_key, PROP_KEY_STRING);
1708 			vt_value.string = graphicfile1;
1709 			parse_put_property(vt_value, PROP_STRING);
1710 			parse_pop_key();
1711 			vt_key.string = "GraphicLen";
1712 			parse_push_key(vt_key, PROP_KEY_STRING);
1713 			vt_value.integer = 0;
1714 			parse_put_property(vt_value, PROP_INTEGER);
1715 			parse_pop_key();
1716 		}
1717 		parse_pop_key();
1718 	}
1719 
1720 	/* Write M2 and Var2. */
1721 	vt_key.string = "M2";
1722 	parse_push_key(vt_key, PROP_KEY_STRING);
1723 	vt_value.string = m2;
1724 	parse_put_property(vt_value, PROP_STRING);
1725 	parse_pop_key();
1726 	vt_key.string = "Var2";
1727 	parse_push_key(vt_key, PROP_KEY_STRING);
1728 	vt_value.integer = var2;
1729 	parse_put_property(vt_value, PROP_INTEGER);
1730 	parse_pop_key();
1731 
1732 	/* If resources, again add these as retrieved above. */
1733 	if (has_sound || has_graphics) {
1734 		vt_key.string = "Res2";
1735 		parse_push_key(vt_key, PROP_KEY_STRING);
1736 		if (has_sound) {
1737 			vt_key.string = "SoundFile";
1738 			parse_push_key(vt_key, PROP_KEY_STRING);
1739 			vt_value.string = soundfile2;
1740 			parse_put_property(vt_value, PROP_STRING);
1741 			parse_pop_key();
1742 			vt_key.string = "SoundLen";
1743 			parse_push_key(vt_key, PROP_KEY_STRING);
1744 			vt_value.integer = 0;
1745 			parse_put_property(vt_value, PROP_INTEGER);
1746 			parse_pop_key();
1747 		}
1748 		if (has_graphics) {
1749 			vt_key.string = "GraphicFile";
1750 			parse_push_key(vt_key, PROP_KEY_STRING);
1751 			vt_value.string = graphicfile2;
1752 			parse_put_property(vt_value, PROP_STRING);
1753 			parse_pop_key();
1754 			vt_key.string = "GraphicLen";
1755 			parse_push_key(vt_key, PROP_KEY_STRING);
1756 			vt_value.integer = 0;
1757 			parse_put_property(vt_value, PROP_INTEGER);
1758 			parse_pop_key();
1759 		}
1760 		parse_pop_key();
1761 	}
1762 
1763 	/* Finish off with the last four alt properties. */
1764 	vt_key.string = "HideObjects";
1765 	parse_push_key(vt_key, PROP_KEY_STRING);
1766 	vt_value.integer = hide_objects;
1767 	parse_put_property(vt_value, PROP_INTEGER);
1768 	parse_pop_key();
1769 	vt_key.string = "Changed";
1770 	parse_push_key(vt_key, PROP_KEY_STRING);
1771 	vt_value.string = changed;
1772 	parse_put_property(vt_value, PROP_STRING);
1773 	parse_pop_key();
1774 	vt_key.string = "Var3";
1775 	parse_push_key(vt_key, PROP_KEY_STRING);
1776 	vt_value.integer = var3;
1777 	parse_put_property(vt_value, PROP_INTEGER);
1778 	parse_pop_key();
1779 	vt_key.string = "DisplayRoom";
1780 	parse_push_key(vt_key, PROP_KEY_STRING);
1781 	vt_value.integer = display_room;
1782 	parse_put_property(vt_value, PROP_INTEGER);
1783 	parse_pop_key();
1784 
1785 	parse_pop_key();
1786 	parse_pop_key();
1787 }
1788 
1789 
1790 /* Multiplier for combination AltDesc Type and HideObject values. */
1791 enum { V390_V380_ALT_TYPEHIDE_MULT = 10 };
1792 
1793 /*
1794  * parse_fixup_v390_v380_room_alts()
1795  *
1796  * Common helper function for parse_fixup_v390() and parse_fixup_v380(),
1797  * converts version 3.9 and version 3.8 fixed room description alts into
1798  * an equivalent array of version 4.0 style room alts.
1799  */
parse_fixup_v390_v380_room_alts(void)1800 static void parse_fixup_v390_v380_room_alts(void) {
1801 	sc_vartype_t vt_key;
1802 	const sc_char *m1, *m2, *changed;
1803 	sc_int type, var2, hide_objects, var3, display_room;
1804 
1805 	/* Room alt invariants. */
1806 	m2 = "";                      /* No else text */
1807 	changed = "";                 /* No changed room name */
1808 
1809 	/*
1810 	 * Create a room alt to override all others, controlled by an object
1811 	 * condition and with optional object hiding.
1812 	 */
1813 	type = 2;                     /* Object condition */
1814 	display_room = 0;             /* Override all others */
1815 
1816 	vt_key.string = "Obj";
1817 	parse_push_key(vt_key, PROP_KEY_STRING);
1818 	var3 = parse_get_integer_property();
1819 	parse_pop_key();
1820 
1821 	if (var3 > 0) {
1822 		sc_int typehideobjects;
1823 
1824 		vt_key.string = "AltDesc";
1825 		parse_push_key(vt_key, PROP_KEY_STRING);
1826 		m1 = parse_get_string_property();
1827 		parse_pop_key();
1828 
1829 		vt_key.string = "TypeHideObjects";
1830 		parse_push_key(vt_key, PROP_KEY_STRING);
1831 		typehideobjects = parse_get_integer_property();
1832 		parse_pop_key();
1833 
1834 		var2 = typehideobjects / V390_V380_ALT_TYPEHIDE_MULT;
1835 		hide_objects = typehideobjects % V390_V380_ALT_TYPEHIDE_MULT;
1836 
1837 		parse_fixup_v390_v380_room_alt(m1, type, "AltRes",
1838 		                               m2, var2, NULL,
1839 		                               hide_objects, changed, var3,
1840 		                               display_room);
1841 	}
1842 
1843 	/*
1844 	 * If a second task alternate description is defined, create a room alt to
1845 	 * add after the main description, one that stops printing once done.
1846 	 */
1847 	type = 0;                     /* Task condition */
1848 	display_room = 1;             /* Print after main and stop */
1849 
1850 	vt_key.string = "Task2";
1851 	parse_push_key(vt_key, PROP_KEY_STRING);
1852 	var2 = parse_get_integer_property();
1853 	parse_pop_key();
1854 
1855 	if (var2 > 0) {
1856 		vt_key.string = "AddDesc2";
1857 		parse_push_key(vt_key, PROP_KEY_STRING);
1858 		m1 = parse_get_string_property();
1859 		parse_pop_key();
1860 
1861 		var3 = 0;
1862 		hide_objects = 0;
1863 
1864 		parse_fixup_v390_v380_room_alt(m1, type, "Task2Res",
1865 		                               m2, var2, NULL,
1866 		                               hide_objects, changed, var3,
1867 		                               display_room);
1868 	}
1869 
1870 	/* Do the same for any first task additional description. */
1871 	type = 0;                     /* Task condition */
1872 	display_room = 1;             /* Print after main and stop */
1873 
1874 	vt_key.string = "Task1";
1875 	parse_push_key(vt_key, PROP_KEY_STRING);
1876 	var2 = parse_get_integer_property();
1877 	parse_pop_key();
1878 
1879 	if (var2 > 0) {
1880 		vt_key.string = "AddDesc1";
1881 		parse_push_key(vt_key, PROP_KEY_STRING);
1882 		m1 = parse_get_string_property();
1883 		parse_pop_key();
1884 
1885 		var3 = 0;
1886 		hide_objects = 0;
1887 
1888 		parse_fixup_v390_v380_room_alt(m1, type, "Task1Res",
1889 		                               m2, var2, NULL,
1890 		                               hide_objects, changed, var3,
1891 		                               display_room);
1892 	}
1893 
1894 	/*
1895 	 * If still printing at this point, we need a catch-all room alt that will
1896 	 * print.  So create one with an always true condition.
1897 	 */
1898 	type = 0;                     /* Task condition */
1899 	display_room = 2;             /* Lowest priority output */
1900 
1901 	vt_key.string = "LastDesc";
1902 	parse_push_key(vt_key, PROP_KEY_STRING);
1903 	m1 = parse_get_string_property();
1904 	parse_pop_key();
1905 
1906 	if (!sc_strempty(m1)) {
1907 		var2 = 0;                 /* No task - always TRUE */
1908 		var3 = 0;
1909 		hide_objects = 0;
1910 
1911 		parse_fixup_v390_v380_room_alt(m1, type, "LastRes",
1912 		                               m2, var2, NULL,
1913 		                               hide_objects, changed, var3,
1914 		                               display_room);
1915 	}
1916 }
1917 
1918 
1919 /*
1920  * parse_fixup_v390()
1921  *
1922  * Handler for fixup special items to help with conversions from TAF version
1923  * 3.9 format into version 4.0.
1924  */
parse_fixup_v390(CONTEXT,const sc_char * fixup)1925 static void parse_fixup_v390(CONTEXT, const sc_char *fixup) {
1926 	if (parse_trace)
1927 		sc_trace("Parse: entering version 3.9 fixup %s\n", fixup);
1928 
1929 	/* Fixup a version 3.9 task action by incrementing Type > 4. */
1930 	if (strcmp(fixup, "|V390_TASK_ACTION:Type>4?#Type++|") == 0) {
1931 		sc_vartype_t vt_key, vt_value;
1932 		sc_int type;
1933 
1934 		/* Retrieve Type, and if > 4, increment. */
1935 		vt_key.string = "Type";
1936 		parse_push_key(vt_key, PROP_KEY_STRING);
1937 		type = parse_get_integer_property();
1938 
1939 		if (type > 4) {
1940 			vt_value.integer = type + 1;
1941 			parse_put_property(vt_value, PROP_INTEGER);
1942 		}
1943 
1944 		parse_pop_key();
1945 	}
1946 
1947 	/* Handle either Expr or Var5 for version 3.9 task actions. */
1948 	else if (strcmp(fixup, "|V390_TASK_ACTION:$Expr_#Var5|") == 0) {
1949 		sc_vartype_t vt_key;
1950 		sc_int var2;
1951 
1952 		/* Either Expr or Var5, depending on Var2. */
1953 		vt_key.string = "Var2";
1954 		parse_push_key(vt_key, PROP_KEY_STRING);
1955 		var2 = parse_get_integer_property();
1956 		parse_pop_key();
1957 
1958 		if (var2 == 5) {
1959 			CALL1(parse_descriptor, "$Expr ZVar5");
1960 		} else {
1961 			CALL1(parse_descriptor, "EExpr #Var5");
1962 		}
1963 	}
1964 
1965 	/*
1966 	 * Exchange openable values 5 and 6, and write -1 key for openable objects.
1967 	 */
1968 	else if (strcmp(fixup, "|V390_OBJECT:_Openable_,Key|") == 0) {
1969 		sc_vartype_t vt_key, vt_value;
1970 		sc_int openable;
1971 
1972 		/* Retrieve Openable, and if 5 or 6, exchange. */
1973 		vt_key.string = "Openable";
1974 		parse_push_key(vt_key, PROP_KEY_STRING);
1975 		openable = parse_get_integer_property();
1976 
1977 		if (openable == 5 || openable == 6) {
1978 			vt_value.integer = (openable == 5) ? 6 : 5;
1979 			parse_put_property(vt_value, PROP_INTEGER);
1980 		}
1981 
1982 		parse_pop_key();
1983 
1984 		/* For openable objects, store a Key of -1. */
1985 		if (openable == 5 || openable == 6) {
1986 			vt_key.string = "Key";
1987 			parse_push_key(vt_key, PROP_KEY_STRING);
1988 			vt_value.integer = -1;
1989 			parse_put_property(vt_value, PROP_INTEGER);
1990 			parse_pop_key();
1991 		}
1992 	}
1993 
1994 	/* Create a RestrMask that 'and's all the restrictions together. */
1995 	else if (strcmp(fixup, "|V390_TASK:$RestrMask|") == 0) {
1996 		sc_vartype_t vt_key, vt_value;
1997 		sc_int restriction_count;
1998 
1999 		/* Get a count of restrictions. */
2000 		vt_key.string = "Restrictions";
2001 		parse_push_key(vt_key, PROP_KEY_STRING);
2002 		restriction_count = parse_get_child_count();
2003 		parse_pop_key();
2004 
2005 		/* Allocate and fill a new mask for these restrictions. */
2006 		if (restriction_count > 0) {
2007 			sc_char *restrmask;
2008 			sc_int index_;
2009 
2010 			restrmask = (sc_char *)sc_malloc(2 * restriction_count);
2011 			strcpy(restrmask, "#");
2012 			for (index_ = 1; index_ < restriction_count; index_++)
2013 				strcat(restrmask, "A#");
2014 
2015 			vt_key.string = "RestrMask";
2016 			parse_push_key(vt_key, PROP_KEY_STRING);
2017 			vt_value.string = restrmask;
2018 			parse_put_property(vt_value, PROP_STRING);
2019 			parse_pop_key();
2020 
2021 			prop_adopt(parse_bundle, restrmask);
2022 		}
2023 	}
2024 
2025 	/*
2026 	 * Increment var1 for variable restrictions to compensate for there being no
2027 	 * referenced text comparison (no string variables).
2028 	 */
2029 	else if (strcmp(fixup, "|V390_TASK_RESTR:Var1>0?#Var1++|") == 0) {
2030 		sc_vartype_t vt_key, vt_value;
2031 		sc_int var1;
2032 
2033 		/* Retrieve Var1, and if greater than zero, increment. */
2034 		vt_key.string = "Var1";
2035 		parse_push_key(vt_key, PROP_KEY_STRING);
2036 		var1 = parse_get_integer_property();
2037 
2038 		if (var1 > 0) {
2039 			vt_value.integer = var1 + 1;
2040 			parse_put_property(vt_value, PROP_INTEGER);
2041 		}
2042 
2043 		parse_pop_key();
2044 	}
2045 
2046 	/* Convert version 3.9 fixed alts into a version 4.0 array. */
2047 	else if (strcmp(fixup, "|V390_ROOM:_Alts_|") == 0) {
2048 		parse_fixup_v390_v380_room_alts();
2049 	}
2050 
2051 	/* Error if no fixup special handler available. */
2052 	else {
2053 		sc_fatal("parse_fixup_v390: no handler for \"%s\"\n", fixup);
2054 	}
2055 
2056 	if (parse_trace)
2057 		sc_trace("Parse: leaving version 3.9 fixup %s\n", fixup);
2058 }
2059 
2060 
2061 /*
2062  * Object surface and container masks for version 3.8 object fixup, container
2063  * capacity conversion factor and default object sizing, and the count of
2064  * task movements in a version 3.8 task.
2065  */
2066 enum { V380_OBJ_IS_SURFACE = 2, V380_OBJ_IS_CONTAINER = 1 };
2067 enum { V380_OBJ_CAPACITY_MULT = 10, V380_OBJ_DEFAULT_SIZE = 2 };
2068 enum { V380_TASK_MOVEMENTS = 6 };
2069 
2070 /*
2071  * parse_fixup_v380_action()
2072  *
2073  * Helper for parse_fixup_v380(), adds a task action.
2074  */
parse_fixup_v380_action(sc_int type,sc_int var_count,sc_int var1,sc_int var2,sc_int var3)2075 static void parse_fixup_v380_action(sc_int type, sc_int var_count,
2076 		sc_int var1, sc_int var2, sc_int var3) {
2077 	sc_vartype_t vt_key, vt_value;
2078 	sc_int action_count;
2079 
2080 	/* Get a count of actions so far defined for the task. */
2081 	vt_key.string = "Actions";
2082 	parse_push_key(vt_key, PROP_KEY_STRING);
2083 	action_count = parse_get_child_count();
2084 	parse_pop_key();
2085 
2086 	/* Write actions key, reversed to emulate parse actions. */
2087 	vt_key.integer = action_count;
2088 	parse_push_key(vt_key, PROP_KEY_INTEGER);
2089 	vt_key.string = "Actions";
2090 	parse_push_key(vt_key, PROP_KEY_STRING);
2091 
2092 	/* Write new action according to the given arguments. */
2093 	vt_key.string = "Type";
2094 	parse_push_key(vt_key, PROP_KEY_STRING);
2095 	vt_value.integer = type;
2096 	parse_put_property(vt_value, PROP_INTEGER);
2097 	parse_pop_key();
2098 
2099 	vt_key.string = "Var1";
2100 	parse_push_key(vt_key, PROP_KEY_STRING);
2101 	vt_value.integer = var1;
2102 	parse_put_property(vt_value, PROP_INTEGER);
2103 	parse_pop_key();
2104 
2105 	if (var_count > 1) {
2106 		vt_key.string = "Var2";
2107 		parse_push_key(vt_key, PROP_KEY_STRING);
2108 		vt_value.integer = var2;
2109 		parse_put_property(vt_value, PROP_INTEGER);
2110 		parse_pop_key();
2111 	}
2112 
2113 	if (var_count > 2) {
2114 		vt_key.string = "Var3";
2115 		parse_push_key(vt_key, PROP_KEY_STRING);
2116 		vt_value.integer = var3;
2117 		parse_put_property(vt_value, PROP_INTEGER);
2118 		parse_pop_key();
2119 	}
2120 
2121 	parse_pop_key();
2122 	parse_pop_key();
2123 }
2124 
2125 
2126 /*
2127  * parse_fixup_v380_movement()
2128  *
2129  * Helper for parse_fixup_v380(), converts a task movement into an action.
2130  */
parse_fixup_v380_movement(sc_int mvar1,sc_int mvar2,sc_int mvar3)2131 static void parse_fixup_v380_movement(sc_int mvar1, sc_int mvar2, sc_int mvar3) {
2132 	sc_int var1;
2133 
2134 	/* If nothing was selected to move, ignore the call. */
2135 	if (mvar1 == 0)
2136 		return;
2137 
2138 	/*
2139 	 * Accept only player moves into rooms.  Other combinations, such as move
2140 	 * player to worn by player, are unlikely.  And move player to same room as
2141 	 * player isn't useful.
2142 	 */
2143 	if (mvar1 == 1) {
2144 		if (mvar3 == 0 && mvar2 >= 2)
2145 			parse_fixup_v380_action(1, 3, 0, 0, mvar2 - 2);
2146 		return;
2147 	}
2148 
2149 	/*
2150 	 * Convert movement var1 into action var1.  Var1 is the dynamic object + 3,
2151 	 * or 2 for referenced object, or 0 for all held.
2152 	 */
2153 	switch (mvar1) {
2154 	case 2:
2155 		var1 = 2;
2156 		break;                    /* Referenced obj */
2157 	case 3:
2158 		var1 = 0;
2159 		break;                    /* All held */
2160 	default:
2161 		var1 = mvar1 - 1;
2162 		break;                    /* Dynamic obj */
2163 	}
2164 
2165 	/* Dissect the rest of the movement. */
2166 	switch (mvar3) {
2167 	case 0:                    /* To room */
2168 		/*
2169 		 * Convert movement var2 into action var2 and var3.  Var2 is 0 for move
2170 		 * to room, 6 for move to player room.  Var3 is 0 for hidden, otherwise
2171 		 * the room number plus one.
2172 		 */
2173 		if (mvar2 == 0)           /* Hidden */
2174 			parse_fixup_v380_action(0, 3, var1, 0, 0);
2175 		else if (mvar2 == 1)      /* Player room */
2176 			parse_fixup_v380_action(0, 3, var1, 6, 0);
2177 		else                      /* Specified room */
2178 			parse_fixup_v380_action(0, 3, var1, 0, mvar2 - 1);
2179 		break;
2180 
2181 	case 1:                    /* To inside */
2182 	case 2:                    /* To onto */
2183 		/*
2184 		 * Convert movement var2 and var3 into action var3 and var2, a simple
2185 		 * conversion, but check that var2 is not 'not selected' first.
2186 		 */
2187 		if (mvar2 > 0)
2188 			parse_fixup_v380_action(0, 3, var1, mvar3 + 1, mvar2 - 1);
2189 		break;
2190 
2191 	case 3:                    /* To held by */
2192 	case 4:                    /* To worn by */
2193 		/*
2194 		 * Convert movement var2 and var3 into action var3 and var2, in this
2195 		 * case a simple conversion, since version 4.0 task actions are close
2196 		 * here.
2197 		 */
2198 		parse_fixup_v380_action(0, 3, var1, mvar3 + 1, mvar2);
2199 		break;
2200 
2201 	default:
2202 		sc_fatal("parse_fixup_v380_movement: invalid mvar3, %ld\n", mvar3);
2203 	}
2204 }
2205 
2206 
2207 /*
2208  * parse_fixup_v380_restr()
2209  *
2210  * Helper for parse_fixup_v380(), adds a task restriction.
2211  */
parse_fixup_v380_restr(sc_int type,sc_int var_count,sc_int var1,sc_int var2,sc_int var3,const sc_char * failmessage)2212 static void parse_fixup_v380_restr(sc_int type, sc_int var_count,
2213 		sc_int var1, sc_int var2, sc_int var3, const sc_char *failmessage) {
2214 	sc_vartype_t vt_key, vt_value;
2215 	sc_int restriction_count;
2216 
2217 	/* Get a count of restrictions so far defined for the task. */
2218 	vt_key.string = "Restrictions";
2219 	parse_push_key(vt_key, PROP_KEY_STRING);
2220 	restriction_count = parse_get_child_count();
2221 	parse_pop_key();
2222 
2223 	/* Write restrictions key, reversed to emulate parse actions. */
2224 	vt_key.integer = restriction_count;
2225 	parse_push_key(vt_key, PROP_KEY_INTEGER);
2226 	vt_key.string = "Restrictions";
2227 	parse_push_key(vt_key, PROP_KEY_STRING);
2228 
2229 	/* Write new restriction according to the given arguments. */
2230 	vt_key.string = "Type";
2231 	parse_push_key(vt_key, PROP_KEY_STRING);
2232 	vt_value.integer = type;
2233 	parse_put_property(vt_value, PROP_INTEGER);
2234 	parse_pop_key();
2235 
2236 	vt_key.string = "Var1";
2237 	parse_push_key(vt_key, PROP_KEY_STRING);
2238 	vt_value.integer = var1;
2239 	parse_put_property(vt_value, PROP_INTEGER);
2240 	parse_pop_key();
2241 
2242 	if (var_count > 1) {
2243 		vt_key.string = "Var2";
2244 		parse_push_key(vt_key, PROP_KEY_STRING);
2245 		vt_value.integer = var2;
2246 		parse_put_property(vt_value, PROP_INTEGER);
2247 		parse_pop_key();
2248 	}
2249 
2250 	if (var_count > 2) {
2251 		vt_key.string = "Var3";
2252 		parse_push_key(vt_key, PROP_KEY_STRING);
2253 		vt_value.integer = var3;
2254 		parse_put_property(vt_value, PROP_INTEGER);
2255 		parse_pop_key();
2256 	}
2257 
2258 	vt_key.string = "FailMessage";
2259 	parse_push_key(vt_key, PROP_KEY_STRING);
2260 	vt_value.string = failmessage;
2261 	parse_put_property(vt_value, PROP_STRING);
2262 	parse_pop_key();
2263 
2264 	parse_pop_key();
2265 	parse_pop_key();
2266 }
2267 
2268 
2269 /*
2270  * parse_fixup_v380_obj_restr()
2271  * parse_fixup_v380_task_restr()
2272  * parse_fixup_v380_wear_restr()
2273  * parse_fixup_v380_npc_restr()
2274  * parse_fixup_v380_objroom_restr()
2275  * parse_fixup_v380_objstate_restr()
2276  *
2277  * Helper handlers for parse_fixup_v380(); create task restrictions.
2278  */
parse_fixup_v380_obj_restr(sc_bool holding,sc_int holdobj,const sc_char * failmessage)2279 static void parse_fixup_v380_obj_restr(sc_bool holding, sc_int holdobj, const sc_char *failmessage) {
2280 	/* Ignore if no object selected. */
2281 	if (holdobj > 0) {
2282 		sc_int var1, var2;
2283 
2284 		/*
2285 		 * Create version 4.0 task restriction to check for either the
2286 		 * referenced object or a dynamic object being either held or in the
2287 		 * same room (visible to player).
2288 		 */
2289 		var1 = (holdobj == 1) ? 2 : holdobj + 1;
2290 		var2 = holding ? 1 : 3;
2291 		parse_fixup_v380_restr(0, 3, var1, var2, 0, failmessage);
2292 	}
2293 }
2294 
parse_fixup_v380_task_restr(sc_bool tasknotdone,sc_int task,const sc_char * failmessage)2295 static void parse_fixup_v380_task_restr(sc_bool tasknotdone, sc_int task, const sc_char *failmessage) {
2296 	/* Ignore if no task selected. */
2297 	if (task > 0) {
2298 		sc_int var2;
2299 
2300 		/* Create version 4.0 restriction to check task state. */
2301 		var2 = tasknotdone ? 1 : 0;
2302 		parse_fixup_v380_restr(2, 2, task, var2, 0, failmessage);
2303 	}
2304 }
2305 
parse_fixup_v380_wear_restr(sc_int wearobj,const sc_char * failmessage)2306 static void parse_fixup_v380_wear_restr(sc_int wearobj, const sc_char *failmessage) {
2307 	/* Ignore if no object selected. */
2308 	if (wearobj > 0) {
2309 		sc_vartype_t vt_key[3];
2310 		sc_int object_count, object, dynamic, obj_index;
2311 
2312 		/*
2313 		 * Create version 4.0 restrictions for something or nothing worn by
2314 		 * player.
2315 		 */
2316 		if (wearobj == 1) {
2317 			parse_fixup_v380_restr(0, 3, 1, 2, 0, failmessage);
2318 			return;
2319 		} else if (wearobj == 2) {
2320 			parse_fixup_v380_restr(0, 3, 0, 2, 0, failmessage);
2321 			return;
2322 		}
2323 
2324 		/* Get the count of objects defined. */
2325 		vt_key[0].string = "Objects";
2326 		object_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
2327 
2328 		/* Convert wearobj from worn index to object index. */
2329 		wearobj -= 2;
2330 		for (object = 0; object < object_count && wearobj > 0; object++) {
2331 			sc_bool bstatic, wearable;
2332 
2333 			vt_key[1].integer = object;
2334 			vt_key[2].string = "Static";
2335 			bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2336 			if (!bstatic) {
2337 				vt_key[2].string = "Wearable";
2338 				wearable = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2339 				if (wearable)
2340 					wearobj--;
2341 			}
2342 		}
2343 		obj_index = object - 1;
2344 
2345 		/* Now convert wearobj from object index to dynamic index. */
2346 		dynamic = 0;
2347 		for (object = 0; object <= obj_index; object++) {
2348 			sc_bool bstatic;
2349 
2350 			vt_key[1].integer = object;
2351 			vt_key[2].string = "Static";
2352 			bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2353 			if (!bstatic)
2354 				dynamic++;
2355 		}
2356 		dynamic--;
2357 
2358 		/* Create version 4.0 restriction for object worn by player. */
2359 		parse_fixup_v380_restr(0, 3, dynamic + 3, 2, 0, failmessage);
2360 	}
2361 }
2362 
parse_fixup_v380_npc_restr(sc_bool notinsameroom,sc_int npc,const sc_char * failmessage)2363 static void parse_fixup_v380_npc_restr(sc_bool notinsameroom, sc_int npc,
2364 		const sc_char *failmessage) {
2365 	/* Ignore if no NPC selected. */
2366 	if (npc > 0) {
2367 		sc_int var2;
2368 
2369 		if (npc == 1) {
2370 			/* Create restriction to look for alone, or not. */
2371 			var2 = notinsameroom ? 3 : 2;
2372 			parse_fixup_v380_restr(3, 3, 0, var2, 0, failmessage);
2373 			return;
2374 		}
2375 
2376 		/* Create restriction to look for company. */
2377 		var2 = notinsameroom ? 1 : 0;
2378 		parse_fixup_v380_restr(3, 3, 0, var2, npc, failmessage);
2379 	}
2380 }
2381 
parse_fixup_v380_objroom_restr(sc_int obj,sc_int objroom,const sc_char * failmessage)2382 static void parse_fixup_v380_objroom_restr(sc_int obj, sc_int objroom, const sc_char *failmessage) {
2383 	/* Ignore if no object selected. */
2384 	if (obj > 0) {
2385 		/* Create version 4.0 restriction to check object in room. */
2386 		parse_fixup_v380_restr(0, 3, obj + 1, 0, objroom, failmessage);
2387 	}
2388 }
2389 
parse_fixup_v380_objstate_restr(sc_int obj,sc_int ivar1,sc_int ivar2,const sc_char * failmessage)2390 static void parse_fixup_v380_objstate_restr(sc_int obj, sc_int ivar1, sc_int ivar2,
2391 		const sc_char *failmessage) {
2392 	sc_vartype_t vt_key[3];
2393 	sc_int object, dynamic, var2, var3;
2394 
2395 	/* Initialize variables to avoid gcc warnings. */
2396 	var2 = -1;
2397 	var3 = -1;
2398 
2399 	/* Ignore restrictions with no "type". */
2400 	if (ivar1 == 0)
2401 		return;
2402 
2403 	/* Look for opened/closed restrictions, convert and return. */
2404 	if (ivar1 == 3 || ivar1 == 4) {
2405 		sc_int stateful;
2406 
2407 		/* Convert obj from object to openable (stateful) index. */
2408 		stateful = 0;
2409 		for (object = 0; object <= obj - 1; object++) {
2410 			sc_int openable;
2411 
2412 			vt_key[0].string = "Objects";
2413 			vt_key[1].integer = object;
2414 			vt_key[2].string = "Openable";
2415 			openable = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2416 			if (openable > 0)
2417 				stateful++;
2418 		}
2419 		stateful--;
2420 
2421 		/*
2422 		 * Create a version 4.0 restriction that checks that an object's state
2423 		 * is open (var2 = 0) or closed (var2 = 1).
2424 		 */
2425 		var2 = (ivar1 == 3) ? 0 : 1;
2426 		parse_fixup_v380_restr(1, 2, stateful + 1, var2, 0, failmessage);
2427 		return;
2428 	}
2429 
2430 	/* Convert obj from object to dynamic index. */
2431 	dynamic = 0;
2432 	for (object = 0; object <= obj - 1; object++) {
2433 		sc_bool bstatic;
2434 
2435 		vt_key[0].string = "Objects";
2436 		vt_key[1].integer = object;
2437 		vt_key[2].string = "Static";
2438 		bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2439 		if (!bstatic)
2440 			dynamic++;
2441 	}
2442 	dynamic--;
2443 
2444 	/* Create version 4.0 object location restrictions for the rest. */
2445 	switch (ivar1) {
2446 	case 1:
2447 		var2 = 4;
2448 		var3 = ivar2;
2449 		break;                    /* Inside */
2450 	case 2:
2451 		var2 = 5;
2452 		var3 = ivar2;
2453 		break;                    /* On */
2454 	case 5:
2455 		var2 = 1;
2456 		var3 = ivar2 + 1;
2457 		break;                    /* Held by */
2458 	case 6:
2459 		var2 = 2;
2460 		var3 = ivar2 + 1;
2461 		break;                    /* Worn by */
2462 	default:
2463 		sc_fatal("parse_fixup_v380_objstate_restr: invalid ivar1, %ld\n", ivar1);
2464 	}
2465 	parse_fixup_v380_restr(0, 3, dynamic + 3, var2, var3, failmessage);
2466 }
2467 
2468 
2469 /*
2470  * parse_fixup_v380()
2471  *
2472  * Handler for fixup special items to help with conversions from TAF version
2473  * 3.8 format into version 4.0.
2474  */
parse_fixup_v380(const sc_char * fixup)2475 static void parse_fixup_v380(const sc_char *fixup) {
2476 	if (parse_trace)
2477 		sc_trace("Parse: entering version 3.8 fixup %s\n", fixup);
2478 
2479 	/* Convert container capacity attributes to version 4.0 values. */
2480 	if (strcmp(fixup, "|V380_OBJECT:#Capacity*10+2|") == 0) {
2481 		sc_vartype_t vt_key, vt_value;
2482 		sc_int surfacecontainer;
2483 
2484 		/* Get the object surface and container attributes. */
2485 		vt_key.string = "SurfaceContainer";
2486 		parse_push_key(vt_key, PROP_KEY_STRING);
2487 		surfacecontainer = parse_get_integer_property();
2488 		parse_pop_key();
2489 
2490 		/* Convert capacity from version 3.8 format to version 4.0. */
2491 		if (surfacecontainer == V380_OBJ_IS_CONTAINER) {
2492 			sc_int capacity;
2493 
2494 			vt_key.string = "Capacity";
2495 			parse_push_key(vt_key, PROP_KEY_STRING);
2496 			capacity = parse_get_integer_property();
2497 
2498 			capacity = capacity * V380_OBJ_CAPACITY_MULT + V380_OBJ_DEFAULT_SIZE;
2499 
2500 			vt_value.integer = capacity;
2501 			parse_put_property(vt_value, PROP_INTEGER);
2502 			parse_pop_key();
2503 		}
2504 	}
2505 
2506 	/*
2507 	 * Exchange openable values 5 and 6, watch for a possible 1 from a 3.8 game
2508 	 * (interpret as 0), and write -1 key for openable objects.
2509 	 */
2510 	else if (strcmp(fixup, "|V380_OBJECT:_Openable_,Key|") == 0) {
2511 		sc_vartype_t vt_key, vt_value;
2512 		sc_int openable;
2513 
2514 		/* Retrieve Openable, and if 5 or 6, exchange. */
2515 		vt_key.string = "Openable";
2516 		parse_push_key(vt_key, PROP_KEY_STRING);
2517 		openable = parse_get_integer_property();
2518 
2519 		if (openable == 5 || openable == 6) {
2520 			vt_value.integer = (openable == 5) ? 6 : 5;
2521 			parse_put_property(vt_value, PROP_INTEGER);
2522 		}
2523 
2524 		/* If the odd value of 1, rewrite as zero. */
2525 		else if (openable == 1) {
2526 			vt_value.integer = 0;
2527 			parse_put_property(vt_value, PROP_INTEGER);
2528 		}
2529 
2530 		parse_pop_key();
2531 
2532 		/* For openable objects, store a Key of -1. */
2533 		if (openable == 5 || openable == 6) {
2534 			vt_key.string = "Key";
2535 			parse_push_key(vt_key, PROP_KEY_STRING);
2536 			vt_value.integer = -1;
2537 			parse_put_property(vt_value, PROP_INTEGER);
2538 			parse_pop_key();
2539 		}
2540 	}
2541 
2542 	/* Create version 4.0 task actions from a version 3.8 task. */
2543 	else if (strcmp(fixup, "|V380_TASK:_Actions_|") == 0) {
2544 		sc_vartype_t vt_key;
2545 		sc_int score;
2546 		sc_bool killsplayer, wingame;
2547 		sc_int movement;
2548 
2549 		/* Retrieve the score change for the task. */
2550 		vt_key.string = "Score";
2551 		parse_push_key(vt_key, PROP_KEY_STRING);
2552 		score = parse_get_integer_property();
2553 		parse_pop_key();
2554 
2555 		/* Create any appropriate score change action. */
2556 		if (score != 0)
2557 			parse_fixup_v380_action(4, 1, score, 0, 0);
2558 
2559 		/* Get player death and game winning flags. */
2560 		vt_key.string = "KillsPlayer";
2561 		parse_push_key(vt_key, PROP_KEY_STRING);
2562 		killsplayer = parse_get_boolean_property();
2563 		parse_pop_key();
2564 		vt_key.string = "WinGame";
2565 		parse_push_key(vt_key, PROP_KEY_STRING);
2566 		wingame = parse_get_boolean_property();
2567 		parse_pop_key();
2568 
2569 		/* Create any appropriate game ending actions. */
2570 		if (killsplayer)
2571 			parse_fixup_v380_action(6, 1, 2, 0, 0);
2572 		if (wingame)
2573 			parse_fixup_v380_action(6, 1, 0, 0, 0);
2574 
2575 		/* Handle each defined movement for the task. */
2576 		for (movement = 0; movement < V380_TASK_MOVEMENTS; movement++) {
2577 			sc_int mvar1, mvar2, mvar3;
2578 
2579 			vt_key.integer = movement;
2580 			parse_push_key(vt_key, PROP_KEY_INTEGER);
2581 			vt_key.string = "Movements";
2582 			parse_push_key(vt_key, PROP_KEY_STRING);
2583 
2584 			/* Retrieve the movement parameters. */
2585 			vt_key.string = "Var1";
2586 			parse_push_key(vt_key, PROP_KEY_STRING);
2587 			mvar1 = parse_get_integer_property();
2588 			parse_pop_key();
2589 			vt_key.string = "Var2";
2590 			parse_push_key(vt_key, PROP_KEY_STRING);
2591 			mvar2 = parse_get_integer_property();
2592 			parse_pop_key();
2593 			vt_key.string = "Var3";
2594 			parse_push_key(vt_key, PROP_KEY_STRING);
2595 			mvar3 = parse_get_integer_property();
2596 			parse_pop_key();
2597 
2598 			parse_pop_key();
2599 			parse_pop_key();
2600 
2601 			/* Create the corresponding task action. */
2602 			parse_fixup_v380_movement(mvar1, mvar2, mvar3);
2603 		}
2604 	}
2605 
2606 	/* Create version 4.0 task restrictions from a version 3.8 task. */
2607 	else if (strcmp(fixup, "|V380_TASK:_Restrictions_|") == 0) {
2608 		sc_vartype_t vt_key, vt_value;
2609 		sc_bool holding, tasknotdone, notinsameroom;
2610 		sc_int holdobj1, holdobj2, holdobj3, task;
2611 		sc_int wearobj1, wearobj2, npc, obj1, obj1room, obj2;
2612 		const sc_char *holdmsg, *taskmsg, *wearmsg, *companymsg;
2613 		const sc_char *obj1msg;
2614 		sc_int restriction_count;
2615 
2616 		/* Create restrictions for objects not held or absent. */
2617 		vt_key.string = "HoldingSameRoom";
2618 		parse_push_key(vt_key, PROP_KEY_STRING);
2619 		holding = parse_get_boolean_property();
2620 		parse_pop_key();
2621 
2622 		vt_key.string = "HoldObj1";
2623 		parse_push_key(vt_key, PROP_KEY_STRING);
2624 		holdobj1 = parse_get_integer_property();
2625 		parse_pop_key();
2626 
2627 		vt_key.string = "HoldObj2";
2628 		parse_push_key(vt_key, PROP_KEY_STRING);
2629 		holdobj2 = parse_get_integer_property();
2630 		parse_pop_key();
2631 
2632 		vt_key.string = "HoldObj3";
2633 		parse_push_key(vt_key, PROP_KEY_STRING);
2634 		holdobj3 = parse_get_integer_property();
2635 		parse_pop_key();
2636 
2637 		vt_key.string = "HoldMsg";
2638 		parse_push_key(vt_key, PROP_KEY_STRING);
2639 		holdmsg = parse_get_string_property();
2640 		parse_pop_key();
2641 
2642 		parse_fixup_v380_obj_restr(holding, holdobj1, holdmsg);
2643 		parse_fixup_v380_obj_restr(holding, holdobj2, holdmsg);
2644 		parse_fixup_v380_obj_restr(holding, holdobj3, holdmsg);
2645 
2646 		/* Create any task state restriction. */
2647 		vt_key.string = "TaskNotDone";
2648 		parse_push_key(vt_key, PROP_KEY_STRING);
2649 		tasknotdone = parse_get_boolean_property();
2650 		parse_pop_key();
2651 
2652 		vt_key.string = "Task";
2653 		parse_push_key(vt_key, PROP_KEY_STRING);
2654 		task = parse_get_integer_property();
2655 		parse_pop_key();
2656 
2657 		vt_key.string = "TaskMsg";
2658 		parse_push_key(vt_key, PROP_KEY_STRING);
2659 		taskmsg = parse_get_string_property();
2660 		parse_pop_key();
2661 
2662 		parse_fixup_v380_task_restr(tasknotdone, task, taskmsg);
2663 
2664 		/* Create any object not worn restrictions. */
2665 		vt_key.string = "WearObj1";
2666 		parse_push_key(vt_key, PROP_KEY_STRING);
2667 		wearobj1 = parse_get_integer_property();
2668 		parse_pop_key();
2669 
2670 		vt_key.string = "WearObj2";
2671 		parse_push_key(vt_key, PROP_KEY_STRING);
2672 		wearobj2 = parse_get_integer_property();
2673 		parse_pop_key();
2674 
2675 		vt_key.string = "WearMsg";
2676 		parse_push_key(vt_key, PROP_KEY_STRING);
2677 		wearmsg = parse_get_string_property();
2678 		parse_pop_key();
2679 
2680 		parse_fixup_v380_wear_restr(wearobj1, wearmsg);
2681 		parse_fixup_v380_wear_restr(wearobj2, wearmsg);
2682 
2683 		/* Check for presence/absence of NPCs restriction. */
2684 		vt_key.string = "NotInSameRoom";
2685 		parse_push_key(vt_key, PROP_KEY_STRING);
2686 		notinsameroom = parse_get_boolean_property();
2687 		parse_pop_key();
2688 
2689 		vt_key.string = "NPC";
2690 		parse_push_key(vt_key, PROP_KEY_STRING);
2691 		npc = parse_get_integer_property();
2692 		parse_pop_key();
2693 
2694 		vt_key.string = "CompanyMsg";
2695 		parse_push_key(vt_key, PROP_KEY_STRING);
2696 		companymsg = parse_get_string_property();
2697 		parse_pop_key();
2698 
2699 		parse_fixup_v380_npc_restr(notinsameroom, npc, companymsg);
2700 
2701 		/* Create any object location restriction. */
2702 		vt_key.string = "Obj1";
2703 		parse_push_key(vt_key, PROP_KEY_STRING);
2704 		obj1 = parse_get_integer_property();
2705 		parse_pop_key();
2706 
2707 		vt_key.string = "Obj1Room";
2708 		parse_push_key(vt_key, PROP_KEY_STRING);
2709 		obj1room = parse_get_integer_property();
2710 		parse_pop_key();
2711 
2712 		vt_key.string = "Obj1Msg";
2713 		parse_push_key(vt_key, PROP_KEY_STRING);
2714 		obj1msg = parse_get_string_property();
2715 		parse_pop_key();
2716 
2717 		parse_fixup_v380_objroom_restr(obj1, obj1room, obj1msg);
2718 
2719 		/* And finally, any object state restriction. */
2720 		vt_key.string = "Obj2";
2721 		parse_push_key(vt_key, PROP_KEY_STRING);
2722 		obj2 = parse_get_integer_property();
2723 		parse_pop_key();
2724 
2725 		if (obj2 > 0) {
2726 			sc_int var1, var2;
2727 			const sc_char *obj2msg;
2728 
2729 			vt_key.string = "Obj2Var1";
2730 			parse_push_key(vt_key, PROP_KEY_STRING);
2731 			var1 = parse_get_integer_property();
2732 			parse_pop_key();
2733 
2734 			vt_key.string = "Obj2Var2";
2735 			parse_push_key(vt_key, PROP_KEY_STRING);
2736 			var2 = parse_get_integer_property();
2737 			parse_pop_key();
2738 
2739 			vt_key.string = "Obj2Msg";
2740 			parse_push_key(vt_key, PROP_KEY_STRING);
2741 			obj2msg = parse_get_string_property();
2742 			parse_pop_key();
2743 
2744 			parse_fixup_v380_objstate_restr(obj2, var1, var2, obj2msg);
2745 		}
2746 
2747 		/* Get a count of restrictions created. */
2748 		vt_key.string = "Restrictions";
2749 		parse_push_key(vt_key, PROP_KEY_STRING);
2750 		restriction_count = parse_get_child_count();
2751 		parse_pop_key();
2752 
2753 		/* Allocate and fill a new mask for these restrictions. */
2754 		if (restriction_count > 0) {
2755 			sc_char *restrmask;
2756 			sc_int index_;
2757 
2758 			restrmask = (sc_char *)sc_malloc(2 * restriction_count);
2759 			strcpy(restrmask, "#");
2760 			for (index_ = 1; index_ < restriction_count; index_++)
2761 				strcat(restrmask, "A#");
2762 
2763 			vt_key.string = "RestrMask";
2764 			parse_push_key(vt_key, PROP_KEY_STRING);
2765 			vt_value.string = restrmask;
2766 			parse_put_property(vt_value, PROP_STRING);
2767 			parse_pop_key();
2768 
2769 			prop_adopt(parse_bundle, restrmask);
2770 		}
2771 	}
2772 
2773 	/*
2774 	 * Adjust dynamic object initial positions and parents (where contained
2775 	 * or on surfaces) into version 4.0 range.
2776 	 */
2777 	else if (strcmp(fixup, "|V380_OBJECT:_InitialPositions_|") == 0) {
2778 		sc_vartype_t vt_key[3];
2779 		sc_int object_count, object, *object_type;
2780 
2781 		/* Get a count of objects. */
2782 		vt_key[0].string = "Objects";
2783 		object_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
2784 
2785 		/* Build an array of object container/surface types. */
2786 		object_type = (sc_int *)sc_malloc(object_count * sizeof(*object_type));
2787 		for (object = 0; object < object_count; object++) {
2788 			vt_key[1].integer = object;
2789 			vt_key[2].string = "SurfaceContainer";
2790 			object_type[object] = prop_get_integer(parse_bundle,
2791 			                                       "I<-sis", vt_key);
2792 		}
2793 
2794 		/* Adjust each object's initial position if necessary. */
2795 		for (object = 0; object < object_count; object++) {
2796 			sc_vartype_t vt_value;
2797 			sc_bool is_static;
2798 			sc_int initialposition;
2799 
2800 			/* Ignore static objects; we only want dynamic ones. */
2801 			vt_key[1].integer = object;
2802 			vt_key[2].string = "Static";
2803 			is_static = prop_get_boolean(parse_bundle, "B<-sis", vt_key);
2804 			if (is_static)
2805 				continue;
2806 
2807 			/* If initial position is above on/in, increment. */
2808 			vt_key[1].integer = object;
2809 			vt_key[2].string = "InitialPosition";
2810 			initialposition = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2811 			if (initialposition > 2) {
2812 				vt_value.integer = initialposition + 1;
2813 				prop_put(parse_bundle, "I->sis", vt_value, vt_key);
2814 			}
2815 
2816 			/*
2817 			 * If initial position is on or in, decide which, depending on the
2818 			 * type of the parent.  From this, expand initial position into a
2819 			 * version 4.0 value.
2820 			 */
2821 			if (initialposition == 2) {
2822 				sc_int count, parent, index_;
2823 
2824 				/* Get parent container/surface index. */
2825 				vt_key[1].integer = object;
2826 				vt_key[2].string = "Parent";
2827 				count = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2828 
2829 				/* Convert container/surface index. */
2830 				for (parent = 0; parent < object_count && count >= 0; parent++) {
2831 					if (object_type[parent] == V380_OBJ_IS_CONTAINER
2832 					        || object_type[parent] == V380_OBJ_IS_SURFACE)
2833 						count--;
2834 				}
2835 				parent--;
2836 
2837 				/* If parent is a surface, adjust position. */
2838 				if (object_type[parent] == V380_OBJ_IS_SURFACE) {
2839 					vt_key[2].string = "InitialPosition";
2840 					vt_value.integer = initialposition + 1;
2841 					prop_put(parse_bundle, "I->sis", vt_value, vt_key);
2842 				}
2843 
2844 				/*
2845 				 * For both, adjust parent to be an object index for that type
2846 				 * of object only.
2847 				 */
2848 				count = 0;
2849 				for (index_ = 0; index_ < parent; index_++) {
2850 					if (object_type[index_] == object_type[parent])
2851 						count++;
2852 				}
2853 				vt_key[2].string = "Parent";
2854 				vt_value.integer = count;
2855 				prop_put(parse_bundle, "I->sis", vt_value, vt_key);
2856 			}
2857 		}
2858 
2859 		/* Done with temporary array. */
2860 		sc_free(object_type);
2861 	}
2862 
2863 	/* Convert carry limit into version 4.0-like size and weight limits. */
2864 	else if (strcmp(fixup, "|V380_MaxSize_MaxWt_|") == 0) {
2865 		sc_vartype_t vt_key, vt_value;
2866 		sc_int maxcarried;
2867 
2868 		vt_key.string = "MaxCarried";
2869 		parse_push_key(vt_key, PROP_KEY_STRING);
2870 		maxcarried = parse_get_integer_property();
2871 		parse_pop_key();
2872 
2873 		vt_value.integer = maxcarried * V380_OBJ_CAPACITY_MULT
2874 		                   + V380_OBJ_DEFAULT_SIZE;
2875 
2876 		vt_key.string = "MaxSize";
2877 		parse_push_key(vt_key, PROP_KEY_STRING);
2878 		parse_put_property(vt_value, PROP_INTEGER);
2879 		parse_pop_key();
2880 
2881 		vt_key.string = "MaxWt";
2882 		parse_push_key(vt_key, PROP_KEY_STRING);
2883 		parse_put_property(vt_value, PROP_INTEGER);
2884 		parse_pop_key();
2885 	}
2886 
2887 	/* Add up positive scoring tasks to arrive at max score. */
2888 	else if (strcmp(fixup, "|V380_GLOBAL:_MaxScore_|") == 0) {
2889 		sc_vartype_t vt_key[3], vt_value;
2890 		sc_int task_count, maxscore, task;
2891 
2892 		/* Get a count of tasks. */
2893 		vt_key[0].string = "Tasks";
2894 		task_count = prop_get_child_count(parse_bundle, "I<-s", vt_key);
2895 
2896 		/* Sum positive scoring tasks. */
2897 		maxscore = 0;
2898 		for (task = 0; task < task_count; task++) {
2899 			sc_int score;
2900 
2901 			vt_key[1].integer = task;
2902 			vt_key[2].string = "Score";
2903 			score = prop_get_integer(parse_bundle, "I<-sis", vt_key);
2904 			if (score > 0)
2905 				maxscore += score;
2906 		}
2907 
2908 		/* Write MaxScore global property. */
2909 		vt_key[0].string = "Globals";
2910 		vt_key[1].string = "MaxScore";
2911 		vt_value.integer = maxscore;
2912 		prop_put(parse_bundle, "I->ss", vt_value, vt_key);
2913 	}
2914 
2915 	/* Convert walk meetobject from dynamic index to object. */
2916 	else if (strcmp(fixup, "|V380_WALK:_MeetObject_|") == 0) {
2917 		sc_vartype_t vt_key, vt_value, vt_gkey[3];
2918 		sc_int meetobject, count, object_count, object;
2919 
2920 		vt_key.string = "MeetObject";
2921 		parse_push_key(vt_key, PROP_KEY_STRING);
2922 		meetobject = parse_get_integer_property();
2923 
2924 		/* Get a count of objects. */
2925 		vt_gkey[0].string = "Objects";
2926 		object_count = prop_get_child_count(parse_bundle, "I<-s", vt_gkey);
2927 
2928 		/* Convert dynamic index to object, and rewrite. */
2929 		count = meetobject - 1;
2930 		for (object = 0; object < object_count && count >= 0; object++) {
2931 			sc_bool bstatic;
2932 
2933 			vt_gkey[1].integer = object;
2934 			vt_gkey[2].string = "Static";
2935 			bstatic = prop_get_boolean(parse_bundle, "B<-sis", vt_gkey);
2936 			if (!bstatic)
2937 				count--;
2938 		}
2939 		object--;
2940 
2941 		vt_value.integer = object;
2942 		parse_put_property(vt_value, PROP_INTEGER);
2943 		parse_pop_key();
2944 	}
2945 
2946 	/* Convert version 3.8 room data into a version 4.0 alts array. */
2947 	else if (strcmp(fixup, "|V380_ROOM:_Alts_|") == 0) {
2948 		parse_fixup_v390_v380_room_alts();
2949 	}
2950 
2951 	/* Error if no fixup special handler available. */
2952 	else {
2953 		sc_fatal("parse_fixup_v380: no handler for \"%s\"\n", fixup);
2954 	}
2955 
2956 	if (parse_trace)
2957 		sc_trace("Parse: leaving version 3.8 fixup %s\n", fixup);
2958 }
2959 
2960 
2961 /*
2962  * parse_fixup()
2963  *
2964  * Handler for fixup special items to help with conversions from TAF version
2965  * 3.9 and version 3.8 formats into version 4.0.
2966  */
parse_fixup(CONTEXT,const sc_char * fixup)2967 static void parse_fixup(CONTEXT, const sc_char *fixup) {
2968 	/*
2969 	 * Pick a fixup handler specific to the TAF version.  This helps keep
2970 	 * fixup code separate, rather than glommed into one large function.
2971 	 */
2972 	switch (taf_get_version(parse_taf)) {
2973 	case TAF_VERSION_400:
2974 		sc_fatal("parse_fixup: unexpected call\n");
2975 		break;
2976 	case TAF_VERSION_390:
2977 		CALL1(parse_fixup_v390, fixup);
2978 		break;
2979 	case TAF_VERSION_380:
2980 		parse_fixup_v380(fixup);
2981 		break;
2982 	default:
2983 		sc_fatal("parse_fixup: invalid TAF file version\n");
2984 		break;
2985 	}
2986 }
2987 
2988 
2989 /*
2990  * parse_element()
2991  *
2992  * Parse a class descriptor element.
2993  */
parse_element(CONTEXT,const sc_char * element)2994 static void parse_element(CONTEXT, const sc_char *element) {
2995 	if (parse_trace)
2996 		sc_trace("Parse: entering element %s\n", element);
2997 
2998 	/* Determine the element type from the first character. */
2999 	switch (element[0]) {
3000 	case PARSE_ARRAY:
3001 		CALL1(parse_array, element);
3002 		break;
3003 	case PARSE_VECTOR:
3004 		CALL1(parse_vector, element);
3005 		break;
3006 	case PARSE_VECTOR_ALTERNATE:
3007 		CALL1(parse_vector_alternate, element);
3008 		break;
3009 	case PARSE_CLASS:
3010 		CALL1(parse_class, element);
3011 		break;
3012 	case PARSE_EXPRESSION:
3013 		CALL1(parse_expression, element);
3014 		break;
3015 	case PARSE_SPECIAL:
3016 		CALL1(parse_special, element);
3017 		break;
3018 	case PARSE_FIXUP:
3019 		CALL1(parse_fixup, element);
3020 		break;
3021 
3022 	case PARSE_INTEGER:
3023 	case PARSE_DEFAULT_ZERO:
3024 	case PARSE_BOOLEAN:
3025 	case PARSE_DEFAULT_TRUE:
3026 	case PARSE_DEFAULT_FALSE:
3027 	case PARSE_STRING:
3028 	case PARSE_DEFAULT_EMPTY:
3029 	case PARSE_IGNORE_INTEGER:
3030 	case PARSE_IGNORE_BOOLEAN:
3031 	case PARSE_IGNORE_STRING:
3032 	case PARSE_MULTILINE:
3033 		CALL1(parse_terminal, element);
3034 		break;
3035 	default:
3036 		sc_fatal("parse_element: bad type, %c\n", element[0]);
3037 	}
3038 
3039 	if (parse_trace)
3040 		sc_trace("Parse: leaving element %s\n", element);
3041 }
3042 
3043 
3044 /*
3045  * parse_descriptor()
3046  *
3047  * Parse a class's properties descriptor list.
3048  */
parse_descriptor(CONTEXT,const sc_char * descriptor)3049 static void parse_descriptor(CONTEXT, const sc_char *descriptor) {
3050 	sc_int next;
3051 
3052 	/* Find and parse each element in the descriptor. */
3053 	for (next = 0; descriptor[next] != NUL;) {
3054 		sc_char element[PARSE_TEMP_LENGTH];
3055 
3056 		/* Isolate the next descriptor element. */
3057 		if (sscanf(descriptor + next, "%[^ ]", element) != 1)
3058 			sc_fatal("parse_element: no element, %s\n", descriptor + next);
3059 
3060 		/* Parse this isolated element. */
3061 		CALL1(parse_element, element);
3062 
3063 		/* Advance over the element and any trailing whitespace. */
3064 		next += strlen(element);
3065 		next += strspn(descriptor + next, " ");
3066 	}
3067 }
3068 
3069 
3070 /*
3071  * parse_class()
3072  *
3073  * Parse a class of properties.
3074  */
parse_class(CONTEXT,const sc_char * class_)3075 static void parse_class(CONTEXT, const sc_char *class_) {
3076 	sc_char class_name[PARSE_TEMP_LENGTH];
3077 	sc_int index_;
3078 	sc_vartype_t vt_key;
3079 
3080 	/* Isolate the class name. */
3081 	if (sscanf(class_, "<%[^>]", class_name) != 1)
3082 		sc_fatal("parse_class: error in class, %s\n", class_);
3083 	if (parse_trace)
3084 		sc_trace("Parse: entering class %s\n", class_name);
3085 
3086 	/* Find the class in the parse schema, and fail if not found. */
3087 	for (index_ = 0; parse_schema[index_].class_name; index_++) {
3088 		if (strcmp(parse_schema[index_].class_name, class_name) == 0)
3089 			break;
3090 	}
3091 	if (!parse_schema[index_].class_name)
3092 		sc_fatal("parse_class: class not described, %s\n", class_name);
3093 
3094 	/*
3095 	 * Unless we are at the top level of the parse schema, push the class tag
3096 	 * as a key.  The top level is "_GAME_", index_ 0, and isn't part of key
3097 	 * formation.
3098 	 */
3099 	if (index_ > 0) {
3100 		vt_key.string = class_ + strlen(class_name) + 2;
3101 		parse_push_key(vt_key, PROP_KEY_STRING);
3102 	}
3103 
3104 	/* Parse each element in the descriptor. */
3105 	CALL1(parse_descriptor, parse_schema[index_].descriptor);
3106 
3107 	/* Pop a key if the class tag was pushed above. */
3108 	if (index_ > 0)
3109 		parse_pop_key();
3110 
3111 	if (parse_trace)
3112 		sc_trace("Parse: leaving class %s\n", class_name);
3113 }
3114 
3115 
3116 /*
3117  * parse_add_walkalerts()
3118  *
3119  * Add a list of all NPC walks started by each task.  This is post-processing
3120  * that occurs after the TAF file has been successfully parsed.
3121  */
parse_add_walkalerts(sc_prop_setref_t bundle)3122 static void parse_add_walkalerts(sc_prop_setref_t bundle) {
3123 	sc_vartype_t vt_key[5];
3124 	sc_int npcs_count, npc;
3125 
3126 	/* Get the count of NPCs. */
3127 	vt_key[0].string = "NPCs";
3128 	npcs_count = prop_get_child_count(bundle, "I<-s", vt_key);
3129 
3130 	/* Set up each NPC. */
3131 	for (npc = 0; npc < npcs_count; npc++) {
3132 		sc_int walk_count, walk;
3133 
3134 		/* Get NPC walk details. */
3135 		vt_key[1].integer = npc;
3136 		vt_key[2].string = "Walks";
3137 		walk_count = prop_get_child_count(bundle, "I<-sis", vt_key);
3138 
3139 		for (walk = 0; walk < walk_count; walk++) {
3140 			sc_int starttask;
3141 
3142 			/* Get start task of walk. */
3143 			vt_key[3].integer = walk;
3144 			vt_key[4].string = "StartTask";
3145 			starttask = prop_get_integer(bundle, "I<-sisis", vt_key) - 1;
3146 			if (starttask >= 0) {
3147 				sc_vartype_t vt_key2[4], vt_value;
3148 				sc_int count;
3149 
3150 				/* Count existing walkalerts for the task. */
3151 				vt_key2[0].string = "Tasks";
3152 				vt_key2[1].integer = starttask;
3153 				vt_key2[2].string = "NPCWalkAlert";
3154 				count = prop_get_child_count(bundle, "I<-sis", vt_key2);
3155 
3156 				/* Add two more -- NPC and walk. */
3157 				vt_key2[3].integer = count;
3158 				vt_value.integer = npc;
3159 				prop_put(bundle, "I->sisi", vt_value, vt_key2);
3160 				vt_key2[3].integer = count + 1;
3161 				vt_value.integer = walk;
3162 				prop_put(bundle, "I->sisi", vt_value, vt_key2);
3163 			}
3164 		}
3165 	}
3166 }
3167 
3168 
3169 /*
3170  * parse_add_movetimes()
3171  *
3172  * Add a list of move times to all NPC walks.  This is post-processing that
3173  * occurs after the TAF file has been successfully parsed.
3174  */
parse_add_movetimes(sc_prop_setref_t bundle)3175 static void parse_add_movetimes(sc_prop_setref_t bundle) {
3176 	sc_vartype_t vt_key[6];
3177 	sc_int npcs_count, npc;
3178 
3179 	/* Get the count of NPCs. */
3180 	vt_key[0].string = "NPCs";
3181 	npcs_count = prop_get_child_count(bundle, "I<-s", vt_key);
3182 
3183 	/* Set up each NPC. */
3184 	for (npc = 0; npc < npcs_count; npc++) {
3185 		sc_int walk_count, walk;
3186 
3187 		/* Get NPC walk details. */
3188 		vt_key[1].integer = npc;
3189 		vt_key[2].string = "Walks";
3190 		walk_count = prop_get_child_count(bundle, "I<-sis", vt_key);
3191 
3192 		for (walk = 0; walk < walk_count; walk++) {
3193 			sc_int waittimes;
3194 			sc_int *movetimes, index_;
3195 			sc_vartype_t vt_value;
3196 
3197 			vt_key[3].integer = walk;
3198 			vt_key[4].string = "Times";
3199 			waittimes = prop_get_child_count(bundle, "I<-sisis", vt_key);
3200 
3201 			movetimes = (sc_int *)sc_malloc((waittimes + 1) * sizeof(*movetimes));
3202 			memset(movetimes, 0, (waittimes + 1) * sizeof(*movetimes));
3203 			for (index_ = waittimes - 1; index_ >= 0; index_--) {
3204 				vt_key[4].string = "Times";
3205 				vt_key[5].integer = index_;
3206 				movetimes[index_] = prop_get_integer(bundle, "I<-sisisi", vt_key)
3207 				                    + movetimes[index_ + 1];
3208 			}
3209 			movetimes[waittimes] = -2;
3210 
3211 			for (index_ = 0; index_ <= waittimes; index_++) {
3212 				vt_key[4].string = "MoveTimes";
3213 				vt_key[5].integer = index_;
3214 				vt_value.integer = movetimes[index_];
3215 				prop_put(bundle, "I->sisisi", vt_value, vt_key);
3216 			}
3217 			sc_free(movetimes);
3218 		}
3219 	}
3220 }
3221 
3222 
3223 /*
3224  * parse_add_alrs_index()
3225  *
3226  * Sort ALRs by original string length and store an indexer property, so
3227  * that ALR replacements look at longer strings before shorter ones.
3228  */
parse_add_alrs_index(sc_prop_setref_t bundle)3229 static void parse_add_alrs_index(sc_prop_setref_t bundle) {
3230 	sc_vartype_t vt_key[3];
3231 	sc_int alr_count, index_, alr;
3232 	sc_int *alr_lengths, longest, shortest, length;
3233 
3234 	/* Count ALRs, and set invariant part of properties key. */
3235 	vt_key[0].string = "ALRs";
3236 	alr_count = prop_get_child_count(bundle, "I<-s", vt_key);
3237 
3238 	/*
3239 	 * Set up an array of the lengths of ALR original strings, and while at it,
3240 	 * get the shortest and longest defined.
3241 	 */
3242 	alr_lengths = (sc_int *)sc_malloc(alr_count * sizeof(*alr_lengths));
3243 	shortest = INTEGER_MAX;
3244 	longest = 0;
3245 	for (index_ = 0; index_ < alr_count; index_++) {
3246 		const sc_char *original;
3247 
3248 		vt_key[1].integer = index_;
3249 		vt_key[2].string = "Original";
3250 		original = prop_get_string(bundle, "S<-sis", vt_key);
3251 		length = strlen(original);
3252 
3253 		alr_lengths[index_] = length;
3254 		shortest = (length < shortest) ? length : shortest;
3255 		longest = (length > longest) ? length : longest;
3256 	}
3257 
3258 	/*
3259 	 * Now write a set of secondary properties that define the order of handling
3260 	 * for ALRs.  Our friend qsort() can't help here as it doesn't define the
3261 	 * final ordering of equal members, and we need here to retain file ordering
3262 	 * for ALR originals of the same length.
3263 	 */
3264 	vt_key[0].string = "ALRs2";
3265 	alr = 0;
3266 	for (length = longest; length >= shortest; length--) {
3267 		/* Find and add each ALR of this length. */
3268 		for (index_ = 0; index_ < alr_count; index_++) {
3269 			if (alr_lengths[index_] == length) {
3270 				sc_vartype_t vt_value;
3271 
3272 				vt_key[1].integer = alr++;
3273 				vt_key[2].string = "ALRIndex";
3274 				vt_value.integer = index_;
3275 				prop_put(bundle, "I->sis", vt_value, vt_key);
3276 			}
3277 		}
3278 	}
3279 	assert(alr == alr_count);
3280 
3281 	/* Done with ALR lengths array. */
3282 	sc_free(alr_lengths);
3283 }
3284 
3285 
3286 /*
3287  * parse_add_resources_offset()
3288  *
3289  * Add the resources offset to the properties as an extra game property
3290  * for version 4.0 games.  For version 3.9 and version 3.8 games, write
3291  * zero; only version 4.0 games can embed their resources into the TAF file.
3292  */
parse_add_resources_offset(sc_prop_setref_t bundle,sc_tafref_t taf)3293 static void parse_add_resources_offset(sc_prop_setref_t bundle, sc_tafref_t taf) {
3294 	sc_vartype_t vt_key[2], vt_value;
3295 	sc_bool embedded;
3296 	sc_int offset;
3297 
3298 	/*
3299 	 * Get the resources offset from the TAF, or default to zero.  The resources
3300 	 * offset is one byte after the end of game data.
3301 	 */
3302 	vt_key[0].string = "Globals";
3303 	vt_key[1].string = "Embedded";
3304 	embedded = prop_get_boolean(bundle, "B<-ss", vt_key);
3305 	offset = embedded ? taf_get_game_data_length(taf) + 1 : 0;
3306 
3307 	/* Add this offset to the properties. */
3308 	vt_key[0].string = "ResourceOffset";
3309 	vt_value.integer = offset;
3310 	prop_put(bundle, "I->s", vt_value, vt_key);
3311 }
3312 
3313 
3314 /*
3315  * parse_add_version()
3316  *
3317  * Add the TAF version to the properties, both integer and character forms
3318  * for convenience.
3319  */
parse_add_version(sc_prop_setref_t bundle,sc_tafref_t taf)3320 static void parse_add_version(sc_prop_setref_t bundle, sc_tafref_t taf) {
3321 	sc_vartype_t vt_key, vt_value;
3322 
3323 	/* Add the version integer to the properties. */
3324 	vt_key.string = "Version";
3325 	vt_value.integer = taf_get_version(taf);
3326 	prop_put(bundle, "I->s", vt_value, &vt_key);
3327 
3328 	/* Add the version string to the properties. */
3329 	switch (taf_get_version(taf)) {
3330 	case TAF_VERSION_400:
3331 		vt_value.string = "4.00";
3332 		break;
3333 	case TAF_VERSION_390:
3334 		vt_value.string = "3.90";
3335 		break;
3336 	case TAF_VERSION_380:
3337 		vt_value.string = "3.80";
3338 		break;
3339 	default:
3340 		sc_error("parse_add_version_string: invalid TAF file version\n");
3341 		vt_value.string = "[Unknown version]";
3342 		break;
3343 	}
3344 	vt_key.string = "VersionString";
3345 	prop_put(bundle, "S->s", vt_value, &vt_key);
3346 }
3347 
3348 
3349 /*
3350  * parse_game()
3351  *
3352  * Parse a game into a set properties.  Return TRUE on success, FALSE if
3353  * it encountered an error reading the TAF file.
3354  */
parse_game(sc_tafref_t taf,sc_prop_setref_t bundle)3355 sc_bool parse_game(sc_tafref_t taf, sc_prop_setref_t bundle) {
3356 	assert(taf && bundle);
3357 	Context context;
3358 
3359 	/* Store the TAF to read from, and the bundle to store into. */
3360 	parse_taf = taf;
3361 	parse_bundle = bundle;
3362 	parse_schema = parse_select_schema(parse_taf);
3363 	parse_depth = 0;
3364 
3365 	// Try parsing a complete game
3366 	taf_first_line(parse_taf);
3367 	parse_tafline = 0;
3368 	parse_class(context, "<_GAME_>");
3369 
3370 	if (context._break) {
3371 		// Error with one of the TAF file lines
3372 		parse_clear_v400_resources_table();
3373 		parse_taf = NULL;
3374 		parse_bundle = NULL;
3375 		parse_schema = NULL;
3376 		parse_depth = 0;
3377 		return FALSE;
3378 	}
3379 
3380 	/* Free the accumulated version 4.0 resources details. */
3381 	parse_clear_v400_resources_table();
3382 
3383 	/* See if we reached the end of the TAF. */
3384 	if (taf_more_lines(parse_taf))
3385 		sc_error("parse_game: unexpected trailing data\n");
3386 
3387 	/* Append post-processing walkalerts and move times. */
3388 	parse_add_walkalerts(parse_bundle);
3389 	parse_add_movetimes(parse_bundle);
3390 
3391 	/* Append sorted ALR list and resources offset. */
3392 	parse_add_alrs_index(parse_bundle);
3393 	parse_add_resources_offset(parse_bundle, parse_taf);
3394 
3395 	/* Add a note of the TAF file version. */
3396 	parse_add_version(parse_bundle, parse_taf);
3397 
3398 	/* Trim excess allocations from properties. */
3399 	prop_solidify(parse_bundle);
3400 
3401 	/* Return successfully. */
3402 	parse_taf = NULL;
3403 	parse_bundle = NULL;
3404 	parse_schema = NULL;
3405 	parse_depth = 0;
3406 	return TRUE;
3407 }
3408 
3409 
3410 /*
3411  * parse_debug_trace()
3412  *
3413  * Set parse tracing on/off.
3414  */
parse_debug_trace(sc_bool flag)3415 void parse_debug_trace(sc_bool flag) {
3416 	parse_trace = flag;
3417 }
3418 
3419 } // End of namespace Adrift
3420 } // End of namespace Glk
3421