Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Xeromyces.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2012 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include <vector>
21 #include <set>
22 #include <map>
23 #include <stack>
24 #include <algorithm>
25 
26 #include "maths/MD5.h"
27 #include "ps/CacheLoader.h"
28 #include "ps/CLogger.h"
29 #include "ps/Filesystem.h"
30 #include "Xeromyces.h"
31 
32 #include <libxml/parser.h>
33 
34 static void errorHandler(void* UNUSED(userData), xmlErrorPtr error)
35 {
36  // Strip a trailing newline
37  std::string message = error->message;
38  if (message.length() > 0 && message[message.length()-1] == '\n')
39  message.erase(message.length()-1);
40 
41  LOGERROR(L"CXeromyces: Parse %ls: %hs:%d: %hs",
42  error->level == XML_ERR_WARNING ? L"warning" : L"error",
43  error->file, error->line, message.c_str());
44  // TODO: The (non-fatal) warnings and errors don't get stored in the XMB,
45  // so the caching is less transparent than it should be
46 }
47 
48 static bool g_XeromycesStarted = false;
50 {
51  ENSURE(!g_XeromycesStarted);
52  xmlInitParser();
53  xmlSetStructuredErrorFunc(NULL, &errorHandler);
54  g_XeromycesStarted = true;
55 }
56 
58 {
59  ENSURE(g_XeromycesStarted);
60  xmlCleanupParser();
61  xmlSetStructuredErrorFunc(NULL, NULL);
62  g_XeromycesStarted = false;
63 }
64 
65 PSRETURN CXeromyces::Load(const PIVFS& vfs, const VfsPath& filename)
66 {
67  ENSURE(g_XeromycesStarted);
68 
69  CCacheLoader cacheLoader(vfs, L".xmb");
70 
71  // Arbitrary version number - change this if we update the code and
72  // need to invalidate old users' caches
73  u32 version = 1;
74 
75  VfsPath xmbPath;
76  Status ret = cacheLoader.TryLoadingCached(filename, MD5(), version, xmbPath);
77 
78  if (ret == INFO::OK)
79  {
80  // Found a cached XMB - load it
81  if (ReadXMBFile(vfs, xmbPath))
82  return PSRETURN_OK;
83  // If this fails then we'll continue and (re)create the loose cache -
84  // this failure legitimately happens due to partially-written XMB files.
85  }
86  else if (ret == INFO::SKIPPED)
87  {
88  // No cached version was found - we'll need to create it
89  }
90  else
91  {
92  ENSURE(ret < 0);
93 
94  // No source file or archive cache was found, so we can't load the
95  // XML file at all
96  LOGERROR(L"CCacheLoader failed to find archived or source file for: \"%ls\"", filename.string().c_str());
98  }
99 
100  // XMB isn't up to date with the XML, so rebuild it
101  return ConvertFile(vfs, filename, xmbPath);
102 }
103 
104 bool CXeromyces::GenerateCachedXMB(const PIVFS& vfs, const VfsPath& sourcePath, VfsPath& archiveCachePath)
105 {
106  CCacheLoader cacheLoader(vfs, L".xmb");
107 
108  archiveCachePath = cacheLoader.ArchiveCachePath(sourcePath);
109 
110  return (ConvertFile(vfs, sourcePath, VfsPath("cache") / archiveCachePath) == PSRETURN_OK);
111 }
112 
113 PSRETURN CXeromyces::ConvertFile(const PIVFS& vfs, const VfsPath& filename, const VfsPath& xmbPath)
114 {
115  CVFSFile input;
116  if (input.Load(vfs, filename))
117  {
118  LOGERROR(L"CXeromyces: Failed to open XML file %ls", filename.string().c_str());
120  }
121 
122  CStr8 filename8(CStrW(filename.string()).ToUTF8());
123  xmlDocPtr doc = xmlReadMemory((const char*)input.GetBuffer(), (int)input.GetBufferSize(),
124  filename8.c_str(), NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA);
125  if (! doc)
126  {
127  LOGERROR(L"CXeromyces: Failed to parse XML file %ls", filename.string().c_str());
129  }
130 
131  WriteBuffer writeBuffer;
132  CreateXMB(doc, writeBuffer);
133 
134  xmlFreeDoc(doc);
135 
136  // Save the file to disk, so it can be loaded quickly next time
137  vfs->CreateFile(xmbPath, writeBuffer.Data(), writeBuffer.Size());
138 
139  m_XMBBuffer = writeBuffer.Data(); // add a reference
140 
141  // Set up the XMBFile
142  const bool ok = Initialise((const char*)m_XMBBuffer.get());
143  ENSURE(ok);
144 
145  return PSRETURN_OK;
146 }
147 
148 bool CXeromyces::ReadXMBFile(const PIVFS& vfs, const VfsPath& filename)
149 {
150  size_t size;
151  if(vfs->LoadFile(filename, m_XMBBuffer, size) < 0)
152  return false;
153  // if the game crashes during loading, (e.g. due to driver bugs),
154  // it sometimes leaves empty XMB files in the cache.
155  // reporting failure will cause our caller to re-generate the XMB.
156  if(size == 0)
157  return false;
158  ENSURE(size >= 4); // make sure it's at least got the initial header
159 
160  // Set up the XMBFile
161  if(!Initialise((const char*)m_XMBBuffer.get()))
162  return false;
163 
164  return true;
165 }
166 
168 {
169  ENSURE(g_XeromycesStarted);
170 
171  xmlDocPtr doc = xmlReadMemory(xml, (int)strlen(xml), "", NULL, XML_PARSE_NONET|XML_PARSE_NOCDATA);
172  if (! doc)
173  {
174  LOGERROR(L"CXeromyces: Failed to parse XML string");
176  }
177 
178  WriteBuffer writeBuffer;
179  CreateXMB(doc, writeBuffer);
180 
181  xmlFreeDoc(doc);
182 
183  m_XMBBuffer = writeBuffer.Data(); // add a reference
184 
185  // Set up the XMBFile
186  const bool ok = Initialise((const char*)m_XMBBuffer.get());
187  ENSURE(ok);
188 
189  return PSRETURN_OK;
190 }
191 
192 
193 static void FindNames(const xmlNodePtr node, std::set<std::string>& elementNames, std::set<std::string>& attributeNames)
194 {
195  elementNames.insert((const char*)node->name);
196 
197  for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
198  attributeNames.insert((const char*)attr->name);
199 
200  for (xmlNodePtr child = node->children; child; child = child->next)
201  if (child->type == XML_ELEMENT_NODE)
202  FindNames(child, elementNames, attributeNames);
203 }
204 
205 static void OutputElement(const xmlNodePtr node, WriteBuffer& writeBuffer,
206  std::map<std::string, u32>& elementIDs,
207  std::map<std::string, u32>& attributeIDs
208 )
209 {
210  // Filled in later with the length of the element
211  size_t posLength = writeBuffer.Size();
212  writeBuffer.Append("????", 4);
213 
214  writeBuffer.Append(&elementIDs[(const char*)node->name], 4);
215 
216  u32 attrCount = 0;
217  for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
218  ++attrCount;
219  writeBuffer.Append(&attrCount, 4);
220 
221  u32 childCount = 0;
222  for (xmlNodePtr child = node->children; child; child = child->next)
223  if (child->type == XML_ELEMENT_NODE)
224  ++childCount;
225  writeBuffer.Append(&childCount, 4);
226 
227  // Filled in later with the offset to the list of child elements
228  size_t posChildrenOffset = writeBuffer.Size();
229  writeBuffer.Append("????", 4);
230 
231 
232  // Trim excess whitespace in the entity's text, while counting
233  // the number of newlines trimmed (so that JS error reporting
234  // can give the correct line number within the script)
235 
236  std::string whitespace = " \t\r\n";
237  std::string text;
238  for (xmlNodePtr child = node->children; child; child = child->next)
239  {
240  if (child->type == XML_TEXT_NODE)
241  {
242  xmlChar* content = xmlNodeGetContent(child);
243  text += std::string((const char*)content);
244  xmlFree(content);
245  }
246  }
247 
248  u32 linenum = xmlGetLineNo(node);
249 
250  // Find the start of the non-whitespace section
251  size_t first = text.find_first_not_of(whitespace);
252 
253  if (first == text.npos)
254  // Entirely whitespace - easy to handle
255  text = "";
256 
257  else
258  {
259  // Count the number of \n being cut off,
260  // and add them to the line number
261  std::string trimmed (text.begin(), text.begin()+first);
262  linenum += std::count(trimmed.begin(), trimmed.end(), '\n');
263 
264  // Find the end of the non-whitespace section,
265  // and trim off everything else
266  size_t last = text.find_last_not_of(whitespace);
267  text = text.substr(first, 1+last-first);
268  }
269 
270 
271  // Output text, prefixed by length in bytes
272  if (text.length() == 0)
273  {
274  // No text; don't write much
275  writeBuffer.Append("\0\0\0\0", 4);
276  }
277  else
278  {
279  // Write length and line number and null-terminated text
280  utf16string textW = CStr8(text).FromUTF8().utf16();
281  u32 nodeLen = u32(4 + 2*(textW.length()+1));
282  writeBuffer.Append(&nodeLen, 4);
283  writeBuffer.Append(&linenum, 4);
284  writeBuffer.Append((void*)textW.c_str(), nodeLen-4);
285  }
286 
287  // Output attributes
288  for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
289  {
290  writeBuffer.Append(&attributeIDs[(const char*)attr->name], 4);
291 
292  xmlChar* value = xmlNodeGetContent(attr->children);
293  utf16string textW = CStr8((const char*)value).FromUTF8().utf16();
294  xmlFree(value);
295  u32 attrLen = u32(2*(textW.length()+1));
296  writeBuffer.Append(&attrLen, 4);
297  writeBuffer.Append((void*)textW.c_str(), attrLen);
298  }
299 
300  // Go back and fill in the child-element offset
301  u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4));
302  writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset);
303 
304  // Output all child elements
305  for (xmlNodePtr child = node->children; child; child = child->next)
306  if (child->type == XML_ELEMENT_NODE)
307  OutputElement(child, writeBuffer, elementIDs, attributeIDs);
308 
309  // Go back and fill in the length
310  u32 length = (u32)(writeBuffer.Size() - posLength);
311  writeBuffer.Overwrite(&length, 4, posLength);
312 }
313 
315 {
316  // Header
317  writeBuffer.Append(UnfinishedHeaderMagicStr, 4);
318 
319  std::set<std::string>::iterator it;
320  u32 i;
321 
322  // Find the unique element/attribute names
323  std::set<std::string> elementNames;
324  std::set<std::string> attributeNames;
325  FindNames(xmlDocGetRootElement(doc), elementNames, attributeNames);
326 
327  std::map<std::string, u32> elementIDs;
328  std::map<std::string, u32> attributeIDs;
329 
330  // Output element names
331  i = 0;
332  u32 elementCount = (u32)elementNames.size();
333  writeBuffer.Append(&elementCount, 4);
334  for (it = elementNames.begin(); it != elementNames.end(); ++it)
335  {
336  u32 textLen = (u32)it->length()+1;
337  writeBuffer.Append(&textLen, 4);
338  writeBuffer.Append((void*)it->c_str(), textLen);
339  elementIDs[*it] = i++;
340  }
341 
342  // Output attribute names
343  i = 0;
344  u32 attributeCount = (u32)attributeNames.size();
345  writeBuffer.Append(&attributeCount, 4);
346  for (it = attributeNames.begin(); it != attributeNames.end(); ++it)
347  {
348  u32 textLen = (u32)it->length()+1;
349  writeBuffer.Append(&textLen, 4);
350  writeBuffer.Append((void*)it->c_str(), textLen);
351  attributeIDs[*it] = i++;
352  }
353 
354  OutputElement(xmlDocGetRootElement(doc), writeBuffer, elementIDs, attributeIDs);
355 
356  // file is now valid, so insert correct magic string
357  writeBuffer.Overwrite(HeaderMagicStr, 4, 0);
358 
359  return PSRETURN_OK;
360 }
const PSRETURN PSRETURN_Xeromyces_XMLParseError
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
size_t Size() const
Definition: write_buffer.h:42
Path VfsPath
VFS path of the form &quot;(dir/)*file?&quot;.
Definition: vfs_path.h:40
void Append(const void *data, size_t size)
PSRETURN Load(const PIVFS &vfs, const VfsPath &filename)
Load from an XML file (with invisible XMB caching).
Definition: Xeromyces.cpp:65
const Status OK
Definition: status.h:386
#define LOGERROR
Definition: CLogger.h:35
const char * UnfinishedHeaderMagicStr
Definition: XeroXMB.cpp:27
const PSRETURN PSRETURN_OK
Definition: Errors.h:103
Reads a file, then gives read-only access to the contents.
Definition: Filesystem.h:69
const char * HeaderMagicStr
Definition: XeroXMB.cpp:26
shared_ptr< IVFS > PIVFS
Definition: vfs.h:226
xmlDoc * xmlDocPtr
Definition: Xeromyces.h:40
tuple xml
Definition: tests.py:119
void Overwrite(const void *data, size_t size, size_t offset)
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
bool Initialise(const char *FileData)
Definition: XeroXMB.cpp:31
bool ReadXMBFile(const PIVFS &vfs, const VfsPath &filename)
Definition: Xeromyces.cpp:148
MD5 hashing algorithm.
Definition: MD5.h:27
u32 PSRETURN
Definition: Errors.h:75
static bool g_XeromycesStarted
Definition: Xeromyces.cpp:48
Definition: path.h:75
VfsPath ArchiveCachePath(const VfsPath &sourcePath)
Return the path of the archive cache for the given source file.
shared_ptr< u8 > Data() const
Definition: write_buffer.h:37
const String & string() const
Definition: path.h:123
bool GenerateCachedXMB(const PIVFS &vfs, const VfsPath &sourcePath, VfsPath &archiveCachePath)
Convert the given XML file into an XMB in the archive cache.
Definition: Xeromyces.cpp:104
PSRETURN LoadString(const char *xml)
Load from an in-memory XML string (with no caching).
Definition: Xeromyces.cpp:167
const PSRETURN PSRETURN_Xeromyces_XMLOpenFailed
const u8 * GetBuffer() const
Returns buffer of this file as a stream of bytes.
Definition: Filesystem.cpp:138
i64 Status
Error handling system.
Definition: status.h:171
static void Terminate()
Call once when shutting down the program, to unload libxml2.
Definition: Xeromyces.cpp:57
shared_ptr< u8 > m_XMBBuffer
Definition: Xeromyces.h:81
std::basic_string< utf16_t, utf16_traits > utf16string
Definition: utf16string.h:109
const Status SKIPPED
Definition: status.h:392
#define u32
Definition: types.h:41
PSRETURN Load(const PIVFS &vfs, const VfsPath &filename)
Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed.
Definition: Filesystem.cpp:117
size_t GetBufferSize() const
Definition: Filesystem.cpp:143
JSBool error(JSContext *cx, uintN argc, jsval *vp)
void errorHandler(void *ctx, const char *msg,...)
Error handler for libxml2.
static void Startup()
Call once when initialising the program, to load libxml2.
Definition: Xeromyces.cpp:49
static void FindNames(const xmlNodePtr node, std::set< std::string > &elementNames, std::set< std::string > &attributeNames)
Definition: Xeromyces.cpp:193
tuple input
Definition: tests.py:115
Status TryLoadingCached(const VfsPath &sourcePath, const MD5 &initialHash, u32 version, VfsPath &loadPath)
Attempts to find a valid cached which can be loaded.
Definition: CacheLoader.cpp:32
static void OutputElement(const xmlNodePtr node, WriteBuffer &writeBuffer, std::map< std::string, u32 > &elementIDs, std::map< std::string, u32 > &attributeIDs)
Definition: Xeromyces.cpp:205
Helper class for systems that have an expensive cacheable conversion process when loading files...
Definition: CacheLoader.h:40
PSRETURN ConvertFile(const PIVFS &vfs, const VfsPath &filename, const VfsPath &xmbPath)
Definition: Xeromyces.cpp:113
static PSRETURN CreateXMB(const xmlDocPtr doc, WriteBuffer &writeBuffer)
Definition: Xeromyces.cpp:314