Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add setting for optional zip compression (different approach) #244

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions ReClass.NET/DataExchange/ReClass/ReClassDataFile.Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace ReClassNET.DataExchange.ReClass
{
public partial class ReClassDataFile
{
public const string FormatName = "ReClass.NET Data File";
public const string FileExtension = ".xml";
public const string FileExtensionId = "rcdatafile";

private const uint FileVersion = 0x00010001;
private const uint FileVersionCriticalMask = 0xFFFF0000;

private const string SerializationClassName = "__Serialization_Class__";

public const string XmlRootElement = "reclass";
public const string XmlCustomDataElement = "custom_data";
public const string XmlTypeMappingElement = "type_mapping";
public const string XmlEnumsElement = "enums";
public const string XmlEnumElement = "enum";
public const string XmlClassesElement = "classes";
public const string XmlClassElement = "class";
public const string XmlNodeElement = "node";
public const string XmlMethodElement = "method";
public const string XmlVersionAttribute = "version";
public const string XmlPlatformAttribute = "type";
public const string XmlUuidAttribute = "uuid";
public const string XmlNameAttribute = "name";
public const string XmlCommentAttribute = "comment";
public const string XmlHiddenAttribute = "hidden";
public const string XmlAddressAttribute = "address";
public const string XmlTypeAttribute = "type";
public const string XmlReferenceAttribute = "reference";
public const string XmlCountAttribute = "count";
public const string XmlBitsAttribute = "bits";
public const string XmlLengthAttribute = "length";
public const string XmlSizeAttribute = "size";
public const string XmlSignatureAttribute = "signature";
public const string XmlFlagsAttribute = "flags";
public const string XmlItemElement = "item";
public const string XmlValueAttribute = "value";
}
}
333 changes: 333 additions & 0 deletions ReClass.NET/DataExchange/ReClass/ReClassDataFile.Read.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Xml.Linq;
using ReClassNET.DataExchange.ReClass.Legacy;
using ReClassNET.Extensions;
using ReClassNET.Logger;
using ReClassNET.Nodes;
using ReClassNET.Project;

namespace ReClassNET.DataExchange.ReClass
{
public partial class ReClassDataFile
{
public void Load(string filePath, ILogger logger)
{
using var fs = new FileStream(filePath, FileMode.Open);

Load(fs, logger);
}

public void Load(Stream input, ILogger logger)
{
Contract.Requires(input != null);
Contract.Requires(logger != null);

var document = XDocument.Load(input);
if (document.Root?.Element(XmlClassesElement) == null)
{
throw new FormatException("The data has not the correct format.");
}

uint.TryParse(document.Root.Attribute(XmlVersionAttribute)?.Value, out var fileVersion);
if ((fileVersion & FileVersionCriticalMask) > (FileVersion & FileVersionCriticalMask))
{
throw new FormatException($"The file version is unsupported. A newer {Constants.ApplicationName} version is required to read it.");
}

var platform = document.Root.Attribute(XmlPlatformAttribute)?.Value;
if (platform != Constants.Platform)
{
logger.Log(LogLevel.Warning, $"The platform of the file ({platform}) doesn't match the program platform ({Constants.Platform}).");
}

var customDataElement = document.Root.Element(XmlCustomDataElement);
if (customDataElement != null)
{
project.CustomData.Deserialize(customDataElement);
}

var typeMappingElement = document.Root.Element(XmlTypeMappingElement);
if (typeMappingElement != null)
{
project.TypeMapping.Deserialize(typeMappingElement);
}

var enumsElement = document.Root.Element(XmlEnumsElement);
if (enumsElement != null)
{
foreach (var enumElement in enumsElement.Elements(XmlEnumElement))
{
var name = enumElement.Attribute(XmlNameAttribute)?.Value ?? string.Empty;
var useFlagsMode = (bool?)enumElement.Attribute(XmlFlagsAttribute) ?? false;
var size = enumElement.Attribute(XmlSizeAttribute).GetEnumValue<EnumDescription.UnderlyingTypeSize>();

var values = new Dictionary<string, long>();
foreach (var itemElement in enumElement.Elements(XmlItemElement))
{
var itemName = itemElement.Attribute(XmlNameAttribute)?.Value ?? string.Empty;
var itemValue = (long?)itemElement.Attribute(XmlValueAttribute) ?? 0L;

values.Add(itemName, itemValue);
}

var @enum = new EnumDescription
{
Name = name
};
@enum.SetData(useFlagsMode, size, values);

project.AddEnum(@enum);
}
}

var classes = new List<(XElement, ClassNode)>();

var classesElement = document.Root.Element(XmlClassesElement);
if (classesElement != null)
{
foreach (var element in classesElement
.Elements(XmlClassElement)
.DistinctBy(e => e.Attribute(XmlUuidAttribute)?.Value))
{
var node = new ClassNode(false)
{
Uuid = ParseUuid(element.Attribute(XmlUuidAttribute)?.Value),
Name = element.Attribute(XmlNameAttribute)?.Value ?? string.Empty,
Comment = element.Attribute(XmlCommentAttribute)?.Value ?? string.Empty,
AddressFormula = element.Attribute(XmlAddressAttribute)?.Value ?? string.Empty
};

if (!project.ContainsClass(node.Uuid))
{
project.AddClass(node);

classes.Add((element, node));
}
}
}

foreach (var (element, classNode) in classes)
{
var nodes = element.Elements(XmlNodeElement)
.Select(e => CreateNodeFromElement(e, classNode, logger))
.Where(n => n != null);

classNode.BeginUpdate();
classNode.AddNodes(nodes);
classNode.EndUpdate();
}
}

private BaseNode CreateNodeFromElement(XElement element, BaseNode parent, ILogger logger)
{
Contract.Requires(element != null);
Contract.Requires(logger != null);

BaseNode CreateNode()
{
var converter = CustomNodeSerializer.GetReadConverter(element);
if (converter != null)
{
return converter.CreateNodeFromElement(element, parent, project.Classes, logger, CreateNodeFromElement);
}

if (!buildInStringToTypeMap.TryGetValue(element.Attribute(XmlTypeAttribute)?.Value ?? string.Empty, out var nodeType))
{
logger.Log(LogLevel.Error, $"Skipping node with unknown type: {element.Attribute(XmlTypeAttribute)?.Value}");
logger.Log(LogLevel.Warning, element.ToString());

return null;
}

return BaseNode.CreateInstanceFromType(nodeType, false);
}

var node = CreateNode();
if (node == null)
{
logger.Log(LogLevel.Error, "Could not create node.");

return null;
}

node.ParentNode = parent;

node.Name = element.Attribute(XmlNameAttribute)?.Value ?? string.Empty;
node.Comment = element.Attribute(XmlCommentAttribute)?.Value ?? string.Empty;
node.IsHidden = bool.TryParse(element.Attribute(XmlHiddenAttribute)?.Value, out var val) && val;

if (node is BaseWrapperNode wrapperNode)
{
ClassNode GetClassNodeFromElementReference()
{
var reference = ParseUuid(element.Attribute(XmlReferenceAttribute)?.Value);
if (!project.ContainsClass(reference))
{
logger.Log(LogLevel.Error, $"Skipping node with unknown reference: {reference}");
logger.Log(LogLevel.Warning, element.ToString());

return null;
}

return project.GetClassByUuid(reference);
}

// Legacy Support
if (node is ClassPointerNode || node is ClassInstanceArrayNode || node is ClassPointerArrayNode)
{
var innerClass = GetClassNodeFromElementReference();
if (innerClass == null)
{
return null;
}

node = node switch
{
BaseClassArrayNode classArrayNode => classArrayNode.GetEquivalentNode(0, innerClass),
ClassPointerNode classPointerNode => classPointerNode.GetEquivalentNode(innerClass)
};
}
else
{
BaseNode innerNode = null;

if (node is BaseClassWrapperNode)
{
innerNode = GetClassNodeFromElementReference();
if (innerNode == null)
{
return null;
}
}
else
{
var innerElement = element.Elements().FirstOrDefault();
if (innerElement != null)
{
innerNode = CreateNodeFromElement(innerElement, node, logger);
}
}

if (wrapperNode.CanChangeInnerNodeTo(innerNode))
{
var rootWrapperNode = node.GetRootWrapperNode();
if (rootWrapperNode.ShouldPerformCycleCheckForInnerNode()
&& innerNode is ClassNode classNode
&& ClassUtil.IsCyclicIfClassIsAccessibleFromParent(node.GetParentClass(), classNode, project.Classes))
{
logger.Log(LogLevel.Error, $"Skipping node with cyclic class reference: {node.GetParentClass().Name}->{rootWrapperNode.Name}");

return null;
}

wrapperNode.ChangeInnerNode(innerNode);
}
else
{
logger.Log(LogLevel.Error, $"The node {innerNode} is not a valid child for {node}.");
}
}
}

switch (node)
{
case VirtualMethodTableNode vtableNode:
{
var nodes = element
.Elements(XmlMethodElement)
.Select(e => new VirtualMethodNode
{
Name = e.Attribute(XmlNameAttribute)?.Value ?? string.Empty,
Comment = e.Attribute(XmlCommentAttribute)?.Value ?? string.Empty,
IsHidden = (bool?)e.Attribute(XmlHiddenAttribute) ?? false
});

vtableNode.AddNodes(nodes);
break;
}
case UnionNode unionNode:
{
var nodes = element
.Elements()
.Select(e => CreateNodeFromElement(e, unionNode, logger));

unionNode.AddNodes(nodes);
break;
}
case BaseWrapperArrayNode arrayNode:
{
arrayNode.Count = (int?)element.Attribute(XmlCountAttribute) ?? 0;
break;
}
case BaseTextNode textNode:
{
textNode.Length = (int?)element.Attribute(XmlLengthAttribute) ?? 0;
break;
}
case BitFieldNode bitFieldNode:
{
bitFieldNode.Bits = (int?)element.Attribute(XmlBitsAttribute) ?? 0;
break;
}
case FunctionNode functionNode:
{
functionNode.Signature = element.Attribute(XmlSignatureAttribute)?.Value ?? string.Empty;

var reference = ParseUuid(element.Attribute(XmlReferenceAttribute)?.Value);
if (project.ContainsClass(reference))
{
functionNode.BelongsToClass = project.GetClassByUuid(reference);
}
break;
}
case EnumNode enumNode:
{
var enumName = element.Attribute(XmlReferenceAttribute)?.Value ?? string.Empty;
var @enum = project.Enums.FirstOrDefault(e => e.Name == enumName) ?? EnumDescription.Default;

enumNode.ChangeEnum(@enum);
break;
}
}

return node;
}

private static Guid ParseUuid(string raw) => raw == null
? throw new ArgumentNullException()
: raw.Length == 24
? new Guid(Convert.FromBase64String(raw))
: Guid.Parse(raw);

public static Tuple<List<ClassNode>, List<BaseNode>> DeserializeNodesFromStream(Stream input, ReClassNetProject templateProject, ILogger logger)
{
Contract.Requires(input != null);
Contract.Requires(logger != null);
Contract.Ensures(Contract.Result<Tuple<List<ClassNode>, List<BaseNode>>>() != null);

using var project = new ReClassNetProject();
templateProject?.Classes.ForEach(project.AddClass);

var file = new ReClassDataFile(project);
file.Load(input, logger);

var classes = project.Classes
.Where(c => c.Name != SerializationClassName);
if (templateProject != null)
{
classes = classes.Where(c => !templateProject.ContainsClass(c.Uuid));
}

var nodes = project.Classes
.Where(c => c.Name == SerializationClassName)
.SelectMany(c => c.Nodes);

return Tuple.Create(classes.ToList(), nodes.ToList());
}
}
}
Loading