// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Text; namespace dnlib.DotNet.Resources { /// /// Writes .NET resources /// public sealed class ResourceWriter { ModuleDef module; BinaryWriter writer; ResourceElementSet resources; ResourceDataFactory typeCreator; Dictionary dataToNewType = new Dictionary(); ResourceWriter(ModuleDef module, ResourceDataFactory typeCreator, Stream stream, ResourceElementSet resources) { this.module = module; this.typeCreator = typeCreator; writer = new BinaryWriter(stream); this.resources = resources; } /// /// Write .NET resources /// /// Owner module /// Output stream /// .NET resources public static void Write(ModuleDef module, Stream stream, ResourceElementSet resources) => new ResourceWriter(module, new ResourceDataFactory(module), stream, resources).Write(); /// /// Write .NET resources /// /// Owner module /// User type factory /// Output stream /// .NET resources public static void Write(ModuleDef module, ResourceDataFactory typeCreator, Stream stream, ResourceElementSet resources) => new ResourceWriter(module, typeCreator, stream, resources).Write(); void Write() { if (resources.FormatVersion != 1 && resources.FormatVersion != 2) throw new ArgumentException($"Invalid format version: {resources.FormatVersion}", nameof(resources)); InitializeUserTypes(resources.FormatVersion); writer.Write(0xBEEFCACE); writer.Write(1); WriteReaderType(); writer.Write(resources.FormatVersion); writer.Write(resources.Count); writer.Write(typeCreator.Count); foreach (var userType in typeCreator.GetSortedTypes()) writer.Write(userType.Name); int extraBytes = 8 - ((int)writer.BaseStream.Position & 7); if (extraBytes != 8) { for (int i = 0; i < extraBytes; i++) writer.Write((byte)'X'); } var nameOffsetStream = new MemoryStream(); var nameOffsetWriter = new BinaryWriter(nameOffsetStream, Encoding.Unicode); var dataStream = new MemoryStream(); var dataWriter = new ResourceBinaryWriter(dataStream) { FormatVersion = resources.FormatVersion, ReaderType = resources.ReaderType, }; var hashes = new int[resources.Count]; var offsets = new int[resources.Count]; var formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.File | StreamingContextStates.Persistence)); int index = 0; foreach (var info in resources.ResourceElements) { offsets[index] = (int)nameOffsetWriter.BaseStream.Position; hashes[index] = (int)Hash(info.Name); index++; nameOffsetWriter.Write(info.Name); nameOffsetWriter.Write((int)dataWriter.BaseStream.Position); WriteData(dataWriter, info, formatter); } Array.Sort(hashes, offsets); foreach (var hash in hashes) writer.Write(hash); foreach (var offset in offsets) writer.Write(offset); writer.Write((int)writer.BaseStream.Position + (int)nameOffsetStream.Length + 4); writer.Write(nameOffsetStream.ToArray()); writer.Write(dataStream.ToArray()); } void WriteData(ResourceBinaryWriter writer, ResourceElement info, IFormatter formatter) { var code = GetResourceType(info.ResourceData, writer.FormatVersion); writer.Write7BitEncodedInt((int)code); info.ResourceData.WriteData(writer, formatter); } ResourceTypeCode GetResourceType(IResourceData data, int formatVersion) { if (formatVersion == 1) { if (data.Code == ResourceTypeCode.Null) return (ResourceTypeCode)(-1); return (ResourceTypeCode)(dataToNewType[data].Code - ResourceTypeCode.UserTypes); } if (data is BuiltInResourceData) return data.Code; return dataToNewType[data].Code; } static uint Hash(string key) { uint val = 0x1505; foreach (var c in key) val = ((val << 5) + val) ^ (uint)c; return val; } void InitializeUserTypes(int formatVersion) { foreach (var resource in resources.ResourceElements) { UserResourceType newType; if (formatVersion == 1 && resource.ResourceData is BuiltInResourceData builtinData) { newType = typeCreator.CreateBuiltinResourceType(builtinData.Code); if (newType is null) throw new NotSupportedException($"Unsupported resource type: {builtinData.Code} in format version 1 resource"); } else if (resource.ResourceData is UserResourceData userData) newType = typeCreator.CreateUserResourceType(userData.TypeName); else continue; dataToNewType[resource.ResourceData] = newType; } } void WriteReaderType() { var memStream = new MemoryStream(); var headerWriter = new BinaryWriter(memStream); if (resources.ResourceReaderTypeName is not null && resources.ResourceSetTypeName is not null) { headerWriter.Write(resources.ResourceReaderTypeName); headerWriter.Write(resources.ResourceSetTypeName); } else { var mscorlibFullName = GetMscorlibFullname(); headerWriter.Write($"System.Resources.ResourceReader, {mscorlibFullName}"); headerWriter.Write("System.Resources.RuntimeResourceSet"); } writer.Write((int)memStream.Position); writer.Write(memStream.ToArray()); } string GetMscorlibFullname() { if (module.CorLibTypes.AssemblyRef.Name == "mscorlib") return module.CorLibTypes.AssemblyRef.FullName; return "mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"; } } }