1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM 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 "common/algorithm.h"
24 #include "common/scummsys.h"
25 #include "common/str.h"
26 #include "common/textconsole.h"
27 #include "common/util.h"
28 #include "engines/wintermute/base/gfx/x/loader_x.h"
29 
30 namespace Wintermute {
31 
nextTokenText(Common::MemoryReadStream & buffer,int & lineCount,Token & tok)32 void nextTokenText(Common::MemoryReadStream &buffer, int &lineCount, Token &tok) {
33 	char current = buffer.readSByte();
34 
35 	while (true) {
36 		if (Common::isSpace(current)) {
37 			if (current == '\n') {
38 				++lineCount;
39 			}
40 
41 			current = buffer.readSByte();
42 		} else if (current == '/' || current == '#') {
43 			// single slashes do not seem to be used in .X files,
44 			// so checking for one should be enough
45 
46 			while (current != '\n') {
47 				current = buffer.readSByte();
48 			}
49 
50 			current = buffer.readSByte();
51 			++lineCount;
52 		} else {
53 			break;
54 		}
55 	}
56 
57 	if (Common::isAlpha(current) || current == '_') {
58 		tok.pushChar(current);
59 		current = buffer.readSByte();
60 
61 		while (Common::isAlnum(current) || current == '_' || current == '-') {
62 			tok.pushChar(current);
63 			current = buffer.readSByte();
64 		}
65 
66 		buffer.seek(-1, SEEK_CUR);
67 		tok._type = IDENTIFIER;
68 		return;
69 	} else if (Common::isDigit(current) || current == '-') {
70 		tok.pushChar(current);
71 		current = buffer.readSByte();
72 
73 		while (Common::isDigit(current)) {
74 			tok.pushChar(current);
75 			current = buffer.readSByte();
76 		}
77 
78 		if (current == '.') {
79 			tok.pushChar(current);
80 			current = buffer.readSByte();
81 
82 			while (Common::isDigit(current)) {
83 				tok.pushChar(current);
84 				current = buffer.readSByte();
85 			}
86 
87 			buffer.seek(-1, SEEK_CUR);
88 			tok._type = FLOAT;
89 			return;
90 		}
91 
92 		buffer.seek(-1, SEEK_CUR);
93 		tok._type = INT;
94 		return;
95 	} else if (current == '<') {
96 		// a uuid consists of 36 characters, 32 alphanumeric characters
97 		// and four "-". We add space for a null character at the
98 		// end of the tempoary buffer
99 		const int uuidSize = 37;
100 		char uuid[uuidSize];
101 		buffer.read(uuid, 36);
102 		uuid[uuidSize - 1] = 0;
103 
104 		current = buffer.readSByte();
105 
106 		if (current != '>') {
107 			warning("Wrong UUID format at line %i", lineCount);
108 		} else {
109 			tok._textVal = uuid;
110 			tok._type = UUID;
111 			return;
112 		}
113 	} else if (current == '"') {
114 		current = buffer.readSByte();
115 
116 		while (current != '"') {
117 			tok.pushChar(current);
118 			current = buffer.readSByte();
119 		}
120 
121 		tok._type = STRING;
122 		return;
123 	}
124 
125 	switch (current) {
126 	case '(':
127 		tok._type = OPEN_PAREN;
128 		break;
129 	case ')':
130 		tok._type = CLOSE_PAREN;
131 		break;
132 	case '{':
133 		tok._type = OPEN_BRACES;
134 		break;
135 	case '}':
136 		tok._type = CLOSE_BRACES;
137 		break;
138 	case ']':
139 		tok._type = OPEN_BRACKET;
140 		break;
141 	case '[':
142 		tok._type = CLOSE_BRACKET;
143 		break;
144 	case ',':
145 		tok._type = COMMA;
146 		break;
147 	case ';':
148 		tok._type = SEMICOLON;
149 		break;
150 	case '.':
151 		tok._type = DOT;
152 		break;
153 	case '\0':
154 		tok._type = NULL_CHAR;
155 		break;
156 	default:
157 		tok._type = UNKNOWN_TOKEN;
158 		warning("Unknown token %c at line %i", current, lineCount);
159 	}
160 }
161 
162 // based on MSDN .X file format documentation
163 const uint16 XBIN_TOKEN_NAME         = 1;
164 const uint16 XBIN_TOKEN_STRING       = 2;
165 const uint16 XBIN_TOKEN_INTEGER      = 3;
166 const uint16 XBIN_TOKEN_GUID         = 5;
167 const uint16 XBIN_TOKEN_INTEGER_LIST = 6;
168 const uint16 XBIN_TOKEN_FLOAT_LIST   = 7;
169 
170 const uint16 XBIN_TOKEN_OBRACE    = 10;
171 const uint16 XBIN_TOKEN_CBRACE    = 11;
172 const uint16 XBIN_TOKEN_OPAREN    = 12;
173 const uint16 XBIN_TOKEN_CPAREN    = 13;
174 const uint16 XBIN_TOKEN_OBRACKET  = 14;
175 const uint16 XBIN_TOKEN_CBRACKET  = 15;
176 const uint16 XBIN_TOKEN_OANGLE    = 16;
177 const uint16 XBIN_TOKEN_CANGLE    = 17;
178 const uint16 XBIN_TOKEN_DOT       = 18;
179 const uint16 XBIN_TOKEN_COMMA     = 19;
180 const uint16 XBIN_TOKEN_SEMICOLON = 20;
181 const uint16 XBIN_TOKEN_TEMPLATE  = 31;
182 const uint16 XBIN_TOKEN_WORD      = 40;
183 const uint16 XBIN_TOKEN_DWORD     = 41;
184 const uint16 XBIN_TOKEN_FLOAT     = 42;
185 const uint16 XBIN_TOKEN_DOUBLE    = 43;
186 const uint16 XBIN_TOKEN_CHAR      = 44;
187 const uint16 XBIN_TOKEN_UCHAR     = 45;
188 const uint16 XBIN_TOKEN_SWORD     = 46;
189 const uint16 XBIN_TOKEN_SDWORD    = 47;
190 const uint16 XBIN_TOKEN_VOID      = 48;
191 const uint16 XBIN_TOKEN_LPSTR     = 49;
192 const uint16 XBIN_TOKEN_UNICODE   = 50;
193 const uint16 XBIN_TOKEN_CSTRING   = 51;
194 const uint16 XBIN_TOKEN_ARRAY     = 52;
195 
nextTokenBinary()196 void XFileLexer::nextTokenBinary() {
197 	uint16 current = _buffer.readUint16LE();
198 	uint32 length = -1;
199 
200 	switch (current) {
201 	case XBIN_TOKEN_NAME:
202 		length = _buffer.readUint32LE();
203 
204 		for (uint32 i = 0; i < length; ++i) {
205 			_tok.pushChar(_buffer.readByte());
206 		}
207 
208 		_tok._type = IDENTIFIER;
209 		break;
210 	case XBIN_TOKEN_STRING:
211 		length = _buffer.readUint32LE();
212 
213 		for (uint32 i = 0; i < length; ++i) {
214 			_tok.pushChar(_buffer.readByte());
215 		}
216 
217 		_tok._type = STRING;
218 		break;
219 	case XBIN_TOKEN_INTEGER:
220 		_tok._integerVal = _buffer.readUint32LE();
221 		_tok._type = INT;
222 		break;
223 	case XBIN_TOKEN_GUID:
224 		// ignore the UUID value
225 		_buffer.readUint32LE();
226 		_buffer.readUint16LE();
227 		_buffer.readUint16LE();
228 
229 		for (int i = 0; i < 8; ++i) {
230 			_buffer.readByte();
231 		}
232 
233 		_tok._type = UUID;
234 		break;
235 	case XBIN_TOKEN_INTEGER_LIST:
236 		_integersToRead = _buffer.readUint32LE();
237 		_tok._type = INT;
238 		_expectsTerminator = false;
239 		break;
240 	case XBIN_TOKEN_FLOAT_LIST:
241 		_floatsToRead = _buffer.readUint32LE();
242 		_tok._type = FLOAT;
243 		_expectsTerminator = false;
244 		break;
245 	case XBIN_TOKEN_OBRACE:
246 		_tok._type = OPEN_BRACES;
247 		break;
248 	case XBIN_TOKEN_CBRACE:
249 		_tok._type = CLOSE_BRACES;
250 		break;
251 	case XBIN_TOKEN_OPAREN:
252 		_tok._type = OPEN_PAREN;
253 		break;
254 	case XBIN_TOKEN_CPAREN:
255 		_tok._type = CLOSE_PAREN;
256 		break;
257 	case XBIN_TOKEN_OBRACKET:
258 		_tok._type = OPEN_BRACKET;
259 		break;
260 	case XBIN_TOKEN_CBRACKET:
261 		_tok._type = CLOSE_BRACKET;
262 		break;
263 	case XBIN_TOKEN_OANGLE:
264 		_tok._type = OPEN_ANGLE;
265 		break;
266 	case XBIN_TOKEN_CANGLE:
267 		_tok._type = CLOSE_ANGLE;
268 		break;
269 	case XBIN_TOKEN_DOT:
270 		_tok._type = DOT;
271 		break;
272 	case XBIN_TOKEN_COMMA:
273 		_tok._type = COMMA;
274 		break;
275 	case XBIN_TOKEN_SEMICOLON:
276 		_tok._type = SEMICOLON;
277 		break;
278 	case XBIN_TOKEN_TEMPLATE:
279 		_tok._textVal = "template";
280 		_tok._type = IDENTIFIER;
281 		break;
282 	case XBIN_TOKEN_WORD:
283 		break;
284 	case XBIN_TOKEN_DWORD:
285 		break;
286 	case XBIN_TOKEN_FLOAT:
287 		break;
288 	case XBIN_TOKEN_DOUBLE:
289 		break;
290 	case XBIN_TOKEN_CHAR:
291 		break;
292 	case XBIN_TOKEN_UCHAR:
293 		break;
294 	case XBIN_TOKEN_SWORD:
295 		break;
296 	case XBIN_TOKEN_SDWORD:
297 		break;
298 	case XBIN_TOKEN_VOID:
299 		break;
300 	case XBIN_TOKEN_LPSTR:
301 		break;
302 	case XBIN_TOKEN_UNICODE:
303 		break;
304 	case XBIN_TOKEN_CSTRING:
305 		break;
306 	case XBIN_TOKEN_ARRAY:
307 		break;
308 	case 0:
309 		_tok._type = NULL_CHAR;
310 		break;
311 	default:
312 		_tok._type = UNKNOWN_TOKEN;
313 		warning("XFileLexer::nextBinaryToken: Unknown token encountered");
314 	}
315 }
316 
XFileLexer(byte * buffer,uint32 fileSize,bool isText)317 XFileLexer::XFileLexer(byte *buffer, uint32 fileSize, bool isText)
318 	: _buffer(buffer, fileSize), _lineCount(1), _isText(isText), _integersToRead(0), _floatsToRead(0), _expectsTerminator(true) {
319 }
320 
advanceToNextToken()321 void XFileLexer::advanceToNextToken() {
322 	_tok._textVal.clear();
323 
324 	if (_isText) {
325 		nextTokenText(_buffer, _lineCount, _tok);
326 	} else {
327 		nextTokenBinary();
328 	}
329 }
330 
skipTerminator()331 void XFileLexer::skipTerminator() {
332 	if (_expectsTerminator) {
333 		advanceToNextToken();
334 	}
335 
336 	_expectsTerminator = (_floatsToRead == 0) && (_integersToRead == 0);
337 }
338 
eof()339 bool XFileLexer::eof() {
340 	return _buffer.pos() == _buffer.size();
341 }
342 
tokenIsIdentifier()343 bool XFileLexer::tokenIsIdentifier() {
344 	return _tok._type == IDENTIFIER;
345 }
346 
tokenIsIdentifier(const char * val)347 bool XFileLexer::tokenIsIdentifier(const char *val) {
348 	return _tok._type == IDENTIFIER && _tok._textVal == val;
349 }
350 
advanceOnOpenBraces()351 void XFileLexer::advanceOnOpenBraces() {
352 	if (_tok._type == OPEN_BRACES) {
353 		advanceToNextToken();
354 	}
355 }
356 
reachedClosedBraces()357 bool XFileLexer::reachedClosedBraces() {
358 	return _tok._type == CLOSE_BRACES;
359 }
360 
getTypeOfToken()361 TokenType XFileLexer::getTypeOfToken() {
362 	return _tok._type;
363 }
364 
tokenIsOfType(TokenType type)365 bool XFileLexer::tokenIsOfType(TokenType type) {
366 	return _tok._type == type;
367 }
368 
tokenToInt()369 int XFileLexer::tokenToInt() {
370 	return atoi(_tok._textVal.c_str());
371 }
372 
tokenToFloat()373 double XFileLexer::tokenToFloat() {
374 	return atof(_tok._textVal.c_str());
375 }
376 
tokenToString()377 Common::String XFileLexer::tokenToString() {
378 	return _tok._textVal;
379 }
380 
tokenToUint32()381 uint32 XFileLexer::tokenToUint32() {
382 	// All integer values in a .X file are unsigned and at most 32 bit
383 	// so parsing to unsigned long and then converting down should be fine
384 	return strtoul(_tok._textVal.c_str(), nullptr, 10);
385 }
386 
skipObject()387 void XFileLexer::skipObject() {
388 	advanceToNextToken(); // optional name
389 	advanceToNextToken();
390 	advanceOnOpenBraces();
391 
392 	// we have one open braces right now, once the counter reaches zero, we're done
393 	int closedBracesCount = 1;
394 
395 	while (closedBracesCount > 0) {
396 		while (_integersToRead > 0 || _floatsToRead > 0) {
397 			if (_integersToRead > 0) {
398 				readInt();
399 			}
400 
401 			if (_floatsToRead > 0) {
402 				readFloat();
403 			}
404 		}
405 
406 		if (_tok._type == OPEN_BRACES) {
407 			++closedBracesCount;
408 		}
409 
410 		if (_tok._type == CLOSE_BRACES) {
411 			--closedBracesCount;
412 		}
413 
414 		advanceToNextToken();
415 	}
416 }
417 
pushChar(char c)418 void Token::pushChar(char c) {
419 	_textVal.insertChar(c, _textVal.size());
420 }
421 
readFloat()422 float XFileLexer::readFloat() {
423 	if (_floatsToRead > 0) {
424 		--_floatsToRead;
425 		float tmp = _buffer.readFloatLE();
426 
427 		if (_floatsToRead == 0) {
428 			advanceToNextToken();
429 		}
430 
431 		return tmp;
432 	}
433 
434 	float tmp = tokenToFloat();
435 	advanceToNextToken();
436 	advanceToNextToken(); // skip comma or semicolon
437 	return tmp;
438 }
439 
readInt()440 int XFileLexer::readInt() {
441 	if (_integersToRead > 0) {
442 		--_integersToRead;
443 		int tmp = _buffer.readUint32LE();
444 
445 		if (_integersToRead == 0) {
446 			advanceToNextToken();
447 		}
448 
449 		return tmp;
450 	}
451 
452 	int tmp = tokenToInt();
453 	advanceToNextToken();
454 	advanceToNextToken(); // skip comma or semicolon
455 	return tmp;
456 }
457 
readString()458 Common::String XFileLexer::readString() {
459 	Common::String tmp = tokenToString();
460 	advanceToNextToken();
461 	advanceToNextToken(); // skip comma or semicolon
462 	return tmp;
463 }
464 
readUint32()465 uint32 XFileLexer::readUint32() {
466 	if (_integersToRead > 0) {
467 		--_integersToRead;
468 		uint32 tmp = _buffer.readUint32LE();
469 
470 		if (_integersToRead == 0) {
471 			advanceToNextToken();
472 		}
473 
474 		return tmp;
475 	}
476 
477 	uint32 tmp = tokenToUint32();
478 	advanceToNextToken();
479 	advanceToNextToken(); // skip comma or semicolon
480 	return tmp;
481 }
482 
483 } // namespace Wintermute
484