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 successful
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
|