aboutsummaryrefslogtreecommitdiff
path: root/engines/agi/wagparser.cpp
blob: 8dee6545c07d1db5e34f8aea32e0b570b8d07f2d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

#include "common/file.h"
#include "common/util.h"
#include "common/fs.h"
#include "common/debug.h"
#include "common/textconsole.h"

#include "agi/wagparser.h"

namespace Agi {

WagProperty::WagProperty() {
	setDefaults();
}

WagProperty::~WagProperty() {
	deleteData();
}

WagProperty::WagProperty(const WagProperty &other) {
	deepCopy(other);
}

WagProperty &WagProperty::operator=(const WagProperty &other) {
	if (&other != this) deepCopy(other); // Don't do self-assignment
	return *this;
}

void WagProperty::deepCopy(const WagProperty &other) {
	_readOk   = other._readOk;
	_propCode = other._propCode;
	_propType = other._propType;
	_propNum  = other._propNum;
	_propSize = other._propSize;

	deleteData(); // Delete old data (If any) and set _propData to NULL
	if (other._propData != NULL) {
		_propData = new char[other._propSize + 1UL]; // Allocate space for property's data plus trailing zero
		memcpy(_propData, other._propData, other._propSize + 1UL); // Copy the whole thing
	}
}

bool WagProperty::read(Common::SeekableReadStream &stream) {
	// First read the property's header
	_propCode = (enum WagPropertyCode) stream.readByte();
	_propType = (enum WagPropertyType) stream.readByte();
	_propNum  = stream.readByte();
	_propSize = stream.readUint16LE();

	if (stream.eos() || stream.err()) { // Check that we got the whole header
		_readOk = false;
		return _readOk;
	}

	// Then read the property's data
	deleteData(); // Delete old data (If any)
	_propData = new char[_propSize + 1UL]; // Allocate space for property's data plus trailing zero
	uint32 readBytes = stream.read(_propData, _propSize); // Read the data in
	_propData[_propSize] = 0; // Set the trailing zero for easy C-style string access

	_readOk = (_propData != NULL && readBytes == _propSize); // Check that we got the whole data
	return _readOk;
}

void WagProperty::clear() {
	deleteData();
	setDefaults();
}

void WagProperty::setDefaults() {
	_readOk   = false;
	_propCode = PC_UNDEFINED;
	_propType = PT_UNDEFINED;
	_propNum  = 0;
	_propSize = 0;
	_propData = NULL;
}

void WagProperty::deleteData() {
	delete[] _propData;
	_propData = NULL;
}

WagFileParser::WagFileParser() :
	_parsedOk(false) {
}

WagFileParser::~WagFileParser() {
}

bool WagFileParser::checkAgiVersionProperty(const WagProperty &version) const {
	if (version.getCode() == WagProperty::PC_INTVERSION && // Must be AGI interpreter version property
		version.getSize() >= 3 && // Need at least three characters for a version number like "X.Y"
		Common::isDigit(version.getData()[0]) && // And the first character must be a digit
		(version.getData()[1] == ',' || version.getData()[1] == '.')) { // And the second a comma or a period

		for (int i = 2; i < version.getSize(); i++) // And the rest must all be digits
			if (!Common::isDigit(version.getData()[i]))
				return false; // Bail out if found a non-digit after the decimal point

		return true;
	} else // Didn't pass the preliminary test so fails
		return false;
}

uint16 WagFileParser::convertToAgiVersionNumber(const WagProperty &version) {
	// Examples of the conversion: "2.44" -> 0x2440, "2.917" -> 0x2917, "3.002086" -> 0x3086.
	if (checkAgiVersionProperty(version)) { // Check that the string is a valid AGI interpreter version string
		// Convert first ascii digit to an integer and put it in the fourth nibble (Bits 12...15) of the version number
		// and at the same time set all other nibbles to zero.
		uint16 agiVerNum = ((uint16) (version.getData()[0] - '0')) << (3 * 4);

		// Convert at most three least significant digits of the version number's minor part
		// (i.e. the part after the decimal point) and put them in order to the third, second
		// and the first nibble of the version number. Just to clarify version.getSize() - 2
		// is the number of digits after the decimal point.
		int32 digitCount = MIN<int32>(3, ((int32) version.getSize()) - 2); // How many digits left to convert
		for (int i = 0; i < digitCount; i++)
			agiVerNum |= ((uint16) (version.getData()[version.getSize() - digitCount + i] - '0')) << ((2 - i) * 4);

		debug(3, "WagFileParser: Converted AGI version from string %s to number 0x%x", version.getData(), agiVerNum);
		return agiVerNum;
	} else // Not a valid AGI interpreter version string
		return 0; // Can't convert, so failure
}

bool WagFileParser::checkWagVersion(Common::SeekableReadStream &stream) {
	if (stream.size() >= WINAGI_VERSION_LENGTH) { // Stream has space to contain the WinAGI version string
		// Read the last WINAGI_VERSION_LENGTH bytes of the stream and make a string out of it
		char str[WINAGI_VERSION_LENGTH+1]; // Allocate space for the trailing zero also
		uint32 oldStreamPos = stream.pos(); // Save the old stream position
		stream.seek(stream.size() - WINAGI_VERSION_LENGTH);
		uint32 readBytes = stream.read(str, WINAGI_VERSION_LENGTH);
		stream.seek(oldStreamPos); // Seek back to the old stream position
		str[readBytes] = 0; // Set the trailing zero to finish the C-style string
		if (readBytes != WINAGI_VERSION_LENGTH) { // Check that we got the whole version string
			debug(3, "WagFileParser::checkWagVersion: Error reading WAG file version from stream");
			return false;
		}
		debug(3, "WagFileParser::checkWagVersion: Read WinAGI version string (\"%s\")", str);

		// Check that the WinAGI version string is one of the two version strings
		// WinAGI 1.1.21 recognizes as acceptable in the end of a *.wag file.
		// Note that they are all of length 16 and are padded with spaces to be that long.
		return scumm_stricmp(str, "WINAGI v1.0     ") == 0 ||
			scumm_stricmp(str, "1.0 BETA        ") == 0;
	} else { // Stream is too small to contain the WinAGI version string
		debug(3, "WagFileParser::checkWagVersion: Stream is too small to contain a valid WAG file");
		return false;
	}
}

bool WagFileParser::parse(const Common::FSNode &node) {
	WagProperty property; // Temporary property used for reading
	Common::SeekableReadStream *stream = NULL; // The file stream

	_parsedOk = false; // We haven't parsed the file yet

	stream = node.createReadStream(); // Open the file
	if (stream) { // Check that opening the file was succesful
		if (checkWagVersion(*stream)) { // Check that WinAGI version string is valid
			// It seems we've got a valid *.wag file so let's parse its properties from the start.
			stream->seek(0); // Rewind the stream
			if (!_propList.empty()) _propList.clear(); // Clear out old properties (If any)

			do { // Parse the properties
				if (property.read(*stream)) { // Read the property and check it was read ok
					_propList.push_back(property); // Add read property to properties list
					debug(4, "WagFileParser::parse: Read property with code %d, type %d, number %d, size %d, data \"%s\"",
						property.getCode(), property.getType(), property.getNumber(), property.getSize(), property.getData());
				} else // Reading failed, let's bail out
					break;
			} while (!endOfProperties(*stream)); // Loop until the end of properties

			// File was parsed successfully only if we got to the end of properties
			// and all the properties were read successfully (Also the last).
			_parsedOk = endOfProperties(*stream) && property.readOk();

			if (!_parsedOk) // Error parsing stream
				warning("Error parsing WAG file (%s). WAG file ignored", node.getPath().c_str());
		} else // Invalid WinAGI version string or it couldn't be read
			warning("Invalid WAG file (%s) version or error reading it. WAG file ignored", node.getPath().c_str());
	} else // Couldn't open file
		warning("Couldn't open WAG file (%s). WAG file ignored", node.getPath().c_str());

	delete stream;
	return _parsedOk;
}

const WagProperty *WagFileParser::getProperty(const WagProperty::WagPropertyCode code) const {
	for (PropertyList::const_iterator iter = _propList.begin(); iter != _propList.end(); ++iter)
		if (iter->getCode() == code) return iter;
	return NULL;
}

bool WagFileParser::endOfProperties(const Common::SeekableReadStream &stream) const {
	return stream.pos() >= (stream.size() - WINAGI_VERSION_LENGTH);
}

} // End of namespace Agi