public static XElement WriteObject(SetObject obj, SetObjectType type = null) { // Parameters var elem = new XElement(obj.ObjectType); for (int i = 0; i < obj.Parameters.Count; ++i) { elem.Add(new XElement((type == null) ? $"Parameter{i + 1}" : type.Parameters[i].Name, obj.Parameters[i].Data)); } // MultiSetTransforms if (obj.Children.Length > 0) { var multiSetParam = new XElement("MultiSetParam"); for (int i = 0; i < obj.Children.Length;) { var multiSetElem = new XElement("Element"); multiSetElem.AddElem("Index", ++i); WriteTransform(obj.Children[i], multiSetElem); multiSetParam.Add(multiSetElem); } elem.AddElem("BaseLine", 0); elem.AddElem("Count", obj.Children.Length + 1); // TODO: Is this right? elem.AddElem("Direction", 0); elem.AddElem("Interval", 1.5f); elem.AddElem("IntervalBase", 0); elem.AddElem("PositionBase", 0); elem.AddElem("RotationBase", 0); elem.Add(multiSetParam); } // Transform WriteTransform(obj.Transform, elem); // Special Parameters elem.AddElem("Range", obj.GetCustomDataValue <float>("Range", 100)); elem.AddElem("SetObjectID", obj.ObjectID); foreach (var customData in obj.CustomData) { if (customData.Key == "Range") { continue; } elem.Add(new XElement( customData.Key, customData.Value)); } return(elem); }
protected bool WriteExtraParamData( BINAWriter writer, SetObject obj, KeyValuePair <string, SetObjectParam> customData) { var param = customData.Value; switch (customData.Key) { case "RangeIn": writer.Write((float)param.Data); writer.Write(obj.GetCustomDataValue <float>("RangeOut")); return(true); } return(false); }
// 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); } }
// 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); } }
public void ImportXML(Stream fileStream) { // Load XML and add loaded data to set data var xml = XDocument.Load(fileStream); uint objID = 0; // For Object elements with no ID attribute. foreach (var objElem in xml.Root.Elements("Object")) { // Generate Object var typeAttr = objElem.Attribute("type"); var objIDAttr = objElem.Attribute("id"); if (typeAttr == null) { continue; } var obj = new SetObject() { ObjectType = typeAttr.Value, ObjectID = (objIDAttr == null) ? objID : Convert.ToUInt32(objIDAttr.Value), }; // Assign CustomData to Object var customDataElem = objElem.Element("CustomData"); if (customDataElem != null) { foreach (var customData in customDataElem.Elements()) { obj.CustomData.Add(customData.Name.LocalName, LoadParam(customData)); } } // Assign Parameters to Object var parametersElem = objElem.Element("Parameters"); if (parametersElem != null) { foreach (var paramElem in parametersElem.Elements()) { obj.Parameters.Add(LoadParam(paramElem)); } } // Assign Transforms to Object var transformsElem = objElem.Element("Transforms"); if (transformsElem != null) { var transforms = transformsElem.Elements("Transform"); int transformCount = transforms.Count(); if (transformCount > 0) { uint i = 0; obj.Children = new SetObjectTransform[transformCount - 1]; foreach (var transformElem in transforms) { var transform = LoadTransform(transformElem); if (i > 0) { obj.Children[i - 1] = transform; } else { obj.Transform = transform; } ++i; } } } ++objID; Objects.Add(obj); } // Sub-Methods SetObjectParam LoadParam(XElement paramElem) { // Groups var dataTypeAttr = paramElem.Attribute("type"); if (dataTypeAttr == null) { var padAttr = paramElem.Attribute("padding"); uint?padding = null; if (uint.TryParse(padAttr?.Value, out var pad)) { padding = pad; } var group = new SetObjectParamGroup(padding); var parameters = group.Parameters; foreach (var param in paramElem.Elements()) { parameters.Add(LoadParam(param)); } return(group); } // Parameters var dataType = Types.GetTypeFromString(dataTypeAttr.Value); object data = null; if (dataType == typeof(Vector2)) { data = Helpers.XMLReadVector2(paramElem); } else if (dataType == typeof(Vector3)) { data = Helpers.XMLReadVector3(paramElem); } else if (dataType == typeof(Vector4)) { data = Helpers.XMLReadVector4(paramElem); } else if (dataType == typeof(Quaternion)) { data = Helpers.XMLReadQuat(paramElem); } else if (dataType == typeof(uint[])) { var countAttr = paramElem.Attribute("count"); uint arrLength = 0; if (countAttr != null) { uint.TryParse(countAttr.Value, out arrLength); } var values = paramElem.Value.Split(','); var arr = new uint[arrLength]; for (uint i = 0; i < arrLength; ++i) { if (i >= values.Length) { break; } uint.TryParse(values[i], out arr[i]); } data = arr; } else if (dataType == typeof(ForcesSetData.ObjectReference[])) { var countAttr = paramElem.Attribute("count"); uint arrLength = 0; if (countAttr != null) { uint.TryParse(countAttr.Value, out arrLength); } uint i = 0; var arr = new ForcesSetData.ObjectReference[arrLength]; foreach (var refElem in paramElem.Elements("ForcesObjectReference")) { var objRef = new ForcesSetData.ObjectReference(); objRef.ImportXML(refElem); arr[i] = objRef; ++i; } data = arr; } else if (dataType == typeof(ForcesSetData.ObjectReference)) { var objRef = new ForcesSetData.ObjectReference(); objRef.ImportXML(paramElem); data = objRef; } else { data = Convert.ChangeType(paramElem.Value, dataType); } return(new SetObjectParam(dataType, data)); } SetObjectTransform LoadTransform(XElement elem) { var posElem = elem.Element("Position"); var rotElem = elem.Element("Rotation"); var scaleElem = elem.Element("Scale"); return(new SetObjectTransform() { Position = Helpers.XMLReadVector3(posElem), Rotation = Helpers.XMLReadQuat(rotElem), Scale = Helpers.XMLReadVector3(scaleElem) }); } }
// 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); } }
public static SetObject ReadObject(XElement element, string elemName = null, Dictionary <string, SetObjectType> objectTemplates = null) { // Parameters var parameters = new List <SetObjectParam>(); var transform = new SetObjectTransform(); SetObjectTransform[] children = null; uint? objID = null; float?range = null; if (elemName == null) { elemName = element.Name.LocalName; } foreach (var paramElement in element.Elements()) { // Special parameters string paramName = paramElement.Name.LocalName; switch (paramName.ToLower()) { case "position": transform.Position = paramElement.GetVector3(); continue; case "range": range = float.Parse(paramElement.Value); continue; case "rotation": transform.Rotation = paramElement.GetQuaternion(); continue; case "setobjectid": objID = uint.Parse(paramElement.Value); continue; case "multisetparam": { var countElem = paramElement.Element("Count"); if (countElem == null) { continue; } if (!int.TryParse(countElem.Value, out var childCount)) { continue; } var childObjs = new List <SetObjectTransform>(); foreach (var specialElem in paramElement.Elements()) { switch (specialElem.Name.LocalName.ToLower()) { case "element": { var indexElem = specialElem.Element("Index"); var posElem = specialElem.Element("Position"); var rotElem = specialElem.Element("Rotation"); if (indexElem == null || !int.TryParse( indexElem.Value, out var index)) { continue; } childObjs.Add(new SetObjectTransform() { Position = specialElem.GetVector3Elem("Position"), Rotation = specialElem.GetQuatElem("Rotation") }); break; } // TODO: Parse other elements. } } children = childObjs.ToArray(); continue; } } // Type bool doAutoDetect = (objectTemplates == null || !objectTemplates.ContainsKey(elemName)); var templateParam = (!doAutoDetect) ? objectTemplates[elemName].GetParameter(paramName) : null; var paramType = (doAutoDetect || templateParam == null) ? AutoDetectParamType(paramElement) : templateParam.DataType; if (paramType == null) { continue; } // Data object data = (paramType == typeof(Vector3)) ? paramElement.GetVector3() : (paramType == typeof(Quaternion)) ? paramElement.GetQuaternion() : Helpers.ChangeType(paramElement.Value, paramType); // Add the Parameter to the list parameters.Add(new SetObjectParam() { Data = data, DataType = paramType }); } // Ensure Object has ID if (!objID.HasValue) { Console.WriteLine("WARNING: {0} \"{1}\" {2}", "Object of type", elemName, "is missing its object ID! Skipping this object..."); return(null); } // Generate Object var obj = new SetObject() { ObjectType = elemName, Parameters = parameters, Transform = transform, Children = children ?? new SetObjectTransform[0], ObjectID = objID.Value }; if (range.HasValue) { obj.CustomData.Add("Range", new SetObjectParam( typeof(float), range.Value)); } return(obj); }
protected SetObject ReadObject(BINAReader reader, Dictionary <string, SetObjectType> objectTemplates) { var obj = new SetObject(); ulong padding1 = reader.ReadUInt64(); long objTypeOffset = reader.ReadInt64(); long objNameOffset = reader.ReadInt64(); ushort id = reader.ReadUInt16(); ushort groupID = reader.ReadUInt16(); ushort parentID = reader.ReadUInt16(); ushort parentGroupID = reader.ReadUInt16(); obj.CustomData.Add("ParentID", new SetObjectParam( typeof(ushort), parentID)); obj.CustomData.Add("ParentGroupID", new SetObjectParam( typeof(ushort), parentGroupID)); obj.ObjectID = id; obj.CustomData.Add("GroupID", new SetObjectParam( typeof(ushort), groupID)); var pos = reader.ReadVector3(); var rot = reader.ReadVector3(); var childPosOffset = reader.ReadVector3(); var childRotOffset = reader.ReadVector3(); obj.CustomData.Add("ChildPosOffset", new SetObjectParam( typeof(Vector3), childPosOffset)); obj.CustomData.Add("ChildRotOffset", new SetObjectParam( typeof(Vector3), childRotOffset)); obj.Transform.Position = pos; obj.Transform.Rotation = new Quaternion(rot, true); long extraParamsOffset = reader.ReadInt64(); ulong unknownCount1 = reader.ReadUInt64(); ulong unknownCount2 = reader.ReadUInt64(); ulong padding3 = reader.ReadUInt64(); long objParamsOffset = reader.ReadInt64(); // Unknown Count Checks if (unknownCount1 != unknownCount2) { Console.WriteLine( "WARNING: UnknownCount1 ({0}) != UnknownCount2 ({1})", unknownCount1, unknownCount2); } if (unknownCount1 > 1) { Console.WriteLine( "WARNING: UnknownCount1 > 1 ({0})", unknownCount1); } // Extra Parameter Offsets var extraParamOffsets = new long[unknownCount1]; reader.JumpTo(extraParamsOffset, false); for (uint i = 0; i < unknownCount1; ++i) { extraParamOffsets[i] = reader.ReadInt64(); ulong padding5 = reader.ReadUInt64(); // TODO: Make sure this is correct } // Extra Parameters for (uint i = 0; i < unknownCount1; ++i) { reader.JumpTo(extraParamOffsets[i], false); ulong padding6 = reader.ReadUInt64(); long extraParamNameOffset = reader.ReadInt64(); ulong extraParamLength = reader.ReadUInt64(); long extraParamDataOffset = reader.ReadInt64(); // Extra Parameter Data reader.JumpTo(extraParamDataOffset, false); var data = reader.ReadBytes((int)extraParamLength); // Extra Parameter Name reader.JumpTo(extraParamNameOffset, false); string name = reader.ReadNullTerminatedString(); // Parse Data switch (name) { case "RangeSpawning": { obj.CustomData.Add("RangeIn", new SetObjectParam( typeof(float), BitConverter.ToSingle(data, 0))); obj.CustomData.Add("RangeOut", new SetObjectParam( typeof(float), BitConverter.ToSingle(data, 4))); continue; } } Console.WriteLine($"WARNING: Unknown extra parameter type {name}"); obj.CustomData.Add(name, new SetObjectParam( data.GetType(), data)); } // Object Name reader.JumpTo(objNameOffset, false); string objName = reader.ReadNullTerminatedString(); obj.CustomData.Add("Name", new SetObjectParam(typeof(string), objName)); // Object Type reader.JumpTo(objTypeOffset, false); string objType = reader.ReadNullTerminatedString(); obj.ObjectType = objType; if (!objectTemplates.ContainsKey(objType)) { Console.WriteLine( "WARNING: Skipped {0} because there is no template for type {1}!", objName, objType); Console.WriteLine("Params at: {0:X}", objParamsOffset + reader.Offset); return(null); } //Console.WriteLine("\"{1}\" Params at: {0:X}", // objParamsOffset + reader.Offset, objName); var template = objectTemplates[objType]; // Object Parameters reader.JumpTo(objParamsOffset, false); foreach (var param in template.Parameters) { obj.Parameters.Add(ReadParameter(reader, param)); } // Additional Padding var rawDataLenExtra = template.GetExtra("RawByteLength"); if (uint.TryParse(rawDataLenExtra?.Value, out var rawLength)) { uint paramLen = (uint)(reader.BaseStream.Position - objParamsOffset - reader.Offset); if (paramLen != rawLength) { obj.CustomData.Add("RawByteLength", new SetObjectParam( typeof(uint), rawLength)); } } // Padding Checks if (padding1 != 0) { Console.WriteLine($"WARNING: Obj Padding1 != 0 ({padding1})"); } if (padding3 != 0) { Console.WriteLine($"WARNING: Obj Padding3 != 0 ({padding3})"); } return(obj); }
protected void WriteObjectParameters(BINAWriter writer, SetObject obj, int objID) { uint arrIndex = 0, strIndex = 0; uint paramStartPos = (uint)writer.BaseStream.Position; writer.FillInOffsetLong($"objParamsOffset{objID}", false, false); // Write Normal Parameters foreach (var param in obj.Parameters) { WriteParameter(param); } // Padding uint rawLength = obj.GetCustomDataValue <uint>("RawByteLength"); uint len = (uint)(writer.BaseStream.Position - paramStartPos); if (rawLength > len) { writer.WriteNulls(rawLength - len); } // Write Arrays if (arrIndex < 1) { return; // Don't bother if there's not even any arrays } writer.FixPadding(8); arrIndex = 0; foreach (var param in obj.Parameters) { WriteArray(param); } // Sub-Methods void WriteParameter(SetObjectParam param) { FixPadding(writer, param.DataType); // Special Param Types if (param is SetObjectParamGroup group) { foreach (var p in group.Parameters) { WriteParameter(p); } writer.FixPadding(group.Padding ?? 16); return; } else if (param.DataType == typeof(ObjectReference[])) { var arr = (param.Data as ObjectReference[]); ulong arrLength = (ulong)arr.LongLength; if (arrLength < 1) { writer.WriteNulls(24); return; } writer.AddOffset($"obj{objID}ArrOffset{arrIndex}", 8); writer.Write(arrLength); writer.Write(arrLength); ++arrIndex; return; } else if (param.DataType == typeof(ObjectReference)) { var reference = (param.Data as ObjectReference); if (reference == null) { writer.Write(0U); return; } reference.Write(writer); return; } else if (param.DataType == typeof(string)) { string str = (param.Data as string); if (string.IsNullOrEmpty(str)) { writer.Write(0UL); writer.Write(0UL); return; } writer.AddString($"obj{objID}StrOffset{strIndex}", str, 8); writer.Write(0UL); ++strIndex; return; } // Data writer.WriteByType(param.DataType, param.Data); // Post-Param Padding if (param.DataType == typeof(Vector3)) { writer.Write(0U); } } void WriteArray(SetObjectParam param) { // Groups if (param is SetObjectParamGroup group) { foreach (var p in group.Parameters) { WriteArray(p); } return; } // Array Values if (param.DataType == typeof(ObjectReference[])) { var arr = (param.Data as ObjectReference[]); if (arr == null || arr.Length < 1) { return; } writer.FillInOffsetLong($"obj{objID}ArrOffset{arrIndex}", false, false); for (uint i = 0; i < arr.Length; ++i) { if (arr[i] == null) { writer.Write(0U); } else { arr[i].Write(writer); } } ++arrIndex; } } }
protected void WriteObject(BINAWriter writer, SetObject obj, int objID) { writer.Write(0UL); // Object Type writer.AddString($"objTypeOffset{objID}", obj.ObjectType, 8); // Object Name string name = ""; if (obj.CustomData.ContainsKey("Name")) { name = (obj.CustomData["Name"].Data as string); } if (string.IsNullOrEmpty(name)) { name = $"{obj.ObjectType}{objID}"; } writer.AddString($"objNameOffset{objID}", name, 8); // Object Entry writer.Write((ushort)obj.ObjectID); writer.Write((obj.CustomData.ContainsKey("GroupID")) ? (ushort)obj.CustomData["GroupID"].Data : obj.GetCustomDataValue <ushort>("Unknown1")); writer.Write(obj.GetCustomDataValue <ushort>("ParentID")); writer.Write((obj.CustomData.ContainsKey("ParentGroupID")) ? (ushort)obj.CustomData["ParentGroupID"].Data : obj.GetCustomDataValue <ushort>("ParentUnknown1")); writer.Write(obj.Transform.Position); writer.Write(obj.Transform.Rotation.ToEulerAngles(true)); writer.Write((obj.CustomData.ContainsKey("ChildPosOffset")) ? (Vector3)obj.CustomData["ChildPosOffset"].Data : obj.Transform.Position); writer.Write((obj.CustomData.ContainsKey("ChildRotOffset")) ? (Vector3)obj.CustomData["ChildRotOffset"].Data : obj.Transform.Rotation.ToEulerAngles(true)); // Extra Parameter Entries uint extraParamCounts = (uint)obj.CustomData.Count; if (obj.CustomData.ContainsKey("Name")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("RangeOut")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("GroupID") || obj.CustomData.ContainsKey("Unknown1")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("ParentID")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("ParentGroupID") || obj.CustomData.ContainsKey("ParentUnknown1")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("ChildPosOffset")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("ChildRotOffset")) { extraParamCounts -= 1; } if (obj.CustomData.ContainsKey("RawByteLength")) { extraParamCounts -= 1; } writer.AddOffset($"extraParamsOffset{objID}", 8); writer.Write((ulong)extraParamCounts); // TODO writer.Write((ulong)extraParamCounts); // TODO writer.Write(0UL); writer.AddOffset($"objParamsOffset{objID}", 8); writer.FixPadding(16); writer.FillInOffsetLong($"extraParamsOffset{objID}", false, false); writer.AddOffsetTable($"extraParamOffset{objID}", extraParamCounts, 8); writer.FixPadding(16); // TODO: Make sure this is correct int i = -1; foreach (var customData in obj.CustomData) { if (customData.Key == "Name" || customData.Key == "GroupID" || customData.Key == "Unknown1" || customData.Key == "ParentID" || customData.Key == "ParentGroupID" || customData.Key == "ParentUnknown1" || customData.Key == "RangeOut" || customData.Key == "ChildPosOffset" || customData.Key == "ChildRotOffset" || customData.Key == "RawByteLength") { continue; } writer.FillInOffsetLong( $"extraParamOffset{objID}_{++i}", false, false); writer.Write(0UL); writer.AddString($"extraParamNameOffset{objID}{i}", (customData.Key == "RangeIn") ? "RangeSpawning" : customData.Key, 8); if (!WriteExtraParamLength(writer, customData)) { writer.Write(0); Console.WriteLine( $"WARNING: CustomData {customData.Key} skipped; Unknown Type!"); } writer.AddOffset($"extraParamDataOffset{objID}{i}", 8); } // Extra Parameter Data foreach (var customData in obj.CustomData) { if (customData.Key == "Name" || customData.Key == "GroupID" || customData.Key == "Unknown1" || customData.Key == "ParentID" || customData.Key == "ParentGroupID" || customData.Key == "ParentUnknown1" || customData.Key == "RangeOut" || customData.Key == "ChildPosOffset" || customData.Key == "ChildRotOffset" || customData.Key == "RawByteLength") { continue; } writer.FillInOffsetLong( $"extraParamDataOffset{objID}{i}", false, false); if (!WriteExtraParamData(writer, obj, customData)) { writer.Write(0UL); } } }
public static SetObject ReadObject(XElement element, string elemName = null, Dictionary <string, SetObjectType> objectTemplates = null) { // Parameters var parameters = new List <SetObjectParam>(); var transform = new SetObjectTransform(); SetObjectTransform[] children = null; uint? objID = null; float?range = null; //float? drawDistance = null; //string objName = ""; if (elemName == null) { elemName = element.Name.LocalName; } foreach (var paramElement in element.Elements()) { // Special parameters string paramName = paramElement.Name.LocalName; switch (paramName.ToLower()) { case "position": transform.Position = paramElement.GetVector3(); continue; //case "range": //range = float.Parse(paramElement.Value); //continue; //case "drawdistance": // drawDistance = float.Parse(paramElement.Value); // continue; //case "objname": // objName = paramElement.Value; // continue; case "rotation": transform.Rotation = paramElement.GetQuaternion(); continue; case "setobjectid": objID = uint.Parse(paramElement.Value); continue; case "multisetparam": { var countElem = paramElement.Element("Count"); if (countElem == null) { continue; } if (!int.TryParse(countElem.Value, out var childCount)) { continue; } var childObjs = new List <SetObjectTransform>(); foreach (var specialElem in paramElement.Elements()) { switch (specialElem.Name.LocalName.ToLower()) { case "element": { var indexElem = specialElem.Element("Index"); var posElem = specialElem.Element("Position"); var rotElem = specialElem.Element("Rotation"); if (indexElem == null || !int.TryParse( indexElem.Value, out var index)) { continue; } childObjs.Add(new SetObjectTransform() { Position = specialElem.GetVector3Elem("Position"), Rotation = specialElem.GetQuatElem("Rotation") }); break; } // TODO: Parse other elements. } } children = childObjs.ToArray(); continue; } } // Type bool doAutoDetect = (objectTemplates == null || !objectTemplates.ContainsKey(elemName)); var templateParam = (!doAutoDetect) ? objectTemplates[elemName].GetParameter(paramName) : null; var paramType = (doAutoDetect || templateParam == null) ? AutoDetectParamType(paramElement) : templateParam.DataType; if (paramType == null) { continue; } // Data object data = (paramType == typeof(Vector3)) ? paramElement.GetVector3() : (paramType == typeof(Quaternion)) ? paramElement.GetQuaternion() : Helpers.ChangeType(paramElement.Value, paramType); // Add the Parameter to the list parameters.Add(new SetObjectParam() { Data = data, DataType = paramType }); } // Ensure Object has ID if (!objID.HasValue) { Console.WriteLine("WARNING: {0} \"{1}\" {2}", "Object of type", elemName, "is missing its object ID! Skipping this object..."); return(null); } // Generate Object var obj = new SetObject() { ObjectType = elemName, Parameters = parameters, Transform = transform, Children = children ?? new SetObjectTransform[0], ObjectID = objID.Value, //ObjectName = objName }; //if (range.HasValue) //{ // obj.CustomData.Add("Range", new SetObjectParam( // typeof(float), range.Value)); //} //if (drawDistance.HasValue) //{ // obj.DrawDistance = drawDistance.Value; //} //else //{ // obj.DrawDistance = 0; //} //Assign Unknown Bytes to object //var unknownBytesElem = element.Element("UnknownBytes"); //int byteNumber = 0; //byte[] bytesXML = new byte[16]; //if (unknownBytesElem != null) //{ // foreach (var bytes in unknownBytesElem.Elements()) // { // bytesXML[byteNumber] = (byte)int.Parse(bytes.Value); // byteNumber++; // } //} //else //{ // bytesXML[0] = 64; // bytesXML[1] = 0; // bytesXML[2] = 0; // bytesXML[3] = 0; // bytesXML[4] = 0; // bytesXML[5] = 0; // bytesXML[6] = 0; // bytesXML[7] = 0; // bytesXML[8] = 0; // bytesXML[9] = 0; // bytesXML[10] = 0; // bytesXML[11] = 0; // bytesXML[12] = 0; // bytesXML[13] = 0; // bytesXML[14] = 0; // bytesXML[15] = 0; //} //obj.UnknownBytes = bytesXML; return(obj); }
private static void WriteObject(BINAWriter writer, SetObject obj, SOBJType type, bool rawDataMode = false) // true = full, false = only remaining bytes) { // Get a bunch of values from the object's custom data, if present. uint unknown1 = obj.GetCustomDataValue <ushort>("Unknown1"); uint unknown2 = obj.GetCustomDataValue <uint>("Unknown2"); uint unknown3 = obj.GetCustomDataValue <uint>("Unknown3"); float unknown4 = obj.GetCustomDataValue <float>("Unknown4"); float rangeIn = obj.GetCustomDataValue <float>("RangeIn"); float rangeOut = obj.GetCustomDataValue <float>("RangeOut"); uint parent = (type == SOBJType.LostWorld) ? obj.GetCustomDataValue <uint>("Parent") : 0; var rawParamData = obj.GetCustomDataValue <byte[]>("RawParamData"); // Combine the two values back into one so we can write with correct endianness. uint unknownData = (unknown1 << 16) | (obj.ObjectID & 0xFFFF); writer.Write(unknownData); writer.Write(unknown2); writer.Write(unknown3); writer.Write(unknown4); writer.Write(rangeIn); writer.Write(rangeOut); if (type == SOBJType.LostWorld) { writer.Write(parent); } writer.FixPadding(4); writer.AddOffset($"transformsOffset_{obj.ObjectID}"); writer.Write((uint)obj.Children.Length + 1); writer.WriteNulls((type == SOBJType.LostWorld) ? 0xC : 4u); // Parameters long paramBegin = writer.BaseStream.Position; foreach (var param in obj.Parameters) { // Write Special Types/Fix Padding if (param.DataType == typeof(uint[])) { // Data Info var arr = (uint[])param.Data; writer.FixPadding(4); writer.AddOffset("arrOffset"); writer.Write((uint)arr.Length); writer.WriteNulls(4); // TODO: Figure out what this is. // Data writer.FillInOffset("arrOffset", false); foreach (uint value in arr) { writer.Write(value); } continue; } else if (param.DataType == typeof(string)) { // Data Info string str = (string)param.Data; writer.AddOffset("strOffset"); writer.WriteNulls(4); // TODO: Figure out what this is. if (string.IsNullOrEmpty(str)) { writer.FillInOffset("strOffset", 0, true); } else { writer.FillInOffset("strOffset", false); writer.WriteNullTerminatedString(str); } continue; } else if (param.DataType == typeof(float) || param.DataType == typeof(int) || param.DataType == typeof(uint)) { writer.FixPadding(4); } else if (type == SOBJType.LostWorld && param.DataType == typeof(Vector3)) { writer.FixPadding(16); } // Write Data writer.WriteByType(param.DataType, param.Data); } // Write remaining raw data from loaded ORC if (rawDataMode == false) { writer.Write(rawParamData); } else { int knownParamLength = (int)(writer.BaseStream.Position - paramBegin); writer.Write(rawParamData, knownParamLength, rawParamData.Length - knownParamLength); } writer.FixPadding(4); }
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); }
// Methods public static List <SetObject> Read(ExtendedBinaryReader reader, Dictionary <string, SetObjectType> objectTemplates, SOBJType type) { var objs = new List <SetObject>(); // SOBJ Header var sig = reader.ReadChars(4); if (!reader.IsBigEndian) { Array.Reverse(sig); } string sigString = new string(sig); if (sigString != Signature) { throw new InvalidSignatureException(Signature, sigString); } uint unknown1 = reader.ReadUInt32(); uint objTypeCount = reader.ReadUInt32(); uint objTypeOffsetsOffset = reader.ReadUInt32(); reader.JumpAhead(4); uint objOffsetsOffset = reader.ReadUInt32(); uint objCount = reader.ReadUInt32(); uint unknown2 = reader.ReadUInt32(); // Probably just padding if (unknown2 != 0) { Console.WriteLine("WARNING: Unknown2 != 0! ({0})", unknown2); } uint transformsCount = reader.ReadUInt32(); // Object Offsets var objOffsets = new uint[objCount]; reader.JumpTo(objOffsetsOffset, false); for (uint i = 0; i < objCount; ++i) { objOffsets[i] = reader.ReadUInt32(); objs.Add(new SetObject()); } // Object Types reader.JumpTo(objTypeOffsetsOffset, false); for (uint i = 0; i < objTypeCount; ++i) { // Object Type string objName = reader.GetString(); uint objOfTypeCount = reader.ReadUInt32(); uint objIndicesOffset = reader.ReadUInt32(); long curTypePos = reader.BaseStream.Position; // Objects reader.JumpTo(objIndicesOffset, false); for (uint i2 = 0; i2 < objOfTypeCount; ++i2) { ushort objIndex = reader.ReadUInt16(); long curPos = reader.BaseStream.Position; var obj = new SetObject(); // We do this check here so we can print an offset that's actually helpful if (!objectTemplates.ContainsKey(objName)) { Console.WriteLine("{0} \"{1}\" (Offset: 0x{2:X})! Skipping this object...", "WARNING: No object template exists for object type", objName, (objOffsets[objIndex] + reader.Offset)); // Object Data without a template reader.JumpTo(objOffsets[objIndex], false); obj = ReadObject(reader, null, objName, type); } else { // Object Data with a template reader.JumpTo(objOffsets[objIndex], false); obj = ReadObject(reader, objectTemplates[objName], objName, type); } objs[objIndex] = obj; reader.BaseStream.Position = curPos; } reader.BaseStream.Position = curTypePos; } return(objs); }