Cleaning up some things.
This commit is contained in:
parent
34642f5656
commit
9c3a673d95
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@ obj/*
|
||||
.vs/*
|
||||
*.suo
|
||||
*.ide
|
||||
*.user
|
||||
*.user
|
||||
Program.cs
|
@ -6,46 +6,41 @@ using LZ4;
|
||||
namespace RobloxFiles.BinaryFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// BinaryRobloxChunk represents a generic LZ4-compressed chunk
|
||||
/// BinaryRobloxFileChunk represents a generic LZ4-compressed chunk
|
||||
/// of data in Roblox's Binary File Format.
|
||||
/// </summary>
|
||||
public class BinaryRobloxChunk
|
||||
public class BinaryRobloxFileChunk
|
||||
{
|
||||
public readonly string ChunkType;
|
||||
public readonly byte[] Reserved;
|
||||
|
||||
public readonly int CompressedSize;
|
||||
public readonly int Size;
|
||||
|
||||
public readonly byte[] Reserved;
|
||||
|
||||
public readonly byte[] CompressedData;
|
||||
public readonly byte[] Data;
|
||||
|
||||
public bool HasCompressedData => (CompressedSize > 0);
|
||||
|
||||
public BinaryRobloxFileReader GetDataReader()
|
||||
{
|
||||
MemoryStream buffer = new MemoryStream(Data);
|
||||
return new BinaryRobloxFileReader(buffer);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ChunkType + " Chunk [" + Size + " bytes]";
|
||||
}
|
||||
|
||||
public BinaryRobloxReader GetReader(string chunkType)
|
||||
{
|
||||
if (ChunkType == chunkType)
|
||||
{
|
||||
MemoryStream buffer = new MemoryStream(Data);
|
||||
return new BinaryRobloxReader(buffer);
|
||||
}
|
||||
|
||||
throw new Exception("Expected " + chunkType + " ChunkType from the input RobloxBinaryChunk");
|
||||
}
|
||||
|
||||
public BinaryRobloxChunk(BinaryRobloxReader reader)
|
||||
public BinaryRobloxFileChunk(BinaryRobloxFileReader reader)
|
||||
{
|
||||
byte[] bChunkType = reader.ReadBytes(4);
|
||||
ChunkType = Encoding.ASCII.GetString(bChunkType);
|
||||
|
||||
CompressedSize = reader.ReadInt32();
|
||||
Size = reader.ReadInt32();
|
||||
|
||||
Reserved = reader.ReadBytes(4);
|
||||
|
||||
if (HasCompressedData)
|
@ -5,9 +5,9 @@ using System.Text;
|
||||
|
||||
namespace RobloxFiles.BinaryFormat
|
||||
{
|
||||
public class BinaryRobloxReader : BinaryReader
|
||||
public class BinaryRobloxFileReader : BinaryReader
|
||||
{
|
||||
public BinaryRobloxReader(Stream stream) : base(stream) { }
|
||||
public BinaryRobloxFileReader(Stream stream) : base(stream) { }
|
||||
private byte[] lastStringBuffer = new byte[0] { };
|
||||
|
||||
// Reads 'count * sizeof(T)' interleaved bytes and converts
|
@ -7,7 +7,7 @@ using RobloxFiles.BinaryFormat.Chunks;
|
||||
|
||||
namespace RobloxFiles.BinaryFormat
|
||||
{
|
||||
public class BinaryRobloxFile : IRobloxFile
|
||||
public class BinaryRobloxFile : RobloxFile
|
||||
{
|
||||
// Header Specific
|
||||
public const string MagicHeader = "<roblox!\x89\xff\x0d\x0a\x1a\x0a";
|
||||
@ -17,12 +17,8 @@ namespace RobloxFiles.BinaryFormat
|
||||
public uint NumInstances;
|
||||
public byte[] Reserved;
|
||||
|
||||
// IRobloxFile
|
||||
internal readonly Instance BinContents = new Instance("Folder", "BinaryRobloxFile");
|
||||
public Instance Contents => BinContents;
|
||||
|
||||
// Runtime Specific
|
||||
public List<BinaryRobloxChunk> Chunks = new List<BinaryRobloxChunk>();
|
||||
public List<BinaryRobloxFileChunk> Chunks = new List<BinaryRobloxFileChunk>();
|
||||
public override string ToString() => GetType().Name;
|
||||
|
||||
public Instance[] Instances;
|
||||
@ -30,11 +26,17 @@ namespace RobloxFiles.BinaryFormat
|
||||
|
||||
public Dictionary<string, string> Metadata;
|
||||
public Dictionary<uint, string> SharedStrings;
|
||||
|
||||
internal BinaryRobloxFile()
|
||||
{
|
||||
Name = "BinaryRobloxFile";
|
||||
ParentLocked = true;
|
||||
}
|
||||
|
||||
public void ReadFile(byte[] contents)
|
||||
protected override void ReadFile(byte[] contents)
|
||||
{
|
||||
using (MemoryStream file = new MemoryStream(contents))
|
||||
using (BinaryRobloxReader reader = new BinaryRobloxReader(file))
|
||||
using (BinaryRobloxFileReader reader = new BinaryRobloxFileReader(file))
|
||||
{
|
||||
// Verify the signature of the file.
|
||||
byte[] binSignature = reader.ReadBytes(14);
|
||||
@ -59,7 +61,7 @@ namespace RobloxFiles.BinaryFormat
|
||||
{
|
||||
try
|
||||
{
|
||||
BinaryRobloxChunk chunk = new BinaryRobloxChunk(reader);
|
||||
BinaryRobloxFileChunk chunk = new BinaryRobloxFileChunk(reader);
|
||||
Chunks.Add(chunk);
|
||||
|
||||
switch (chunk.ChunkType)
|
||||
@ -88,7 +90,7 @@ namespace RobloxFiles.BinaryFormat
|
||||
reading = false;
|
||||
break;
|
||||
default:
|
||||
Console.WriteLine("Unhandled chunk type: {0}!", chunk.ChunkType);
|
||||
Console.WriteLine("BinaryRobloxFile: Unhandled chunk type: {0}!", chunk.ChunkType);
|
||||
Chunks.Remove(chunk);
|
||||
break;
|
||||
}
|
||||
@ -101,7 +103,7 @@ namespace RobloxFiles.BinaryFormat
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFile(Stream stream)
|
||||
public override void Save(Stream stream)
|
||||
{
|
||||
throw new NotImplementedException("Not implemented yet!");
|
||||
}
|
||||
|
@ -13,9 +13,9 @@
|
||||
return TypeName;
|
||||
}
|
||||
|
||||
public INST(BinaryRobloxChunk chunk)
|
||||
public INST(BinaryRobloxFileChunk chunk)
|
||||
{
|
||||
using (BinaryRobloxReader reader = chunk.GetReader("INST"))
|
||||
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
|
||||
{
|
||||
TypeIndex = reader.ReadInt32();
|
||||
TypeName = reader.ReadString();
|
||||
@ -30,7 +30,7 @@
|
||||
{
|
||||
foreach (int instId in InstanceIds)
|
||||
{
|
||||
Instance inst = new Instance(TypeName);
|
||||
Instance inst = new Instance() { ClassName = TypeName };
|
||||
file.Instances[instId] = inst;
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
||||
public int NumEntries;
|
||||
public Dictionary<string, string> Data = new Dictionary<string, string>();
|
||||
|
||||
public META(BinaryRobloxChunk chunk)
|
||||
public META(BinaryRobloxFileChunk chunk)
|
||||
{
|
||||
using (BinaryRobloxReader reader = chunk.GetReader("META"))
|
||||
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
|
||||
{
|
||||
NumEntries = reader.ReadInt32();
|
||||
|
@ -8,9 +8,9 @@
|
||||
public readonly int[] ChildrenIds;
|
||||
public readonly int[] ParentIds;
|
||||
|
||||
public PRNT(BinaryRobloxChunk chunk)
|
||||
public PRNT(BinaryRobloxFileChunk chunk)
|
||||
{
|
||||
using (BinaryRobloxReader reader = chunk.GetReader("PRNT"))
|
||||
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
|
||||
{
|
||||
Format = reader.ReadByte();
|
||||
NumRelations = reader.ReadInt32();
|
||||
@ -28,14 +28,7 @@
|
||||
int parentId = ParentIds[i];
|
||||
|
||||
Instance child = file.Instances[childId];
|
||||
Instance parent = null;
|
||||
|
||||
if (parentId >= 0)
|
||||
parent = file.Instances[parentId];
|
||||
else
|
||||
parent = file.BinContents;
|
||||
|
||||
child.Parent = parent;
|
||||
child.Parent = (parentId >= 0 ? file.Instances[parentId] : file);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,11 +14,11 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
||||
public readonly int TypeIndex;
|
||||
public readonly PropertyType Type;
|
||||
|
||||
private BinaryRobloxReader Reader;
|
||||
private BinaryRobloxFileReader Reader;
|
||||
|
||||
public PROP(BinaryRobloxChunk chunk)
|
||||
public PROP(BinaryRobloxFileChunk chunk)
|
||||
{
|
||||
Reader = chunk.GetReader("PROP");
|
||||
Reader = chunk.GetDataReader();
|
||||
|
||||
TypeIndex = Reader.ReadInt32();
|
||||
Name = Reader.ReadString();
|
||||
@ -78,7 +78,7 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
||||
// Leave an access point for the original byte sequence, in case this is a BinaryString.
|
||||
// This will allow the developer to read the sequence without any mangling from C# strings.
|
||||
byte[] buffer = Reader.GetLastStringBuffer();
|
||||
props[i].SetRawBuffer(buffer);
|
||||
props[i].RawBuffer = buffer;
|
||||
|
||||
return result;
|
||||
});
|
@ -11,9 +11,9 @@ namespace RobloxFiles.BinaryFormat.Chunks
|
||||
public Dictionary<string, uint> Lookup = new Dictionary<string, uint>();
|
||||
public Dictionary<uint, string> Strings = new Dictionary<uint, string>();
|
||||
|
||||
public SSTR(BinaryRobloxChunk chunk)
|
||||
public SSTR(BinaryRobloxFileChunk chunk)
|
||||
{
|
||||
using (BinaryRobloxReader reader = chunk.GetReader("SSTR"))
|
||||
using (BinaryRobloxFileReader reader = chunk.GetDataReader())
|
||||
{
|
||||
Version = reader.ReadInt32();
|
||||
NumHashes = reader.ReadInt32();
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace RobloxFiles
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface which represents a RobloxFile implementation.
|
||||
/// </summary>
|
||||
public interface IRobloxFile
|
||||
{
|
||||
Instance Contents { get; }
|
||||
|
||||
void ReadFile(byte[] buffer);
|
||||
void WriteFile(Stream stream);
|
||||
}
|
||||
}
|
101
RobloxFile.cs
101
RobloxFile.cs
@ -12,79 +12,36 @@ namespace RobloxFiles
|
||||
/// Represents a loaded *.rbxl/*.rbxm Roblox file.
|
||||
/// All of the surface-level Instances are stored in the RobloxFile's 'Contents' property.
|
||||
/// </summary>
|
||||
public class RobloxFile : IRobloxFile
|
||||
public abstract class RobloxFile : Instance
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates if this RobloxFile has loaded data already.
|
||||
/// </summary>
|
||||
public bool Initialized { get; private set; }
|
||||
protected abstract void ReadFile(byte[] buffer);
|
||||
public abstract void Save(Stream stream);
|
||||
|
||||
/// <summary>
|
||||
/// A reference to the inner IRobloxFile implementation that this RobloxFile opened with.<para/>
|
||||
/// It can be a BinaryRobloxFile, or an XmlRobloxFile.
|
||||
/// Opens a RobloxFile using the provided buffer.
|
||||
/// </summary>
|
||||
public IRobloxFile InnerFile { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A reference to a Folder Instance that stores all of the contents that were loaded.
|
||||
/// </summary>
|
||||
public Instance Contents => InnerFile.Contents;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the RobloxFile from the provided buffer, if it hasn't been Initialized yet.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
public void ReadFile(byte[] buffer)
|
||||
{
|
||||
if (!Initialized)
|
||||
{
|
||||
if (buffer.Length > 14)
|
||||
{
|
||||
string header = Encoding.UTF7.GetString(buffer, 0, 14);
|
||||
IRobloxFile file = null;
|
||||
|
||||
if (header == BinaryRobloxFile.MagicHeader)
|
||||
file = new BinaryRobloxFile();
|
||||
else if (header.StartsWith("<roblox"))
|
||||
file = new XmlRobloxFile();
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
file.ReadFile(buffer);
|
||||
InnerFile = file;
|
||||
|
||||
Initialized = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Unrecognized header!");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFile(Stream stream)
|
||||
{
|
||||
InnerFile.WriteFile(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a RobloxFile from a provided byte sequence that represents the file.
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
private RobloxFile(byte[] buffer)
|
||||
{
|
||||
ReadFile(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a Roblox file from a byte sequence that represents the file.
|
||||
/// </summary>
|
||||
/// <param name="buffer">A byte sequence that represents the file.</param>
|
||||
public static RobloxFile Open(byte[] buffer)
|
||||
{
|
||||
return new RobloxFile(buffer);
|
||||
}
|
||||
if (buffer.Length > 14)
|
||||
{
|
||||
string header = Encoding.UTF7.GetString(buffer, 0, 14);
|
||||
RobloxFile file = null;
|
||||
|
||||
if (header == BinaryRobloxFile.MagicHeader)
|
||||
file = new BinaryRobloxFile();
|
||||
else if (header.StartsWith("<roblox"))
|
||||
file = new XmlRobloxFile();
|
||||
|
||||
if (file != null)
|
||||
{
|
||||
file.ReadFile(buffer);
|
||||
return file;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Unrecognized header!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a Roblox file by reading from a provided Stream.
|
||||
/// </summary>
|
||||
@ -138,17 +95,5 @@ namespace RobloxFiles
|
||||
{
|
||||
return Task.Run(() => Open(filePath));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to access a child/descendant of this file's contents, and/or one of its properties.<para/>
|
||||
/// The provided string should be a period-separated (.) path to what you wish to access.<para/>
|
||||
/// This will throw an exception if any part of the path cannot be found.<para/>
|
||||
///
|
||||
/// ~ Examples ~<para/>
|
||||
/// var terrain = robloxFile["Workspace.Terrain"] as Instance;<para/>
|
||||
/// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;<para/>
|
||||
///
|
||||
/// </summary>
|
||||
public object this[string accessor] => Contents[accessor];
|
||||
}
|
||||
}
|
||||
|
@ -62,14 +62,15 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BinaryFormat\BinaryChunk.cs" />
|
||||
<Compile Include="BinaryFormat\BinaryReader.cs" />
|
||||
<Compile Include="BinaryFormat\BinaryFileChunk.cs" />
|
||||
<Compile Include="BinaryFormat\BinaryFileReader.cs" />
|
||||
<Compile Include="BinaryFormat\BinaryRobloxFile.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\INST.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\META.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\PRNT.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\PROP.cs" />
|
||||
<Compile Include="BinaryFormat\ChunkTypes\SSTR.cs" />
|
||||
<Compile Include="BinaryFormat\Chunks\INST.cs" />
|
||||
<Compile Include="BinaryFormat\Chunks\META.cs" />
|
||||
<Compile Include="BinaryFormat\Chunks\PRNT.cs" />
|
||||
<Compile Include="BinaryFormat\Chunks\PROP.cs" />
|
||||
<Compile Include="BinaryFormat\Chunks\SSTR.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Tree\Enums.cs" />
|
||||
<Compile Include="Tree\Property.cs" />
|
||||
<Compile Include="Tree\Instance.cs" />
|
||||
@ -87,8 +88,7 @@
|
||||
<Compile Include="DataTypes\PhysicalProperties.cs" />
|
||||
<Compile Include="DataTypes\Ray.cs" />
|
||||
<Compile Include="DataTypes\Region3int16.cs" />
|
||||
<Compile Include="Interfaces\IRobloxFile.cs" />
|
||||
<Compile Include="Interfaces\IXmlPropertyToken.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\IXmlPropertyToken.cs" />
|
||||
<Compile Include="Utility\BrickColors.cs" />
|
||||
<Compile Include="DataTypes\Vector3int16.cs" />
|
||||
<Compile Include="DataTypes\Rect.cs" />
|
||||
@ -101,38 +101,38 @@
|
||||
<Compile Include="Utility\MaterialInfo.cs" />
|
||||
<Compile Include="Utility\Quaternion.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\SharedString.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Vector3int16.cs" />
|
||||
<Compile Include="XmlFormat\XmlDataWriter.cs" />
|
||||
<Compile Include="XmlFormat\XmlPropertyTokens.cs" />
|
||||
<Compile Include="XmlFormat\XmlDataReader.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\SharedString.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector3int16.cs" />
|
||||
<Compile Include="XmlFormat\IO\XmlFileWriter.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\XmlPropertyTokens.cs" />
|
||||
<Compile Include="XmlFormat\IO\XmlFileReader.cs" />
|
||||
<Compile Include="XmlFormat\XmlRobloxFile.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Axes.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\BinaryString.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Boolean.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\BrickColor.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\CFrame.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Content.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Color3.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Color3uint8.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\ColorSequence.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Double.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Enum.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Faces.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Float.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Int.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Int64.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\NumberRange.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\NumberSequence.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\PhysicalProperties.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Ray.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Rect.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Ref.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\String.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\UDim.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\UDim2.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Vector2.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Vector3.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Axes.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\BinaryString.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Boolean.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\BrickColor.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\CFrame.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Content.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Color3.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Color3uint8.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\ColorSequence.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Double.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Enum.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Faces.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Float.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Int.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Int64.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\NumberRange.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\NumberSequence.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\PhysicalProperties.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Ray.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Rect.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Ref.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\String.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\UDim.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\UDim2.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector2.cs" />
|
||||
<Compile Include="XmlFormat\PropertyTokens\Tokens\Vector3.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
|
||||
|
200
Tree/Instance.cs
200
Tree/Instance.cs
@ -15,40 +15,23 @@ namespace RobloxFiles
|
||||
public string ClassName;
|
||||
|
||||
/// <summary>A list of properties that are defined under this Instance.</summary>
|
||||
public Dictionary<string, Property> Properties = new Dictionary<string, Property>();
|
||||
private Dictionary<string, Property> props = new Dictionary<string, Property>();
|
||||
public IReadOnlyDictionary<string, Property> Properties => props;
|
||||
|
||||
private List<Instance> Children = new List<Instance>();
|
||||
private Instance rawParent;
|
||||
private Instance parent;
|
||||
|
||||
/// <summary>The name of this Instance, if a Name property is defined.</summary>
|
||||
public string Name => ReadProperty("Name", ClassName);
|
||||
public override string ToString() => Name;
|
||||
|
||||
internal string XmlReferent;
|
||||
/// <summary>A unique identifier for this instance when being serialized as XML.</summary>
|
||||
public string XmlReferent { get; internal set; }
|
||||
|
||||
/// <summary>Creates an instance using the provided ClassName.</summary>
|
||||
/// <param name="className">The ClassName to use for this Instance.</param>
|
||||
public Instance(string className = "Instance")
|
||||
{
|
||||
ClassName = className;
|
||||
}
|
||||
/// <summary>Indicates whether the parent of this object is locked.</summary>
|
||||
public bool ParentLocked { get; protected set; }
|
||||
|
||||
/// <summary>Creates an instance using the provided ClassName and Name.</summary>
|
||||
/// <param name="className">The ClassName to use for this Instance.</param>
|
||||
/// <param name="name">The Name to use for this Instance.</param>
|
||||
public Instance(string className = "Instance", string name = "Instance")
|
||||
{
|
||||
Property propName = new Property()
|
||||
{
|
||||
Type = PropertyType.String,
|
||||
Instance = this,
|
||||
Name = "Name",
|
||||
Value = name,
|
||||
};
|
||||
|
||||
ClassName = className;
|
||||
AddProperty(ref propName);
|
||||
}
|
||||
/// <summary>Indicates whether this object should be serialized.</summary>
|
||||
public bool Archivable = true;
|
||||
|
||||
/// <summary>Returns true if this Instance is an ancestor to the provided Instance.</summary>
|
||||
/// <param name="descendant">The instance whose descendance will be tested against this Instance.</param>
|
||||
@ -72,6 +55,23 @@ namespace RobloxFiles
|
||||
return ancestor.IsAncestorOf(this);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get
|
||||
{
|
||||
Property propName = GetProperty("Name");
|
||||
|
||||
if (propName == null)
|
||||
SetProperty("Name", "Instance");
|
||||
|
||||
return propName.Value.ToString();
|
||||
}
|
||||
set
|
||||
{
|
||||
SetProperty("Name", value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The parent of this Instance, or null if the instance is the root of a tree.<para/>
|
||||
/// Setting the value of this property will throw an exception if:<para/>
|
||||
@ -82,21 +82,24 @@ namespace RobloxFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
return rawParent;
|
||||
return parent;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ParentLocked)
|
||||
throw new Exception("The Parent property of this instance is locked.");
|
||||
|
||||
if (IsAncestorOf(value))
|
||||
throw new Exception("Parent would result in circular reference.");
|
||||
|
||||
if (Parent == this)
|
||||
throw new Exception("Attempt to set parent to self.");
|
||||
|
||||
if (rawParent != null)
|
||||
rawParent.Children.Remove(this);
|
||||
if (parent != null)
|
||||
parent.Children.Remove(this);
|
||||
|
||||
value.Children.Add(this);
|
||||
rawParent = value;
|
||||
parent = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,6 +183,11 @@ namespace RobloxFiles
|
||||
return ancestor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first ancestor of this Instance whose ClassName is the provided string className.
|
||||
/// If the instance is not found, this returns null.
|
||||
/// </summary>
|
||||
/// <param name="name">The Name of the Instance to find.</param>
|
||||
public Instance FindFirstAncestorOfClass(string className)
|
||||
{
|
||||
Instance ancestor = Parent;
|
||||
@ -196,7 +204,8 @@ namespace RobloxFiles
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the first Instance whose ClassName is the provided string className. If the instance is not found, this returns null.
|
||||
/// Returns the first Instance whose ClassName is the provided string className.
|
||||
/// If the instance is not found, this returns null.
|
||||
/// </summary>
|
||||
/// <param name="className">The ClassName of the Instance to find.</param>
|
||||
public Instance FindFirstChildOfClass(string className, bool recursive = false)
|
||||
@ -228,21 +237,77 @@ namespace RobloxFiles
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a property with the specified property name, and returns it as an object.
|
||||
/// Returns a Property object if a property with the specified name is defined in this Instance.
|
||||
/// </summary>
|
||||
public Property GetProperty(string name)
|
||||
{
|
||||
Property result = null;
|
||||
|
||||
if (Properties.ContainsKey(name))
|
||||
result = Properties[name];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds or creates a property with the specified name, and sets its value to the provided object.
|
||||
/// Returns the property object that had its value set, if the value is not null.
|
||||
/// </summary>
|
||||
public Property SetProperty(string name, object value, PropertyType? preferType = null)
|
||||
{
|
||||
Property prop = GetProperty(name) ?? new Property()
|
||||
{
|
||||
Type = preferType ?? PropertyType.Unknown,
|
||||
Name = name
|
||||
};
|
||||
|
||||
if (preferType == null)
|
||||
{
|
||||
object oldValue = prop.Value;
|
||||
|
||||
Type oldType = oldValue?.GetType();
|
||||
Type newType = value?.GetType();
|
||||
|
||||
if (oldType != newType)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
RemoveProperty(name);
|
||||
return prop;
|
||||
}
|
||||
|
||||
string typeName = newType.Name;
|
||||
|
||||
if (value is Instance)
|
||||
typeName = "Ref";
|
||||
else if (value is int)
|
||||
typeName = "Int";
|
||||
else if (value is long)
|
||||
typeName = "Int64";
|
||||
|
||||
Enum.TryParse(typeName, out prop.Type);
|
||||
}
|
||||
}
|
||||
|
||||
prop.Value = value;
|
||||
|
||||
if (prop.Instance == null)
|
||||
AddProperty(ref prop);
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks for a property with the specified property name, and returns its value as an object.
|
||||
/// <para/>The resulting value may be null if the property is not serialized.
|
||||
/// <para/>You can use the templated ReadProperty overload to fetch it as a specific type with a default value provided.
|
||||
/// </summary>
|
||||
/// <param name="propertyName">The name of the property to be fetched from this Instance.</param>
|
||||
/// <returns>An object reference to the value of the specified property, if it exists.</returns>
|
||||
///
|
||||
public object ReadProperty(string propertyName)
|
||||
{
|
||||
Property property = null;
|
||||
|
||||
if (Properties.ContainsKey(propertyName))
|
||||
property = Properties[propertyName];
|
||||
|
||||
return (property != null ? property.Value : null);
|
||||
Property property = GetProperty(propertyName);
|
||||
return property?.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -291,52 +356,39 @@ namespace RobloxFiles
|
||||
|
||||
/// <summary>
|
||||
/// Adds a property by reference to this Instance's property list.
|
||||
/// This is used during the file loading procedure.
|
||||
/// </summary>
|
||||
/// <param name="prop">A reference to the property that will be added.</param>
|
||||
internal void AddProperty(ref Property prop)
|
||||
{
|
||||
Properties.Add(prop.Name, prop);
|
||||
prop.Instance = this;
|
||||
|
||||
if (prop.Name == "Name")
|
||||
{
|
||||
Property nameProp = GetProperty("Name");
|
||||
|
||||
if (nameProp != null)
|
||||
{
|
||||
nameProp.Value = prop.Value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
props.Add(prop.Name, prop);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to access a child/descendant of this Instance, and/or one of its properties.<para/>
|
||||
/// The provided string should be a period-separated (.) path to what you wish to access.<para/>
|
||||
/// This will throw an exception if any part of the path cannot be found.<para/>
|
||||
///
|
||||
/// ~ Examples ~<para/>
|
||||
/// var terrain = robloxFile["Workspace.Terrain"] as Instance;<para/>
|
||||
/// var currentCamera = robloxFile["Workspace.CurrentCamera"] as Property;<para/>
|
||||
///
|
||||
/// Removes a property with the provided name if a property with the provided name exists.
|
||||
/// </summary>
|
||||
public object this[string accessor]
|
||||
/// <param name="name">The name of the property to be removed.</param>
|
||||
/// <returns>True if a property with the provided name was removed.</returns>
|
||||
public bool RemoveProperty(string name)
|
||||
{
|
||||
get
|
||||
{
|
||||
Instance result = this;
|
||||
Property prop = GetProperty(name);
|
||||
|
||||
foreach (string name in accessor.Split('.'))
|
||||
{
|
||||
Instance next = result.FindFirstChild(name);
|
||||
if (prop != null)
|
||||
prop.Instance = null;
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
// Check if there is any property with this name.
|
||||
Property prop = null;
|
||||
|
||||
if (result.Properties.ContainsKey(name))
|
||||
prop = result.Properties[name];
|
||||
else
|
||||
throw new Exception(name + " is not a valid member of " + result.Name);
|
||||
|
||||
return prop;
|
||||
}
|
||||
|
||||
result = next;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return props.Remove(name);
|
||||
}
|
||||
}
|
||||
}
|
@ -39,13 +39,13 @@ namespace RobloxFiles
|
||||
public class Property
|
||||
{
|
||||
public string Name;
|
||||
public Instance Instance;
|
||||
public Instance Instance { get; internal set; }
|
||||
|
||||
public PropertyType Type;
|
||||
public object Value;
|
||||
|
||||
public string XmlToken = "";
|
||||
private byte[] RawBuffer = null;
|
||||
public byte[] RawBuffer { get; internal set; }
|
||||
|
||||
public bool HasRawBuffer
|
||||
{
|
||||
@ -116,15 +116,5 @@ namespace RobloxFiles
|
||||
|
||||
return string.Join(" ", typeName, Name, '=', valueLabel);
|
||||
}
|
||||
|
||||
internal void SetRawBuffer(byte[] buffer)
|
||||
{
|
||||
RawBuffer = buffer;
|
||||
}
|
||||
|
||||
public byte[] GetRawBuffer()
|
||||
{
|
||||
return RawBuffer;
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public static class XmlDataReader
|
||||
public static class XmlRobloxFileReader
|
||||
{
|
||||
private static Func<string, Exception> createErrorHandler(string label)
|
||||
{
|
||||
@ -59,10 +59,12 @@ namespace RobloxFiles.XmlFormat
|
||||
|
||||
if (tokenHandler != null)
|
||||
{
|
||||
Property prop = new Property();
|
||||
prop.Name = propName.InnerText;
|
||||
prop.Instance = instance;
|
||||
prop.XmlToken = propType;
|
||||
Property prop = new Property()
|
||||
{
|
||||
Name = propName.InnerText,
|
||||
Instance = instance,
|
||||
XmlToken = propType
|
||||
};
|
||||
|
||||
if (!tokenHandler.ReadProperty(prop, propNode))
|
||||
Console.WriteLine("Could not read property: " + prop.GetFullName() + '!');
|
||||
@ -88,19 +90,20 @@ namespace RobloxFiles.XmlFormat
|
||||
if (classToken == null)
|
||||
throw error("Got an Item without a defined 'class' attribute!");
|
||||
|
||||
Instance inst = new Instance(classToken.InnerText);
|
||||
Instance inst = new Instance() { ClassName = classToken.InnerText };
|
||||
|
||||
// The 'referent' attribute is optional, but should be defined if a Ref property needs to link to this Instance.
|
||||
XmlNode refToken = instNode.Attributes.GetNamedItem("referent");
|
||||
|
||||
if (refToken != null && file != null)
|
||||
{
|
||||
string refId = refToken.InnerText;
|
||||
string referent = refToken.InnerText;
|
||||
inst.XmlReferent = referent;
|
||||
|
||||
if (file.Instances.ContainsKey(refId))
|
||||
if (file.Instances.ContainsKey(referent))
|
||||
throw error("Got an Item with a duplicate 'referent' attribute!");
|
||||
|
||||
file.Instances.Add(refId, inst);
|
||||
file.Instances.Add(referent, inst);
|
||||
}
|
||||
|
||||
// Process the child nodes of this instance.
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
@ -7,19 +8,18 @@ using RobloxFiles.XmlFormat.PropertyTokens;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public static class XmlDataWriter
|
||||
public static class XmlRobloxFileWriter
|
||||
{
|
||||
public static XmlWriterSettings Settings = new XmlWriterSettings()
|
||||
public static readonly XmlWriterSettings Settings = new XmlWriterSettings()
|
||||
{
|
||||
Indent = true,
|
||||
IndentChars = "\t",
|
||||
NewLineChars = "\r\n",
|
||||
Encoding = Encoding.UTF8,
|
||||
OmitXmlDeclaration = true,
|
||||
NamespaceHandling = NamespaceHandling.Default
|
||||
OmitXmlDeclaration = true
|
||||
};
|
||||
|
||||
private static string CreateReferent()
|
||||
public static string CreateReferent()
|
||||
{
|
||||
Guid referentGuid = Guid.NewGuid();
|
||||
|
||||
@ -40,21 +40,18 @@ namespace RobloxFiles.XmlFormat
|
||||
foreach (Instance child in inst.GetChildren())
|
||||
RecordInstances(file, child);
|
||||
|
||||
string referent = CreateReferent();
|
||||
file.Instances.Add(referent, inst);
|
||||
inst.XmlReferent = referent;
|
||||
if (inst.XmlReferent == "")
|
||||
inst.XmlReferent = CreateReferent();
|
||||
|
||||
file.Instances.Add(inst.XmlReferent, inst);
|
||||
}
|
||||
|
||||
public static XmlElement CreateRobloxElement(XmlDocument doc)
|
||||
{
|
||||
XmlElement roblox = doc.CreateElement("roblox");
|
||||
doc.AppendChild(roblox);
|
||||
|
||||
roblox.SetAttribute("xmlns:xmime", "http://www.w3.org/2005/05/xmlmime");
|
||||
roblox.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
|
||||
roblox.SetAttribute("xsi:noNamespaceSchemaLocation", "http://www.roblox.com/roblox.xsd");
|
||||
roblox.SetAttribute("version", "4");
|
||||
|
||||
doc.AppendChild(roblox);
|
||||
|
||||
XmlElement externalNull = doc.CreateElement("External");
|
||||
roblox.AppendChild(externalNull);
|
||||
externalNull.InnerText = "null";
|
||||
@ -150,7 +147,7 @@ namespace RobloxFiles.XmlFormat
|
||||
instNode.AppendChild(propsNode);
|
||||
|
||||
var props = instance.Properties;
|
||||
|
||||
|
||||
foreach (string propName in props.Keys)
|
||||
{
|
||||
Property prop = props[propName];
|
||||
@ -182,7 +179,7 @@ namespace RobloxFiles.XmlFormat
|
||||
string data = file.SharedStrings[md5];
|
||||
byte[] buffer = Convert.FromBase64String(data);
|
||||
|
||||
bufferProp.SetRawBuffer(buffer);
|
||||
bufferProp.RawBuffer = buffer;
|
||||
binaryWriter.WriteProperty(bufferProp, doc, sharedString);
|
||||
|
||||
sharedStrings.AppendChild(sharedString);
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
@ -15,14 +15,14 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
prop.Value = base64;
|
||||
|
||||
byte[] buffer = Convert.FromBase64String(base64);
|
||||
prop.SetRawBuffer(buffer);
|
||||
prop.RawBuffer = buffer;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
||||
{
|
||||
byte[] data = prop.GetRawBuffer();
|
||||
byte[] data = prop.RawBuffer;
|
||||
string value = Convert.ToBase64String(data);
|
||||
|
||||
if (value.Length > 72)
|
@ -22,7 +22,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
// Roblox technically doesn't support this anymore, but load it anyway :P
|
||||
byte[] buffer = Convert.FromBase64String(content);
|
||||
prop.SetRawBuffer(buffer);
|
||||
prop.RawBuffer = buffer;
|
||||
}
|
||||
}
|
||||
|
@ -20,8 +20,6 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
{
|
||||
return XmlPropertyTokens.ReadPropertyGeneric<int>(prop, PropertyType.Int, token);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void WriteProperty(Property prop, XmlDocument doc, XmlNode node)
|
@ -78,6 +78,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
|
||||
XmlElement element = doc.CreateElement(elementType);
|
||||
element.InnerText = value.ToInvariantString();
|
||||
|
||||
node.AppendChild(element);
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
read[i] = Vector3Token.ReadVector3(fieldToken);
|
||||
}
|
||||
|
||||
Vector3 origin = read[0],
|
||||
Vector3 origin = read[0],
|
||||
direction = read[1];
|
||||
|
||||
Ray ray = new Ray(origin, direction);
|
||||
@ -42,11 +42,12 @@ namespace RobloxFiles.XmlFormat.PropertyTokens
|
||||
Ray ray = prop.Value as Ray;
|
||||
|
||||
XmlElement origin = doc.CreateElement("origin");
|
||||
Vector3Token.WriteVector3(doc, origin, ray.Origin);
|
||||
node.AppendChild(origin);
|
||||
|
||||
XmlElement direction = doc.CreateElement("direction");
|
||||
|
||||
Vector3Token.WriteVector3(doc, origin, ray.Origin);
|
||||
Vector3Token.WriteVector3(doc, direction, ray.Direction);
|
||||
|
||||
node.AppendChild(origin);
|
||||
node.AppendChild(direction);
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
using RobloxFiles;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public static class XmlPropertyTokens
|
||||
@ -60,7 +58,6 @@ namespace RobloxFiles.XmlFormat
|
||||
}
|
||||
|
||||
prop.Type = propType;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
@ -8,23 +8,22 @@ using System.Xml;
|
||||
|
||||
namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
public class XmlRobloxFile : IRobloxFile
|
||||
public class XmlRobloxFile : RobloxFile
|
||||
{
|
||||
// IRobloxFile
|
||||
internal readonly Instance XmlContents = new Instance("Folder", "XmlRobloxFile");
|
||||
public Instance Contents => XmlContents;
|
||||
|
||||
// Runtime Specific
|
||||
public readonly XmlDocument Root = new XmlDocument();
|
||||
|
||||
public Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
||||
public Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
|
||||
|
||||
public void ReadFile(byte[] buffer)
|
||||
{
|
||||
Instances.Clear();
|
||||
SharedStrings.Clear();
|
||||
internal Dictionary<string, Instance> Instances = new Dictionary<string, Instance>();
|
||||
internal Dictionary<string, string> SharedStrings = new Dictionary<string, string>();
|
||||
|
||||
internal XmlRobloxFile()
|
||||
{
|
||||
Name = "XmlRobloxFile";
|
||||
ParentLocked = true;
|
||||
}
|
||||
|
||||
protected override void ReadFile(byte[] buffer)
|
||||
{
|
||||
try
|
||||
{
|
||||
string xml = Encoding.UTF8.GetString(buffer);
|
||||
@ -53,22 +52,22 @@ namespace RobloxFiles.XmlFormat
|
||||
{
|
||||
if (child.Name == "Item")
|
||||
{
|
||||
Instance item = XmlDataReader.ReadInstance(child, this);
|
||||
item.Parent = XmlContents;
|
||||
Instance item = XmlRobloxFileReader.ReadInstance(child, this);
|
||||
item.Parent = this;
|
||||
}
|
||||
else if (child.Name == "SharedStrings")
|
||||
{
|
||||
XmlDataReader.ReadSharedStrings(child, this);
|
||||
XmlRobloxFileReader.ReadSharedStrings(child, this);
|
||||
}
|
||||
}
|
||||
|
||||
// Query the properties.
|
||||
var props = Instances.Values
|
||||
var allProps = Instances.Values
|
||||
.SelectMany(inst => inst.Properties)
|
||||
.Select(pair => pair.Value);
|
||||
|
||||
// Resolve referent properties.
|
||||
var refProps = props.Where(prop => prop.Type == PropertyType.Ref);
|
||||
var refProps = allProps.Where(prop => prop.Type == PropertyType.Ref);
|
||||
|
||||
foreach (Property refProp in refProps)
|
||||
{
|
||||
@ -87,7 +86,7 @@ namespace RobloxFiles.XmlFormat
|
||||
}
|
||||
|
||||
// Resolve shared strings.
|
||||
var sharedProps = props.Where(prop => prop.Type == PropertyType.SharedString);
|
||||
var sharedProps = allProps.Where(prop => prop.Type == PropertyType.SharedString);
|
||||
|
||||
foreach (Property sharedProp in sharedProps)
|
||||
{
|
||||
@ -99,13 +98,13 @@ namespace RobloxFiles.XmlFormat
|
||||
sharedProp.Value = value;
|
||||
|
||||
byte[] data = Convert.FromBase64String(value);
|
||||
sharedProp.SetRawBuffer(data);
|
||||
|
||||
continue;
|
||||
sharedProp.RawBuffer = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
string name = sharedProp.GetFullName();
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name);
|
||||
}
|
||||
|
||||
string name = sharedProp.GetFullName();
|
||||
Console.WriteLine("XmlRobloxFile: Could not resolve shared string for {0}", name);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -114,38 +113,41 @@ namespace RobloxFiles.XmlFormat
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFile(Stream stream)
|
||||
public override void Save(Stream stream)
|
||||
{
|
||||
XmlDocument doc = new XmlDocument();
|
||||
XmlElement roblox = XmlDataWriter.CreateRobloxElement(doc);
|
||||
|
||||
XmlElement roblox = doc.CreateElement("roblox");
|
||||
roblox.SetAttribute("version", "4");
|
||||
doc.AppendChild(roblox);
|
||||
|
||||
Instances.Clear();
|
||||
SharedStrings.Clear();
|
||||
|
||||
Instance[] topLevelItems = Contents.GetChildren();
|
||||
Instance[] children = GetChildren();
|
||||
|
||||
// First, record all of the instances.
|
||||
foreach (Instance inst in topLevelItems)
|
||||
XmlDataWriter.RecordInstances(this, inst);
|
||||
foreach (Instance inst in children)
|
||||
XmlRobloxFileWriter.RecordInstances(this, inst);
|
||||
|
||||
// Now append them into the document.
|
||||
foreach (Instance inst in Contents.GetChildren())
|
||||
foreach (Instance inst in children)
|
||||
{
|
||||
XmlNode instNode = XmlDataWriter.WriteInstance(inst, doc, this);
|
||||
XmlNode instNode = XmlRobloxFileWriter.WriteInstance(inst, doc, this);
|
||||
roblox.AppendChild(instNode);
|
||||
}
|
||||
|
||||
// Append the shared strings.
|
||||
if (SharedStrings.Count > 0)
|
||||
{
|
||||
XmlNode sharedStrings = XmlDataWriter.WriteSharedStrings(doc, this);
|
||||
XmlNode sharedStrings = XmlRobloxFileWriter.WriteSharedStrings(doc, this);
|
||||
roblox.AppendChild(sharedStrings);
|
||||
}
|
||||
|
||||
// Write the XML file.
|
||||
using (StringWriter buffer = new StringWriter())
|
||||
{
|
||||
XmlWriterSettings settings = XmlDataWriter.Settings;
|
||||
XmlWriterSettings settings = XmlRobloxFileWriter.Settings;
|
||||
|
||||
using (XmlWriter xmlWriter = XmlWriter.Create(buffer, settings))
|
||||
doc.WriteContentTo(xmlWriter);
|
||||
|
Loading…
Reference in New Issue
Block a user