public List <ModelAnimationGraph.ResourceGroup> ConvertModelAnimationGraphResourceGroups(Stream cacheStream, Dictionary <ResourceLocation, Stream> resourceStreams, List <ModelAnimationGraph.ResourceGroup> resourceGroups) { if (BlamCache.ResourceGestalt == null) { BlamCache.LoadResourceTags(); } var resourceDefinition = new List <ModelAnimationTagResource>(); foreach (var group in resourceGroups) { var resourceEntry = BlamCache.ResourceGestalt.TagResources[group.ZoneAssetHandle.Index]; group.Resource = new PageableResource { Page = new RawPage { Index = -1, }, Resource = new TagResourceGen3 { ResourceType = TagResourceTypeGen3.Animation, DefinitionData = BlamCache.ResourceGestalt.FixupInformation.Skip(resourceEntry.FixupInformationOffset).Take(resourceEntry.FixupInformationLength).ToArray(), DefinitionAddress = resourceEntry.DefinitionAddress, ResourceFixups = new List <TagResourceGen3.ResourceFixup>(), ResourceDefinitionFixups = new List <TagResourceGen3.ResourceDefinitionFixup>(), Unknown2 = 1 } }; // Convert blam fixups // get the list of members in this resourcegroup. this list contains address, various offsets, and other info about the member. if (group.Resource.Resource.DefinitionData.Length != 0) { using (var definitionStream = new MemoryStream(group.Resource.Resource.DefinitionData, true)) using (var definitionReader = new EndianReader(definitionStream, EndianFormat.BigEndian)) using (var definitionWriter = new EndianWriter(definitionStream, EndianFormat.BigEndian)) { foreach (var fixup in resourceEntry.ResourceFixups) { var newFixup = new TagResourceGen3.ResourceFixup { BlockOffset = (uint)fixup.BlockOffset, Address = new CacheResourceAddress(CacheResourceAddressType.Resource, fixup.Offset) }; definitionStream.Position = newFixup.BlockOffset; definitionWriter.Write(newFixup.Address.Value); group.Resource.Resource.ResourceFixups.Add(newFixup); } var dataContext = new DataSerializationContext(definitionReader, definitionWriter, CacheResourceAddressType.Definition); definitionStream.Position = group.Resource.Resource.DefinitionAddress.Offset + 0x4; definitionWriter.Write(0x20000000); // ODST's resource type is 4 when it's supposed to be 2 because the resource definition is in the tag and not as a raw resource definitionStream.Position = group.Resource.Resource.DefinitionAddress.Offset; resourceDefinition.Add(BlamCache.Deserializer.Deserialize <ModelAnimationTagResource>(dataContext)); } } } var diffLines = new List <string>(); var resDefIndex = -1; foreach (var group in resourceGroups) { resDefIndex++; if (resourceDefinition.Count < resDefIndex + 1) { continue; // rare cases, might break the game } // Get the resource group real size, which is probably not in the resource definition var groupSize = 0; foreach (var groupMember in resourceDefinition[resDefIndex].GroupMembers) { groupSize += groupMember.AnimationData.Size; while (groupSize % 0x10 != 0) // align to 0x10. { groupSize += 4; } } var resourceData = BlamCache.GetRawFromID(group.ZoneAssetHandle, groupSize); if (resourceData == null) { return(null); } using (var blamResourceStream = new MemoryStream(resourceData)) using (var resourceReader = new EndianReader(blamResourceStream, EndianFormat.BigEndian)) using (var dataStream = new MemoryStream(new byte[groupSize])) using (var resourceWriter = new EndianWriter(dataStream, EndianFormat.LittleEndian)) { var dataContext = new DataSerializationContext(resourceReader, resourceWriter); var memberOffset = 0; for (var memberIndex = 0; memberIndex < resourceDefinition[resDefIndex].GroupMembers.Count; memberIndex++) { var member = resourceDefinition[resDefIndex].GroupMembers[memberIndex]; ModelAnimationTagResource.GroupMember.Codec codec; ModelAnimationTagResource.GroupMember.FrameInfo frameInfo; if (member.BaseHeader != ModelAnimationTagResource.GroupMemberHeaderType.Overlay) { blamResourceStream.Position = member.AnimationData.Address.Offset; dataStream.Position = member.AnimationData.Address.Offset; codec = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Codec>(dataContext); CacheContext.Serializer.Serialize(dataContext, codec); var Format1 = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Format1>(dataContext); CacheContext.Serializer.Serialize(dataContext, Format1); // blamResourceStream.Position = (long)member.AnimationData.Address.Offset + headerSize; // edResourceStream.Position = blamResourceStream.Position; for (int i = 0; i < codec.RotationNodeCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrame>(dataContext)); } blamResourceStream.Position = member.AnimationData.Address.Offset + Format1.DataStart; dataStream.Position = blamResourceStream.Position; for (int i = 0; i < codec.PositionNodeCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } blamResourceStream.Position = member.AnimationData.Address.Offset + Format1.ScaleFramesOffset; dataStream.Position = blamResourceStream.Position; for (int i = 0; i < codec.ScaleNodeCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } // If the overlay header is alone, member.OverlayOffset = 0 blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset; dataStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset; codec = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Codec>(dataContext); CacheContext.Serializer.Serialize(dataContext, codec); // deserialize second header. or as first header if the type1/format1 header isn't used. switch (codec.AnimationCodec) { case ModelAnimationTagResource.AnimationCompressionFormats.Type3: // should merge with type1 var header = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Format1>(dataContext); CacheContext.Serializer.Serialize(dataContext, header); for (int nodeIndex = 0; nodeIndex < codec.RotationNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrame>(dataContext)); } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + header.DataStart; dataStream.Position = blamResourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.PositionNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + header.ScaleFramesOffset; dataStream.Position = blamResourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.ScaleNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } break; case ModelAnimationTagResource.AnimationCompressionFormats.Type4: case ModelAnimationTagResource.AnimationCompressionFormats.Type5: case ModelAnimationTagResource.AnimationCompressionFormats.Type6: case ModelAnimationTagResource.AnimationCompressionFormats.Type7: var overlay = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Overlay>(dataContext); CacheContext.Serializer.Serialize(dataContext, overlay); #region Description // Description by DemonicSandwich from http://remnantmods.com/forums/viewtopic.php?f=13&t=1574 (matches my previous observations) // Format 6 uses Keyframes the way there are supposed to be used. As KEY frames, with the majority of the frames being Tweens. // // This format adds two extra blocks of data to it's structure. // One block that determines how many Keyframes each Node will have, and an offset to to where it's Markers start from. // // Advantages: // This format requires far fewer Keyframes to make a complex animation. // You do not need a keyframe for each render frame. // Disadvantages: // It's a bit more complex to work with. // Since it's Keyrame Markers are only 1 byte in size, you're animation cannot be longer than 256 frames, or ~8.5 seconds for non - machine objects. > 12 bits for gen3, max 0xFFF frames // Machines are still limited to 256 frames but the frames can be stretched out. #endregion var RotationFrameCount = new List <uint>(); var PositionFrameCount = new List <uint>(); var ScaleFrameCount = new List <uint>(); for (int i = 0; i < codec.RotationNodeCount; i++) { frameInfo = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfo>(dataContext); CacheContext.Serializer.Serialize(dataContext, frameInfo); var keyframesOffset = frameInfo.FrameCount & 0x00FFF000; // unused in this conversion var keyframes = frameInfo.FrameCount & 0x00000FFF; RotationFrameCount.Add(keyframes); } for (int i = 0; i < codec.PositionNodeCount; i++) { frameInfo = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfo>(dataContext); CacheContext.Serializer.Serialize(dataContext, frameInfo); var keyframesOffset = frameInfo.FrameCount & 0x00FFF000; var keyframes = frameInfo.FrameCount & 0x00000FFF; PositionFrameCount.Add(keyframes); } for (int i = 0; i < codec.ScaleNodeCount; i++) { frameInfo = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfo>(dataContext); CacheContext.Serializer.Serialize(dataContext, frameInfo); var keyframesOffset = frameInfo.FrameCount & 0x00FFF000; var keyframes = frameInfo.FrameCount & 0x00000FFF; ScaleFrameCount.Add(keyframes); } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + overlay.RotationKeyframesOffset; dataStream.Position = blamResourceStream.Position; foreach (var framecount in RotationFrameCount) { for (int i = 0; i < framecount; i++) { if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type4) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type5) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type6) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type7) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + overlay.PositionKeyframesOffset; dataStream.Position = blamResourceStream.Position; foreach (var framecount in PositionFrameCount) { for (int i = 0; i < framecount; i++) { if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type4) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type5) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type6) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type7) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + overlay.ScaleKeyframesOffset; dataStream.Position = blamResourceStream.Position; foreach (var framecount in ScaleFrameCount) { for (int i = 0; i < framecount; i++) { if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type4) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type5) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type6) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Keyframe>(dataContext)); } else if (codec.AnimationCodec == ModelAnimationTagResource.AnimationCompressionFormats.Type7) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.KeyframeType5>(dataContext)); } } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + overlay.RotationFramesOffset; dataStream.Position = blamResourceStream.Position; foreach (var framecount in RotationFrameCount) { for (int i = 0; i < framecount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrame>(dataContext)); } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + overlay.PositionFramesOffset; dataStream.Position = blamResourceStream.Position; foreach (var framecount in PositionFrameCount) { for (int i = 0; i < framecount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + overlay.ScaleFramesOffset; dataStream.Position = blamResourceStream.Position; foreach (var framecount in ScaleFrameCount) { for (int i = 0; i < framecount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } break; case ModelAnimationTagResource.AnimationCompressionFormats.Type8: // Type 8 is basically a type 3 but with rotation frames using 4 floats, or a realQuaternion var Format8 = BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.Format8>(dataContext); CacheContext.Serializer.Serialize(dataContext, Format8); for (int nodeIndex = 0; nodeIndex < codec.RotationNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.RotationFrameFloat>(dataContext)); } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + Format8.PositionFramesOffset; dataStream.Position = blamResourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.PositionNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.PositionFrame>(dataContext)); } } blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + Format8.ScaleFramesOffset; dataStream.Position = blamResourceStream.Position; for (int nodeIndex = 0; nodeIndex < codec.ScaleNodeCount; nodeIndex++) { for (int frameIndex = 0; frameIndex < member.FrameCount; frameIndex++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } } break; default: throw new DataMisalignedException(); } #region How Footer/Flags works // Better description by DemonicSandwich from http://remnantmods.com/forums/viewtopic.php?f=13&t=1574 : Node List Block: (matches my previous observations) // Just a block of flags. Tick a flag and the respective node will be affected by animation. // The size of this block should always be a multiple of 12. It's size is determined my the meta value Node List Size [byte, offset: 61] // When set to 12, the list can handle objects with a node count up to 32 (0-31). // When set to 24, the object can have 64 nodes and so on. // The block is split into 3 groups of flags. // The first group determines what nodes are affected by rotation, the second group for position, and the third group for scale. // // If looking at it in hex, the Node ticks for each group will be in order as follows: // [7][6][5][4][3][2][1][0] - [15][14][13][12][11][10][9][8] - etc. // Each flag corresponding to a Node index. #endregion #region Footer/Flag block // There's one bitfield32 for every 32 nodes that are animated which i'll call a node flags. // There's at least 3 flags if the animation only has an overlay header, which i'll call a flag set. // There's at least 6 flags if the animation has both a base header and an overlay header, so 2 sets. // If the animated nodes count is over 32, then a new flags set is added. // 1 set per header is added, such as 32 nodes = 1 set, 64 = 2 sets, 96 = 3 sets etc , 128-256 maybe max blamResourceStream.Position = (long)member.AnimationData.Address.Offset + member.OverlayOffset + member.FlagsOffset; dataStream.Position = blamResourceStream.Position; var footerSizeBase = (byte)member.BaseHeader / 4; for (int flagsCount = 0; flagsCount < footerSizeBase; flagsCount++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } var footerSizeOverlay = (byte)member.OverlayHeader / 4; for (int flagsCount = 0; flagsCount < footerSizeOverlay; flagsCount++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } #endregion switch (member.MovementDataType) { case ModelAnimationTagResource.GroupMemberMovementDataType.None: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDyaw>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } break; case ModelAnimationTagResource.GroupMemberMovementDataType.dx_dy: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDy>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } break; case ModelAnimationTagResource.GroupMemberMovementDataType.dx_dy_dyaw: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDyaw>(dataContext)); } } break; case ModelAnimationTagResource.GroupMemberMovementDataType.dx_dy_dz_dyaw: if (member.Unknown1 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDzDyaw>(dataContext)); } } if (member.Unknown2 > 0) { for (int i = 0; i < member.FrameCount; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.FrameInfoDxDyDzDyaw>(dataContext)); } } break; default: break; } dataStream.Position = memberOffset + member.AnimationData.Size; // Before the next animation member, there's some padding that is garbage data in H3/ODST, but zeroed in HO. // In order to compare converted to original raw easily, copy the original data. while (blamResourceStream.Position % 0x10 != 0) // align to 0x10, useless padding of garbage data, it's zeroed in 1:1 HO raw, just read as 4 lame bytes { if (blamResourceStream.Position == blamResourceStream.Length) { break; } CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <ModelAnimationTagResource.GroupMember.ScaleFrame>(dataContext)); } // Align the next animation member to 0x10. memberOffset += member.AnimationData.Size; while (memberOffset % 0x10 != 0) { memberOffset += 4; } } dataStream.Position = 0; CacheContext.Serializer.Serialize(new ResourceSerializationContext(CacheContext, group.Resource), resourceDefinition[resDefIndex]); group.Resource.ChangeLocation(ResourceLocation.ResourcesB); var resource = group.Resource; if (resource == null) { throw new ArgumentNullException("resource"); } if (!dataStream.CanRead) { throw new ArgumentException("The input stream is not open for reading", "dataStream"); } var cache = CacheContext.GetResourceCache(ResourceLocation.ResourcesB); if (!resourceStreams.ContainsKey(ResourceLocation.ResourcesB)) { resourceStreams[ResourceLocation.ResourcesB] = FlagIsSet(PortingFlags.Memory) ? new MemoryStream() : (Stream)CacheContext.OpenResourceCacheReadWrite(ResourceLocation.ResourcesB); if (FlagIsSet(PortingFlags.Memory)) { using (var resourceStream = CacheContext.OpenResourceCacheRead(ResourceLocation.ResourcesB)) resourceStream.CopyTo(resourceStreams[ResourceLocation.ResourcesB]); } } var dataSize = (int)(dataStream.Length - dataStream.Position); var data = new byte[dataSize]; dataStream.Read(data, 0, dataSize); resource.Page.Index = cache.Add(resourceStreams[ResourceLocation.ResourcesB], data, out uint compressedSize); resource.Page.CompressedBlockSize = compressedSize; resource.Page.UncompressedBlockSize = (uint)dataSize; resource.DisableChecksum(); } } return(resourceGroups); }
private PageableResource ConvertStructureBspCacheFileTagResources(ScenarioStructureBsp bsp, Dictionary <ResourceLocation, Stream> resourceStreams) { // // Set up ElDorado resource reference // bsp.PathfindingResource = new PageableResource { Page = new RawPage { Index = -1 }, Resource = new TagResourceGen3 { ResourceType = TagResourceTypeGen3.Pathfinding, DefinitionData = new byte[0x30], DefinitionAddress = new CacheResourceAddress(CacheResourceAddressType.Definition, 0), ResourceFixups = new List <TagResourceGen3.ResourceFixup>(), ResourceDefinitionFixups = new List <TagResourceGen3.ResourceDefinitionFixup>(), Unknown2 = 1 } }; // // Load Blam resource data // var resourceData = BlamCache.Version > CacheVersion.Halo3Retail ? BlamCache.GetRawFromID(bsp.ZoneAssetIndex4) : null; if (resourceData == null) { if (BlamCache.Version >= CacheVersion.Halo3ODST) { return(bsp.PathfindingResource); } resourceData = new byte[0x30]; } // // Port Blam resource definition // StructureBspCacheFileTagResources resourceDefinition = null; if (BlamCache.Version >= CacheVersion.Halo3ODST) { var resourceEntry = BlamCache.ResourceGestalt.TagResources[bsp.ZoneAssetIndex4.Index]; bsp.PathfindingResource.Resource.DefinitionAddress = resourceEntry.DefinitionAddress; bsp.PathfindingResource.Resource.DefinitionData = BlamCache.ResourceGestalt.FixupInformation.Skip(resourceEntry.FixupInformationOffset).Take(resourceEntry.FixupInformationLength).ToArray(); using (var definitionStream = new MemoryStream(bsp.PathfindingResource.Resource.DefinitionData, true)) using (var definitionReader = new EndianReader(definitionStream, EndianFormat.BigEndian)) using (var definitionWriter = new EndianWriter(definitionStream, EndianFormat.BigEndian)) { foreach (var fixup in resourceEntry.ResourceFixups) { var newFixup = new TagResourceGen3.ResourceFixup { BlockOffset = (uint)fixup.BlockOffset, Address = new CacheResourceAddress( fixup.Type == 4 ? CacheResourceAddressType.Resource : CacheResourceAddressType.Definition, fixup.Offset) }; definitionStream.Position = newFixup.BlockOffset; definitionWriter.Write(newFixup.Address.Value); bsp.PathfindingResource.Resource.ResourceFixups.Add(newFixup); } var dataContext = new DataSerializationContext(definitionReader, definitionWriter, CacheResourceAddressType.Definition); definitionStream.Position = bsp.PathfindingResource.Resource.DefinitionAddress.Offset; resourceDefinition = BlamCache.Deserializer.Deserialize <StructureBspCacheFileTagResources>(dataContext); } } else { resourceDefinition = new StructureBspCacheFileTagResources() { SurfacePlanes = new TagBlock <ScenarioStructureBsp.SurfacesPlanes>(bsp.SurfacePlanes.Count, new CacheResourceAddress()), Planes = new TagBlock <ScenarioStructureBsp.Plane>(bsp.Planes.Count, new CacheResourceAddress()), EdgeToSeams = new TagBlock <ScenarioStructureBsp.EdgeToSeamMapping>(bsp.EdgeToSeams.Count, new CacheResourceAddress()), PathfindingData = new List <StructureBspCacheFileTagResources.PathfindingDatum>() // TODO: copy from bsp.PathfindingData... }; } // // Port Blam resource to ElDorado resource cache // using (var blamResourceStream = new MemoryStream(resourceData)) using (var resourceReader = new EndianReader(blamResourceStream, EndianFormat.BigEndian)) using (var dataStream = new MemoryStream()) using (var resourceWriter = new EndianWriter(dataStream, EndianFormat.LittleEndian)) { var dataContext = new DataSerializationContext(resourceReader, resourceWriter); // // Surfaces Planes // StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = resourceDefinition.SurfacePlanes.Address.Offset; } resourceDefinition.SurfacePlanes = new TagBlock <ScenarioStructureBsp.SurfacesPlanes>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.SurfacePlanes.Count : resourceDefinition.SurfacePlanes.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < resourceDefinition.SurfacePlanes.Count; i++) { var element = BlamCache.Version < CacheVersion.Halo3ODST ? bsp.SurfacePlanes[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.SurfacesPlanes>(dataContext); if (BlamCache.Version < CacheVersion.Halo3ODST) { element.PlaneIndexNew = element.PlaneIndexOld; element.PlaneCountNew = element.PlaneCountOld; } CacheContext.Serializer.Serialize(dataContext, element); } // // UnknownRaw1sts // StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = resourceDefinition.Planes.Address.Offset; } resourceDefinition.Planes = new TagBlock <ScenarioStructureBsp.Plane>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.Planes.Count : resourceDefinition.Planes.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < resourceDefinition.Planes.Count; i++) { var element = BlamCache.Version < CacheVersion.Halo3ODST ? bsp.Planes[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.Plane>(dataContext); CacheContext.Serializer.Serialize(dataContext, element); } // // UnknownRaw7ths // StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = resourceDefinition.EdgeToSeams.Address.Offset; } resourceDefinition.EdgeToSeams = new TagBlock <ScenarioStructureBsp.EdgeToSeamMapping>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.EdgeToSeams.Count : resourceDefinition.EdgeToSeams.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < resourceDefinition.EdgeToSeams.Count; i++) { var element = BlamCache.Version < CacheVersion.Halo3ODST ? bsp.EdgeToSeams[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.EdgeToSeamMapping>(dataContext); CacheContext.Serializer.Serialize(dataContext, element); } if (BlamCache.Version < CacheVersion.Halo3ODST && bsp.PathfindingData.Count != 0) { var pathfinding = new StructureBspCacheFileTagResources.PathfindingDatum() { StructureChecksum = bsp.PathfindingData[0].StructureChecksum, ObjectReferences = new List <StructureBspCacheFileTagResources.PathfindingDatum.ObjectReference>(), Seams = new List <StructureBspCacheFileTagResources.PathfindingDatum.Seam>(), JumpSeams = new List <StructureBspCacheFileTagResources.PathfindingDatum.JumpSeam>() }; foreach (var oldObjectReference in bsp.PathfindingData[0].ObjectReferences) { var objectReference = new StructureBspCacheFileTagResources.PathfindingDatum.ObjectReference { Flags = oldObjectReference.Flags, Bsps = new List <StructureBspCacheFileTagResources.PathfindingDatum.ObjectReference.BspReference>(), ObjectUniqueID = oldObjectReference.ObjectUniqueID, OriginBspIndex = oldObjectReference.OriginBspIndex, ObjectType = oldObjectReference.ObjectType.DeepClone(), Source = oldObjectReference.Source }; foreach (var bspRef in oldObjectReference.Bsps) { objectReference.Bsps.Add(new StructureBspCacheFileTagResources.PathfindingDatum.ObjectReference.BspReference { BspIndex = bspRef.BspIndex, NodeIndex = bspRef.NodeIndex, Bsp2dRefs = new TagBlock <ScenarioStructureBsp.PathfindingDatum.ObjectReference.BspReference.Bsp2dRef>(bspRef.Bsp2dRefs.Count, new CacheResourceAddress()), VertexOffset = bspRef.VertexOffset }); } pathfinding.ObjectReferences.Add(objectReference); } foreach (var oldSeam in bsp.PathfindingData[0].Seams) { pathfinding.Seams.Add(new StructureBspCacheFileTagResources.PathfindingDatum.Seam { LinkIndices = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Seam.LinkIndexBlock>( oldSeam.LinkIndices.Count, new CacheResourceAddress()) }); } foreach (var oldJumpSeam in bsp.PathfindingData[0].JumpSeams) { pathfinding.JumpSeams.Add(new StructureBspCacheFileTagResources.PathfindingDatum.JumpSeam { UserJumpIndex = oldJumpSeam.UserJumpIndex, DestOnly = oldJumpSeam.DestOnly, Length = oldJumpSeam.Length, JumpIndices = new TagBlock <ScenarioStructureBsp.PathfindingDatum.JumpSeam.JumpIndexBlock>( oldJumpSeam.JumpIndices.Count, new CacheResourceAddress()) }); } resourceDefinition.PathfindingData.Add(pathfinding); } foreach (var pathfindingDatum in resourceDefinition.PathfindingData) { StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.Sectors.Address.Offset; } pathfindingDatum.Sectors = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Sector>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Sectors.Count : pathfindingDatum.Sectors.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.Sectors.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Sectors[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Sector>(dataContext)); } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.Links.Address.Offset; } pathfindingDatum.Links = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Link>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Links.Count : pathfindingDatum.Links.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.Links.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Links[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Link>(dataContext)); } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.References.Address.Offset; } pathfindingDatum.References = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Reference>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].References.Count : pathfindingDatum.References.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.References.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].References[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Reference>(dataContext)); } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.Bsp2dNodes.Address.Offset; } pathfindingDatum.Bsp2dNodes = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Bsp2dNode>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Bsp2dNodes.Count : pathfindingDatum.Bsp2dNodes.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.Bsp2dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Bsp2dNodes[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Bsp2dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.Vertices.Address.Offset; } pathfindingDatum.Vertices = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Vertex>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Vertices.Count : pathfindingDatum.Vertices.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.Vertices.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Vertices[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Vertex>(dataContext)); } for (var objRefIdx = 0; objRefIdx < pathfindingDatum.ObjectReferences.Count; objRefIdx++) { for (var bspRefIdx = 0; bspRefIdx < pathfindingDatum.ObjectReferences[objRefIdx].Bsps.Count; bspRefIdx++) { var bspRef = pathfindingDatum.ObjectReferences[objRefIdx].Bsps[bspRefIdx]; StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = bspRef.Bsp2dRefs.Address.Offset; } bspRef.Bsp2dRefs.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var bsp2dRefIdx = 0; bsp2dRefIdx < bspRef.Bsp2dRefs.Count; bsp2dRefIdx++) { var bsp2dRef = BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].ObjectReferences[objRefIdx].Bsps[bspRefIdx].Bsp2dRefs[bsp2dRefIdx] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.ObjectReference.BspReference.Bsp2dRef>(dataContext); CacheContext.Serializer.Serialize(dataContext, bsp2dRef); } } } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.PathfindingHints.Address.Offset; } pathfindingDatum.PathfindingHints = new TagBlock <ScenarioStructureBsp.PathfindingDatum.PathfindingHint>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].PathfindingHints.Count : pathfindingDatum.PathfindingHints.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.PathfindingHints.Count; i++) { var hint = BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].PathfindingHints[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.PathfindingHint>(dataContext); if (BlamCache.Version < CacheVersion.Halo3ODST && (hint.HintType == JumpLink || hint.HintType == WallJumpLink)) { hint.Data[3] = (hint.Data[3] & ~ushort.MaxValue) | ((hint.Data[2] >> 16) & ushort.MaxValue); hint.Data[2] = (hint.Data[2] & ~(ushort.MaxValue << 16)); //remove old landing sector hint.Data[2] = (hint.Data[2] | ((hint.Data[2] & (byte.MaxValue << 8)) << 8)); //move jump height flags hint.Data[2] = (hint.Data[2] & ~(byte.MaxValue << 8)); //remove old flags } CacheContext.Serializer.Serialize(dataContext, hint); } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.InstancedGeometryReferences.Address.Offset; } pathfindingDatum.InstancedGeometryReferences = new TagBlock <ScenarioStructureBsp.PathfindingDatum.InstancedGeometryReference>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].InstancedGeometryReferences.Count : pathfindingDatum.InstancedGeometryReferences.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.InstancedGeometryReferences.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].InstancedGeometryReferences[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.InstancedGeometryReference>(dataContext)); } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.GiantPathfinding.Address.Offset; } pathfindingDatum.GiantPathfinding = new TagBlock <ScenarioStructureBsp.PathfindingDatum.GiantPathfindingBlock>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].GiantPathfinding.Count : pathfindingDatum.GiantPathfinding.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.GiantPathfinding.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].GiantPathfinding[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.GiantPathfindingBlock>(dataContext)); } for (var unk2Idx = 0; unk2Idx < pathfindingDatum.Seams.Count; unk2Idx++) { var unknown2 = pathfindingDatum.Seams[unk2Idx]; StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = unknown2.LinkIndices.Address.Offset; } unknown2.LinkIndices = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Seam.LinkIndexBlock>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Seams[unk2Idx].LinkIndices.Count : unknown2.LinkIndices.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var unkIdx = 0; unkIdx < unknown2.LinkIndices.Count; unkIdx++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Seams[unk2Idx].LinkIndices[unkIdx] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Seam.LinkIndexBlock>(dataContext)); } } for (var unk3Idx = 0; unk3Idx < pathfindingDatum.JumpSeams.Count; unk3Idx++) { var unknown3 = pathfindingDatum.JumpSeams[unk3Idx]; StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = unknown3.JumpIndices.Address.Offset; } unknown3.JumpIndices = new TagBlock <ScenarioStructureBsp.PathfindingDatum.JumpSeam.JumpIndexBlock>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].JumpSeams[unk3Idx].JumpIndices.Count : unknown3.JumpIndices.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var unk4Idx = 0; unk4Idx < unknown3.JumpIndices.Count; unk4Idx++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].JumpSeams[unk3Idx].JumpIndices[unk4Idx] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.JumpSeam.JumpIndexBlock>(dataContext)); } } StreamUtil.Align(dataStream, 0x4); if (BlamCache.Version >= CacheVersion.Halo3ODST) { blamResourceStream.Position = pathfindingDatum.Doors.Address.Offset; } pathfindingDatum.Doors = new TagBlock <ScenarioStructureBsp.PathfindingDatum.Door>( (BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Doors.Count : pathfindingDatum.Doors.Count), new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position)); for (var i = 0; i < pathfindingDatum.Doors.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Version < CacheVersion.Halo3ODST ? bsp.PathfindingData[0].Doors[i] : BlamCache.Deserializer.Deserialize <ScenarioStructureBsp.PathfindingDatum.Door>(dataContext)); } } CacheContext.Serializer.Serialize(new ResourceSerializationContext(CacheContext, bsp.PathfindingResource), resourceDefinition); resourceWriter.BaseStream.Position = 0; dataStream.Position = 0; bsp.PathfindingResource.ChangeLocation(ResourceLocation.ResourcesB); var resource = bsp.PathfindingResource; if (resource == null) { throw new ArgumentNullException("resource"); } if (!dataStream.CanRead) { throw new ArgumentException("The input stream is not open for reading", "dataStream"); } var cache = CacheContext.GetResourceCache(ResourceLocation.ResourcesB); if (!resourceStreams.ContainsKey(ResourceLocation.ResourcesB)) { resourceStreams[ResourceLocation.ResourcesB] = FlagIsSet(PortingFlags.Memory) ? new MemoryStream() : (Stream)CacheContext.OpenResourceCacheReadWrite(ResourceLocation.ResourcesB); if (FlagIsSet(PortingFlags.Memory)) { using (var resourceStream = CacheContext.OpenResourceCacheRead(ResourceLocation.ResourcesB)) resourceStream.CopyTo(resourceStreams[ResourceLocation.ResourcesB]); } } var dataSize = (int)(dataStream.Length - dataStream.Position); var data = new byte[dataSize]; dataStream.Read(data, 0, dataSize); resource.Page.Index = cache.Add(resourceStreams[ResourceLocation.ResourcesB], data, out uint compressedSize); resource.Page.CompressedBlockSize = compressedSize; resource.Page.UncompressedBlockSize = (uint)dataSize; resource.DisableChecksum(); } if (BlamCache.Version < CacheVersion.Halo3ODST) { bsp.SurfacePlanes.Clear(); bsp.Planes.Clear(); bsp.EdgeToSeams.Clear(); bsp.PathfindingData.Clear(); } return(bsp.PathfindingResource); }
private PageableResource ConvertStructureBspTagResources(ScenarioStructureBsp bsp, Dictionary <ResourceLocation, Stream> resourceStreams) { // // Set up ElDorado resource reference // bsp.CollisionBspResource = new PageableResource { Page = new RawPage { Index = -1 }, Resource = new TagResourceGen3 { ResourceType = TagResourceTypeGen3.Collision, DefinitionData = new byte[0x30], DefinitionAddress = new CacheResourceAddress(CacheResourceAddressType.Definition, 0), ResourceFixups = new List <TagResourceGen3.ResourceFixup>(), ResourceDefinitionFixups = new List <TagResourceGen3.ResourceDefinitionFixup>(), Unknown2 = 1 } }; // // Port Blam resource definition // var resourceEntry = BlamCache.ResourceGestalt.TagResources[bsp.ZoneAssetIndex3.Index]; bsp.CollisionBspResource.Resource.DefinitionAddress = resourceEntry.DefinitionAddress; bsp.CollisionBspResource.Resource.DefinitionData = BlamCache.ResourceGestalt.FixupInformation.Skip(resourceEntry.FixupInformationOffset).Take(resourceEntry.FixupInformationLength).ToArray(); StructureBspTagResources resourceDefinition = null; using (var definitionStream = new MemoryStream(bsp.CollisionBspResource.Resource.DefinitionData, true)) using (var definitionReader = new EndianReader(definitionStream, EndianFormat.BigEndian)) using (var definitionWriter = new EndianWriter(definitionStream, EndianFormat.BigEndian)) { foreach (var fixup in resourceEntry.ResourceFixups) { var newFixup = new TagResourceGen3.ResourceFixup { BlockOffset = (uint)fixup.BlockOffset, Address = new CacheResourceAddress( fixup.Type == 4 ? CacheResourceAddressType.Resource : CacheResourceAddressType.Definition, fixup.Offset) }; definitionStream.Position = newFixup.BlockOffset; definitionWriter.Write(newFixup.Address.Value); bsp.CollisionBspResource.Resource.ResourceFixups.Add(newFixup); } var dataContext = new DataSerializationContext(definitionReader, definitionWriter, CacheResourceAddressType.Definition); definitionStream.Position = bsp.CollisionBspResource.Resource.DefinitionAddress.Offset; resourceDefinition = BlamCache.Deserializer.Deserialize <StructureBspTagResources>(dataContext); // // Apply game-specific fixes to the resource definition // if (BlamCache.Version < CacheVersion.Halo3ODST) { resourceDefinition.LargeCollisionBsps = new List <StructureBspTagResources.LargeCollisionBspBlock>(); resourceDefinition.HavokData = new List <StructureBspTagResources.HavokDatum>(); } foreach (var instance in resourceDefinition.InstancedGeometry) { instance.Unknown5 = new TagBlock <StructureBspTagResources.InstancedGeometryBlock.Unknown4Block>(); instance.Unknown2 = new TagBlock <StructureBspTagResources.InstancedGeometryBlock.Unknown2Block>(); } } // // Load Blam resource data // var resourceData = BlamCache.GetRawFromID(bsp.ZoneAssetIndex3); if (resourceData == null) { CacheContext.Serializer.Serialize(new ResourceSerializationContext(CacheContext, bsp.CollisionBspResource), resourceDefinition); return(bsp.CollisionBspResource); } // // Port Blam resource to ElDorado resource cache // using (var blamResourceStream = resourceData != null ? new MemoryStream(resourceData) : new MemoryStream()) using (var resourceReader = new EndianReader(blamResourceStream, EndianFormat.BigEndian)) using (var dataStream = new MemoryStream()) using (var resourceWriter = new EndianWriter(dataStream, EndianFormat.LittleEndian)) { var dataContext = new DataSerializationContext(resourceReader, resourceWriter); foreach (var collisionBsp in resourceDefinition.CollisionBsps) { StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionBsp.Bsp3dNodes.Address.Offset; collisionBsp.Bsp3dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Bsp3dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp3dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionBsp.Planes.Address.Offset; collisionBsp.Planes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Planes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Plane>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionBsp.Leaves.Address.Offset; collisionBsp.Leaves.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Leaves.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Leaf>(dataContext)); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = collisionBsp.Bsp2dReferences.Address.Offset; collisionBsp.Bsp2dReferences.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Bsp2dReferences.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp2dReference>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionBsp.Bsp2dNodes.Address.Offset; collisionBsp.Bsp2dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Bsp2dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp2dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionBsp.Surfaces.Address.Offset; collisionBsp.Surfaces.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Surfaces.Count; i++) { var surface = BlamCache.Deserializer.Deserialize <CollisionGeometry.Surface>(dataContext); CacheContext.Serializer.Serialize(dataContext, surface); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = collisionBsp.Edges.Address.Offset; collisionBsp.Edges.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Edges.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Edge>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionBsp.Vertices.Address.Offset; collisionBsp.Vertices.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionBsp.Vertices.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Vertex>(dataContext)); } } foreach (var largeCollisionBsp in resourceDefinition.LargeCollisionBsps) { StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Bsp3dNodes.Address.Offset; largeCollisionBsp.Bsp3dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Bsp3dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.LargeCollisionBspBlock.Bsp3dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Planes.Address.Offset; largeCollisionBsp.Planes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Planes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Plane>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Leaves.Address.Offset; largeCollisionBsp.Leaves.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Leaves.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Leaf>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Bsp2dReferences.Address.Offset; largeCollisionBsp.Bsp2dReferences.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Bsp2dReferences.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.LargeCollisionBspBlock.Bsp2dReference>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Bsp2dNodes.Address.Offset; largeCollisionBsp.Bsp2dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Bsp2dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.LargeCollisionBspBlock.Bsp2dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Surfaces.Address.Offset; largeCollisionBsp.Surfaces.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Surfaces.Count; i++) { var surface = BlamCache.Deserializer.Deserialize <StructureBspTagResources.LargeCollisionBspBlock.Surface>(dataContext); // surface.Material = PortGlobalMaterialIndex(CacheContext, BlamCache, surface.Material); CacheContext.Serializer.Serialize(dataContext, surface); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = largeCollisionBsp.Edges.Address.Offset; largeCollisionBsp.Edges.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Edges.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.LargeCollisionBspBlock.Edge>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = largeCollisionBsp.Vertices.Address.Offset; largeCollisionBsp.Vertices.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < largeCollisionBsp.Vertices.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.LargeCollisionBspBlock.Vertex>(dataContext)); } } foreach (var instance in resourceDefinition.InstancedGeometry) { StreamUtil.Align(dataStream, 0x10); // 0x8 > 0x10 blamResourceStream.Position = instance.CollisionInfo.Bsp3dNodes.Address.Offset; instance.CollisionInfo.Bsp3dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Bsp3dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp3dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = instance.CollisionInfo.Planes.Address.Offset; instance.CollisionInfo.Planes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Planes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Plane>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = instance.CollisionInfo.Leaves.Address.Offset; instance.CollisionInfo.Leaves.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Leaves.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Leaf>(dataContext)); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = instance.CollisionInfo.Bsp2dReferences.Address.Offset; instance.CollisionInfo.Bsp2dReferences.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Bsp2dReferences.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp2dReference>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = instance.CollisionInfo.Bsp2dNodes.Address.Offset; instance.CollisionInfo.Bsp2dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Bsp2dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp2dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = instance.CollisionInfo.Surfaces.Address.Offset; instance.CollisionInfo.Surfaces.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Surfaces.Count; i++) { var surface = BlamCache.Deserializer.Deserialize <CollisionGeometry.Surface>(dataContext); // surface.Material = PortGlobalMaterialIndex(CacheContext, BlamCache, surface.Material); CacheContext.Serializer.Serialize(dataContext, surface); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = instance.CollisionInfo.Edges.Address.Offset; instance.CollisionInfo.Edges.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Edges.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Edge>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = instance.CollisionInfo.Vertices.Address.Offset; instance.CollisionInfo.Vertices.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.CollisionInfo.Vertices.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Vertex>(dataContext)); } foreach (var collisionGeometry in instance.CollisionGeometries) { StreamUtil.Align(dataStream, 0x10); // 0x8 > 0x10 blamResourceStream.Position = collisionGeometry.Bsp3dNodes.Address.Offset; collisionGeometry.Bsp3dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Bsp3dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp3dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionGeometry.Planes.Address.Offset; collisionGeometry.Planes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Planes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Plane>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionGeometry.Leaves.Address.Offset; collisionGeometry.Leaves.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Leaves.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Leaf>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionGeometry.Bsp2dReferences.Address.Offset; collisionGeometry.Bsp2dReferences.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Bsp2dReferences.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp2dReference>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionGeometry.Bsp2dNodes.Address.Offset; collisionGeometry.Bsp2dNodes.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Bsp2dNodes.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Bsp2dNode>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionGeometry.Surfaces.Address.Offset; collisionGeometry.Surfaces.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Surfaces.Count; i++) { var surface = BlamCache.Deserializer.Deserialize <CollisionGeometry.Surface>(dataContext); // surface.Material = PortGlobalMaterialIndex(CacheContext, BlamCache, surface.Material); CacheContext.Serializer.Serialize(dataContext, surface); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = collisionGeometry.Edges.Address.Offset; collisionGeometry.Edges.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Edges.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Edge>(dataContext)); } StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = collisionGeometry.Vertices.Address.Offset; collisionGeometry.Vertices.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < collisionGeometry.Vertices.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <CollisionGeometry.Vertex>(dataContext)); } } foreach (var moppCode in instance.BspPhysics) { StreamUtil.Align(dataStream, 0x10); blamResourceStream.Position = moppCode.Data.Address.Offset; moppCode.Data.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); var moppData = resourceReader.ReadBytes(moppCode.Data.Count).Select(i => new CollisionMoppCode.Datum { Value = i }).ToList(); if (BlamCache.Version < CacheVersion.Halo3ODST) { moppData = ConvertCollisionMoppData(moppData); } resourceWriter.Write(moppData.Select(i => i.Value).ToArray()); } StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = instance.Unknown1.Address.Offset; instance.Unknown1.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.Unknown1.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.InstancedGeometryBlock.Unknown1Block>(dataContext)); } /* * StreamUtil.Align(dataStream, 0x4); // 0x4 > 0x10 * blamResourceStream.Position = instance.Unknown2.Address.Offset; * instance.Unknown2.Address = new CacheAddress(CacheAddressType.Resource, (int)dataStream.Position); * for (var i = 0; i < instance.Unknown2.Count; i++) * { * var element = BlamCache.Deserializer.Deserialize<StructureBspTagResources.InstancedGeometryBlock.Unknown2Block>(dataContext); * if (BlamCache.Version <= CacheVersion.Halo3ODST) * { * element.Unknown1 = element.Unknown1_H3; * element.Unknown2 = element.Unknown2_H3; * } * CacheContext.Serializer.Serialize(dataContext, element); */ StreamUtil.Align(dataStream, 0x10); // 0x4 > 0x10 blamResourceStream.Position = instance.Unknown3.Address.Offset; instance.Unknown3.Address = new CacheResourceAddress(CacheResourceAddressType.Resource, (int)dataStream.Position); for (var i = 0; i < instance.Unknown3.Count; i++) { CacheContext.Serializer.Serialize(dataContext, BlamCache.Deserializer.Deserialize <StructureBspTagResources.InstancedGeometryBlock.Unknown3Block>(dataContext)); } } dataStream.Position = 0; CacheContext.Serializer.Serialize(new ResourceSerializationContext(CacheContext, bsp.CollisionBspResource), resourceDefinition); bsp.CollisionBspResource.ChangeLocation(ResourceLocation.ResourcesB); var resource = bsp.CollisionBspResource; if (resource == null) { throw new ArgumentNullException("resource"); } if (!dataStream.CanRead) { throw new ArgumentException("The input stream is not open for reading", "dataStream"); } var cache = CacheContext.GetResourceCache(ResourceLocation.ResourcesB); if (!resourceStreams.ContainsKey(ResourceLocation.ResourcesB)) { resourceStreams[ResourceLocation.ResourcesB] = FlagIsSet(PortingFlags.Memory) ? new MemoryStream() : (Stream)CacheContext.OpenResourceCacheReadWrite(ResourceLocation.ResourcesB); if (FlagIsSet(PortingFlags.Memory)) { using (var resourceStream = CacheContext.OpenResourceCacheRead(ResourceLocation.ResourcesB)) resourceStream.CopyTo(resourceStreams[ResourceLocation.ResourcesB]); } } var dataSize = (int)(dataStream.Length - dataStream.Position); var data = new byte[dataSize]; dataStream.Read(data, 0, dataSize); resource.Page.Index = cache.Add(resourceStreams[ResourceLocation.ResourcesB], data, out uint compressedSize); resource.Page.CompressedBlockSize = compressedSize; resource.Page.UncompressedBlockSize = (uint)dataSize; resource.DisableChecksum(); } return(bsp.CollisionBspResource); }