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