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