// Methods public override void Load(Stream fileStream, Dictionary <string, SetObjectType> objectTemplates) { // Header var reader = new BINAReader(fileStream); Header = reader.ReadHeader(); reader.JumpAhead(0x2C); // Skip "test" string uint objectLength = reader.ReadUInt32(); uint objectOffset = reader.ReadUInt32(); uint groupLength = reader.ReadUInt32(); uint groupOffset = reader.ReadUInt32(); Console.WriteLine($"Object Count: {objectLength}"); Console.WriteLine($"Object Table Offset Location: {objectOffset}"); Console.WriteLine($"Group Count: {groupLength}"); Console.WriteLine($"Group Table Offset Location: {groupOffset}"); //Groups reader.JumpTo(groupOffset, false); for (uint i = 0; i < groupLength; ++i) { //What we know so far: //First 4 bytes is a name for the group (usually GroupHelperXX?) //Second 4 bytes are the type? (according to LibS06), might be a second name (stuff like next_set/GroupHelperXX)? //Third 4 bytes is the amount of objects in this group. //Last 4 bytes is a list of the object IDs in this group. uint nameOffset = reader.ReadUInt32(); //Name uint typeOffset = reader.ReadUInt32(); //Type? uint groupObjectCount = reader.ReadUInt32(); //Count uint groupObjectOffset = reader.ReadUInt32(); //Address of Objects string groupName = string.Empty; string groupType = string.Empty; long pos = reader.BaseStream.Position; reader.JumpTo(nameOffset, false); groupName = reader.ReadNullTerminatedString(); groupNames.Add(groupName); reader.JumpTo(typeOffset, false); groupType = reader.ReadNullTerminatedString(); groupTypes.Add(groupType); reader.JumpTo(groupObjectOffset, false); for (int c = 0; c < groupObjectCount; c++) { reader.JumpAhead(4); uint objID = reader.ReadUInt32(); objGroupData.Add($"{groupName}|{groupType}|{objID}"); groupIDs.Add(objID); } reader.JumpTo(pos, true); } // Data reader.JumpTo(objectOffset, false); for (uint i = 0; i < objectLength; ++i) { Objects.Add(ReadObject(i)); } // TODO: Read Footer // Sub-Methods SetObject ReadObject(uint id) { // Object Entry var obj = new SetObject(); uint nameOffset = reader.ReadUInt32(); uint typeOffset = reader.ReadUInt32(); reader.JumpAhead(16); obj.Transform.Position = reader.ReadVector3(); reader.JumpAhead(4); obj.Transform.Rotation = new Quaternion(reader.ReadVector4()); uint paramCount = reader.ReadUInt32(); uint paramOffset = reader.ReadUInt32(); // Object Parameters long pos = reader.BaseStream.Position; for (uint i = 0; i < paramCount; ++i) { reader.JumpTo(paramOffset + i * 0x14, false); obj.Parameters.Add(ReadParam()); } // Object Name reader.JumpTo(nameOffset, false); obj.CustomData.Add("Name", new SetObjectParam( typeof(string), reader.ReadNullTerminatedString())); // Object Type reader.JumpTo(typeOffset, false); obj.ObjectType = reader.ReadNullTerminatedString(); obj.ObjectID = id; //Object Group if (!groupIDs.Contains(id)) { obj.CustomData.Add("GroupName", new SetObjectParam( typeof(string), "")); obj.CustomData.Add("GroupType", new SetObjectParam( typeof(string), "")); } else { string[] groupData = objGroupData[groupIDs.IndexOf(id)].Split('|'); obj.CustomData.Add("GroupName", new SetObjectParam( typeof(string), groupData[0])); obj.CustomData.Add("GroupType", new SetObjectParam( typeof(string), groupData[1])); } reader.JumpTo(pos, true); return(obj); } SetObjectParam ReadParam() { var param = new SetObjectParam(); uint type = reader.ReadUInt32(); switch (type) { case 0: param.DataType = typeof(bool); param.Data = (reader.ReadUInt32() == 1); break; case 1: param.DataType = typeof(int); param.Data = reader.ReadInt32(); break; case 2: param.DataType = typeof(float); param.Data = reader.ReadSingle(); break; case 3: uint offset = reader.ReadUInt32(); uint amount = reader.ReadUInt32(); if (amount != 1) { Console.WriteLine($"WARNING: Amount != 1. ({amount})"); } long pos = reader.BaseStream.Position; reader.JumpTo(offset, false); param.DataType = typeof(string); param.Data = reader.ReadNullTerminatedString(); reader.JumpTo(pos, true); break; case 4: param.DataType = typeof(Vector3); param.Data = reader.ReadVector3(); break; case 6: param.DataType = typeof(uint); param.Data = reader.ReadUInt32(); break; default: Console.WriteLine($"WARNING: Unknown object param type {type}!"); return(null); } return(param); } }
public void GensExportXML(Stream fileStream, Dictionary <string, SetObjectType> objectTemplates = null, Dictionary <string, string> ColorstoGensRenamers = null, Dictionary <string, string> ColorstoGensObjPhys = null, Dictionary <string, string> ColorstoGensPosYMods = null, Dictionary <string, string> ColorstoGensRotateXMods = null, Dictionary <string, string> ColorstoGensRotateYMods = null, Dictionary <string, string> ColorstoGensParamMods = null) { // Convert to XML file and save var rootElem = new XElement("SetObject"); foreach (var obj in Objects) { // Skip objects with no template. if (!objectTemplates.ContainsKey(obj.ObjectType)) { continue; } // Generate Object Element string GensObjName = obj.ObjectType; // Rename if applicable foreach (var node in ColorstoGensRenamers) { if (GensObjName == node.Value) { GensObjName = node.Key; } } var objElem = new XElement(GensObjName); // Generate CustomData Element // Messy use RangeOut value as Range value. foreach (var customData in obj.CustomData) { // Experimental - use RangeIn as Range for SoundPoint if (customData.Key == "RangeIn") { if (obj.ObjectType == "EnvSound") { objElem.Add(GenerateParamElementGens( customData.Value, "Range")); } } else if (customData.Key == "RangeOut") { if (obj.ObjectType == "EnvSound") { objElem.Add(GenerateParamElementGens( customData.Value, "Radius")); } else { objElem.Add(GenerateParamElementGens( customData.Value, "Range")); } } } // Generate Parameters Element var template = objectTemplates?[obj.ObjectType]; for (int i = 0; i < obj.Parameters.Count; ++i) { string name = template?.Parameters[i].Name; // Ignore parameters containing "Unknown" if (!name.Contains("Unknown")) { objElem.Add(GenerateParamElementGens(obj.Parameters[i], template?.Parameters[i].Name)); } } // Change to ObjectPhysics if necessary foreach (var node in ColorstoGensObjPhys) { if (obj.ObjectType == node.Key) { var param = new SetObjectParam(); param.DataType = typeof(string); param.Data = obj.ObjectType; objElem.Add(GenerateParamElementGens(param, "Type")); objElem.Name = "ObjectPhysics"; } } // Generate Transforms Elements // Apply position to objects that need it float posYModifier = new float(); foreach (var node in ColorstoGensPosYMods) { if (obj.ObjectType == node.Key) { posYModifier = float.Parse(node.Value.ToString()); break; } } objElem.Add(GeneratePositionElement(obj.Transform, obj.ObjectType, posYModifier)); // Apply rotation to objects that need it // X float rotateXModifier = new float(); foreach (var node in ColorstoGensRotateXMods) { if (obj.ObjectType == node.Key) { rotateXModifier = float.Parse(node.Value.ToString()); break; } } // Y float rotateYModifier = new float(); foreach (var node in ColorstoGensRotateYMods) { if (obj.ObjectType == node.Key) { rotateYModifier = float.Parse(node.Value.ToString()); break; } } objElem.Add(GenerateRotationElement(obj.Transform, obj.ObjectType, rotateXModifier, rotateYModifier)); // Generate ID Element var objIDAttr = new XElement("SetObjectID", obj.ObjectID); objElem.Add(objIDAttr); // Generate MultiSet Elements if (obj.Children.Length > 0) { var multiElem = new XElement("MultiSetParam"); for (int i = 0; i < obj.Children.Length; ++i) { var childElem = new XElement("Element"); childElem.Add(new XElement("Index", i + 1)); childElem.Add(GeneratePositionElement(obj.Children[i], obj.ObjectType, posYModifier)); childElem.Add(GenerateRotationElement(obj.Children[i], obj.ObjectType, rotateXModifier, rotateYModifier)); multiElem.Add(childElem); } multiElem.Add(new XElement("BaseLine", 1)); multiElem.Add(new XElement("Direction", 0)); multiElem.Add(new XElement("Interval", 1)); multiElem.Add(new XElement("IntervalBase", 0)); multiElem.Add(new XElement("PositionBase", 0)); multiElem.Add(new XElement("RotationBase", 0)); objElem.Add(multiElem); } // Add all of this to the XDocument rootElem.Add(objElem); } var xml = new XDocument(rootElem); xml.Save(fileStream); // Sub-Methods XElement GenerateParamElementGens( SetObjectParam param, string name) { var dataType = param.DataType; var elem = new XElement((string.IsNullOrEmpty(name)) ? "Parameter" : name); if (dataType == typeof(Vector3)) { // Scale var tempVector3 = new Vector3(); tempVector3 = (Vector3)param.Data; tempVector3.X = (tempVector3.X / 10); tempVector3.Y = (tempVector3.Y / 10); tempVector3.Z = (tempVector3.Z / 10); param.Data = tempVector3; Helpers.XMLWriteVector3(elem, (Vector3)param.Data); } else if (dataType == typeof(Vector4) || dataType == typeof(Quaternion)) { Helpers.XMLWriteVector4(elem, (Vector4)param.Data); } else if (dataType == typeof(Single)) { var singleValue = new Single(); singleValue = float.Parse(param.Data.ToString()); // Parameter scaling foreach (var node in ColorstoGensParamMods) { if (name.Contains(node.Key)) { singleValue = singleValue / float.Parse(node.Value.ToString()); break; } } if (System.Math.Abs(singleValue) < 1) { elem.Value = singleValue.ToString("0.########################"); // Prevent scientific notation } else { elem.Value = singleValue.ToString( "#################################.########################"); // Prevent scientific notation } } else if ((name == "ACameraID") || (name == "BCameraID") || (name == "ALinkObjID") || (name == "BLinkObjID")) { var targetIDAttr = new XElement("SetObjectID", param.Data.ToString()); elem.Add(targetIDAttr); } else { elem.Value = param.Data.ToString(); // Boolean caps if (param.Data.ToString() == "True") { elem.Value = "true"; } else if (param.Data.ToString() == "False") { elem.Value = "false"; } } return(elem); } XElement GeneratePositionElement( SetObjectTransform transform, string name = "Transform", float posYModifier = 0) { // Convert Position into elements. var posElem = new XElement("Position"); // Scaling transform.Position.X = (transform.Position.X / 10); transform.Position.Y = ((transform.Position.Y / 10) + posYModifier); transform.Position.Z = (transform.Position.Z / 10); Helpers.XMLWriteVector3(posElem, transform.Position); // Add elements to new position element and return it. return(new XElement(posElem)); } XElement GenerateRotationElement( SetObjectTransform transform, string name = "Transform", float rotateXModifier = 0, float rotateYModifier = 0) { // Convert Rotation into elements. var rotElem = new XElement("Rotation"); // Rotate objects that need it if (rotateXModifier != 0 || rotateYModifier != 0) { var temp = transform.Rotation.ToEulerAngles(); // X if (rotateXModifier != 0) { if ((temp.Y == 0) && (temp.Z == 0)) { temp.X = temp.X + rotateXModifier; transform.Rotation = new Quaternion(temp); } else if ((temp.Y == 0) && (System.Math.Abs(rotateXModifier) == 90)) { temp.X = -90 + System.Math.Abs(temp.Z); temp.Y = rotateXModifier * -1; temp.Z = -90; Console.WriteLine("X rotation"); // This is necessary since conversion between // Vector 3 and Quaternion is wonky var Rotation = new Quaternion(temp); float temptemp = Rotation.Y; Rotation.Y = Rotation.W; Rotation.W = temptemp; transform.Rotation = new Quaternion(Rotation); } else { Console.WriteLine("Unsupported X rotation modification detected"); } } // Y if (rotateYModifier != 0) { temp.Y = temp.Y + rotateYModifier; if ((rotateYModifier == 180) || (rotateYModifier == -180)) { temp.X = temp.X * -1; } else if ((rotateYModifier == 90) || (rotateYModifier == -90)) { float temptemp = temp.X; temp.X = temp.Z; temp.Z = temptemp; if (rotateYModifier == 90) { temp.X = temp.X * -1; } } else { Console.WriteLine("Y Rotation currently only supports 90, 180 or -90 degrees on Y axis."); } transform.Rotation = new Quaternion(temp); } } Helpers.XMLWriteVector4(rotElem, transform.Rotation); // Add elements to new rotation element and return it. return(new XElement(rotElem)); } }
// Methods public override void Load(Stream fileStream, Dictionary <string, SetObjectType> objectTemplates) { // Header var reader = new BINAReader(fileStream); Header = reader.ReadHeader(); reader.JumpAhead(0x2C); // Skip "test" string uint objectLength = reader.ReadUInt32(); uint objectOffset = reader.ReadUInt32(); uint groupLength = reader.ReadUInt32(); uint groupOffset = reader.ReadUInt32(); // Data reader.JumpTo(objectOffset, false); for (uint i = 0; i < objectLength; ++i) { Objects.Add(ReadObject()); } // TODO: Read Groups // TODO: Read Footer // Sub-Methods SetObject ReadObject() { // Object Entry var obj = new SetObject(); uint nameOffset = reader.ReadUInt32(); uint typeOffset = reader.ReadUInt32(); reader.JumpAhead(16); obj.Transform.Position = reader.ReadVector3(); reader.JumpAhead(4); obj.Transform.Rotation = new Quaternion(reader.ReadVector4()); uint paramCount = reader.ReadUInt32(); uint paramOffset = reader.ReadUInt32(); // Object Parameters long pos = reader.BaseStream.Position; for (uint i = 0; i < paramCount; ++i) { reader.JumpTo(paramOffset + i * 0x14, false); obj.Parameters.Add(ReadParam()); } // TODO: Read Object Name // Object Type reader.JumpTo(typeOffset, false); obj.ObjectType = reader.ReadNullTerminatedString(); reader.JumpTo(pos, true); return(obj); } SetObjectParam ReadParam() { var param = new SetObjectParam(); uint type = reader.ReadUInt32(); switch (type) { case 0: param.DataType = typeof(bool); param.Data = (reader.ReadUInt32() == 1); break; case 1: param.DataType = typeof(int); param.Data = reader.ReadInt32(); break; case 2: param.DataType = typeof(float); param.Data = reader.ReadSingle(); break; case 3: uint offset = reader.ReadUInt32(); uint amount = reader.ReadUInt32(); if (amount != 1) { Console.WriteLine($"WARNING: Amount != 1. ({amount})"); } long pos = reader.BaseStream.Position; reader.JumpTo(offset, false); param.DataType = typeof(string); param.Data = reader.ReadNullTerminatedString(); reader.JumpTo(pos, true); break; case 4: param.DataType = typeof(Vector3); param.Data = reader.ReadVector3(); break; case 6: param.DataType = typeof(uint); param.Data = reader.ReadUInt32(); break; default: Console.WriteLine($"WARNING: Unknown object param type {type}!"); return(null); } return(param); } }
// Methods public override void Load(Stream fileStream, Dictionary <string, SetObjectType> objectTemplates) { // Header var reader = new BINAReader(fileStream); Header = reader.ReadHeader(); //Set Name (hardcoded to ASSUME it's four characters long) long namePosition = reader.BaseStream.Position; //Save position so we can jump back after reading name, type and parameters reader.JumpAhead(0xC); Name = reader.ReadNullTerminatedString(); reader.JumpTo(namePosition, true); reader.JumpAhead(0x2C); uint objectCount = reader.ReadUInt32(); uint objectTableOffset = reader.ReadUInt32(); uint groupCount = reader.ReadUInt32(); uint groupTableOffset = reader.ReadUInt32(); //Objects reader.JumpTo(objectTableOffset, false); for (uint i = 0; i < objectCount; i++) { var obj = new SetObject(); obj.ObjectID = i; uint objectNameOffset = reader.ReadUInt32(); uint objectTypeOffset = reader.ReadUInt32(); obj.UnknownBytes = reader.ReadBytes(16); //parameter.Unknown 16 bytes (pattern tends to be 40 00 00 00/01 (depending on whether object is activated by a group) 00 00 00 00 00 00 00 00 00 00 00 00) obj.Transform.Position = reader.ReadVector3(); obj.DrawDistance = reader.ReadSingle(); obj.Transform.Rotation = reader.ReadQuaternion(); uint parameterCount = reader.ReadUInt32(); uint parameterOffset = reader.ReadUInt32(); long position = reader.BaseStream.Position; //Save position so we can jump back after reading name, type and parameters //Object Name and Type reader.JumpTo(objectNameOffset, false); obj.ObjectName = reader.ReadNullTerminatedString(); reader.JumpTo(objectTypeOffset, false); obj.ObjectType = reader.ReadNullTerminatedString(); reader.JumpTo(parameterOffset, false); //Object Parameters for (uint c = 0; c < parameterCount; c++) { var parameter = new SetObjectParam(); uint parameterType = reader.ReadUInt32(); switch (parameterType) { case 0: //boolean parameter.DataType = typeof(bool); parameter.Data = reader.ReadUInt32() == 1; parameter.Unknown1 = reader.ReadUInt32(); parameter.Unknown2 = reader.ReadUInt32(); parameter.Unknown3 = reader.ReadUInt32(); break; case 1: //int parameter.DataType = typeof(int); parameter.Data = reader.ReadInt32(); parameter.Unknown1 = reader.ReadUInt32(); parameter.Unknown2 = reader.ReadUInt32(); parameter.Unknown3 = reader.ReadUInt32(); break; case 2: //single parameter.DataType = typeof(float); parameter.Data = reader.ReadSingle(); parameter.Unknown1 = reader.ReadUInt32(); parameter.Unknown2 = reader.ReadUInt32(); parameter.Unknown3 = reader.ReadUInt32(); break; case 3: //string uint offset = reader.ReadUInt32(); parameter.Unknown1 = reader.ReadUInt32(); parameter.Unknown2 = reader.ReadUInt32(); parameter.Unknown3 = reader.ReadUInt32(); long stringParameterPosition = reader.BaseStream.Position; //Save position so we can jump back after reading name, type and parameters reader.JumpTo(offset, false); parameter.DataType = typeof(string); parameter.Data = reader.ReadNullTerminatedString(); reader.JumpTo(stringParameterPosition, true); break; case 4: //Vector3 parameter.DataType = typeof(Vector3); parameter.Data = reader.ReadVector3(); parameter.Unknown3 = reader.ReadUInt32(); break; case 6: //uint parameter.DataType = typeof(uint); parameter.Data = reader.ReadUInt32(); parameter.Unknown1 = reader.ReadUInt32(); parameter.Unknown2 = reader.ReadUInt32(); parameter.Unknown3 = reader.ReadUInt32(); break; default: Console.WriteLine("Unhandled Data Type!"); break; } obj.Parameters.Add(parameter); } //Save Object and jump back for the next one Objects.Add(obj); reader.JumpTo(position, true); } //Groups reader.JumpTo(groupTableOffset, false); for (uint i = 0; i < groupCount; i++) { var group = new SetGroup(); uint groupNameOffset = reader.ReadUInt32(); uint groupTypeOffset = reader.ReadUInt32(); group.GroupObjectCount = reader.ReadUInt32(); uint groupObjectListOffset = reader.ReadUInt32(); long position = reader.BaseStream.Position; //Save position so we can jump back after reading name, type and object list //Group Name and Type reader.JumpTo(groupNameOffset, false); group.GroupName = reader.ReadNullTerminatedString(); reader.JumpTo(groupTypeOffset, false); group.GroupType = reader.ReadNullTerminatedString(); //Group Object List reader.JumpTo(groupObjectListOffset, false); for (uint c = 0; c < group.GroupObjectCount; c++) { reader.JumpAhead(4); group.ObjectIDs.Add(reader.ReadUInt32()); } //Save Group and jump back for the next one Groups.Add(group); reader.JumpTo(position, true); } }
protected SetObjectParam ReadParameter(BINAReader reader, SetObjectTypeParam param) { FixPadding(reader, param.DataType); // Special Param Types if (param is SetObjectTypeParamGroup group) { var g = new SetObjectParamGroup(group.Padding); var groupParams = g.Parameters; foreach (var p in group.Parameters) { groupParams.Add(ReadParameter(reader, p)); } reader.FixPadding(group.Padding ?? 16); return(g); } else if (param.DataType == typeof(ObjectReference[])) { long arrOffset = reader.ReadInt64(); ulong arrLength = reader.ReadUInt64(); ulong arrLength2 = reader.ReadUInt64(); long curPos = reader.BaseStream.Position; if (arrLength != arrLength2) { Console.WriteLine( "WARNING: ArrLength ({0}) != ArrLength2 ({1})", arrLength, arrLength2); } var arr = new ObjectReference[arrLength]; if (arrLength > 0 && arrOffset > 0) { reader.JumpTo(arrOffset, false); for (uint i = 0; i < arrLength; ++i) { arr[i] = new ObjectReference(reader); } reader.JumpTo(curPos); } return(new SetObjectParam(param.DataType, arr)); } else if (param.DataType == typeof(ObjectReference)) { return(new SetObjectParam(typeof(ObjectReference), new ObjectReference(reader))); } else if (param.DataType == typeof(string)) { var stringParam = new SetObjectParam(typeof(string), string.Empty); long offset = reader.ReadInt64(); long stringPadding = reader.ReadInt64(); if (offset > 0) { long curPos = reader.BaseStream.Position; reader.JumpTo(offset, false); stringParam.Data = reader.ReadNullTerminatedString(); reader.JumpTo(curPos); } if (stringPadding != 0) { Console.WriteLine("WARNING: String Padding != 0 ({0:X})!!", stringPadding); } //reader.FixPadding(16); return(stringParam); } // Data var objParam = new SetObjectParam(param.DataType, reader.ReadByType(param.DataType)); // Post-Param Padding if (param.DataType == typeof(Vector3)) { uint vecPadding = reader.ReadUInt32(); if (vecPadding != 0) { Console.WriteLine("WARNING: Vector Padding != 0 ({0:X})!!", vecPadding); } } return(objParam); }
private static SetObject ReadObject(ExtendedBinaryReader reader, SetObjectType objTemplate, string objType, SOBJType type, bool rawDataMode = false) // true = full, false = only remaining bytes { // For some reason these separate values are saved as one uint rather than two ushorts. // Because of this, the values are in a different order depending on endianness, and // this is the easiest known way to read them. uint unknownValue = reader.ReadUInt32(); ushort unknown1 = (ushort)((unknownValue >> 16) & 0xFFFF); ushort objID = (ushort)(unknownValue & 0xFFFF); var obj = new SetObject() { ObjectType = objType, ObjectID = objID }; uint unknown2 = reader.ReadUInt32(); uint unknown3 = reader.ReadUInt32(); float unknown4 = reader.ReadSingle(); float rangeIn = reader.ReadSingle(); float rangeOut = reader.ReadSingle(); uint parent = (type == SOBJType.LostWorld) ? reader.ReadUInt32() : 0; uint transformsOffset = reader.ReadUInt32(); uint transformCount = reader.ReadUInt32(); uint unknown5 = reader.ReadUInt32(); uint unknown6 = (type == SOBJType.LostWorld) ? reader.ReadUInt32() : 0; uint unknown7 = (type == SOBJType.LostWorld) ? reader.ReadUInt32() : 0; // Call me crazy, but I have a weird feeling these values aren't JUST padding if (unknown3 != 0 || unknown5 != 0 || unknown6 != 0 || unknown7 != 0) { Console.WriteLine("WARNING: Not padding?! ({0},{1},{2},{3})", unknown3, unknown5, unknown6, unknown7); } // Add custom data to object obj.CustomData.Add("Unknown1", new SetObjectParam(typeof(ushort), unknown1)); obj.CustomData.Add("Unknown2", new SetObjectParam(typeof(uint), unknown2)); obj.CustomData.Add("Unknown3", new SetObjectParam(typeof(uint), unknown3)); obj.CustomData.Add("Unknown4", new SetObjectParam(typeof(float), unknown4)); obj.CustomData.Add("RangeIn", new SetObjectParam(typeof(float), rangeIn)); obj.CustomData.Add("RangeOut", new SetObjectParam(typeof(float), rangeOut)); if (type == SOBJType.LostWorld) { obj.CustomData.Add("Parent", new SetObjectParam(typeof(uint), parent)); } // Skip loading parameters if template doesn't exist if (objTemplate != null) { // Get Raw Byte Length var rawDataLenExtra = objTemplate.GetExtra("RawByteLength"); long paramBegin = reader.BaseStream.Position; int rawLength = 0; if (rawDataLenExtra != null && !string.IsNullOrEmpty(rawDataLenExtra.Value)) { int.TryParse(rawDataLenExtra.Value, out rawLength); } // Read all the data then return to beginning if (rawDataMode == true && rawLength != 0) { obj.CustomData.Add("RawParamData", new SetObjectParam(typeof(byte[]), reader.ReadBytes(rawLength))); reader.JumpTo(paramBegin); } // Parameters foreach (var param in objTemplate.Parameters) { // For compatibility with SonicGlvl templates. if (param.Name == "Unknown1" || param.Name == "Unknown2" || param.Name == "Unknown3" || param.Name == "RangeIn" || param.Name == "RangeOut" || param.Name == "Parent") { continue; } // Read Special Types/Fix Padding if (param.DataType == typeof(uint[])) { // Data Info reader.FixPadding(4); uint arrOffset = reader.ReadUInt32(); uint arrLength = reader.ReadUInt32(); uint arrUnknown = reader.ReadUInt32(); long curPos = reader.BaseStream.Position; // Data var arr = new uint[arrLength]; reader.JumpTo(arrOffset, false); for (uint i = 0; i < arrLength; ++i) { arr[i] = reader.ReadUInt32(); } obj.Parameters.Add(new SetObjectParam(param.DataType, arr)); reader.BaseStream.Position = curPos; continue; } else if (param.DataType == typeof(string)) { // Data Info uint strOffset = reader.ReadUInt32(); uint strUnknown = reader.ReadUInt32(); string str = null; // Data if (strOffset != 0) { long curPos = reader.BaseStream.Position; reader.JumpTo(strOffset, false); str = reader.ReadNullTerminatedString(); reader.BaseStream.Position = curPos; } obj.Parameters.Add(new SetObjectParam(param.DataType, str)); continue; } else if (param.DataType == typeof(float) || param.DataType == typeof(int) || param.DataType == typeof(uint)) { reader.FixPadding(4); } else if (type == SOBJType.LostWorld && param.DataType == typeof(Vector3)) { reader.FixPadding(16); } // Read Data var objParam = new SetObjectParam(param.DataType, reader.ReadByType(param.DataType)); obj.Parameters.Add(objParam); } if (rawDataMode == false) { long knownParamLength = (reader.BaseStream.Position - paramBegin); long remainingBytes = (rawLength - knownParamLength); obj.CustomData.Add("RawParamData", new SetObjectParam(typeof(byte[]), reader.ReadBytes((int)remainingBytes))); } } // Transforms uint childCount = transformCount - 1; obj.Children = new SetObjectTransform[childCount]; reader.JumpTo(transformsOffset, false); obj.Transform = ReadTransform(reader, type == SOBJType.LostWorld); for (uint i = 0; i < childCount; ++i) { obj.Children[i] = ReadTransform(reader, type == SOBJType.LostWorld); } return(obj); }