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