Roblox-File-Format/XmlFormat/XmlRobloxFile.cs

199 lines
6.8 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using RobloxFiles.DataTypes;
using RobloxFiles.XmlFormat;
namespace RobloxFiles
{
2019-05-19 04:44:51 +00:00
public class XmlRobloxFile : RobloxFile
{
public readonly XmlDocument XmlDocument = new XmlDocument();
2019-05-19 04:44:51 +00:00
internal Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
internal HashSet<string> SharedStrings = new HashSet<string>();
public Dictionary<string, string> Metadata { get; private set; } = new Dictionary<string, string>();
2020-08-18 01:12:24 +00:00
internal int RefCounter = 0;
2019-05-19 04:44:51 +00:00
public XmlRobloxFile()
2019-05-19 04:44:51 +00:00
{
2020-09-14 16:20:34 +00:00
Name = "Xml:";
Referent = "null";
2020-09-14 16:20:34 +00:00
ParentLocked = true;
2019-05-19 04:44:51 +00:00
}
2019-05-19 04:44:51 +00:00
protected override void ReadFile(byte[] buffer)
{
try
{
string xml = Encoding.UTF8.GetString(buffer);
2020-08-17 05:33:59 +00:00
var settings = new XmlReaderSettings() { XmlResolver = null };
using (StringReader reader = new StringReader(xml))
{
XmlReader xmlReader = XmlReader.Create(reader, settings);
XmlDocument.Load(xmlReader);
xmlReader.Dispose();
}
}
catch
{
throw new Exception("XmlRobloxFile: Could not read provided buffer as XML!");
}
XmlNode roblox = XmlDocument.FirstChild;
if (roblox != null && roblox.Name == "roblox")
{
// Verify the version we are using.
XmlNode version = roblox.Attributes.GetNamedItem("version");
2020-08-17 05:33:59 +00:00
if (version == null || !int.TryParse(version.Value, out int schemaVersion))
throw new Exception("XmlRobloxFile: No version number defined!");
else if (schemaVersion < 4)
throw new Exception("XmlRobloxFile: Provided version must be at least 4!");
// Process the instances.
foreach (XmlNode child in roblox.ChildNodes)
{
if (child.Name == "Item")
{
2019-05-19 04:44:51 +00:00
Instance item = XmlRobloxFileReader.ReadInstance(child, this);
2021-05-01 22:40:09 +00:00
if (item == null)
continue;
2019-05-19 04:44:51 +00:00
item.Parent = this;
}
else if (child.Name == "SharedStrings")
{
2019-05-19 04:44:51 +00:00
XmlRobloxFileReader.ReadSharedStrings(child, this);
}
else if (child.Name == "Meta")
{
XmlRobloxFileReader.ReadMetadata(child, this);
}
}
// Query the properties.
2019-05-19 04:44:51 +00:00
var allProps = Instances.Values
.SelectMany(inst => inst.Properties)
.Select(pair => pair.Value);
// Resolve referent properties.
2019-05-19 04:44:51 +00:00
var refProps = allProps.Where(prop => prop.Type == PropertyType.Ref);
foreach (Property refProp in refProps)
{
string refId = refProp.XmlToken;
refProp.XmlToken = "Ref";
if (Instances.ContainsKey(refId))
{
Instance refInst = Instances[refId];
refProp.Value = refInst;
}
else if (refId != "null")
{
2021-01-20 20:45:58 +00:00
LogError($"XmlRobloxFile: Could not resolve reference for {refProp.GetFullName()}");
2019-05-25 23:45:54 +00:00
refProp.Value = null;
}
}
// Record shared strings.
2019-05-19 04:44:51 +00:00
var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString);
foreach (Property sharedProp in sharedProps)
{
SharedString shared = sharedProp.CastValue<SharedString>();
2021-01-20 20:45:58 +00:00
if (shared == null)
{
var nullBuffer = Array.Empty<byte>();
shared = SharedString.FromBuffer(nullBuffer);
sharedProp.Value = shared;
}
SharedStrings.Add(shared.Key);
}
}
else
{
throw new Exception("XmlRobloxFile: No 'roblox' element found!");
}
}
2019-05-19 04:44:51 +00:00
public override void Save(Stream stream)
{
XmlDocument doc = new XmlDocument();
2020-08-18 01:12:24 +00:00
2019-05-19 04:44:51 +00:00
XmlElement roblox = doc.CreateElement("roblox");
roblox.SetAttribute("version", "4");
doc.AppendChild(roblox);
2020-08-18 01:12:24 +00:00
RefCounter = 0;
Instances.Clear();
SharedStrings.Clear();
// First, append the metadata
foreach (string key in Metadata.Keys)
{
string value = Metadata[key];
XmlElement meta = doc.CreateElement("Meta");
meta.SetAttribute("name", key);
meta.InnerText = value;
roblox.AppendChild(meta);
}
2019-05-19 04:44:51 +00:00
Instance[] children = GetChildren();
// Record all of the instances.
2019-05-19 04:44:51 +00:00
foreach (Instance inst in children)
XmlRobloxFileWriter.RecordInstances(this, inst);
// Now append them into the document.
2019-05-19 04:44:51 +00:00
foreach (Instance inst in children)
{
if (inst.Archivable)
{
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
roblox.AppendChild(instNode);
}
}
// Append the shared strings.
if (SharedStrings.Count > 0)
{
2019-05-19 04:44:51 +00:00
XmlNode sharedStrings = XmlRobloxFileWriter.WriteSharedStrings(doc, this);
roblox.AppendChild(sharedStrings);
}
// Write the XML file.
using (StringWriter buffer = new StringWriter())
{
2019-05-19 04:44:51 +00:00
XmlWriterSettings settings = XmlRobloxFileWriter.Settings;
using (XmlWriter xmlWriter = XmlWriter.Create(buffer, settings))
doc.WriteContentTo(xmlWriter);
string result = buffer.ToString()
.Replace("<![CDATA[]]>", "");
using (BinaryWriter writer = new BinaryWriter(stream))
{
byte[] data = Encoding.UTF8.GetBytes(result);
2019-05-25 23:45:54 +00:00
stream.SetLength(0);
writer.Write(data);
}
}
}
}
}