1 /***************************************
2 ** Tsunagari Tile Engine **
4 ** Copyright 2011-2013 PariahSoft LLC **
5 ***************************************/
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
30 #include <boost/scoped_ptr.hpp>
31 #include <boost/shared_ptr.hpp>
32 #include <Gosu/Bitmap.hpp>
33 #include <Gosu/Image.hpp>
34 #include <Gosu/IO.hpp>
38 #include "cache-template.cpp"
39 #include "client-conf.h"
40 #include "formatter.h"
43 #include "python-bindings-template.cpp"
49 #define ASSERT(x) if (!(x)) { return false; }
51 typedef boost::shared_ptr
<xmlDtd
> DTDRef
;
52 typedef boost::shared_ptr
<std::string
> StringRef
;
55 // Caches that store processed, game-ready objects. Garbage collected.
56 Cache
<ImageRef
> images
;
57 Cache
<TiledImageRef
> tiles
;
58 Cache
<SampleRef
> sounds
;
60 Cache
<StringRef
> texts
;
62 // DTDs don't expire. No garbage collection.
63 typedef std::map
<std::string
, DTDRef
> DTDMap
;
68 static std::string
path(const std::string
& entryName
)
70 // XXX: archive might not be world
71 return conf
.worldFilename
+ "/" + entryName
;
75 static bool readFromDisk(const std::string
& name
, T
& buf
)
80 if (!PHYSFS_exists(name
.c_str())) {
81 Log::err("Reader", Formatter("%: file missing")
86 zf
= PHYSFS_openRead(name
.c_str());
88 Log::err("Reader", Formatter("%: error opening file: %")
89 % path(name
) % PHYSFS_getLastError());
93 size
= PHYSFS_fileLength(zf
);
95 Log::err("Reader", Formatter("%: could not determine file size: %")
96 % path(name
) % PHYSFS_getLastError());
100 if (size
> std::numeric_limits
<uint32_t>::max()) {
101 // FIXME: Technically, we just need to issue multiple calls to
102 // PHYSFS_read. Fix when needed.
103 Log::err("Reader", Formatter("%: file too long (>4GB)")
115 if (PHYSFS_read(zf
, (char*)(buf
.data()),
116 (PHYSFS_uint32
)size
, 1) != 1) {
117 Log::err("Reader", Formatter("%: error reading file: %")
118 % path(name
) % PHYSFS_getLastError());
127 // FIXME: Should be moved to xml.cpp!!!!!!
128 static DTDRef
parseDTD(const std::string
& path
)
130 xmlCharEncoding enc
= XML_CHAR_ENCODING_NONE
;
132 std::string bytes
= Reader::readString(path
);
136 xmlParserInputBuffer
* input
= xmlParserInputBufferCreateMem(
137 bytes
.c_str(), (int)bytes
.size(), enc
);
141 xmlDtd
* dtd
= xmlIOParseDTD(NULL
, input
, enc
);
145 return DTDRef(dtd
, xmlFreeDtd
);
148 // FIXME: Should be moved to xml.cpp!!!!!!
149 static bool preloadDTDs()
151 ASSERT(dtds
["dtd/area.dtd"] = parseDTD("dtd/area.dtd"));
152 ASSERT(dtds
["dtd/entity.dtd"] = parseDTD("dtd/entity.dtd"));
153 ASSERT(dtds
["dtd/tsx.dtd"] = parseDTD("dtd/tsx.dtd"));
154 ASSERT(dtds
["dtd/world.dtd"] = parseDTD("dtd/world.dtd"));
158 static xmlDtd
* getDTD(const std::string
& name
)
160 DTDMap::iterator it
= dtds
.find(name
);
161 return it
== dtds
.end() ? NULL
: it
->second
.get();
164 static XMLDoc
* readXMLDoc(const std::string
& name
,
165 const std::string
& dtdPath
)
167 std::string p
= path(name
);
168 std::string data
= Reader::readString(name
);
169 xmlDtd
* dtd
= getDTD(dtdPath
);
171 if (!dtd
|| data
.empty())
173 XMLDoc
* doc
= new XMLDoc
;
174 if (!doc
->init(p
, data
, dtd
)) {
181 static bool callInitpy(const std::string
& archivePath
)
183 ASSERT(Reader::prependPath(archivePath
));
184 bool exists
= Reader::resourceExists("__init__.py");
185 Log::info(archivePath
,
186 std::string("__init__.py: ") +
187 (exists
? "found" : "not found"));
189 // FIXME: Python will cache the __init__ module with no path prefix
190 ASSERT(Script::create("__init__"));
191 ASSERT(Reader::rmPath(archivePath
));
205 bool Reader::init(char* argv0
)
207 ASSERT(PHYSFS_init(argv0
) != 0);
209 // If any of our archives contain a file called "__init__.py", call it.
210 for (Conf::StringVector::const_iterator it
= conf
.dataPath
.begin(); it
!= conf
.dataPath
.end(); it
++) {
211 const std::string archive
= *it
;
212 ASSERT(callInitpy(archive
));
214 ASSERT(callInitpy(BASE_ZIP_PATH
));
216 ASSERT(prependPath(BASE_ZIP_PATH
));
218 // DTDs must be loaded from BASE_ZIP. They cannot be allowed to be
219 // loaded from the world.
220 ASSERT(preloadDTDs());
222 ASSERT(prependPath(conf
.worldFilename
));
223 for (Conf::StringVector::const_iterator it
= conf
.dataPath
.begin(); it
!= conf
.dataPath
.end(); it
++) {
224 const std::string archive
= *it
;
225 ASSERT(prependPath(archive
));
231 void Reader::deinit()
236 bool Reader::prependPath(const std::string
& path
)
238 int err
= PHYSFS_mount(path
.c_str(), NULL
, 0);
240 Log::fatal("Reader", Formatter("%: could not open archive: %")
241 % path
% PHYSFS_getLastError());
245 pythonPrependPath(path
);
250 bool Reader::appendPath(const std::string
& path
)
252 int err
= PHYSFS_mount(path
.c_str(), NULL
, 1);
254 Log::fatal("Reader", Formatter("%: could not open archive: %")
255 % path
% PHYSFS_getLastError());
264 bool Reader::rmPath(const std::string
& path
)
266 int err
= PHYSFS_removeFromSearchPath(path
.c_str());
268 Log::err("Reader", Formatter("libphysfs: %: %")
269 % path
% PHYSFS_getLastError());
276 bool Reader::resourceExists(const std::string
& name
)
278 return PHYSFS_exists(name
.c_str());
281 bool Reader::directoryExists(const std::string
& name
)
283 return resourceExists(name
) && PHYSFS_isDirectory(name
.c_str());
286 bool Reader::fileExists(const std::string
& name
)
288 return resourceExists(name
) && !PHYSFS_isDirectory(name
.c_str());
291 Gosu::Buffer
* Reader::readBuffer(const std::string
& name
)
293 Gosu::Buffer
* buf
= new Gosu::Buffer();
295 if (readFromDisk(name
, *buf
)) {
304 std::string
Reader::readString(const std::string
& name
)
307 return readFromDisk(name
, str
) ? str
: "";
310 ImageRef
Reader::getImage(const std::string
& name
)
312 ImageRef existing
= images
.lifetimeRequest(name
);
316 boost::scoped_ptr
<Gosu::Buffer
> buffer(readBuffer(name
));
320 ImageRef
result(Image::create(buffer
->data(), buffer
->size()));
324 images
.lifetimePut(name
, result
);
328 TiledImageRef
Reader::getTiledImage(const std::string
& name
,
331 TiledImageRef existing
= tiles
.momentaryRequest(name
);
335 boost::scoped_ptr
<Gosu::Buffer
> buffer(readBuffer(name
));
337 return TiledImageRef();
339 TiledImageRef
result(
340 TiledImage::create(buffer
->data(), buffer
->size(), w
, h
)
343 return TiledImageRef();
345 tiles
.momentaryPut(name
, result
);
349 SampleRef
Reader::getSample(const std::string
& name
)
351 if (!conf
.audioEnabled
)
354 SampleRef existing
= sounds
.lifetimeRequest(name
);
358 boost::scoped_ptr
<Gosu::Buffer
> buffer(readBuffer(name
));
361 SampleRef
result(new Sound(new Gosu::Sample(buffer
->frontReader())));
363 sounds
.lifetimePut(name
, result
);
367 XMLRef
Reader::getXMLDoc(const std::string
& name
,
368 const std::string
& dtdFile
)
370 XMLRef existing
= xmls
.momentaryRequest(name
);
374 XMLRef
result(readXMLDoc(name
, dtdFile
));
376 xmls
.momentaryPut(name
, result
);
380 std::string
Reader::getText(const std::string
& name
)
382 StringRef existing
= texts
.momentaryRequest(name
);
384 return *existing
.get();
386 StringRef
result(new std::string(readString(name
)));
388 texts
.momentaryPut(name
, result
);
389 return *result
.get();
392 void Reader::garbageCollect()
394 images
.garbageCollect();
395 tiles
.garbageCollect();
396 sounds
.garbageCollect();
397 // songs.garbageCollect();
398 xmls
.garbageCollect();
399 texts
.garbageCollect();
404 // FIXME: Broken with shift to singleton. No instantiated object to bind.
405 // Fix will require a stub object.
408 using namespace boost::python
;
410 class_
<Reader
>("Reader", no_init
)
411 .def("resource_exists", &Reader::resourceExists
)
412 .def("run_python_script", &Reader::runPythonScript
)
413 .def("get_text", &Reader::getText
);
414 pythonSetGlobal("Reader");