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 /*
24  * This code is based on Labyrinth of Time code with assistance of
25  *
26  * Copyright (c) 1993 Terra Nova Development
27  * Copyright (c) 2004 The Wyrmkeep Entertainment Co.
28  *
29  */
30 
31 #include "common/file.h"
32 
33 #include "lab/lab.h"
34 
35 #include "lab/dispman.h"
36 #include "lab/music.h"
37 #include "lab/processroom.h"
38 #include "lab/resource.h"
39 
40 namespace Lab {
41 
Resource(LabEngine * vm)42 Resource::Resource(LabEngine *vm) : _vm(vm) {
43 	readStaticText();
44 }
45 
readStaticText()46 void Resource::readStaticText() {
47 	Common::File *labTextFile = openDataFile("Lab:Rooms/LabText");
48 
49 	for (int i = 0; i < 48; i++)
50 		_staticText[i] = labTextFile->readLine();
51 
52 	delete labTextFile;
53 }
54 
getFont(const Common::String fileName)55 TextFont *Resource::getFont(const Common::String fileName) {
56 	// TODO: Add support for the font format of the Amiga version
57 	Common::File *dataFile = openDataFile(fileName, MKTAG('V', 'G', 'A', 'F'));
58 
59 	uint32 headerSize = 4 + 2 + 256 * 3 + 4;
60 	uint32 fileSize = dataFile->size();
61 	if (fileSize <= headerSize)
62 		return nullptr;
63 
64 	TextFont *textfont = new TextFont();
65 	textfont->_dataLength = fileSize - headerSize;
66 	textfont->_height = dataFile->readUint16LE();
67 	dataFile->read(textfont->_widths, 256);
68 	for (int i = 0; i < 256; i++)
69 		textfont->_offsets[i] = dataFile->readUint16LE();
70 	dataFile->skip(4);
71 	textfont->_data = new byte[textfont->_dataLength + 4];
72 	dataFile->read(textfont->_data, textfont->_dataLength);
73 	delete dataFile;
74 	return textfont;
75 }
76 
getText(const Common::String fileName)77 Common::String Resource::getText(const Common::String fileName) {
78 	Common::File *dataFile = openDataFile(fileName);
79 
80 	uint32 count = dataFile->size();
81 	byte *buffer = new byte[count];
82 	byte *text = buffer;
83 	dataFile->read(buffer, count);
84 
85 	while (text && (*text != '\0'))
86 		*text++ -= (byte)95;
87 
88 	delete dataFile;
89 
90 	Common::String str = (char *)buffer;
91 	delete[] buffer;
92 
93 	return str;
94 }
95 
readRoomData(const Common::String fileName)96 void Resource::readRoomData(const Common::String fileName) {
97 	Common::File *dataFile = openDataFile(fileName, MKTAG('D', 'O', 'R', '1'));
98 
99 	_vm->_manyRooms = dataFile->readUint16LE();
100 	_vm->_highestCondition = dataFile->readUint16LE();
101 	_vm->_rooms = new RoomData[_vm->_manyRooms + 1];
102 
103 	for (int i = 1; i <= _vm->_manyRooms; i++) {
104 		RoomData *curRoom = &_vm->_rooms[i];
105 		curRoom->_doors[kDirectionNorth] = dataFile->readUint16LE();
106 		curRoom->_doors[kDirectionSouth] = dataFile->readUint16LE();
107 		curRoom->_doors[kDirectionEast] = dataFile->readUint16LE();
108 		curRoom->_doors[kDirectionWest] = dataFile->readUint16LE();
109 		curRoom->_transitionType = dataFile->readByte();
110 	}
111 
112 	delete dataFile;
113 }
114 
readInventory(const Common::String fileName)115 InventoryData *Resource::readInventory(const Common::String fileName) {
116 	Common::File *dataFile = openDataFile(fileName, MKTAG('I', 'N', 'V', '1'));
117 
118 	_vm->_numInv = dataFile->readUint16LE();
119 	InventoryData *inventory = new InventoryData[_vm->_numInv + 1];
120 
121 	for (int i = 1; i <= _vm->_numInv; i++) {
122 		inventory[i]._quantity = dataFile->readUint16LE();
123 		inventory[i]._name = readString(dataFile);
124 		inventory[i]._bitmapName = readString(dataFile);
125 	}
126 
127 	delete dataFile;
128 	return inventory;
129 }
130 
readViews(uint16 roomNum)131 void Resource::readViews(uint16 roomNum) {
132 	Common::String fileName = "LAB:Rooms/" + Common::String::format("%d", roomNum);
133 	Common::File *dataFile = openDataFile(fileName, MKTAG('R', 'O', 'M', '4'));
134 
135 	RoomData *curRoom = &_vm->_rooms[roomNum];
136 
137 	curRoom->_roomMsg = readString(dataFile);
138 	readView(dataFile, curRoom->_view[kDirectionNorth]);
139 	readView(dataFile, curRoom->_view[kDirectionSouth]);
140 	readView(dataFile, curRoom->_view[kDirectionEast]);
141 	readView(dataFile, curRoom->_view[kDirectionWest]);
142 	readRule(dataFile, curRoom->_rules);
143 
144 	delete dataFile;
145 }
146 
translateFileName(const Common::String filename)147 Common::String Resource::translateFileName(const Common::String filename) {
148 	Common::String upperFilename;
149 
150 	// The DOS and Windows version aren't looking for the right file,
151 	if (!filename.compareToIgnoreCase("P:ZigInt/BLK") && (_vm->getPlatform() != Common::kPlatformAmiga))
152 		upperFilename = "P:ZigInt/ZIGINT.BLK";
153 	else
154 		upperFilename = filename;
155 
156 	upperFilename.toUppercase();
157 	Common::String fileNameStrFinal;
158 
159 	if (upperFilename.hasPrefix("P:") || upperFilename.hasPrefix("F:")) {
160 		if (_vm->_isHiRes)
161 			fileNameStrFinal = "SPICT/";
162 		else
163 			fileNameStrFinal = "PICT/";
164 
165 		if (_vm->getPlatform() == Common::kPlatformAmiga) {
166 			if (upperFilename.hasPrefix("P:")) {
167 				fileNameStrFinal = "PICT/";
168 			} else {
169 				fileNameStrFinal = "LABFONTS/";
170 				upperFilename += "T";	// all the Amiga fonts have a ".FONT" suffix
171 			}
172 		}
173 	} else if (upperFilename.hasPrefix("LAB:")) {
174 		// Look inside the game folder
175 	} else if (upperFilename.hasPrefix("MUSIC:")) {
176 		fileNameStrFinal = "MUSIC/";
177 	}
178 
179 	if (upperFilename.contains(':')) {
180 		while (upperFilename[0] != ':') {
181 			upperFilename.deleteChar(0);
182 		}
183 
184 		upperFilename.deleteChar(0);
185 	}
186 
187 	if (_vm->getPlatform() == Common::kPlatformDOS) {
188 		// Some script of the DOS version uses names used in the Amiga (and Windows) version,
189 		// which isn't limited to 8.3 characters. We need to parse upperFilename to detect
190 		// the filename, and fix it if required so it matches a DOS filename.
191 		while (upperFilename.contains('/') && upperFilename.size()) {
192 			fileNameStrFinal += upperFilename[0];
193 			upperFilename.deleteChar(0);
194 		}
195 
196 		for (int i = 0; (i < 8) && upperFilename.size() && (upperFilename[0] != '.'); i++) {
197 			fileNameStrFinal += upperFilename[0];
198 			upperFilename.deleteChar(0);
199 		}
200 
201 		// Remove the extra character in the filename
202 		while (upperFilename.size() && (upperFilename[0] != '.'))
203 			upperFilename.deleteChar(0);
204 
205 		// copy max 4 characters for the extension ('.foo')
206 		for (int i = 0; (i < 4) && upperFilename.size(); i++) {
207 			fileNameStrFinal += upperFilename[0];
208 			upperFilename.deleteChar(0);
209 		}
210 
211 		// Skip the extra characters of the extension
212 		upperFilename.clear();
213 	}
214 
215 	fileNameStrFinal += upperFilename;
216 
217 	return fileNameStrFinal;
218 }
219 
openDataFile(const Common::String filename,uint32 fileHeader)220 Common::File *Resource::openDataFile(const Common::String filename, uint32 fileHeader) {
221 	Common::File *dataFile = new Common::File();
222 	dataFile->open(translateFileName(filename));
223 
224 	if (!dataFile->isOpen()) {
225 		// The DOS version is known to have some missing files
226 		if (_vm->getPlatform() == Common::kPlatformDOS) {
227 			warning("Incomplete DOS version, skipping file %s", filename.c_str());
228 			return nullptr;
229 		} else
230 			error("openDataFile: Couldn't open %s (%s)", translateFileName(filename).c_str(), filename.c_str());
231 	}
232 	if (fileHeader > 0) {
233 		uint32 headerTag = dataFile->readUint32BE();
234 		if (headerTag != fileHeader) {
235 			dataFile->close();
236 			error("openDataFile: Unexpected header in %s (%s) - expected: %d, got: %d", translateFileName(filename).c_str(), filename.c_str(), fileHeader, headerTag);
237 		}
238 	}
239 
240 	return dataFile;
241 }
242 
readString(Common::File * file)243 Common::String Resource::readString(Common::File *file) {
244 	byte size = file->readByte();
245 	if (!size)
246 		return Common::String("");
247 
248 	char *str = new char[size];
249 	for (int i = 0; i < size; i++) {
250 		char c = file->readByte();
251 		// Decrypt char
252 		c = (i < size - 1) ? c - 95 : '\0';
253 		str[i] = c;
254 	}
255 
256 	Common::String result = str;
257 	delete[] str;
258 	return result;
259 }
260 
readConditions(Common::File * file)261 Common::Array<int16> Resource::readConditions(Common::File *file) {
262 	int16 cond;
263 	Common::Array<int16> list;
264 
265 	while ((cond = file->readUint16LE()) != 0)
266 		list.push_back(cond);
267 
268 	if (list.size() > 24) {
269 		// The original only allocated 24 elements, and silently
270 		// dropped remaining parts.
271 		warning("More than 24 parts in condition");
272 	}
273 
274 	return list;
275 }
276 
readRule(Common::File * file,RuleList & rules)277 void Resource::readRule(Common::File *file, RuleList &rules) {
278 	rules.clear();
279 	while (file->readByte() == 1) {
280 		rules.push_back(Rule());
281 		Rule &rule = rules.back();
282 
283 		rule._ruleType = (RuleType)file->readSint16LE();
284 		rule._param1 = file->readSint16LE();
285 		rule._param2 = file->readSint16LE();
286 		rule._condition = readConditions(file);
287 		readAction(file, rule._actionList);
288 	}
289 }
290 
readAction(Common::File * file,ActionList & list)291 void Resource::readAction(Common::File *file, ActionList &list) {
292 	list.clear();
293 
294 	while (file->readByte() == 1) {
295 		list.push_back(Action());
296 		Action &action = list.back();
297 
298 		action._actionType = (ActionType)file->readSint16LE();
299 		action._param1 = file->readSint16LE();
300 		action._param2 = file->readSint16LE();
301 		action._param3 = file->readSint16LE();
302 
303 		if (action._actionType == kActionShowMessages) {
304 			action._messages.reserve(action._param1);
305 			for (int i = 0; i < action._param1; i++)
306 				action._messages.push_back(readString(file));
307 		} else {
308 			action._messages.push_back(readString(file));
309 		}
310 	}
311 }
312 
readCloseUps(uint16 depth,Common::File * file,CloseDataList & list)313 void Resource::readCloseUps(uint16 depth, Common::File *file, CloseDataList &list) {
314 	list.clear();
315 	while (file->readByte() != '\0') {
316 		list.push_back(CloseData());
317 		CloseData &closeup = list.back();
318 
319 		closeup._x1 = file->readUint16LE();
320 		closeup._y1 = file->readUint16LE();
321 		closeup._x2 = file->readUint16LE();
322 		closeup._y2 = file->readUint16LE();
323 		closeup._closeUpType = file->readSint16LE();
324 		closeup._depth = depth;
325 		closeup._graphicName = readString(file);
326 		closeup._message = readString(file);
327 		readCloseUps(depth + 1, file, closeup._subCloseUps);
328 	}
329 }
330 
readView(Common::File * file,ViewDataList & list)331 void Resource::readView(Common::File *file, ViewDataList &list) {
332 	list.clear();
333 	while (file->readByte() == 1) {
334 		list.push_back(ViewData());
335 		ViewData &view = list.back();
336 
337 		view._condition = readConditions(file);
338 		view._graphicName = readString(file);
339 		readCloseUps(0, file, view._closeUps);
340 	}
341 }
342 
343 } // End of namespace Lab
344