diff --git a/eng/testing/tests.mobile.targets b/eng/testing/tests.mobile.targets index 039b0d9a39b3c5..38a4080de10e36 100644 --- a/eng/testing/tests.mobile.targets +++ b/eng/testing/tests.mobile.targets @@ -147,6 +147,7 @@ MainAssembly="$(PublishDir)WasmTestRunner.dll" MainJS="$(MonoProjectRoot)\wasm\runtime-test.js" ExtraAssemblies="@(ExtraAssemblies)" + SatelliteAssemblies="@(WasmSatelliteAssemblies)" FilesToIncludeInFileSystem="@(WasmFilesToIncludeInFileSystem)" AssemblySearchPaths="@(AssemblySearchPaths)" /> diff --git a/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs b/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs index 7851469527bced..c8d74fb0177397 100644 --- a/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs +++ b/src/libraries/System.Runtime.Loader/tests/SatelliteAssemblies.cs @@ -189,8 +189,7 @@ public static IEnumerable SatelliteLoadsCorrectly_TestData() [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] [MemberData(nameof(SatelliteLoadsCorrectly_TestData))] - [ActiveIssue("https://github.com/dotnet/runtime/issues/39379", TestPlatforms.Browser)] - public void SatelliteLoadsCorrectly(string alc, string assemblyName, string culture) + public void SatelliteLoadsCorrectly_FromName(string alc, string assemblyName, string culture) { AssemblyName satelliteAssemblyName = new AssemblyName(assemblyName + ".resources"); satelliteAssemblyName.CultureInfo = new CultureInfo(culture); @@ -206,5 +205,24 @@ public void SatelliteLoadsCorrectly(string alc, string assemblyName, string cult Assert.Equal(AssemblyLoadContext.GetLoadContext(parentAssembly), AssemblyLoadContext.GetLoadContext(satelliteAssembly)); } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotInvariantGlobalization))] + [MemberData(nameof(SatelliteLoadsCorrectly_TestData))] + public void SatelliteLoadsCorrectly_FromPath(string alc, string assemblyName, string culture) + { + string satelliteAssemblyName = assemblyName + ".resources.dll"; + + AssemblyLoadContext assemblyLoadContext = contexts[alc]; + + string assemblyPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, culture, satelliteAssemblyName); + Assembly satelliteAssembly = assemblyLoadContext.LoadFromAssemblyPath(assemblyPath); + + Assert.NotNull(satelliteAssembly); + + AssemblyName parentAssemblyName = new AssemblyName(assemblyName); + Assembly parentAssembly = assemblyLoadContext.LoadFromAssemblyName(parentAssemblyName); + + Assert.Equal(culture, satelliteAssembly.GetName().CultureName); + } } } diff --git a/src/mono/mono/metadata/assembly.c b/src/mono/mono/metadata/assembly.c index dcbfbf7d8d63a2..8892553aaa3c98 100644 --- a/src/mono/mono/metadata/assembly.c +++ b/src/mono/mono/metadata/assembly.c @@ -375,6 +375,8 @@ mono_assemblies_unlock () /* If defined, points to the bundled assembly information */ static const MonoBundledAssembly **bundles; +static const MonoBundledSatelliteAssembly **satellite_bundles; + static mono_mutex_t assembly_binding_mutex; /* Loaded assembly binding info */ @@ -1643,18 +1645,18 @@ load_reference_by_aname_individual_asmctx (MonoAssemblyName *aname, MonoAssembly } #else static MonoAssembly * -search_bundle_for_assembly (MonoAssemblyLoadContext *alc, MonoAssemblyName *aname) +search_bundle_for_assembly (MonoAssemblyLoadContext *alc, MonoAssemblyName *aname, gboolean is_satellite) { - if (bundles == NULL) + if ((bundles == NULL && !is_satellite) || (satellite_bundles == NULL && is_satellite)) return NULL; MonoImageOpenStatus status; MonoImage *image; MonoAssemblyLoadRequest req; - image = mono_assembly_open_from_bundle (alc, aname->name, &status, FALSE); + image = mono_assembly_open_from_bundle (alc, aname->name, &status, FALSE, aname->culture); if (!image) { char *name = g_strdup_printf ("%s.dll", aname->name); - image = mono_assembly_open_from_bundle (alc, name, &status, FALSE); + image = mono_assembly_open_from_bundle (alc, name, &status, FALSE, aname->culture); } if (image) { mono_assembly_request_prepare_load (&req, MONO_ASMCTX_DEFAULT, alc); @@ -1709,8 +1711,8 @@ netcore_load_reference (MonoAssemblyName *aname, MonoAssemblyLoadContext *alc, M goto leave; } - if (bundles != NULL) { - reference = search_bundle_for_assembly (alc, aname); + if (bundles != NULL || satellite_bundles != NULL) { + reference = search_bundle_for_assembly (alc, aname, is_satellite); if (reference) { mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly found in the bundle: '%s'.", aname->name); goto leave; @@ -1769,6 +1771,25 @@ netcore_load_reference (MonoAssemblyName *aname, MonoAssemblyLoadContext *alc, M return reference; } +static MonoImage * +open_from_satellite_bundle (MonoAssemblyLoadContext *alc, const char *filename, MonoImageOpenStatus *status, gboolean refonly, const char *culture) +{ + if (!satellite_bundles) + return NULL; + + MonoImage *image = NULL; + char *name = g_strdup (filename); + for (int i = 0; !image && satellite_bundles [i]; ++i) { + if (strcmp (satellite_bundles [i]->name, name) == 0 && strcmp (satellite_bundles [i]->culture, culture) == 0) { + image = mono_image_open_from_data_internal (alc, (char *)satellite_bundles [i]->data, satellite_bundles [i]->size, FALSE, status, refonly, FALSE, name, NULL); + break; + } + } + + g_free (name); + return image; +} + #endif /* ENABLE_NETCORE */ /** @@ -2514,6 +2535,30 @@ absolute_dir (const gchar *filename) return res; } +static MonoImage * +open_from_bundle_internal (MonoAssemblyLoadContext *alc, const char *filename, MonoImageOpenStatus *status, gboolean refonly, gboolean is_satellite) +{ + if (!bundles) + return NULL; + + MonoImage *image = NULL; + char *name = is_satellite ? g_strdup (filename) : g_path_get_basename (filename); + for (int i = 0; !image && bundles [i]; ++i) { + if (strcmp (bundles [i]->name, name) == 0) { +#ifdef ENABLE_NETCORE + // Since bundled images don't exist on disk, don't give them a legit filename + image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name, NULL); +#else + image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name, name); +#endif + break; + } + } + + g_free (name); + return image; +} + /** * mono_assembly_open_from_bundle: * \param filename Filename requested @@ -2524,44 +2569,36 @@ absolute_dir (const gchar *filename) * returns NULL */ MonoImage * -mono_assembly_open_from_bundle (MonoAssemblyLoadContext *alc, const char *filename, MonoImageOpenStatus *status, gboolean refonly) +mono_assembly_open_from_bundle (MonoAssemblyLoadContext *alc, const char *filename, MonoImageOpenStatus *status, gboolean refonly, const char *culture) { - int i; - char *name; - gchar *lowercase_filename; - MonoImage *image = NULL; - gboolean is_satellite = FALSE; /* * we do a very simple search for bundled assemblies: it's not a general * purpose assembly loading mechanism. */ - + MonoImage *image = NULL; +#ifndef ENABLE_NETCORE if (!bundles) return NULL; - lowercase_filename = g_utf8_strdown (filename, -1); - is_satellite = g_str_has_suffix (lowercase_filename, ".resources.dll"); + gchar *lowercase_filename = g_utf8_strdown (filename, -1); + gboolean is_satellite = g_str_has_suffix (lowercase_filename, ".resources.dll"); g_free (lowercase_filename); - name = g_path_get_basename (filename); - for (i = 0; !image && bundles [i]; ++i) { - if (strcmp (bundles [i]->name, is_satellite ? filename : name) == 0) { -#ifdef ENABLE_NETCORE - // Since bundled images don't exist on disk, don't give them a legit filename - image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name, NULL); + image = open_from_bundle_internal (alc, filename, status, refonly, is_satellite); #else - image = mono_image_open_from_data_internal (alc, (char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, FALSE, name, name); -#endif - break; - } + gboolean is_satellite = culture && culture [0] != 0;; + if (is_satellite) + { + image = open_from_satellite_bundle (alc, filename, status, refonly, culture); + } else { + image = open_from_bundle_internal (alc, filename, status, refonly, FALSE); } +#endif + if (image) { mono_image_addref (image); - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly Loader loaded assembly from bundle: '%s'.", is_satellite ? filename : name); - g_free (name); - return image; + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Assembly Loader loaded assembly from bundle: '%s'.", filename); } - g_free (name); - return NULL; + return image; } /** @@ -2715,8 +2752,9 @@ mono_assembly_request_open (const char *filename, const MonoAssemblyOpenRequest // If VM built with mkbundle loaded_from_bundle = FALSE; - if (bundles != NULL) { - image = mono_assembly_open_from_bundle (load_req.alc, fname, status, refonly); + if (bundles != NULL || satellite_bundles != NULL) { + /* We don't know the culture of the filename we're loading here, so this call is not culture aware. */ + image = mono_assembly_open_from_bundle (load_req.alc, fname, status, refonly, NULL); loaded_from_bundle = image != NULL; } @@ -5300,6 +5338,31 @@ mono_register_bundled_assemblies (const MonoBundledAssembly **assemblies) bundles = assemblies; } +/** + * mono_create_new_bundled_satellite_assembly: + */ +MonoBundledSatelliteAssembly * +mono_create_new_bundled_satellite_assembly (const char *name, const char *culture, const unsigned char *data, unsigned int size) +{ + MonoBundledSatelliteAssembly *satellite_assembly = g_new0 (MonoBundledSatelliteAssembly, 1); + satellite_assembly->name = strdup (name); + satellite_assembly->culture = strdup (culture); + satellite_assembly->data = data; + satellite_assembly->size = size; + return satellite_assembly; +} + +/** + * mono_register_bundled_satellite_assemblies: + */ +void +mono_register_bundled_satellite_assemblies (const MonoBundledSatelliteAssembly **assemblies) +{ +#ifdef ENABLE_NETCORE + satellite_bundles = assemblies; +#endif +} + #define MONO_DECLSEC_FORMAT_10 0x3C #define MONO_DECLSEC_FORMAT_20 0x2E #define MONO_DECLSEC_FIELD 0x53 diff --git a/src/mono/mono/metadata/domain-internals.h b/src/mono/mono/metadata/domain-internals.h index 0985b671df7f52..4b79f0ceac1619 100644 --- a/src/mono/mono/metadata/domain-internals.h +++ b/src/mono/mono/metadata/domain-internals.h @@ -651,7 +651,8 @@ mono_domain_assembly_open_internal (MonoDomain *domain, MonoAssemblyLoadContext MonoImage *mono_assembly_open_from_bundle (MonoAssemblyLoadContext *alc, const char *filename, MonoImageOpenStatus *status, - gboolean refonly); + gboolean refonly, + const char *culture); MonoAssembly * mono_try_assembly_resolve (MonoAssemblyLoadContext *alc, const char *fname, MonoAssembly *requesting, gboolean refonly, MonoError *error); diff --git a/src/mono/mono/metadata/domain.c b/src/mono/mono/metadata/domain.c index 4f4294adda2e3c..0faba3dcbe91cf 100644 --- a/src/mono/mono/metadata/domain.c +++ b/src/mono/mono/metadata/domain.c @@ -582,7 +582,7 @@ mono_init_internal (const char *filename, const char *exe_filename, const char * runtimes = get_runtimes_from_exe (exe_filename, &exe_image); #ifdef HOST_WIN32 if (!exe_image) { - exe_image = mono_assembly_open_from_bundle (mono_domain_default_alc (domain), exe_filename, NULL, FALSE); + exe_image = mono_assembly_open_from_bundle (mono_domain_default_alc (domain), exe_filename, NULL, FALSE, NULL); if (!exe_image) exe_image = mono_image_open (exe_filename, NULL); } @@ -1974,7 +1974,7 @@ get_runtimes_from_exe (const char *file, MonoImage **out_image) } /* Look for a runtime with the exact version */ - image = mono_assembly_open_from_bundle (mono_domain_default_alc (mono_domain_get ()), file, NULL, FALSE); + image = mono_assembly_open_from_bundle (mono_domain_default_alc (mono_domain_get ()), file, NULL, FALSE, NULL); if (image == NULL) image = mono_image_open (file, NULL); diff --git a/src/mono/mono/metadata/image-internals.h b/src/mono/mono/metadata/image-internals.h index 1ec204b335b006..22965a70548242 100644 --- a/src/mono/mono/metadata/image-internals.h +++ b/src/mono/mono/metadata/image-internals.h @@ -9,6 +9,9 @@ #include #include +char * +mono_image_get_name_with_culture_if_needed (MonoImage *image); + MonoImage* mono_image_loaded_internal (MonoAssemblyLoadContext *alc, const char *name, mono_bool refonly); diff --git a/src/mono/mono/metadata/image.c b/src/mono/mono/metadata/image.c index c82e2c516ed513..2d06947676e90a 100644 --- a/src/mono/mono/metadata/image.c +++ b/src/mono/mono/metadata/image.c @@ -1846,34 +1846,75 @@ mono_image_loaded_by_guid (const char *guid) return mono_image_loaded_by_guid_internal (guid, FALSE); } +static const char * +get_image_culture (MonoImage *image) +{ + MonoTableInfo *t = &image->tables [MONO_TABLE_ASSEMBLY]; + if (!t->rows) + return NULL; + + guint32 cols [MONO_ASSEMBLY_SIZE]; + mono_metadata_decode_row (t, 0, cols, MONO_ASSEMBLY_SIZE); + return mono_metadata_string_heap (image, cols [MONO_ASSEMBLY_CULTURE]); +} + +char * +mono_image_get_name_with_culture_if_needed (MonoImage *image) +{ + if (!g_str_has_prefix (image->name, "data-") && + !g_path_is_absolute (image->name)) + { + const char *culture = get_image_culture (image); + + if (culture && culture [0] != 0) + return g_strdup_printf ("%s/%s", culture, image->name); + } + + return NULL; +} + static MonoImage * register_image (MonoLoadedImages *li, MonoImage *image, gboolean *problematic) { MonoImage *image2; + char *name = image->name; +#ifdef ENABLE_NETCORE + /* Since we register cultures by file name, we need to make this culture aware for + satellite assemblies */ + char *name_with_culture = mono_image_get_name_with_culture_if_needed (image); + if (name_with_culture) + name = name_with_culture; +#endif GHashTable *loaded_images = mono_loaded_images_get_hash (li, image->ref_only); mono_images_lock (); - image2 = (MonoImage *)g_hash_table_lookup (loaded_images, image->name); + image2 = (MonoImage *)g_hash_table_lookup (loaded_images, name); if (image2) { /* Somebody else beat us to it */ mono_image_addref (image2); mono_images_unlock (); mono_image_close (image); +#ifdef ENABLE_NETCORE + g_free (name_with_culture); +#endif return image2; } GHashTable *loaded_images_by_name = mono_loaded_images_get_by_name_hash (li, image->ref_only); - g_hash_table_insert (loaded_images, image->name, image); + g_hash_table_insert (loaded_images, name, image); if (image->assembly_name && (g_hash_table_lookup (loaded_images_by_name, image->assembly_name) == NULL)) g_hash_table_insert (loaded_images_by_name, (char *) image->assembly_name, image); mono_images_unlock (); if (mono_is_problematic_image (image)) { - mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Registering %s, problematic image '%s'", image->ref_only ? "REFONLY" : "default", image->name); + mono_trace (G_LOG_LEVEL_DEBUG, MONO_TRACE_ASSEMBLY, "Registering %s, problematic image '%s'", image->ref_only ? "REFONLY" : "default", name); if (problematic) *problematic = TRUE; } +#ifdef ENABLE_NETCORE + g_free (name_with_culture); +#endif return image; } @@ -2029,6 +2070,11 @@ mono_image_open_full (const char *fname, MonoImageOpenStatus *status, gboolean r return mono_image_open_a_lot (alc, fname, status, refonly, FALSE); } +/** + * mono_image_open_a_lot_parameterized + * this API is not culture aware, so if we load a satellite assembly for one culture by name + * via this API, and then try to load it with another culture we will return the first one. + */ static MonoImage * mono_image_open_a_lot_parameterized (MonoLoadedImages *li, MonoAssemblyLoadContext *alc, const char *fname, MonoImageOpenStatus *status, gboolean refonly, gboolean load_from_context, gboolean *problematic) { diff --git a/src/mono/mono/metadata/loaded-images.c b/src/mono/mono/metadata/loaded-images.c index 91f3fd51c69ad8..c0fa0d5e6aa3ac 100644 --- a/src/mono/mono/metadata/loaded-images.c +++ b/src/mono/mono/metadata/loaded-images.c @@ -1,6 +1,7 @@ #include "config.h" #include "mono/metadata/loaded-images-internals.h" +#include "mono/metadata/image-internals.h" #include "mono/metadata/metadata-internals.h" #include "mono/utils/mono-logger-internals.h" @@ -78,6 +79,7 @@ loaded_images_get_owner (MonoImage *image) gboolean mono_loaded_images_remove_image (MonoImage *image) { + char *name = NULL; gboolean proceed = FALSE; /* * Atomically decrement the refcount and remove ourselves from the hash tables, so @@ -100,15 +102,25 @@ mono_loaded_images_remove_image (MonoImage *image) loaded_images = mono_loaded_images_get_hash (li, image->ref_only); loaded_images_by_name = mono_loaded_images_get_by_name_hash (li, image->ref_only); - image2 = (MonoImage *)g_hash_table_lookup (loaded_images, image->name); + + name = image->name; +#ifdef ENABLE_NETCORE + char *name_with_culture = mono_image_get_name_with_culture_if_needed (image); + if (name_with_culture) + name = name_with_culture; +#endif + image2 = (MonoImage *)g_hash_table_lookup (loaded_images, name); if (image == image2) { /* This is not true if we are called from mono_image_open () */ - g_hash_table_remove (loaded_images, image->name); + g_hash_table_remove (loaded_images, name); } if (image->assembly_name && (g_hash_table_lookup (loaded_images_by_name, image->assembly_name) == image)) g_hash_table_remove (loaded_images_by_name, (char *) image->assembly_name); proceed = TRUE; +#ifdef ENABLE_NETCORE + g_free (name_with_culture); +#endif done: mono_images_unlock (); diff --git a/src/mono/mono/metadata/loader-internals.h b/src/mono/mono/metadata/loader-internals.h index 43134540d59ab7..b8ee8c8cd1cd91 100644 --- a/src/mono/mono/metadata/loader-internals.h +++ b/src/mono/mono/metadata/loader-internals.h @@ -26,6 +26,13 @@ typedef struct _MonoLoadedImages MonoLoadedImages; typedef struct _MonoAssemblyLoadContext MonoAssemblyLoadContext; +struct _MonoBundledSatelliteAssembly { + const char *name; + const char *culture; + const unsigned char *data; + unsigned int size; +}; + #ifndef DISABLE_DLLMAP typedef struct _MonoDllMap MonoDllMap; struct _MonoDllMap { diff --git a/src/mono/mono/metadata/mono-private-unstable.h b/src/mono/mono/metadata/mono-private-unstable.h index c7d2531a7c744c..562e9f1c4df635 100644 --- a/src/mono/mono/metadata/mono-private-unstable.h +++ b/src/mono/mono/metadata/mono-private-unstable.h @@ -27,4 +27,12 @@ mono_install_assembly_preload_hook_v3 (MonoAssemblyPreLoadFuncV3 func, void *use MONO_API MONO_RT_EXTERNAL_ONLY MonoAssemblyLoadContextGCHandle mono_alc_get_default_gchandle (void); +typedef struct _MonoBundledSatelliteAssembly MonoBundledSatelliteAssembly; + +MONO_API void +mono_register_bundled_satellite_assemblies (const MonoBundledSatelliteAssembly **assemblies); + +MONO_API MonoBundledSatelliteAssembly * +mono_create_new_bundled_satellite_assembly (const char *name, const char *culture, const unsigned char *data, unsigned int size); + #endif /*__MONO_METADATA_MONO_PRIVATE_UNSTABLE_H__*/ diff --git a/src/mono/wasm/runtime/binding_support.js b/src/mono/wasm/runtime/binding_support.js index 6ce96816cf180d..3e710354976665 100644 --- a/src/mono/wasm/runtime/binding_support.js +++ b/src/mono/wasm/runtime/binding_support.js @@ -60,6 +60,7 @@ var BindingSupportLib = { this.mono_array_get = Module.cwrap ('mono_wasm_array_get', 'number', ['number', 'number']); this.mono_obj_array_new = Module.cwrap ('mono_wasm_obj_array_new', 'number', ['number']); this.mono_obj_array_set = Module.cwrap ('mono_wasm_obj_array_set', 'void', ['number', 'number', 'number']); + this.mono_wasm_register_bundled_satellite_assemblies = Module.cwrap ('mono_wasm_register_bundled_satellite_assemblies', 'void', [ ]); this.mono_unbox_enum = Module.cwrap ('mono_wasm_unbox_enum', 'number', ['number']); this.assembly_get_entry_point = Module.cwrap ('mono_wasm_assembly_get_entry_point', 'number', ['number']); diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 264a5b9d7eb643..24daadef584fdc 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -15,6 +15,9 @@ #include // FIXME: unavailable in emscripten // #include + +#include + #include #include #include @@ -196,6 +199,26 @@ mono_wasm_add_assembly (const char *name, const unsigned char *data, unsigned in return mono_has_pdb_checksum (data, size); } +typedef struct WasmSatelliteAssembly_ WasmSatelliteAssembly; + +struct WasmSatelliteAssembly_ { + MonoBundledSatelliteAssembly *assembly; + WasmSatelliteAssembly *next; +}; + +static WasmSatelliteAssembly *satellite_assemblies; +static int satellite_assembly_count; + +EMSCRIPTEN_KEEPALIVE void +mono_wasm_add_satellite_assembly (const char *name, const char *culture, const unsigned char *data, unsigned int size) +{ + WasmSatelliteAssembly *entry = g_new0 (WasmSatelliteAssembly, 1); + entry->assembly = mono_create_new_bundled_satellite_assembly (name, culture, data, size); + entry->next = satellite_assemblies; + satellite_assemblies = entry; + ++satellite_assembly_count; +} + EMSCRIPTEN_KEEPALIVE void mono_wasm_setenv (const char *name, const char *value) { @@ -383,6 +406,23 @@ void mono_initialize_internals () } +EMSCRIPTEN_KEEPALIVE void +mono_wasm_register_bundled_satellite_assemblies () +{ + /* In legacy satellite_assembly_count is always false */ + if (satellite_assembly_count) { + MonoBundledSatelliteAssembly **satellite_bundle_array = g_new0 (MonoBundledSatelliteAssembly *, satellite_assembly_count + 1); + WasmSatelliteAssembly *cur = satellite_assemblies; + int i = 0; + while (cur) { + satellite_bundle_array [i] = cur->assembly; + cur = cur->next; + ++i; + } + mono_register_bundled_satellite_assemblies ((const MonoBundledSatelliteAssembly **)satellite_bundle_array); + } +} + EMSCRIPTEN_KEEPALIVE void mono_wasm_load_runtime (const char *unused, int debug_level) { @@ -470,6 +510,7 @@ mono_wasm_load_runtime (const char *unused, int debug_level) mono_register_bundled_assemblies ((const MonoBundledAssembly **)bundle_array); } + mono_wasm_register_bundled_satellite_assemblies (); mono_trace_init (); mono_trace_set_log_handler (wasm_logger, NULL); root_domain = mono_jit_init_version ("mono", "v4.0.30319"); diff --git a/src/mono/wasm/runtime/library_mono.js b/src/mono/wasm/runtime/library_mono.js index eccc137f7b1ab5..2b8e37535650fc 100644 --- a/src/mono/wasm/runtime/library_mono.js +++ b/src/mono/wasm/runtime/library_mono.js @@ -1219,6 +1219,7 @@ var MonoSupportLib = { var offset = null; switch (asset.behavior) { + case "resource": case "assembly": ctx.loaded_files.push ({ url: url, file: virtualName}); case "heap": @@ -1278,6 +1279,9 @@ var MonoSupportLib = { else console.error ("Error loading ICU asset", asset.name); } + else if (asset.behavior === "resource") { + ctx.mono_wasm_add_satellite_assembly (virtualName, asset.culture, offset, bytes.length); + } }, // deprecated @@ -1321,6 +1325,7 @@ var MonoSupportLib = { // behavior: (required) determines how the asset will be handled once loaded: // "heap": store asset into the native heap // "assembly": load asset as a managed assembly (or debugging information) + // "resource": load asset as a managed resource assembly // "icu": load asset as an ICU data archive // "vfs": load asset into the virtual filesystem (for fopen, File.Open, etc) // load_remote: (optional) if true, an attempt will be made to load the asset @@ -1431,6 +1436,7 @@ var MonoSupportLib = { tracing: args.diagnostic_tracing || false, pending_count: args.assets.length, mono_wasm_add_assembly: Module.cwrap ('mono_wasm_add_assembly', 'number', ['string', 'number', 'number']), + mono_wasm_add_satellite_assembly: Module.cwrap ('mono_wasm_add_satellite_assembly', 'void', ['string', 'string', 'number', 'number']), loaded_assets: Object.create (null), // dlls and pdbs, used by blazor and the debugger loaded_files: [], @@ -1523,6 +1529,10 @@ var MonoSupportLib = { if (sourcePrefix.trim() === "") { if (asset.behavior === "assembly") attemptUrl = locateFile (args.assembly_root + "/" + asset.name); + else if (asset.behavior === "resource") { + var path = asset.culture !== '' ? `${asset.culture}/${asset.name}` : asset.name; + attemptUrl = locateFile (args.assembly_root + "/" + path); + } else attemptUrl = asset.name; } else { diff --git a/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs b/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs index 3fa63daf9ed67b..c011a324a0dd86 100644 --- a/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs +++ b/tools-local/tasks/mobile.tasks/WasmAppBuilder/WasmAppBuilder.cs @@ -36,6 +36,7 @@ public class WasmAppBuilder : Task public ITaskItem[]? AssemblySearchPaths { get; set; } public int DebugLevel { get; set; } public ITaskItem[]? ExtraAssemblies { get; set; } + public ITaskItem[]? SatelliteAssemblies { get; set; } public ITaskItem[]? FilesToIncludeInFileSystem { get; set; } public ITaskItem[]? RemoteSources { get; set; } public bool InvariantGlobalization { get; set; } @@ -72,6 +73,17 @@ private class AssemblyEntry : AssetEntry public AssemblyEntry(string name) : base(name, "assembly") {} } + private class SatelliteAssemblyEntry : AssetEntry + { + public SatelliteAssemblyEntry(string name, string culture) : base(name, "resource") + { + CultureName = culture; + } + + [JsonPropertyName("culture")] + public string CultureName { get; set; } + } + private class VfsEntry : AssetEntry { public VfsEntry(string name) : base(name, "vfs") {} [JsonPropertyName("virtual_path")] @@ -147,17 +159,31 @@ public override bool Execute () File.WriteAllText(Path.Join(AppDir, "index.html"), html); foreach (var assembly in _assemblies.Values) { - config.Assets.Add(new AssemblyEntry (Path.GetFileName(assembly.Location))); + config.Assets.Add(new AssemblyEntry(Path.GetFileName(assembly.Location))); if (DebugLevel > 0) { var pdb = assembly.Location; pdb = Path.ChangeExtension(pdb, ".pdb"); if (File.Exists(pdb)) - config.Assets.Add(new AssemblyEntry (Path.GetFileName(pdb))); + config.Assets.Add(new AssemblyEntry(Path.GetFileName(pdb))); } } config.DebugLevel = DebugLevel; + if (SatelliteAssemblies != null) + { + foreach (var assembly in SatelliteAssemblies) + { + string culture = assembly.GetMetadata("CultureName") ?? string.Empty; + string fullPath = assembly.GetMetadata("Identity"); + string name = Path.GetFileName(fullPath); + string directory = Path.Join(AppDir, config.AssemblyRoot, culture); + Directory.CreateDirectory(directory); + File.Copy(fullPath, Path.Join(directory, name), true); + config.Assets.Add(new SatelliteAssemblyEntry(name, culture)); + } + } + if (FilesToIncludeInFileSystem != null) { string supportFilesDir = Path.Join(AppDir, "supportFiles");