diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
index 8a64f834b80..c01bf0248cd 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.After.targets
@@ -29,4 +29,5 @@ This file is imported *after* the Microsoft.NET.Sdk/Sdk.targets.
   
   
   
+  
 
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyStores.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyStores.targets
new file mode 100644
index 00000000000..1547a850e7a
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyStores.targets
@@ -0,0 +1,38 @@
+
+
+  
+  
+
+  
+    
+      
+    
+
+    
+      <_CreateEmbeddedAssemblyStoreAssembly Include="@(_ShrunkUserAssemblies);@(_AndroidResolvedSatellitePaths);@(_ShrunkFrameworkAssemblies)"/>
+      <_CreateEmbeddedAssemblyStoreAssembly Condition=" '@(_CreateEmbeddedAssemblyStoreAssembly->Count())' == '0' " Include="@(_ResolvedUserAssemblies);@(_ResolvedFrameworkAssemblies);@(_AndroidResolvedSatellitePaths)" />
+    
+  
+
+  
+    
+
+    
+      
+    
+  
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
index 17558b8efcd..b3285c44966 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
@@ -62,7 +62,6 @@ properties that determine build ordering.
     <_PrepareBuildApkDependsOnTargets>
       _SetLatestTargetFrameworkVersion;
       _GetLibraryImports;
-      _RemoveRegisterAttribute;
       _ResolveAssemblies;
       _ResolveSatellitePaths;
       _CreatePackageWorkspace;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CollectRuntimeConfigFilesForArchive.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CollectRuntimeConfigFilesForArchive.cs
deleted file mode 100644
index ca53119efcb..00000000000
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CollectRuntimeConfigFilesForArchive.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-using System;
-using System.IO;
-using Microsoft.Android.Build.Tasks;
-using Microsoft.Build.Framework;
-
-namespace Xamarin.Android.Tasks;
-
-/// 
-/// Collects rc.bin to be added to the final archive.
-/// 
-public class CollectRuntimeConfigFilesForArchive : AndroidTask
-{
-	const string ArchiveLibPath = "lib";
-
-	public override string TaskPrefix => "CRF";
-
-	[Required]
-	public string AndroidBinUtilsDirectory { get; set; } = "";
-
-	[Required]
-	public ITaskItem[] RuntimePackLibraryDirectories { get; set; } = Array.Empty ();
-
-	[Required]
-	public string IntermediateOutputPath { get; set; } = "";
-
-	public string RuntimeConfigBinFilePath { get; set; } = "";
-
-	[Required]
-	public string [] SupportedAbis { get; set; } = [];
-
-	[Output]
-	public ITaskItem [] FilesToAddToArchive { get; set; } = [];
-
-	public override bool RunTask ()
-	{
-		var files = new PackageFileListBuilder ();
-		var dsoWrapperConfig = DSOWrapperGenerator.GetConfig (Log, AndroidBinUtilsDirectory, RuntimePackLibraryDirectories, IntermediateOutputPath);
-
-		// We will place rc.bin in the `lib` directory next to the blob, to make startup slightly faster, as we will find the config file right after we encounter
-		// our assembly store.  Not only that, but also we'll be able to skip scanning the `base.apk` archive when split configs are enabled (which they are in 99%
-		// of cases these days, since AAB enforces that split).  `base.apk` contains only ABI-agnostic file, while one of the split config files contains only
-		// ABI-specific data+code.
-		if (!string.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath)) {
-			foreach (var abi in SupportedAbis) {
-				// Prefix it with `a` because bundletool sorts entries alphabetically, and this will place it right next to `assemblies.*.blob.so`, which is what we
-				// like since we can finish scanning the zip central directory earlier at startup.
-				var inArchivePath = MakeArchiveLibPath (abi, "libarc.bin.so");
-				var wrappedSourcePath = DSOWrapperGenerator.WrapIt (Log, dsoWrapperConfig, MonoAndroidHelper.AbiToTargetArch (abi), RuntimeConfigBinFilePath, Path.GetFileName (inArchivePath));
-				files.AddItem (wrappedSourcePath, inArchivePath);
-			}
-		}
-
-		FilesToAddToArchive = files.ToArray ();
-
-		return !Log.HasLoggedErrors;
-	}
-
-	static string MakeArchiveLibPath (string abi, string fileName) => MonoAndroidHelper.MakeZipArchivePath (ArchiveLibPath, abi, fileName);
-}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs
index 9d6d324f678..8fed9a084b9 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CompileNativeAssembly.cs
@@ -2,12 +2,8 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
-using System.Threading;
 
 using Microsoft.Build.Framework;
-using Microsoft.Build.Utilities;
-
-using Xamarin.Android.Tools;
 using Microsoft.Android.Build.Tasks;
 
 namespace Xamarin.Android.Tasks
@@ -37,109 +33,34 @@ sealed class Config
 
 		public override System.Threading.Tasks.Task RunTaskAsync ()
 		{
-			return this.WhenAll (GetAssemblerConfigs (), RunAssembler);
+			var context = new NativeAssemblerCompilation.AssemblerRunContext (
+				Log,
+				Path.GetFullPath (WorkingDirectory),
+				registerForCancellation: RegisterForCancellation,
+				cancel: Cancel
+			);
+
+			return this.WhenAll (
+				GetAssemblerConfigs (),
+				(NativeAssemblerCompilation.AssemblerConfig config) => NativeAssemblerCompilation.RunAssembler (context, config)
+			);
 		}
 
-		void RunAssembler (Config config)
+		void RegisterForCancellation (Process proc)
 		{
-			var stdout_completed = new ManualResetEvent (false);
-			var stderr_completed = new ManualResetEvent (false);
-			var psi = new ProcessStartInfo () {
-				FileName = config.AssemblerPath,
-				Arguments = config.AssemblerOptions,
-				UseShellExecute = false,
-				RedirectStandardOutput = true,
-				RedirectStandardError = true,
-				CreateNoWindow = true,
-				WindowStyle = ProcessWindowStyle.Hidden,
-				WorkingDirectory = WorkingDirectory,
-			};
-
-			string assemblerName = Path.GetFileName (config.AssemblerPath);
-			LogDebugMessage ($"[LLVM llc] {psi.FileName} {psi.Arguments}");
-
-			var stdoutLines = new List ();
-			var stderrLines = new List ();
-
-			using (var proc = new Process ()) {
-				proc.OutputDataReceived += (s, e) => {
-					if (e.Data != null) {
-						OnOutputData (assemblerName, s, e);
-						stdoutLines.Add (e.Data);
-					} else
-						stdout_completed.Set ();
-				};
-
-				proc.ErrorDataReceived += (s, e) => {
-					if (e.Data != null) {
-						OnErrorData (assemblerName, s, e);
-						stderrLines.Add (e.Data);
-					} else
-						stderr_completed.Set ();
-				};
-
-				proc.StartInfo = psi;
-				proc.Start ();
-				proc.BeginOutputReadLine ();
-				proc.BeginErrorReadLine ();
-				CancellationToken.Register (() => { try { proc.Kill (); } catch (Exception) { } });
-				proc.WaitForExit ();
-
-				if (psi.RedirectStandardError)
-					stderr_completed.WaitOne (TimeSpan.FromSeconds (30));
-
-				if (psi.RedirectStandardOutput)
-					stdout_completed.WaitOne (TimeSpan.FromSeconds (30));
-
-				if (proc.ExitCode != 0) {
-					var sb = MonoAndroidHelper.MergeStdoutAndStderrMessages (stdoutLines, stderrLines);
-					LogCodedError ("XA3006", Properties.Resources.XA3006, Path.GetFileName (config.InputSource), sb.ToString ());
-					Cancel ();
+			CancellationToken.Register (() => {
+				try {
+					proc.Kill ();
+				} catch (Exception) {
 				}
-			}
+			});
 		}
 
-		IEnumerable GetAssemblerConfigs ()
+		IEnumerable GetAssemblerConfigs ()
 		{
-			const string assemblerOptions =
-				"-O2 " +
-				"--debugger-tune=lldb " + // NDK uses lldb now
-				"--debugify-level=location+variables " +
-				"--fatal-warnings " +
-				"--filetype=obj " +
-				"--relocation-model=pic";
-			string llcPath = Path.Combine (AndroidBinUtilsDirectory, "llc");
-
 			foreach (ITaskItem item in Sources) {
-				// We don't need the directory since our WorkingDirectory is where all the sources are
-				string sourceFile = Path.GetFileName (item.ItemSpec);
-				string outputFile = QuoteFileName (sourceFile.Replace (".ll", ".o"));
-				string executableDir = Path.GetDirectoryName (llcPath);
-				string executableName = MonoAndroidHelper.GetExecutablePath (executableDir, Path.GetFileName (llcPath));
-
-				yield return new Config {
-					InputSource = item.ItemSpec,
-					AssemblerPath = Path.Combine (executableDir, executableName),
-					AssemblerOptions = $"{assemblerOptions} -o={outputFile} {QuoteFileName (sourceFile)}",
-				};
+				yield return NativeAssemblerCompilation.GetAssemblerConfig (AndroidBinUtilsDirectory, item, stripFilePaths: true);
 			}
 		}
-
-		void OnOutputData (string assemblerName, object sender, DataReceivedEventArgs e)
-		{
-			LogDebugMessage ($"[{assemblerName} stdout] {e.Data}");
-		}
-
-		void OnErrorData (string assemblerName, object sender, DataReceivedEventArgs e)
-		{
-			LogMessage ($"[{assemblerName} stderr] {e.Data}", MessageImportance.High);
-		}
-
-		static string QuoteFileName (string fileName)
-		{
-			var builder = new CommandLineBuilder ();
-			builder.AppendFileNameIfNotNull (fileName);
-			return builder.ToString ();
-		}
 	}
 }
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
index eb0a3724295..55ef0d7252d 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateAssemblyStore.cs
@@ -36,49 +36,16 @@ public class CreateAssemblyStore : AndroidTask
 	public override bool RunTask ()
 	{
 		// Get all the user and framework assemblies we may need to package
-		var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(ShouldSkipAssembly (asm))).ToArray ();
+		var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(AssemblyPackagingHelper.ShouldSkipAssembly (Log, asm)));
 
 		if (!UseAssemblyStore) {
-			AssembliesToAddToArchive = assemblies;
+			AssembliesToAddToArchive = assemblies.ToArray ();
 			return !Log.HasLoggedErrors;
 		}
 
-		var store_builder = new AssemblyStoreBuilder (Log);
-		var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, SupportedAbis, true);
-
-		foreach (var kvp in per_arch_assemblies) {
-			Log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'");
-
-			foreach (var assembly in kvp.Value.Values) {
-				var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec);
-				store_builder.AddAssembly (sourcePath, assembly, includeDebugSymbols: IncludeDebugSymbols);
-
-				Log.LogDebugMessage ($"Added '{sourcePath}' to assembly store.");
-			}
-		}
-
-		var assembly_store_paths = store_builder.Generate (AppSharedLibrariesDir);
-
-		if (assembly_store_paths.Count == 0) {
-			throw new InvalidOperationException ("Assembly store generator did not generate any stores");
-		}
-
-		if (assembly_store_paths.Count != SupportedAbis.Length) {
-			throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI");
-		}
-
+		var assembly_store_paths = AssemblyPackagingHelper.CreateAssemblyStore (Log, assemblies, AppSharedLibrariesDir, SupportedAbis, IncludeDebugSymbols);
 		AssembliesToAddToArchive = assembly_store_paths.Select (kvp => new TaskItem (kvp.Value, new Dictionary { { "Abi", MonoAndroidHelper.ArchToAbi (kvp.Key) } })).ToArray ();
 
 		return !Log.HasLoggedErrors;
 	}
-
-	bool ShouldSkipAssembly (ITaskItem asm)
-	{
-		var should_skip = asm.GetMetadataOrDefault ("AndroidSkipAddToPackage", false);
-
-		if (should_skip)
-			Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' ");
-
-		return should_skip;
-	}
 }
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/CreateEmbeddedAssemblyStore.cs b/src/Xamarin.Android.Build.Tasks/Tasks/CreateEmbeddedAssemblyStore.cs
new file mode 100644
index 00000000000..c75474ef7aa
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/CreateEmbeddedAssemblyStore.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+public class CreateEmbeddedAssemblyStore : AndroidTask
+{
+	public override string TaskPrefix => "CEAS";
+
+	[Required]
+	public string AndroidBinUtilsDirectory { get; set; } = "";
+
+	[Required]
+	public string AppSharedLibrariesDir { get; set; } = "";
+
+	[Required]
+	public string AssemblySourcesDir { get; set; } = "";
+
+	[Required]
+	public bool AssemblyStoreEmbeddedInRuntime { get; set; }
+
+	[Required]
+	public bool IncludeDebugSymbols { get; set; }
+
+	[Required]
+	public ITaskItem[] ResolvedUserAssemblies { get; set; } = [];
+
+	[Required]
+	public ITaskItem[] ResolvedFrameworkAssemblies { get; set; } = [];
+
+	[Required]
+	public string [] SupportedAbis { get; set; } = [];
+
+	public override bool RunTask ()
+	{
+		if (AssemblyStoreEmbeddedInRuntime) {
+			return EmbedAssemblyStore ();
+		}
+
+		// Generate sources to satisfy libmonodroid's ABI requirements
+		foreach (string abi in SupportedAbis) {
+			ELFEmbeddingHelper.EmbedBinary (
+				Log,
+				abi,
+				AndroidBinUtilsDirectory,
+				inputFile: null,
+				ELFEmbeddingHelper.KnownEmbedItems.AssemblyStore,
+				AssemblySourcesDir,
+				missingContentOK: true
+			);
+		}
+
+		return !Log.HasLoggedErrors;
+	}
+
+	bool EmbedAssemblyStore ()
+	{
+		var assemblies = ResolvedFrameworkAssemblies.Concat (ResolvedUserAssemblies).Where (asm => !(AssemblyPackagingHelper.ShouldSkipAssembly (Log, asm)));
+		var assemblyStorePaths = AssemblyPackagingHelper.CreateAssemblyStore (
+			Log, assemblies,
+			Path.Combine (AppSharedLibrariesDir, "embedded"),
+			SupportedAbis,
+			IncludeDebugSymbols
+		);
+
+		foreach (var kvp in assemblyStorePaths) {
+			string abi = MonoAndroidHelper.ArchToAbi (kvp.Key);
+			string inputFile = kvp.Value;
+
+			ELFEmbeddingHelper.EmbedBinary (
+				Log,
+				abi,
+				AndroidBinUtilsDirectory,
+				inputFile,
+				ELFEmbeddingHelper.KnownEmbedItems.AssemblyStore,
+				AssemblySourcesDir,
+				missingContentOK: false
+			);
+		}
+
+		return !Log.HasLoggedErrors;
+	}
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
index fd6b78fcebd..be5346385bf 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs
@@ -66,7 +66,7 @@ void GenerateCompressedAssemblySources ()
 
 					var assemblyKey = CompressedAssemblyInfo.GetDictionaryKey (assembly);
 					if (assemblies.ContainsKey (assemblyKey)) {
-						Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec} (arch {MonoAndroidHelper.GetAssemblyAbi(assembly)})");
+						Log.LogDebugMessage ($"Skipping duplicate assembly: {assembly.ItemSpec} (arch {MonoAndroidHelper.GetItemAbi(assembly)})");
 						continue;
 					}
 
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs
index c905016a9c3..acb45995b70 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs
@@ -8,6 +8,7 @@
 using System.Reflection.PortableExecutable;
 using System.Text;
 using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
 
 using Java.Interop.Tools.TypeNameMappings;
 using Xamarin.Android.Tools;
@@ -50,6 +51,9 @@ public class GenerateNativeApplicationConfigSources : AndroidTask
 		[Required]
 		public bool TargetsCLR { get; set; }
 
+		[Required]
+		public string AndroidBinUtilsDirectory { get; set; } = "";
+
 		public bool EnableMarshalMethods { get; set; }
 		public bool EnableManagedMarshalMethodsLookup { get; set; }
 		public string? RuntimeConfigBinFilePath { get; set; }
@@ -234,6 +238,10 @@ public override bool RunTask ()
 			}
 
 			var uniqueNativeLibraries = new List ();
+
+			// Number of DSOs that will be packaged, it may be different to the number of items in the above
+			// `uniqueNativeLibraries` list.
+			uint packagedNativeLibrariesCount = 0;
 			var seenNativeLibraryNames = new HashSet (StringComparer.OrdinalIgnoreCase);
 			if (NativeLibraries != null) {
 				foreach (ITaskItem item in NativeLibraries) {
@@ -243,12 +251,21 @@ public override bool RunTask ()
 						continue;
 					}
 
+					if (!ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec)) {
+						packagedNativeLibrariesCount++;
+					}
+
 					seenNativeLibraryNames.Add (name);
 					uniqueNativeLibraries.Add (item);
 				}
+
+				// libxamarin-app.so is not in NativeLibraries, but we must count it
+				if (!seenNativeLibraryNames.Contains ("libxamarin-app.so")) {
+					uniqueNativeLibraries.Add (new TaskItem ("libxamarin-app.so"));
+					packagedNativeLibrariesCount++;
+				}
 			}
 
-			bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
 			var jniRemappingNativeCodeInfo = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (ProjectSpecificTaskObjectKey (GenerateJniRemappingNativeCode.JniRemappingNativeCodeInfoKey), RegisteredTaskObjectLifetime.Build);
 			LLVMIR.LlvmIrComposer appConfigAsmGen;
 
@@ -262,6 +279,7 @@ public override bool RunTask ()
 					NumberOfAssembliesInApk = assemblyCount,
 					BundledAssemblyNameWidth = assemblyNameWidth,
 					NativeLibraries = uniqueNativeLibraries,
+					PackagedNativeLibrariesCount = packagedNativeLibrariesCount,
 					AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token,
 					JNIEnvInitializeToken = jnienv_initialize_method_token,
 					JNIEnvRegisterJniNativesToken = jnienv_registerjninatives_method_token,
@@ -272,6 +290,17 @@ public override bool RunTask ()
 					IgnoreSplitConfigs = ShouldIgnoreSplitConfigs (),
 				};
 			} else {
+				bool haveRuntimeConfigBlob = !String.IsNullOrEmpty (RuntimeConfigBinFilePath) && File.Exists (RuntimeConfigBinFilePath);
+				ELFEmbeddingHelper.EmbedBinary (
+					Log,
+					SupportedAbis,
+					AndroidBinUtilsDirectory,
+					RuntimeConfigBinFilePath,
+					ELFEmbeddingHelper.KnownEmbedItems.RuntimeConfig,
+					EnvironmentOutputDirectory,
+					missingContentOK: !haveRuntimeConfigBlob
+				);
+
 				appConfigAsmGen = new ApplicationConfigNativeAssemblyGenerator (environmentVariables, systemProperties, Log) {
 					UsesMonoAOT = usesMonoAOT,
 					UsesMonoLLVM = EnableLLVM,
@@ -283,11 +312,11 @@ public override bool RunTask ()
 					PackageNamingPolicy = pnp,
 					BoundExceptionType = boundExceptionType,
 					JniAddNativeMethodRegistrationAttributePresent = NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent,
-					HaveRuntimeConfigBlob = haveRuntimeConfigBlob,
 					NumberOfAssembliesInApk = assemblyCount,
 					BundledAssemblyNameWidth = assemblyNameWidth,
 					MonoComponents = (MonoComponent)monoComponents,
 					NativeLibraries = uniqueNativeLibraries,
+					PackagedNativeLibrariesCount = packagedNativeLibrariesCount,
 					HaveAssemblyStore = UseAssemblyStore,
 					AndroidRuntimeJNIEnvToken = android_runtime_jnienv_class_token,
 					JNIEnvInitializeToken = jnienv_initialize_method_token,
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
index cad5439df21..1f9148b4f9f 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/PrepareAbiItems.cs
@@ -1,5 +1,3 @@
-using System;
-using System.IO;
 using System.Collections.Generic;
 
 using Microsoft.Build.Framework;
@@ -10,13 +8,6 @@ namespace Xamarin.Android.Tasks
 {
 	public class PrepareAbiItems : AndroidTask
 	{
-		const string ArmV7a = "armeabi-v7a";
-		const string TypeMapBase = "typemaps";
-		const string EnvBase = "environment";
-		const string CompressedAssembliesBase = "compressed_assemblies";
-		const string JniRemappingBase = "jni_remap";
-		const string MarshalMethodsBase = "marshal_methods";
-
 		public override string TaskPrefix => "PAI";
 
 		[Required]
@@ -28,9 +19,6 @@ public class PrepareAbiItems : AndroidTask
 		[Required]
 		public string Mode { get; set; } = "";
 
-		[Required]
-		public bool Debug { get; set; }
-
 		[Output]
 		public ITaskItem[]? AssemblySources { get; set; }
 
@@ -40,33 +28,17 @@ public class PrepareAbiItems : AndroidTask
 		public override bool RunTask ()
 		{
 			var sources = new List ();
-			var includes = new List ();
-			string baseName;
-
-			if (String.Compare ("typemap", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
-				baseName = TypeMapBase;
-			} else if (String.Compare ("environment", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
-				baseName = EnvBase;
-			} else if (String.Compare ("compressed", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
-				baseName = CompressedAssembliesBase;
-			} else if (String.Compare ("jniremap", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
-				baseName = JniRemappingBase;
-			} else if (String.Compare ("marshal_methods", Mode, StringComparison.OrdinalIgnoreCase) == 0) {
-				baseName = MarshalMethodsBase;
-			} else {
-				Log.LogError ($"Unknown mode: {Mode}");
-				return false;
-			}
 
 			TaskItem item;
+			NativeAssemblerItemsHelper.KnownMode mode = NativeAssemblerItemsHelper.ToKnownMode (Mode);
 			foreach (string abi in BuildTargetAbis) {
-				item = new TaskItem (Path.Combine (NativeSourcesDir, $"{baseName}.{abi}.ll"));
+				item = new TaskItem (NativeAssemblerItemsHelper.GetSourcePath (Log, mode, NativeSourcesDir, abi));
 				item.SetMetadata ("abi", abi);
+				item.SetMetadata ("RuntimeIdentifier", MonoAndroidHelper.AbiToRid (abi));
 				sources.Add (item);
 			}
 
 			AssemblySources = sources.ToArray ();
-			AssemblyIncludes = includes.ToArray ();
 			return !Log.HasLoggedErrors;
 		}
 	}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
index 21f48d77025..5931e9843e5 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
@@ -129,7 +129,6 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto
 				"System.Collections.dll",
 				"System.Collections.Concurrent.dll",
 				"System.Text.RegularExpressions.dll",
-				"libarc.bin.so",
 			};
 
 			using (var b = CreateApkBuilder ()) {
@@ -797,5 +796,46 @@ void CreateEmptyFile (string path)
 			}
 		}
 
+		[Test]
+		[TestCase (false)]
+		[TestCase (true)]
+		public void CheckEmbeddedAssemblyStorePackaging (bool useCLR)
+		{
+			var proj = new XamarinAndroidApplicationProject {
+				IsRelease = true
+			};
+
+			AndroidTargetArch[] supportedArches = new[] {
+				AndroidTargetArch.Arm64,
+				AndroidTargetArch.X86_64,
+			};
+
+			proj.SetRuntimeIdentifiers (supportedArches);
+			proj.SetProperty ("AndroidUseAssemblyStore", "true");
+			proj.SetProperty ("_AndroidEmbedAssemblyStoreInRuntime", "true");
+			proj.SetProperty ("UseMonoRuntime", useCLR ? "false" : "true");
+
+			using var b = CreateApkBuilder ();
+			Assert.IsTrue (b.Build (proj), "build should have succeeded.");
+			string apk = Path.Combine (Root, b.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk");
+
+			var knownAssemblyStoreFiles = new HashSet (StringComparer.Ordinal);
+			foreach (AndroidTargetArch arch in supportedArches) {
+				string archName = MonoAndroidHelper.ArchToAbi (arch);
+				knownAssemblyStoreFiles.Add ($"lib/{archName}/libassemblies.{archName}.blob.so");
+			}
+
+			var foundAssemblyStoreFiles = new HashSet (StringComparer.Ordinal);
+			using var zip = ZipHelper.OpenZip (apk);
+
+			foreach (var entry in zip) {
+				if (knownAssemblyStoreFiles.Contains (entry.FullName)) {
+					foundAssemblyStoreFiles.Add (entry.FullName);
+				}
+			}
+
+			Assert.IsFalse (foundAssemblyStoreFiles.Count != 0, $"APK should not contain any of the files: {FoundFiles ()}");
+			string FoundFiles () => String.Join (", ", foundAssemblyStoreFiles);
+		}
 	}
 }
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs
index 7a86601ce43..7aff38ae573 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/EnvironmentHelper.cs
@@ -45,7 +45,6 @@ public sealed class ApplicationConfig
 			public bool   uses_assembly_preload;
 			public bool   broken_exception_transitions;
 			public bool   jni_add_native_method_registration_attribute_present;
-			public bool   have_runtime_config_blob;
 			public bool   have_assemblies_blob;
 			public bool   marshal_methods_enabled;
 			public bool   ignore_split_configs;
@@ -68,7 +67,7 @@ public sealed class ApplicationConfig
 			public bool   managed_marshal_methods_lookup_enabled;
 		}
 
-		const uint ApplicationConfigFieldCount = 27;
+		const uint ApplicationConfigFieldCount = 26;
 
 		const string ApplicationConfigSymbolName = "application_config";
 		const string AppEnvironmentVariablesSymbolName = "app_environment_variables";
@@ -237,107 +236,102 @@ static ApplicationConfig ReadApplicationConfig (EnvironmentFile envFile)
 						ret.jni_add_native_method_registration_attribute_present = ConvertFieldToBool ("jni_add_native_method_registration_attribute_present", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 6: // have_runtime_config_blob: bool / .byte
-						AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
-						ret.have_runtime_config_blob = ConvertFieldToBool ("have_runtime_config_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
-						break;
-
-					case 7: // have_assemblies_blob: bool / .byte
+					case 6: // have_assemblies_blob: bool / .byte
 						AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
 						ret.have_assemblies_blob = ConvertFieldToBool ("have_assemblies_blob", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 8: // marshal_methods_enabled: bool / .byte
+					case 7: // marshal_methods_enabled: bool / .byte
 						AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
 						ret.marshal_methods_enabled = ConvertFieldToBool ("marshal_methods_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 9: // ignore_split_configs: bool / .byte
+					case 8: // ignore_split_configs: bool / .byte
 						AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
 						ret.ignore_split_configs = ConvertFieldToBool ("ignore_split_configs", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 10: // bound_stream_io_exception_type: byte / .byte
+					case 9: // bound_stream_io_exception_type: byte / .byte
 						AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
 						ret.bound_stream_io_exception_type = ConvertFieldToByte ("bound_stream_io_exception_type", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 11: // package_naming_policy: uint32_t / .word | .long
+					case 10: // package_naming_policy: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.package_naming_policy = ConvertFieldToUInt32 ("package_naming_policy", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 12: // environment_variable_count: uint32_t / .word | .long
+					case 11: // environment_variable_count: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.environment_variable_count = ConvertFieldToUInt32 ("environment_variable_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 13: // system_property_count: uint32_t / .word | .long
+					case 12: // system_property_count: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.system_property_count = ConvertFieldToUInt32 ("system_property_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 14: // number_of_assemblies_in_apk: uint32_t / .word | .long
+					case 13: // number_of_assemblies_in_apk: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.number_of_assemblies_in_apk = ConvertFieldToUInt32 ("number_of_assemblies_in_apk", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 15: // bundled_assembly_name_width: uint32_t / .word | .long
+					case 14: // bundled_assembly_name_width: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.bundled_assembly_name_width = ConvertFieldToUInt32 ("bundled_assembly_name_width", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 16: // number_of_assembly_store_files: uint32_t / .word | .long
+					case 15: // number_of_assembly_store_files: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.number_of_assembly_store_files = ConvertFieldToUInt32 ("number_of_assembly_store_files", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 17: // number_of_dso_cache_entries: uint32_t / .word | .long
+					case 16: // number_of_dso_cache_entries: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.number_of_dso_cache_entries = ConvertFieldToUInt32 ("number_of_dso_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 18: // number_of_aot_cache_entries: uint32_t / .word | .long
+					case 17: // number_of_aot_cache_entries: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.number_of_aot_cache_entries = ConvertFieldToUInt32 ("number_of_aot_cache_entries", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 19: // android_runtime_jnienv_class_token: uint32_t / .word | .long
+					case 18: // android_runtime_jnienv_class_token: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.android_runtime_jnienv_class_token = ConvertFieldToUInt32 ("android_runtime_jnienv_class_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 20: // jnienv_initialize_method_token: uint32_t / .word | .long
+					case 19: // jnienv_initialize_method_token: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.jnienv_initialize_method_token = ConvertFieldToUInt32 ("jnienv_initialize_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 21: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
+					case 20: // jnienv_registerjninatives_method_token: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.jnienv_registerjninatives_method_token = ConvertFieldToUInt32 ("jnienv_registerjninatives_method_token", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 22: // jni_remapping_replacement_type_count: uint32_t / .word | .long
+					case 21: // jni_remapping_replacement_type_count: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.jni_remapping_replacement_type_count = ConvertFieldToUInt32 ("jni_remapping_replacement_type_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 23: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long
+					case 22: // jni_remapping_replacement_method_index_entry_count: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.jni_remapping_replacement_method_index_entry_count = ConvertFieldToUInt32 ("jni_remapping_replacement_method_index_entry_count", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 24: // mono_components_mask: uint32_t / .word | .long
+					case 23: // mono_components_mask: uint32_t / .word | .long
 						Assert.IsTrue (expectedUInt32Types.Contains (field [0]), $"Unexpected uint32_t field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						ret.mono_components_mask = ConvertFieldToUInt32 ("mono_components_mask", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
 
-					case 25: // android_package_name: string / [pointer type]
+					case 24: // android_package_name: string / [pointer type]
 						Assert.IsTrue (expectedPointerTypes.Contains (field [0]), $"Unexpected pointer field type in '{envFile.Path}:{item.LineNumber}': {field [0]}");
 						pointers.Add (field [1].Trim ());
 						break;
 
-					case 26: // managed_marshal_methods_lookup_enabled: bool / .byte
+					case 25: // managed_marshal_methods_lookup_enabled: bool / .byte
 						AssertFieldType (envFile.Path, parser.SourceFilePath, ".byte", field [0], item.LineNumber);
 						ret.managed_marshal_methods_lookup_enabled = ConvertFieldToBool ("managed_marshal_methods_lookup_enabled", envFile.Path, parser.SourceFilePath, item.LineNumber, field [1]);
 						break;
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
index 0d09c5250f4..53233030a42 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
@@ -5,31 +5,31 @@
       "Size": 3036
     },
     "classes.dex": {
-      "Size": 22484
+      "Size": 22488
     },
     "lib/arm64-v8a/lib__Microsoft.Android.Resource.Designer.dll.so": {
       "Size": 18288
     },
     "lib/arm64-v8a/lib_Java.Interop.dll.so": {
-      "Size": 86688
+      "Size": 87624
     },
     "lib/arm64-v8a/lib_Mono.Android.dll.so": {
-      "Size": 117712
+      "Size": 120768
     },
     "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": {
-      "Size": 22384
+      "Size": 23160
     },
     "lib/arm64-v8a/lib_System.Console.dll.so": {
-      "Size": 24392
+      "Size": 24408
     },
     "lib/arm64-v8a/lib_System.Linq.dll.so": {
-      "Size": 25336
+      "Size": 25344
     },
     "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": {
-      "Size": 628216
+      "Size": 636760
     },
     "lib/arm64-v8a/lib_System.Runtime.dll.so": {
-      "Size": 20056
+      "Size": 20096
     },
     "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": {
       "Size": 21480
@@ -37,41 +37,38 @@
     "lib/arm64-v8a/lib_UnnamedProject.dll.so": {
       "Size": 20024
     },
-    "lib/arm64-v8a/libarc.bin.so": {
-      "Size": 18872
-    },
     "lib/arm64-v8a/libmono-component-marshal-ilgen.so": {
-      "Size": 36440
+      "Size": 36616
     },
     "lib/arm64-v8a/libmonodroid.so": {
-      "Size": 1524752
+      "Size": 1525968
     },
     "lib/arm64-v8a/libmonosgen-2.0.so": {
-      "Size": 3101112
+      "Size": 3118632
     },
     "lib/arm64-v8a/libSystem.Globalization.Native.so": {
-      "Size": 71976
+      "Size": 71952
     },
     "lib/arm64-v8a/libSystem.IO.Compression.Native.so": {
-      "Size": 758896
+      "Size": 759304
     },
     "lib/arm64-v8a/libSystem.Native.so": {
-      "Size": 103520
+      "Size": 104312
     },
     "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": {
-      "Size": 165000
+      "Size": 165240
     },
     "lib/arm64-v8a/libxamarin-app.so": {
-      "Size": 19536
+      "Size": 22384
     },
     "META-INF/BNDLTOOL.RSA": {
-      "Size": 1221
+      "Size": 1223
     },
     "META-INF/BNDLTOOL.SF": {
-      "Size": 3266
+      "Size": 3167
     },
     "META-INF/MANIFEST.MF": {
-      "Size": 3139
+      "Size": 3040
     },
     "res/drawable-hdpi-v4/icon.png": {
       "Size": 2178
@@ -98,5 +95,5 @@
       "Size": 1904
     }
   },
-  "PackageSize": 3078677
+  "PackageSize": 3099084
 }
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
index cf5b327b70c..c397f8e99d2 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
@@ -5,7 +5,7 @@
       "Size": 6652
     },
     "classes.dex": {
-      "Size": 9179108
+      "Size": 9173200
     },
     "kotlin/annotation/annotation.kotlin_builtins": {
       "Size": 928
@@ -35,13 +35,13 @@
       "Size": 25424
     },
     "lib/arm64-v8a/lib_Java.Interop.dll.so": {
-      "Size": 96104
+      "Size": 96320
     },
     "lib/arm64-v8a/lib_Mono.Android.dll.so": {
-      "Size": 562336
+      "Size": 562416
     },
     "lib/arm64-v8a/lib_Mono.Android.Runtime.dll.so": {
-      "Size": 23224
+      "Size": 23200
     },
     "lib/arm64-v8a/lib_mscorlib.dll.so": {
       "Size": 21456
@@ -50,25 +50,25 @@
       "Size": 23096
     },
     "lib/arm64-v8a/lib_System.Collections.Concurrent.dll.so": {
-      "Size": 29896
+      "Size": 29904
     },
     "lib/arm64-v8a/lib_System.Collections.dll.so": {
       "Size": 36304
     },
     "lib/arm64-v8a/lib_System.Collections.NonGeneric.dll.so": {
-      "Size": 25776
+      "Size": 25784
     },
     "lib/arm64-v8a/lib_System.Collections.Specialized.dll.so": {
       "Size": 23856
     },
     "lib/arm64-v8a/lib_System.ComponentModel.dll.so": {
-      "Size": 19608
+      "Size": 19600
     },
     "lib/arm64-v8a/lib_System.ComponentModel.Primitives.dll.so": {
-      "Size": 21336
+      "Size": 21328
     },
     "lib/arm64-v8a/lib_System.ComponentModel.TypeConverter.dll.so": {
-      "Size": 42440
+      "Size": 42432
     },
     "lib/arm64-v8a/lib_System.Console.dll.so": {
       "Size": 24440
@@ -80,25 +80,25 @@
       "Size": 24704
     },
     "lib/arm64-v8a/lib_System.dll.so": {
-      "Size": 19856
+      "Size": 19864
     },
     "lib/arm64-v8a/lib_System.Drawing.dll.so": {
-      "Size": 19456
+      "Size": 19448
     },
     "lib/arm64-v8a/lib_System.Drawing.Primitives.dll.so": {
       "Size": 30064
     },
     "lib/arm64-v8a/lib_System.Formats.Asn1.dll.so": {
-      "Size": 50312
+      "Size": 50320
     },
     "lib/arm64-v8a/lib_System.IO.Compression.Brotli.dll.so": {
-      "Size": 29496
+      "Size": 29504
     },
     "lib/arm64-v8a/lib_System.IO.Compression.dll.so": {
       "Size": 33800
     },
     "lib/arm64-v8a/lib_System.IO.IsolatedStorage.dll.so": {
-      "Size": 28336
+      "Size": 28328
     },
     "lib/arm64-v8a/lib_System.Linq.dll.so": {
       "Size": 40696
@@ -107,7 +107,7 @@
       "Size": 185864
     },
     "lib/arm64-v8a/lib_System.Net.Http.dll.so": {
-      "Size": 85904
+      "Size": 85856
     },
     "lib/arm64-v8a/lib_System.Net.Primitives.dll.so": {
       "Size": 42184
@@ -116,13 +116,13 @@
       "Size": 21568
     },
     "lib/arm64-v8a/lib_System.ObjectModel.dll.so": {
-      "Size": 27088
+      "Size": 27096
     },
     "lib/arm64-v8a/lib_System.Private.CoreLib.dll.so": {
-      "Size": 967200
+      "Size": 968480
     },
     "lib/arm64-v8a/lib_System.Private.DataContractSerialization.dll.so": {
-      "Size": 216496
+      "Size": 216504
     },
     "lib/arm64-v8a/lib_System.Private.Uri.dll.so": {
       "Size": 62728
@@ -137,10 +137,10 @@
       "Size": 20264
     },
     "lib/arm64-v8a/lib_System.Runtime.InteropServices.dll.so": {
-      "Size": 21488
+      "Size": 21480
     },
     "lib/arm64-v8a/lib_System.Runtime.Numerics.dll.so": {
-      "Size": 55784
+      "Size": 55840
     },
     "lib/arm64-v8a/lib_System.Runtime.Serialization.dll.so": {
       "Size": 19376
@@ -149,16 +149,16 @@
       "Size": 20352
     },
     "lib/arm64-v8a/lib_System.Runtime.Serialization.Primitives.dll.so": {
-      "Size": 21472
+      "Size": 21464
     },
     "lib/arm64-v8a/lib_System.Security.Cryptography.dll.so": {
-      "Size": 81288
+      "Size": 81280
     },
     "lib/arm64-v8a/lib_System.Text.RegularExpressions.dll.so": {
-      "Size": 187056
+      "Size": 187120
     },
     "lib/arm64-v8a/lib_System.Xml.dll.so": {
-      "Size": 19272
+      "Size": 19264
     },
     "lib/arm64-v8a/lib_System.Xml.Linq.dll.so": {
       "Size": 19288
@@ -235,17 +235,14 @@
     "lib/arm64-v8a/lib_Xamarin.Google.Android.Material.dll.so": {
       "Size": 84912
     },
-    "lib/arm64-v8a/libarc.bin.so": {
-      "Size": 18936
-    },
     "lib/arm64-v8a/libmono-component-marshal-ilgen.so": {
-      "Size": 36600
+      "Size": 36616
     },
     "lib/arm64-v8a/libmonodroid.so": {
-      "Size": 1516584
+      "Size": 1525968
     },
     "lib/arm64-v8a/libmonosgen-2.0.so": {
-      "Size": 3110944
+      "Size": 3118632
     },
     "lib/arm64-v8a/libSystem.Globalization.Native.so": {
       "Size": 71952
@@ -254,13 +251,13 @@
       "Size": 759304
     },
     "lib/arm64-v8a/libSystem.Native.so": {
-      "Size": 103520
+      "Size": 104312
     },
     "lib/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so": {
-      "Size": 165000
+      "Size": 165240
     },
     "lib/arm64-v8a/libxamarin-app.so": {
-      "Size": 353048
+      "Size": 357088
     },
     "META-INF/androidx.activity_activity.version": {
       "Size": 6
@@ -416,7 +413,7 @@
       "Size": 1221
     },
     "META-INF/BNDLTOOL.SF": {
-      "Size": 98445
+      "Size": 98346
     },
     "META-INF/com.android.tools/proguard/coroutines.pro": {
       "Size": 1345
@@ -443,7 +440,7 @@
       "Size": 5
     },
     "META-INF/MANIFEST.MF": {
-      "Size": 98318
+      "Size": 98219
     },
     "META-INF/maven/com.google.guava/listenablefuture/pom.properties": {
       "Size": 96
@@ -2483,5 +2480,5 @@
       "Size": 812848
     }
   },
-  "PackageSize": 10955937
+  "PackageSize": 10959960
 }
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
index 40ea29ee665..f9fcecb7e44 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfig.cs
@@ -30,7 +30,6 @@ sealed class ApplicationConfig
 		public bool   uses_assembly_preload;
 		public bool   broken_exception_transitions;
 		public bool   jni_add_native_method_registration_attribute_present;
-		public bool   have_runtime_config_blob;
 		public bool   have_assemblies_blob;
 		public bool   marshal_methods_enabled;
 		public bool   ignore_split_configs;
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs
index 5844b9fb427..c45eb1fcaf6 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs
@@ -182,7 +182,6 @@ sealed class XamarinAndroidBundledAssembly
 		public bool BrokenExceptionTransitions { get; set; }
 		public global::Android.Runtime.BoundExceptionType BoundExceptionType { get; set; }
 		public bool JniAddNativeMethodRegistrationAttributePresent { get; set; }
-		public bool HaveRuntimeConfigBlob { get; set; }
 		public bool HaveAssemblyStore { get; set; }
 		public int NumberOfAssembliesInApk { get; set; }
 		public int BundledAssemblyNameWidth { get; set; } // including the trailing NUL
@@ -194,6 +193,7 @@ sealed class XamarinAndroidBundledAssembly
 		public MonoComponent MonoComponents { get; set; }
 		public PackageNamingPolicy PackageNamingPolicy { get; set; }
 		public List NativeLibraries { get; set; }
+		public uint PackagedNativeLibrariesCount { get; set; }
 		public bool MarshalMethodsEnabled { get; set; }
 		public bool ManagedMarshalMethodsLookupEnabled { get; set; }
 		public bool IgnoreSplitConfigs { get; set; }
@@ -237,7 +237,6 @@ protected override void Construct (LlvmIrModule module)
 				uses_assembly_preload = UsesAssemblyPreload,
 				broken_exception_transitions = BrokenExceptionTransitions,
 				jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent,
-				have_runtime_config_blob = HaveRuntimeConfigBlob,
 				have_assemblies_blob = HaveAssemblyStore,
 				marshal_methods_enabled = MarshalMethodsEnabled,
 				managed_marshal_methods_lookup_enabled = ManagedMarshalMethodsLookupEnabled,
@@ -247,7 +246,7 @@ protected override void Construct (LlvmIrModule module)
 				environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2),
 				system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2),
 				number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
-				number_of_shared_libraries = (uint)NativeLibraries.Count,
+				number_of_shared_libraries = PackagedNativeLibrariesCount,
 				bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
 				number_of_dso_cache_entries = (uint)dsoCache.Count,
 				number_of_aot_cache_entries = (uint)aotDsoCache.Count,
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs
index ca8bc0d5f55..88bd177000c 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs
@@ -211,6 +211,7 @@ sealed class XamarinAndroidBundledAssembly
 	public bool MarshalMethodsEnabled { get; set; }
 	public bool ManagedMarshalMethodsLookupEnabled { get; set; }
 	public bool IgnoreSplitConfigs { get; set; }
+	public uint PackagedNativeLibrariesCount { get; set; }
 
 	public ApplicationConfigNativeAssemblyGeneratorCLR (IDictionary environmentVariables, IDictionary systemProperties,
 		IDictionary? runtimeProperties, TaskLoggingHelper log)
@@ -266,7 +267,7 @@ protected override void Construct (LlvmIrModule module)
 			environment_variable_count = (uint)(environmentVariables == null ? 0 : environmentVariables.Count * 2),
 			system_property_count = (uint)(systemProperties == null ? 0 : systemProperties.Count * 2),
 			number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
-			number_of_shared_libraries = (uint)NativeLibraries.Count,
+			number_of_shared_libraries = PackagedNativeLibrariesCount,
 			bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
 			number_of_dso_cache_entries = (uint)dsoCache.Count,
 			number_of_aot_cache_entries = (uint)aotDsoCache.Count,
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs
index 9ef32a4dcb8..71da2ab254a 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyCompression.cs
@@ -170,7 +170,7 @@ static string GetCompressedAssemblyOutputDirectory (ITaskItem assembly, string c
 		{
 			string assemblyOutputDir;
 			string subDirectory = assembly.GetMetadata ("DestinationSubDirectory");
-			string abi = MonoAndroidHelper.GetAssemblyAbi (assembly);
+			string abi = MonoAndroidHelper.GetItemAbi (assembly);
 
 			if (!string.IsNullOrEmpty (subDirectory) && !(subDirectory.EndsWith ($"{abi}/", StringComparison.Ordinal) || subDirectory.EndsWith ($"{abi}\\", StringComparison.Ordinal))) {
 				assemblyOutputDir = Path.Combine (compressedOutputDir, abi, subDirectory);
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs
index 1599a8e581e..572af36521b 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyPackagingHelper.cs
@@ -10,20 +10,52 @@ namespace Xamarin.Android.Tasks;
 
 static class AssemblyPackagingHelper
 {
+	public static bool ShouldSkipAssembly (TaskLoggingHelper log, ITaskItem asm)
+	{
+		var should_skip = asm.GetMetadataOrDefault ("AndroidSkipAddToPackage", false);
+
+		if (should_skip) {
+			log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' ");
+		}
+
+		return should_skip;
+	}
+
+	public static Dictionary CreateAssemblyStore (TaskLoggingHelper log, IEnumerable assemblies, string outputDir, string[] supportedAbis, bool includeDebugSymbols)
+	{
+		var storeBuilder = new AssemblyStoreBuilder (log);
+		var per_arch_assemblies = MonoAndroidHelper.GetPerArchAssemblies (assemblies, supportedAbis, true);
+
+		foreach (var kvp in per_arch_assemblies) {
+			log.LogDebugMessage ($"Adding assemblies for architecture '{kvp.Key}'");
+
+			foreach (var assembly in kvp.Value.Values) {
+				var sourcePath = assembly.GetMetadataOrDefault ("CompressedAssembly", assembly.ItemSpec);
+				storeBuilder.AddAssembly (sourcePath, assembly, includeDebugSymbols: includeDebugSymbols);
+
+				log.LogDebugMessage ($"Added '{sourcePath}' to assembly store.");
+			}
+		}
+
+		Dictionary assemblyStorePaths = storeBuilder.Generate (outputDir);
+		if (assemblyStorePaths.Count == 0) {
+			throw new InvalidOperationException ("Assembly store generator did not generate any stores");
+		}
+
+		if (assemblyStorePaths.Count != supportedAbis.Length) {
+			throw new InvalidOperationException ("Internal error: assembly store did not generate store for each supported ABI");
+		}
+
+		return assemblyStorePaths;
+	}
+
 	public static void AddAssembliesFromCollection (TaskLoggingHelper Log, ICollection SupportedAbis, ICollection assemblies, Action doAddAssembly)
 	{
 		Dictionary> perArchAssemblies = MonoAndroidHelper.GetPerArchAssemblies (
 			assemblies,
 			SupportedAbis,
 			validate: true,
-			shouldSkip: (ITaskItem asm) => {
-				if (bool.TryParse (asm.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) {
-					Log.LogDebugMessage ($"Skipping {asm.ItemSpec} due to 'AndroidSkipAddToPackage' == 'true' ");
-					return true;
-				}
-
-				return false;
-			}
+			shouldSkip: (ITaskItem asm) => ShouldSkipAssembly (Log, asm)
 		);
 
 		foreach (var kvp in perArchAssemblies) {
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs
index 024f2cf0ac2..35c355a5872 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/AssemblyStoreGenerator.cs
@@ -104,7 +104,9 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List ();
 		var descriptors = new List ();
 		ulong namesSize = 0;
@@ -118,7 +120,8 @@ string Generate (string baseOutputDirectory, AndroidTargetArch arch, List {
 			"--add-section",
 			$"payload={MonoAndroidHelper.QuoteFileNameArgument (payloadFilePath)}",
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFEmbeddingHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFEmbeddingHelper.cs
new file mode 100644
index 00000000000..7481164a829
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFEmbeddingHelper.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+class ELFEmbeddingHelper
+{
+	public sealed class EmbedItem
+	{
+		public readonly string SymbolName;
+		public readonly string BaseFileName;
+		public readonly NativeAssemblerItemsHelper.KnownMode NativeAssemblerMode;
+
+		public EmbedItem (string symbolName, string baseFileName, NativeAssemblerItemsHelper.KnownMode nativeAssemblerMode)
+		{
+			SymbolName = symbolName;
+			BaseFileName = baseFileName;
+			NativeAssemblerMode = nativeAssemblerMode;
+		}
+	}
+
+	public static class KnownEmbedItems
+	{
+		public static readonly EmbedItem RuntimeConfig = new ("embedded_runtime_config", "runtime_config", NativeAssemblerItemsHelper.KnownMode.EmbeddedRuntimeConfig);
+		public static readonly EmbedItem AssemblyStore = new ("embedded_assembly_store", "assembly_store", NativeAssemblerItemsHelper.KnownMode.EmbeddedAssemblyStore);
+	}
+
+	static readonly Encoding asmFileEncoding = new UTF8Encoding (false);
+
+	public static void EmbedBinary (
+		TaskLoggingHelper log,
+		ICollection supportedAbis,
+		string androidBinUtilsDirectory,
+		string? inputFile,
+		EmbedItem embedItem,
+		string outputDirectory,
+		bool missingContentOK)
+	{
+		if (supportedAbis.Count < 1) {
+			log.LogDebugMessage ("ELFEmbeddingHelper: at least one target ABI must be specified. Probably a DTB build, skipping generation.");
+			return;
+		}
+
+		foreach (string abi in supportedAbis) {
+			DoEmbed (
+				log,
+				MonoAndroidHelper.AbiToTargetArch (abi),
+				inputFile, outputDirectory,
+				embedItem,
+				missingContentOK
+			);
+		}
+	}
+
+	public static void EmbedBinary (
+		TaskLoggingHelper log,
+		string abi,
+		string androidBinUtilsDirectory,
+		string? inputFile,
+		EmbedItem embedItem,
+		string outputDirectory,
+		bool missingContentOK)
+	{
+		if (String.IsNullOrEmpty (abi)) {
+			log.LogDebugMessage ("ELFEmbeddingHelper: ABI must be specified. Probably a DTB build, skipping generation.");
+			return;
+		}
+
+		DoEmbed (
+			log,
+			MonoAndroidHelper.AbiToTargetArch (abi),
+			inputFile,
+			outputDirectory,
+			embedItem,
+			missingContentOK
+		);
+	}
+
+	static void DoEmbed (
+		TaskLoggingHelper log,
+		AndroidTargetArch arch,
+		string? inputFile,
+		string outputDirectory,
+		EmbedItem item,
+		bool missingContentOK)
+	{
+		NativeAssemblerCompilation.LlvmMcTargetConfig cfg = NativeAssemblerCompilation.GetLlvmMcConfig (arch);
+
+		bool haveInputFile = !String.IsNullOrEmpty (inputFile);
+		if (!haveInputFile) {
+			if (!missingContentOK) {
+				throw new InvalidOperationException ("Internal error: input file must be specified");
+			}
+		} else {
+			inputFile = Path.GetFullPath (inputFile);
+		}
+
+		long inputFileSize = 0;
+		string? sanitizedInputFilePath = null;
+
+		if (haveInputFile) {
+			var fi = new FileInfo (inputFile);
+			if (fi.Exists) {
+				inputFileSize = fi.Length;
+				sanitizedInputFilePath = inputFile!.Replace ("\\", "\\\\");
+			} else if (!missingContentOK) {
+				throw new InvalidOperationException ($"Internal error: input file '{inputFile}' does not exist");
+			}
+		}
+
+		string? asmSourceFile = NativeAssemblerItemsHelper.GetSourcePath (log, item.NativeAssemblerMode, outputDirectory, arch);
+		if (String.IsNullOrEmpty (asmSourceFile)) {
+			log.LogError ("Unable to embed a binary file in native assembly, no assembly source path given.");
+			return;
+		}
+
+		Directory.CreateDirectory (Path.GetDirectoryName (asmSourceFile));
+		using var fs = File.Open (asmSourceFile, FileMode.Create, FileAccess.Write, FileShare.Read);
+		using var sw = new StreamWriter (fs, asmFileEncoding);
+
+		string symbolName = item.SymbolName;
+		sw.WriteLine ($".section .rodata,\"a\",{cfg.AssemblerDirectivePrefix}progbits");
+		sw.WriteLine (".p2align 3, 0x00"); // Put the data at the 4k boundary
+		sw.WriteLine ();
+		sw.WriteLine ($".global {symbolName}");
+		sw.WriteLine ($".type {symbolName},{cfg.AssemblerDirectivePrefix}object");
+		sw.WriteLine ($"{symbolName}:");
+
+		if (!String.IsNullOrEmpty (sanitizedInputFilePath)) {
+			sw.WriteLine ($"\t.incbin \"{sanitizedInputFilePath}\"");
+		}
+		sw.WriteLine ($"\t.size {symbolName}, {inputFileSize}");
+		sw.WriteLine ();
+
+		symbolName += "_size";
+		sw.WriteLine ($".global {symbolName}");
+		sw.WriteLine ($"{symbolName}:");
+		sw.WriteLine ($"\t{cfg.SizeType}\t{inputFileSize}");
+		sw.WriteLine ($"\t.size {symbolName}, {cfg.WordSize}");
+
+		sw.Flush ();
+		sw.Close ();
+	}
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.Basic.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.Basic.cs
new file mode 100644
index 00000000000..df401b2f462
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.Basic.cs
@@ -0,0 +1,61 @@
+using System;
+
+using ELFSharp.ELF;
+using ELFSharp.ELF.Sections;
+
+namespace Xamarin.Android.Tasks;
+
+static partial class ELFHelper
+{
+	public static ISymbolTable? GetSymbolTable (IELF elf, string sectionName)
+	{
+		ISection? section = GetSection (elf, sectionName);
+		if (section == null) {
+			return null;
+		}
+
+		var symtab = section as ISymbolTable;
+		if (symtab == null) {
+			return null;
+		}
+
+		return symtab;
+	}
+
+	public static ISection? GetSection (IELF elf, string sectionName)
+	{
+		if (!elf.TryGetSection (sectionName, out ISection section)) {
+			return null;
+		}
+
+		return section;
+	}
+
+	public static SymbolEntry? FindSymbol (ISymbolTable? symbolTable, string symbolName) where T: struct
+	{
+		if (symbolTable == null) {
+			return null;
+		}
+
+		ISymbolEntry? symbol = null;
+		foreach (ISymbolEntry entry in symbolTable.Entries) {
+			if (String.Compare (entry.Name, symbolName, StringComparison.Ordinal) != 0) {
+				continue;
+			}
+
+			symbol = entry;
+			break;
+		}
+
+		if (symbol == null) {
+			return null;
+		}
+
+		Type t = typeof(T);
+		if (t == typeof(ulong) || t == typeof(uint)) {
+			return (SymbolEntry)symbol;
+		}
+
+		throw new InvalidOperationException ($"Only `ulong` and `uint` types are accepted");
+	}
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
index 5c4879dc323..f51b9fb30b9 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ELFHelper.cs
@@ -16,7 +16,7 @@
 
 namespace Xamarin.Android.Tasks
 {
-	static class ELFHelper
+	static partial class ELFHelper
 	{
 		public static void AssertValidLibraryAlignment (TaskLoggingHelper log, int alignmentInPages, string path, ITaskItem? item)
 		{
@@ -226,29 +226,5 @@ bool IsNonEmptyCodeSymbol (SymbolEntry? symbolEntry) where T : struct
 				return size != 0 && symbolEntry.PointedSection.Type == ELFSectionType.ProgBits;
 			}
 		}
-
-		static ISymbolTable? GetSymbolTable (IELF elf, string sectionName)
-		{
-			ISection? section = GetSection (elf, sectionName);
-			if (section == null) {
-				return null;
-			}
-
-			var symtab = section as ISymbolTable;
-			if (symtab == null) {
-				return null;
-			}
-
-			return symtab;
-		}
-
-		static ISection? GetSection (IELF elf, string sectionName)
-		{
-			if (!elf.TryGetSection (sectionName, out ISection section)) {
-				return null;
-			}
-
-			return section;
-		}
 	}
 }
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
index 416ccb3f78f..48369483e06 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
@@ -290,7 +290,7 @@ public IList Merge (TaskLoggingHelper log, TypeDefinitionCache cache, Li
 					string.IsNullOrEmpty (VersionName) ? "1.0" : VersionName);
 			}
 
-			app = CreateApplicationElement (manifest, applicationClass, subclasses, cache);
+			app = CreateApplicationElement (log, manifest, applicationClass, subclasses, cache);
 
 			if (app.Attribute (androidNs + "label") == null && !string.IsNullOrEmpty (ApplicationLabel))
 				app.SetAttributeValue (androidNs + "label", ApplicationLabel);
@@ -570,7 +570,7 @@ Func GetGenerator (T
 			return null;
 		}
 
-		XElement CreateApplicationElement (XElement manifest, string applicationClass, List subclasses, TypeDefinitionCache cache)
+		XElement CreateApplicationElement (TaskLoggingHelper log, XElement manifest, string applicationClass, List subclasses, TypeDefinitionCache cache)
 		{
 			var application = manifest.Descendants ("application").FirstOrDefault ();
 
@@ -581,6 +581,11 @@ XElement CreateApplicationElement (XElement manifest, string applicationClass, L
 			List usesConfigurationAttr = [];
 			foreach (var assemblyPath in Assemblies) {
 				var assembly = Resolver.GetAssembly (assemblyPath);
+				if (assembly == null) {
+					log.LogDebugMessage ($"Assembly '{assemblyPath}' not found.");
+					continue;
+				}
+
 				if (ApplicationAttribute.FromCustomAttributeProvider (assembly, cache) is ApplicationAttribute a) {
 					assemblyAttr.Add (a);
 				}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
index 518477d9ec4..6e49b7973af 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
@@ -568,17 +568,17 @@ public static int ConvertSupportedOSPlatformVersionToApiLevel (string version)
 		}
 
 #if MSBUILD
-		public static string GetAssemblyAbi (ITaskItem asmItem)
+		public static string GetItemAbi (ITaskItem asmItem)
 		{
 			string? abi = asmItem.GetMetadata ("Abi");
 			if (String.IsNullOrEmpty (abi)) {
-				throw new InvalidOperationException ($"Internal error: assembly '{asmItem}' lacks ABI metadata");
+				throw new InvalidOperationException ($"Internal error: item '{asmItem}' lacks ABI metadata");
 			}
 
 			return abi;
 		}
 
-		public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) => AbiToTargetArch (GetAssemblyAbi (asmItem));
+		public static AndroidTargetArch GetTargetArch (ITaskItem asmItem) => AbiToTargetArch (GetItemAbi (asmItem));
 
 
 		public static AndroidTargetArch GetRequiredValidArchitecture (ITaskItem item)
@@ -783,6 +783,15 @@ public static string QuoteFileNameArgument (string? fileName)
 			return builder.ToString ();
 		}
 
+		public static string GetLlvmObjcopyPath (string androidBinUtilsDirectory) => GetBinUtilsToolPath (androidBinUtilsDirectory, "llvm-objcopy");
+		public static string GetLlvmMcPath (string androidBinUtilsDirectory) => GetBinUtilsToolPath (androidBinUtilsDirectory, "llvm-mc");
+		public static string GetLlvmLlcPath (string androidBinUtilsDirectory) => GetBinUtilsToolPath (androidBinUtilsDirectory, "llc");
+
+		static string GetBinUtilsToolPath (string androidBinUtilsDirectory, string toolName)
+		{
+			return Path.Combine (androidBinUtilsDirectory, MonoAndroidHelper.GetExecutablePath (androidBinUtilsDirectory, toolName));
+		}
+
 		public static AndroidRuntime ParseAndroidRuntime (string androidRuntime)
 		{
 			if (string.Equals (androidRuntime, "CoreCLR", StringComparison.OrdinalIgnoreCase))
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerCompilation.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerCompilation.cs
new file mode 100644
index 00000000000..6e4c08478ce
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerCompilation.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+class NativeAssemblerCompilation
+{
+	public sealed class AssemblerConfig
+	{
+		public readonly string ExecutablePath;
+		public readonly string Options;
+		public readonly string InputSource;
+
+		public AssemblerConfig (string executablePath, string options, string inputSource)
+		{
+			ExecutablePath = executablePath;
+			Options = options;
+			InputSource = inputSource;
+		}
+	}
+
+	public sealed class AssemblerRunContext
+	{
+		public readonly TaskLoggingHelper Log;
+		public readonly Action? RegisterForCancellation;
+		public readonly Action? Cancel;
+		public readonly string? WorkingDirectory;
+
+		public AssemblerRunContext (TaskLoggingHelper log, string? workingDirectory = null, Action? registerForCancellation = null, Action? cancel = null)
+		{
+			Log = log;
+			RegisterForCancellation = registerForCancellation;
+			Cancel = cancel;
+			WorkingDirectory = workingDirectory;
+		}
+	}
+
+	public sealed class LlvmMcTargetConfig
+	{
+		public readonly string TargetArch;
+		public readonly string TripleArch;
+		public readonly string TripleApiPrefix;
+		public readonly string AssemblerDirectivePrefix;
+		public readonly string SizeType;
+		public readonly uint WordSize;
+
+		public LlvmMcTargetConfig (string targetArch, string tripleArch, string tripleApiPrefix, string assemblerDirectivePrefix, string sizeType, uint wordSize)
+		{
+			TargetArch = targetArch;
+			TripleArch = tripleArch;
+			TripleApiPrefix = tripleApiPrefix;
+			AssemblerDirectivePrefix = assemblerDirectivePrefix;
+			SizeType = sizeType;
+			WordSize = wordSize;
+		}
+	}
+
+	static readonly Dictionary llvmMcConfigs = new () {
+		{ AndroidTargetArch.Arm64,  new ("aarch64", "aarch64", "android",     "@", ".xword", 8) },
+		{ AndroidTargetArch.Arm,    new ("arm",     "armv7a",  "androideabi", "%", ".long",  4) },
+		{ AndroidTargetArch.X86_64, new ("x86-64",  "x86_64",  "android",     "@", ".quad",  8) },
+		{ AndroidTargetArch.X86,    new ("x86",     "i686",    "android",     "@", ".long",  4) },
+	};
+
+	static readonly List llcArguments = new () {
+		"-O2",
+		"--debugger-tune=lldb", // NDK uses lldb now
+		"--debugify-level=location+variables",
+		"--fatal-warnings",
+		"--filetype=obj",
+		"--relocation-model=pic",
+	};
+
+	static readonly List llvmMcArguments = new () {
+		"--assemble",
+		"--filetype=obj",
+		"-g",
+	};
+
+	public static AssemblerConfig GetAssemblerConfig (string androidBinUtilsDir, ITaskItem source, bool stripFilePaths)
+	{
+		string sourceFile = stripFilePaths ? Path.GetFileName (source.ItemSpec) : source.ItemSpec;
+		string sourceExtension = Path.GetExtension (sourceFile);
+		string executable;
+		var arguments = new List ();
+
+		if (String.Compare (".ll", sourceExtension, StringComparison.OrdinalIgnoreCase) == 0) {
+			executable = MonoAndroidHelper.GetLlvmLlcPath (androidBinUtilsDir);
+			arguments.AddRange (llcArguments);
+		} else if (String.Compare (".s", sourceExtension, StringComparison.OrdinalIgnoreCase) == 0) {
+			executable = MonoAndroidHelper.GetLlvmMcPath (androidBinUtilsDir);
+			arguments.AddRange (llvmMcArguments);
+
+			LlvmMcTargetConfig cfg = GetLlvmMcConfig (MonoAndroidHelper.GetTargetArch (source));
+			arguments.Add ($"--arch={cfg.TargetArch}");
+			arguments.Add ($"--triple={cfg.TripleArch}-linux-{cfg.TripleApiPrefix}{XABuildConfig.AndroidMinimumDotNetApiLevel}");
+		} else {
+			throw new InvalidOperationException ($"Internal exception: unknown native assembler source {source.ItemSpec}");
+		}
+
+		string outputFile = Path.ChangeExtension (sourceFile, ".o");
+		arguments.Add ("-o");
+		arguments.Add (MonoAndroidHelper.QuoteFileNameArgument (outputFile));
+		arguments.Add (MonoAndroidHelper.QuoteFileNameArgument (sourceFile));
+
+		return new AssemblerConfig (executable, String.Join (" ", arguments), source.ItemSpec);
+	}
+
+	public static LlvmMcTargetConfig GetLlvmMcConfig (AndroidTargetArch arch)
+	{
+		if (!llvmMcConfigs.TryGetValue (arch, out LlvmMcTargetConfig cfg)) {
+			throw new NotSupportedException ($"Internal error: unsupported target arch '{arch}'");
+		}
+
+		return cfg;
+	}
+
+	public static void RunAssembler (AssemblerRunContext context, AssemblerConfig config)
+	{
+		var stdout_completed = new ManualResetEvent (false);
+		var stderr_completed = new ManualResetEvent (false);
+		var psi = new ProcessStartInfo () {
+			FileName = config.ExecutablePath,
+			Arguments = config.Options,
+			UseShellExecute = false,
+			RedirectStandardOutput = true,
+			RedirectStandardError = true,
+			CreateNoWindow = true,
+			WindowStyle = ProcessWindowStyle.Hidden,
+			WorkingDirectory = context.WorkingDirectory,
+		};
+
+		string assemblerName = Path.GetFileName (config.ExecutablePath);
+		context.Log.LogDebugMessage ($"[{assemblerName}] {psi.FileName} {psi.Arguments}");
+
+		var stdoutLines = new List ();
+		var stderrLines = new List ();
+
+		using var proc = new Process ();
+		proc.OutputDataReceived += (s, e) => {
+			if (e.Data != null) {
+				OnOutputData (context, assemblerName, s, e);
+				stdoutLines.Add (e.Data);
+			} else {
+				stdout_completed.Set ();
+			}
+		};
+
+		proc.ErrorDataReceived += (s, e) => {
+			if (e.Data != null) {
+				OnErrorData (context, assemblerName, s, e);
+				stderrLines.Add (e.Data);
+			} else {
+				stderr_completed.Set ();
+			}
+		};
+
+		proc.StartInfo = psi;
+		proc.Start ();
+		proc.BeginOutputReadLine ();
+		proc.BeginErrorReadLine ();
+		context.RegisterForCancellation?.Invoke (proc);
+
+		proc.WaitForExit ();
+
+		if (psi.RedirectStandardError) {
+			stderr_completed.WaitOne (TimeSpan.FromSeconds (30));
+		}
+
+		if (psi.RedirectStandardOutput) {
+			stdout_completed.WaitOne (TimeSpan.FromSeconds (30));
+		}
+
+		if (proc.ExitCode != 0) {
+			var sb = MonoAndroidHelper.MergeStdoutAndStderrMessages (stdoutLines, stderrLines);
+			context.Log.LogCodedError ("XA3006", Properties.Resources.XA3006, Path.GetFileName (config.InputSource), sb.ToString ());
+			context.Cancel?.Invoke ();
+		}
+	}
+
+	static void OnOutputData (AssemblerRunContext context, string assemblerName, object sender, DataReceivedEventArgs e)
+	{
+		context.Log.LogDebugMessage ($"[{assemblerName} stdout] {e.Data}");
+	}
+
+	static void OnErrorData (AssemblerRunContext context, string assemblerName, object sender, DataReceivedEventArgs e)
+	{
+		context.Log.LogMessage ($"[{assemblerName} stderr] {e.Data}", MessageImportance.High);
+	}
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerItemsHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerItemsHelper.cs
new file mode 100644
index 00000000000..6781f2461eb
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/NativeAssemblerItemsHelper.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+using Microsoft.Build.Utilities;
+using Xamarin.Android.Tools;
+
+namespace Xamarin.Android.Tasks;
+
+static class NativeAssemblerItemsHelper
+{
+	public enum KnownMode
+	{
+		CompressedAssemblies,
+		EmbeddedAssemblyStore,
+		EmbeddedRuntimeConfig,
+		Environment,
+		JNIRemap,
+		MarshalMethods,
+		TypeMap,
+	}
+
+	sealed class ModeConfig
+	{
+		public readonly string FileNameBase;
+		public readonly string Extension;
+
+		public ModeConfig (string fileNameBase, string extension)
+		{
+			FileNameBase = fileNameBase;
+			Extension = extension;
+		}
+	}
+
+	const string LlvmIrExtension = "ll";
+	const string NativeAssemblerExtension = "s";
+
+	static readonly Dictionary ModeConfigs = new () {
+		{ KnownMode.CompressedAssemblies,  new ("compressed_assemblies", LlvmIrExtension) },
+		{ KnownMode.EmbeddedAssemblyStore, new ("embed_assembly_store",  NativeAssemblerExtension) },
+		{ KnownMode.EmbeddedRuntimeConfig, new ("embed_runtime_config",  NativeAssemblerExtension) },
+		{ KnownMode.Environment,           new ("environment",           LlvmIrExtension) },
+		{ KnownMode.JNIRemap,              new ("jni_remap",             LlvmIrExtension) },
+		{ KnownMode.MarshalMethods,        new ("marshal_methods",       LlvmIrExtension) },
+		{ KnownMode.TypeMap,               new ("typemaps",              LlvmIrExtension) },
+	};
+
+	public static string? GetSourcePath (TaskLoggingHelper log, KnownMode mode, string nativeSourcesDir, AndroidTargetArch arch)
+	{
+		return GetSourcePath (log, mode, nativeSourcesDir, MonoAndroidHelper.ArchToAbi (arch));
+	}
+
+	public static string? GetSourcePath (TaskLoggingHelper log, KnownMode mode, string nativeSourcesDir, string abi)
+	{
+		if (!ModeConfigs.TryGetValue (mode, out ModeConfig config)) {
+			log.LogError ($"Unknown mode: {mode}");
+			return null;
+		}
+
+		return Path.Combine (nativeSourcesDir, $"{config.FileNameBase}.{abi.ToLowerInvariant ()}.{config.Extension}");
+	}
+
+	public static KnownMode ToKnownMode (string mode)
+	{
+		if (!Enum.TryParse (mode, ignoreCase: true, out KnownMode result)) {
+			throw new InvalidOperationException ($"Internal exception: uknown native assembler generator mode '{mode}'");
+		}
+
+		return result;
+	}
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index 6421833e60e..d5289830858 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -47,7 +47,6 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
 
 
 
-
 
 
 
@@ -312,6 +311,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
 	<_AndroidFastDeployEnvironmentFiles Condition=" '$(_AndroidFastDeployEnvironmentFiles)' == '' And '$(EmbedAssembliesIntoApk)' == 'False' ">True
 	<_AndroidFastDeployEnvironmentFiles Condition=" '$(_AndroidFastDeployEnvironmentFiles)' == '' ">False
 
+  <_AndroidCompressedAssembliesDir>$(IntermediateOutputPath)android\lz4
   <_AndroidUseCLR Condition=" '$(_AndroidRuntime)' == 'CoreCLR' ">True
   <_AndroidUseCLR Condition=" '$(_AndroidRuntime)' != 'CoreCLR' ">False
 
@@ -353,6 +353,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
 
 
 
+  <_EmbedAssemblyStoreInRuntime Condition=" '$(_AndroidUseAssemblyStore)' == 'True' And '$(_AndroidEmbedAssemblyStoreInRuntime)' == 'True' ">true
+  <_EmbedAssemblyStoreInRuntime Condition=" '$(_EmbedAssemblyStoreInRuntime)' != 'true' ">false
+
   <_AndroidAotStripLibraries Condition=" '$(_AndroidAotStripLibraries)' == '' And '$(AndroidIncludeDebugSymbols)' != 'true' ">True
   True
   False
@@ -363,6 +366,9 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
   <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' ">False
   <_AndroidUseMarshalMethods Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">$(AndroidEnableMarshalMethods)
 
+  <_AndroidEmbedAssemblyStoreInRuntime Condition=" '$(AndroidUseAssemblyStore)' == 'True' And '$(EmbedAssembliesIntoApk)' == 'true' And '$(_AndroidEmbedAssemblyStoreInRuntime)' == '' ">True
+  <_AndroidEmbedAssemblyStoreInRuntime Condition="'$(_AndroidEmbedAssemblyStoreInRuntime)' == '' ">False
+
   <_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' and '$(_AndroidUseMarshalMethods)' == 'True' and '$(_AndroidRuntime)' != 'MonoVM' ">True
   <_AndroidUseManagedMarshalMethodsLookup Condition=" '$(_AndroidUseManagedMarshalMethodsLookup)' == '' ">False
 
@@ -1536,10 +1542,8 @@ because xbuild doesn't support framework reference assemblies.
   
+      Mode="TypeMap">
     
-    
   
 
 
@@ -1682,7 +1686,6 @@ because xbuild doesn't support framework reference assemblies.
 
   
     
-    
     
     
   
@@ -1754,24 +1757,28 @@ because xbuild doesn't support framework reference assemblies.
   
+    Mode="Environment">
       
   
   
+    Mode="CompressedAssemblies">
       
   
   
+    Mode="MarshalMethods">
       
   
+  
+      
+  
 
 
 
@@ -1796,8 +1803,7 @@ because xbuild doesn't support framework reference assemblies.
   
+      Mode="JNIRemap">
     
   
 
@@ -1869,6 +1875,7 @@ because xbuild doesn't support framework reference assemblies.
 
   
     <_GeneratePackageManagerJavaInputs Include="@(_GenerateJavaStubsInputs)" />
+    <_GeneratePackageManagerJavaInputs Include="$(_BinaryRuntimeConfigPath)" />
   
 
 
@@ -1877,7 +1884,7 @@ because xbuild doesn't support framework reference assemblies.
   DependsOnTargets="$(_GeneratePackageManagerJavaDependsOn)"
   Inputs="@(_GeneratePackageManagerJavaInputs)"
   Outputs="$(_AndroidStampDirectory)_GeneratePackageManagerJava.stamp">
-  
+
   
   
+      ProjectRuntimeConfigFilePath="$(ProjectRuntimeConfigFilePath)"
+      AndroidBinUtilsDirectory="$(AndroidBinUtilsDirectory)">
   
 
   
   
-      
+
   
   
     
+    
   
 
 
@@ -2039,6 +2048,7 @@ because xbuild doesn't support framework reference assemblies.
 
 	<_CompileToDalvikDependsOnTargets>
 		_CompileJava;
+		_RemoveRegisterAttribute;
 		_CreateApplicationSharedLibraries;
 		_GetMonoPlatformJarPath;
 		_GetLibraryImports;
@@ -2131,7 +2141,7 @@ because xbuild doesn't support framework reference assemblies.
    
 
 
-
+
   
     <_NativeAssemblyTarget Include="@(_TypeMapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
       %(_TypeMapAssemblySource.abi)
@@ -2151,6 +2161,22 @@ because xbuild doesn't support framework reference assemblies.
     <_NativeAssemblyTarget Include="@(_AndroidRemapAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
       %(_AndroidRemapAssemblySource.abi)
     
+    <_NativeAssemblyTarget Include="@(_EmbeddedAssemblyStoreSourceFiles->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+      %(_EmbeddedAssemblyStoreSourceFiles.abi)
+    
+    <_NativeAssemblyTarget Include="@(_EmbeddedRuntimeConfigAssemblySource->'$([System.IO.Path]::ChangeExtension('%(Identity)', '.o'))')">
+      %(_EmbeddedRuntimeConfigAssemblySource.abi)
+    
+  
+
+  
+    <_NativeAssemblySource Include="@(_AndroidRemapAssemblySource)" />
+    <_NativeAssemblySource Include="@(_CompressedAssembliesAssemblySource)" />
+    <_NativeAssemblySource Include="@(_EmbeddedAssemblyStoreSourceFiles)" />
+    <_NativeAssemblySource Include="@(_EmbeddedRuntimeConfigAssemblySource)" />
+    <_NativeAssemblySource Include="@(_EnvironmentAssemblySource)" />
+    <_NativeAssemblySource Include="@(_MarshalMethodsAssemblySource)" />
+    <_NativeAssemblySource Include="@(_TypeMapAssemblySource)" />
   
 
 
@@ -2169,11 +2195,11 @@ because xbuild doesn't support framework reference assemblies.
 
 
 
   
   
 
   
   
 
-  
-  
-    
-  
-
   
   
 
-  
-  
-    
-  
-
   (payload_start);
-
-	auto get_full_store_path = [&apk_path, &store_path]() -> std::string {
-		std::string full_store_path;
-
-		if (!apk_path.empty ()) {
-			full_store_path.append (apk_path);
-			// store path will be relative, to the apk
-			full_store_path.append ("!/"sv);
-			full_store_path.append (store_path);
-		} else {
-			full_store_path.append (store_path);
-		}
-
-		return full_store_path;
-	};
+	auto header = static_cast(data_start);
 
 	if (header->magic != ASSEMBLY_STORE_MAGIC) {
 		Helpers::abort_application (
 			LOG_ASSEMBLY,
 			std::format (
 				"Assembly store '{}' is not a valid .NET for Android assembly store file"sv,
-				get_full_store_path ()
+				name
 			)
 		);
 	}
@@ -274,7 +256,7 @@ void AssemblyStore::map (int fd, std::string_view const& apk_path, std::string_v
 			LOG_ASSEMBLY,
 			std::format (
 				"Assembly store '{}' uses format version {:x}, instead of the expected {:x}"sv,
-				get_full_store_path (),
+				name,
 				header->version,
 				ASSEMBLY_STORE_FORMAT_VERSION
 			)
@@ -283,11 +265,35 @@ void AssemblyStore::map (int fd, std::string_view const& apk_path, std::string_v
 
 	constexpr size_t header_size = sizeof(AssemblyStoreHeader);
 
-	assembly_store.data_start = static_cast(payload_start);
+	assembly_store.data_start = static_cast(data_start);
 	assembly_store.assembly_count = header->entry_count;
 	assembly_store.index_entry_count = header->index_entry_count;
 	assembly_store.assemblies = reinterpret_cast(assembly_store.data_start + header_size + header->index_size);
 	assembly_store_hashes = reinterpret_cast(assembly_store.data_start + header_size);
+}
+
+void AssemblyStore::map (int fd, std::string_view const& apk_path, std::string_view const& store_path, uint32_t offset, uint32_t size) noexcept
+{
+	detail::mmap_info assembly_store_map = Util::mmap_file (fd, offset, size, store_path);
+	auto [payload_start, payload_size] = Util::get_wrapper_dso_payload_pointer_and_size (assembly_store_map, store_path);
+	log_debug (LOG_ASSEMBLY, "Adjusted assembly store pointer: {:p}; size: {}"sv, payload_start, payload_size);
 
-	log_debug (LOG_ASSEMBLY, "Mapped assembly store {}"sv, get_full_store_path ());
+	std::string full_store_path;
+	if (!apk_path.empty ()) {
+		full_store_path.append (apk_path);
+		// store path will be relative, to the apk
+		full_store_path.append ("!/"sv);
+		full_store_path.append (store_path);
+	} else {
+		full_store_path.append (store_path);
+	}
+
+	verify_assembly_store_and_set_info (payload_start, full_store_path);
+	log_debug (LOG_ASSEMBLY, "Mapped assembly store {}"sv, full_store_path);
+}
+
+void AssemblyStore::map () noexcept
+{
+	verify_assembly_store_and_set_info (embedded_assembly_store, ""sv);
+	log_debug (LOG_ASSEMBLY, "Mapped embedded assembly store");
 }
diff --git a/src/native/clr/host/host.cc b/src/native/clr/host/host.cc
index c48d1a7ca9c..de333da14bb 100644
--- a/src/native/clr/host/host.cc
+++ b/src/native/clr/host/host.cc
@@ -231,11 +231,23 @@ void Host::scan_filesystem_for_assemblies_and_libraries () noexcept
 
 void Host::gather_assemblies_and_libraries (jstring_array_wrapper& runtimeApks, bool have_split_apks)
 {
+	// Embedded assembly takes priority over the one found on the filesystem.
+	if (found_assembly_store) {
+		// We have an embedded store, map it
+		AssemblyStore::map ();
+	}
+
 	if (!AndroidSystem::is_embedded_dso_mode_enabled ()) {
 		scan_filesystem_for_assemblies_and_libraries ();
 		return;
 	}
 
+	if (found_assembly_store) {
+		// In CoreCLR we only look in the APK for the assembly store. Since we have
+		// an embedded one, though, there's no need to waste time scanning the ZIP.
+		return;
+	}
+
 	int64_t apk_count = static_cast(runtimeApks.get_length ());
 	bool got_split_config_abi_apk = false;
 
diff --git a/src/native/clr/include/host/assembly-store.hh b/src/native/clr/include/host/assembly-store.hh
index 033e7d3ce1c..f49a85f94e3 100644
--- a/src/native/clr/include/host/assembly-store.hh
+++ b/src/native/clr/include/host/assembly-store.hh
@@ -14,6 +14,8 @@ namespace xamarin::android {
 	public:
 		static auto open_assembly (std::string_view const& name, int64_t &size) noexcept -> void*;
 
+		// Maps the embedded assembly store.
+		static void map () noexcept;
 		static void map (int fd, std::string_view const& apk_path, std::string_view const& store_path, uint32_t offset, uint32_t size) noexcept;
 
 		static void map (int fd, std::string_view const& file_path, uint32_t offset, uint32_t size) noexcept
@@ -23,6 +25,7 @@ namespace xamarin::android {
 
 	private:
 		static void set_assembly_data_and_size (uint8_t* source_assembly_data, uint32_t source_assembly_data_size, uint8_t*& dest_assembly_data, uint32_t& dest_assembly_data_size) noexcept;
+		static void verify_assembly_store_and_set_info (void *data_start, std::string_view const& name) noexcept;
 
 		// Returns a tuple of 
 		static auto get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData const& e, std::string_view const& name) noexcept -> std::tuple;
diff --git a/src/native/clr/include/host/host.hh b/src/native/clr/include/host/host.hh
index c1ae90788c2..fb1d2f89bc7 100644
--- a/src/native/clr/include/host/host.hh
+++ b/src/native/clr/include/host/host.hh
@@ -8,8 +8,9 @@
 
 #include 
 #include 
-#include "../shared/log_types.hh"
+#include 
 #include "managed-interface.hh"
+#include 
 
 namespace xamarin::android {
 	class Host
@@ -53,7 +54,7 @@ namespace xamarin::android {
 		static inline void *clr_host = nullptr;
 		static inline unsigned int domain_id = 0;
 		static inline std::shared_ptr _timing{};
-		static inline bool found_assembly_store = false;
+		static inline bool found_assembly_store = embedded_assembly_store_size > 0;
 		static inline jnienv_register_jni_natives_fn jnienv_register_jni_natives = nullptr;
 
 		static inline JavaVM *jvm = nullptr;
diff --git a/src/native/clr/include/xamarin-app.hh b/src/native/clr/include/xamarin-app.hh
index 8caa09323d4..f896014d910 100644
--- a/src/native/clr/include/xamarin-app.hh
+++ b/src/native/clr/include/xamarin-app.hh
@@ -427,4 +427,10 @@ struct MarshalMethodName
 #endif // def RELEASE
 
 using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr);
-extern "C" [[gnu::visibility("default")]] void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept;
+
+extern "C" {
+	[[gnu::visibility("default")]] extern size_t embedded_assembly_store_size;
+	[[gnu::visibility("default")]] extern uint8_t embedded_assembly_store[];
+
+	[[gnu::visibility("default")]] void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept;
+}
diff --git a/src/native/clr/xamarin-app-stub/application_dso_stub.cc b/src/native/clr/xamarin-app-stub/application_dso_stub.cc
index 7919083ef1f..8a32a41fba4 100644
--- a/src/native/clr/xamarin-app-stub/application_dso_stub.cc
+++ b/src/native/clr/xamarin-app-stub/application_dso_stub.cc
@@ -355,3 +355,6 @@ const char *init_runtime_property_names[] = {
 char *init_runtime_property_values[] {
 	nullptr,
 };
+
+size_t embedded_assembly_store_size = 0;
+uint8_t embedded_assembly_store[0];
diff --git a/src/native/mono/monodroid/embedded-assemblies-zip.cc b/src/native/mono/monodroid/embedded-assemblies-zip.cc
index 95c36da3f3c..30e01a25fbd 100644
--- a/src/native/mono/monodroid/embedded-assemblies-zip.cc
+++ b/src/native/mono/monodroid/embedded-assemblies-zip.cc
@@ -16,7 +16,7 @@
 using namespace xamarin::android::internal;
 
 [[gnu::always_inline]] bool
-EmbeddedAssemblies::zip_load_entry_common (size_t entry_index, std::vector const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept
+EmbeddedAssemblies::zip_load_entry_common (size_t entry_index, std::span const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept
 {
 	entry_name.clear ();
 
@@ -62,14 +62,6 @@ EmbeddedAssemblies::zip_load_entry_common (size_t entry_index, std::vector const& buf, uint32_t num_entries, [[maybe_unused]] monodroid_should_register should_register, ZipEntryLoadState &state) noexcept
+EmbeddedAssemblies::zip_load_individual_assembly_entries (std::span const& buf, uint32_t num_entries, [[maybe_unused]] monodroid_should_register should_register, ZipEntryLoadState &state) noexcept
 {
 	// TODO: do away with all the string manipulation here. Replace it with generating xxhash for the entry name
 	dynamic_local_string entry_name;
@@ -183,6 +175,47 @@ EmbeddedAssemblies::zip_load_individual_assembly_entries (std::vector c
 	}
 }
 
+
+[[gnu::always_inline]] void
+EmbeddedAssemblies::verify_assembly_store_and_set_info (void *data_start, const char *name) noexcept
+{
+	auto header = static_cast(data_start);
+
+	if (header->magic != ASSEMBLY_STORE_MAGIC) {
+		Helpers::abort_application (
+			LOG_ASSEMBLY,
+			std::format (
+				"Assembly store '{}' is not a valid .NET for Android assembly store file",
+				optional_string (name)
+			)
+		);
+	}
+
+	if (header->version != ASSEMBLY_STORE_FORMAT_VERSION) {
+		Helpers::abort_application (
+			LOG_ASSEMBLY,
+			std::format (
+				"Assembly store '{}' uses format version 0x{:x}, instead of the expected 0x{:x}",
+				optional_string (name),
+				header->version,
+				ASSEMBLY_STORE_FORMAT_VERSION
+			)
+		);
+	}
+
+	constexpr size_t header_size = sizeof(AssemblyStoreHeader);
+
+	assembly_store.data_start = static_cast(data_start);
+	assembly_store.assembly_count = header->entry_count;
+	assembly_store.index_entry_count = header->index_entry_count;
+	assembly_store.assemblies = reinterpret_cast(assembly_store.data_start + header_size + header->index_size);
+	assembly_store_hashes = reinterpret_cast(assembly_store.data_start + header_size);
+
+	number_of_found_assemblies += assembly_store.assembly_count;
+	number_of_mapped_assembly_stores++;
+	have_and_want_debug_symbols = register_debug_symbols;
+}
+
 inline void
 EmbeddedAssemblies::map_assembly_store (dynamic_local_string const& entry_name, ZipEntryLoadState &state) noexcept
 {
@@ -219,55 +252,26 @@ EmbeddedAssemblies::map_assembly_store (dynamic_local_string
 	}
 
 	auto [payload_start, payload_size] = get_wrapper_dso_payload_pointer_and_size (assembly_store_map, entry_name.get ());
-	log_debug (LOG_ASSEMBLY, "Adjusted assembly store pointer: {:p}; size: {}", payload_start, payload_size);
-	auto header = static_cast(payload_start);
-
-	if (header->magic != ASSEMBLY_STORE_MAGIC) {
-		Helpers::abort_application (
-			LOG_ASSEMBLY,
-			std::format (
-				"Assembly store '{}' is not a valid .NET for Android assembly store file",
-				optional_string (entry_name.get ())
-			)
-		);
-	}
-
-	if (header->version != ASSEMBLY_STORE_FORMAT_VERSION) {
-		Helpers::abort_application (
-			LOG_ASSEMBLY,
-			std::format (
-				"Assembly store '{}' uses format version {:x}, instead of the expected {:x}",
-				optional_string (entry_name.get ()),
-				header->version,
-				ASSEMBLY_STORE_FORMAT_VERSION
-			)
-		);
-	}
-
-	constexpr size_t header_size = sizeof(AssemblyStoreHeader);
-
-	assembly_store.data_start = static_cast(payload_start);
-	assembly_store.assembly_count = header->entry_count;
-	assembly_store.index_entry_count = header->index_entry_count;
-	assembly_store.assemblies = reinterpret_cast(assembly_store.data_start + header_size + header->index_size);
-	assembly_store_hashes = reinterpret_cast(assembly_store.data_start + header_size);
-
-	number_of_found_assemblies += assembly_store.assembly_count;
-	number_of_mapped_assembly_stores++;
-	have_and_want_debug_symbols = register_debug_symbols;
+	log_debug (LOG_ASSEMBLY, "Adjusted assembly store pointer: %p; size: %zu", payload_start, payload_size);
+	verify_assembly_store_and_set_info (payload_start, entry_name.get ());
 }
 
 [[gnu::always_inline]] void
-EmbeddedAssemblies::zip_load_assembly_store_entries (std::vector const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept
+EmbeddedAssemblies::zip_load_assembly_store_entries (std::span const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept
 {
 	if (all_required_zip_entries_found ()) {
 		return;
 	}
 
 	dynamic_local_string entry_name;
-	bool assembly_store_found = false;
+	bool assembly_store_found = embedded_assembly_store_size != 0;
+	if (assembly_store_found) {
+		load_embedded_assembly_store ();
+		log_debug (LOG_ASSEMBLY, "Looking for DSOs in APK");
+	} else {
+		log_debug (LOG_ASSEMBLY, "Looking for assembly store ('{}') and DSOs in APK", assembly_store_file_path);
+	}
 
-	log_debug (LOG_ASSEMBLY, "Looking for assembly stores in APK ('{}')", assembly_store_file_path.data ());
 	for (size_t i = 0uz; i < num_entries; i++) {
 		if (all_required_zip_entries_found ()) {
 			need_to_scan_more_apks = false;
@@ -315,6 +319,7 @@ EmbeddedAssemblies::zip_load_assembly_store_entries (std::vector const&
 	}
 }
 
+[[gnu::flatten]]
 void
 EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unused]] monodroid_should_register should_register) noexcept
 {
@@ -322,7 +327,7 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus
 	uint32_t cd_size;
 	uint16_t cd_entries;
 
-	if (!zip_read_cd_info (fd, cd_offset, cd_size, cd_entries)) {
+	if (!zip_read_cd_info (fd, cd_offset, cd_size, cd_entries)) [[unlikely]] {
 		Helpers::abort_application (
 			LOG_ASSEMBLY,
 			std::format (
@@ -331,13 +336,13 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus
 			)
 		);
 	}
-#ifdef DEBUG
-	log_info (LOG_ASSEMBLY, "Central directory offset: {}", cd_offset);
-	log_info (LOG_ASSEMBLY, "Central directory size: {}", cd_size);
-	log_info (LOG_ASSEMBLY, "Central directory entries: {}", cd_entries);
-#endif
+
+	log_debug (LOG_ASSEMBLY, "Central directory offset: {}", cd_offset);
+	log_debug (LOG_ASSEMBLY, "Central directory size: {}", cd_size);
+	log_debug (LOG_ASSEMBLY, "Central directory entries: {}", cd_entries);
+
 	off_t retval = ::lseek (fd, static_cast(cd_offset), SEEK_SET);
-	if (retval < 0) {
+	if (retval < 0) [[unlikely]] {
 		Helpers::abort_application (
 			LOG_ASSEMBLY,
 			std::format (
@@ -350,7 +355,6 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus
 		);
 	}
 
-	std::vector  buf (cd_size);
 	const auto [prefix, prefix_len] = get_assemblies_prefix_and_length ();
 	ZipEntryLoadState state {
 		.file_fd             = fd,
@@ -367,8 +371,10 @@ EmbeddedAssemblies::zip_load_entries (int fd, const char *apk_name, [[maybe_unus
 		.max_assembly_file_name_size = 0u,
 	};
 
+	std::unique_ptr raw_data (new uint8_t[cd_size]);
+	std::span buf (raw_data.get (), cd_size);
 	ssize_t nread = read (fd, buf.data (), buf.size ());
-	if (static_cast(nread) != cd_size) {
+	if (static_cast(nread) != cd_size) [[unlikely]] {
 		Helpers::abort_application (
 			LOG_ASSEMBLY,
 			std::format (
@@ -647,7 +653,7 @@ EmbeddedAssemblies::zip_read_field (T const& buf, size_t index, size_t count, dy
 }
 
 bool
-EmbeddedAssemblies::zip_read_entry_info (std::vector const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept
+EmbeddedAssemblies::zip_read_entry_info (std::span const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept
 {
 	constexpr size_t CD_COMPRESSION_METHOD_OFFSET = 10uz;
 	constexpr size_t CD_UNCOMPRESSED_SIZE_OFFSET  = 24uz;
diff --git a/src/native/mono/monodroid/embedded-assemblies.cc b/src/native/mono/monodroid/embedded-assemblies.cc
index c35324f408b..73bb98e4751 100644
--- a/src/native/mono/monodroid/embedded-assemblies.cc
+++ b/src/native/mono/monodroid/embedded-assemblies.cc
@@ -910,51 +910,6 @@ EmbeddedAssemblies::typemap_managed_to_java (MonoReflectionType *reflection_type
 	return ret;
 }
 
-EmbeddedAssemblies::md_mmap_info
-EmbeddedAssemblies::md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename)
-{
-	md_mmap_info file_info;
-	md_mmap_info mmap_info;
-
-	size_t pageSize        = static_cast(Util::monodroid_getpagesize ());
-	size_t offsetFromPage  = offset % pageSize;
-	size_t offsetPage      = offset - offsetFromPage;
-	size_t offsetSize      = size + offsetFromPage;
-
-	mmap_info.area        = mmap (nullptr, offsetSize, PROT_READ, MAP_PRIVATE, fd, static_cast(offsetPage));
-
-	if (mmap_info.area == MAP_FAILED) {
-		Helpers::abort_application (
-			LOG_ASSEMBLY,
-			std::format (
-				"Could not mmap APK fd {}: {}; File={}",
-				fd,
-				strerror (errno),
-				optional_string (filename)
-			)
-		);
-	}
-
-	mmap_info.size  = offsetSize;
-	file_info.area  = pointer_add (mmap_info.area, offsetFromPage);
-	file_info.size  = size;
-
-	log_info (
-		LOG_ASSEMBLY,
-			"  mmap_start: {:<8p}; mmap_end: {:<8p}  mmap_len: {:<12}  file_start: {:<8p}  file_end: {:<8p}  file_len: {:<12}     apk descriptor: {}  file: {}",
-			mmap_info.area,
-			pointer_add (mmap_info.area, mmap_info.size),
-			mmap_info.size,
-			file_info.area,
-			pointer_add (file_info.area, file_info.size),
-			file_info.size,
-			fd,
-			optional_string (filename)
-	);
-
-	return file_info;
-}
-
 void
 EmbeddedAssemblies::gather_bundled_assemblies_from_apk (const char* apk, monodroid_should_register should_register) noexcept
 {
@@ -1450,13 +1405,21 @@ EmbeddedAssemblies::register_from_filesystem (const char *lib_dir_path,bool look
 size_t
 EmbeddedAssemblies::register_from_filesystem (monodroid_should_register should_register) noexcept
 {
-	log_debug (LOG_ASSEMBLY, "Registering assemblies from the filesystem"sv);
-	constexpr bool LookForMangledNames = true;
-	size_t assembly_count = register_from_filesystem (
-		AndroidSystem::app_lib_directories[0],
-		LookForMangledNames,
-		should_register
-	);
+	size_t assembly_count;
+
+	if (embedded_assembly_store_size > 0) {
+		log_debug (LOG_ASSEMBLY, "Filesystem mode, but registering assemblies from the embedded assembly store");
+		load_embedded_assembly_store ();
+		assembly_count = assembly_store.assembly_count;
+	} else {
+		log_debug (LOG_ASSEMBLY, "Registering assemblies from the filesystem");
+		constexpr bool LookForMangledNames = true;
+		assembly_count = register_from_filesystem (
+			AndroidSystem::app_lib_directories[0],
+			LookForMangledNames,
+			should_register
+		);
+	}
 
 #if defined(DEBUG)
 	constexpr bool DoNotLookForMangledNames = false;
diff --git a/src/native/mono/monodroid/embedded-assemblies.hh b/src/native/mono/monodroid/embedded-assemblies.hh
index f3447a69840..da3a86328f6 100644
--- a/src/native/mono/monodroid/embedded-assemblies.hh
+++ b/src/native/mono/monodroid/embedded-assemblies.hh
@@ -9,6 +9,7 @@
 #include 
 #include 
 #include 
+#include 
 
 #include 
 #include 
@@ -193,11 +194,6 @@ namespace xamarin::android::internal {
 			runtime_config_data_size = 0uz;
 		}
 
-		static bool have_runtime_config_blob () noexcept
-		{
-			return application_config.have_runtime_config_blob && runtime_config_blob_mmap.area != nullptr;
-		}
-
 		static bool keep_scanning () noexcept
 		{
 			return need_to_scan_more_apks;
@@ -258,7 +254,57 @@ namespace xamarin::android::internal {
 		static const TypeMapEntry *typemap_managed_to_java (const char *managed_type_name) noexcept;
 #endif // DEBUG
 
-		static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename);
+		[[gnu::always_inline]]
+		static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename, md_mmap_info &original_info, md_mmap_info &adjusted_info) noexcept
+		{
+			size_t pageSize        = static_cast(Util::monodroid_getpagesize ());
+			size_t offsetFromPage  = offset % pageSize;
+			size_t offsetPage      = offset - offsetFromPage;
+			size_t offsetSize      = size + offsetFromPage;
+
+			original_info.area        = mmap (nullptr, offsetSize, PROT_READ, MAP_PRIVATE, fd, static_cast(offsetPage));
+
+			if (original_info.area == MAP_FAILED) {
+				Helpers::abort_application (
+					LOG_ASSEMBLY,
+					Util::monodroid_strdup_printf (
+						"Could not mmap APK fd %d: %s; File=%s",
+						fd,
+						strerror (errno),
+						filename
+					)
+				);
+			}
+
+			original_info.size  = offsetSize;
+			adjusted_info.area  = (void*)((const char*)original_info.area + offsetFromPage);
+			adjusted_info.size  = size;
+
+			log_info (
+				LOG_ASSEMBLY,
+				"  mmap_start: {:<8p}; mmap_end: {:<8p}  mmap_len: {:<12}  file_start: {:<8p}  file_end: {:<8p}  file_len: {:<12}     apk descriptor: {}  file: {}",
+				original_info.area,
+				pointer_add (original_info.area, original_info.size),
+				original_info.size,
+				adjusted_info.area,
+				pointer_add (adjusted_info.area, adjusted_info.size),
+				adjusted_info.size,
+				fd,
+				optional_string (filename)
+			);
+
+			return adjusted_info;
+		}
+
+		[[gnu::flatten, gnu::always_inline]]
+		static md_mmap_info md_mmap_apk_file (int fd, uint32_t offset, size_t size, const char* filename) noexcept
+		{
+			md_mmap_info file_info;
+			md_mmap_info mmap_info;
+
+			return md_mmap_apk_file (fd, offset, size, filename, mmap_info, file_info);
+		}
+
 		static MonoAssembly* open_from_bundles_full (MonoAssemblyName *aname, char **assemblies_path, void *user_data);
 		static MonoAssembly* open_from_bundles (MonoAssemblyLoadContextGCHandle alc_gchandle, MonoAssemblyName *aname, char **assemblies_path, void *user_data, MonoError *error);
 
@@ -268,9 +314,9 @@ namespace xamarin::android::internal {
 		static void get_assembly_data (AssemblyStoreSingleAssemblyRuntimeData const& e, uint8_t*& assembly_data, uint32_t& assembly_data_size) noexcept;
 
 		static void zip_load_entries (int fd, const char *apk_name, monodroid_should_register should_register) noexcept;
-		static void zip_load_individual_assembly_entries (std::vector const& buf, uint32_t num_entries, monodroid_should_register should_register, ZipEntryLoadState &state) noexcept;
-		static void zip_load_assembly_store_entries (std::vector const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept;
-		static bool zip_load_entry_common (size_t entry_index, std::vector const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept;
+		static void zip_load_individual_assembly_entries (std::span const& buf, uint32_t num_entries, monodroid_should_register should_register, ZipEntryLoadState &state) noexcept;
+		static void zip_load_assembly_store_entries (std::span const& buf, uint32_t num_entries, ZipEntryLoadState &state) noexcept;
+		static bool zip_load_entry_common (size_t entry_index, std::span const& buf, dynamic_local_string &entry_name, ZipEntryLoadState &state) noexcept;
 		static bool zip_read_cd_info (int fd, uint32_t& cd_offset, uint32_t& cd_size, uint16_t& cd_entries) noexcept;
 		static bool zip_adjust_data_offset (int fd, ZipEntryLoadState &state) noexcept;
 
@@ -292,7 +338,7 @@ namespace xamarin::android::internal {
 		template
 		static bool zip_read_field (T const& buf, size_t index, size_t count, dynamic_local_string& characters) noexcept;
 
-		static bool zip_read_entry_info (std::vector const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept;
+		static bool zip_read_entry_info (std::span const& buf, dynamic_local_string& file_name, ZipEntryLoadState &state) noexcept;
 
 		[[gnu::always_inline]]
 		static std::tuple get_wrapper_dso_payload_pointer_and_size (md_mmap_info const& map_info, const  char *file_name) noexcept
@@ -351,8 +397,7 @@ namespace xamarin::android::internal {
 		static bool all_required_zip_entries_found () noexcept
 		{
 			return
-				number_of_mapped_assembly_stores == number_of_assembly_store_files && number_of_zip_dso_entries >= application_config.number_of_shared_libraries
-				&& ((application_config.have_runtime_config_blob && runtime_config_blob_found) || !application_config.have_runtime_config_blob);
+				number_of_mapped_assembly_stores == number_of_assembly_store_files && number_of_zip_dso_entries >= application_config.number_of_shared_libraries;
 		}
 
 		[[gnu::always_inline]] static c_unique_ptr to_utf8 (const MonoString *s) noexcept
@@ -378,6 +423,15 @@ namespace xamarin::android::internal {
 		static void set_entry_data (XamarinAndroidBundledAssembly &entry, ZipEntryLoadState const& state, dynamic_local_string const& entry_name) noexcept;
 		static void set_assembly_entry_data (XamarinAndroidBundledAssembly &entry, ZipEntryLoadState const& state, dynamic_local_string const& entry_name) noexcept;
 		static void set_debug_entry_data (XamarinAndroidBundledAssembly &entry, ZipEntryLoadState const& state, dynamic_local_string const& entry_name) noexcept;
+
+		static void verify_assembly_store_and_set_info (void *data_start, const char *name) noexcept;
+
+		static void load_embedded_assembly_store () noexcept
+		{
+			log_debug (LOG_ASSEMBLY, "Loading embedded assembly store");
+			verify_assembly_store_and_set_info (embedded_assembly_store, "embedded");
+		}
+
 		static void map_assembly_store (dynamic_local_string const& entry_name, ZipEntryLoadState &state) noexcept;
 		static const AssemblyStoreIndexEntry* find_assembly_store_entry (hash_t hash, const AssemblyStoreIndexEntry *entries, size_t entry_count) noexcept;
 		static void store_individual_assembly_data (dynamic_local_string const& entry_name, ZipEntryLoadState const& state, monodroid_should_register should_register) noexcept;
@@ -458,11 +512,10 @@ namespace xamarin::android::internal {
 		size_t                 type_map_count;
 #endif // DEBUG
 		static inline const char   *assemblies_prefix_override = nullptr;
-
 		static inline md_mmap_info  runtime_config_blob_mmap{};
 		static inline void         *runtime_config_data = nullptr;
 		static inline size_t        runtime_config_data_size = 0uz;
-		static inline bool          runtime_config_blob_found = false;
+		static inline bool          runtime_config_blob_found = embedded_runtime_config_size > 0u;
 		static inline uint32_t      number_of_mapped_assembly_stores = 0u;
 		static inline uint32_t      number_of_zip_dso_entries = 0u;
 		static inline bool          need_to_scan_more_apks = true;
diff --git a/src/native/mono/monodroid/monodroid-glue.cc b/src/native/mono/monodroid/monodroid-glue.cc
index d8c722f48f2..954661663a2 100644
--- a/src/native/mono/monodroid/monodroid-glue.cc
+++ b/src/native/mono/monodroid/monodroid-glue.cc
@@ -728,13 +728,15 @@ MonodroidRuntime::create_domain (JNIEnv *env, jstring_array_wrapper &runtimeApks
 
 	gather_bundled_assemblies (runtimeApks, &user_assemblies_count, have_split_apks);
 
-	if (EmbeddedAssemblies::have_runtime_config_blob ()) {
+	if (embedded_runtime_config_size > 0) {
 		if (FastTiming::enabled ()) [[unlikely]] {
 			internal_timing.start_event (TimingEventKind::RuntimeConfigBlob);
 		}
 
 		runtime_config_args.kind = 1;
-		EmbeddedAssemblies::get_runtime_config_blob (runtime_config_args.runtimeconfig.data.data, runtime_config_args.runtimeconfig.data.data_len);
+		runtime_config_args.runtimeconfig.data.data = reinterpret_cast(embedded_runtime_config);
+		runtime_config_args.runtimeconfig.data.data_len = static_cast(embedded_runtime_config_size);
+
 		monovm_runtimeconfig_initialize (&runtime_config_args, cleanup_runtime_config, nullptr);
 
 		if (FastTiming::enabled ()) [[unlikely]] {
@@ -1155,7 +1157,8 @@ MonodroidRuntime::set_profile_options () noexcept
 			.append (OUTPUT_ARG)
 			.append (output_path.get (), output_path.length ());
 	}
-	if (Util::create_directory (AndroidSystem::override_dirs[0], 0) < 0) {
+
+	if (Util::create_directory (AndroidSystem::override_dirs[0], 0777, 000) < 0) {
 		log_warn (LOG_DEFAULT, "Failed to create directory '{}'. {}", optional_string (AndroidSystem::override_dirs[0]), std::strerror (errno));
 	}
 
diff --git a/src/native/mono/runtime-base/android-system.cc b/src/native/mono/runtime-base/android-system.cc
index 0fcd825bead..62c358f3816 100644
--- a/src/native/mono/runtime-base/android-system.cc
+++ b/src/native/mono/runtime-base/android-system.cc
@@ -262,26 +262,14 @@ AndroidSystem::monodroid_get_system_property_from_overrides ([[maybe_unused]] co
 	return 0;
 }
 
-// TODO: review this. Do we really have to create the dir in release?
 void
 AndroidSystem::create_update_dir (char *override_dir) noexcept
 {
-#if defined (RELEASE)
-	/*
-	 * Don't create .__override__ on Release builds, because Google requires
-	 * that pre-loaded apps not create world-writable directories.
-	 *
-	 * However, if any logging is enabled (which should _not_ happen with
-	 * pre-loaded apps!), we need the .__override__ directory...
-	 */
-	if (log_categories == 0 && monodroid_get_system_property (SharedConstants::DEBUG_MONO_PROFILE_PROPERTY, nullptr) == 0) {
-		return;
-	}
-#endif // def RELEASE
-
 	override_dirs [0] = override_dir;
+#if defined(DEBUG)
+	log_debug (LOG_DEFAULT, "Creating public update directory: `%s`", override_dir);
 	Util::create_public_directory (override_dir);
-	log_warn (LOG_DEFAULT, "Creating public update directory: `{}`", override_dir);
+#endif
 }
 
 bool
diff --git a/src/native/mono/runtime-base/util.cc b/src/native/mono/runtime-base/util.cc
index fda41ecfd88..a5159f1f066 100644
--- a/src/native/mono/runtime-base/util.cc
+++ b/src/native/mono/runtime-base/util.cc
@@ -147,14 +147,14 @@ Util::path_combine (const char *path1, const char *path2)
 void
 Util::create_public_directory (const char *dir)
 {
-	int ret = create_directory (dir, 0777);
+	int ret = create_directory (dir, 0777, 0);
 	if (ret < 0) {
 		log_warn (LOG_DEFAULT, "Failed to create directory '{}'. {}", dir, std::strerror (errno));
 	}
 }
 
 int
-Util::create_directory (const char *pathname, mode_t mode)
+Util::create_directory (const char *pathname, mode_t mode, mode_t mask)
 {
 	if (mode <= 0)
 		mode = DEFAULT_DIRECTORY_MODE;
@@ -163,7 +163,7 @@ Util::create_directory (const char *pathname, mode_t mode)
 		errno = EINVAL;
 		return -1;
 	}
-	mode_t oldumask = umask (022);
+	mode_t oldumask = umask (mask);
 	std::unique_ptr path {strdup_new (pathname)};
 	int rv, ret = 0;
 	for (char *d = path.get (); d != nullptr && *d; ++d) {
diff --git a/src/native/mono/runtime-base/util.hh b/src/native/mono/runtime-base/util.hh
index a43009478ae..3bfd897658f 100644
--- a/src/native/mono/runtime-base/util.hh
+++ b/src/native/mono/runtime-base/util.hh
@@ -69,7 +69,7 @@ namespace xamarin::android
 		static char            *monodroid_strdup_vprintf (const char *format, va_list vargs);
 		static char*            path_combine (const char *path1, const char *path2);
 		static void             create_public_directory (const char *dir);
-		static int              create_directory (const char *pathname, mode_t mode);
+		static int              create_directory (const char *pathname, mode_t mode, mode_t mask = 022);
 		static void             set_world_accessable (const char *path);
 		static auto             set_world_accessible (int fd) noexcept -> bool;
 		static void             set_user_executable (const char *path);
diff --git a/src/native/mono/xamarin-app-stub/application_dso_stub.cc b/src/native/mono/xamarin-app-stub/application_dso_stub.cc
index 0316a55408b..d60e11c82c9 100644
--- a/src/native/mono/xamarin-app-stub/application_dso_stub.cc
+++ b/src/native/mono/xamarin-app-stub/application_dso_stub.cc
@@ -48,7 +48,6 @@ const ApplicationConfig application_config = {
 	.uses_assembly_preload = false,
 	.broken_exception_transitions = false,
 	.jni_add_native_method_registration_attribute_present = false,
-	.have_runtime_config_blob = false,
 	.have_assembly_store = false,
 	.marshal_methods_enabled = false,
 	.ignore_split_configs = false,
@@ -305,3 +304,9 @@ const JniRemappingTypeReplacementEntry jni_remapping_type_replacements[] = {
 		.replacement = "another/replacement/java/type",
 	},
 };
+
+size_t embedded_runtime_config_size = 0;
+uint8_t embedded_runtime_config[0];
+
+size_t embedded_assembly_store_size = 0;
+uint8_t embedded_assembly_store[0];
diff --git a/src/native/mono/xamarin-app-stub/xamarin-app.hh b/src/native/mono/xamarin-app-stub/xamarin-app.hh
index 5b058a99d2d..d8badede691 100644
--- a/src/native/mono/xamarin-app-stub/xamarin-app.hh
+++ b/src/native/mono/xamarin-app-stub/xamarin-app.hh
@@ -238,7 +238,6 @@ struct ApplicationConfig
 	bool uses_assembly_preload;
 	bool broken_exception_transitions;
 	bool jni_add_native_method_registration_attribute_present;
-	bool have_runtime_config_blob;
 	bool have_assembly_store;
 	bool marshal_methods_enabled;
 	bool ignore_split_configs;
@@ -394,6 +393,12 @@ MONO_API MONO_API_EXPORT const MarshalMethodName mm_method_names[];
 
 #endif // def RELEASE
 
+MONO_API MONO_API_EXPORT size_t embedded_runtime_config_size;
+MONO_API MONO_API_EXPORT uint8_t embedded_runtime_config[];
+
+MONO_API MONO_API_EXPORT size_t embedded_assembly_store_size;
+MONO_API MONO_API_EXPORT uint8_t embedded_assembly_store[];
+
 using get_function_pointer_fn = void(*)(uint32_t mono_image_index, uint32_t class_index, uint32_t method_token, void*& target_ptr);
 MONO_API MONO_API_EXPORT void xamarin_app_init (JNIEnv *env, get_function_pointer_fn fn) noexcept;
 
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index d179740e994..92593ce63c1 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -1380,5 +1380,26 @@ public void AppStartsWithManagedMarshalMethodsLookupEnabled ()
 				Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30);
 			Assert.IsTrue (didLaunch, "Activity should have started.");
 		}
+
+		[Test]
+		[TestCase (false)]
+		[TestCase (true)]
+		public void AppStartsWithEmbeddedAssemblyStore (bool useCLR)
+		{
+			var proj = new XamarinAndroidApplicationProject { IsRelease = true };
+			proj.SetProperty ("_AndroidEmbedAssemblyStoreInRuntime", "true");
+			proj.SetProperty ("UseMonoRuntime", useCLR ? "false" : "true");
+
+			using var builder = CreateApkBuilder ();
+			builder.Save (proj);
+
+			var dotnet = new DotNetCLI (Path.Combine (Root, builder.ProjectDirectory, proj.ProjectFilePath));
+			Assert.IsTrue (dotnet.Build (), "`dotnet build` should succeed");
+			Assert.IsTrue (dotnet.Run (), "`dotnet run --no-build` should succeed");
+
+			bool didLaunch = WaitForActivityToStart (proj.PackageName, "MainActivity",
+				Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30);
+			Assert.IsTrue (didLaunch, "Activity should have started.");
+		}
 	}
 }
diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs
index d6a5a638302..aef64a09d3d 100644
--- a/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs
+++ b/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreExplorer.cs
@@ -140,7 +140,19 @@ public static (IList? explorers, string? errorMessage) Op
 			ZipEntry entry = zip.ReadEntry (path);
 			var stream = new MemoryStream ();
 			entry.Extract (stream);
-			ret.Add (new AssemblyStoreExplorer (stream, $"{fi.FullName}!{path}"));
+			AssemblyStoreExplorer? explorer = null;
+			try {
+				// It may throw when opening an apk without any assembly stores, in which case the v2 store reader would
+				// always find `libxamarin-app.so` and try to read the embedded store and fail, throwing from the explorer
+				// constructor.
+				explorer = new AssemblyStoreExplorer (stream, $"{fi.FullName}!{path}");
+			} catch (NotSupportedException) {
+				// Ignore
+			}
+
+			if (explorer != null) {
+				ret.Add (explorer);
+			}
 		}
 
 		if (ret.Count == 0) {
diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs b/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs
index 932f151c80f..78372c26853 100644
--- a/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs
+++ b/tools/assembly-store-reader-mk2/AssemblyStore/ELFPayloadError.cs
@@ -8,4 +8,5 @@ enum ELFPayloadError
 	NotSharedLibrary,
 	NotLittleEndian,
 	NoPayloadSection,
+	NoEmbeddedStore,
 }
diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs
index a43960102e2..36dad0c4c45 100644
--- a/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs
+++ b/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs
@@ -40,6 +40,11 @@ static StoreReader_V2 ()
 			GetArchPath (AndroidTargetArch.Arm),
 			GetArchPath (AndroidTargetArch.X86_64),
 			GetArchPath (AndroidTargetArch.X86),
+
+			GetArchPath (AndroidTargetArch.Arm64, embeddedBlob: true),
+			GetArchPath (AndroidTargetArch.Arm, embeddedBlob: true),
+			GetArchPath (AndroidTargetArch.X86_64, embeddedBlob: true),
+			GetArchPath (AndroidTargetArch.X86, embeddedBlob: true),
 		};
 		ApkPaths = paths.AsReadOnly ();
 		AabBasePaths = ApkPaths;
@@ -50,10 +55,15 @@ static StoreReader_V2 ()
 			GetArchPath (AndroidTargetArch.Arm, AabBaseDir),
 			GetArchPath (AndroidTargetArch.X86_64, AabBaseDir),
 			GetArchPath (AndroidTargetArch.X86, AabBaseDir),
+
+			GetArchPath (AndroidTargetArch.Arm64, AabBaseDir, embeddedBlob: true),
+			GetArchPath (AndroidTargetArch.Arm, AabBaseDir, embeddedBlob: true),
+			GetArchPath (AndroidTargetArch.X86_64, AabBaseDir, embeddedBlob: true),
+			GetArchPath (AndroidTargetArch.X86, AabBaseDir, embeddedBlob: true),
 		};
 		AabPaths = paths.AsReadOnly ();
 
-		string GetArchPath (AndroidTargetArch arch, string? root = null)
+		string GetArchPath (AndroidTargetArch arch, string? root = null, bool embeddedBlob = false)
 		{
 			const string LibDirName = "lib";
 
@@ -65,7 +75,7 @@ string GetArchPath (AndroidTargetArch arch, string? root = null)
 				root = LibDirName;
 			}
 			parts.Add (abi);
-			parts.Add (GetBlobName (abi));
+			parts.Add (GetBlobName (abi, embeddedBlob));
 
 			return MonoAndroidHelper.MakeZipArchivePath (root, parts);
 		}
@@ -82,10 +92,23 @@ public StoreReader_V2 (Stream store, string path)
 		};
 	}
 
-	static string GetBlobName (string abi) => $"libassemblies.{abi}.blob.so";
+	static string GetBlobName (string abi, bool embeddedBlob) => embeddedBlob ? "libxamarin-app.so" : $"libassemblies.{abi}.blob.so";
 
 	protected override ulong GetStoreStartDataOffset () => elfOffset;
 
+	string PayloadErrorToString (ELFPayloadError error)
+	{
+		return error switch {
+			ELFPayloadError.NotELF           => $"Store '{StorePath}' is not a valid ELF binary",
+			ELFPayloadError.LoadFailed       => $"Store '{StorePath}' could not be loaded",
+			ELFPayloadError.NotSharedLibrary => $"Store '{StorePath}' is not a shared ELF library",
+			ELFPayloadError.NotLittleEndian  => $"Store '{StorePath}' is not a little-endian ELF image",
+			ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section",
+			ELFPayloadError.NoEmbeddedStore  => $"Store '{StorePath}' does not contain embedded data blob",
+			_                                => $"Unknown ELF payload section error for store '{StorePath}': {error}"
+		};
+	}
+
 	protected override bool IsSupported ()
 	{
 		StoreStream.Seek (0, SeekOrigin.Begin);
@@ -94,21 +117,24 @@ protected override bool IsSupported ()
 		uint magic = reader.ReadUInt32 ();
 		if (magic == Utils.ELF_MAGIC) {
 			ELFPayloadError error;
-			(elfOffset, _, error) = Utils.FindELFPayloadSectionOffsetAndSize (StoreStream);
-
+			(elfOffset, _, error) = Utils.FindEmbeddedStoreOffsetAndSize (StoreStream);
 			if (error != ELFPayloadError.None) {
-				string message = error switch {
-					ELFPayloadError.NotELF           => $"Store '{StorePath}' is not a valid ELF binary",
-					ELFPayloadError.LoadFailed       => $"Store '{StorePath}' could not be loaded",
-					ELFPayloadError.NotSharedLibrary => $"Store '{StorePath}' is not a shared ELF library",
-					ELFPayloadError.NotLittleEndian  => $"Store '{StorePath}' is not a little-endian ELF image",
-					ELFPayloadError.NoPayloadSection => $"Store '{StorePath}' does not contain the 'payload' section",
-					_                                => $"Unknown ELF payload section error for store '{StorePath}': {error}"
-				};
-				Log.Debug (message);
-			} else if (elfOffset >= 0) {
+				MaybeLogError (error);
+
+				(elfOffset, _, error) = Utils.FindELFPayloadSectionOffsetAndSize (StoreStream);
+				if (error != ELFPayloadError.None) {
+					MaybeLogError (error);
+					return false;
+				}
+			}
+
+			// elfOffset cannot be 0, since we have ELF magic and headers (at least) at the beginning of
+			// the file.
+			if (elfOffset > 0) {
 				StoreStream.Seek ((long)elfOffset, SeekOrigin.Begin);
 				magic = reader.ReadUInt32 ();
+			} else {
+				return false;
 			}
 		}
 
@@ -129,6 +155,15 @@ protected override bool IsSupported ()
 
 		header = new Header (magic, version, entry_count, index_entry_count, index_size);
 		return true;
+
+		void MaybeLogError (ELFPayloadError error)
+		{
+			if (error == ELFPayloadError.None) {
+				return;
+			}
+
+			Log.Debug (PayloadErrorToString (error));
+		}
 	}
 
 	protected override void Prepare ()
diff --git a/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs b/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs
index 284f501c9ac..2e2f03e5e52 100644
--- a/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs
+++ b/tools/assembly-store-reader-mk2/AssemblyStore/Utils.cs
@@ -4,6 +4,7 @@
 
 using ELFSharp.ELF;
 using ELFSharp.ELF.Sections;
+using Xamarin.Android.Tasks;
 using Xamarin.Tools.Zip;
 
 namespace Xamarin.Android.AssemblyStore;
@@ -29,31 +30,28 @@ static class Utils
 
 	public static readonly ArrayPool BytePool = ArrayPool.Shared;
 
-	public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSectionOffsetAndSize (Stream stream)
+	static (bool is64, IELF? elf, ELFPayloadError error) TryOpenELF (Stream stream)
 	{
+		bool is64 = false;
 		stream.Seek (0, SeekOrigin.Begin);
 		Class elfClass = ELFReader.CheckELFType (stream);
 		if (elfClass == Class.NotELF) {
-			return ReturnError (null, ELFPayloadError.NotELF);
+			return ReturnError (is64, null, ELFPayloadError.NotELF);
 		}
 
 		if (!ELFReader.TryLoad (stream, shouldOwnStream: false, out IELF? elf)) {
-			return ReturnError (elf, ELFPayloadError.LoadFailed);
+			return ReturnError (is64, elf, ELFPayloadError.LoadFailed);
 		}
 
 		if (elf.Type != FileType.SharedObject) {
-			return ReturnError (elf, ELFPayloadError.NotSharedLibrary);
+			return ReturnError (is64, elf, ELFPayloadError.NotSharedLibrary);
 		}
 
 		if (elf.Endianess != ELFSharp.Endianess.LittleEndian) {
-			return ReturnError (elf, ELFPayloadError.NotLittleEndian);
-		}
-
-		if (!elf.TryGetSection ("payload", out ISection? payloadSection)) {
-			return ReturnError (elf, ELFPayloadError.NoPayloadSection);
+			return ReturnError (is64, elf, ELFPayloadError.NotLittleEndian);
 		}
 
-		bool is64 = elf.Machine switch {
+		is64 = elf.Machine switch {
 			Machine.ARM      => false,
 			Machine.Intel386 => false,
 
@@ -63,6 +61,51 @@ public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSe
 			_                => throw new NotSupportedException ($"Unsupported ELF architecture '{elf.Machine}'")
 		};
 
+		return (is64, elf, ELFPayloadError.None);
+
+		(bool is64, IELF? elf, ELFPayloadError error) ReturnError (bool is64, IELF? elf, ELFPayloadError error)
+		{
+			elf?.Dispose ();
+
+			return (is64, null, error);
+		}
+	}
+
+	public static (ulong offset, ulong size, ELFPayloadError error) FindEmbeddedStoreOffsetAndSize (Stream stream)
+	{
+		(bool is64, IELF? elf, ELFPayloadError error) = TryOpenELF (stream);
+		if (elf == null || error != ELFPayloadError.None) {
+			return ReturnError (elf, error);
+		}
+
+		const string SymbolName = "embedded_assembly_store";
+		ISymbolTable? dynsym = ELFHelper.GetSymbolTable (elf, ".dynsym");
+		if (is64) {
+			SymbolEntry? symbol = ELFHelper.FindSymbol (dynsym, SymbolName);
+			if (symbol != null) {
+				return (symbol.Value, symbol.Size, ELFPayloadError.None);
+			}
+		} else {
+			SymbolEntry? symbol = ELFHelper.FindSymbol (dynsym, SymbolName);
+			if (symbol != null) {
+				return ((ulong)symbol.Value, (ulong)symbol.Size, ELFPayloadError.None);
+			}
+		}
+
+		return (0, 0, ELFPayloadError.NoEmbeddedStore);
+	}
+
+	public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSectionOffsetAndSize (Stream stream)
+	{
+		(bool is64, IELF? elf, ELFPayloadError error) = TryOpenELF (stream);
+		if (elf == null || error != ELFPayloadError.None) {
+			return ReturnError (elf, error);
+		}
+
+		if (!elf.TryGetSection ("payload", out ISection? payloadSection)) {
+			return ReturnError (elf, ELFPayloadError.NoPayloadSection);
+		}
+
 		ulong offset;
 		ulong size;
 
@@ -84,13 +127,13 @@ public static (ulong offset, ulong size, ELFPayloadError error) FindELFPayloadSe
 		{
 			return ((ulong)payload.Offset, (ulong)payload.Size);
 		}
+	}
 
-		(ulong offset, ulong size, ELFPayloadError error) ReturnError (IELF? elf, ELFPayloadError error)
-		{
-			elf?.Dispose ();
+	static (ulong offset, ulong size, ELFPayloadError error) ReturnError (IELF? elf, ELFPayloadError error)
+	{
+		elf?.Dispose ();
 
-			return (0, 0, error);
-		}
+		return (0, 0, error);
 	}
 
 	public static (FileFormat format, FileInfo? info) DetectFileFormat (string path)
diff --git a/tools/assembly-store-reader-mk2/assembly-store-reader.csproj b/tools/assembly-store-reader-mk2/assembly-store-reader.csproj
index ec4c90fecb1..3bd2852d04e 100644
--- a/tools/assembly-store-reader-mk2/assembly-store-reader.csproj
+++ b/tools/assembly-store-reader-mk2/assembly-store-reader.csproj
@@ -24,6 +24,7 @@
 
   
     
+