Files
2025-09-18 17:55:52 -04:00

265 lines
7.5 KiB
C++

/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#include "stdafx.h"
#include "V8Xml/SerializerV2.h"
#include "V8Xml/XmlSerializer.h"
#include "V8DataModel/DataModel.h"
#include "v8datamodel/contentprovider.h"
#include "rbx/Debug.h"
#include "util/standardout.h"
#include "v8xml/SerializerBinary.h"
#include <map>
#ifdef _WIN32
using std::mem_fun1;
#else
#include <ext/functional>
using __gnu_cxx::mem_fun1;
#endif
using std::string;
using std::vector;
using namespace RBX;
// An implementation of IReferenceBinder
class ArchiveBinder : public RBX::MergeBinder
{
private:
typedef RBX::MergeBinder Super;
// A map of temporary IDs to Referents
std::map<std::string, InstanceHandle> idMap;
struct IDREFBinding {
const XmlNameValuePair* valueIDREF;
Reflection::DescribedBase* propertyOwner;
const IIDREF* idref;
};
std::list<IDREFBinding> idrefBindings;
public:
virtual bool processID(const XmlNameValuePair* valueID, Reflection::DescribedBase* source) {
if (!Super::processID(valueID, source)) {
std::string s;
bool foundString = valueID->getValue(s);
RBXASSERT(foundString);
// RBXASSERT(idMap.find(s)==idMap.end()); // This is in BugHost for Erik - implies we are writing references multiple times
// See this file to make the assert pop:
// game.Workspace:insertContent("http://watrbx.wtf/asset/?id=2286288")
idMap[s].linkTo(shared_from(source));
if (source)
{
source->setXmlId(s);
}
}
return true;
}
virtual bool processIDREF(const XmlNameValuePair* valueIDREF, Reflection::DescribedBase* propertyOwner, const IIDREF* idref) {
if (!Super::processIDREF(valueIDREF, propertyOwner, idref)) {
IDREFBinding binding = {valueIDREF, propertyOwner, idref};
idrefBindings.push_back( binding );
}
return true;
}
bool resolveIDREF(IDREFBinding binding)
{
std::string s;
bool foundString = binding.valueIDREF->getValue(s);
RBXASSERT(foundString);
// The following 3 cases should have been handled during the processIDREF phase
RBXASSERT (value_IDREF_nil!=s);
RBXASSERT (value_IDREF_null!=s);
RBXASSERT (s!="");
// Find the InstanceHandle belonging to the requested ID
std::map<std::string, InstanceHandle>::iterator iter = idMap.find(s);
if (iter!=idMap.end())
{
// TODO: should we give the handle over to valueIDREF?
assign(binding.idref, binding.propertyOwner, iter->second);
return true;
}
// Unable to find the requested InstanceHandle
assign(binding.idref, binding.propertyOwner, InstanceHandle(NULL));
return false;
}
bool resolveRefs()
{
int count = 0;
count += count_if(
idrefBindings.begin(),
idrefBindings.end(),
std::bind1st(mem_fun1(&ArchiveBinder::resolveIDREF),this)
);
return Super::resolveRefs() && (count == idrefBindings.size());
}
};
void SerializerV2::load(std::istream& stream, RBX::DataModel* dataModel)
{
// See file format spec in CWorkspace::Save()
char header[8];
if (!stream.read(header, 8).good())
throw std::runtime_error("SerializerV2::load can't read header");
stream.clear();
stream.seekg (0, std::ios::beg);
if (memcmp(header, SerializerBinary::kMagicHeader, 8) == 0)
{
// read the binary content
SerializerBinary::deserialize(stream, dataModel);
}
else
{
// read the XML content
loadXML(stream, dataModel);
}
}
void SerializerV2::loadInstances(std::istream& stream, RBX::Instances& result)
{
char header[8];
if (!stream.read(header, 8).good())
throw std::runtime_error("SerializerV2::loadInstances can't read header");
stream.clear();
stream.seekg(0, std::ios::beg);
if (memcmp(header, SerializerBinary::kMagicHeader, 8) == 0)
{
// read the binary content
SerializerBinary::deserialize(stream, result);
}
else
{
// read the XML content
TextXmlParser machine(stream.rdbuf());
std::auto_ptr<XmlElement> root(machine.parse());
ArchiveBinder binder;
loadInstancesXML(root.get(), result, binder, RBX::SerializationCreator);
}
}
void SerializerV2::loadXML(std::istream& stream, RBX::DataModel* dataModel)
{
TextXmlParser machine(stream.rdbuf());
std::auto_ptr<XmlElement> root(machine.parse());
if (root->getTag() == tag_roblox)
{
if(const XmlAttribute* version = root->findAttribute(tag_version))
{
if (!version || !version->getValue(schemaVersionLoading))
{
throw std::runtime_error("SerializerV2::loadXML no version number");
}
else if (schemaVersionLoading<4)
{
throw std::runtime_error("SerializerV2::loadXML schemaVersionLoading<4");
}
else
{
ArchiveBinder binder;
dataModel->readChildren(root.get(), binder, SerializationCreator);
binder.resolveRefs();
}
}
}
else
{
schemaVersionLoading = 1;
throw std::runtime_error("SerializerV2::loadXML ill-formed XML. No Roblox tag");
}
// Should we need to do this???
dataModel->setDirty(false);
}
shared_ptr<Instance> SerializerV2::loadInstanceXML(const XmlElement* itemElement, IReferenceBinder& binder, CreatorRole creatorRole)
{
const RBX::Name* className = NULL;
if (itemElement->findAttributeValue(tag_class, className))
{
shared_ptr<Instance> instance = Creatable<Instance>::createByName(*className, RBX::SerializationCreator);
if (instance)
{
instance->read(itemElement, binder, creatorRole);
return instance;
}
else
StandardOut::singleton()->printf(MESSAGE_WARNING, "Unknown object class \"%s\" while reading XML", className ? className->c_str() : "");
}
return shared_ptr<Instance>();
}
void SerializerV2::loadInstancesFromText(const XmlElement* root, Instances& result)
{
ArchiveBinder binder;
loadInstancesXML(root, result, binder, RBX::SerializationCreator);
}
void SerializerV2::loadInstancesXML(const XmlElement* root, Instances& result, IReferenceBinder& binder, CreatorRole creatorRole)
{
// TODO: This code has a lot in common with Instance::readChildren and with SerializerV2::merge
// Find a way of combining these code chunks
bool v4model = false;
if (root->getTag() == tag_roblox) {
const XmlAttribute* version = root->findAttribute(tag_version);
if (version!=NULL && version->getValue(schemaVersionLoading) && schemaVersionLoading >= 4) {
v4model = true;
const XmlElement* childElement = root->findFirstChildByTag(tag_Item);
while (childElement)
{
if(shared_ptr<Instance> instance = loadInstanceXML(childElement, binder, creatorRole))
result.push_back(instance);
childElement = root->findNextChildWithSameTag(childElement);
}
binder.resolveRefs();
// TODO: ASSERT
//RBXASSERT(resolvedBindings);
}
}
};
XmlElement* SerializerV2::newRootElement()
{
return newRootElement("");
}
XmlElement* SerializerV2::newRootElement(const std::string& type)
{
static const XmlTag& tag_xmlnsxmime = Name::declare("xmlns:xmime");
XmlElement* root = new XmlElement(tag_roblox);
root->addAttribute(tag_xmlnsxmime, "http://www.w3.org/2005/05/xmlmime");
root->addAttribute(tag_xmlnsxsi, "http://www.w3.org/2001/XMLSchema-instance");
root->addAttribute(tag_xsinoNamespaceSchemaLocation, "http://www.watrbx.wtf/roblox.xsd");
root->addAttribute(tag_version, SerializerV2::CURRENT_SCHEMA_VERSION);
if(!type.empty()){
root->addAttribute(tag_assettype, type);
}
// Used for schema validation with Roblox.xsd:
root->addChild(new XmlElement(tag_External, &value_IDREF_null));
root->addChild(new XmlElement(tag_External, &value_IDREF_nil));
return root;
}