Large scale refactor to add class support!

Instance classes are now strongly typed with real property fields that
are derived from the JSON API Dump! This required a lot of reworking
across the board:

- Classes and Enums are auto-generated in the 'Generated' folder now.
This is done using a custom built-in plugin, which can be found in
the Plugins folder of this project.
- Property objects are now tied to .NET's reflection system. Reading
and writing from them will try to redirect into a field of the
Instance they are bound to.
- Property types that were loosely defined now have proper data types
(such as Color3uint8, Content, ProtectedString, SharedString, etc)
- Fixed an error with the CFrame directional vectors.
- The binary PRNT chunk now writes instances in child->parent order.
- Enums are now generated correctly, with up-to-date values.
- INST chunks are now referred to as 'Classes' instead of 'Types'.
- Unary operator added to Vector2 and Vector3.
- CollectionService tags can now be manipulated per-instance using
the Instance.Tags member.
- The Instance.Archivable property now works correctly.
- XML files now save/load metadata correctly.
- Cleaned up the property tokens directory.

I probably missed a few things, but that's a general overview of
everything that changed.
This commit is contained in:
CloneTrooper1019
2019-06-30 17:01:19 -05:00
parent 8e01f33d6b
commit de8df15d3f
67 changed files with 6297 additions and 667 deletions

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
@ -16,10 +18,15 @@ namespace RobloxFiles.BinaryFormat
public string ChunkType { get; private set; }
public long ChunkStart { get; private set; }
public Dictionary<string, INST> TypeMap;
public Dictionary<string, INST> ClassMap;
public readonly BinaryRobloxFile File;
// Instances in parent->child order
public List<Instance> Instances;
// Instances in child->parent order
public List<Instance> PostInstances;
public BinaryRobloxFileWriter(BinaryRobloxFile file, Stream workBuffer = null) : base(workBuffer ?? new MemoryStream())
{
File = file;
@ -28,7 +35,9 @@ namespace RobloxFiles.BinaryFormat
ChunkType = "";
Instances = new List<Instance>();
TypeMap = new Dictionary<string, INST>();
PostInstances = new List<Instance>();
ClassMap = new Dictionary<string, INST>();
}
private static byte[] GetBytes<T>(T value, int bufferSize, IntPtr converter)
@ -144,36 +153,64 @@ namespace RobloxFiles.BinaryFormat
{
foreach (Instance instance in instances)
{
int instId = (int)(File.NumInstances++);
if (!instance.Archivable)
continue;
int instId = (int)(File.NumInstances++);
instance.Referent = instId.ToString();
Instances.Add(instance);
string className = instance.ClassName;
INST inst = null;
if (!TypeMap.ContainsKey(className))
if (!ClassMap.ContainsKey(className))
{
inst = new INST()
{
TypeName = className,
ClassName = className,
InstanceIds = new List<int>(),
IsService = instance.IsService
};
TypeMap.Add(className, inst);
ClassMap.Add(className, inst);
}
else
{
inst = TypeMap[className];
inst = ClassMap[className];
}
inst.NumInstances++;
inst.InstanceIds.Add(instId);
RecordInstances(instance.GetChildren());
PostInstances.Add(instance);
}
}
internal void ApplyClassMap()
{
File.Instances = Instances.ToArray();
var classNames = ClassMap
.Select(type => type.Key)
.ToList();
classNames.Sort(StringComparer.Ordinal);
var classes = classNames
.Select(className => ClassMap[className])
.ToArray();
for (int i = 0; i < classes.Length; i++, File.NumClasses++)
{
string className = classNames[i];
INST inst = ClassMap[className];
inst.ClassIndex = i;
}
File.Classes = classes;
}
// Marks that we are writing a chunk.
public bool StartWritingChunk(string chunkType)
@ -236,5 +273,19 @@ namespace RobloxFiles.BinaryFormat
return chunk;
}
public void SaveChunk(IBinaryFileChunk handler)
{
var chunk = handler.SaveAsChunk(this);
File.Chunks.Add(chunk);
}
public BinaryRobloxFileChunk WriteEndChunk()
{
StartWritingChunk("END\0");
WriteString("</roblox>", true);
return FinishWritingChunk(false);
}
}
}