diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp index 00c2eae5c7..4c2e6d04e8 100755 --- a/source/graphics/ObjectEntry.cpp +++ b/source/graphics/ObjectEntry.cpp @@ -1,5 +1,4 @@ #include "precompiled.h" -#undef new // if it was redefined for leak detection, since xerces doesn't like it #include "ObjectEntry.h" #include "ObjectManager.h" @@ -9,12 +8,7 @@ #include "UnitManager.h" -#include "XML.h" - - -// automatically use namespace .. -XERCES_CPP_NAMESPACE_USE - +#include "ps/Xeromyces.h" CObjectEntry::CObjectEntry(int type) : m_Model(0), m_Type(type) { @@ -36,6 +30,7 @@ CObjectEntry::~CObjectEntry() bool CObjectEntry::BuildModel() { + // check we've enough data to consider building the object if (m_ModelName.Length()==0 || m_TextureName.Length()==0) { return false; @@ -149,138 +144,98 @@ CSkeletonAnim* CObjectEntry::GetNamedAnimation( CStr animationName ) bool CObjectEntry::Load(const char* filename) { - bool parseOK = false; - - // Initialize XML library + CXeromyces XeroFile; + try { - XMLCh* attachpointtext=XMLString::transcode("attachpoint"); - XMLCh* modeltext=XMLString::transcode("model"); - XMLCh* nametext=XMLString::transcode("name"); - XMLCh* filetext=XMLString::transcode("file"); - XMLCh* speedtext=XMLString::transcode("speed"); + XeroFile.Load(filename); + } + catch (...) + { + return false; + } - // Create parser instance - XercesDOMParser *parser = new XercesDOMParser(); + // Define all the elements and attributes used in the XML file + #define EL(x) int el_##x = XeroFile.getElementID(L#x) + #define AT(x) int at_##x = XeroFile.getAttributeID(L#x) + EL(name); + EL(modelname); + EL(texturename); + EL(animations); + EL(props); + AT(attachpoint); + AT(model); + AT(name); + AT(file); + AT(speed); + #undef AT + #undef EL - // Setup parser - parser->setValidationScheme(XercesDOMParser::Val_Auto); - parser->setDoNamespaces(false); - parser->setDoSchema(false); - parser->setCreateEntityReferenceNodes(false); + XMBElement root = XeroFile.getRoot(); - // Set customized error handler - CXercesErrorHandler *errorHandler = new CXercesErrorHandler(); - parser->setErrorHandler(errorHandler); + XMBElementList children = root.getChildNodes(); - CVFSEntityResolver *entityResolver = new CVFSEntityResolver(filename); - parser->setEntityResolver(entityResolver); + for (int i = 0; i < children.Count; ++i) { + // Get node + XMBElement child = children.item(i); - // Push the CLogger to mark it's reading this file. + int element_name = child.getNodeName(); + CStr element_value=tocstr(child.getText()); - // Get main node - CVFSInputSource source; - parseOK = source.OpenFile(filename) == 0; - if (parseOK) + if (element_name == el_name) + m_Name=element_value; + + else if (element_name == el_modelname) + m_ModelName=element_value; + + else if (element_name == el_texturename) + m_TextureName=element_value; + + else if (element_name == el_animations) { - // Parse file - parser->parse(source); + XMBElementList animations=child.getChildNodes(); - // Check how many errors - parseOK = parser->getErrorCount() == 0; - } - if (parseOK) { - // parsed successfully - grab our data - DOMDocument *doc = parser->getDocument(); - DOMElement *element = doc->getDocumentElement(); + for (int j = 0; j < animations.Count; ++j) { + XMBElement anim_element = animations.item(j); + XMBAttributeList attributes=anim_element.getAttributes(); + if (attributes.Count) { + Anim anim; - // root_name should be Object - CStr root_name = XMLTranscode( element->getNodeName() ); + anim.m_AnimName=tocstr(attributes.getNamedItem(at_name)); + anim.m_FileName=tocstr(attributes.getNamedItem(at_file)); + CStr16 speedstr=attributes.getNamedItem(at_speed); - // should have at least 3 children - Name, ModelName and TextureName - DOMNodeList *children = element->getChildNodes(); - int numChildren=children->getLength(); - for (int i=0; iitem(i); + anim.m_Speed=float(speedstr.ToInt())/100.0f; + if (anim.m_Speed<=0.0) anim.m_Speed=1.0f; - // A child element - if (child->getNodeType() == DOMNode::ELEMENT_NODE) - { - // First get element and not node - DOMElement *child_element = (DOMElement*)child; - - CStr element_name = XMLTranscode( child_element->getNodeName() ); - DOMNode *value_node= child_element->getChildNodes()->item(0); - CStr element_value=value_node ? XMLTranscode(value_node->getNodeValue()) : ""; - - if (element_name==CStr("Name")) { - m_Name=element_value; - } else if (element_name==CStr("ModelName")) { - m_ModelName=element_value; - } else if (element_name==CStr("TextureName")) { - m_TextureName=element_value; - } else if (element_name==CStr("Animations")) { - DOMNodeList* animations=(DOMNodeList*) child_element->getChildNodes(); - - for (uint j=0; jgetLength(); ++j) { - DOMElement *anim_element = (DOMElement*) animations->item(j); - CStr element_name = XMLTranscode( anim_element->getNodeName() ); - DOMNamedNodeMap* attributes=anim_element->getAttributes(); - if (attributes) { - Anim anim; - - DOMNode *nameattr=attributes->getNamedItem(nametext); - anim.m_AnimName=XMLTranscode(nameattr->getChildNodes()->item(0)->getNodeValue()); - DOMNode *fileattr=attributes->getNamedItem(filetext); - anim.m_FileName=XMLTranscode(fileattr->getChildNodes()->item(0)->getNodeValue()); - - DOMNode *speedattr=attributes->getNamedItem(speedtext); - CStr speedstr=XMLTranscode(speedattr->getChildNodes()->item(0)->getNodeValue()); - - anim.m_Speed=float(atoi((const char*) speedstr))/100.0f; - if (anim.m_Speed<=0) anim.m_Speed=1.0f; - - m_Animations.push_back(anim); - } - } - } else if (element_name==CStr("Props")) { - DOMNodeList* props=(DOMNodeList*) child_element->getChildNodes(); - - for (uint j=0; jgetLength(); ++j) { - DOMElement *prop_element = (DOMElement*) props->item(j); - CStr element_name = XMLTranscode( prop_element->getNodeName() ); - DOMNamedNodeMap* attributes=prop_element->getAttributes(); - if (attributes) { - Prop prop; - - DOMNode *nameattr=attributes->getNamedItem(attachpointtext); - prop.m_PropPointName=XMLTranscode(nameattr->getChildNodes()->item(0)->getNodeValue()); - DOMNode *modelattr=attributes->getNamedItem(modeltext); - prop.m_ModelName=XMLTranscode(modelattr->getChildNodes()->item(0)->getNodeValue()); - - m_Props.push_back(prop); - } - } - } + m_Animations.push_back(anim); } } } + else if (element_name == el_props) + { + XMBElementList props=child.getChildNodes(); - XMLString::release(&attachpointtext); - XMLString::release(&modeltext); - XMLString::release(&nametext); - XMLString::release(&filetext); - XMLString::release(&speedtext); + for (int j = 0; j < props.Count; ++j) { + XMBElement prop_element = props.item(j); + XMBAttributeList attributes=prop_element.getAttributes(); + if (attributes.Count) { + Prop prop; - delete parser; - delete errorHandler; - delete entityResolver; + prop.m_PropPointName=tocstr(attributes.getNamedItem(at_attachpoint)); + prop.m_ModelName=tocstr(attributes.getNamedItem(at_model)); + + m_Props.push_back(prop); + } + } + } } - return parseOK; + return true; + } + bool CObjectEntry::Save(const char* filename) { FILE* fp=fopen(filename,"w"); diff --git a/source/lib/crc32.cpp b/source/lib/crc32.cpp new file mode 100755 index 0000000000..b9a6393e6a --- /dev/null +++ b/source/lib/crc32.cpp @@ -0,0 +1,49 @@ +// $Id: crc32.cpp,v 1.1 2004/07/08 15:21:21 philip Exp $ + +#include "precompiled.h" + +// CRC32, based on code copied from anywhere on the internet. +// Find a thousand possible sources at +// http://www.google.com/search?q=0xEDB88320L + +unsigned long crc_table[256]; + +int generate_table() +{ + unsigned long crc; + int i, j; + + const unsigned long poly = 0xEDB88320L; // magic + + for (i = 0; i < 256; ++i) + { + crc = i; + for (j = 0; j < 8; ++j) + { + if (crc & 1) + crc = (crc >> 1) ^ poly; + else + crc >>= 1; + } + crc_table[i] = crc; + } + return 0; +} + + +unsigned long crc32_calculate(char* data, int len) +{ + // Only calculate the table once + static int _temp = generate_table(); + + unsigned long crc; + + crc = ~0; + while (len) + { + crc = (crc>>8) ^ crc_table[ (crc^*data) & 0xff ]; + --len; ++data; + } + + return ~crc; +} diff --git a/source/lib/crc32.h b/source/lib/crc32.h new file mode 100755 index 0000000000..bbe22385d0 --- /dev/null +++ b/source/lib/crc32.h @@ -0,0 +1,4 @@ +// $Id: crc32.h,v 1.1 2004/07/08 15:21:21 philip Exp $ + +// I don't think this really needs any documentation +unsigned long crc32_calculate(char* data, int len); diff --git a/source/lib/precompiled.h b/source/lib/precompiled.h index b9945527d6..d4913de113 100755 --- a/source/lib/precompiled.h +++ b/source/lib/precompiled.h @@ -37,22 +37,6 @@ #include #include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - // Nicer memory leak reporting in MSVC: // (You've got to include all STL headers first to avoid lots of errors, // so make sure they're in the list above and you compile with PCH) diff --git a/source/main.cpp b/source/main.cpp index 66e35b78d5..c0c8455559 100755 --- a/source/main.cpp +++ b/source/main.cpp @@ -38,17 +38,16 @@ #include "EntityHandles.h" #include "EntityManager.h" #include "PathfindEngine.h" -#include "XML.h" #include "scripting/JSInterface_Entity.h" #include "scripting/JSInterface_BaseEntity.h" #include "scripting/JSInterface_Vector3D.h" +#include "gui/scripting/JSInterface_IGUIObject.h" +#include "gui/scripting/JSInterface_GUITypes.h" #include "ConfigDB.h" #include "CLogger.h" - - #ifndef NO_GUI #include "gui/GUI.h" #endif @@ -548,14 +547,8 @@ void ParseArgs(int argc, char* argv[]) - static void psInit() { - // start up Xerces - only needs to be done once (unless locale changes mid-game, for - // some reason), not on every XML file load; multiple initialization calls are ok, though, - // provided there are a matching number of XMLPlatformUtils::Terminate calls - XMLPlatformUtils::Initialize(); - g_Font_Console = unifont_load("fonts/console"); g_Font_Misc = unifont_load("fonts/verdana16"); @@ -566,9 +559,11 @@ static void psInit() #ifndef NO_GUI // GUI uses VFS, so this must come after VFS init. g_GUI.Initialize(); - g_GUI.LoadXMLFile("gui/styles.xml"); - g_GUI.LoadXMLFile("gui/hello.xml"); - g_GUI.LoadXMLFile("gui/sprite1.xml"); + + g_GUI.LoadXMLFile("gui/test/styles.xml"); + g_GUI.LoadXMLFile("gui/test/hello.xml"); + g_GUI.LoadXMLFile("gui/test/sprite1.xml"); + #endif } @@ -582,15 +577,14 @@ static void psShutdown() delete g_Console; - // close down Xerces - XMLPlatformUtils::Terminate(); + // close down Xerces if it was loaded + CXeromyces::Terminate(); } - - extern u64 PREVTSC; int main(int argc, char* argv[]) { + #ifdef _MSC_VER u64 TSC=rdtsc(); debug_out( @@ -600,6 +594,7 @@ debug_out( PREVTSC=TSC; #endif + const int ERR_MSG_SIZE = 1000; wchar_t err_msg[ERR_MSG_SIZE]; @@ -613,6 +608,13 @@ PREVTSC=TSC; // Create the scripting host. This needs to be done before the GUI is created. new ScriptingHost; + // Register the JavaScript interfaces with the runtime + JSI_Entity::init(); + JSI_BaseEntity::init(); + JSI_IGUIObject::init(); + JSI_GUITypes::init(); + JSI_Vector3D::init(); + detect(); // init SDL @@ -729,11 +731,6 @@ PREVTSC=CURTSC; g_EntityTemplateCollection.loadTemplates(); - // Register the JavaScript interfaces with the runtime - JSI_Entity::init(); -// JSI_BaseEntity::init(); // janwas: commented this out to avoid crash in JS_DestroyContext - JSI_Vector3D::init(); - // if no map name specified, load test01.pmp (for convenience during // development. that means loading no map at all is currently impossible. // is that a problem? @@ -833,7 +830,6 @@ PREVTSC=CURTSC; // ugly, but necessary. these are one-shot events, have to be reset. mouseButtons[SDL_BUTTON_WHEELUP] = false; mouseButtons[SDL_BUTTON_WHEELDOWN] = false; - in_get_events(); float TimeSinceLastFrame = (float)(time1-time0); @@ -862,6 +858,8 @@ PREVTSC=CURTSC; if (g_FixedFrameTiming && frameCount==100) quit=true; } // main loop, while(!quit) + psShutdown(); // Must delete g_GUI before g_ScriptingHost + delete &g_ScriptingHost; delete &g_Pathfinder; delete &g_EntityManager; @@ -880,8 +878,6 @@ PREVTSC=CURTSC; delete &g_ConfigDB; - psShutdown(); - exit(0); return 0; } diff --git a/source/ps/XML.h b/source/ps/XML.h index aa028637c8..569b0990b2 100755 --- a/source/ps/XML.h +++ b/source/ps/XML.h @@ -22,8 +22,11 @@ #ifndef _XercesVFS_H #define _XercesVFS_H -#include -#include +// Temporarily undefine new, because the Xerces headers don't like it +#ifdef HAVE_DEBUGALLOC +# undef new +#endif + #include #include @@ -32,12 +35,13 @@ #include #include -#include #include -#include - +#ifdef HAVE_DEBUGALLOC +# define new new(_NORMAL_BLOCK, __FILE__, __LINE__) +#endif +#include "lib/crc32.h" #include "res/h_mgr.h" #include "lib.h" @@ -73,6 +77,9 @@ public: // returns 0 if successful, -1 on failure int OpenFile(const char *path); + // Calculate the CRC32 checksum of the file's contents + unsigned long CRC32() { return crc32_calculate((char*)m_pBuffer, (int)m_BufferSize); } + virtual BinInputStream *makeStream() const; }; diff --git a/source/ps/XMLUtils.cpp b/source/ps/XMLUtils.cpp index 04ae443804..56aff218d8 100755 --- a/source/ps/XMLUtils.cpp +++ b/source/ps/XMLUtils.cpp @@ -1,7 +1,9 @@ #include "precompiled.h" -#undef new // if it was redefined for leak detection, since xerces doesn't like it #include "XML.h" + +#undef new // if it was redefined for leak detection, since xerces doesn't like it + #include "CStr.h" #include "CLogger.h" #include "posix.h" // ptrdiff_t diff --git a/source/ps/XercesErrorHandler.h b/source/ps/XercesErrorHandler.h index 2d179206f5..7ae04f6b29 100755 --- a/source/ps/XercesErrorHandler.h +++ b/source/ps/XercesErrorHandler.h @@ -22,10 +22,6 @@ gee@pyro.nu #include "XML.h" -//#include -//#include - - #include /** diff --git a/source/ps/XeroXMB.cpp b/source/ps/XeroXMB.cpp new file mode 100755 index 0000000000..1bd339f3a3 --- /dev/null +++ b/source/ps/XeroXMB.cpp @@ -0,0 +1,250 @@ +// $Id: XeroXMB.cpp,v 1.1 2004/07/08 15:21:42 philip Exp $ + +#include "precompiled.h" + +#include "Xeromyces.h" + +#include + +const int HeaderMagic = 0x30424D58; // = "XMB0" (little-endian) +const char* HeaderMagicStr = "XMB0"; + +// Warning: May contain traces of pointer abuse + +void XMBFile::Initialise(char* FileData) +{ + m_Pointer = FileData; + int Header = *(int*)m_Pointer; m_Pointer += 4; + assert(Header == HeaderMagic && "Invalid XMB header!"); + + int Checksum = *(int*)m_Pointer; m_Pointer += 4; + + int i; + +#ifdef XERO_USEMAP + // Build a std::map of all the names->ids + int ElementNameCount = *(int*)m_Pointer; m_Pointer += 4; + for (i = 0; i < ElementNameCount; ++i) + m_ElementNames[ReadZStr()] = i; + + int AttributeNameCount = *(int*)m_Pointer; m_Pointer += 4; + for (i = 0; i < AttributeNameCount; ++i) + m_AttributeNames[ReadZStr()] = i; +#else + // Ignore all the names for now, and skip over them + // (remembering the position of the first) + m_ElementNameCount = *(int*)m_Pointer; m_Pointer += 4; + m_ElementPointer = m_Pointer; + for (i = 0; i < m_ElementNameCount; ++i) + m_Pointer += 4 + *(int*)m_Pointer; // skip over the string + + m_AttributeNameCount = *(int*)m_Pointer; m_Pointer += 4; + m_AttributePointer = m_Pointer; + for (i = 0; i < m_AttributeNameCount; ++i) + m_Pointer += 4 + *(int*)m_Pointer; // skip over the string +#endif + +} + +std::wstring XMBFile::ReadZStr() +{ + int Length = *(int*)m_Pointer; + m_Pointer += 4; + std::wstring String ((wchar_t*)m_Pointer); + m_Pointer += Length; + return String; +} + +XMBElement XMBFile::getRoot() +{ + return XMBElement(m_Pointer); +} + + +#ifdef XERO_USEMAP + +int XMBFile::getElementID(const wchar_t* Name) +{ + return m_ElementNames[Name]; +} + +int XMBFile::getAttributeID(const wchar_t* Name) +{ + return m_AttributeNames[Name]; +} + +#else // #ifdef XERO_USEMAP + +int XMBFile::getElementID(const wchar_t* Name) +{ + char* Pos = m_ElementPointer; + + int len = ((int)wcslen(Name)+1)<<1; // count bytes, including null terminator + + // Loop through each string to find a match + for (int i = 0; i < m_ElementNameCount; ++i) + { + // See if this could be the right string, checking its + // length and then its contents + if (*(int*)Pos == len && memcmp((wchar_t*)(Pos+4), Name, len) == 0) + return i; + // If not, jump to the next string + Pos += 4 + *(int*)Pos; + } + // Failed + return -1; +} + +int XMBFile::getAttributeID(const wchar_t* Name) +{ + char* Pos = m_AttributePointer; + + int len = ((int)wcslen(Name)+1)<<1; // count bytes, including null terminator + + // Loop through each string to find a match + for (int i = 0; i < m_AttributeNameCount; ++i) + { + // See if this could be the right string, checking its + // length and then its contents + if (*(int*)Pos == len && memcmp((wchar_t*)(Pos+4), Name, len) == 0) + return i; + // If not, jump to the next string + Pos += 4 + *(int*)Pos; + } + // Failed + return -1; +} +#endif // #ifdef XERO_USEMAP / #else + + +// Relatively inefficient, so only use when +// laziness overcomes the need for speed +std::wstring XMBFile::getElementString(const int ID) +{ + char* Pos = m_ElementPointer; + for (int i = 0; i < ID; ++i) + Pos += 4 + *(int*)Pos; + return std::wstring((wchar_t*)(Pos+4)); +} + +std::wstring XMBFile::getAttributeString(const int ID) +{ + char* Pos = m_AttributePointer; + for (int i = 0; i < ID; ++i) + Pos += 4 + *(int*)Pos; + return std::wstring((wchar_t*)(Pos+4)); +} + + + +int XMBElement::getNodeName() +{ + return *(int*)(m_Pointer + 4); // == ElementName +} + +XMBElementList XMBElement::getChildNodes() +{ + return XMBElementList( + m_Pointer + 20 + *(int*)(m_Pointer + 16), // == Children[] + *(int*)(m_Pointer + 12) // == ChildCount + ); +} + +XMBAttributeList XMBElement::getAttributes() +{ + return XMBAttributeList( + m_Pointer + 24 + *(int*)(m_Pointer + 20), // == Attributes[] + *(int*)(m_Pointer + 8) // == AttributeCount + ); +} + +std::wstring XMBElement::getText() +{ + return std::wstring((wchar_t*)(m_Pointer + 24)); // == Text +} + + +XMBElement XMBElementList::item(const int id) +{ + assert(id >= 0 && id < Count && "Element ID out of range"); + char* Pos; + + // If access is sequential, don't bother scanning + // through all the nodes to find the next one + if (id == m_LastItemID+1) + { + Pos = m_LastPointer; + Pos += *(int*)Pos; // skip over the last node + } + else + { + Pos = m_Pointer; + // Skip over each preceding node + for (int i=0; i= 0 && id < Count && "Attribute ID out of range"); + char* Pos; + + // If access is sequential, don't bother scanning through + // all the nodes to find the right one + if (id == m_LastItemID+1) + { + Pos = m_LastPointer; + // Skip over the last attribute + Pos += 8 + *(int*)(Pos+4); + } + else + { + Pos = m_Pointer; + // Skip over each preceding attribute + for (int i=0; i Text " and "
Text
" are + considered identical. + * Tries to avoid using strings - you usually have to load the + numeric IDs and use them instead. + * Case-sensitive (but converts all element/attribute names in + the XML file to lowercase, so you only have to be careful in + the code) + + +Theoretical file structure: + +XMB_File { + char Header[4]; // because everyone has one; currently "XMB0" + + int Checksum; // CRC32 of original XML file, to detect changes + + int ElementNameCount; + XMB_ZStr ElementNames[]; + + int AttributeNameCount; + ZStr AttributeNames[]; + + XMB_Node Root; +} + +XMB_Node { +0) int Length; // of entire struct, so it can be skipped over + +4) int ElementName; + +8) int AttributeCount; +12) int ChildCount; + +16) int ChildrenOffset; // == sizeof(Text)+sizeof(Attributes) +20) ZStr Text; + XMB_Attribute Attributes[]; + XMB_Node Children[]; + +} + +XMB_Attribute { + int Name; + XMB_ZStr Value; +} + +XMB_ZStr { + int Length; // in bytes (always a multiple of 2) + wchar_t* Text; // null-terminated, UTF16-LE +} + + +*/ + +#ifndef _XEROXMB_H_ +#define _XEROXMB_H_ + +// Define to use a std::map for name lookups rather than a linear search. +// (The map is usually slower.) +//#define XERO_USEMAP + +#include + +#ifdef XERO_USEMAP +# include +#endif + +// File headers, to make sure it doesn't try loading anything other than an XMB +extern const int HeaderMagic; +extern const char* HeaderMagicStr; + +class XMBElement; +class XMBElementList; +class XMBAttributeList; + + +class XMBFile +{ +public: + + XMBFile() : m_Pointer(NULL) {}; + + // Initialise from the contents of an XMB file. + // FileData must remain allocated and unchanged while + // the XMBFile is being used. + void Initialise(char* FileData); + + // Returns the root element + XMBElement getRoot(); + + + // Returns internal ID for a given element/attribute string. + // Use getElementID(L"name") to get a wchar_t* + int getElementID(const wchar_t* Name); + int getAttributeID(const wchar_t* Name); + + // For lazy people (e.g. me) when speed isn't vital: + + // Returns element/attribute string for a given internal ID + std::wstring getElementString(const int ID); + std::wstring getAttributeString(const int ID); + +private: + char* m_Pointer; + +#ifdef XERO_USEMAP + std::map m_ElementNames; + std::map m_AttributeNames; +#else + int m_ElementNameCount; + int m_AttributeNameCount; + char* m_ElementPointer; + char* m_AttributePointer; +#endif + + std::wstring ReadZStr(); +}; + +class XMBElement +{ +public: + XMBElement(char* offset) + : m_Pointer(offset) {} + + int getNodeName(); // == ElementName + XMBElementList getChildNodes(); + XMBAttributeList getAttributes(); + std::wstring getText(); + +private: + // Pointer to the start of the node + char* m_Pointer; +}; + +class XMBElementList +{ +public: + XMBElementList(char* offset, int count) + : Count(count), + m_Pointer(offset), + m_LastItemID(-2) {} // use -2 because it isn't x-1 where x is a non-negative integer + + XMBElement item(const int id); // returns Children[id] + + int Count; + +private: + char* m_Pointer; + + // For optimised sequential access: + int m_LastItemID; + char* m_LastPointer; +}; + + +struct XMBAttribute +{ + XMBAttribute(int name, std::wstring value) + : Name(name), Value(value) {}; + + int Name; + std::wstring Value; +}; + +class XMBAttributeList +{ +public: + XMBAttributeList(char* offset, int count) + : Count(count), m_Pointer(offset) {}; + + // Get the attribute value directly (unlike Xerces) + std::wstring getNamedItem(const int AttributeName); + + // Returns an attribute by position in the list + XMBAttribute item(const int id); + + int Count; + +private: + // Pointer to start of attribute list + char* m_Pointer; + + // For optimised sequential access: + int m_LastItemID; + char* m_LastPointer; +}; + + + +#include "ps/CStr.h" +CStr tocstr(std::wstring s); + +#endif // _XEROXMB_H_ diff --git a/source/ps/Xeromyces.cpp b/source/ps/Xeromyces.cpp new file mode 100755 index 0000000000..bcb153e8aa --- /dev/null +++ b/source/ps/Xeromyces.cpp @@ -0,0 +1,463 @@ +// $Id: Xeromyces.cpp,v 1.1 2004/07/08 15:21:42 philip Exp $ + +#include "precompiled.h" + +#include +#include +#include +#include + +#include "ps/Xeromyces.h" +#include "ps/CLogger.h" +#include "lib/res/file.h" + +// Because I (and Xerces) don't like these being redefined by wposix.h: +#ifdef HAVE_PCH +# undef read +# undef write +#endif + +#include "XML.h" + +// For Xerces headers: +#ifdef HAVE_DEBUGALLOC +# undef new +#endif + +// The converter uses SAX2, so it should [theoretically] +// be fairly easy to swap Xerces for something else (if desired) +#include +#include + +// Reenable better memory-leak messages +#ifdef HAVE_DEBUGALLOC +# define new new(_NORMAL_BLOCK, __FILE__, __LINE__) +#endif + + +int CXeromyces::XercesLoaded = 0; // for once-only initialisation + + +// Slightly nasty fwrite/fseek/ftell style thing +class membuffer +{ +public: + membuffer() + { + buffer = (char*)malloc(bufferinc); + assert(buffer); + allocated = bufferinc; + length = 0; + } + + ~membuffer() + { + free(buffer); + } + + void write(void* data, int size) + { + while (length + size >= allocated) grow(); + memcpy(&buffer[length], data, size); + length += size; + } + + void write(void* data, int size, int offset) + { + assert(offset >= 0 && offset+size <= length); + memcpy(&buffer[offset], data, size); + } + + int tell() + { + return length; + } + + char* steal_buffer() + { + char* ret = buffer; + buffer = NULL; + return ret; + } + + char* buffer; + int length; +private: + int allocated; + static const int bufferinc = 1024; + void grow() + { + allocated += bufferinc; + buffer = (char*)realloc(buffer, allocated); + assert(buffer); + } +}; + +// Convenient storage for the internal tree +typedef struct { + std::wstring name; + std::wstring value; +} XMLAttribute; + +typedef struct XMLElement { + std::wstring name; + std::wstring text; + std::vector childs; + std::vector attrs; +} XMLElement; + +class XeroHandler : public DefaultHandler +{ +public: + // SAX2 event handlers: + virtual void startDocument(); + virtual void endDocument(); + virtual void startElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const Attributes& attrs); + virtual void endElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname); + virtual void characters(const XMLCh* const chars, const unsigned int length); + + // Non-SAX2 stuff, used for storing the + // parsed data and constructing the XMB: + + void CreateXMB(unsigned long crc); + membuffer buffer; +private: + std::set ElementNames; + std::set AttributeNames; + XMLElement* Root; + XMLElement* CurrentElement; + std::stack ElementStack; + + std::map ElementID; + std::map AttributeID; + + void OutputElement(XMLElement* el); +}; + + + +CXeromyces::CXeromyces() + : XMBFileHandle(0), XMBBuffer(NULL) +{ +} + +CXeromyces::~CXeromyces() { + + if (XMBFileHandle) + { + // If it was read from a file, close it + vfs_unmap(XMBFileHandle); + vfs_close(XMBFileHandle); + } + else + { + // If it was converted from a XML directly into memory, + // free that memory buffer + free(XMBBuffer); + } +} + +void CXeromyces::Terminate() +{ + if (XercesLoaded) + { + XMLPlatformUtils::Terminate(); + XercesLoaded = 0; + } +} + + + +void CXeromyces::Load(const char* filename) +{ + // HACK: This is only done so early because CVFSInputSource + // requires XMLTranscode. It would preferably not be done until + // we actually need Xerces. + if (! XercesLoaded) + { + XMLPlatformUtils::Initialize(); + XercesLoaded = 1; + } + + + CVFSInputSource source; + if (source.OpenFile(filename)) + { + LOG(ERROR, "CXeromyces: Failed to load XML file '%s'", filename); + throw "Failed to load XML file"; + } + unsigned long XMLChecksum = source.CRC32(); + + // Check whether the XMB file needs to be regenerated: + + // Generate the XMB's filename + CStr filenameXMB = filename; + filenameXMB[(int)filenameXMB.Length()-1] = 'b'; + + // HACK: Check whether the XMB exists (in a rather unpleasant way) + char path[VFS_MAX_PATH]; + vfs_realpath(filename, path); // can't get the XMB's VFS path because it doesn't exist + path[strlen(path)-1] = 'b'; + bool fileExists = false; + { + FILE* f = fopen(path, "r"); + if (f) + { + fileExists = true; + fclose(f); + } + } + + // Load the entire file, with the assumption that usually it's + // going to be valid and will then be passed to XMBFile(). + if (fileExists && ReadXMBFile(filenameXMB.c_str(), true, XMLChecksum)) + return; + + + // XMB isn't up to date with the XML, so rebuild it: + + + SAX2XMLReader* Parser = XMLReaderFactory::createXMLReader(); + + // Enable validation + Parser->setFeature(L"http://xml.org/sax/features/validation", true); + Parser->setFeature(L"http://apache.org/xml/features/validation/dynamic", true); + + XeroHandler handler; + Parser->setContentHandler(&handler); + + CXercesErrorHandler errorHandler; + Parser->setErrorHandler(&errorHandler); + + CVFSEntityResolver entityResolver(filename); + Parser->setEntityResolver(&entityResolver); + + // Build a tree inside handler + Parser->parse(source); + + // (It's horribly inefficient doing SAX2->tree then tree->XMB, + // but the XML->XMB conversion should be done very rarely + // anyway. If it's ever needed, the XMB writing can be done + // directly from inside the SAX2 event handlers, although that's + // a little more complex) + + delete Parser; + + // Convert the data structures into the XMB format + handler.CreateXMB(XMLChecksum); + + // Save the file to disk, so it can be loaded quickly next time + vfs_uncached_store(filenameXMB, handler.buffer.buffer, handler.buffer.length); + + XMBBuffer = handler.buffer.steal_buffer(); + + Initialise(XMBBuffer); +} + +bool CXeromyces::ReadXMBFile(const char* filename, bool CheckCRC, unsigned long CRC) +{ + Handle file = vfs_open(filename); + if (file <= 0) + { + LOG(ERROR, "CXeromyces: file '%s' couldn't be opened (vfs_open: %lld)\n", filename, file); + return false; + } + + void* buffer; + size_t bufferSize; + int err; + if ( (err=vfs_map(file, 0, buffer, bufferSize)) ) + { + LOG(ERROR, "CXeromyces: file '%s' couldn't be read (vfs_map: %d)\n", filename, err); + vfs_close(file); + return false; + } + + assert(bufferSize >= 42 && "Invalid XMB file"); // 42 bytes is the smallest possible XMB. (Well, okay, 50 bytes is the smallest, but it was 42 the first time I counted, and 42 is a much nicer number.) + assert(*(int*)buffer == HeaderMagic && "Invalid XMB file header"); + + if (CheckCRC) + { + unsigned long fileCRC = *((unsigned long*)buffer + 1); // read the second four-byte number + if (CRC != fileCRC) + { + // Checksums don't match; have to regenerate from the XML + vfs_unmap(file); + vfs_close(file); + return false; + } + } + + // Store the Handle so it can be closed later + XMBFileHandle = file; + + // Set up the XMBFile + Initialise((char*)buffer); + + return true; +} + + + +void XeroHandler::startDocument() +{ + Root = new XMLElement; + ElementStack.push(Root); +} + +void XeroHandler::endDocument() +{ +} + +std::wstring lowercase(std::wstring a) +{ + std::wstring b; + b.resize(a.length()); + for (size_t i = 0; i < a.length(); ++i) + b[i] = towlower(a[i]); + return b; +} + +void XeroHandler::startElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname, const Attributes& attrs) +{ + std::wstring elementName = lowercase(localname); + ElementNames.insert(elementName); + + // Create a new element + XMLElement* e = new XMLElement; + e->name = elementName; + + // Store all the attributes in the new element + for (unsigned int i = 0; i < attrs.getLength(); ++i) + { + std::wstring attrName = lowercase(attrs.getLocalName(i)); + AttributeNames.insert(attrName); + XMLAttribute* a = new XMLAttribute; + a->name = attrName; + a->value = attrs.getValue(i); + e->attrs.push_back(a); + } + + // Add the element to its parent + ElementStack.top()->childs.push_back(e); + + // Set as parent of following elements + ElementStack.push(e); +} + +void XeroHandler::endElement(const XMLCh* const uri, const XMLCh* const localname, const XMLCh* const qname) +{ + ElementStack.pop(); +} + +void XeroHandler::characters(const XMLCh* const chars, const unsigned int length) +{ + ElementStack.top()->text += chars; +} + + +void XeroHandler::CreateXMB(unsigned long crc) +{ + // Header + buffer.write((void*)HeaderMagicStr, 4); + + // Checksum + buffer.write(&crc, 4); + + std::set::iterator it; + int i; + + // Element names + i = 0; + int ElementCount = (int)ElementNames.size(); + buffer.write(&ElementCount, 4); + for (it = ElementNames.begin(); it != ElementNames.end(); ++it) + { + int TextLen = 2*((int)it->length()+1); + buffer.write(&TextLen, 4); + buffer.write((void*)it->c_str(), TextLen); + ElementID[*it] = i++; + } + + // Attribute names + i = 0; + int AttributeCount = (int)AttributeNames.size(); + buffer.write(&AttributeCount, 4); + for (it = AttributeNames.begin(); it != AttributeNames.end(); ++it) + { + int TextLen = 2*((int)it->length()+1); + buffer.write(&TextLen, 4); + buffer.write((void*)it->c_str(), TextLen); + AttributeID[*it] = i++; + } + + // All the XML contents must be surrounded by a single element + assert(Root->childs.size() == 1); + + OutputElement(Root->childs[0]); + delete Root; +} + +// Writes a whole element (recursively if it has children) into the buffer, +// and also frees all the memory that has been allocated for that element. +void XeroHandler::OutputElement(XMLElement* el) +{ + // Filled in later with the length of the element + int Pos_Length = buffer.tell(); + buffer.write("????", 4); + + int NameID = ElementID[el->name]; + buffer.write(&NameID, 4); + + int AttrCount = (int)el->attrs.size(); + buffer.write(&AttrCount, 4); + + int ChildCount = (int)el->childs.size(); + buffer.write(&ChildCount, 4); + + // Filled in later with the offset to the list of child elements + int Pos_ChildrenOffset = buffer.tell(); + buffer.write("????", 4); + + // Trim excess whitespace + std::wstring whitespace = L" \t\r\n"; + size_t first = el->text.find_first_not_of(whitespace); + if (first == -1) // entirely whitespace + el->text = L""; + else + { + size_t last = el->text.find_last_not_of(whitespace); + el->text = el->text.substr(first, 1+last-first); + } + // Output text, prefixed by length in bytes + int TextLen = 2*((int)el->text.length()+1); + buffer.write(&TextLen, 4); + buffer.write((void*)el->text.c_str(), TextLen); + + for (int i = 0; i < AttrCount; ++i) + { + int AttrName = AttributeID[el->attrs[i]->name]; + buffer.write(&AttrName, 4); + + int AttrLen = 2*((int)el->attrs[i]->value.length()+1); + buffer.write(&AttrLen, 4); + buffer.write((void*)el->attrs[i]->value.c_str(), AttrLen); + + // Free each attribute as soon as it's been dealt with + delete el->attrs[i]; + } + + // Go back and fill in the child-element offset + int ChildrenOffset = buffer.tell() - (Pos_ChildrenOffset+4); + buffer.write(&ChildrenOffset, 4, Pos_ChildrenOffset); + + for (int i = 0; i < ChildCount; ++i) + OutputElement(el->childs[i]); + + // Go back and fill in the length + int Length = buffer.tell() - Pos_Length; + buffer.write(&Length, 4, Pos_Length); + + // Tidy up the parser's mess + delete el; +} \ No newline at end of file diff --git a/source/ps/Xeromyces.h b/source/ps/Xeromyces.h new file mode 100755 index 0000000000..7ebfacef11 --- /dev/null +++ b/source/ps/Xeromyces.h @@ -0,0 +1,46 @@ +/* $Id: Xeromyces.h,v 1.1 2004/07/08 15:21:42 philip Exp $ + + Xeromyces file-loading interface. + Automatically creates and caches relatively + efficient binary representations of XML files. + + - Philip Taylor (philip@zaynar.demon.co.uk / @wildfiregames.com) + +*/ + +#ifndef _XEROMYCES_H_ +#define _XEROMYCES_H_ + +#include "ps/XeroXMB.h" + +#include "lib/res/vfs.h" + +class CXeromyces : public XMBFile +{ +public: + CXeromyces(); + ~CXeromyces(); + + // Load from an XML file (with invisible XMB caching). + // Throws a const char* if stuff breaks. + void Load(const char* filename); + + // Call once when shutting down the program. + static void Terminate(); + + // Get the XMBFile after having called Load +// XMBFile* GetXMB() { assert(XMB); return XMB; } + + +private: + bool ReadXMBFile(const char* filename, bool CheckCRC, unsigned long CRC); + + XMBFile* XMB; + Handle XMBFileHandle; // if it's being read from disk + char* XMBBuffer; // if it's being read from RAM + + static int XercesLoaded; // for once-only initialisation +}; + + +#endif // _XEROMYCES_H_ \ No newline at end of file diff --git a/source/simulation/BaseEntity.cpp b/source/simulation/BaseEntity.cpp index 4898b856db..c807b73413 100755 --- a/source/simulation/BaseEntity.cpp +++ b/source/simulation/BaseEntity.cpp @@ -1,14 +1,10 @@ #include "precompiled.h" -#undef new // if it was redefined for leak detection, since xerces doesn't like it #include "BaseEntity.h" #include "ObjectManager.h" #include "CStr.h" -#include "XML.h" - -// automatically use namespace .. -XERCES_CPP_NAMESPACE_USE +#include "ps/Xeromyces.h" CBaseEntity::CBaseEntity() { @@ -32,125 +28,98 @@ CBaseEntity::~CBaseEntity() bool CBaseEntity::loadXML( CStr filename ) { - bool parseOK = false; - - // Initialize XML library - XMLPlatformUtils::Initialize(); + CXeromyces XeroFile; + try { - // Create parser instance - XercesDOMParser *parser = new XercesDOMParser(); - - // Setup parser - parser->setValidationScheme(XercesDOMParser::Val_Auto); - parser->setDoNamespaces(false); - parser->setDoSchema(false); - parser->setCreateEntityReferenceNodes(false); - - // Set customized error handler - CXercesErrorHandler *errorHandler = new CXercesErrorHandler(); - parser->setErrorHandler(errorHandler); - - CVFSEntityResolver *entityResolver = new CVFSEntityResolver(filename); - parser->setEntityResolver(entityResolver); - - // Get main node - CVFSInputSource source; - parseOK=source.OpenFile(filename)==0; - - if (parseOK) - { - // Parse file - parser->parse(source); - - // Check how many errors - parseOK = parser->getErrorCount() == 0; - } - - if (parseOK) { - // parsed successfully - grab our data - DOMDocument *doc = parser->getDocument(); - DOMElement *element = doc->getDocumentElement(); - - // root_name should be Object - CStr root_name = XMLTranscode( element->getNodeName() ); - - // should have at least 3 children - Name, ModelName and TextureName - DOMNodeList *children = element->getChildNodes(); - int numChildren=children->getLength(); - for (int i=0; iitem(i); - - // A child element - if (child->getNodeType() == DOMNode::ELEMENT_NODE) - { - // First get element and not node - DOMElement *child_element = (DOMElement*)child; - - CStr element_name = XMLTranscode( child_element->getNodeName() ); - DOMNode *value_node= child_element->getChildNodes()->item(0); - CStr element_value=value_node ? XMLTranscode(value_node->getNodeValue()) : ""; - - if( element_name == CStr( "Name" ) ) - { - m_name = element_value; - } - else if( element_name == CStr( "Actor" ) ) - { - m_actorObject = g_ObjMan.FindObject( element_value ); - } - else if( element_name == CStr( "Speed" ) ) - { - m_speed = element_value.ToFloat(); - } - else if( element_name == CStr( "TurningRadius" ) ) - { - m_turningRadius = element_value.ToFloat(); - } - else if( element_name == CStr( "Size" ) ) - { - if( !m_bound_circle ) - m_bound_circle = new CBoundingCircle(); - CStr radius = XMLTranscode( child_element->getAttribute( (XMLCh*)L"Radius" ) ); - m_bound_circle->setRadius( radius.ToFloat() ); - m_bound_type = CBoundingObject::BOUND_CIRCLE; - } - else if( element_name == CStr( "Footprint" ) ) - { - if( !m_bound_box ) - m_bound_box = new CBoundingBox(); - CStr width = XMLTranscode( child_element->getAttribute( (XMLCh*)L"Width" ) ); - CStr height = XMLTranscode( child_element->getAttribute( (XMLCh*)L"Height" ) ); - - m_bound_box->setDimensions( width.ToFloat(), height.ToFloat() ); - m_bound_type = CBoundingObject::BOUND_OABB; - } - else if( element_name == CStr( "BoundsOffset" ) ) - { - CStr x = XMLTranscode( child_element->getAttribute( (XMLCh*)L"x" ) ); - CStr y = XMLTranscode( child_element->getAttribute( (XMLCh*)L"y" ) ); - - if( !m_bound_circle ) - m_bound_circle = new CBoundingCircle(); - if( !m_bound_box ) - m_bound_box = new CBoundingBox(); - - m_bound_circle->m_offset.x = x.ToFloat(); - m_bound_circle->m_offset.y = y.ToFloat(); - m_bound_box->m_offset.x = x.ToFloat(); - m_bound_box->m_offset.y = y.ToFloat(); - - } - - } - } - - } - delete parser; - delete errorHandler; - delete entityResolver; + XeroFile.Load(filename); + } + catch (...) + { + return false; } - XMLPlatformUtils::Terminate(); - return parseOK; + // Define all the elements and attributes used in the XML file + #define EL(x) int el_##x = XeroFile.getElementID(L#x) + #define AT(x) int at_##x = XeroFile.getAttributeID(L#x) + EL(entity); + EL(name); + EL(actor); + EL(speed); + EL(turningradius); + EL(size); + EL(footprint); + EL(boundsoffset); + AT(radius); + AT(width); + AT(height); + AT(x); + AT(y); + #undef AT + #undef EL + + XMBElement Root = XeroFile.getRoot(); + + assert(Root.getNodeName() == el_entity); + + XMBElementList RootChildren = Root.getChildNodes(); + + for (int i = 0; i < RootChildren.Count; ++i) + { + XMBElement Child = RootChildren.item(i); + + int ChildName = Child.getNodeName(); + + if (ChildName == el_name) + { + m_name = tocstr(Child.getText()); + } + else if (ChildName == el_actor) + { + m_actorObject = g_ObjMan.FindObject( tocstr(Child.getText()) ); + } + else if (ChildName == el_speed) + { + m_speed = CStr16(Child.getText()).ToFloat(); + } + else if (ChildName == el_turningradius) + { + m_turningRadius = CStr16(Child.getText()).ToFloat(); + } + else if (ChildName == el_size) + { + if( !m_bound_circle ) + m_bound_circle = new CBoundingCircle(); + CStr16 radius = Child.getAttributes().getNamedItem(at_radius); + m_bound_circle->setRadius( radius.ToFloat() ); + m_bound_type = CBoundingObject::BOUND_CIRCLE; + } + else if (ChildName == el_footprint) + { + if( !m_bound_box ) + m_bound_box = new CBoundingBox(); + CStr16 width = Child.getAttributes().getNamedItem(at_width); + CStr16 height = Child.getAttributes().getNamedItem(at_height); + + m_bound_box->setDimensions( width.ToFloat(), height.ToFloat() ); + m_bound_type = CBoundingObject::BOUND_OABB; + } + else if (ChildName == el_boundsoffset) + { + CStr16 x = Child.getAttributes().getNamedItem(at_x); + CStr16 y = Child.getAttributes().getNamedItem(at_y); + + if( !m_bound_circle ) + m_bound_circle = new CBoundingCircle(); + if( !m_bound_box ) + m_bound_box = new CBoundingBox(); + + m_bound_circle->m_offset.x = x.ToFloat(); + m_bound_circle->m_offset.y = y.ToFloat(); + m_bound_box->m_offset.x = x.ToFloat(); + m_bound_box->m_offset.y = y.ToFloat(); + + } + } + + return true; }