// Methods public override void Load(Stream fileStream, Dictionary <string, SetObjectType> objectTemplates) { if (objectTemplates == null) { throw new ArgumentNullException("objectTemplates", "Cannot load Heroes set data without object templates."); } // We use lists so we don't have to skip null entries later var objs = new List <SetObject>(); var miscEntryIDs = new List <ushort>(); // Object Entries var reader = new ExtendedBinaryReader(fileStream, Encoding.ASCII, true); for (uint i = 0; i < HeroesObjectLimit; ++i) { var pos = reader.ReadVector3(); float rotX = (float)(reader.ReadInt32() * 180.0 / 32768.0); float rotY = (float)(reader.ReadInt32() * 180.0 / 32768.0); float rotZ = (float)(reader.ReadInt32() * 180.0 / 32768.0); ushort unknown1 = reader.ReadUInt16(); byte stageType = reader.ReadByte(); byte unknown2 = reader.ReadByte(); uint unknown3 = reader.ReadUInt32(); ulong unknown4 = reader.ReadUInt64(); // Repeat of the last 8 bytes? byte objList = reader.ReadByte(); byte objType = reader.ReadByte(); byte linkID = reader.ReadByte(); byte renderDistance = reader.ReadByte(); ushort unknown5 = reader.ReadUInt16(); if (unknown5 != 0) { Console.WriteLine("WARNING: Unknown5 != 0! ({0})", unknown5); } ushort miscEntryID = reader.ReadUInt16(); // Check if what we just read is a blank entry if (objList == 0 && objType == 0) { continue; } // Make sure we have a template for the object as well string objectType = string.Format("{0:x2}-{1:x2}", objList, objType); if (!objectTemplates.ContainsKey(objectType)) { Console.WriteLine("Skipping obj type {0}...", objectType); continue; } // Otherwise, generate a new object and add it to the list. miscEntryIDs.Add(miscEntryID); objs.Add(new SetObject() { ObjectID = i, ObjectType = objectType, CustomData = { { "StageType", new SetObjectParam(typeof(byte), stageType) }, { "LinkID", new SetObjectParam(typeof(byte), linkID) }, { "RenderDistance", new SetObjectParam(typeof(byte), renderDistance) }, }, Transform = new SetObjectTransform() { Position = pos, // TODO: Make sure rotation is correct Rotation = new Quaternion(new Vector3(rotX, rotY, rotZ), false) } }); } // Object Parameters for (int i = 0; i < objs.Count; ++i) { var obj = objs[i]; // Get the Set Object Type and jump to the correct misc entry var setObjType = objectTemplates[obj.ObjectType]; reader.BaseStream.Position = 4 + // We skip the first 4 bytes like the game does MiscEntriesStartPos + (miscEntryIDs[i] * MiscEntryLength); // Read the parameters according to the template foreach (var param in setObjType.Parameters) { obj.Parameters.Add(new SetObjectParam(param.DataType, reader.ReadByType(param.DataType))); } } Objects.AddRange(objs); }
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); }