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