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  * This file contains the handle based Memory Manager code
22  */
23 
24 #define BODGE
25 
26 #include "common/file.h"
27 #include "common/textconsole.h"
28 
29 #include "tinsel/drives.h"
30 #include "tinsel/dw.h"
31 #include "tinsel/handle.h"
32 #include "tinsel/heapmem.h"			// heap memory manager
33 #include "tinsel/scn.h"		// for the DW1 Mac resource handler
34 #include "tinsel/timers.h"	// for DwGetCurrentTime()
35 #include "tinsel/tinsel.h"
36 #include "tinsel/scene.h"
37 
38 namespace Tinsel {
39 
40 //----------------- EXTERNAL GLOBAL DATA --------------------
41 
42 #ifdef DEBUG
43 static uint32 s_lockedScene = 0;
44 #endif
45 
46 
47 //----------------- LOCAL DEFINES --------------------
48 
49 struct MEMHANDLE {
50 	char szName[12];	///< file name of graphics file
51 	int32 filesize;		///< file size and flags
52 	MEM_NODE *_node;	///< memory node for the graphics
53 	uint32 flags2;
54 };
55 
56 
57 /** memory allocation flags - stored in the top bits of the filesize field */
58 enum {
59 	fPreload	= 0x01000000L,	///< preload memory
60 	fDiscard	= 0x02000000L,	///< discard memory
61 	fSound		= 0x04000000L,	///< sound data
62 	fGraphic	= 0x08000000L,	///< graphic data
63 	fCompressed	= 0x10000000L,	///< compressed data
64 	fLoaded		= 0x20000000L	///< set when file data has been loaded
65 };
66 #define	FSIZE_MASK	0x00FFFFFFL	///< mask to isolate the filesize
67 
68 //----------------- LOCAL GLOBAL DATA --------------------
69 
70 // FIXME: Avoid non-const global vars
71 
72 // handle table gets loaded from index file at runtime
73 static MEMHANDLE *g_handleTable = 0;
74 
75 // number of handles in the handle table
76 static uint g_numHandles = 0;
77 
78 static uint32 g_cdPlayHandle = (uint32)-1;
79 
80 static SCNHANDLE g_cdBaseHandle = 0, g_cdTopHandle = 0;
81 static Common::File *g_cdGraphStream = 0;
82 
83 static char g_szCdPlayFile[100];
84 
85 //----------------- FORWARD REFERENCES --------------------
86 
87 static void LoadFile(MEMHANDLE *pH);	// load a memory block as a file
88 
89 /**
90  * Loads the graphics handle table index file and preloads all the
91  * permanent graphics etc.
92  */
SetupHandleTable()93 void SetupHandleTable() {
94 	bool t2Flag = (TinselVersion == TINSEL_V2);
95 	int RECORD_SIZE = t2Flag ? 24 : 20;
96 
97 	int len;
98 	uint i;
99 	MEMHANDLE *pH;
100 	TinselFile f;
101 
102 	const char *indexFileName = TinselV1PSX ? PSX_INDEX_FILENAME : INDEX_FILENAME;
103 
104 	if (f.open(indexFileName)) {
105 		// get size of index file
106 		len = f.size();
107 
108 		if (len > 0) {
109 			if ((len % RECORD_SIZE) != 0) {
110 				// index file is corrupt
111 				error(FILE_IS_CORRUPT, indexFileName);
112 			}
113 
114 			// calc number of handles
115 			g_numHandles = len / RECORD_SIZE;
116 
117 			// allocate memory for the index file
118 			g_handleTable = (MEMHANDLE *)calloc(g_numHandles, sizeof(struct MEMHANDLE));
119 
120 			// make sure memory allocated
121 			assert(g_handleTable);
122 
123 			// load data
124 			for (i = 0; i < g_numHandles; i++) {
125 				f.read(g_handleTable[i].szName, 12);
126 				g_handleTable[i].filesize = f.readUint32();
127 				// The pointer should always be NULL. We don't
128 				// need to read that from the file.
129 				g_handleTable[i]._node = NULL;
130 				f.seek(4, SEEK_CUR);
131 				// For Discworld 2, read in the flags2 field
132 				g_handleTable[i].flags2 = t2Flag ? f.readUint32() : 0;
133 			}
134 
135 			if (f.eos() || f.err()) {
136 				// index file is corrupt
137 				error(FILE_IS_CORRUPT, indexFileName);
138 			}
139 
140 			// close the file
141 			f.close();
142 		} else {	// index file is corrupt
143 			error(FILE_IS_CORRUPT, indexFileName);
144 		}
145 	} else {	// cannot find the index file
146 		error(CANNOT_FIND_FILE, indexFileName);
147 	}
148 
149 	// allocate memory nodes and load all permanent graphics
150 	for (i = 0, pH = g_handleTable; i < g_numHandles; i++, pH++) {
151 		if (pH->filesize & fPreload) {
152 			// allocate a fixed memory node for permanent files
153 			pH->_node = MemoryAllocFixed((pH->filesize & FSIZE_MASK));
154 
155 			// make sure memory allocated
156 			assert(pH->_node);
157 
158 			// load the data
159 			LoadFile(pH);
160 		}
161 #ifdef BODGE
162 		else if ((pH->filesize & FSIZE_MASK) == 8) {
163 			pH->_node = NULL;
164 		}
165 #endif
166 		else {
167 			// allocate a discarded memory node for other files
168 			pH->_node = MemoryNoAlloc();
169 
170 			// make sure memory allocated
171 			assert(pH->_node);
172 		}
173 	}
174 }
175 
FreeHandleTable()176 void FreeHandleTable() {
177 	free(g_handleTable);
178 	g_handleTable = NULL;
179 
180 	delete g_cdGraphStream;
181 	g_cdGraphStream = NULL;
182 }
183 
184 /**
185  * Loads a memory block as a file.
186  */
OpenCDGraphFile()187 void OpenCDGraphFile() {
188 	delete g_cdGraphStream;
189 
190 	// As the theory goes, the right CD will be in there!
191 
192 	g_cdGraphStream = new Common::File;
193 	if (!g_cdGraphStream->open(g_szCdPlayFile))
194 		error(CANNOT_FIND_FILE, g_szCdPlayFile);
195 }
196 
LoadCDGraphData(MEMHANDLE * pH)197 void LoadCDGraphData(MEMHANDLE *pH) {
198 	// read the data
199 	uint bytes;
200 	byte *addr;
201 	int	retries = 0;
202 
203 	assert(!(pH->filesize & fCompressed));
204 
205 	// Can't be preloaded
206 	assert(!(pH->filesize & fPreload));
207 
208 	// discardable - lock the memory
209 	addr = (byte *)MemoryLock(pH->_node);
210 
211 	// make sure address is valid
212 	assert(addr);
213 
214 	// Move to correct place in file and load the required data
215 	assert(g_cdGraphStream);
216 	g_cdGraphStream->seek(g_cdBaseHandle & OFFSETMASK, SEEK_SET);
217 	bytes = g_cdGraphStream->read(addr, (g_cdTopHandle - g_cdBaseHandle) & OFFSETMASK);
218 
219 	// New code to try and handle CD read failures 24/2/97
220 	while (bytes != ((g_cdTopHandle - g_cdBaseHandle) & OFFSETMASK) && retries++ < MAX_READ_RETRIES)	{
221 		// Try again
222 		g_cdGraphStream->seek(g_cdBaseHandle & OFFSETMASK, SEEK_SET);
223 		bytes = g_cdGraphStream->read(addr, (g_cdTopHandle - g_cdBaseHandle) & OFFSETMASK);
224 	}
225 
226 	// discardable - unlock the memory
227 	MemoryUnlock(pH->_node);
228 
229 	// set the loaded flag
230 	pH->filesize |= fLoaded;
231 
232 	// clear the loading flag
233 //	pH->filesize &= ~fLoading;
234 
235 	if (bytes != ((g_cdTopHandle - g_cdBaseHandle) & OFFSETMASK))
236 		// file is corrupt
237 		error(FILE_READ_ERROR, "CD play file");
238 }
239 
240 /**
241  * Called immediatly preceding a CDplay().
242  * Prepares the ground so that when LockMem() is called, the
243  * appropriate section of the extra scene file is loaded.
244  * @param start			Handle of start of range
245  * @param next			Handle of end of range + 1
246  */
LoadExtraGraphData(SCNHANDLE start,SCNHANDLE next)247 void LoadExtraGraphData(SCNHANDLE start, SCNHANDLE next) {
248 	OpenCDGraphFile();
249 
250 	MemoryDiscard((g_handleTable + g_cdPlayHandle)->_node); // Free it
251 
252 	// It must always be the same
253 	assert(g_cdPlayHandle == (start >> SCNHANDLE_SHIFT));
254 	assert(g_cdPlayHandle == (next >> SCNHANDLE_SHIFT));
255 
256 	g_cdBaseHandle = start;
257 	g_cdTopHandle = next;
258 }
259 
SetCdPlaySceneDetails(int fileNum,const char * fileName)260 void SetCdPlaySceneDetails(int fileNum, const char *fileName) {
261 	Common::strlcpy(g_szCdPlayFile, fileName, 100);
262 }
263 
SetCdPlayHandle(int fileNum)264 void SetCdPlayHandle(int fileNum) {
265 	g_cdPlayHandle = fileNum;
266 }
267 
268 
269 /**
270  * Loads a memory block as a file.
271  * @param pH			Memory block pointer
272  */
LoadFile(MEMHANDLE * pH)273 void LoadFile(MEMHANDLE *pH) {
274 	Common::File f;
275 	char szFilename[sizeof(pH->szName) + 1];
276 
277 	if (pH->filesize & fCompressed) {
278 		error("Compression handling has been removed");
279 	}
280 
281 	// extract and zero terminate the filename
282 	memcpy(szFilename, pH->szName, sizeof(pH->szName));
283 	szFilename[sizeof(pH->szName)] = 0;
284 
285 	if (f.open(szFilename)) {
286 		// read the data
287 		int bytes;
288 		uint8 *addr;
289 
290 		// lock the memory
291 		addr = (uint8 *)MemoryLock(pH->_node);
292 
293 		// make sure address is valid
294 		assert(addr);
295 
296 		bytes = f.read(addr, pH->filesize & FSIZE_MASK);
297 
298 		// close the file
299 		f.close();
300 
301 		// discardable - unlock the memory
302 		MemoryUnlock(pH->_node);
303 
304 		// set the loaded flag
305 		pH->filesize |= fLoaded;
306 
307 		if (bytes == (pH->filesize & FSIZE_MASK)) {
308 			return;
309 		}
310 
311 		// file is corrupt
312 		error(FILE_IS_CORRUPT, szFilename);
313 	}
314 
315 	// cannot find file
316 	error(CANNOT_FIND_FILE, szFilename);
317 }
318 
319 /**
320  * Compute and return the address specified by a SCNHANDLE.
321  * @param offset			Handle and offset to data
322  */
LockMem(SCNHANDLE offset)323 byte *LockMem(SCNHANDLE offset) {
324 	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
325 	//debug("Locking offset of type %d (%x), offset %d, handle %d", (offset & HANDLEMASK) >> SCNHANDLE_SHIFT, (offset & HANDLEMASK) >> SCNHANDLE_SHIFT, offset & OFFSETMASK, handle);
326 	MEMHANDLE *pH;			// points to table entry
327 
328 	// range check the memory handle
329 	assert(handle < g_numHandles);
330 
331 #ifdef DEBUG
332 	if (handle != s_lockedScene)
333 		warning("  Calling LockMem(0x%x), handle %d differs from active scene %d", offset, handle, s_lockedScene);
334 #endif
335 
336 	pH = g_handleTable + handle;
337 
338 	if (pH->filesize & fPreload) {
339 		// permanent files are already loaded, nothing to be done
340 	} else if (handle == g_cdPlayHandle) {
341 		// Must be in currently loaded/loadable range
342 		if (offset < g_cdBaseHandle || offset >= g_cdTopHandle)
343 			error("Overlapping (in time) CD-plays");
344 
345 		// May have been discarded, if so, we have to reload
346 		if (!MemoryDeref(pH->_node)) {
347 			// Data was discarded, we have to reload
348 			MemoryReAlloc(pH->_node, g_cdTopHandle - g_cdBaseHandle);
349 
350 			LoadCDGraphData(pH);
351 
352 			// update the LRU time (new in this file)
353 			MemoryTouch(pH->_node);
354 		}
355 
356 		// make sure address is valid
357 		assert(pH->filesize & fLoaded);
358 
359 		offset -= g_cdBaseHandle;
360 	} else {
361 		if (!MemoryDeref(pH->_node)) {
362 			// Data was discarded, we have to reload
363 			MemoryReAlloc(pH->_node, pH->filesize & FSIZE_MASK);
364 
365 			if (TinselV2) {
366 				SetCD(pH->flags2 & fAllCds);
367 				CdCD(Common::nullContext);
368 			}
369 			LoadFile(pH);
370 		}
371 
372 		// make sure address is valid
373 		assert(pH->filesize & fLoaded);
374 	}
375 
376 	return MemoryDeref(pH->_node) + (offset & OFFSETMASK);
377 }
378 
379 /**
380  * Called to lock the current scene and make it non-discardable.
381  * @param offset			Handle and offset to data
382  */
LockScene(SCNHANDLE offset)383 void LockScene(SCNHANDLE offset) {
384 
385 	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
386 	MEMHANDLE *pH;					// points to table entry
387 
388 #ifdef DEBUG
389 	assert(0 == s_lockedScene); // Trying to lock more than one scene
390 #endif
391 
392 	// range check the memory handle
393 	assert(handle < g_numHandles);
394 
395 	pH = g_handleTable + handle;
396 
397 	if ((pH->filesize & fPreload) == 0) {
398 		// Ensure the scene handle is allocated.
399 		MemoryReAlloc(pH->_node, pH->filesize & FSIZE_MASK);
400 
401 		// Now lock it to make sure it stays allocated and in a fixed position.
402 		MemoryLock(pH->_node);
403 
404 #ifdef DEBUG
405 		s_lockedScene = handle;
406 #endif
407 	}
408 }
409 
410 /**
411  * Called to make the current scene discardable again.
412  * @param offset			Handle and offset to data
413  */
UnlockScene(SCNHANDLE offset)414 void UnlockScene(SCNHANDLE offset) {
415 
416 	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
417 	MEMHANDLE *pH;					// points to table entry
418 
419 	// range check the memory handle
420 	assert(handle < g_numHandles);
421 
422 	pH = g_handleTable + handle;
423 
424 	if ((pH->filesize & fPreload) == 0) {
425 		// unlock the scene data
426 		MemoryUnlock(pH->_node);
427 
428 #ifdef DEBUG
429 		s_lockedScene = 0;
430 #endif
431 	}
432 }
433 
434 /*----------------------------------------------------------------------*/
435 
436 #ifdef BODGE
437 
438 /**
439  * Validates that a specified handle pointer is valid
440  * @param offset			Handle and offset to data
441  */
ValidHandle(SCNHANDLE offset)442 bool ValidHandle(SCNHANDLE offset) {
443 	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
444 	MEMHANDLE *pH;					// points to table entry
445 
446 	// range check the memory handle
447 	assert(handle < g_numHandles);
448 
449 	pH = g_handleTable + handle;
450 
451 	return (pH->filesize & FSIZE_MASK) != 8;
452 }
453 #endif
454 
455 /**
456  * TouchMem
457  * @param offset			Handle and offset to data
458  */
TouchMem(SCNHANDLE offset)459 void TouchMem(SCNHANDLE offset) {
460 	MEMHANDLE *pH;					// points to table entry
461 	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
462 
463 	if (offset != 0) {
464 		pH = g_handleTable + handle;
465 
466 		// update the LRU time whether its loaded or not!
467 		if (pH->_node)
468 			MemoryTouch(pH->_node);
469 	}
470 }
471 
472 /**
473  * Returns true if the given handle is into the cd graph data
474  * @param offset			Handle and offset to data
475  */
IsCdPlayHandle(SCNHANDLE offset)476 bool IsCdPlayHandle(SCNHANDLE offset) {
477 	uint32 handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
478 
479 	// range check the memory handle
480 	assert(handle < g_numHandles);
481 
482 	return (handle == g_cdPlayHandle);
483 }
484 
485 /**
486  * Returns the CD for a given scene handle
487  */
CdNumber(SCNHANDLE offset)488 int CdNumber(SCNHANDLE offset) {
489 	uint handle = offset >> SCNHANDLE_SHIFT;	// calc memory handle to use
490 
491 	// range check the memory handle
492 	assert(handle < g_numHandles);
493 
494 	MEMHANDLE *pH = g_handleTable + handle;
495 
496 	if (!TinselV2)
497 		return 1;
498 
499 	return GetCD(pH->flags2 & fAllCds);
500 }
501 
502 } // End of namespace Tinsel
503