And neutral logic too, and the combinations with custom privatePath
config.
This fixes a crash with Mafia III launcher, when it tries to load its
localized strings from culture-specific assemblies to display its
warning popup message.
Signed-off-by: Rémi Bernon <rbernon(a)codeweavers.com>
---
dlls/mscoree/metahost.c | 75 ++++++--
dlls/mscoree/tests/loadpaths.dll.cs | 33 ++++
dlls/mscoree/tests/loadpaths.exe.config | 8 +
dlls/mscoree/tests/loadpaths.exe.cs | 28 +++
dlls/mscoree/tests/mscoree.c | 226 ++++++++++++++++++++++++
dlls/mscoree/tests/resource.rc | 9 +
6 files changed, 360 insertions(+), 19 deletions(-)
create mode 100644 dlls/mscoree/tests/loadpaths.dll.cs
create mode 100644 dlls/mscoree/tests/loadpaths.exe.config
create mode 100644 dlls/mscoree/tests/loadpaths.exe.cs
diff --git a/dlls/mscoree/metahost.c b/dlls/mscoree/metahost.c
index a3ed1534753..d9b599fadc7 100644
--- a/dlls/mscoree/metahost.c
+++ b/dlls/mscoree/metahost.c
@@ -29,6 +29,7 @@
#include "winreg.h"
#include "winternl.h"
#include "ole2.h"
+#include "shlwapi.h"
#include "corerror.h"
#include "cor.h"
@@ -87,6 +88,7 @@ typedef void (CDECL *MonoProfilerRuntimeShutdownBeginCallback) (MonoProfiler *pr
MonoImage* (CDECL *mono_assembly_get_image)(MonoAssembly *assembly);
MonoAssembly* (CDECL *mono_assembly_load_from)(MonoImage *image, const char *fname, MonoImageOpenStatus *status);
const char* (CDECL *mono_assembly_name_get_name)(MonoAssemblyName *aname);
+const char* (CDECL *mono_assembly_name_get_culture)(MonoAssemblyName *aname);
MonoAssembly* (CDECL *mono_assembly_open)(const char *filename, MonoImageOpenStatus *status);
void (CDECL *mono_callspec_set_assembly)(MonoAssembly *assembly);
MonoClass* (CDECL *mono_class_from_mono_type)(MonoType *type);
@@ -193,6 +195,7 @@ static HRESULT load_mono(LPCWSTR mono_path)
LOAD_MONO_FUNCTION(mono_assembly_get_image);
LOAD_MONO_FUNCTION(mono_assembly_load_from);
LOAD_MONO_FUNCTION(mono_assembly_name_get_name);
+ LOAD_MONO_FUNCTION(mono_assembly_name_get_culture);
LOAD_MONO_FUNCTION(mono_assembly_open);
LOAD_MONO_FUNCTION(mono_config_parse);
LOAD_MONO_FUNCTION(mono_class_from_mono_type);
@@ -1651,24 +1654,48 @@ HRESULT get_file_from_strongname(WCHAR* stringnameW, WCHAR* assemblies_path, int
return hr;
}
+static MonoAssembly* mono_assembly_try_load(WCHAR *path)
+{
+ MonoAssembly *result = NULL;
+ MonoImageOpenStatus stat;
+ char *pathA;
+
+ if (!(pathA = WtoA(path))) return NULL;
+
+ result = mono_assembly_open(pathA, &stat);
+ HeapFree(GetProcessHeap(), 0, pathA);
+
+ if (result) TRACE("found: %s\n", debugstr_w(path));
+ return result;
+}
+
static MonoAssembly* CDECL mono_assembly_preload_hook_fn(MonoAssemblyName *aname, char **assemblies_path, void *user_data)
{
HRESULT hr;
MonoAssembly *result=NULL;
char *stringname=NULL;
const char *assemblyname;
- LPWSTR stringnameW;
- int stringnameW_size;
+ const char *culture;
+ LPWSTR stringnameW, cultureW;
+ int stringnameW_size, cultureW_size;
WCHAR path[MAX_PATH];
char *pathA;
MonoImageOpenStatus stat;
DWORD search_flags;
int i;
static const WCHAR dotdllW[] = {'.','d','l','l',0};
- static const WCHAR slashW[] = {'\\',0};
+ static const WCHAR dotexeW[] = {'.','e','x','e',0};
stringname = mono_stringify_assembly_name(aname);
assemblyname = mono_assembly_name_get_name(aname);
+ culture = mono_assembly_name_get_culture(aname);
+ if (culture)
+ {
+ cultureW_size = MultiByteToWideChar(CP_UTF8, 0, culture, -1, NULL, 0);
+ cultureW = HeapAlloc(GetProcessHeap(), 0, cultureW_size * sizeof(WCHAR));
+ if (cultureW) MultiByteToWideChar(CP_UTF8, 0, culture, -1, cultureW, cultureW_size);
+ }
+ else cultureW = NULL;
TRACE("%s\n", debugstr_a(stringname));
@@ -1684,26 +1711,34 @@ static MonoAssembly* CDECL mono_assembly_preload_hook_fn(MonoAssemblyName *aname
MultiByteToWideChar(CP_UTF8, 0, assemblyname, -1, stringnameW, stringnameW_size);
for (i = 0; private_path[i] != NULL; i++)
{
+ /* This is the lookup order used in Mono */
+ /* 1st try: [culture]/[name].dll (culture may be empty) */
wcscpy(path, private_path[i]);
- wcscat(path, slashW);
- wcscat(path, stringnameW);
+ if (cultureW) PathAppendW(path, cultureW);
+ PathAppendW(path, stringnameW);
wcscat(path, dotdllW);
- pathA = WtoA(path);
- if (pathA)
- {
- result = mono_assembly_open(pathA, &stat);
- if (result)
- {
- TRACE("found: %s\n", debugstr_w(path));
- HeapFree(GetProcessHeap(), 0, pathA);
- HeapFree(GetProcessHeap(), 0, stringnameW);
- mono_free(stringname);
- return result;
- }
- HeapFree(GetProcessHeap(), 0, pathA);
- }
+ result = mono_assembly_try_load(path);
+ if (result) break;
+
+ /* 2nd try: [culture]/[name].exe (culture may be empty) */
+ wcscpy(path + wcslen(path) - wcslen(dotdllW), dotexeW);
+ result = mono_assembly_try_load(path);
+ if (result) break;
+
+ /* 3rd try: [culture]/[name]/[name].dll (culture may be empty) */
+ path[wcslen(path) - wcslen(dotexeW)] = 0;
+ PathAppendW(path, stringnameW);
+ wcscat(path, dotdllW);
+ result = mono_assembly_try_load(path);
+ if (result) break;
+
+ /* 4th try: [culture]/[name]/[name].exe (culture may be empty) */
+ wcscpy(path + wcslen(path) - wcslen(dotdllW), dotexeW);
+ result = mono_assembly_try_load(path);
+ if (result) break;
}
HeapFree(GetProcessHeap(), 0, stringnameW);
+ if (result) goto done;
}
}
@@ -1745,6 +1780,8 @@ static MonoAssembly* CDECL mono_assembly_preload_hook_fn(MonoAssemblyName *aname
else
TRACE("skipping Windows GAC search due to override setting\n");
+done:
+ if (cultureW) HeapFree(GetProcessHeap(), 0, cultureW);
mono_free(stringname);
return result;
diff --git a/dlls/mscoree/tests/loadpaths.dll.cs b/dlls/mscoree/tests/loadpaths.dll.cs
new file mode 100644
index 00000000000..99732b011d8
--- /dev/null
+++ b/dlls/mscoree/tests/loadpaths.dll.cs
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 Rémi Bernon for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+using System.Reflection;
+
+#if NEUTRAL
+[assembly: AssemblyCulture("")]
+#else
+[assembly: AssemblyCulture("en")]
+#endif
+
+namespace LoadPaths
+{
+ public class Test2
+ {
+ public int Foo() { return 0; }
+ }
+}
diff --git a/dlls/mscoree/tests/loadpaths.exe.config b/dlls/mscoree/tests/loadpaths.exe.config
new file mode 100644
index 00000000000..be6d9fb95cd
--- /dev/null
+++ b/dlls/mscoree/tests/loadpaths.exe.config
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <probing privatePath="private"/>
+ </assemblyBinding>
+ </runtime>
+</configuration>
diff --git a/dlls/mscoree/tests/loadpaths.exe.cs b/dlls/mscoree/tests/loadpaths.exe.cs
new file mode 100644
index 00000000000..22c32603c59
--- /dev/null
+++ b/dlls/mscoree/tests/loadpaths.exe.cs
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 Rémi Bernon for CodeWeavers
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+namespace LoadPaths
+{
+ public static class Test
+ {
+ static int Main(string[] args)
+ {
+ return new Test2().Foo();
+ }
+ }
+}
diff --git a/dlls/mscoree/tests/mscoree.c b/dlls/mscoree/tests/mscoree.c
index 26c06246a94..4be3da8c731 100644
--- a/dlls/mscoree/tests/mscoree.c
+++ b/dlls/mscoree/tests/mscoree.c
@@ -538,6 +538,229 @@ static void test_createinstance(void)
}
}
+static BOOL write_resource(const WCHAR *resource, const WCHAR *filename)
+{
+ HANDLE file;
+ HRSRC rsrc;
+ void *data;
+ DWORD size;
+ BOOL ret;
+
+ rsrc = FindResourceW(GetModuleHandleW(NULL), resource, MAKEINTRESOURCEW(RT_RCDATA));
+ if (!rsrc) return FALSE;
+
+ data = LockResource(LoadResource(GetModuleHandleA(NULL), rsrc));
+ if (!data) return FALSE;
+
+ size = SizeofResource(GetModuleHandleA(NULL), rsrc);
+ if (!size) return FALSE;
+
+ file = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
+ if (file == INVALID_HANDLE_VALUE) return FALSE;
+
+ ret = WriteFile(file, data, size, &size, NULL);
+ CloseHandle(file);
+ return ret;
+}
+
+static BOOL compile_cs(const WCHAR *source, const WCHAR *target, const WCHAR *type, const WCHAR *args)
+{
+ static const WCHAR *csc = L"C:\\windows\\Microsoft.NET\\Framework\\v2.0.50727\\csc.exe";
+ WCHAR cmdline[2 * MAX_PATH + 74];
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si = { 0 };
+ BOOL ret;
+
+ if (!PathFileExistsW(csc))
+ {
+ skip("Can't find csc.exe\n");
+ return FALSE;
+ }
+
+ swprintf(cmdline, ARRAY_SIZE(cmdline), L"%s /t:%s %s /out:\"%s\" \"%s\"", csc, type, args, target, source);
+
+ si.cb = sizeof(si);
+ ret = CreateProcessW(csc, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+ ok(ret, "Could not create process: %u\n", GetLastError());
+
+ wait_child_process(pi.hProcess);
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+
+ ret = PathFileExistsW(target);
+ ok(ret, "Compilation failed\n");
+
+ return ret;
+}
+
+static void test_loadpaths_execute(const WCHAR *exe_name, const WCHAR *dll_name, const WCHAR *cfg_name,
+ const WCHAR *dll_dest, BOOL expect_failure, BOOL todo)
+{
+ WCHAR tmp[MAX_PATH], tmpdir[MAX_PATH], tmpexe[MAX_PATH], tmpcfg[MAX_PATH], tmpdll[MAX_PATH];
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si = { 0 };
+ WCHAR *ptr, *end;
+ DWORD exit_code = 0xdeadbeef;
+ LUID id;
+ BOOL ret;
+
+ GetTempPathW(MAX_PATH, tmp);
+ ret = AllocateLocallyUniqueId(&id);
+ ok(ret, "AllocateLocallyUniqueId failed: %u\n", GetLastError());
+ ret = GetTempFileNameW(tmp, L"loadpaths", id.LowPart, tmpdir);
+ ok(ret, "GetTempFileNameW failed: %u\n", GetLastError());
+
+ ret = CreateDirectoryW(tmpdir, NULL);
+ ok(ret, "CreateDirectoryW(%s) failed: %u\n", debugstr_w(tmpdir), GetLastError());
+
+ wcscpy(tmpexe, tmpdir);
+ PathAppendW(tmpexe, exe_name);
+ ret = CopyFileW(exe_name, tmpexe, FALSE);
+ ok(ret, "CopyFileW(%s) failed: %u\n", debugstr_w(tmpexe), GetLastError());
+
+ if (cfg_name)
+ {
+ wcscpy(tmpcfg, tmpdir);
+ PathAppendW(tmpcfg, cfg_name);
+ ret = CopyFileW(cfg_name, tmpcfg, FALSE);
+ ok(ret, "CopyFileW(%s) failed: %u\n", debugstr_w(tmpcfg), GetLastError());
+ }
+
+ ptr = tmpdir + wcslen(tmpdir);
+ PathAppendW(tmpdir, dll_dest);
+ while (*ptr && (ptr = wcschr(ptr + 1, '\\')))
+ {
+ *ptr = '\0';
+ ret = CreateDirectoryW(tmpdir, NULL);
+ ok(ret, "CreateDirectoryW(%s) failed: %u\n", debugstr_w(tmpdir), GetLastError());
+ *ptr = '\\';
+ }
+
+ wcscpy(tmpdll, tmpdir);
+ if ((ptr = wcsrchr(tmpdir, '\\'))) *ptr = '\0';
+
+ ret = CopyFileW(dll_name, tmpdll, FALSE);
+ ok(ret, "CopyFileW(%s) failed: %u\n", debugstr_w(tmpdll), GetLastError());
+
+ si.cb = sizeof(si);
+ ret = CreateProcessW(tmpexe, tmpexe, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+ ok(ret, "CreateProcessW(%s) failed: %u\n", debugstr_w(tmpexe), GetLastError());
+
+ if (expect_failure) ret = WaitForSingleObject(pi.hProcess, 500);
+ else
+ {
+ ret = WaitForSingleObject(pi.hProcess, 5000);
+ ok(ret == WAIT_OBJECT_0, "%s: WaitForSingleObject returned %d: %u\n", debugstr_w(dll_dest), ret, GetLastError());
+ }
+
+ GetExitCodeProcess(pi.hProcess, &exit_code);
+ if (ret == WAIT_TIMEOUT) TerminateProcess(pi.hProcess, 0xdeadbeef);
+ CloseHandle(pi.hThread);
+ CloseHandle(pi.hProcess);
+
+ if (expect_failure) todo_wine_if(todo) ok(exit_code != 0, "%s: Succeeded to execute process\n", debugstr_w(dll_dest));
+ else ok(exit_code == 0, "%s: Failed to execute process\n", debugstr_w(dll_dest));
+
+ /* sometimes the failing process never returns, in which case cleaning up won't work */
+ if (ret == WAIT_TIMEOUT && expect_failure) return;
+
+ if (cfg_name)
+ {
+ ret = DeleteFileW(tmpcfg);
+ ok(ret, "DeleteFileW(%s) failed: %u\n", debugstr_w(tmpcfg), GetLastError());
+ }
+ ret = DeleteFileW(tmpdll);
+ ok(ret, "DeleteFileW(%s) failed: %u\n", debugstr_w(tmpdll), GetLastError());
+ ret = DeleteFileW(tmpexe);
+ ok(ret, "DeleteFileW(%s) failed: %u\n", debugstr_w(tmpexe), GetLastError());
+
+ end = tmpdir + wcslen(tmp);
+ ptr = tmpdir + wcslen(tmpdir) - 1;
+ while (ptr > end && (ptr = wcsrchr(tmpdir, '\\')))
+ {
+ ret = RemoveDirectoryW(tmpdir);
+ ok(ret, "RemoveDirectoryW(%s) failed: %u\n", debugstr_w(tmpdir), GetLastError());
+ *ptr = '\0';
+ }
+}
+
+static void test_loadpaths(BOOL neutral)
+{
+ static const WCHAR *loadpaths[] = {L"", L"en", L"libloadpaths", L"en\\libloadpaths"};
+ static const WCHAR *dll_source = L"loadpaths.dll.cs";
+ static const WCHAR *dll_name = L"libloadpaths.dll";
+ static const WCHAR *exe_source = L"loadpaths.exe.cs";
+ static const WCHAR *exe_name = L"loadpaths.exe";
+ static const WCHAR *cfg_name = L"loadpaths.exe.config";
+ WCHAR tmp[MAX_PATH];
+ BOOL ret;
+ int i;
+
+ DeleteFileW(dll_source);
+ ret = write_resource(dll_source, dll_source);
+ ok(ret, "Could not write resource: %u\n", GetLastError());
+ DeleteFileW(dll_name);
+ ret = compile_cs(dll_source, dll_name, L"library", neutral ? L"-define:NEUTRAL" : L"");
+ if (!ret) return;
+ ret = DeleteFileW(dll_source);
+ ok(ret, "DeleteFileW failed: %u\n", GetLastError());
+
+ DeleteFileW(exe_source);
+ ret = write_resource(exe_source, exe_source);
+ ok(ret, "Could not write resource: %u\n", GetLastError());
+ DeleteFileW(exe_name);
+ ret = compile_cs(exe_source, exe_name, L"exe", L"/reference:libloadpaths.dll");
+ if (!ret) return;
+ ret = DeleteFileW(exe_source);
+ ok(ret, "DeleteFileW failed: %u\n", GetLastError());
+
+ DeleteFileW(cfg_name);
+ ret = write_resource(cfg_name, cfg_name);
+ ok(ret, "Could not write resource: %u\n", GetLastError());
+
+ for (i = 0; i < ARRAY_SIZE(loadpaths); ++i)
+ {
+ const WCHAR *path = loadpaths[i];
+ BOOL expect_failure = neutral ? wcsstr(path, L"en") != NULL
+ : wcsstr(path, L"en") == NULL;
+
+ wcscpy(tmp, path);
+ PathAppendW(tmp, dll_name);
+ test_loadpaths_execute(exe_name, dll_name, NULL, tmp, expect_failure, !neutral && !*path);
+
+ wcscpy(tmp, L"private");
+ if (*path) PathAppendW(tmp, path);
+ PathAppendW(tmp, dll_name);
+
+ test_loadpaths_execute(exe_name, dll_name, NULL, tmp, TRUE, FALSE);
+ test_loadpaths_execute(exe_name, dll_name, cfg_name, tmp, expect_failure, FALSE);
+
+ /* exe name for dll should work too */
+ if (*path)
+ {
+ wcscpy(tmp, path);
+ PathAppendW(tmp, dll_name);
+ wcscpy(tmp + wcslen(tmp) - 4, L".exe");
+ test_loadpaths_execute(exe_name, dll_name, NULL, tmp, expect_failure, FALSE);
+ }
+
+ wcscpy(tmp, L"private");
+ if (*path) PathAppendW(tmp, path);
+ PathAppendW(tmp, dll_name);
+ wcscpy(tmp + wcslen(tmp) - 4, L".exe");
+
+ test_loadpaths_execute(exe_name, dll_name, NULL, tmp, TRUE, FALSE);
+ test_loadpaths_execute(exe_name, dll_name, cfg_name, tmp, expect_failure, FALSE);
+ }
+
+ ret = DeleteFileW(cfg_name);
+ ok(ret, "DeleteFileW failed: %u\n", GetLastError());
+ ret = DeleteFileW(exe_name);
+ ok(ret, "DeleteFileW failed: %u\n", GetLastError());
+ ret = DeleteFileW(dll_name);
+ ok(ret, "DeleteFileW failed: %u\n", GetLastError());
+}
+
static void test_createdomain(void)
{
static const WCHAR test_name[] = {'t','e','s','t',0};
@@ -650,5 +873,8 @@ START_TEST(mscoree)
test_createdomain();
}
+ test_loadpaths(FALSE);
+ test_loadpaths(TRUE);
+
FreeLibrary(hmscoree);
}
diff --git a/dlls/mscoree/tests/resource.rc b/dlls/mscoree/tests/resource.rc
index 9a1b89f6569..34516f744a0 100644
--- a/dlls/mscoree/tests/resource.rc
+++ b/dlls/mscoree/tests/resource.rc
@@ -28,3 +28,12 @@ comtest_exe.manifest RCDATA comtest_exe.manifest
/* @makedep: comtest_dll.manifest */
comtest_dll.manifest RCDATA comtest_dll.manifest
+
+/* @makedep: loadpaths.exe.cs */
+loadpaths.exe.cs RCDATA loadpaths.exe.cs
+
+/* @makedep: loadpaths.dll.cs */
+loadpaths.dll.cs RCDATA loadpaths.dll.cs
+
+/* @makedep: loadpaths.exe.config */
+loadpaths.exe.config RCDATA loadpaths.exe.config
--
2.30.0