From 29d0e4fc2493e8eec8e6680f2b706a95dc72ad8a Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Tue, 14 Jul 2026 22:28:27 +0200 Subject: [PATCH 1/3] [tools] Replace APK utilities with skill file apps Move assembly-store inspection and managed assembly extraction into skill-owned .NET file-based apps. Remove the redundant executable projects and launch wrappers while keeping the shared compatibility parser and exercising both apps in CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 55493571-86e2-4326-850e-ea55883aa5d8 --- .../extract-android-assemblies/SKILL.md | 35 ++ .../scripts/extract-android-assemblies.cs | 366 ++++++++++++++++++ .github/skills/read-assembly-store/SKILL.md | 71 +--- .../scripts/read-assembly-store.cs | 228 +++++++++++ Xamarin.Android.sln | 36 -- .../yaml-templates/build-windows-steps.yaml | 16 + .../AssemblyStore/AssemblyStorePayload.cs | 61 +++ .../Directory.Build.targets | 7 - tools/assembly-store-reader-mk2/Main.cs | 112 ------ .../StorePrettyPrinter.cs | 114 ------ .../assembly-store-reader.csproj | 36 -- .../Directory.Build.targets | 6 - tools/assembly-store-reader/Program.cs | 171 -------- .../assembly-store-reader.csproj | 27 -- .../decompress-assemblies.csproj | 38 -- tools/decompress-assemblies/main.cs | 198 ---------- tools/scripts/decompress-assemblies | 3 - tools/scripts/read-assembly-store | 10 - 18 files changed, 721 insertions(+), 814 deletions(-) create mode 100644 .github/skills/extract-android-assemblies/SKILL.md create mode 100644 .github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs create mode 100644 .github/skills/read-assembly-store/scripts/read-assembly-store.cs create mode 100644 tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStorePayload.cs delete mode 100644 tools/assembly-store-reader-mk2/Directory.Build.targets delete mode 100644 tools/assembly-store-reader-mk2/Main.cs delete mode 100644 tools/assembly-store-reader-mk2/StorePrettyPrinter.cs delete mode 100644 tools/assembly-store-reader-mk2/assembly-store-reader.csproj delete mode 100644 tools/assembly-store-reader/Directory.Build.targets delete mode 100644 tools/assembly-store-reader/Program.cs delete mode 100644 tools/assembly-store-reader/assembly-store-reader.csproj delete mode 100644 tools/decompress-assemblies/decompress-assemblies.csproj delete mode 100644 tools/decompress-assemblies/main.cs delete mode 100755 tools/scripts/decompress-assemblies delete mode 100755 tools/scripts/read-assembly-store diff --git a/.github/skills/extract-android-assemblies/SKILL.md b/.github/skills/extract-android-assemblies/SKILL.md new file mode 100644 index 00000000000..796111784db --- /dev/null +++ b/.github/skills/extract-android-assemblies/SKILL.md @@ -0,0 +1,35 @@ +--- +name: extract-android-assemblies +description: Locally extract managed DLL files from .NET for Android APK, AAB, assembly-store, or compressed DLL inputs, including legacy/current store layouts and LZ4/Zstd compression. Use when investigating a customer Android package, getting assemblies for ILSpy or decompilation, unpacking assembly stores, or recovering ABI-specific managed files. +--- + +# Extract Android Assemblies + +Extract customer assemblies locally with the bundled C# file-based app. Never upload the input package or extracted code. + +## Workflow + +1. Resolve the input file and choose a new output directory. Keep temporary customer artifacts outside the repository. +2. From the repository root, run: + + ```text + dotnet run --file .github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs -- --output + ``` + +3. Pass multiple input files after the output option when needed. +4. Do not reuse a non-empty output directory: the app refuses to overwrite files. +5. Report: + - detected inputs + - extracted DLL count grouped by ABI + - whether LZ4 or Zstd decompression occurred + - the local output directory + - unsupported or corrupt store details + +The app supports: + +- legacy `assemblies/assemblies.blob` store sets and manifests +- historical `libassemblies.{abi}.blob.so` stores +- current `libassembly-store.so` stores +- individual legacy and RID-specific packaged assemblies +- raw, ELF `payload`, and `_assembly_store` symbol wrappers +- uncompressed, `XALZ`/LZ4, and `XAZS`/Zstd assembly data diff --git a/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs b/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs new file mode 100644 index 00000000000..4c89abf9abf --- /dev/null +++ b/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs @@ -0,0 +1,366 @@ +#!/usr/bin/env dotnet +#:property TargetFramework=net11.0 +#:project ../../../../tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj + +using System; +using System.Collections.Generic; +using System.IO; + +using Xamarin.Android.AssemblyStore; +using Xamarin.Android.Tools; +using Xamarin.Tools.Zip; + +namespace Xamarin.Android.Tools.DecompressAssemblies +{ + class App + { + static readonly AndroidTargetArch[] targetArchitectures = [ + AndroidTargetArch.Arm64, + AndroidTargetArch.Arm, + AndroidTargetArch.X86_64, + AndroidTargetArch.X86, + ]; + + static int Usage (int exitCode = 1) + { + Console.WriteLine ("Usage: extract-android-assemblies [--output DIRECTORY] {file.{dll,apk,aab}} [{file.{dll,apk,aab} ...]"); + Console.WriteLine (); + Console.WriteLine ("Extracts managed assemblies from .NET for Android files, including legacy/current stores and LZ4/Zstd compression."); + Console.WriteLine ("Without --output, files are written under `uncompressed-{input-name}` in the current directory."); + return exitCode; + } + + static bool ExtractDLL (Stream inputStream, string fileName, string outputFile) + { + Console.WriteLine ($"Processing {fileName}"); + + using var assemblyStream = new MemoryStream (); + Stream outputSource = inputStream; + AssemblyCompressionFormat? format = null; + try { + if (AssemblyCompression.TryDecompress (inputStream, assemblyStream, out AssemblyCompressionFormat detectedFormat)) { + format = detectedFormat; + assemblyStream.Seek (0, SeekOrigin.Begin); + outputSource = assemblyStream; + } else { + inputStream.Seek (0, SeekOrigin.Begin); + } + } catch (InvalidDataException e) { + Console.Error.WriteLine ($" Failed to decompress {fileName}: {e.Message}"); + return false; + } + + string? outputDir = Path.GetDirectoryName (outputFile); + if (!String.IsNullOrEmpty (outputDir)) { + Directory.CreateDirectory (outputDir); + } + if (File.Exists (outputFile)) { + Console.Error.WriteLine ($" Refusing to overwrite existing file '{outputFile}'"); + return false; + } + + using (var output = File.Open (outputFile, FileMode.CreateNew, FileAccess.Write)) { + outputSource.CopyTo (output); + output.Flush (); + } + string compression = format.HasValue ? $"{format.Value} assembly decompressed and" : "assembly"; + Console.WriteLine ($" {compression} extracted to: {outputFile}"); + return true; + } + + static bool ExtractDLLFile (string filePath, string outputFile) + { + using (var fs = File.Open (filePath, FileMode.Open, FileAccess.Read)) { + return ExtractDLL (fs, filePath, outputFile); + } + } + + static bool ExtractIndividualEntries (ZipArchive apk, string filePath, string assembliesPath, string nativeLibrariesPath, string outputDirectory) + { + bool retVal = true; + int assemblyCount = 0; + foreach (ZipEntry entry in apk) { + if (!TryGetAssemblyOutputPath (entry.FullName, assembliesPath, nativeLibrariesPath, out string assemblyName)) { + continue; + } + + assemblyCount++; + + using (var stream = new MemoryStream ()) { + entry.Extract (stream); + stream.Seek (0, SeekOrigin.Begin); + string outputFile = GetSafeOutputFile (outputDirectory, assemblyName); + using var payload = new MemoryStream (); + Stream assemblyInput = stream; + try { + if (AssemblyStorePayload.TryExtractELFPayload (stream, payload)) { + assemblyInput = payload; + } + } catch (InvalidDataException e) { + Console.Error.WriteLine ($"Unable to extract '{filePath}!{entry.FullName}': {e.Message}"); + retVal = false; + continue; + } + retVal &= ExtractDLL (assemblyInput, $"{filePath}!{entry.FullName}", outputFile); + } + } + + if (assemblyCount == 0) { + Console.Error.WriteLine ($"No managed assemblies were found in '{filePath}'"); + return false; + } + + return retVal; + } + + static bool ExtractAssemblyStores (string filePath, string outputDirectory) + { + (IList? stores, string? errorMessage) = AssemblyStoreExplorer.Open (filePath); + if (stores == null) { + Console.Error.WriteLine (errorMessage ?? $"Unable to read assembly stores from '{filePath}'"); + return false; + } + + bool retVal = true; + int assemblyCount = 0; + foreach (AssemblyStoreExplorer store in stores) { + string? abi = store.TargetArch.HasValue ? GetAndroidAbi (store.TargetArch.Value) : null; + foreach (AssemblyStoreItem assembly in store.Assemblies ?? []) { + if (assembly.Ignore) { + continue; + } + + assemblyCount++; + string fileName = assembly.Name.EndsWith (".dll", StringComparison.OrdinalIgnoreCase) ? assembly.Name : $"{assembly.Name}.dll"; + string assemblyName = abi == null ? fileName : $"{abi}/{fileName}"; + using Stream? stream = store.ReadImageData (assembly); + if (stream == null) { + Console.Error.WriteLine ($"Unable to read '{assembly.Name}' from assembly store '{store.StorePath}'"); + retVal = false; + continue; + } + + string outputFile = GetSafeOutputFile (outputDirectory, assemblyName); + retVal &= ExtractDLL (stream, $"{filePath}!{assemblyName}", outputFile); + } + } + + if (assemblyCount == 0) { + Console.Error.WriteLine ($"No managed assemblies were found in stores from '{filePath}'"); + return false; + } + + return retVal; + } + + static string GetAndroidAbi (AndroidTargetArch arch) + { + return arch switch { + AndroidTargetArch.Arm64 => "arm64-v8a", + AndroidTargetArch.Arm => "armeabi-v7a", + AndroidTargetArch.X86_64 => "x86_64", + AndroidTargetArch.X86 => "x86", + _ => throw new NotSupportedException ($"Unsupported target architecture '{arch}'"), + }; + } + + static bool HasAssemblyStore (ZipArchive apk, string assembliesPath, string nativeLibrariesPath) + { + if (apk.ContainsEntry ($"{assembliesPath}assemblies.blob")) { + return true; + } + + foreach (AndroidTargetArch arch in targetArchitectures) { + string abi = GetAndroidAbi (arch); + if ( + apk.ContainsEntry ($"{nativeLibrariesPath}{abi}/libassembly-store.so") || + apk.ContainsEntry ($"{nativeLibrariesPath}{abi}/libassemblies.{abi}.blob.so") + ) { + return true; + } + } + + return false; + } + + static bool ExtractFromArchive (string filePath, string assembliesPath, string nativeLibrariesPath, string outputDirectory) + { + using (ZipArchive apk = ZipArchive.Open (filePath, FileMode.Open)) { + if (HasAssemblyStore (apk, assembliesPath, nativeLibrariesPath)) { + return ExtractAssemblyStores (filePath, outputDirectory); + } + + return ExtractIndividualEntries (apk, filePath, assembliesPath, nativeLibrariesPath, outputDirectory); + } + } + + static int Main (string[] args) + { + string? outputRoot = null; + var files = new List (); + for (int i = 0; i < args.Length; i++) { + if (args [i] == "--help" || args [i] == "-h") { + return Usage (0); + } else if (args [i] == "--output" || args [i] == "-o") { + if (++i >= args.Length) { + return Usage (); + } + outputRoot = args [i]; + } else { + files.Add (args [i]); + } + } + + if (files.Count == 0) { + return Usage (); + } + if (!HaveUniqueOutputs (files)) { + return 1; + } + + bool haveErrors = false; + foreach (string file in files) { + try { + string ext = Path.GetExtension (file); + if (String.Compare (".dll", ext, StringComparison.OrdinalIgnoreCase) == 0) { + string outputFile = outputRoot == null ? + Path.Combine (Directory.GetCurrentDirectory (), $"uncompressed-{Path.GetFileName (file)}") : + GetSafeOutputFile (outputRoot, Path.GetFileName (file)); + if (!ExtractDLLFile (file, outputFile)) { + haveErrors = true; + } + continue; + } + + if (!TryGetOutputName (file, out string archiveName)) { + Console.Error.WriteLine ($"Input file '{file}' does not have a safe output name"); + haveErrors = true; + continue; + } + string archiveOutput = outputRoot == null ? + Path.Combine (Directory.GetCurrentDirectory (), $"uncompressed-{archiveName}") : + Path.Combine (outputRoot, archiveName); + if ( + String.Compare (".blob", ext, StringComparison.OrdinalIgnoreCase) == 0 || + String.Compare (".manifest", ext, StringComparison.OrdinalIgnoreCase) == 0 || + String.Compare (".so", ext, StringComparison.OrdinalIgnoreCase) == 0 || + String.IsNullOrEmpty (ext) + ) { + if (!ExtractAssemblyStores (file, archiveOutput)) { + haveErrors = true; + } + continue; + } + + if (String.Compare (".apk", ext, StringComparison.OrdinalIgnoreCase) == 0) { + if (!ExtractFromArchive (file, "assemblies/", "lib/", archiveOutput)) { + haveErrors = true; + } + continue; + } + + if (String.Compare (".aab", ext, StringComparison.OrdinalIgnoreCase) == 0) { + if (!ExtractFromArchive (file, "base/root/assemblies/", "base/lib/", archiveOutput)) { + haveErrors = true; + } + continue; + } + + Console.Error.WriteLine ($"Unsupported input file '{file}'"); + haveErrors = true; + } catch (UnauthorizedAccessException e) { + Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); + haveErrors = true; + } catch (IOException e) { + Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); + haveErrors = true; + } catch (InvalidOperationException e) { + Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); + haveErrors = true; + } catch (NotSupportedException e) { + Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); + haveErrors = true; + } catch (ArgumentException e) { + Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); + haveErrors = true; + } + } + + return haveErrors ? 1 : 0; + } + + static bool TryGetAssemblyOutputPath (string entryPath, string assembliesPath, string nativeLibrariesPath, out string assemblyName) + { + if (entryPath.StartsWith (assembliesPath, StringComparison.Ordinal) && entryPath.EndsWith (".dll", StringComparison.OrdinalIgnoreCase)) { + assemblyName = entryPath.Substring (assembliesPath.Length); + return true; + } + + foreach (AndroidTargetArch arch in targetArchitectures) { + string abi = GetAndroidAbi (arch); + string prefix = $"{nativeLibrariesPath}{abi}/"; + if (!entryPath.StartsWith (prefix, StringComparison.Ordinal)) { + continue; + } + + string fileName = entryPath.Substring (prefix.Length); + if (fileName.EndsWith (".ni.dll.so", StringComparison.OrdinalIgnoreCase) || !fileName.EndsWith (".dll.so", StringComparison.OrdinalIgnoreCase)) { + continue; + } + + if (fileName.StartsWith ("lib_", StringComparison.Ordinal)) { + assemblyName = $"{abi}/{fileName.Substring ("lib_".Length, fileName.Length - "lib_".Length - ".so".Length)}"; + return true; + } + + if (fileName.StartsWith ("lib-", StringComparison.Ordinal)) { + string satelliteName = fileName.Substring ("lib-".Length, fileName.Length - "lib-".Length - ".so".Length); + assemblyName = $"{abi}/satellites/{satelliteName}"; + return true; + } + } + + assemblyName = ""; + return false; + } + + static bool HaveUniqueOutputs (List files) + { + var outputNames = new HashSet (StringComparer.OrdinalIgnoreCase); + foreach (string file in files) { + if (!TryGetOutputName (file, out string outputName)) { + Console.Error.WriteLine ($"Input file '{file}' does not have a safe output name"); + return false; + } + if (!outputNames.Add (outputName)) { + Console.Error.WriteLine ($"Multiple inputs would use the same output name '{outputName}'. Run them separately or rename one input."); + return false; + } + } + return true; + } + + static bool TryGetOutputName (string file, out string outputName) + { + outputName = Path.GetExtension (file).Equals (".dll", StringComparison.OrdinalIgnoreCase) ? + Path.GetFileName (file) : + Path.GetFileNameWithoutExtension (file); + return !String.IsNullOrEmpty (outputName) && outputName != "." && outputName != ".."; + } + + static string GetSafeOutputFile (string outputDirectory, string relativePath) + { + string root = Path.GetFullPath (outputDirectory); + string outputFile = Path.GetFullPath (Path.Combine (root, relativePath.Replace ('/', Path.DirectorySeparatorChar))); + string relativeOutput = Path.GetRelativePath (root, outputFile); + if ( + Path.IsPathRooted (relativeOutput) || + relativeOutput == ".." || + relativeOutput.StartsWith ($"..{Path.DirectorySeparatorChar}", StringComparison.Ordinal) + ) { + throw new InvalidDataException ($"Assembly path '{relativePath}' escapes output directory '{root}'"); + } + return outputFile; + } + } +} diff --git a/.github/skills/read-assembly-store/SKILL.md b/.github/skills/read-assembly-store/SKILL.md index ae32459b464..f7ffd394dd4 100644 --- a/.github/skills/read-assembly-store/SKILL.md +++ b/.github/skills/read-assembly-store/SKILL.md @@ -1,67 +1,26 @@ --- name: read-assembly-store -description: Read and inspect .NET assembly stores from Android APK, AAB, or raw store files. Use this when users want to inspect, examine, or list assemblies in an APK, AAB, assembly store, or manifest file. +description: Inspect and list managed assemblies in .NET for Android APK, AAB, legacy assembly blob, manifest, or libassembly-store.so files. Use for customer package investigation, assembly-store format identification, ABI-specific assembly listings, offsets, sizes, hashes, or determining what managed code an Android package contains. --- # Read Assembly Store -Read and inspect .NET assembly stores from Android APK, AAB, or raw store files using the `assembly-store-reader-mk2` tool in this repository. +Inspect the package locally with the bundled C# file-based app. Never upload customer packages or extracted code. -## What It Does +## Workflow -Lists all .NET managed assemblies embedded in an Android app, showing per-assembly metadata (PE image offset/size, debug data, config data, name hashes) grouped by target architecture. +1. Resolve the input file and optional ABI filter. +2. From the repository root, run: -Supports these input types: -- **APK files** (`.apk`) -- **AAB files** (`.aab`) -- **Assembly store files** (`libassembly-store.so`, `assemblies.blob`) -- **Store manifest files** (`base_assemblies.manifest`) -- **Store base names** (e.g. `base` or `base_assemblies`) + ```bash + dotnet run --file .github/skills/read-assembly-store/scripts/read-assembly-store.cs -- + ``` -## How to Run +3. To filter architectures, add `--arch=Arm64`, `--arch=Arm`, `--arch=X86_64`, or `--arch=X86`. Separate multiple values with commas. +4. Report: + - target architectures + - assembly count + - assembly names and notable ignored entries + - the local input path -From the repository root, run the tool using `dotnet-local` to ensure the correct SDK is used. - -**On Windows:** - -```powershell -.\dotnet-local.cmd run --project .\tools\assembly-store-reader-mk2\ -- [OPTIONS] -``` - -**On macOS/Linux:** - -```bash -./dotnet-local.sh run --project ./tools/assembly-store-reader-mk2/ -- [OPTIONS] -``` - -### Options - -- `-a`, `--arch=ARCHITECTURES` — Comma-separated list of architectures to limit output to (e.g. `Arm64`, `x64`, `Arm`, `X86`). Aliases like `aarch64`, `arm32`, `armv7a`, `armv8a` also work. -- `-h`, `--help` — Show help. - -## Instructions - -1. Determine the user's operating system from the environment context. -2. Ask the user which file to inspect if not already specified. -3. Run the appropriate command from the **repository root**, passing the file path and any options after `--`. -4. Report the output to the user, summarizing the assemblies found per architecture. - -## Examples - -Inspect an APK: - -```powershell -.\dotnet-local.cmd run --project .\tools\assembly-store-reader-mk2\ -- path\to\app.apk -``` - -Inspect an APK, arm64 only: - -```powershell -.\dotnet-local.cmd run --project .\tools\assembly-store-reader-mk2\ -- --arch=Arm64 path\to\app.apk -``` - -Inspect a raw store file: - -```powershell -.\dotnet-local.cmd run --project .\tools\assembly-store-reader-mk2\ -- path\to\libassembly-store.so -``` +The app supports legacy v1 store sets and manifests, v2/v3 RID-specific stores, raw blobs, ELF `payload` wrappers, and `_assembly_store` symbol wrappers. diff --git a/.github/skills/read-assembly-store/scripts/read-assembly-store.cs b/.github/skills/read-assembly-store/scripts/read-assembly-store.cs new file mode 100644 index 00000000000..1fbb520765b --- /dev/null +++ b/.github/skills/read-assembly-store/scripts/read-assembly-store.cs @@ -0,0 +1,228 @@ +#!/usr/bin/env dotnet +#:property TargetFramework=net11.0 +#:project ../../../../tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj +#:package Mono.Options@6.12.0.148 + +using System; +using System.Collections.Generic; +using System.Text; + +using Mono.Options; +using Xamarin.Android.Tools; + +namespace Xamarin.Android.AssemblyStore; + +class App +{ + static readonly AndroidTargetArch[] supportedTargetArchitectures = [ + AndroidTargetArch.Arm, + AndroidTargetArch.X86, + AndroidTargetArch.Arm64, + AndroidTargetArch.X86_64, + ]; + + static void ShowHelp () + { + } + + static int WriteErrorAndReturn (string message) + { + Console.Error.WriteLine (message); + return 1; + } + + static HashSet? ParseArchList (string values) + { + if (String.IsNullOrEmpty (values)) { + return null; + } + + var ret = new HashSet (); + foreach (string a in values.Split (',')) { + string archName = a.Trim (); + if (Enum.TryParse (archName, out AndroidTargetArch arch)) { + ret.Add (arch); + continue; + } + + arch = archName.ToLowerInvariant () switch { + "aarch64" => AndroidTargetArch.Arm64, + "arm32" => AndroidTargetArch.Arm, + "arm64" => AndroidTargetArch.Arm64, + "armv7a" => AndroidTargetArch.Arm, + "armv8a" => AndroidTargetArch.Arm64, + "x64" => AndroidTargetArch.X86_64, + _ => throw new InvalidOperationException ($"Unknown architecture name '{archName}'") + }; + ret.Add (arch); + } + + return ret; + } + + static string GetArchNames () + { + return String.Join (", ", supportedTargetArchitectures); + } + + static int Main (string[] args) + { + HashSet? arches = null; + bool showHelp = false; + + var options = new OptionSet { + "Usage: read-assembly-store [OPTIONS] BLOB_PATH", + "", + " where each BLOB_PATH can point to:", + " * aab file", + " * apk file", + " * index store file (e.g. base_assemblies.blob or libassembly-store.so)", + " * arch store file (e.g. base_assemblies.blob)", + " * store manifest file (e.g. base_assemblies.manifest)", + " * store base name (e.g. base or base_assemblies)", + "", + " In each case the whole set of stores and manifests will be read (if available). Search for the", + " various members of the store set (common/main store, arch stores, manifest) is based on this naming", + " convention:", + "", + " {BASE_NAME}[.ARCH_NAME].{blob|so|manifest}", + "", + " Whichever file is referenced in `BLOB_PATH`, the BASE_NAME component is extracted and all the found files are read.", + " If `BLOB_PATH` points to an aab or an apk, BASE_NAME will always be `assemblies`", + "", + {"a|arch=", $"Limit listing of assemblies to these {{ARCHITECTURES}} only. A comma-separated list of one or more of: {GetArchNames ()}", v => arches = ParseArchList (v) }, + "", + {"?|h|help", "Show this help screen", v => showHelp = true}, + }; + + List? theRest = options.Parse (args); + if (theRest == null || theRest.Count == 0 || showHelp) { + options.WriteOptionDescriptions (Console.Out); + return showHelp ? 0 : 1; + } + + string inputFile = theRest[0]; + (IList? explorers, string? errorMessage) = AssemblyStoreExplorer.Open (inputFile); + if (explorers == null) { + return WriteErrorAndReturn (errorMessage ?? "Unknown error"); + } + + foreach (AssemblyStoreExplorer store in explorers) { + if (arches != null && store.TargetArch.HasValue && !arches.Contains (store.TargetArch.Value)) { + continue; + } + + var printer = new StorePrettyPrinter (store); + printer.Show (); + } + + return 0; + } +} + +class StorePrettyPrinter +{ + readonly AssemblyStoreExplorer explorer; + + public StorePrettyPrinter (AssemblyStoreExplorer storeExplorer) + { + explorer = storeExplorer; + } + + public void Show () + { + Console.WriteLine ($"Store: {explorer.StorePath}"); + Console.WriteLine ($" Target architecture: {GetTargetArch (explorer)} ({GetBitness (explorer.Is64Bit)}-bit)"); + Console.WriteLine ($" Assembly count: {explorer.AssemblyCount}"); + Console.WriteLine ($" Index entry count: {explorer.IndexEntryCount}"); + Console.WriteLine (); + + if (explorer.Assemblies == null || explorer.Assemblies.Count == 0) { + Console.WriteLine ("NO ASSEMBLIES!"); + return; + } + + var assemblies = new List (explorer.Assemblies); + assemblies.Sort ((AssemblyStoreItem a, AssemblyStoreItem b) => a.Name.CompareTo (b.Name)); + + Console.WriteLine ("Assemblies:"); + var line = new StringBuilder (); + foreach (AssemblyStoreItem assembly in assemblies) { + line.Clear (); + line.Append (" "); + line.Append (assembly.Name); + if (assembly.Ignore) { + line.AppendLine (" "); + } else { + line.AppendLine (); + line.Append (" PE image data: "); + FormatOffsetAndSize (line, assembly.DataOffset, assembly.DataSize); + line.AppendLine (); + line.Append (" Debug data: "); + FormatOffsetAndSize (line, assembly.DebugOffset, assembly.DebugSize); + line.AppendLine (); + line.Append (" Config data: "); + FormatOffsetAndSize (line, assembly.ConfigOffset, assembly.ConfigSize); + line.AppendLine (); + } + line.Append (" Name hashes: "); + FormatHashes (line, assembly.Hashes); + line.AppendLine (); + Console.WriteLine (line.ToString ()); + } + Console.WriteLine (); + } + + static void FormatOffsetAndSize (StringBuilder sb, uint offset, uint size) + { + if (offset == 0) { + FormatNone (sb); + return; + } + + sb.Append ("offset "); + sb.Append (offset); + sb.Append (", size "); + sb.Append (size); + } + + static void FormatHashes (StringBuilder sb, IList hashes) + { + if (hashes.Count == 0) { + FormatNone (sb); + return; + } + + bool first = true; + foreach (ulong hash in hashes) { + if (first) { + first = false; + } else { + sb.Append (", "); + } + sb.Append ($"0x{hash:x}"); + } + } + + static void FormatNone (StringBuilder sb) + { + sb.Append ("none"); + } + + static string GetBitness (bool is64bit) => is64bit ? "64" : "32"; + + static string GetTargetArch (AssemblyStoreExplorer storeExplorer) + { + if (storeExplorer.TargetArch == null) { + return "ABI agnostic"; + } + + return storeExplorer.TargetArch switch { + AndroidTargetArch.Arm64 => "Arm64", + AndroidTargetArch.Arm => "Arm32", + AndroidTargetArch.X86_64 => "x64", + AndroidTargetArch.X86 => "x86", + _ => throw new NotSupportedException ($"Unsupported target architecture {storeExplorer.TargetArch}") + }; + } +} diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index ea2e1ad545e..7cbecc3325c 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -117,12 +117,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.SourceWriter", "ext EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "java-source-utils", "external\Java.Interop\tools\java-source-utils\java-source-utils.csproj", "{37FCD325-1077-4603-98E7-4509CAD648D6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "decompress-assemblies", "tools\decompress-assemblies\decompress-assemblies.csproj", "{88B746FF-8D6E-464D-9D66-FF2ECCF148E0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tmt", "tools\tmt\tmt.csproj", "{1A273ED2-AE84-48E9-9C23-E978C2D0CB34}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "assembly-store-reader", "tools\assembly-store-reader-mk2\assembly-store-reader.csproj", "{DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Java.Interop.Tools.JavaTypeSystem", "external\Java.Interop\src\Java.Interop.Tools.JavaTypeSystem\Java.Interop.Tools.JavaTypeSystem.csproj", "{4EFCED6E-9A6B-453A-94E4-CE4B736EC684}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "relnote-gen", "tools\relnote-gen\relnote-gen.csproj", "{D8E14B43-E929-4C18-9FA6-2C3DC47EFC17}" @@ -155,10 +151,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fastdevtools", "tools\fastd EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.Tasks", "src\Microsoft.Android.Build.Tasks\Microsoft.Android.Build.Tasks.csproj", "{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "xamarin-android-tools", "xamarin-android-tools", "{BB3C8E1B-31A2-1C4E-0B3F-36B3F475D6AA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{65925F91-7BB8-AC56-CADB-FB3DD28A5B5B}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyStore.Tests", "tools\assembly-store-reader-mk2\AssemblyStore.Tests\AssemblyStore.Tests.csproj", "{34FEC4BE-168E-460A-B049-94AA4627CE83}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyStore", "tools\assembly-store-reader-mk2\AssemblyStore\AssemblyStore.csproj", "{19DA4CD1-21A7-49FE-9431-4A8DF69A186B}" @@ -785,18 +777,6 @@ Global {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x64.Build.0 = Release|Any CPU {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x86.ActiveCfg = Release|Any CPU {37FCD325-1077-4603-98E7-4509CAD648D6}.Release|x86.Build.0 = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x64.ActiveCfg = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x64.Build.0 = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x86.ActiveCfg = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Debug|x86.Build.0 = Debug|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|Any CPU.Build.0 = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x64.ActiveCfg = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x64.Build.0 = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x86.ActiveCfg = Release|Any CPU - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0}.Release|x86.Build.0 = Release|Any CPU {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|Any CPU.ActiveCfg = Debug|anycpu {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|Any CPU.Build.0 = Debug|anycpu {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -809,18 +789,6 @@ Global {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x64.Build.0 = Release|Any CPU {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x86.ActiveCfg = Release|Any CPU {1A273ED2-AE84-48E9-9C23-E978C2D0CB34}.Release|x86.Build.0 = Release|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|Any CPU.ActiveCfg = Debug|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|Any CPU.Build.0 = Debug|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x64.ActiveCfg = Debug|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x64.Build.0 = Debug|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x86.ActiveCfg = Debug|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Debug|x86.Build.0 = Debug|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|Any CPU.ActiveCfg = Release|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|Any CPU.Build.0 = Release|anycpu - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x64.ActiveCfg = Release|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x64.Build.0 = Release|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x86.ActiveCfg = Release|Any CPU - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51}.Release|x86.Build.0 = Release|Any CPU {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|Any CPU.Build.0 = Debug|Any CPU {4EFCED6E-9A6B-453A-94E4-CE4B736EC684}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1079,9 +1047,7 @@ Global {2CE4CD4B-B7B7-4EAE-A9BE-2699824D6096} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {86A8DEFE-7ABB-4097-9389-C249581E243D} = {04E3E11E-B47D-4599-8AFC-50515A95E715} {37FCD325-1077-4603-98E7-4509CAD648D6} = {864062D3-A415-4A6F-9324-5820237BA058} - {88B746FF-8D6E-464D-9D66-FF2ECCF148E0} = {864062D3-A415-4A6F-9324-5820237BA058} {1A273ED2-AE84-48E9-9C23-E978C2D0CB34} = {864062D3-A415-4A6F-9324-5820237BA058} - {DA50FC92-7FE7-48B5-BDB6-CDA57B37BB51} = {864062D3-A415-4A6F-9324-5820237BA058} {4EFCED6E-9A6B-453A-94E4-CE4B736EC684} = {864062D3-A415-4A6F-9324-5820237BA058} {D8E14B43-E929-4C18-9FA6-2C3DC47EFC17} = {864062D3-A415-4A6F-9324-5820237BA058} {BA4D889D-066B-4C2C-A973-09E319CBC396} = {E351F97D-EA4F-4E7F-AAA0-8EBB1F2A4A62} @@ -1097,8 +1063,6 @@ Global {624C58EB-57CF-4754-95EF-E9893E584296} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} {4723786E-4BB5-4939-BBFD-3C2D0EAE236B} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} {4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B} = {FFCF518F-2A4A-40A2-9174-2EE13B76C723} - {BB3C8E1B-31A2-1C4E-0B3F-36B3F475D6AA} = {05C3B1D6-A4CE-4534-A9E4-E9117591ADF7} - {65925F91-7BB8-AC56-CADB-FB3DD28A5B5B} = {BB3C8E1B-31A2-1C4E-0B3F-36B3F475D6AA} {34FEC4BE-168E-460A-B049-94AA4627CE83} = {864062D3-A415-4A6F-9324-5820237BA058} {19DA4CD1-21A7-49FE-9431-4A8DF69A186B} = {864062D3-A415-4A6F-9324-5820237BA058} EndGlobalSection diff --git a/build-tools/automation/yaml-templates/build-windows-steps.yaml b/build-tools/automation/yaml-templates/build-windows-steps.yaml index 8c3080f0574..61645047a8b 100644 --- a/build-tools/automation/yaml-templates/build-windows-steps.yaml +++ b/build-tools/automation/yaml-templates/build-windows-steps.yaml @@ -120,6 +120,22 @@ steps: testResultsFiles: "$(Agent.TempDirectory)/assembly-store-tests/*.trx" testRunTitle: AssemblyStore.Tests +- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self + parameters: + command: run + project: --file .github/skills/read-assembly-store/scripts/read-assembly-store.cs + arguments: -- --help + displayName: Build read-assembly-store file app + continueOnError: false + +- template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self + parameters: + command: run + project: --file .github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs + arguments: -- --help + displayName: Build extract-android-assemblies file app + continueOnError: false + - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self parameters: command: test diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStorePayload.cs b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStorePayload.cs new file mode 100644 index 00000000000..d18cf57f812 --- /dev/null +++ b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStorePayload.cs @@ -0,0 +1,61 @@ +using System; +using System.IO; + +namespace Xamarin.Android.AssemblyStore; + +public static class AssemblyStorePayload +{ + public static bool TryExtractELFPayload (Stream input, Stream output) + { + ArgumentNullException.ThrowIfNull (input); + ArgumentNullException.ThrowIfNull (output); + + if (!input.CanRead || !input.CanSeek) { + throw new ArgumentException ("Input stream must be readable and seekable", nameof (input)); + } + if (!output.CanWrite) { + throw new ArgumentException ("Output stream must be writable", nameof (output)); + } + + long originalPosition = input.Position; + (ulong offset, ulong size, ELFPayloadError error) = Utils.FindELFPayloadOffsetAndSize (input); + if (error == ELFPayloadError.NotELF) { + input.Seek (originalPosition, SeekOrigin.Begin); + return false; + } + if (error != ELFPayloadError.None) { + throw new InvalidDataException (error switch { + ELFPayloadError.LoadFailed => "ELF image could not be loaded", + ELFPayloadError.NotSharedLibrary => "ELF image is not a shared library", + ELFPayloadError.NotLittleEndian => "ELF image is not little-endian", + ELFPayloadError.InvalidPayloadSymbol => "ELF image has an invalid '_assembly_store' symbol", + ELFPayloadError.NoPayloadSection => "ELF image does not contain a payload", + _ => $"Unable to locate ELF payload: {error}", + }); + } + if (offset > Int64.MaxValue || size > Int64.MaxValue) { + throw new InvalidDataException ("ELF payload offset or size exceeds the supported stream range"); + } + + input.Seek ((long)offset, SeekOrigin.Begin); + byte[] buffer = Utils.BytePool.Rent (65535); + try { + long remaining = (long)size; + while (remaining > 0) { + int read = input.Read (buffer, 0, (int)Math.Min (buffer.Length, remaining)); + if (read == 0) { + throw new InvalidDataException ("Unexpected end of ELF payload"); + } + output.Write (buffer, 0, read); + remaining -= read; + } + output.Flush (); + if (output.CanSeek) { + output.Seek (0, SeekOrigin.Begin); + } + return true; + } finally { + Utils.BytePool.Return (buffer); + } + } +} diff --git a/tools/assembly-store-reader-mk2/Directory.Build.targets b/tools/assembly-store-reader-mk2/Directory.Build.targets deleted file mode 100644 index 11acff32947..00000000000 --- a/tools/assembly-store-reader-mk2/Directory.Build.targets +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/tools/assembly-store-reader-mk2/Main.cs b/tools/assembly-store-reader-mk2/Main.cs deleted file mode 100644 index 3626e7744cc..00000000000 --- a/tools/assembly-store-reader-mk2/Main.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Mono.Options; -using Xamarin.Android.Tasks; -using Xamarin.Android.Tools; - -namespace Xamarin.Android.AssemblyStore; - -class App -{ - static void ShowHelp () - { - } - - static int WriteErrorAndReturn (string message) - { - Console.Error.WriteLine (message); - return 1; - } - - static HashSet? ParseArchList (string values) - { - if (String.IsNullOrEmpty (values)) { - return null; - } - - var ret = new HashSet (); - foreach (string a in values.Split (',')) { - string archName = a.Trim (); - if (Enum.TryParse (archName, out AndroidTargetArch arch)) { - ret.Add (arch); - continue; - } - - arch = archName.ToLowerInvariant () switch { - "aarch64" => AndroidTargetArch.Arm64, - "arm32" => AndroidTargetArch.Arm, - "arm64" => AndroidTargetArch.Arm64, - "armv7a" => AndroidTargetArch.Arm, - "armv8a" => AndroidTargetArch.Arm64, - "x64" => AndroidTargetArch.X86_64, - _ => throw new InvalidOperationException ($"Unknown architecture name '{archName}'") - }; - ret.Add (arch); - } - - return ret; - } - - static string GetArchNames () - { - return String.Join (", ", MonoAndroidHelper.SupportedTargetArchitectures.Select (a => a.ToString ().ToLowerInvariant ())); - } - - static int Main (string[] args) - { - HashSet? arches = null; - bool showHelp = false; - - var options = new OptionSet { - "Usage: read-assembly-store [OPTIONS] BLOB_PATH", - "", - " where each BLOB_PATH can point to:", - " * aab file", - " * apk file", - " * index store file (e.g. base_assemblies.blob or libassembly-store.so)", - " * arch store file (e.g. base_assemblies.blob)", - " * store manifest file (e.g. base_assemblies.manifest)", - " * store base name (e.g. base or base_assemblies)", - "", - " In each case the whole set of stores and manifests will be read (if available). Search for the", - " various members of the store set (common/main store, arch stores, manifest) is based on this naming", - " convention:", - "", - " {BASE_NAME}[.ARCH_NAME].{blob|so|manifest}", - "", - " Whichever file is referenced in `BLOB_PATH`, the BASE_NAME component is extracted and all the found files are read.", - " If `BLOB_PATH` points to an aab or an apk, BASE_NAME will always be `assemblies`", - "", - {"a|arch=", $"Limit listing of assemblies to these {{ARCHITECTURES}} only. A comma-separated list of one or more of: {GetArchNames ()}", v => arches = ParseArchList (v) }, - "", - {"?|h|help", "Show this help screen", v => showHelp = true}, - }; - - List? theRest = options.Parse (args); - if (theRest == null || theRest.Count == 0 || showHelp) { - options.WriteOptionDescriptions (Console.Out); - return showHelp ? 0 : 1; - } - - string inputFile = theRest[0]; - (IList? explorers, string? errorMessage) = AssemblyStoreExplorer.Open (inputFile); - if (explorers == null) { - return WriteErrorAndReturn (errorMessage ?? "Unknown error"); - } - - foreach (AssemblyStoreExplorer store in explorers) { - if (arches != null && store.TargetArch.HasValue && !arches.Contains (store.TargetArch.Value)) { - continue; - } - - var printer = new StorePrettyPrinter (store); - printer.Show (); - } - - return 0; - } - - -} diff --git a/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs b/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs deleted file mode 100644 index d49c5d3d5cf..00000000000 --- a/tools/assembly-store-reader-mk2/StorePrettyPrinter.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -using Xamarin.Android.Tools; - -namespace Xamarin.Android.AssemblyStore; - -class StorePrettyPrinter -{ - AssemblyStoreExplorer explorer; - - public StorePrettyPrinter (AssemblyStoreExplorer storeExplorer) - { - explorer = storeExplorer; - } - - public void Show () - { - Console.WriteLine ($"Store: {explorer.StorePath}"); - Console.WriteLine ($" Target architecture: {GetTargetArch (explorer)} ({GetBitness (explorer.Is64Bit)}-bit)"); - Console.WriteLine ($" Assembly count: {explorer.AssemblyCount}"); - Console.WriteLine ($" Index entry count: {explorer.IndexEntryCount}"); - Console.WriteLine (); - - if (explorer.Assemblies == null || explorer.Assemblies.Count == 0) { - Console.WriteLine ("NO ASSEMBLIES!"); - return; - } - - var assemblies = new List (explorer.Assemblies); - assemblies.Sort ((AssemblyStoreItem a, AssemblyStoreItem b) => a.Name.CompareTo (b.Name)); - - Console.WriteLine ("Assemblies:"); - var line = new StringBuilder (); - foreach (AssemblyStoreItem assembly in assemblies) { - line.Clear (); - line.Append (" "); - line.Append (assembly.Name); - if (assembly.Ignore) { - line.AppendLine (" "); - } else { - line.AppendLine (); - line.Append (" PE image data: "); - FormatOffsetAndSize (line, assembly.DataOffset, assembly.DataSize); - line.AppendLine (); - line.Append (" Debug data: "); - FormatOffsetAndSize (line, assembly.DebugOffset, assembly.DebugSize); - line.AppendLine (); - line.Append (" Config data: "); - FormatOffsetAndSize (line, assembly.ConfigOffset, assembly.ConfigSize); - line.AppendLine (); - } - line.Append (" Name hashes: "); - FormatHashes (line, assembly.Hashes); - line.AppendLine (); - Console.WriteLine (line.ToString ()); - } - Console.WriteLine (); - } - - static void FormatOffsetAndSize (StringBuilder sb, uint offset, uint size) - { - if (offset == 0) { - FormatNone (sb); - return; - } - - sb.Append ("offset "); - sb.Append (offset); - sb.Append (", size "); - sb.Append (size); - } - - static void FormatHashes (StringBuilder sb, IList hashes) - { - if (hashes.Count == 0) { - FormatNone (sb); - return; - } - - bool first = true; - foreach (ulong hash in hashes) { - if (first) { - first = false; - } else { - sb.Append (", "); - } - sb.Append ($"0x{hash:x}"); - } - } - - static void FormatNone (StringBuilder sb) - { - sb.Append ("none"); - } - - static string GetBitness (bool is64bit) => is64bit ? "64" : "32"; - - static string GetTargetArch (AssemblyStoreExplorer storeExplorer) - { - if (storeExplorer.TargetArch == null) { - return "ABI agnostic"; - } - - return storeExplorer.TargetArch switch { - AndroidTargetArch.Arm64 => "Arm64", - AndroidTargetArch.Arm => "Arm32", - AndroidTargetArch.X86_64 => "x64", - AndroidTargetArch.X86 => "x86", - _ => throw new NotSupportedException ($"Unsupported target architecture {storeExplorer.TargetArch}") - }; - } -} diff --git a/tools/assembly-store-reader-mk2/assembly-store-reader.csproj b/tools/assembly-store-reader-mk2/assembly-store-reader.csproj deleted file mode 100644 index a70e8504173..00000000000 --- a/tools/assembly-store-reader-mk2/assembly-store-reader.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Microsoft Corporation - 2023 Microsoft Corporation - 0.0.2 - false - ../../bin/$(Configuration)/bin/assembly-store-reader - Exe - $(DotNetTargetFramework) - Xamarin.Android.AssemblyStoreReader - disable - enable - - - - - - - - - - - - - - - - - - - - - - diff --git a/tools/assembly-store-reader/Directory.Build.targets b/tools/assembly-store-reader/Directory.Build.targets deleted file mode 100644 index e58eed5ca2c..00000000000 --- a/tools/assembly-store-reader/Directory.Build.targets +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/tools/assembly-store-reader/Program.cs b/tools/assembly-store-reader/Program.cs deleted file mode 100644 index 90d1449bc03..00000000000 --- a/tools/assembly-store-reader/Program.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -using Mono.Options; -using Xamarin.Android.AssemblyStore.V1; - -namespace Xamarin.Android.AssemblyStore -{ - class Program - { - static void ShowAssemblyStoreInfo (string storePath) - { - var explorer = new AssemblyStoreExplorer (storePath); - - string yesno = explorer.IsCompleteSet ? "yes" : "no"; - Console.WriteLine ($"Store set '{explorer.StoreSetName}':"); - Console.WriteLine ($" Is complete set? {yesno}"); - Console.WriteLine ($" Number of stores in the set: {explorer.NumberOfStores}"); - Console.WriteLine (); - Console.WriteLine ("Assemblies:"); - - string infoIndent = " "; - foreach (AssemblyStoreAssembly assembly in explorer.Assemblies) { - Console.WriteLine ($" {assembly.RuntimeIndex}:"); - Console.Write ($"{infoIndent}Name: "); - if (String.IsNullOrEmpty (assembly.Name)) { - Console.WriteLine ("unknown"); - } else { - Console.WriteLine (assembly.Name); - } - - Console.Write ($"{infoIndent}Store ID: {assembly.Store.StoreID} ("); - if (String.IsNullOrEmpty (assembly.Store.Arch)) { - Console.Write ("shared"); - } else { - Console.Write (assembly.Store.Arch); - } - Console.WriteLine (")"); - - Console.Write ($"{infoIndent}Hashes: 32-bit == "); - WriteHashValue (assembly.Hash32); - - Console.Write ("; 64-bit == "); - WriteHashValue (assembly.Hash64); - Console.WriteLine (); - - Console.WriteLine ($"{infoIndent}Assembly image: offset == {assembly.DataOffset}; size == {assembly.DataSize}"); - WriteOptionalDataLine ("Debug data", assembly.DebugDataOffset, assembly.DebugDataOffset); - WriteOptionalDataLine ("Config file", assembly.ConfigDataOffset, assembly.ConfigDataSize); - - Console.WriteLine (); - } - - AssemblyStoreReader? indexStore = null; - foreach (var kvp in explorer.Stores) { - List storeList = kvp.Value; - foreach (AssemblyStoreReader store in storeList) { - if (!store.HasGlobalIndex) { - continue; - } - - indexStore = store; - break; - } - } - - if (indexStore == null) { - Console.WriteLine ("Index store not found in the set, not showing hash tables."); - return; - } - - Console.WriteLine ("Hash tables:"); - WriteHashTable ("32", indexStore.GlobalIndex32, "x08"); - WriteHashTable ("64", indexStore.GlobalIndex64, "x016"); - - void WriteHashTable (string bitness, List table, string hashFormat) - { - Console.WriteLine ($" {bitness}-bit:"); - var sb = new StringBuilder (); - ulong prev = 0; - - for (int i = 0; i < table.Count; i++) { - AssemblyStoreHashEntry entry = table[i]; - - if (entry.Hash < prev) { - Console.WriteLine ($"{infoIndent}* entry {i} is smaller than the previous one"); - } else if (entry.Hash == prev) { - Console.WriteLine ($"{infoIndent}* entry {i} is a duplicate of the previous one"); - } - sb.Append ($"{infoIndent}0x"); - sb.AppendLine (entry.Hash.ToString (hashFormat)); - prev = entry.Hash; - } - Console.WriteLine (sb.ToString ()); - } - - void WriteOptionalDataLine (string label, uint offset, uint size) - { - Console.Write ($"{infoIndent}{label}: "); - if (offset == 0) { - Console.WriteLine ("absent"); - } else { - Console.WriteLine ($"offset == {offset}; size == {size}"); - } - } - - void WriteHashValue (ulong hash) - { - if (hash == 0) { - Console.Write ("unknown"); - } else { - Console.Write ($"0x{hash:x}"); - } - } - } - - static int Main (string[] args) - { - if (args.Length == 0) { - Console.Error.WriteLine ("Usage: read-assembly-store BLOB_PATH [BLOB_PATH ...]"); - Console.Error.WriteLine (); - Console.Error.WriteLine (@" where each BLOB_PATH can point to: - * aab file - * apk file - * index store file (e.g. base_assemblies.blob or assemblies.arm64_v8a.blob.so) - * arch store file (e.g. base_assemblies.arm64_v8a.blob) - * store manifest file (e.g. base_assemblies.manifest) - * store base name (e.g. base or base_assemblies) - - In each case the whole set of stores and manifests will be read (if available). Search for the - various members of the store set (common/main store, arch stores, manifest) is based on this naming - convention: - - {BASE_NAME}[.ARCH_NAME].{blob|manifest} - - Whichever file is referenced in `BLOB_PATH`, the BASE_NAME component is extracted and all the found files are read. - If `BLOB_PATH` points to an aab or an apk, BASE_NAME will always be `assemblies` - -"); - return 1; - } - - bool first = true; - foreach (string path in args) { - ShowAssemblyStoreInfo (path); - if (first) { - first = false; - continue; - } - - Console.WriteLine (); - Console.WriteLine ("***********************************"); - Console.WriteLine (); - } - - return 0; - } - - static void WriteAssemblySegment (string label, uint offset, uint size) - { - if (offset == 0) { - Console.Write ($"no {label}"); - return; - } - - Console.Write ($"{label} starts at {offset}, {size} bytes"); - } - } -} diff --git a/tools/assembly-store-reader/assembly-store-reader.csproj b/tools/assembly-store-reader/assembly-store-reader.csproj deleted file mode 100644 index 0e68dc702b6..00000000000 --- a/tools/assembly-store-reader/assembly-store-reader.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - - Microsoft Corporation - 2023 Microsoft Corporation - 0.0.2 - false - ../../bin/$(Configuration)/bin/assembly-store-reader - Exe - $(DotNetStableTargetFramework) - Xamarin.Android.AssemblyStoreReader - disable - enable - - - - - - - - - - - - - diff --git a/tools/decompress-assemblies/decompress-assemblies.csproj b/tools/decompress-assemblies/decompress-assemblies.csproj deleted file mode 100644 index 458582c7463..00000000000 --- a/tools/decompress-assemblies/decompress-assemblies.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - Microsoft Corporation - 2021 Microsoft Corporation - 0.0.1 - $(DotNetTargetFramework) - false - Xamarin.Android.Tools.DecompressAssemblies - decompress-assemblies - ../../bin/$(Configuration)/bin - Exe - true - enable - - - - - - - - - - - - - - - - - - - - decompress-assemblies - PreserveNewest - - - diff --git a/tools/decompress-assemblies/main.cs b/tools/decompress-assemblies/main.cs deleted file mode 100644 index 6a64030454d..00000000000 --- a/tools/decompress-assemblies/main.cs +++ /dev/null @@ -1,198 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using Xamarin.Android.AssemblyStore; -using Xamarin.Android.Tasks; -using Xamarin.Android.Tools; -using Xamarin.Tools.Zip; - -namespace Xamarin.Android.Tools.DecompressAssemblies -{ - class App - { - static int Usage () - { - Console.WriteLine ("Usage: decompress-assemblies {file.{dll,apk,aab}} [{file.{dll,apk,aab} ...]"); - Console.WriteLine (); - Console.WriteLine ("DLL files passed on command line are uncompressed to the current directory with the `uncompressed-` prefix added to their name."); - Console.WriteLine ("DLL files from AAB/APK archives are uncompressed to a subdirectory of the current directory named after the archive with extension removed"); - return 1; - } - - static bool UncompressDLL (Stream inputStream, string fileName, string filePath, string prefix) - { - string outputFile = $"{prefix}{filePath}"; - Console.WriteLine ($"Processing {fileName}"); - - using var assemblyStream = new MemoryStream (); - AssemblyCompressionFormat format; - try { - if (!AssemblyCompression.TryDecompress (inputStream, assemblyStream, out format)) { - Console.WriteLine (" assembly is not compressed"); - return true; - } - } catch (InvalidDataException e) { - Console.Error.WriteLine ($" Failed to decompress {fileName}: {e.Message}"); - return false; - } - - string? outputDir = Path.GetDirectoryName (outputFile); - if (!String.IsNullOrEmpty (outputDir)) { - Directory.CreateDirectory (outputDir); - } - - assemblyStream.Seek (0, SeekOrigin.Begin); - using (var output = File.Open (outputFile, FileMode.Create, FileAccess.Write)) { - assemblyStream.CopyTo (output); - output.Flush (); - } - Console.WriteLine ($" {format} assembly uncompressed to: {outputFile}"); - return true; - } - - static bool UncompressDLL (string filePath, string prefix) - { - using (var fs = File.Open (filePath, FileMode.Open, FileAccess.Read)) { - return UncompressDLL (fs, filePath, Path.GetFileName (filePath), prefix); - } - } - - static bool UncompressFromAPK_IndividualEntries (ZipArchive apk, string filePath, string assembliesPath, string prefix) - { - bool retVal = true; - foreach (ZipEntry entry in apk) { - if (!entry.FullName.StartsWith (assembliesPath, StringComparison.Ordinal)) { - continue; - } - - if (!entry.FullName.EndsWith (".dll", StringComparison.Ordinal)) { - continue; - } - - using (var stream = new MemoryStream ()) { - entry.Extract (stream); - stream.Seek (0, SeekOrigin.Begin); - string fileName = entry.FullName.Substring (assembliesPath.Length); - retVal &= UncompressDLL (stream, $"{filePath}!{entry.FullName}", fileName, prefix); - } - } - - return retVal; - } - - static bool UncompressFromAPK_AssemblyStores (string filePath, string prefix) - { - (IList? stores, string? errorMessage) = AssemblyStoreExplorer.Open (filePath); - if (stores == null) { - Console.Error.WriteLine (errorMessage ?? $"Unable to read assembly stores from '{filePath}'"); - return false; - } - - bool retVal = true; - foreach (AssemblyStoreExplorer store in stores) { - string? abi = null; - if (store.TargetArch.HasValue && !TryGetAndroidAbi (store.TargetArch.Value, out abi)) { - Console.Error.WriteLine ($"Assembly store '{store.StorePath}' has unsupported target architecture '{store.TargetArch.Value}'"); - retVal = false; - continue; - } - - foreach (AssemblyStoreItem assembly in store.Assemblies ?? []) { - if (assembly.Ignore) { - continue; - } - - string fileName = assembly.Name.EndsWith (".dll", StringComparison.OrdinalIgnoreCase) ? assembly.Name : $"{assembly.Name}.dll"; - string assemblyName = abi == null ? fileName : $"{abi}/{fileName}"; - using Stream? stream = store.ReadImageData (assembly); - if (stream == null) { - Console.Error.WriteLine ($"Unable to read '{assembly.Name}' from assembly store '{store.StorePath}'"); - retVal = false; - continue; - } - - retVal &= UncompressDLL (stream, $"{filePath}!{assemblyName}", assemblyName, prefix); - } - } - - return retVal; - } - - static bool TryGetAndroidAbi (AndroidTargetArch arch, out string abi) - { - if (!MonoAndroidHelper.SupportedTargetArchitectures.Contains (arch)) { - abi = ""; - return false; - } - - abi = MonoAndroidHelper.ArchToAbi (arch); - return true; - } - - static bool HasAssemblyStore (ZipArchive apk, string assembliesPath, string nativeLibrariesPath) - { - if (apk.ContainsEntry ($"{assembliesPath}assemblies.blob")) { - return true; - } - - foreach (AndroidTargetArch arch in MonoAndroidHelper.SupportedTargetArchitectures) { - string abi = MonoAndroidHelper.ArchToAbi (arch); - if ( - apk.ContainsEntry ($"{nativeLibrariesPath}{abi}/libassembly-store.so") || - apk.ContainsEntry ($"{nativeLibrariesPath}{abi}/libassemblies.{abi}.blob.so") - ) { - return true; - } - } - - return false; - } - - static bool UncompressFromAPK (string filePath, string assembliesPath, string nativeLibrariesPath) - { - string prefix = $"uncompressed-{Path.GetFileNameWithoutExtension (filePath)}{Path.DirectorySeparatorChar}"; - using (ZipArchive apk = ZipArchive.Open (filePath, FileMode.Open)) { - if (HasAssemblyStore (apk, assembliesPath, nativeLibrariesPath)) { - return UncompressFromAPK_AssemblyStores (filePath, prefix); - } - - return UncompressFromAPK_IndividualEntries (apk, filePath, assembliesPath, prefix); - } - } - - static int Main (string[] args) - { - if (args.Length == 0) { - return Usage (); - } - - bool haveErrors = false; - foreach (string file in args) { - string ext = Path.GetExtension (file); - if (String.Compare (".dll", ext, StringComparison.OrdinalIgnoreCase) == 0) { - if (!UncompressDLL (file, "uncompressed-")) { - haveErrors = true; - } - continue; - } - - if (String.Compare (".apk", ext, StringComparison.OrdinalIgnoreCase) == 0) { - if (!UncompressFromAPK (file, "assemblies/", "lib/")) { - haveErrors = true; - } - continue; - } - - if (String.Compare (".aab", ext, StringComparison.OrdinalIgnoreCase) == 0) { - if (!UncompressFromAPK (file, "base/root/assemblies/", "base/lib/")) { - haveErrors = true; - } - continue; - } - } - - return haveErrors ? 1 : 0; - } - } -} diff --git a/tools/scripts/decompress-assemblies b/tools/scripts/decompress-assemblies deleted file mode 100755 index 8897f401de3..00000000000 --- a/tools/scripts/decompress-assemblies +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -truepath=$(readlink "$0" || echo "$0") -exec mono --debug "${truepath}.exe" "$@" diff --git a/tools/scripts/read-assembly-store b/tools/scripts/read-assembly-store deleted file mode 100755 index 3386ec91783..00000000000 --- a/tools/scripts/read-assembly-store +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -truepath=$(readlink "$0" || echo "$0") -mydir=$(dirname ${truepath}) -binariesdir="${mydir}/assembly-store-reader" - -if [ -x "${binariesdir}/assembly-store-reader" ]; then - exec "${binariesdir}/assembly-store-reader" "$@" -else - exec dotnet "${binariesdir}/assembly-store-reader.dll" -fi From 531a983464e57ea6856774317be5b7ae60cf058d Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Wed, 15 Jul 2026 00:16:40 +0200 Subject: [PATCH 2/3] [tools] Address file app review Report invalid architecture filters and path traversal errors without stack traces, remove dead reader code, and correct the extractor usage text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 55493571-86e2-4326-850e-ea55883aa5d8 --- .../scripts/extract-android-assemblies.cs | 5 ++- .../scripts/read-assembly-store.cs | 38 +++++++++++-------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs b/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs index 4c89abf9abf..978316a46a9 100644 --- a/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs +++ b/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs @@ -23,7 +23,7 @@ class App static int Usage (int exitCode = 1) { - Console.WriteLine ("Usage: extract-android-assemblies [--output DIRECTORY] {file.{dll,apk,aab}} [{file.{dll,apk,aab} ...]"); + Console.WriteLine ("Usage: extract-android-assemblies [--output DIRECTORY] {file.{dll,apk,aab}} [{file.{dll,apk,aab}} ...]"); Console.WriteLine (); Console.WriteLine ("Extracts managed assemblies from .NET for Android files, including legacy/current stores and LZ4/Zstd compression."); Console.WriteLine ("Without --output, files are written under `uncompressed-{input-name}` in the current directory."); @@ -271,6 +271,9 @@ static int Main (string[] args) } catch (UnauthorizedAccessException e) { Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); haveErrors = true; + } catch (InvalidDataException e) { + Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); + haveErrors = true; } catch (IOException e) { Console.Error.WriteLine ($"Unable to extract '{file}': {e.Message}"); haveErrors = true; diff --git a/.github/skills/read-assembly-store/scripts/read-assembly-store.cs b/.github/skills/read-assembly-store/scripts/read-assembly-store.cs index 1fbb520765b..f90015cb0fc 100644 --- a/.github/skills/read-assembly-store/scripts/read-assembly-store.cs +++ b/.github/skills/read-assembly-store/scripts/read-assembly-store.cs @@ -21,43 +21,43 @@ class App AndroidTargetArch.X86_64, ]; - static void ShowHelp () - { - } - static int WriteErrorAndReturn (string message) { Console.Error.WriteLine (message); return 1; } - static HashSet? ParseArchList (string values) + static bool TryParseArchList (string values, out HashSet? arches, out string? errorMessage) { if (String.IsNullOrEmpty (values)) { - return null; + arches = null; + errorMessage = null; + return true; } var ret = new HashSet (); foreach (string a in values.Split (',')) { string archName = a.Trim (); - if (Enum.TryParse (archName, out AndroidTargetArch arch)) { - ret.Add (arch); - continue; - } - - arch = archName.ToLowerInvariant () switch { + AndroidTargetArch arch = archName.ToLowerInvariant () switch { "aarch64" => AndroidTargetArch.Arm64, "arm32" => AndroidTargetArch.Arm, "arm64" => AndroidTargetArch.Arm64, "armv7a" => AndroidTargetArch.Arm, "armv8a" => AndroidTargetArch.Arm64, "x64" => AndroidTargetArch.X86_64, - _ => throw new InvalidOperationException ($"Unknown architecture name '{archName}'") + _ => Enum.TryParse (archName, ignoreCase: true, out AndroidTargetArch parsed) ? parsed : AndroidTargetArch.None, }; + if (Array.IndexOf (supportedTargetArchitectures, arch) < 0) { + arches = null; + errorMessage = $"Unknown architecture name '{archName}'. Supported architectures: {GetArchNames ()}"; + return false; + } ret.Add (arch); } - return ret; + arches = ret; + errorMessage = null; + return true; } static string GetArchNames () @@ -68,6 +68,7 @@ static string GetArchNames () static int Main (string[] args) { HashSet? arches = null; + string? archError = null; bool showHelp = false; var options = new OptionSet { @@ -90,12 +91,19 @@ static int Main (string[] args) " Whichever file is referenced in `BLOB_PATH`, the BASE_NAME component is extracted and all the found files are read.", " If `BLOB_PATH` points to an aab or an apk, BASE_NAME will always be `assemblies`", "", - {"a|arch=", $"Limit listing of assemblies to these {{ARCHITECTURES}} only. A comma-separated list of one or more of: {GetArchNames ()}", v => arches = ParseArchList (v) }, + {"a|arch=", $"Limit listing of assemblies to these {{ARCHITECTURES}} only. A comma-separated list of one or more of: {GetArchNames ()}", v => { + if (!TryParseArchList (v, out arches, out string? errorMessage)) { + archError = errorMessage; + } + }}, "", {"?|h|help", "Show this help screen", v => showHelp = true}, }; List? theRest = options.Parse (args); + if (archError != null) { + return WriteErrorAndReturn (archError); + } if (theRest == null || theRest.Count == 0 || showHelp) { options.WriteOptionDescriptions (Console.Out); return showHelp ? 0 : 1; From 197d0cbba8f81958eea0b41a591e8e25cdb3feb5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 20 Jul 2026 15:59:24 -0500 Subject: [PATCH 3/3] [tools] Move assembly store implementation into skill Relocate the shared parser and compatibility tests under the read-assembly-store skill, update all source and project consumers, and remove the obsolete assembly-store-reader-mk2 directory. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8c81166b-f589-4e13-87d3-942acf3c9df2 --- .../scripts/extract-android-assemblies.cs | 2 +- .../read-assembly-store/scripts/read-assembly-store.cs | 2 +- .../src}/AssemblyStore/AssemblyCompression.cs | 0 .../src}/AssemblyStore/AssemblyStore.csproj | 6 +++--- .../src}/AssemblyStore/AssemblyStoreExplorer.cs | 0 .../src}/AssemblyStore/AssemblyStoreItem.cs | 0 .../src}/AssemblyStore/AssemblyStorePayload.cs | 0 .../src}/AssemblyStore/AssemblyStoreReader.cs | 0 .../src}/AssemblyStore/ELFPayloadError.cs | 0 .../read-assembly-store/src}/AssemblyStore/FileFormat.cs | 0 .../skills/read-assembly-store/src}/AssemblyStore/Log.cs | 0 .../src}/AssemblyStore/StoreReader_V1.cs | 0 .../src}/AssemblyStore/StoreReader_V2.Classes.cs | 0 .../src}/AssemblyStore/StoreReader_V2.cs | 0 .../skills/read-assembly-store/src}/AssemblyStore/Utils.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreAssembly.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreExplorer.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreExplorerLogLevel.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreHashEntry.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreManifestEntry.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreManifestReader.cs | 0 .../src}/AssemblyStore/V1/AssemblyStoreReader.cs | 0 .../tests}/AssemblyStore.Tests/AssemblyStore.Tests.csproj | 6 +++--- .../tests}/AssemblyStore.Tests/AssemblyStoreTests.cs | 0 .github/skills/tests/references/test-catalog.md | 4 ++-- Xamarin.Android.sln | 4 ++-- .../automation/yaml-templates/build-windows-steps.yaml | 2 +- .../Xamarin.Android.Build.Tests.csproj | 4 ++-- .../Utilities/DlopenAssemblyStoreGenerator.cs | 4 ++-- .../MSBuildDeviceIntegration.csproj | 4 ++-- tools/tmt/tmt.csproj | 4 ++-- 31 files changed, 21 insertions(+), 21 deletions(-) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/AssemblyCompression.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/AssemblyStore.csproj (73%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/AssemblyStoreExplorer.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/AssemblyStoreItem.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/AssemblyStorePayload.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/AssemblyStoreReader.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/ELFPayloadError.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/FileFormat.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/Log.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/StoreReader_V1.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/StoreReader_V2.Classes.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/StoreReader_V2.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/Utils.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreAssembly.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreExplorer.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreExplorerLogLevel.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreHashEntry.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreManifestEntry.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreManifestReader.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/src}/AssemblyStore/V1/AssemblyStoreReader.cs (100%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/tests}/AssemblyStore.Tests/AssemblyStore.Tests.csproj (67%) rename {tools/assembly-store-reader-mk2 => .github/skills/read-assembly-store/tests}/AssemblyStore.Tests/AssemblyStoreTests.cs (100%) diff --git a/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs b/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs index 978316a46a9..7d62036449a 100644 --- a/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs +++ b/.github/skills/extract-android-assemblies/scripts/extract-android-assemblies.cs @@ -1,6 +1,6 @@ #!/usr/bin/env dotnet #:property TargetFramework=net11.0 -#:project ../../../../tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj +#:project ../../read-assembly-store/src/AssemblyStore/AssemblyStore.csproj using System; using System.Collections.Generic; diff --git a/.github/skills/read-assembly-store/scripts/read-assembly-store.cs b/.github/skills/read-assembly-store/scripts/read-assembly-store.cs index f90015cb0fc..65e3255182a 100644 --- a/.github/skills/read-assembly-store/scripts/read-assembly-store.cs +++ b/.github/skills/read-assembly-store/scripts/read-assembly-store.cs @@ -1,6 +1,6 @@ #!/usr/bin/env dotnet #:property TargetFramework=net11.0 -#:project ../../../../tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj +#:project ../src/AssemblyStore/AssemblyStore.csproj #:package Mono.Options@6.12.0.148 using System; diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyCompression.cs b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyCompression.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/AssemblyCompression.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/AssemblyCompression.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyStore.csproj similarity index 73% rename from tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj rename to .github/skills/read-assembly-store/src/AssemblyStore/AssemblyStore.csproj index e15dd161b97..caa26561303 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStore.csproj +++ b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyStore.csproj @@ -1,5 +1,5 @@ - + Microsoft Corporation @@ -19,10 +19,10 @@ - + - + diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyStoreExplorer.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/AssemblyStoreExplorer.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyStoreItem.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/AssemblyStoreItem.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStorePayload.cs b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyStorePayload.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStorePayload.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/AssemblyStorePayload.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreReader.cs b/.github/skills/read-assembly-store/src/AssemblyStore/AssemblyStoreReader.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreReader.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/AssemblyStoreReader.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs b/.github/skills/read-assembly-store/src/AssemblyStore/ELFPayloadError.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/ELFPayloadError.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/FileFormat.cs b/.github/skills/read-assembly-store/src/AssemblyStore/FileFormat.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/FileFormat.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/FileFormat.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/Log.cs b/.github/skills/read-assembly-store/src/AssemblyStore/Log.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/Log.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/Log.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V1.cs b/.github/skills/read-assembly-store/src/AssemblyStore/StoreReader_V1.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V1.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/StoreReader_V1.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs b/.github/skills/read-assembly-store/src/AssemblyStore/StoreReader_V2.Classes.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/StoreReader_V2.Classes.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs b/.github/skills/read-assembly-store/src/AssemblyStore/StoreReader_V2.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/StoreReader_V2.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs b/.github/skills/read-assembly-store/src/AssemblyStore/Utils.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/Utils.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreAssembly.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreAssembly.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreAssembly.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreAssembly.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreExplorer.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreExplorer.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreExplorer.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreExplorer.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreExplorerLogLevel.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreExplorerLogLevel.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreExplorerLogLevel.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreExplorerLogLevel.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreHashEntry.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreHashEntry.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreHashEntry.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreHashEntry.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreManifestEntry.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreManifestEntry.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreManifestEntry.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreManifestEntry.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreManifestReader.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreManifestReader.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreManifestReader.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreManifestReader.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreReader.cs b/.github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreReader.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore/V1/AssemblyStoreReader.cs rename to .github/skills/read-assembly-store/src/AssemblyStore/V1/AssemblyStoreReader.cs diff --git a/tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStore.Tests.csproj b/.github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStore.Tests.csproj similarity index 67% rename from tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStore.Tests.csproj rename to .github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStore.Tests.csproj index 149f621c0a9..347b3480bf4 100644 --- a/tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStore.Tests.csproj +++ b/.github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStore.Tests.csproj @@ -1,6 +1,6 @@ - - + + $(DotNetStableTargetFramework);$(DotNetTargetFramework) @@ -14,6 +14,6 @@ - + diff --git a/tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStoreTests.cs b/.github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStoreTests.cs similarity index 100% rename from tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStoreTests.cs rename to .github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStoreTests.cs diff --git a/.github/skills/tests/references/test-catalog.md b/.github/skills/tests/references/test-catalog.md index 3c76975e1d7..5796b4871be 100644 --- a/.github/skills/tests/references/test-catalog.md +++ b/.github/skills/tests/references/test-catalog.md @@ -20,7 +20,7 @@ These tests can be run immediately with `dotnet test` on the `.csproj`, even if | Test Area | Project | Command | |-----------|---------|---------| -| **assembly store reader** | `tools/assembly-store-reader-mk2/AssemblyStore.Tests/` | `dotnet test tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStore.Tests.csproj -v minimal` | +| **assembly store reader** | `.github/skills/read-assembly-store/tests/AssemblyStore.Tests/` | `dotnet test .github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStore.Tests.csproj -v minimal` | | **trimmable type map** (unit) | `tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/` | `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests.csproj -v minimal` | | **aidl** | `tests/Xamarin.Android.Tools.Aidl-Tests/` | `dotnet test tests/Xamarin.Android.Tools.Aidl-Tests/Xamarin.Android.Tools.Aidl-Tests.csproj -v minimal` | | **source writer** | `external/Java.Interop/tests/Xamarin.SourceWriter-Tests/` | `dotnet test external/Java.Interop/tests/Xamarin.SourceWriter-Tests/Xamarin.SourceWriter-Tests.csproj -v minimal` | @@ -206,6 +206,6 @@ Run these tests with `dotnet test` from each test project directory listed above | Test Area | Tier | Assembly / Project | Notes | |-----------|------|--------------------|-------| | **aidl** | **Standalone** | `tests/Xamarin.Android.Tools.Aidl-Tests/` | AIDL compiler tests — `dotnet test` on `.csproj` | -| **assembly store reader** | **Standalone** | `tools/assembly-store-reader-mk2/AssemblyStore.Tests/` | Legacy/current store layouts and LZ4/Zstd decompression | +| **assembly store reader** | **Standalone** | `.github/skills/read-assembly-store/tests/AssemblyStore.Tests/` | Legacy/current store layouts and LZ4/Zstd decompression | | **api compatibility** | N/A | `tests/api-compatibility/` | Not a test runner — reference data for API surface checks | | **android sdk tools** | **Standalone** | `external/xamarin-android-tools/tests/` | Android SDK helper tooling tests — `dotnet test` on `.csproj` | diff --git a/Xamarin.Android.sln b/Xamarin.Android.sln index 7cbecc3325c..21f72321884 100644 --- a/Xamarin.Android.sln +++ b/Xamarin.Android.sln @@ -151,9 +151,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "fastdevtools", "tools\fastd EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.Tasks", "src\Microsoft.Android.Build.Tasks\Microsoft.Android.Build.Tasks.csproj", "{4E5C5846-CBFD-4695-B1F0-D48DEC0AF50B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyStore.Tests", "tools\assembly-store-reader-mk2\AssemblyStore.Tests\AssemblyStore.Tests.csproj", "{34FEC4BE-168E-460A-B049-94AA4627CE83}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyStore.Tests", ".github\skills\read-assembly-store\tests\AssemblyStore.Tests\AssemblyStore.Tests.csproj", "{34FEC4BE-168E-460A-B049-94AA4627CE83}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyStore", "tools\assembly-store-reader-mk2\AssemblyStore\AssemblyStore.csproj", "{19DA4CD1-21A7-49FE-9431-4A8DF69A186B}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyStore", ".github\skills\read-assembly-store\src\AssemblyStore\AssemblyStore.csproj", "{19DA4CD1-21A7-49FE-9431-4A8DF69A186B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/build-tools/automation/yaml-templates/build-windows-steps.yaml b/build-tools/automation/yaml-templates/build-windows-steps.yaml index 61645047a8b..8e803182699 100644 --- a/build-tools/automation/yaml-templates/build-windows-steps.yaml +++ b/build-tools/automation/yaml-templates/build-windows-steps.yaml @@ -108,7 +108,7 @@ steps: - template: /build-tools/automation/yaml-templates/run-dotnet-preview.yaml@self parameters: command: test - project: tools/assembly-store-reader-mk2/AssemblyStore.Tests/AssemblyStore.Tests.csproj + project: .github/skills/read-assembly-store/tests/AssemblyStore.Tests/AssemblyStore.Tests.csproj arguments: -c $(XA.Build.Configuration) --logger trx --results-directory $(Agent.TempDirectory)/assembly-store-tests displayName: Test AssemblyStore.Tests $(XA.Build.Configuration) diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj index 708f81906a5..b29fad9cbc6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj @@ -34,8 +34,8 @@ - - + + ..\Expected\GenerateDesignerFileExpected.cs PreserveNewest diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/DlopenAssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/DlopenAssemblyStoreGenerator.cs index 90bba8fb3c0..c7c210acb64 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/DlopenAssemblyStoreGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/DlopenAssemblyStoreGenerator.cs @@ -31,7 +31,7 @@ static class DlopenAssemblyStoreGenerator { public const string PayloadStartSymbol = "_assembly_store"; - // Section name that holds the payload. Must match the name `tools/assembly-store-reader-mk2` + // Section name that holds the payload. Must match the name `read-assembly-store` // (Utils.FindELFPayloadSectionOffsetAndSize) looks for and the one `DSOWrapperGenerator` uses. const string PayloadSectionName = "payload"; @@ -67,7 +67,7 @@ public static string WrapIt (TaskLoggingHelper log, DSOWrapperGenerator.Config c // The `.incbin` uses an absolute path so we don't depend on the assembler's working directory. // The section is named `payload` (no leading dot) to match the name the MonoVM - // `DSOWrapperGenerator` uses and that `tools/assembly-store-reader-mk2` looks for. + // `DSOWrapperGenerator` uses and that `read-assembly-store` looks for. string incbinPath = payloadFilePath.Replace ("\\", "\\\\").Replace ("\"", "\\\""); string asm = $""" .section {PayloadSectionName}, "a" diff --git a/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj b/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj index 59d31a19cc1..37abac988a8 100644 --- a/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj +++ b/tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj @@ -25,8 +25,8 @@ - - + + diff --git a/tools/tmt/tmt.csproj b/tools/tmt/tmt.csproj index 83863d30cee..993b787b37e 100644 --- a/tools/tmt/tmt.csproj +++ b/tools/tmt/tmt.csproj @@ -16,8 +16,8 @@ - - + +