/// <summary> /// Reads a controller from a stream. If the controller is not implemented, the raw data is returned as byte array. /// </summary> /// <param name="stream"></param> /// <param name="controllerName">The name of the controller or null if the controller must be auto-detected (not optimal, some controllers cannot be detected).</param> /// <returns></returns> public object Read(Stream stream, string controllerName) { long currentPos = stream.Position; int availableData = (int)(stream.Length - stream.Position); var reader = new BinaryReader(stream, FileEncoding.Default); // In case of a normal controller (10/-1) subtype the next Int32 contains the size of the following structure. In case of animation controllers (sub type 0/1/2/4/5/6), see below. int controllerSize = reader.ReadInt32(); // The size read from the stream matches the available data (incl. the 4 bytes of the Int32 already read), then we have a normal controller. Otherwise, we have to do some more logic to determine correct type of controller. // PROBLEM: In the odd unlucky case the size of a controller may match exactly, and it is then assumed to be a behavior controller. bool isBaseController = controllerSize + 4 != availableData; var profile = ControllerProfile.Unknown; // Base or animation controller? (byte stream) if (isBaseController) { // First 4 bytes means something else. ushort animationControllerSubType = unchecked ((ushort)(controllerSize & 0xFFFF)); ushort unknown = unchecked ((ushort)((controllerSize & 0xFFFF0000) >> 16)); if (string.IsNullOrEmpty(controllerName)) { // If no name provided, check if sub type matches that of an animation controller. if (Enum.IsDefined(typeof(AnimationType), animationControllerSubType)) { controllerName = ((AnimationType)animationControllerSubType).ToString(); } } // Only continue reading if we have a controller name. if (!string.IsNullOrEmpty(controllerName)) { profile = _controllerAssembly.GetControllerProfilesByName(controllerName).FirstOrDefault(); if (profile != ControllerProfile.Unknown && _controllerAssembly.TryGetControllerType(controllerName, profile, out Type controllerType)) { // Check if the controller is indeed a base/animation controller. If it isn't, the size descriptor may have contained an invalid size for controller data. We just attempt normal deserialization then. if (!controllerType.IsBehaviorController()) { ControllerAttribute controllerAttribute = controllerType.GetCustomAttribute <ControllerAttribute>() ?? new ControllerAttribute(); // Check that the detected controller matches subtype. if (controllerAttribute.SubType.HasValue && controllerAttribute.SubType.Value == animationControllerSubType) { // For animation controllers, the 2 bytes are part of the data and identify an 'ushort' count field for n number of frames. if (controllerType.IsAnimationController()) { stream.Position -= 2; } } else // The subtype/count is part of the controller data, move back. { stream.Position -= 4; } } } // exits } // exits } else // Behavior controller, including names and size specifiers. { // Check if the stream contains a controller name. string readControllerName = PeekName(reader); // Substitute name of controller with the one we read from stream, if it does not match. if (controllerName == null || controllerName != readControllerName) { Debug.WriteLine("Controller name mismatch between '{0}' and '{1}', using '{1}'.", controllerName ?? "<unspecified>", readControllerName); controllerName = readControllerName; } profile = _controllerAssembly.GetControllerProfilesByName(controllerName).FirstOrDefault(); } // If we have found a profile to use, try to read the controller. if (profile != ControllerProfile.Unknown && !string.IsNullOrEmpty(controllerName)) { long controllerStartPosition = stream.Position; // Attempt to parse controller, starting with newest implementation. Type previousControllerType = null; Type controllerType = null; while (true) { try { if (_controllerAssembly.TryGetControllerType(controllerName, profile, out controllerType) && previousControllerType != controllerType) { Controller controller = _controllerFactory.CreateController(controllerType, false); IControllerSerializer cs = _controllerSerializerResolver.GetSerializer(controllerType); cs.Deserialize(stream, controller); return(controller); } previousControllerType = controllerType; } catch (Exception ex) { // Reset stream to beginning. stream.Position = controllerStartPosition; Debug.WriteLine(ex.Message); previousControllerType = controllerType; } // If we reach the oldest version of SH, we are finished. if (profile >= ControllerProfile.SH3) { break; } // Move to next profile. profile++; } } // If not implemented, return the raw bytes. stream.Position = currentPos; return(reader.ReadBytes((int)(stream.Length - currentPos))); }
/// <summary> /// Writes a controller to a stream. /// </summary> /// <param name="stream"></param> /// <param name="controller"></param> public void Write(Stream stream, object controller) { if (controller == null) { throw new ArgumentNullException(nameof(controller)); } // For fallback support, allow byte arrays/streams to be passed. // We simply write it as is. if (controller is byte[] byteBuffer) { stream.Write(byteBuffer, 0, byteBuffer.Length); return; } if (controller is Stream inputStream) { inputStream.CopyTo(stream); return; } using (var writer = new BinaryWriter(stream, FileEncoding.Default, true)) { Type controllerType = controller.GetType(); IControllerSerializer cs = _controllerSerializerResolver.GetSerializer(controllerType); if (!controllerType.IsBehaviorController()) { ControllerAttribute controllerAttribute = controllerType.GetCustomAttribute <ControllerAttribute>() ?? new ControllerAttribute(); if (controllerAttribute.SubType.HasValue) { writer.Write(controllerAttribute.SubType.Value); } if (controllerType.IsAnimationController() || !controllerAttribute.SubType.HasValue) { // Skip writing the count field, the serializer will take care of it. } else { writer.Write((ushort)0); } cs.Serialize(stream, (Controller)controller); return; } // Test if the type is from our controller assembly. if (!_controllerFactory.CanCreate(controllerType)) { throw new NotSupportedException("Invalid controller."); } // We don't know the size yet, so just write 0 for now. writer.Write(0); long startPos = stream.Position; cs.Serialize(stream, (Controller)controller); // After the controller is written, determine and write the size. long currentPos = stream.Position; stream.Position = startPos - 4; writer.Write((int)(currentPos - startPos)); // Restore position to the end of the controller. stream.Position = currentPos; } }