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