|
| 1 | +using System; |
| 2 | +using System.IO; |
| 3 | +using System.Buffers; |
| 4 | + |
| 5 | +using ELFSharp.ELF; |
| 6 | +using ELFSharp.ELF.Sections; |
| 7 | +using Xamarin.Tools.Zip; |
| 8 | + |
| 9 | +namespace Microsoft.Android.AppTools; |
| 10 | + |
| 11 | +static class Utils |
| 12 | +{ |
| 13 | + static readonly string[] aabZipEntries = { |
| 14 | + "base/manifest/AndroidManifest.xml", |
| 15 | + "BundleConfig.pb", |
| 16 | + }; |
| 17 | + |
| 18 | + static readonly string[] aabBaseZipEntries = { |
| 19 | + "manifest/AndroidManifest.xml", |
| 20 | + }; |
| 21 | + |
| 22 | + static readonly string[] apkZipEntries = { |
| 23 | + "AndroidManifest.xml", |
| 24 | + }; |
| 25 | + |
| 26 | + public const uint ZIP_MAGIC = 0x4034b50; |
| 27 | + public const uint ASSEMBLY_STORE_MAGIC = 0x41424158; |
| 28 | + public const uint ELF_MAGIC = 0x464c457f; |
| 29 | + |
| 30 | + public static readonly ArrayPool<byte> BytePool = ArrayPool<byte>.Shared; |
| 31 | + |
| 32 | + public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSectionOffsetAndSize (Stream stream) |
| 33 | + { |
| 34 | + stream.Seek (0, SeekOrigin.Begin); |
| 35 | + Class elfClass = ELFReader.CheckELFType (stream); |
| 36 | + if (elfClass == Class.NotELF) { |
| 37 | + return ReturnError (null, ELFPayloadError.NotELF); |
| 38 | + } |
| 39 | + |
| 40 | + if (!ELFReader.TryLoad (stream, shouldOwnStream: false, out IELF? elf)) { |
| 41 | + return ReturnError (elf, ELFPayloadError.LoadFailed); |
| 42 | + } |
| 43 | + |
| 44 | + if (elf.Type != FileType.SharedObject) { |
| 45 | + return ReturnError (elf, ELFPayloadError.NotSharedLibrary); |
| 46 | + } |
| 47 | + |
| 48 | + if (elf.Endianess != ELFSharp.Endianess.LittleEndian) { |
| 49 | + return ReturnError (elf, ELFPayloadError.NotLittleEndian); |
| 50 | + } |
| 51 | + |
| 52 | + if (!elf.TryGetSection ("payload", out ISection? payloadSection)) { |
| 53 | + return ReturnError (elf, ELFPayloadError.NoPayloadSection); |
| 54 | + } |
| 55 | + |
| 56 | + bool is64 = elf.Machine switch { |
| 57 | + Machine.ARM => false, |
| 58 | + Machine.Intel386 => false, |
| 59 | + |
| 60 | + Machine.AArch64 => true, |
| 61 | + Machine.AMD64 => true, |
| 62 | + |
| 63 | + _ => throw new NotSupportedException ($"Unsupported ELF architecture '{elf.Machine}'") |
| 64 | + }; |
| 65 | + |
| 66 | + ulong offset; |
| 67 | + ulong size; |
| 68 | + |
| 69 | + if (is64) { |
| 70 | + (offset, size) = GetOffsetAndSize64 ((Section<ulong>)payloadSection); |
| 71 | + } else { |
| 72 | + (offset, size) = GetOffsetAndSize32 ((Section<uint>)payloadSection); |
| 73 | + } |
| 74 | + |
| 75 | + elf.Dispose (); |
| 76 | + return (offset, size, ELFPayloadError.None); |
| 77 | + |
| 78 | + (ulong offset, ulong size) GetOffsetAndSize64 (Section<ulong> payload) |
| 79 | + { |
| 80 | + return (payload.Offset, payload.Size); |
| 81 | + } |
| 82 | + |
| 83 | + (ulong offset, ulong size) GetOffsetAndSize32 (Section<uint> payload) |
| 84 | + { |
| 85 | + return ((ulong)payload.Offset, (ulong)payload.Size); |
| 86 | + } |
| 87 | + |
| 88 | + (ulong offset, ulong size, ELFPayloadError error) ReturnError (IELF? elf, ELFPayloadError error) |
| 89 | + { |
| 90 | + elf?.Dispose (); |
| 91 | + |
| 92 | + return (0, 0, error); |
| 93 | + } |
| 94 | + } |
| 95 | + |
| 96 | + public static (FileFormat format, FileInfo? info) DetectFileFormat (ILogger log, string path) |
| 97 | + { |
| 98 | + if (String.IsNullOrEmpty (path)) { |
| 99 | + return (FileFormat.Unknown, null); |
| 100 | + } |
| 101 | + |
| 102 | + var info = new FileInfo (path); |
| 103 | + if (!info.Exists) { |
| 104 | + return (FileFormat.Unknown, null); |
| 105 | + } |
| 106 | + |
| 107 | + using var reader = new BinaryReader (info.OpenRead ()); |
| 108 | + |
| 109 | + // ATM, all formats we recognize have 4-byte magic at the start |
| 110 | + FileFormat format = reader.ReadUInt32 () switch { |
| 111 | + Utils.ZIP_MAGIC => FileFormat.Zip, |
| 112 | + Utils.ELF_MAGIC => FileFormat.ELF, |
| 113 | + Utils.ASSEMBLY_STORE_MAGIC => FileFormat.AssemblyStore, |
| 114 | + _ => FileFormat.Unknown |
| 115 | + }; |
| 116 | + |
| 117 | + if (format == FileFormat.Unknown || format != FileFormat.Zip) { |
| 118 | + return (format, info); |
| 119 | + } |
| 120 | + |
| 121 | + return (DetectAndroidArchive (info, format), info); |
| 122 | + } |
| 123 | + |
| 124 | + static FileFormat DetectAndroidArchive (FileInfo info, FileFormat defaultFormat) |
| 125 | + { |
| 126 | + using var zip = ZipArchive.Open (info.FullName, FileMode.Open); |
| 127 | + |
| 128 | + if (HasAllEntries (zip, aabZipEntries)) { |
| 129 | + return FileFormat.Aab; |
| 130 | + } |
| 131 | + |
| 132 | + if (HasAllEntries (zip, apkZipEntries)) { |
| 133 | + return FileFormat.Apk; |
| 134 | + } |
| 135 | + |
| 136 | + if (HasAllEntries (zip, aabBaseZipEntries)) { |
| 137 | + return FileFormat.AabBase; |
| 138 | + } |
| 139 | + |
| 140 | + return defaultFormat; |
| 141 | + } |
| 142 | + |
| 143 | + static bool HasAllEntries (ZipArchive zip, string[] entries) |
| 144 | + { |
| 145 | + foreach (string entry in entries) { |
| 146 | + if (!zip.ContainsEntry (entry, caseSensitive: true)) { |
| 147 | + return false; |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + return true; |
| 152 | + } |
| 153 | +} |
0 commit comments