Add license text to the top of every source file.
[Tsunagari.git] / src / resourcer.cpp
blob3aeb220f734ae7136bceac9a809777153c907354
1 /*********************************
2 ** Tsunagari Tile Engine **
3 ** resourcer.cpp **
4 ** Copyright 2011-2012 OmegaSDG **
5 *********************************/
7 // **********
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to
10 // deal in the Software without restriction, including without limitation the
11 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
12 // sell copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
24 // IN THE SOFTWARE.
25 // **********
27 #include <errno.h>
28 #include <stdlib.h>
30 #include <boost/foreach.hpp>
31 #include <boost/format.hpp>
32 #include <boost/python.hpp>
33 #include <boost/scoped_array.hpp>
34 #include <boost/scoped_ptr.hpp>
35 #include <boost/shared_ptr.hpp>
36 #include <Gosu/Bitmap.hpp>
37 #include <Gosu/Image.hpp>
38 #include <Gosu/IO.hpp>
39 #include <physfs.h>
41 #include "client-conf.h"
42 #include "log.h"
43 #include "python.h"
44 #include "resourcer.h"
45 #include "window.h"
46 #include "xml.h"
48 #define ASSERT(x) if (!(x)) { return false; }
50 typedef boost::scoped_ptr<Gosu::Buffer> BufferPtr;
53 static Resourcer* globalResourcer = NULL;
55 Resourcer* Resourcer::instance()
57 return globalResourcer;
60 Resourcer::Resourcer()
62 globalResourcer = this;
63 pythonSetGlobal("Resourcer", this);
66 Resourcer::~Resourcer()
68 PHYSFS_deinit();
71 static bool callInitpy(Resourcer* rc, const std::string& archivePath)
73 ASSERT(rc->prependPath(archivePath));
74 bool exists = rc->resourceExists("init.py");
75 Log::info(archivePath,
76 std::string("init.py: ") +
77 (exists ? "found" : "not found"));
78 if (exists)
79 ASSERT(rc->runPythonScript("init.py"));
80 ASSERT(rc->rmPath(archivePath));
81 return true;
84 bool Resourcer::init(char* argv0)
86 ASSERT(PHYSFS_init(argv0) != 0);
88 // If any of our archives contain a file called "init.py", call it.
89 BOOST_FOREACH(std::string archivePath, conf.dataPath)
90 ASSERT(callInitpy(this, archivePath));
91 ASSERT(callInitpy(this, BASE_ZIP_PATH));
93 ASSERT(prependPath(BASE_ZIP_PATH));
95 // DTDs must be loaded from BASE_ZIP. They cannot be allowed to be
96 // loaded from the world.
97 ASSERT(preloadDTDs());
99 ASSERT(prependPath(conf.worldFilename));
100 BOOST_FOREACH(std::string pathname, conf.dataPath)
101 ASSERT(prependPath(pathname));
103 return true;
106 bool Resourcer::prependPath(const std::string& path)
108 using namespace boost;
110 int err = PHYSFS_mount(path.c_str(), NULL, 0);
111 if (err == 0) {
112 Log::fatal("Resourcer", str(
113 format("%s: could not open archive: %s")
114 % path % PHYSFS_getLastError()));
115 return false;
118 return true;
121 bool Resourcer::appendPath(const std::string& path)
123 using namespace boost;
125 int err = PHYSFS_mount(path.c_str(), NULL, 1);
126 if (err == 0) {
127 Log::fatal("Resourcer", str(
128 format("%s: could not open archive: %s")
129 % path % PHYSFS_getLastError()));
130 return false;
133 return true;
136 bool Resourcer::rmPath(const std::string& path)
138 using namespace boost;
140 int err = PHYSFS_removeFromSearchPath(path.c_str());
141 if (err == 0) {
142 Log::err("Resourcer", str(format("libphysfs: %s: %s")
143 % path % PHYSFS_getLastError()));
144 return false;
147 return true;
150 bool Resourcer::resourceExists(const std::string& name) const
152 return PHYSFS_exists(name.c_str());
155 ImageRef Resourcer::getImage(const std::string& name)
157 ImageRef existing = images.lifetimeRequest(name);
158 if (existing)
159 return existing;
161 BufferPtr buffer(readBuffer(name));
162 if (!buffer)
163 return ImageRef();
165 ImageRef result(Image::create(buffer->data(), buffer->size()));
166 if (!result)
167 return ImageRef();
169 images.lifetimePut(name, result);
170 return result;
173 TiledImageRef Resourcer::getTiledImage(const std::string& name,
174 int w, int h)
176 TiledImageRef existing = tiles.momentaryRequest(name);
177 if (existing)
178 return existing;
180 BufferPtr buffer(readBuffer(name));
181 if (!buffer)
182 return TiledImageRef();
184 TiledImageRef result(
185 TiledImage::create(buffer->data(), buffer->size(), w, h)
187 if (!result)
188 return TiledImageRef();
190 tiles.momentaryPut(name, result);
191 return result;
194 SampleRef Resourcer::getSample(const std::string& name)
196 if (!conf.audioEnabled)
197 return SampleRef();
199 SampleRef existing = sounds.lifetimeRequest(name);
200 if (existing)
201 return existing;
203 BufferPtr buffer(readBuffer(name));
204 if (!buffer)
205 return SampleRef();
206 SampleRef result(new Sound(new Gosu::Sample(buffer->frontReader())));
208 sounds.lifetimePut(name, result);
209 return result;
212 SongRef Resourcer::getSong(const std::string& name)
214 if (!conf.audioEnabled)
215 return SongRef();
217 SongRef existing = songs.lifetimeRequest(name);
218 if (existing)
219 return existing;
221 BufferPtr buffer(readBuffer(name));
222 if (!buffer)
223 return SongRef();
224 SongRef result(new Gosu::Song(buffer->frontReader()));
226 songs.lifetimePut(name, result);
227 return result;
230 XMLRef Resourcer::getXMLDoc(const std::string& name,
231 const std::string& dtdFile)
233 XMLRef existing = xmls.momentaryRequest(name);
234 if (existing)
235 return existing;
237 XMLRef result(readXMLDoc(name, dtdFile));
239 xmls.momentaryPut(name, result);
240 return result;
243 bool Resourcer::runPythonScript(const std::string& name)
245 PyCodeObject* existing = codes.momentaryRequest(name);
246 if (existing)
247 return pythonExec(existing);
249 std::string code = readString(name);
250 PyCodeObject* result = code.size() ?
251 pythonCompile(name.c_str(), code.c_str()) : NULL;
253 codes.momentaryPut(name, result);
254 return pythonExec(result);
257 std::string Resourcer::getText(const std::string& name)
259 StringRef existing = texts.momentaryRequest(name);
260 if (existing)
261 return *existing.get();
263 StringRef result(new std::string(readString(name)));
265 texts.momentaryPut(name, result);
266 return *result.get();
269 void Resourcer::garbageCollect()
271 images.garbageCollect();
272 tiles.garbageCollect();
273 sounds.garbageCollect();
274 songs.garbageCollect();
275 xmls.garbageCollect();
276 texts.garbageCollect();
279 // FIXME: Should be moved to xml.cpp!!!!!!
280 DTDRef Resourcer::parseDTD(const std::string& path)
282 xmlCharEncoding enc = XML_CHAR_ENCODING_NONE;
284 std::string bytes = readString(path);
285 if (bytes.empty())
286 return DTDRef();
288 xmlParserInputBuffer* input = xmlParserInputBufferCreateMem(
289 bytes.c_str(), (int)bytes.size(), enc);
290 if (!input)
291 return DTDRef();
293 xmlDtd* dtd = xmlIOParseDTD(NULL, input, enc);
294 if (!dtd)
295 return DTDRef();
297 return DTDRef(dtd, xmlFreeDtd);
300 // FIXME: Should be moved to xml.cpp!!!!!!
301 bool Resourcer::preloadDTDs()
303 ASSERT(dtds["dtd/area.dtd"] = parseDTD("dtd/area.dtd"));
304 ASSERT(dtds["dtd/entity.dtd"] = parseDTD("dtd/entity.dtd"));
305 ASSERT(dtds["dtd/world.dtd"] = parseDTD("dtd/world.dtd"));
306 return true;
309 XMLDoc* Resourcer::readXMLDoc(const std::string& name,
310 const std::string& dtdPath)
312 std::string p = path(name);
313 std::string data = readString(name);
314 xmlDtd* dtd = getDTD(dtdPath);
316 if (!dtd || data.empty())
317 return NULL;
318 XMLDoc* doc = new XMLDoc;
319 if (!doc->init(p, data, dtd)) {
320 delete doc;
321 return NULL;
323 return doc;
326 xmlDtd* Resourcer::getDTD(const std::string& name)
328 TextMap::iterator it = dtds.find(name);
329 return it == dtds.end() ? NULL : it->second.get();
332 Gosu::Buffer* Resourcer::readBuffer(const std::string& name)
334 Gosu::Buffer* buf = new Gosu::Buffer();
336 if (readFromDisk(name, *buf)) {
337 return buf;
339 else {
340 delete buf;
341 return NULL;
345 std::string Resourcer::readString(const std::string& name)
347 std::string str;
348 return readFromDisk(name, str) ? str : "";
351 template <class T>
352 bool Resourcer::readFromDisk(const std::string& name, T& buf)
354 using namespace boost;
356 PHYSFS_sint64 size;
357 PHYSFS_File* zf;
359 if (!PHYSFS_exists(name.c_str())) {
360 Log::err("Resourcer", str(format("%s: file missing")
361 % path(name)));
362 return false;
365 zf = PHYSFS_openRead(name.c_str());
366 if (!zf) {
367 Log::err("Resourcer", str(format("%s: error opening file: %s")
368 % path(name) % PHYSFS_getLastError()));
369 return false;
372 size = PHYSFS_fileLength(zf);
373 if (size == -1) {
374 Log::err("Resourcer", str(
375 format("%s: could not determine file size: %s")
376 % path(name) % PHYSFS_getLastError()));
377 PHYSFS_close(zf);
378 return false;
380 if (size > std::numeric_limits<uint32_t>::max()) {
381 // FIXME: Technically, we just need to issue multiple calls to
382 // PHYSFS_read. Fix when needed.
383 Log::err("Resourcer", str(format("%s: file too long (>4GB)")
384 % path(name)));
385 PHYSFS_close(zf);
386 return false;
389 buf.resize(size);
390 if (size == 0) {
391 PHYSFS_close(zf);
392 return true;
395 if (PHYSFS_read(zf, (char*)(buf.data()),
396 (PHYSFS_uint32)size, 1) != 1) {
397 Log::err("Resourcer", str(format("%s: error reading file: %s")
398 % path(name) % PHYSFS_getLastError()));
399 PHYSFS_close(zf);
400 return false;
403 PHYSFS_close(zf);
404 return true;
407 std::string Resourcer::path(const std::string& entryName) const
409 // XXX: archive might not be world
410 return conf.worldFilename + "/" + entryName;
415 void exportResourcer()
417 using namespace boost::python;
419 class_<Resourcer>("Resourcer", no_init)
420 .def("resource_exists", &Resourcer::resourceExists)
421 .def("run_python_script", &Resourcer::runPythonScript)
422 .def("get_text", &Resourcer::getText);