/// <summary> /// Compresses the provided stream into the other stream, if the map was previously decompressed. /// If the provided stream was not previously decompressed, it will simply copy and return false /// </summary> /// <returns>A boolean indicating if compression was performed</returns> public static bool Compress(Stream decompressed, Stream compressTo) { if (decompressed.CanSeek == false) { throw new NotSupportedException("Must be able to seek on decompressed"); } if (compressTo.CanSeek == false) { throw new NotSupportedException("Must be able to seek on compressTo"); } var fourCC = decompressed.ReadUInt32At(0); if (fourCC == RealFourCC) { decompressed.Position = 0; decompressed.CopyTo(compressTo); return(false); } if (fourCC != DecompressedFourCC) { throw new Exception("Provided decompressed map stream was not valid for compression"); } decompressed.Position = 0; compressTo.Position = 0; var header = new Span <byte>(new byte[BlamSerializer.SizeOf <H2mccMapHeader>()]); // Copy header decompressed.Read(header); compressTo.WriteUInt32(RealFourCC); compressTo.Write(header.Slice(4)); // Write empty compression info until we're done var compressionSections = new Span <byte>(new byte[8192]); compressTo.Write(compressionSections); var sections = new List <H2mccCompressionSections.CompressionSection>(); var chunk = new Span <byte>(new byte[CompressionChunkSize]); // Compress chunks, write to compressTo while (decompressed.Position < decompressed.Length - 1) { var bytesToTake = Math.Min(CompressionChunkSize, decompressed.Length - decompressed.Position); var readBytes = decompressed.Read(chunk); Debug.Assert(readBytes == bytesToTake); using var compressed = new MemoryStream(); using (var compressor = new DeflateStream(compressed, CompressionLevel.Optimal, true)) { compressor.Write(chunk.ToArray(), 0, readBytes); } compressed.Seek(0, SeekOrigin.Begin); var section = new H2mccCompressionSections.CompressionSection((int)compressed.Length + 2, (uint)compressTo.Position); sections.Add(section); // Write magic bytes compressTo.Write(BitConverter.GetBytes((ushort)5416)); compressed.CopyTo(compressTo); } // Go back and write compression section info compressTo.Seek(BlamSerializer.SizeOf <H2mccMapHeader>(), SeekOrigin.Begin); foreach (var section in sections) { compressTo.WriteInt32(section.Count); compressTo.WriteUInt32(section.Offset); } return(true); }
private ResolvedTagPropertyInfo ResolvePropertyInfo(TagIndexEntry tagInfo, string propertyPath) { var topTagType = TagFactory.GetTypeForTag(tagInfo.Tag); var steps = PropertyAccessorParser.ExtractProperties(propertyPath); var offset = 0; var stepType = topTagType; foreach (var step in steps) { var prop = stepType.GetProperty(step.PropertyName); if (prop == null) { throw new Exception($"Couldn't find property '{step.PropertyName}' on type '{stepType}'"); } if (step.AccessType == PropertyAccessorParser.PropertyAccessType.Normal) { offset += BlamSerializer.StartsAt(stepType, step.PropertyName); stepType = prop.PropertyType; } else if (step.AccessType == PropertyAccessorParser.PropertyAccessType.ElementAccess) { if (prop.PropertyType.IsArray == false || step.ElementArgument is not int) { throw new NotSupportedException("Only arrays are currently supported for element access"); } var elementSize = BlamSerializer.SizeOf(prop.PropertyType.GetElementType()); var elementOffset = (elementSize * ((int)step.ElementArgument)); if (prop.GetCustomAttribute <ReferenceArrayAttribute>() != null) { var startsAt = BlamSerializer.StartsAt(stepType, step.PropertyName); // Read element array base offset var baseOffset = new SecondaryOffset(this.originalMap, this.mapToPatch.ReadInt32At(tagInfo.Offset.Value + offset + startsAt + 4)); // baseOffset is the absolute offset, need to subtract tag offset and prior property offsets to get relative offset += baseOffset.Value - tagInfo.Offset.Value - offset + elementOffset; } else if (prop.GetCustomAttribute <PrimitiveArrayAttribute>() != null) { offset += elementOffset; } else { throw new Exception("Only primitive and reference arrays are supported"); } stepType = prop.PropertyType.GetElementType(); } } return(new ResolvedTagPropertyInfo() { RelativeOffset = offset, PropertyType = stepType }); }
public static void PatchMap(H2mccMap scene, Stream map, string patchFilePath) { var scenarioStart = scene.TagIndex[scene.IndexHeader.Scenario].Offset.Value; var nodesStart = BlamSerializer.StartsAt <ScenarioTag>(s => s.ScriptSyntaxNodes); var nodeCount = map.ReadUInt32At(scenarioStart + nodesStart); var nodeOffset = (int)map.ReadUInt32At(scenarioStart + nodesStart + 4) - scene.SecondaryMagic; var nodeSize = BlamSerializer.SizeOf <ScenarioTag.ScriptSyntaxNode>(); var patchLines = File.ReadAllLines(patchFilePath); foreach (var line in patchLines) { if (string.IsNullOrWhiteSpace(line)) { continue; } if (ShouldPatchFrom(scene, line, out var patch)) { Console.WriteLine($"\t Patching {scene.Header.Name} [{patch.Index}]"); var patchStart = nodeOffset + patch.Index * nodeSize; // Fixup next node's check value. We never change check values, so we can // re-use the 'old' nodes here to get that info if (patch.NodeData.NextIndex == ushort.MaxValue) { patch.NodeData.NextCheckval = ushort.MaxValue; } else { var nextNode = scene.Scenario.ScriptSyntaxNodes[patch.NodeData.NextIndex]; patch.NodeData.NextCheckval = nextNode.Checkval; } // Fixup next node's check value for scope/invocation nodes if ((patch.NodeData.NodeType == NodeType.BuiltinInvocation || patch.NodeData.NodeType == NodeType.ScriptInvocation)) { if (patch.NodeData.NodeData_H16 == ushort.MaxValue) { patch.NodeData.NodeData_32 = patch.NodeData.NodeData_H16 | ((uint)ushort.MaxValue) << 16; } else { var nextNode = scene.Scenario.ScriptSyntaxNodes[patch.NodeData.NodeData_H16]; patch.NodeData.NodeData_32 = patch.NodeData.NodeData_H16 | ((uint)nextNode.Checkval) << 16; } } //map.WriteUInt16At(patchStart + 0, patch.NodeData.Checkval); map.WriteUInt16At(patchStart + 2, patch.NodeData.OperationId); map.WriteUInt16At(patchStart + 4, (ushort)patch.NodeData.DataType); map.WriteUInt16At(patchStart + 6, (ushort)patch.NodeData.NodeType); map.WriteUInt16At(patchStart + 8, patch.NodeData.NextIndex); map.WriteUInt16At(patchStart + 10, patch.NodeData.NextCheckval); map.WriteUInt16At(patchStart + 12, patch.NodeData.NodeString); //map.WriteUInt16At(patchStart + 14, patch.NodeData.ValueH); map.WriteUInt32At(patchStart + 16, patch.NodeData.NodeData_32); } } }