1 /* ResidualVM - A 3D game interpreter
2  *
3  * ResidualVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the AUTHORS
5  * file distributed with this source distribution.
6  *
7  * Additional copyright for this file:
8  * Copyright (C) 1999-2000 Revolution Software Ltd.
9  * This code is based on source code created by Revolution Software,
10  * used with permission.
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License
14  * as published by the Free Software Foundation; either version 2
15  * of the License, or (at your option) any later version.
16  *
17  * This program is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25  *
26  */
27 
28 #include "engines/icb/common/px_common.h"
29 #include "engines/icb/common/px_array.h"
30 #include "engines/icb/p4_generic.h"
31 #include "engines/icb/res_man.h"
32 #include "engines/icb/debug.h"
33 #include "engines/icb/protocol.h"
34 #include "engines/icb/keyboard.h"
35 #include "engines/icb/global_objects.h"
36 #include "engines/icb/zsupport.h"
37 
38 #include "common/archive.h"
39 #include "common/mutex.h"
40 #include "common/textconsole.h"
41 #include "common/config-manager.h"
42 #include "common/memstream.h"
43 
44 namespace ICB {
45 
46 uint32 MAX_MEM_BLOCKS = 0;
47 
openDiskFileForBinaryRead(const char * filename)48 Common::File *openDiskFileForBinaryRead(const char *filename) {
49 	pxString path = filename;
50 	path.ConvertPath();
51 	Common::File *result = new Common::File();
52 	if (result->open(path.c_str())) {
53 		return result;
54 	} else {
55 		delete result;
56 		warning("openDiskFileForBinaryRead(%s) - FAILED", path.c_str());
57 		return NULL;
58 	}
59 }
60 
openDiskFileForBinaryStreamRead(const Common::String & filename)61 Common::SeekableReadStream *openDiskFileForBinaryStreamRead(const Common::String &filename) {
62 	// Quick-fix to start replacing FILE with Stream.
63 	Common::File *f = openDiskFileForBinaryRead(filename.c_str());
64 	if (!f) {
65 		return nullptr;
66 	}
67 	int32 size = f->size();
68 	byte *data = (byte *)malloc(size);
69 	f->read(data, size);
70 	f->close();
71 	delete f;
72 	Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, size, DisposeAfterUse::YES);
73 	return stream;
74 }
75 
openDiskWriteStream(const Common::String & filename)76 Common::WriteStream *openDiskWriteStream(const Common::String &filename) { error("TODO: Connect up the savegame-handler and friends"); }
77 
checkFileExists(const char * fullpath)78 bool checkFileExists(const char *fullpath) {
79 	Common::File file;
80 
81 	return file.exists(fullpath);
82 }
83 
Memory_stats()84 void Memory_stats() {
85 	uint32 one_meg = 1024 * 1024;
86 
87 	// Find ram specs and decide how much we'll later give to resource manager(s)
88 	int32 m;
89 
90 	// HACK: set to 256MB to ensure most generous caching
91 	m = 256;
92 
93 	if (m < 16) { // less than 16mb
94 		Fatal_error("not enough memory - requires 16mb or better");
95 	} else if (m < 20) { // 16Mb to 20Mb - take 12Mb
96 		memory_available = 12 * one_meg;
97 		BACKGROUND_BUFFER_SIZE = 6 * one_meg;
98 		ANIMATION_BUFFER_SIZE = 4 * one_meg;
99 		BITMAP_BUFFER_SIZE = 1 * one_meg;
100 		SONICS_BUFFER_SIZE = 1 * one_meg;
101 		MAX_MEM_BLOCKS = (1024); // 4K of table space
102 	}
103 
104 	else if (m <= 32) { // 20-32MB - take 18Mb
105 		memory_available = 18 * one_meg;
106 		BACKGROUND_BUFFER_SIZE = 9 * one_meg;
107 		ANIMATION_BUFFER_SIZE = 6 * one_meg;
108 		BITMAP_BUFFER_SIZE = 2 * one_meg;
109 		SONICS_BUFFER_SIZE = 1 * one_meg;
110 		MAX_MEM_BLOCKS = (1024); // 4K of table space
111 	}
112 
113 	else if (m <= 64) { // 33 - 64MB - take 32mb
114 		memory_available = 30 * one_meg;
115 		BACKGROUND_BUFFER_SIZE = 15 * one_meg;
116 		ANIMATION_BUFFER_SIZE = 8 * one_meg;
117 		BITMAP_BUFFER_SIZE = 5 * one_meg;
118 		SONICS_BUFFER_SIZE = 2 * one_meg;
119 		MAX_MEM_BLOCKS = (2048); // 8K of table space
120 	}
121 
122 	else if (m <= 128) { // 65 - 128MB - take 64 mb
123 		memory_available = 60 * one_meg;
124 		BACKGROUND_BUFFER_SIZE = 36 * one_meg;
125 		ANIMATION_BUFFER_SIZE = 10 * one_meg;
126 		BITMAP_BUFFER_SIZE = 10 * one_meg;
127 		SONICS_BUFFER_SIZE = 4 * one_meg;
128 		MAX_MEM_BLOCKS = (4096); // 16K of table space
129 	}
130 
131 	else { // Lots and lots and lots of memory - take 128Mb
132 		memory_available = 120 * one_meg;
133 		BACKGROUND_BUFFER_SIZE = 80 * one_meg;
134 		ANIMATION_BUFFER_SIZE = 15 * one_meg;
135 		BITMAP_BUFFER_SIZE = 15 * one_meg;
136 		SONICS_BUFFER_SIZE = 10 * one_meg;
137 		MAX_MEM_BLOCKS = (8192); // 32K of table space
138 	}
139 }
140 
AllocMemory(uint32 & memory_tot)141 uint8 *res_man::AllocMemory(uint32 &memory_tot) {
142 	// use new so the overloaded new gets called
143 	uint8 *memory_b = new uint8[memory_tot];
144 
145 	if (!memory_b) { // could not grab the memory
146 		Zdebug("couldn't malloc %d in Init_memory_manager", total_free_memory);
147 		Fatal_error("Init_memory_manager() couldn't malloc %d bytes [line=%d file=%s]", total_free_memory, __LINE__, __FILE__);
148 	}
149 	return memory_b;
150 }
151 
Fetch_size(const char *,uint32 url_hash,const char * cluster,uint32 cluster_hash)152 uint32 res_man::Fetch_size(const char * /*url*/, uint32 url_hash, const char *cluster, uint32 cluster_hash) {
153 	// try to find the cluster file
154 	RMParams params;
155 	params.url_hash = NULL_HASH;
156 	params.cluster = cluster;
157 	params.cluster_hash = cluster_hash;
158 	params.mode = RM_LOADNOW;
159 	params.not_ready_yet = 0;
160 	int32 cluster_search = FindFile(&params);
161 	params.url_hash = url_hash;
162 
163 	HEADER_NORMAL *hn = GetFileHeader(cluster_search, &params);
164 
165 	// return value of 0 means no such file
166 	if (hn == NULL)
167 		return 0;
168 
169 	return hn->size;
170 }
171 
Test_file(const char * url,uint32 url_hash,const char * cluster,uint32 cluster_hash)172 bool8 res_man::Test_file(const char *url, uint32 url_hash, const char *cluster, uint32 cluster_hash) {
173 	Tdebug("clusters.txt", "**Testing file %s in cluster %s", url, cluster);
174 
175 	// try to find the cluster file
176 	RMParams params;
177 	params.url_hash = NULL_HASH;
178 	params.cluster = cluster;
179 	params.cluster_hash = cluster_hash;
180 	params.mode = RM_LOADNOW;
181 	params.not_ready_yet = 0;
182 	int32 cluster_search = FindFile(&params);
183 	params.url_hash = url_hash;
184 
185 	HEADER_NORMAL *hn = GetFileHeader(cluster_search, &params);
186 
187 	if (hn == NULL)
188 		return 0;
189 
190 	return 1;
191 }
192 
Test_file(const char * url)193 bool8 res_man::Test_file(const char *url) {
194 	pxString path(url);
195 	path.ConvertPath();
196 
197 	Common::File file;
198 
199 	return (bool8)file.exists(path.c_str());
200 }
201 
ReadFile(const char *,RMParams * params)202 void res_man::ReadFile(const char * /*url*/, RMParams *params) {
203 	// params->search is mem_list slot number to load the file into
204 
205 	// Normally the url in params->url is stored, but when we are loading a cluster
206 	// this is empty, so we should use params->cluster
207 	// If we are reading in a cluster
208 
209 	mem_list[params->search].state = MEM_in_use; // slot now being used
210 	mem_list[params->search].url_hash = params->url_hash;
211 	mem_list[params->search].cluster_hash = params->cluster_hash;
212 	mem_list[params->search].total_hash = MAKE_TOTAL_HASH(params->cluster_hash, params->url_hash);
213 
214 	//      now load the file
215 	if (params->mode == RM_LOADNOW) {
216 		if (params->zipped) {
217 			// When loading from a cluster the params->seekpos value holds the position to read from
218 			Tdebug("clusters.txt", "  fseek to pos %d", params->seekpos);
219 			if (!params->_stream->seek(params->seekpos, SEEK_SET))
220 				Fatal_error("Could not fseek to %d bytes in %s", params->seekpos, params->cluster);
221 
222 			memUncompress(mem_list[params->search].ad, params->cluster, params->_stream);
223 		} else {
224 			// When loading from a cluster the params->seekpos value holds the position to read from
225 			Tdebug("clusters.txt", "  fseek to pos %d", params->seekpos);
226 			if (!params->_stream->seek(params->seekpos, SEEK_SET))
227 				Fatal_error("Could not fseek to %d bytes in %s", params->seekpos, params->cluster);
228 
229 			Tdebug("clusters.txt", "  Read %d bytes", params->len);
230 
231 			// hurray, load it in.
232 			if (params->_stream->read(mem_list[params->search].ad, sizeof(char) * params->len) != (uint)params->len)
233 				Fatal_error("Failed to read %d bytes from 0x%X", params->len, params->url_hash);
234 		}
235 
236 		Tdebug("clusters.txt", "  Close handle %x", params->_stream);
237 		delete params->_stream; // close the cluster
238 		params->_stream = NULL;
239 
240 		mem_list[params->search].protect = 0;
241 	}
242 }
243 
OpenFile(int32 & cluster_search,RMParams * params)244 const char *res_man::OpenFile(int32 &cluster_search, RMParams *params) {
245 	pxString rootPath("");
246 	pxString clusterName(params->cluster);
247 	clusterName.ToLower();
248 
249 	pxString clusterPath = rootPath + clusterName;
250 	clusterPath.ConvertPath();
251 
252 	// Are we are trying to open a cluster
253 	if (params->url_hash == NULL_HASH) {
254 		Tdebug("clusters.txt", "  Read in cluster header");
255 		// We are trying to open a cluster.
256 		// The name of the cluster is, in theory, in params->cluster
257 
258 		// Open up the cluster
259 		params->_stream = openDiskFileForBinaryStreamRead(clusterPath.c_str());
260 		Tdebug("clusters.txt", "  open cluster file %s handle %x", clusterPath.c_str(), params->_stream);
261 
262 		if (params->_stream == NULL)
263 			Fatal_error("Res_open cannot *OPEN* cluster file %s", clusterPath.c_str());
264 
265 		// Read in 16 bytes, part of which is the cluster header length
266 
267 		uint32 data[4];
268 
269 		if (params->_stream->read(&data, 16) != 16) {
270 			Fatal_error("res_man::OpenFile cannot read 16 bytes from cluster %s %d", clusterPath.c_str(), params->cluster_hash);
271 		}
272 
273 		params->seekpos = 0;
274 		params->len = data[2];
275 		return params->cluster;
276 	}
277 
278 	HEADER_NORMAL *hn = GetFileHeader(cluster_search, params);
279 	if (hn == NULL) {
280 		// Big error the file wasn't found in the cluster
281 		Fatal_error("res_man::OpenFile couldn't find url %X in cluster %s %X", params->url_hash, params->cluster, params->cluster_hash);
282 	}
283 
284 	// This has to be done here because GetFileHeader can read in data which closes the file
285 	// whose handle is stored in params->fh
286 
287 	// Open up the cluster
288 	params->_stream = openDiskFileForBinaryStreamRead(clusterPath.c_str());
289 	Tdebug("clusters.txt", "  open cluster file %s handle %x", clusterPath.c_str(), params->_stream);
290 
291 	if (params->_stream == NULL)
292 		Fatal_error("Res_open cannot *OPEN* cluster file %s", clusterPath.c_str());
293 
294 	params->seekpos = hn->offset;
295 
296 	// Set the length of the data
297 	if (params->zipped) {
298 		params->_stream->seek(params->seekpos, SEEK_SET);
299 		params->len = fileGetZipLength2(params->_stream); // TODO: Use wrapCompressedStream to solve?
300 	} else
301 		params->len = hn->size;
302 
303 	return NULL;
304 }
305 
306 // Get the header infomation for a particular file from a cluster
307 // Will load the cluster in if need be
GetFileHeader(int32 & cluster_search,RMParams * params)308 HEADER_NORMAL *res_man::GetFileHeader(int32 &cluster_search, RMParams *params) {
309 	Cluster_API *clu;
310 
311 	// If cluster_search = -1 then the cluster was not found so load the cluster in
312 	if (cluster_search == -1) {
313 		uint32 url_hash = params->url_hash;
314 		params->url_hash = NULL_HASH;
315 		uint32 compression = params->compressed; // Cluster headers are not compressed
316 		params->compressed = params->zipped = FALSE8;
317 		clu = (Cluster_API *)LoadFile(cluster_search, params);
318 		cluster_search = params->search;
319 		params->url_hash = url_hash;
320 		params->compressed = params->zipped = compression; // Restore compression
321 	} else {
322 		// The cluster is in the memory pool at position cluster_search
323 		clu = (Cluster_API *)mem_list[cluster_search].ad;
324 	}
325 
326 	// So we are trying to read in a file from a cluster
327 	// and the cluster is now in memory in clu variable
328 
329 	// So just need to search the cluster to find the url_hash value
330 	// and get the position and length to get the data from
331 
332 	// Check the schema value and the ID
333 	if ((clu->schema != CLUSTER_API_SCHEMA) || (*(int32 *)clu->ID != *(int32 *)const_cast<char *>(CLUSTER_API_ID))) {
334 		// Big error unknown cluster filetype
335 		Fatal_error("res_man::GetFileHeader unknown cluster schema or ID %d %s for %s::0x%X", clu->schema, clu->ID, params->cluster, params->url_hash);
336 	}
337 
338 	if (clu->ho.cluster_hash != params->cluster_hash) {
339 		// Big error this cluster has a different internal hash value to the one
340 		// we are looking for
341 //		Fatal_error("res_man::GetFileHeader different internal cluster_hash value %x file %x for %s::0x%X", params->cluster_hash, clu->ho.cluster_hash, params->cluster,
342 //		            params->url_hash);
343 	}
344 
345 	HEADER_NORMAL *hn = clu->hn;
346 	uint32 i;
347 	for (i = 0; i < clu->ho.noFiles; hn++, i++) {
348 		// Hey it has been found
349 		if (hn->hash == params->url_hash)
350 			break;
351 	}
352 
353 	// Check that the file was actually found
354 	if (i == clu->ho.noFiles) {
355 		return NULL;
356 	}
357 
358 	return hn;
359 }
360 
Res_open_cluster(const char *,uint32 &,int32)361 void res_man::Res_open_cluster(const char * /* cluster_url */, uint32 & /* cluster_hash */, int32 /* size */) { Fatal_error("Res_open_cluster not supported on the pc"); }
362 
Res_open_mini_cluster(const char * cluster_url,uint32 & cluster_hash,const char * fake_cluster_url,uint32 & fake_cluster_hash)363 void res_man::Res_open_mini_cluster(const char *cluster_url, uint32 &cluster_hash, const char *fake_cluster_url, uint32 &fake_cluster_hash) {
364 	// open the mini-cluster
365 
366 	uint32 zeroHash = 0;
367 	Cluster_API *clu = (Cluster_API *)Res_open(NULL, zeroHash, cluster_url, cluster_hash);
368 
369 	int32 numFiles = clu->ho.noFiles;
370 
371 	// check none of the fake files exist
372 	// also find total size required
373 
374 	int32 mem_needed = 0;
375 	int32 i;
376 
377 	for (i = 0; i < numFiles; i++) {
378 		HEADER_NORMAL *hn = clu->hn + i;
379 		uint32 check_hash = hn->hash;
380 
381 		if (FindFile(check_hash, fake_cluster_hash, MAKE_TOTAL_HASH(fake_cluster_hash, check_hash)) != -1) {
382 			warning("File %s::%08x exists in res_man so can't load my mini-cluster!", fake_cluster_url, check_hash);
383 			return;
384 		}
385 
386 		int32 fileSize = (hn->size + 7) & (~7);
387 		mem_needed += fileSize;
388 	}
389 
390 	// check mem_needed is multiple of 8
391 
392 	mem_needed = (mem_needed + 7) & (~7);
393 
394 	// now grab enough memory..
395 
396 	uint16 mem_block;
397 	RMParams params; // the params are only used for debugging
398 
399 	params.url_hash = NULL_HASH;
400 	params.cluster = fake_cluster_url;
401 	params.cluster_hash = fake_cluster_hash;
402 
403 	mem_block = (uint16)FindMemBlock(mem_needed, &params);
404 
405 	// ensure the header is still in memory
406 
407 	clu = (Cluster_API *)Res_open(NULL, zeroHash, cluster_url, cluster_hash);
408 
409 	// now load in the body...
410 	// from first file upwards
411 
412 	// DiscRead(mem_list[mem_block].ad,(clu->hn)->offset,mem_needed,NULL,clu->ho.cdpos);
413 
414 	pxString rootPath("");
415 	pxString clusterName(fake_cluster_url);
416 	clusterName.ToLower();
417 
418 	pxString clusterPath = rootPath + clusterName;
419 	clusterPath.ConvertPath();
420 
421 	Common::SeekableReadStream *stream;
422 
423 	// open
424 	stream = openDiskFileForBinaryStreamRead(clusterPath.c_str());
425 
426 	// seek
427 	stream->seek((clu->hn)->offset, SEEK_SET);
428 
429 	// read
430 	stream->read(mem_list[mem_block].ad, mem_needed);
431 
432 	// close
433 	delete stream;
434 
435 	// now tie up the files...
436 
437 	uint8 *ad = mem_list[mem_block].ad;
438 	uint16 current_parent = mem_list[mem_block].parent;
439 	uint16 current_child = mem_list[mem_block].child;
440 
441 	for (i = 0; i < numFiles; i++) {
442 		// printf("tying up files %d\n",i);
443 
444 		HEADER_NORMAL *hn = clu->hn + i;
445 
446 		if (i != 0) {
447 			current_parent = mem_block;
448 
449 			// spawn a new block for all but first...
450 			mem_block = Fetch_spawn(current_parent);
451 
452 			// set parent...
453 			mem_list[current_parent].child = mem_block;
454 
455 			// setup current...
456 			mem_list[mem_block].parent = current_parent;
457 			mem_list[mem_block].child = current_child;
458 
459 			// setup child...
460 			mem_list[current_child].parent = mem_block;
461 		}
462 
463 		mem_list[mem_block].url_hash = hn->hash;
464 		mem_list[mem_block].cluster_hash = fake_cluster_hash;
465 		mem_list[mem_block].total_hash = MAKE_TOTAL_HASH(fake_cluster_hash, hn->hash);
466 
467 		mem_list[mem_block].ad = ad;
468 
469 		mem_list[mem_block].state = MEM_in_use;
470 		mem_list[mem_block].protect = 0;
471 		mem_list[mem_block].age = current_time_frame;
472 
473 		// adjusted size
474 		int32 fileSize = (hn->size + 7) & (~7);
475 
476 		mem_list[mem_block].size = fileSize;
477 
478 		// one more file open
479 		number_files_open++;
480 
481 		// next file has next address along
482 		ad += fileSize;
483 	}
484 }
485 
Garbage_removal()486 void res_man::Garbage_removal() {
487 	// does nothing at present on pc
488 }
489 
490 } // End of namespace ICB
491