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 #include "toon/resource.h"
24 #include "common/debug.h"
25 #include "common/file.h"
26 #include "common/memstream.h"
27 #include "common/substream.h"
28 #include "toon/toon.h"
29 
30 namespace Toon {
31 
Resources(ToonEngine * vm)32 Resources::Resources(ToonEngine *vm) : _vm(vm), _cacheSize(0) {
33 	_resourceCache.clear();
34 }
35 
~Resources()36 Resources::~Resources() {
37 
38 	while (!_resourceCache.empty()) {
39 		CacheEntry *temp = _resourceCache.back();
40 		_resourceCache.pop_back();
41 		delete temp;
42 	}
43 
44 	while (!_pakFiles.empty()) {
45 		PakFile *temp = _pakFiles.back();
46 		_pakFiles.pop_back();
47 		delete temp;
48 	}
49 
50 	purgeFileData();
51 }
52 
removePackageFromCache(const Common::String & packName)53 void Resources::removePackageFromCache(const Common::String &packName) {
54 	// I'm not sure what's a good strategy here. It seems unnecessary to
55 	// actually remove the cached resources, because the player may be
56 	// wandering back and forth between rooms. So for now, do nothing.
57 }
58 
getFromCache(const Common::String & fileName,uint32 * fileSize,uint8 ** fileData)59 bool Resources::getFromCache(const Common::String &fileName, uint32 *fileSize, uint8 **fileData) {
60 	for (Common::Array<CacheEntry *>::iterator entry = _resourceCache.begin(); entry != _resourceCache.end(); ++entry) {
61 		if ((*entry)->_data && (*entry)->_fileName.compareToIgnoreCase(fileName) == 0) {
62 			debugC(5, kDebugResource, "getFromCache(%s) - Got %d bytes from %s", fileName.c_str(), (*entry)->_size, (*entry)->_packName.c_str());
63 			(*entry)->_age = 0;
64 			*fileSize = (*entry)->_size;
65 			*fileData = (*entry)->_data;
66 			return true;
67 			}
68 		}
69 	return false;
70 }
71 
addToCache(const Common::String & packName,const Common::String & fileName,uint32 fileSize,uint8 * fileData)72 void Resources::addToCache(const Common::String &packName, const Common::String &fileName, uint32 fileSize, uint8 *fileData) {
73 	debugC(5, kDebugResource, "addToCache(%s, %s, %d) - Total Size: %d", packName.c_str(), fileName.c_str(), fileSize, _cacheSize + fileSize);
74 	for (Common::Array<CacheEntry *>::iterator entry = _resourceCache.begin(); entry != _resourceCache.end(); ++entry) {
75 		if ((*entry)->_data) {
76 			(*entry)->_age++;
77 		}
78 	}
79 	_cacheSize += fileSize;
80 
81 	while (_cacheSize > MAX_CACHE_SIZE) {
82 		CacheEntry *bestEntry = 0;
83 		for (Common::Array<CacheEntry *>::iterator entry = _resourceCache.begin(); entry != _resourceCache.end(); ++entry) {
84 			if ((*entry)->_data) {
85 				if (!bestEntry || ((*entry)->_age >= bestEntry->_age && (*entry)->_size >= bestEntry->_size)) {
86 					bestEntry = *entry;
87 				}
88 			}
89 		}
90 		if (!bestEntry)
91 			break;
92 
93 		free(bestEntry->_data);
94 		bestEntry->_data = 0;
95 		_cacheSize -= bestEntry->_size;
96 		debugC(5, kDebugResource, "Freed %s (%s) to reclaim %d bytes", bestEntry->_fileName.c_str(), bestEntry->_packName.c_str(), bestEntry->_size);
97 	}
98 
99 	for (Common::Array<CacheEntry *>::iterator entry = _resourceCache.begin(); entry != _resourceCache.end(); ++entry) {
100 		if (!(*entry)->_data) {
101 			(*entry)->_packName = packName;
102 			(*entry)->_fileName = fileName;
103 			(*entry)->_age = 0;
104 			(*entry)->_size = fileSize;
105 			(*entry)->_data = fileData;
106 			return;
107 		}
108 	}
109 
110 	CacheEntry *entry = new CacheEntry();
111 	entry->_packName = packName;
112 	entry->_fileName = fileName;
113 	entry->_size = fileSize;
114 	entry->_data = fileData;
115 	_resourceCache.push_back(entry);
116 }
117 
openPackage(const Common::String & fileName)118 void Resources::openPackage(const Common::String &fileName) {
119 	debugC(1, kDebugResource, "openPackage(%s)", fileName.c_str());
120 
121 	Common::File file;
122 	bool opened = file.open(fileName);
123 
124 	if (!opened)
125 		return;
126 
127 	PakFile *pakFile = new PakFile();
128 	pakFile->open(&file, fileName);
129 
130 	file.close();
131 
132 	_pakFiles.push_back(pakFile);
133 }
134 
closePackage(const Common::String & fileName)135 void Resources::closePackage(const Common::String &fileName) {
136 
137 	removePackageFromCache(fileName);
138 	for (uint32 i = 0; i < _pakFiles.size(); i++) {
139 		if (_pakFiles[i]->getPackName() == fileName) {
140 			delete _pakFiles[i];
141 			_pakFiles.remove_at(i);
142 			return;
143 		}
144 	}
145 }
146 
getFileData(const Common::String & fileName,uint32 * fileSize)147 uint8 *Resources::getFileData(const Common::String &fileName, uint32 *fileSize) {
148 	debugC(4, kDebugResource, "getFileData(%s, fileSize)", fileName.c_str());
149 
150 	// first try to find files outside of .pak
151 	// some patched files have not been included in package.
152 	if (Common::File::exists(fileName)) {
153 		Common::File file;
154 		bool opened = file.open(fileName);
155 		if (!opened)
156 			return 0;
157 
158 		*fileSize = file.size();
159 		uint8 *memory = (uint8 *)new uint8[*fileSize];
160 		file.read(memory, *fileSize);
161 		file.close();
162 		_allocatedFileData.push_back(memory);
163 		return memory;
164 	} else {
165 
166 		uint32 locFileSize = 0;
167 		uint8 *locFileData = 0;
168 
169 		if (getFromCache(fileName, &locFileSize, &locFileData)) {
170 			*fileSize = locFileSize;
171 			return locFileData;
172 		}
173 
174 		for (uint32 i = 0; i < _pakFiles.size(); i++) {
175 
176 			locFileData = _pakFiles[i]->getFileData(fileName, &locFileSize);
177 			if (locFileData) {
178 				*fileSize = locFileSize;
179 				addToCache(_pakFiles[i]->getPackName(), fileName, locFileSize, locFileData);
180 				return locFileData;
181 			}
182 		}
183 		return 0;
184 	}
185 }
186 
openFile(const Common::String & fileName)187 Common::SeekableReadStream *Resources::openFile(const Common::String &fileName) {
188 	debugC(1, kDebugResource, "openFile(%s)", fileName.c_str());
189 
190 	// first try to find files outside of .pak
191 	// some patched files have not been included in package.
192 	if (Common::File::exists(fileName)) {
193 		Common::File *file = new Common::File();
194 		bool opened = file->open(fileName);
195 		if (!opened) {
196 			delete file;
197 			return 0;
198 		}
199 		return file;
200 	} else {
201 		for (uint32 i = 0; i < _pakFiles.size(); i++) {
202 			Common::SeekableReadStream *stream = 0;
203 			stream = _pakFiles[i]->createReadStream(fileName);
204 			if (stream)
205 				return stream;
206 		}
207 
208 		return 0;
209 	}
210 }
211 
purgeFileData()212 void Resources::purgeFileData() {
213 	for (uint32 i = 0; i < _allocatedFileData.size(); i++) {
214 		delete[] _allocatedFileData[i];
215 	}
216 	_allocatedFileData.clear();
217 }
218 
createReadStream(const Common::String & fileName)219 Common::SeekableReadStream *PakFile::createReadStream(const Common::String &fileName) {
220 	debugC(1, kDebugResource, "createReadStream(%s)", fileName.c_str());
221 
222 	uint32 fileSize = 0;
223 	uint8 *buffer = getFileData(fileName, &fileSize);
224 	if (buffer)
225 		return new Common::MemoryReadStream(buffer, fileSize, DisposeAfterUse::YES);
226 	else
227 		return 0;
228 }
229 
getFileData(const Common::String & fileName,uint32 * fileSize)230 uint8 *PakFile::getFileData(const Common::String &fileName, uint32 *fileSize) {
231 	debugC(4, kDebugResource, "getFileData(%s, fileSize)", fileName.c_str());
232 
233 	for (uint32 i = 0; i < _numFiles; i++) {
234 		if (fileName.compareToIgnoreCase(_files[i]._name) == 0) {
235 			Common::File file;
236 			if (file.open(_packName)) {
237 					*fileSize = _files[i]._size;
238 					file.seek(_files[i]._offset);
239 
240 					// Use malloc() because that's what MemoryReadStream
241 					// uses to dispose of the memory when it's done.
242 					uint8 *buffer = (uint8 *)malloc(*fileSize);
243 					file.read(buffer, *fileSize);
244 					file.close();
245 					return buffer;
246 			}
247 		}
248 	}
249 
250 	return 0;
251 }
252 
open(Common::SeekableReadStream * rs,const Common::String & packName)253 void PakFile::open(Common::SeekableReadStream *rs, const Common::String &packName) {
254 	debugC(1, kDebugResource, "open(rs)");
255 
256 	char buffer[64];
257 	int32 currentPos = 0;
258 	_numFiles = 0;
259 	_packName = packName;
260 
261 	while (1) {
262 		rs->seek(currentPos);
263 		rs->read(buffer, 64);
264 
265 		int32 offset = READ_LE_UINT32(buffer);
266 		char *name = buffer + 4;
267 
268 		if (!*name)
269 			break;
270 
271 		int32 nameSize = strlen(name) + 1;
272 		int32 nextOffset = READ_LE_UINT32(buffer + 4 + nameSize);
273 		currentPos += 4 + nameSize;
274 
275 		PakFile::File newFile;
276 		Common::strlcpy(newFile._name, name, sizeof(newFile._name));
277 		newFile._offset = offset;
278 		newFile._size = nextOffset - offset;
279 		_numFiles++;
280 		_files.push_back(newFile);
281 	}
282 }
283 
close()284 void PakFile::close() {
285 }
286 
PakFile()287 PakFile::PakFile() {
288 	_numFiles = 0;
289 }
290 
~PakFile()291 PakFile::~PakFile() {
292 	close();
293 }
294 
295 } // End of namespace Toon
296