You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
4.8 KiB

  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. using System;
  4. using System.Collections.Immutable;
  5. using System.IO;
  6. using System.IO.MemoryMappedFiles;
  7. using System.Runtime.CompilerServices;
  8. using System.Text;
  9. namespace ICSharpCode.Decompiler
  10. {
  11. /// <summary>
  12. /// Class for dealing with .NET 5 single-file bundles.
  13. ///
  14. /// Based on code from Microsoft.NET.HostModel.
  15. /// </summary>
  16. public static class SingleFileBundle
  17. {
  18. /// <summary>
  19. /// Check if the memory-mapped data is a single-file bundle
  20. /// </summary>
  21. public static unsafe bool IsBundle(MemoryMappedViewAccessor view, out long bundleHeaderOffset)
  22. {
  23. var buffer = view.SafeMemoryMappedViewHandle;
  24. byte* ptr = null;
  25. buffer.AcquirePointer(ref ptr);
  26. try
  27. {
  28. return IsBundle(ptr, checked((long)buffer.ByteLength), out bundleHeaderOffset);
  29. }
  30. finally
  31. {
  32. buffer.ReleasePointer();
  33. }
  34. }
  35. public static unsafe bool IsBundle(byte* data, long size, out long bundleHeaderOffset)
  36. {
  37. ReadOnlySpan<byte> bundleSignature = new byte[] {
  38. // 32 bytes represent the bundle signature: SHA-256 for ".net core bundle"
  39. 0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
  40. 0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
  41. 0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
  42. 0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae
  43. };
  44. byte* end = data + (size - bundleSignature.Length);
  45. for (byte* ptr = data; ptr < end; ptr++)
  46. {
  47. if (*ptr == 0x8b && bundleSignature.SequenceEqual(new ReadOnlySpan<byte>(ptr, bundleSignature.Length)))
  48. {
  49. bundleHeaderOffset = Unsafe.ReadUnaligned<long>(ptr - sizeof(long));
  50. if (bundleHeaderOffset > 0 && bundleHeaderOffset < size)
  51. {
  52. return true;
  53. }
  54. }
  55. }
  56. bundleHeaderOffset = 0;
  57. return false;
  58. }
  59. public struct Header
  60. {
  61. public uint MajorVersion;
  62. public uint MinorVersion;
  63. public int FileCount;
  64. public string BundleID;
  65. // Fields introduced with v2:
  66. public long DepsJsonOffset;
  67. public long DepsJsonSize;
  68. public long RuntimeConfigJsonOffset;
  69. public long RuntimeConfigJsonSize;
  70. public ulong Flags;
  71. public ImmutableArray<Entry> Entries;
  72. }
  73. /// <summary>
  74. /// FileType: Identifies the type of file embedded into the bundle.
  75. ///
  76. /// The bundler differentiates a few kinds of files via the manifest,
  77. /// with respect to the way in which they'll be used by the runtime.
  78. /// </summary>
  79. public enum FileType : byte
  80. {
  81. Unknown, // Type not determined.
  82. Assembly, // IL and R2R Assemblies
  83. NativeBinary, // NativeBinaries
  84. DepsJson, // .deps.json configuration file
  85. RuntimeConfigJson, // .runtimeconfig.json configuration file
  86. Symbols // PDB Files
  87. };
  88. public struct Entry
  89. {
  90. public long Offset;
  91. public long Size;
  92. public FileType Type;
  93. public string RelativePath; // Path of an embedded file, relative to the Bundle source-directory.
  94. }
  95. static UnmanagedMemoryStream AsStream(MemoryMappedViewAccessor view)
  96. {
  97. long size = checked((long)view.SafeMemoryMappedViewHandle.ByteLength);
  98. return new UnmanagedMemoryStream(view.SafeMemoryMappedViewHandle, 0, size);
  99. }
  100. /// <summary>
  101. /// Reads the manifest header from the memory mapping.
  102. /// </summary>
  103. public static Header ReadManifest(MemoryMappedViewAccessor view, long bundleHeaderOffset)
  104. {
  105. using var stream = AsStream(view);
  106. stream.Seek(bundleHeaderOffset, SeekOrigin.Begin);
  107. return ReadManifest(stream);
  108. }
  109. /// <summary>
  110. /// Reads the manifest header from the stream.
  111. /// </summary>
  112. public static Header ReadManifest(Stream stream)
  113. {
  114. var header = new Header();
  115. using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
  116. header.MajorVersion = reader.ReadUInt32();
  117. header.MinorVersion = reader.ReadUInt32();
  118. if (header.MajorVersion < 1 || header.MajorVersion > 2)
  119. {
  120. throw new InvalidDataException($"Unsupported manifest version: {header.MajorVersion}.{header.MinorVersion}");
  121. }
  122. header.FileCount = reader.ReadInt32();
  123. header.BundleID = reader.ReadString();
  124. if (header.MajorVersion >= 2)
  125. {
  126. header.DepsJsonOffset = reader.ReadInt64();
  127. header.DepsJsonSize = reader.ReadInt64();
  128. header.RuntimeConfigJsonOffset = reader.ReadInt64();
  129. header.RuntimeConfigJsonSize = reader.ReadInt64();
  130. header.Flags = reader.ReadUInt64();
  131. }
  132. var entries = ImmutableArray.CreateBuilder<Entry>(header.FileCount);
  133. for (int i = 0; i < header.FileCount; i++)
  134. {
  135. entries.Add(ReadEntry(reader));
  136. }
  137. header.Entries = entries.MoveToImmutable();
  138. return header;
  139. }
  140. private static Entry ReadEntry(BinaryReader reader)
  141. {
  142. Entry entry;
  143. entry.Offset = reader.ReadInt64();
  144. entry.Size = reader.ReadInt64();
  145. entry.Type = (FileType)reader.ReadByte();
  146. entry.RelativePath = reader.ReadString();
  147. return entry;
  148. }
  149. }
  150. }