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 "common/debug.h"
24 #include "common/endian.h"
25 #include "common/config-manager.h"
26 #include "common/substream.h"
27 #include "common/textconsole.h"
28 #include "queen/resource.h"
29 
30 namespace Queen {
31 
32 
33 const char *const Resource::_tableFilename = "queen.tbl";
34 
35 const RetailGameVersion Resource::_gameVersions[] = {
36 	{ "PEM10", 1, 0x00000008,  22677657 },
37 	{ "CEM10", 1, 0x0000584E, 190787021 },
38 	{ "PFM10", 1, 0x0002CD93,  22157304 },
39 	{ "CFM10", 1, 0x00032585, 186689095 },
40 	{ "PGM10", 1, 0x00059ACA,  22240013 },
41 	{ "CGM10", 1, 0x0005F2A7, 217648975 },
42 	{ "PIM10", 1, 0x000866B1,  22461366 },
43 	{ "CIM10", 1, 0x0008BEE2, 190795582 },
44 	{ "CSM10", 1, 0x000B343C, 190730602 },
45 	{ "CHM10", 1, 0x000DA981, 190705558 },
46 	{ "PE100", 1, 0x00101EC6,   3724538 },
47 	{ "PE100", 1, 0x00102B7F,   3732177 },
48 	{ "PEint", 1, 0x00103838,   1915913 },
49 	{ "aEM10", 2, 0x00103F1E,    351775 },
50 	{ "CE101", 2, 0x00107D8D,    563335 },
51 	{ "PE100", 2, 0x001086D4,    597032 }
52 };
53 
compareResourceEntry(const void * a,const void * b)54 static int compareResourceEntry(const void *a, const void *b) {
55 	const char *filename = (const char *)a;
56 	const ResourceEntry *entry = (const ResourceEntry *)b;
57 	return strcmp(filename, entry->filename);
58 }
59 
Resource()60 Resource::Resource()
61 	: _resourceEntries(0), _resourceTable(NULL) {
62 	memset(&_version, 0, sizeof(_version));
63 
64 	_currentResourceFileNum = 1;
65 	if (!_resourceFile.open("queen.1c")) {
66 		if (!_resourceFile.open("queen.1")) {
67 			error("Could not open resource file 'queen.1[c]'");
68 		}
69 	}
70 	if (!detectVersion(&_version, &_resourceFile)) {
71 		error("Unable to detect game version");
72 	}
73 
74 	if (_version.features & GF_REBUILT) {
75 		readTableEntries(&_resourceFile);
76 	} else {
77 		readTableFile(_version.queenTblVersion, _version.queenTblOffset);
78 	}
79 
80 	checkJASVersion();
81 	debug(5, "Detected game version: %s, which has %d resource entries", _version.str, _resourceEntries);
82 }
83 
~Resource()84 Resource::~Resource() {
85 	_resourceFile.close();
86 
87 	if (_resourceTable != _resourceTablePEM10)
88 		delete[] _resourceTable;
89 }
90 
resourceEntry(const char * filename) const91 ResourceEntry *Resource::resourceEntry(const char *filename) const {
92 	assert(filename[0] && strlen(filename) < 14);
93 
94 	Common::String entryName(filename);
95 	entryName.toUppercase();
96 
97 	ResourceEntry *re = NULL;
98 	re = (ResourceEntry *)bsearch(entryName.c_str(), _resourceTable, _resourceEntries, sizeof(ResourceEntry), compareResourceEntry);
99 	return re;
100 }
101 
loadFile(const char * filename,uint32 skipBytes,uint32 * size)102 uint8 *Resource::loadFile(const char *filename, uint32 skipBytes, uint32 *size) {
103 	debug(7, "Resource::loadFile('%s')", filename);
104 	ResourceEntry *re = resourceEntry(filename);
105 	assert(re != NULL);
106 	uint32 sz = re->size - skipBytes;
107 	if (size != NULL) {
108 		*size = sz;
109 	}
110 	byte *dstBuf = new byte[sz];
111 	seekResourceFile(re->bundle, re->offset + skipBytes);
112 	_resourceFile.read(dstBuf, sz);
113 	return dstBuf;
114 }
115 
loadTextFile(const char * filename,Common::StringArray & stringList)116 void Resource::loadTextFile(const char *filename, Common::StringArray &stringList) {
117 	debug(7, "Resource::loadTextFile('%s')", filename);
118 	ResourceEntry *re = resourceEntry(filename);
119 	assert(re != NULL);
120 	seekResourceFile(re->bundle, re->offset);
121 	Common::SeekableSubReadStream stream(&_resourceFile, re->offset, re->offset + re->size);
122 	while (true) {
123 		Common::String tmp = stream.readLine();
124 		if (stream.eos() || stream.err())
125 			break;
126 		stringList.push_back(tmp);
127 	}
128 }
129 
detectVersion(DetectedGameVersion * ver,Common::File * f)130 bool Resource::detectVersion(DetectedGameVersion *ver, Common::File *f) {
131 	memset(ver, 0, sizeof(DetectedGameVersion));
132 
133 	if (f->readUint32BE() == MKTAG('Q','T','B','L')) {
134 		f->read(ver->str, 6);
135 		f->skip(2);
136 		ver->compression = f->readByte();
137 		ver->features = GF_REBUILT;
138 		ver->queenTblVersion = 0;
139 		ver->queenTblOffset = 0;
140 	} else {
141 		const RetailGameVersion *gameVersion = detectGameVersionFromSize(f->size());
142 		if (gameVersion == NULL) {
143 			warning("Unknown/unsupported FOTAQ version");
144 			return false;
145 		}
146 		strcpy(ver->str, gameVersion->str);
147 		ver->compression = COMPRESSION_NONE;
148 		ver->features = 0;
149 		ver->queenTblVersion = gameVersion->queenTblVersion;
150 		ver->queenTblOffset = gameVersion->queenTblOffset;
151 		strcpy(ver->str, gameVersion->str);
152 
153 		// Handle game versions for which versionStr information is irrevelant
154 		if (gameVersion == &_gameVersions[VER_AMI_DEMO]) { // CE101
155 			ver->language = Common::EN_ANY;
156 			ver->features |= GF_FLOPPY | GF_DEMO;
157 			ver->platform = Common::kPlatformAmiga;
158 			return true;
159 		}
160 		if (gameVersion == &_gameVersions[VER_AMI_INTERVIEW]) { // PE100
161 			ver->language = Common::EN_ANY;
162 			ver->features |= GF_FLOPPY | GF_INTERVIEW;
163 			ver->platform = Common::kPlatformAmiga;
164 			return true;
165 		}
166 	}
167 
168 	switch (ver->str[1]) {
169 	case 'E':
170 		if (Common::parseLanguage(ConfMan.get("language")) == Common::RU_RUS) {
171 			ver->language = Common::RU_RUS;
172 		} else if (Common::parseLanguage(ConfMan.get("language")) == Common::GR_GRE) {
173 			ver->language = Common::GR_GRE;
174 		} else {
175 			ver->language = Common::EN_ANY;
176 		}
177 		break;
178 	case 'F':
179 		ver->language = Common::FR_FRA;
180 		break;
181 	case 'G':
182 		ver->language = Common::DE_DEU;
183 		break;
184 	case 'H':
185 		ver->language = Common::HE_ISR;
186 		break;
187 	case 'I':
188 		ver->language = Common::IT_ITA;
189 		break;
190 	case 'S':
191 		ver->language = Common::ES_ESP;
192 		break;
193 	case 'g':
194 		ver->language = Common::GR_GRE;
195 		break;
196 	case 'R':
197 		ver->language = Common::RU_RUS;
198 		break;
199 	default:
200 		error("Invalid language id '%c'", ver->str[1]);
201 		break;
202 	}
203 
204 	switch (ver->str[0]) {
205 	case 'P':
206 		ver->features |= GF_FLOPPY;
207 		ver->platform = Common::kPlatformDOS;
208 		break;
209 	case 'C':
210 		ver->features |= GF_TALKIE;
211 		ver->platform = Common::kPlatformDOS;
212 		break;
213 	case 'a':
214 		ver->features |= GF_FLOPPY;
215 		ver->platform = Common::kPlatformAmiga;
216 		break;
217 	default:
218 		error("Invalid platform id '%c'", ver->str[0]);
219 		break;
220 	}
221 
222 	if (strcmp(ver->str + 2, "100") == 0 || strcmp(ver->str + 2, "101") == 0) {
223 		ver->features |= GF_DEMO;
224 	} else if (strcmp(ver->str + 2, "int") == 0) {
225 		ver->features |= GF_INTERVIEW;
226 	}
227 	return true;
228 }
229 
checkJASVersion()230 void Resource::checkJASVersion() {
231 	if (_version.platform == Common::kPlatformAmiga) {
232 		// don't bother verifying the JAS version string with these versions,
233 		// it will be done at the end of Logic::readQueenJas, anyway
234 		return;
235 	}
236 	ResourceEntry *re = resourceEntry("QUEEN.JAS");
237 	assert(re != NULL);
238 	uint32 offset = re->offset;
239 	if (isDemo())
240 		offset += JAS_VERSION_OFFSET_DEMO;
241 	else if (isInterview())
242 		offset += JAS_VERSION_OFFSET_INTV;
243 	else
244 		offset += JAS_VERSION_OFFSET_PC;
245 	seekResourceFile(re->bundle, offset);
246 
247 	char versionStr[6];
248 	_resourceFile.read(versionStr, 6);
249 	if (strcmp(_version.str, versionStr))
250 		error("Verifying game version failed! (expected: '%s', found: '%s')", _version.str, versionStr);
251 }
252 
seekResourceFile(int num,uint32 offset)253 void Resource::seekResourceFile(int num, uint32 offset) {
254 	if (_currentResourceFileNum != num) {
255 		debug(7, "Opening resource file %d, current %d", num, _currentResourceFileNum);
256 		_resourceFile.close();
257 		char name[20];
258 		sprintf(name, "queen.%d", num);
259 		if (!_resourceFile.open(name)) {
260 			error("Could not open resource file '%s'", name);
261 		}
262 		_currentResourceFileNum = num;
263 	}
264 	_resourceFile.seek(offset);
265 }
266 
readTableFile(uint8 version,uint32 offset)267 void Resource::readTableFile(uint8 version, uint32 offset) {
268 	Common::File tableFile;
269 	tableFile.open(_tableFilename);
270 	if (tableFile.isOpen() && tableFile.readUint32BE() == MKTAG('Q','T','B','L')) {
271 		uint32 tableVersion = tableFile.readUint32BE();
272 		if (version > tableVersion) {
273 			error("The game you are trying to play requires version %d of queen.tbl, "
274 			      "you have version %d ; please update it", version, tableVersion);
275 		}
276 		tableFile.seek(offset);
277 		readTableEntries(&tableFile);
278 	} else {
279 		// check if it is the english floppy version, for which we have a hardcoded version of the table
280 		if (strcmp(_version.str, _gameVersions[VER_ENG_FLOPPY].str) == 0) {
281 			_resourceEntries = 1076;
282 			_resourceTable = _resourceTablePEM10;
283 		} else {
284 			error("Could not find tablefile '%s'", _tableFilename);
285 		}
286 	}
287 }
288 
readTableEntries(Common::File * file)289 void Resource::readTableEntries(Common::File *file) {
290 	_resourceEntries = file->readUint16BE();
291 	_resourceTable = new ResourceEntry[_resourceEntries];
292 	for (uint16 i = 0; i < _resourceEntries; ++i) {
293 		ResourceEntry *re = &_resourceTable[i];
294 		file->read(re->filename, 12);
295 		re->filename[12] = '\0';
296 		re->bundle = file->readByte();
297 		re->offset = file->readUint32BE();
298 		re->size = file->readUint32BE();
299 	}
300 }
301 
detectGameVersionFromSize(uint32 size)302 const RetailGameVersion *Resource::detectGameVersionFromSize(uint32 size) {
303 	for (int i = 0; i < VER_COUNT; ++i) {
304 		if (_gameVersions[i].dataFileSize == size) {
305 			return &_gameVersions[i];
306 		}
307 	}
308 	return NULL;
309 }
310 
findSound(const char * filename,uint32 * size)311 Common::File *Resource::findSound(const char *filename, uint32 *size) {
312 	assert(strstr(filename, ".SB") != NULL || strstr(filename, ".AMR") != NULL || strstr(filename, ".INS") != NULL);
313 	ResourceEntry *re = resourceEntry(filename);
314 	if (re) {
315 		*size = re->size;
316 		seekResourceFile(re->bundle, re->offset);
317 		return &_resourceFile;
318 	}
319 	return NULL;
320 }
321 
322 } // End of namespace Queen
323