2024-10-25 01:24:14 -07:00
|
|
|
/* Copyright (C) 2024 Wildfire Games.
|
2023-12-02 16:30:12 -08:00
|
|
|
* This file is part of 0 A.D.
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
2023-12-02 16:30:12 -08:00
|
|
|
* 0 A.D. is distributed in the hope that it will be useful,
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2023-12-02 16:30:12 -08:00
|
|
|
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "precompiled.h"
|
|
|
|
|
|
|
|
|
|
#include "XMBStorage.h"
|
|
|
|
|
|
|
|
|
|
#include "lib/file/io/write_buffer.h"
|
2021-05-12 06:48:55 -07:00
|
|
|
#include "lib/file/vfs/vfs.h"
|
|
|
|
|
#include "ps/CLogger.h"
|
2021-05-13 10:23:52 -07:00
|
|
|
#include "scriptinterface/Object.h"
|
2021-05-13 02:43:33 -07:00
|
|
|
#include "scriptinterface/ScriptConversions.h"
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
#include "scriptinterface/ScriptExtraHeaders.h"
|
|
|
|
|
#include "scriptinterface/ScriptInterface.h"
|
|
|
|
|
|
|
|
|
|
#include <libxml/parser.h>
|
2022-12-04 11:56:12 -08:00
|
|
|
#include <string_view>
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
#include <unordered_map>
|
|
|
|
|
|
|
|
|
|
const char* XMBStorage::HeaderMagicStr = "XMB0";
|
|
|
|
|
const char* XMBStorage::UnfinishedHeaderMagicStr = "XMBu";
|
|
|
|
|
// Arbitrary version number - change this if we update the code and
|
|
|
|
|
// need to invalidate old users' caches
|
|
|
|
|
const u32 XMBStorage::XMBVersion = 4;
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
class XMBStorageWriter
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
template<typename ...Args>
|
|
|
|
|
bool Load(WriteBuffer& writeBuffer, Args&&... args);
|
|
|
|
|
|
|
|
|
|
int GetElementName(const std::string& name) { return GetName(m_ElementSize, m_ElementIDs, name); }
|
|
|
|
|
int GetAttributeName(const std::string& name) { return GetName(m_AttributeSize, m_AttributeIDs, name); }
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
int GetName(int& totalSize, std::unordered_map<std::string, int>& names, const std::string& name)
|
|
|
|
|
{
|
|
|
|
|
int nameIdx = totalSize;
|
|
|
|
|
auto [iterator, inserted] = names.try_emplace(name, nameIdx);
|
|
|
|
|
if (inserted)
|
|
|
|
|
totalSize += name.size() + 5; // Add 1 for the null terminator & 4 for the size int.
|
|
|
|
|
return iterator->second;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OutputNames(WriteBuffer& writeBuffer, const std::unordered_map<std::string, int>& names) const;
|
|
|
|
|
|
|
|
|
|
template<typename ...Args>
|
|
|
|
|
bool OutputElements(WriteBuffer&, Args...)
|
|
|
|
|
{
|
|
|
|
|
static_assert(sizeof...(Args) != sizeof...(Args), "OutputElements must be specialized.");
|
2021-05-04 07:01:18 -07:00
|
|
|
return false;
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int m_ElementSize = 0;
|
|
|
|
|
int m_AttributeSize = 0;
|
|
|
|
|
std::unordered_map<std::string, int> m_ElementIDs;
|
|
|
|
|
std::unordered_map<std::string, int> m_AttributeIDs;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Output text, prefixed by length in bytes (including null-terminator)
|
|
|
|
|
void WriteStringAndLineNumber(WriteBuffer& writeBuffer, const std::string& text, int lineNumber)
|
|
|
|
|
{
|
|
|
|
|
if (text.empty())
|
|
|
|
|
{
|
|
|
|
|
// No text; don't write much
|
|
|
|
|
writeBuffer.Append("\0\0\0\0", 4);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Write length and line number and null-terminated text
|
|
|
|
|
u32 nodeLen = u32(4 + text.length() + 1);
|
|
|
|
|
writeBuffer.Append(&nodeLen, 4);
|
|
|
|
|
writeBuffer.Append(&lineNumber, 4);
|
|
|
|
|
writeBuffer.Append((void*)text.c_str(), nodeLen-4);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename ...Args>
|
|
|
|
|
bool XMBStorageWriter::Load(WriteBuffer& writeBuffer, Args&&... args)
|
|
|
|
|
{
|
|
|
|
|
// Header
|
|
|
|
|
writeBuffer.Append(XMBStorage::UnfinishedHeaderMagicStr, 4);
|
|
|
|
|
// Version
|
|
|
|
|
writeBuffer.Append(&XMBStorage::XMBVersion, 4);
|
|
|
|
|
|
|
|
|
|
// Filled in below.
|
|
|
|
|
size_t elementPtr = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Append("????????", 8);
|
|
|
|
|
// Likewise with attributes.
|
|
|
|
|
size_t attributePtr = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Append("????????", 8);
|
|
|
|
|
|
|
|
|
|
if (!OutputElements<Args&&...>(writeBuffer, std::forward<Args>(args)...))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
u32 data = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Overwrite(&data, 4, elementPtr);
|
|
|
|
|
data = m_ElementIDs.size();
|
|
|
|
|
writeBuffer.Overwrite(&data, 4, elementPtr + 4);
|
|
|
|
|
OutputNames(writeBuffer, m_ElementIDs);
|
|
|
|
|
|
|
|
|
|
data = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Overwrite(&data, 4, attributePtr);
|
|
|
|
|
data = m_AttributeIDs.size();
|
|
|
|
|
writeBuffer.Overwrite(&data, 4, attributePtr + 4);
|
|
|
|
|
OutputNames(writeBuffer, m_AttributeIDs);
|
|
|
|
|
|
|
|
|
|
// File is now valid, so insert correct magic string.
|
|
|
|
|
writeBuffer.Overwrite(XMBStorage::HeaderMagicStr, 4, 0);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void XMBStorageWriter::OutputNames(WriteBuffer& writeBuffer, const std::unordered_map<std::string, int>& names) const
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::pair<std::string, int>> orderedElements;
|
2022-02-13 15:41:47 -08:00
|
|
|
for (const std::pair<const std::string, int>& n : names)
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
orderedElements.emplace_back(n);
|
|
|
|
|
std::sort(orderedElements.begin(), orderedElements.end(), [](const auto& a, const auto&b) { return a.second < b.second; });
|
|
|
|
|
for (const std::pair<std::string, int>& n : orderedElements)
|
|
|
|
|
{
|
|
|
|
|
u32 textLen = (u32)n.first.length() + 1;
|
|
|
|
|
writeBuffer.Append(&textLen, 4);
|
|
|
|
|
writeBuffer.Append((void*)n.first.c_str(), textLen);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class JSNodeData
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
JSNodeData(const ScriptInterface& s) : scriptInterface(s), rq(s) {}
|
|
|
|
|
|
|
|
|
|
bool Setup(XMBStorageWriter& xmb, JS::HandleValue value);
|
|
|
|
|
bool Output(WriteBuffer& writeBuffer, JS::HandleValue value) const;
|
|
|
|
|
|
|
|
|
|
std::vector<std::pair<u32, std::string>> m_Attributes;
|
|
|
|
|
std::vector<std::pair<u32, JS::Heap<JS::Value>>> m_Children;
|
|
|
|
|
|
|
|
|
|
const ScriptInterface& scriptInterface;
|
|
|
|
|
const ScriptRequest rq;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
bool XMBStorageWriter::OutputElements<JSNodeData&, const u32&, JS::HandleValue&&>(WriteBuffer& writeBuffer, JSNodeData& data, const u32& nodeName, JS::HandleValue&& value)
|
|
|
|
|
{
|
|
|
|
|
// Set up variables.
|
|
|
|
|
if (!data.Setup(*this, value))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
size_t posLength = writeBuffer.Size();
|
|
|
|
|
// Filled in later with the length of the element
|
|
|
|
|
writeBuffer.Append("????", 4);
|
|
|
|
|
|
|
|
|
|
writeBuffer.Append(&nodeName, 4);
|
|
|
|
|
|
|
|
|
|
u32 attrCount = static_cast<u32>(data.m_Attributes.size());
|
|
|
|
|
writeBuffer.Append(&attrCount, 4);
|
|
|
|
|
|
|
|
|
|
u32 childCount = data.m_Children.size();
|
|
|
|
|
writeBuffer.Append(&childCount, 4);
|
|
|
|
|
|
|
|
|
|
// Filled in later with the offset to the list of child elements
|
|
|
|
|
size_t posChildrenOffset = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Append("????", 4);
|
|
|
|
|
|
|
|
|
|
data.Output(writeBuffer, value);
|
|
|
|
|
|
|
|
|
|
// Output attributes
|
2022-02-13 15:41:47 -08:00
|
|
|
for (const std::pair<const u32, std::string> attr : data.m_Attributes)
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
{
|
|
|
|
|
writeBuffer.Append(&attr.first, 4);
|
|
|
|
|
u32 attrLen = u32(attr.second.size())+1;
|
|
|
|
|
writeBuffer.Append(&attrLen, 4);
|
|
|
|
|
writeBuffer.Append((void*)attr.second.c_str(), attrLen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Go back and fill in the child-element offset
|
|
|
|
|
u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4));
|
|
|
|
|
writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset);
|
|
|
|
|
|
|
|
|
|
// Output all child elements, making a copy since data will be overwritten.
|
|
|
|
|
std::vector<std::pair<u32, JS::Heap<JS::Value>>> children = data.m_Children;
|
|
|
|
|
for (const std::pair<u32, JS::Heap<JS::Value>>& child : children)
|
|
|
|
|
{
|
|
|
|
|
JS::RootedValue val(data.rq.cx, child.second);
|
|
|
|
|
if (!OutputElements<JSNodeData&, const u32&, JS::HandleValue&&>(writeBuffer, data, child.first, val))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Go back and fill in the length
|
|
|
|
|
u32 length = (u32)(writeBuffer.Size() - posLength);
|
|
|
|
|
writeBuffer.Overwrite(&length, 4, posLength);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool JSNodeData::Setup(XMBStorageWriter& xmb, JS::HandleValue value)
|
|
|
|
|
{
|
|
|
|
|
m_Attributes.clear();
|
|
|
|
|
m_Children.clear();
|
|
|
|
|
JSType valType = JS_TypeOfValue(rq.cx, value);
|
|
|
|
|
if (valType != JSTYPE_OBJECT)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
std::vector<std::string> props;
|
2021-05-13 10:23:52 -07:00
|
|
|
if (!Script::EnumeratePropertyNames(rq, value, true, props))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
{
|
|
|
|
|
LOGERROR("Failed to enumerate component properties.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const std::string& prop : props)
|
|
|
|
|
{
|
|
|
|
|
// Special 'value' key.
|
|
|
|
|
if (prop == "_string")
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
bool attrib = !prop.empty() && prop.front() == '@';
|
|
|
|
|
|
|
|
|
|
std::string_view name = prop;
|
|
|
|
|
if (!attrib && !prop.empty() && prop.back() == '@')
|
|
|
|
|
{
|
2022-12-04 11:56:12 -08:00
|
|
|
const size_t idx = std::string_view{prop}.substr(0, prop.size() - 1)
|
|
|
|
|
.find_last_of('@');
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
if (idx == std::string::npos)
|
|
|
|
|
{
|
|
|
|
|
LOGERROR("Object key name cannot end with an '@' unless it is an index specifier.");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
name = std::string_view(prop.c_str(), idx);
|
|
|
|
|
}
|
|
|
|
|
else if (attrib)
|
|
|
|
|
name = std::string_view(prop.c_str()+1, prop.length()-1);
|
|
|
|
|
|
|
|
|
|
JS::RootedValue child(rq.cx);
|
2021-05-13 10:23:52 -07:00
|
|
|
if (!Script::GetProperty(rq, value, prop.c_str(), &child))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (attrib)
|
|
|
|
|
{
|
|
|
|
|
std::string attrVal;
|
2021-05-13 02:43:33 -07:00
|
|
|
if (!Script::FromJSVal(rq, child, attrVal))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
{
|
|
|
|
|
LOGERROR("Attributes must be convertible to string");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
m_Attributes.emplace_back(xmb.GetAttributeName(std::string(name)), attrVal);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isArray = false;
|
|
|
|
|
if (!JS::IsArrayObject(rq.cx, child, &isArray))
|
|
|
|
|
return false;
|
|
|
|
|
if (!isArray)
|
|
|
|
|
{
|
|
|
|
|
m_Children.emplace_back(xmb.GetElementName(std::string(name)), child);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse each array object as a child.
|
|
|
|
|
JS::RootedObject obj(rq.cx);
|
|
|
|
|
JS_ValueToObject(rq.cx, child, &obj);
|
|
|
|
|
u32 length;
|
|
|
|
|
JS::GetArrayLength(rq.cx, obj, &length);
|
|
|
|
|
for (size_t i = 0; i < length; ++i)
|
|
|
|
|
{
|
|
|
|
|
JS::RootedValue arrayChild(rq.cx);
|
2021-05-13 10:23:52 -07:00
|
|
|
Script::GetPropertyInt(rq, child, i, &arrayChild);
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
m_Children.emplace_back(xmb.GetElementName(std::string(name)), arrayChild);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool JSNodeData::Output(WriteBuffer& writeBuffer, JS::HandleValue value) const
|
|
|
|
|
{
|
|
|
|
|
switch (JS_TypeOfValue(rq.cx, value))
|
|
|
|
|
{
|
|
|
|
|
case JSTYPE_UNDEFINED:
|
|
|
|
|
{
|
|
|
|
|
writeBuffer.Append("\0\0\0\0", 4);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case JSTYPE_OBJECT:
|
|
|
|
|
{
|
2021-05-13 10:23:52 -07:00
|
|
|
if (!Script::HasProperty(rq, value, "_string"))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
{
|
|
|
|
|
writeBuffer.Append("\0\0\0\0", 4);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
JS::RootedValue actualValue(rq.cx);
|
2021-05-13 10:23:52 -07:00
|
|
|
if (!Script::GetProperty(rq, value, "_string", &actualValue))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
return false;
|
|
|
|
|
std::string strVal;
|
2021-05-13 02:43:33 -07:00
|
|
|
if (!Script::FromJSVal(rq, actualValue, strVal))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
{
|
|
|
|
|
LOGERROR("'_string' value must be convertible to string");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
WriteStringAndLineNumber(writeBuffer, strVal, 0);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case JSTYPE_STRING:
|
|
|
|
|
case JSTYPE_NUMBER:
|
|
|
|
|
{
|
|
|
|
|
std::string strVal;
|
2021-05-13 02:43:33 -07:00
|
|
|
if (!Script::FromJSVal(rq, value, strVal))
|
XMB Improvements, parse JS into XMB, make strings more efficient.
XMB format is bumped to 4, invalidating all cached files. The
differences are:
- element/attribute names are stored after the elements themselves, and
not before. This allows writing XMB data in one pass instead of two.
- names themselves becomes offsets (instead of arbitrary integers),
making getting the string from the int name much more efficient.
XMBFile is renamed to XMBData to clarify that it does not, in fact,
refer to a file on disk.
XMBData::GetElementString is also changed to return a const char*, thus
not creating an std::string. A string_view version is added where
convenient.
The XML->XMB and JS->XMB conversion functions and the corresponding
storage are moved to `ps/XMB`, since that format doesn't particularly
relate to XML. CXeromyces becomes lighter and more focused as a result.
The XML->XMB conversion also benefits from the above streamlining.
Note that in a few cases, string_view gets printed to CLogger via
data(), which is generally not legal, but we know that the strings are
null-terminated here. Our libfmt (version 4) doesn't support
string_view, that would be v5.
Differential Revision: https://code.wildfiregames.com/D3909
This was SVN commit r25375.
2021-05-04 06:02:34 -07:00
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
WriteStringAndLineNumber(writeBuffer, strVal, 0);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
LOGERROR("Unsupported JS construct when parsing ParamNode");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template<>
|
|
|
|
|
bool XMBStorageWriter::OutputElements<xmlNodePtr&&>(WriteBuffer& writeBuffer, xmlNodePtr&& node)
|
|
|
|
|
{
|
|
|
|
|
// Filled in later with the length of the element
|
|
|
|
|
size_t posLength = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Append("????", 4);
|
|
|
|
|
|
|
|
|
|
u32 name = GetElementName((const char*)node->name);
|
|
|
|
|
writeBuffer.Append(&name, 4);
|
|
|
|
|
|
|
|
|
|
u32 attrCount = 0;
|
|
|
|
|
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
|
|
|
|
|
++attrCount;
|
|
|
|
|
writeBuffer.Append(&attrCount, 4);
|
|
|
|
|
|
|
|
|
|
u32 childCount = 0;
|
|
|
|
|
for (xmlNodePtr child = node->children; child; child = child->next)
|
|
|
|
|
if (child->type == XML_ELEMENT_NODE)
|
|
|
|
|
++childCount;
|
|
|
|
|
writeBuffer.Append(&childCount, 4);
|
|
|
|
|
|
|
|
|
|
// Filled in later with the offset to the list of child elements
|
|
|
|
|
size_t posChildrenOffset = writeBuffer.Size();
|
|
|
|
|
writeBuffer.Append("????", 4);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Trim excess whitespace in the entity's text, while counting
|
|
|
|
|
// the number of newlines trimmed (so that JS error reporting
|
|
|
|
|
// can give the correct line number within the script)
|
|
|
|
|
|
|
|
|
|
std::string whitespace = " \t\r\n";
|
|
|
|
|
std::string text;
|
|
|
|
|
for (xmlNodePtr child = node->children; child; child = child->next)
|
|
|
|
|
{
|
|
|
|
|
if (child->type == XML_TEXT_NODE)
|
|
|
|
|
{
|
|
|
|
|
xmlChar* content = xmlNodeGetContent(child);
|
|
|
|
|
text += std::string((const char*)content);
|
|
|
|
|
xmlFree(content);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 linenum = xmlGetLineNo(node);
|
|
|
|
|
|
|
|
|
|
// Find the start of the non-whitespace section
|
|
|
|
|
size_t first = text.find_first_not_of(whitespace);
|
|
|
|
|
|
|
|
|
|
if (first == text.npos)
|
|
|
|
|
// Entirely whitespace - easy to handle
|
|
|
|
|
text = "";
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Count the number of \n being cut off,
|
|
|
|
|
// and add them to the line number
|
|
|
|
|
std::string trimmed (text.begin(), text.begin()+first);
|
|
|
|
|
linenum += std::count(trimmed.begin(), trimmed.end(), '\n');
|
|
|
|
|
|
|
|
|
|
// Find the end of the non-whitespace section,
|
|
|
|
|
// and trim off everything else
|
|
|
|
|
size_t last = text.find_last_not_of(whitespace);
|
|
|
|
|
text = text.substr(first, 1+last-first);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Output text, prefixed by length in bytes
|
|
|
|
|
WriteStringAndLineNumber(writeBuffer, text, linenum);
|
|
|
|
|
|
|
|
|
|
// Output attributes
|
|
|
|
|
for (xmlAttrPtr attr = node->properties; attr; attr = attr->next)
|
|
|
|
|
{
|
|
|
|
|
u32 attrName = GetAttributeName((const char*)attr->name);
|
|
|
|
|
writeBuffer.Append(&attrName, 4);
|
|
|
|
|
|
|
|
|
|
xmlChar* value = xmlNodeGetContent(attr->children);
|
|
|
|
|
u32 attrLen = u32(xmlStrlen(value)+1);
|
|
|
|
|
writeBuffer.Append(&attrLen, 4);
|
|
|
|
|
writeBuffer.Append((void*)value, attrLen);
|
|
|
|
|
xmlFree(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Go back and fill in the child-element offset
|
|
|
|
|
u32 childrenOffset = (u32)(writeBuffer.Size() - (posChildrenOffset+4));
|
|
|
|
|
writeBuffer.Overwrite(&childrenOffset, 4, posChildrenOffset);
|
|
|
|
|
|
|
|
|
|
// Output all child elements
|
|
|
|
|
for (xmlNodePtr child = node->children; child; child = child->next)
|
|
|
|
|
if (child->type == XML_ELEMENT_NODE)
|
|
|
|
|
OutputElements<xmlNodePtr&&>(writeBuffer, std::move(child));
|
|
|
|
|
|
|
|
|
|
// Go back and fill in the length
|
|
|
|
|
u32 length = (u32)(writeBuffer.Size() - posLength);
|
|
|
|
|
writeBuffer.Overwrite(&length, 4, posLength);
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
|
|
bool XMBStorage::ReadFromFile(const PIVFS& vfs, const VfsPath& filename)
|
|
|
|
|
{
|
|
|
|
|
if(vfs->LoadFile(filename, m_Buffer, m_Size) < 0)
|
|
|
|
|
return false;
|
|
|
|
|
// if the game crashes during loading, (e.g. due to driver bugs),
|
|
|
|
|
// it sometimes leaves empty XMB files in the cache.
|
|
|
|
|
// reporting failure will cause our caller to re-generate the XMB.
|
|
|
|
|
if (m_Size == 0)
|
|
|
|
|
return false;
|
|
|
|
|
ENSURE(m_Size >= 4); // make sure it's at least got the initial header
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool XMBStorage::LoadXMLDoc(const xmlDocPtr doc)
|
|
|
|
|
{
|
|
|
|
|
WriteBuffer writeBuffer;
|
|
|
|
|
|
|
|
|
|
XMBStorageWriter writer;
|
|
|
|
|
if (!writer.Load(writeBuffer, std::move(xmlDocGetRootElement(doc))))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
m_Buffer = writeBuffer.Data(); // add a reference
|
|
|
|
|
m_Size = writeBuffer.Size();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool XMBStorage::LoadJSValue(const ScriptInterface& scriptInterface, JS::HandleValue value, const std::string& rootName)
|
|
|
|
|
{
|
|
|
|
|
WriteBuffer writeBuffer;
|
|
|
|
|
|
|
|
|
|
XMBStorageWriter writer;
|
|
|
|
|
const u32 name = writer.GetElementName(rootName);
|
|
|
|
|
JSNodeData data(scriptInterface);
|
|
|
|
|
if (!writer.Load(writeBuffer, data, name, std::move(value)))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
m_Buffer = writeBuffer.Data(); // add a reference
|
|
|
|
|
m_Size = writeBuffer.Size();
|
|
|
|
|
return true;
|
|
|
|
|
}
|