[PATCH 0/1] MR10365: kernelbase: Add language specific resource support.
When an application relies upon language specific resources, search for an associated language specific subfolder and subsequent mui file. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365
From: Craig Schulstad <craigaschulstad@gmail.com> When an application relies upon language specific resources, search for an associated language specific subfolder and subsequent mui file. --- dlls/kernelbase/loader.c | 183 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 173 insertions(+), 10 deletions(-) diff --git a/dlls/kernelbase/loader.c b/dlls/kernelbase/loader.c index f4e1ca2e23a..d5fc62385c6 100644 --- a/dlls/kernelbase/loader.c +++ b/dlls/kernelbase/loader.c @@ -46,6 +46,11 @@ struct exclusive_datafile }; static struct list exclusive_datafile_list = LIST_INIT( exclusive_datafile_list ); +static WCHAR mui_locale[LOCALE_NAME_MAX_LENGTH]; +static BOOL locale_found = FALSE; +static BOOL recursion_flag = FALSE; +static HMODULE module_mui = NULL; + static CRITICAL_SECTION exclusive_datafile_list_section; static CRITICAL_SECTION_DEBUG critsect_debug = { @@ -1056,10 +1061,115 @@ BOOL WINAPI DECLSPEC_HOTPATCH EnumResourceTypesExW( HMODULE module, ENUMRESTYPEP } -/********************************************************************** - * FindResourceExW (kernelbase.@) - */ -HRSRC WINAPI DECLSPEC_HOTPATCH FindResourceExW( HMODULE module, LPCWSTR type, LPCWSTR name, WORD lang ) +/***********************************************************************/ +/* get_mui - Acquire an MUI module for the associated resource */ +/***********************************************************************/ + +HMODULE get_mui(HMODULE module) +{ + WCHAR module_name[MAX_PATH], mui_name[MAX_PATH]; + HMODULE mui_module = NULL; + INT i, j = 0, k = 0, l = 0; + LONG save_error = GetLastError(); + + /* Initialize the work strings */ + + for (i = 0; i < MAX_PATH; i++) { + module_name[i] = 0; + mui_name[i] = 0; + } + + /* Acquire the base resource file name */ + + if (!(GetModuleFileNameW(module, module_name, MAX_PATH))) { + TRACE ("Module file name was not found - returning with source module\n"); + SetLastError(save_error); + return module; + } + + /* Stay with the original module reference if this file is not an executable file. */ + + if (!(wcsstr(module_name, L".exe"))) + return module; + + if (!(locale_found)) { + + if (recursion_flag) + return module; + + recursion_flag = TRUE; + + LCIDToLocaleName( GetUserDefaultLCID(), mui_locale, LOCALE_NAME_MAX_LENGTH, 0 ); + + recursion_flag = FALSE; + + locale_found = TRUE; + + } + + /* Locate the position of the final backslash in the retrieved executable file. */ + + for (i = 0; i < MAX_PATH; i++) { + + if (module_name[i] == 0) break; + + if (module_name[i] == '\\') j = i; + } + + /* Set up the work index that will be used to extract just the executable file from the fully qualified file name. */ + + for (i = 0; i < MAX_PATH; i++) { + + if (module_name[i] == 0) break; + + /* If work index "j" has been set to -1, then the file portion of the qualified */ + /* name has been reached and will be copied to the "MUI" file reference. */ + + if (j < 0) { + mui_name[k] = module_name[i]; + k++; + } + + /* When the position of the final backslash has been reached, add the locale name as */ + /* the folder/directory containing the "MUI" file and reset work index "j" to -1. */ + + if (i >= j && j > 0) { + for (l = 0; l < 5; l++) { + mui_name[k] = mui_locale[l]; + k++; + } + mui_name[k] = '/'; + k++; + j = -1; + } + } + + /* Finally, append the literal ".mui" onto the file reference. */ + + wcscat(mui_name, L".mui"); + + /* Now, see if there is an associated "MUI" file and if so, use its handle for the module handle. */ + + mui_module = LoadLibraryExW(mui_name, 0, 0); + + SetLastError(save_error); + + if (mui_module != NULL) { + module_mui = mui_module; + return mui_module; + } else { + module_mui = NULL; + return module; + } + +} + +/***********************************************************************/ +/* get_res_handle - Isolated call of the LdrFindResource function */ +/***********************************************************************/ + +HRSRC get_res_handle(HMODULE module, LPCWSTR type, LPCWSTR name, WORD lang) + { NTSTATUS status; UNICODE_STRING nameW, typeW; @@ -1068,7 +1178,6 @@ HRSRC WINAPI DECLSPEC_HOTPATCH FindResourceExW( HMODULE module, LPCWSTR type, LP TRACE( "%p %s %s %04x\n", module, debugstr_w(type), debugstr_w(name), lang ); - if (!module) module = GetModuleHandleW( 0 ); nameW.Buffer = typeW.Buffer = NULL; __TRY @@ -1077,10 +1186,11 @@ HRSRC WINAPI DECLSPEC_HOTPATCH FindResourceExW( HMODULE module, LPCWSTR type, LP if ((status = get_res_nameW( type, &typeW )) != STATUS_SUCCESS) goto done; info.Type = (ULONG_PTR)typeW.Buffer; info.Name = (ULONG_PTR)nameW.Buffer; - info.Language = lang; + info.Language = lang; status = LdrFindResource_U( module, &info, 3, &entry ); done: - if (status != STATUS_SUCCESS) SetLastError( RtlNtStatusToDosError(status) ); + if (status != STATUS_SUCCESS) + SetLastError( RtlNtStatusToDosError(status) ); } __EXCEPT_PAGE_FAULT { @@ -1090,10 +1200,49 @@ HRSRC WINAPI DECLSPEC_HOTPATCH FindResourceExW( HMODULE module, LPCWSTR type, LP if (!IS_INTRESOURCE(nameW.Buffer)) HeapFree( GetProcessHeap(), 0, nameW.Buffer ); if (!IS_INTRESOURCE(typeW.Buffer)) HeapFree( GetProcessHeap(), 0, typeW.Buffer ); + return (HRSRC)entry; } +/********************************************************************** + * FindResourceExW (kernelbase.@) + */ +HRSRC WINAPI DECLSPEC_HOTPATCH FindResourceExW( HMODULE module, LPCWSTR type, LPCWSTR name, WORD lang ) +{ + + HRSRC rsrc; + HMODULE work_module = NULL, test_module = NULL; + + if (!module) module = GetModuleHandleW( 0 ); + + work_module = GetModuleHandleW( 0 ); + + if (module != work_module) { + rsrc = get_res_handle(module, type, name, lang); + module_mui = NULL; + } else { + test_module = get_mui(module); + if (test_module == module) { + rsrc = get_res_handle(module, type, name, lang); + module_mui = NULL; + } else { + rsrc = get_res_handle(test_module, type, name, lang); + + if (!rsrc) { + TRACE("Fallback from MUI to base module: %p %p %s %s\n", test_module, module, debugstr_w(type), debugstr_w(name)); + rsrc = get_res_handle(module, type, name, lang); + module_mui = NULL; + } + } + } + + return rsrc; + +} + + + /********************************************************************** * FindResourceW (kernelbase.@) */ @@ -1118,14 +1267,28 @@ BOOL WINAPI DECLSPEC_HOTPATCH FreeResource( HGLOBAL handle ) HGLOBAL WINAPI DECLSPEC_HOTPATCH LoadResource( HINSTANCE module, HRSRC rsrc ) { void *ret; - - TRACE( "%p %p\n", module, rsrc ); + HMODULE work_module = NULL; if (!rsrc) return 0; if (!module) module = GetModuleHandleW( 0 ); - if (!set_ntstatus( LdrAccessResource( module, (IMAGE_RESOURCE_DATA_ENTRY *)rsrc, &ret, NULL ))) + work_module = module; + + /* Check for and use a MUI module */ + + if (module_mui != NULL) { + if (((HMODULE)rsrc < module) || ((module_mui > module) && ((HMODULE)rsrc > module_mui))) + work_module = module_mui; + } + + /* Ready this handle for next resource retrieval */ + + module_mui= NULL; + + if (!set_ntstatus( LdrAccessResource( work_module, (IMAGE_RESOURCE_DATA_ENTRY *)rsrc, &ret, NULL ))) return 0; + return ret; + } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10365
Alfred Agrell (@Alcaro) commented about dlls/kernelbase/loader.c:
}; static struct list exclusive_datafile_list = LIST_INIT( exclusive_datafile_list );
+static WCHAR mui_locale[LOCALE_NAME_MAX_LENGTH]; +static BOOL locale_found = FALSE; +static BOOL recursion_flag = FALSE; Global variables without a mutex always make me nervous.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134079
Alfred Agrell (@Alcaro) commented about dlls/kernelbase/loader.c:
+ for (i = 0; i < MAX_PATH; i++) { + module_name[i] = 0; + mui_name[i] = 0; + } + + /* Acquire the base resource file name */ + + if (!(GetModuleFileNameW(module, module_name, MAX_PATH))) { + TRACE ("Module file name was not found - returning with source module\n"); + SetLastError(save_error); + return module; + } + + /* Stay with the original module reference if this file is not an executable file. */ + + if (!(wcsstr(module_name, L".exe"))) Okay we definitely need a test with an uppercase filename.
And a test at all. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134080
Alfred Agrell (@Alcaro) commented about dlls/kernelbase/loader.c:
-HRSRC WINAPI DECLSPEC_HOTPATCH FindResourceExW( HMODULE module, LPCWSTR type, LPCWSTR name, WORD lang ) +/***********************************************************************/ +/* get_mui - Acquire an MUI module for the associated resource */ +/***********************************************************************/ + +HMODULE get_mui(HMODULE module) +{ + WCHAR module_name[MAX_PATH], mui_name[MAX_PATH]; + HMODULE mui_module = NULL; + INT i, j = 0, k = 0, l = 0; + LONG save_error = GetLastError(); + + /* Initialize the work strings */ + + for (i = 0; i < MAX_PATH; i++) { + module_name[i] = 0; Initializing those looks like a waste of time.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134081
Alfred Agrell (@Alcaro) commented about dlls/kernelbase/loader.c:
+ LCIDToLocaleName( GetUserDefaultLCID(), mui_locale, LOCALE_NAME_MAX_LENGTH, 0 ); + + recursion_flag = FALSE; + + locale_found = TRUE; + + } + + /* Locate the position of the final backslash in the retrieved executable file. */ + + for (i = 0; i < MAX_PATH; i++) { + + if (module_name[i] == 0) break; + + if (module_name[i] == '\\') j = i; + } Should probably replace that with wcsrchr.
(Yes, libc function names are gibberish.) -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134082
Alfred Agrell (@Alcaro) commented about dlls/kernelbase/loader.c:
+ if (j < 0) { + mui_name[k] = module_name[i]; + k++; + } + + /* When the position of the final backslash has been reached, add the locale name as */ + /* the folder/directory containing the "MUI" file and reset work index "j" to -1. */ + + if (i >= j && j > 0) { + for (l = 0; l < 5; l++) { + mui_name[k] = mui_locale[l]; + k++; + } + mui_name[k] = '/'; + k++; + j = -1; I think a second loop would be cleaner than messing with the j variable like that.
And I think a few wcscpy, wmemcpy and wcscat would be cleaner than those loops. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134083
Alfred Agrell (@Alcaro) commented about dlls/kernelbase/loader.c:
+ /* the folder/directory containing the "MUI" file and reset work index "j" to -1. */ + + if (i >= j && j > 0) { + for (l = 0; l < 5; l++) { + mui_name[k] = mui_locale[l]; + k++; + } + mui_name[k] = '/'; + k++; + j = -1; + } + } + + /* Finally, append the literal ".mui" onto the file reference. */ + + wcscat(mui_name, L".mui"); There are fewer bounds checks in this function than I'd expect.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134084
I'm not very familiar with this area, these are just the most obvious odd-looking pieces I could find. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134085
Alfred, I read through your comments and I will attempt to apply some or all of the suggestions. I share your concern with the global variables; however, that seemed to be the only method of ensuring some of the insulation of this enhancement. Quite frankly, some of this code dates back to when I first worked on deriving the functionality over five years ago, so the code bits like "j = -1;" actually give me pause as well. I am all for making this enhancement more robust, so let me apply some of your suggestions and test on my sandbox. So, for now, if you want to not have this enhancement accepted as is, I am fine with it. Regards, Craig -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_134095
This merge request was closed by Craig Schulstad. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365
Alfred, Using your suggestions, I incrementally revised the code block within the "get_mui" function utilizing the wide character functions to determine the last backslash location, truncating and concatenating the language locale, and adding testing for upper and lower case existence of ".exe". Along with that, I worked at reducing the global variable count and utilized a better method of retrieving the language locale in effect for the Wine prefix. Like you, I do like to minimize the usage of global variables; however, retaining the two variables and prudent usage of them did minimize the calling of new functions, and thus minimizing possible knock-on effects. The resulting enhancement code became more compact, and I was able to eliminate all of the "for" loops I had used to construct the language resource path. If you look at pipeline 10575, you will see again that all builds and tests were successful. Again, I was unsure who to request as a reviewer, so if you know of someone, including yourself, feel free to review the updated code. Thanks again for your input. Regards, Craig -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_135292
Hello Alfred, I gave another try to submit my language enhancement and after having the enhancement previously pass the tests, I got a test failure on the "test-linux-32" test set. Here is the email message I received: wine | Failed pipeline for wine-mui | 3a1e0862 I scrolled through the log. In the past, failure text showed up in red, but I found no such highlighted text. There were some mentions of failures within a "GStreamer" section but the text was not highlighted and "graphicspath.c" "todo" messages in gold. I am not sure if the testing failures are in response to someone else's modifications and my enhancement is collateral information, or somehow worked its way back to my enhancement. I guess I could use some enlightenment here. Regards, Craig -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_137339
On Wed Apr 22 17:05:17 2026 +0000, Craig Schulstad wrote:
Hello Alfred, I gave another try to submit my language enhancement and after having the enhancement previously pass the tests, I got a test failure on the "test-linux-32" test set. Here is the email message I received: wine | Failed pipeline for wine-mui | 3a1e0862 I scrolled through the log. In the past, failure text showed up in red, but I found no such highlighted text. There were some mentions of failures within a "GStreamer" section but the text was not highlighted and "graphicspath.c" "todo" messages in gold. I am not sure if the testing failures are in response to someone else's modifications and my enhancement is collateral information, or somehow worked its way back to my enhancement. I guess I could use some enlightenment here. Regards, Craig gdiplus:graphicspath:0518 done (-1073741784) in 0s 4473B
That's a crash (more specifically, STATUS_BAD_STACK), which means test suite failure. The missing highlight is indeed rather unhelpful. Other MRs are failing with the same symptoms (https://gitlab.winehq.org/wine/wine/-/jobs/256670, https://gitlab.winehq.org/wine/wine/-/jobs/256659, https://gitlab.winehq.org/wine/wine/-/jobs/256585), so whatever's wrong in gdiplus, it's not your fault. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_137342
On Wed Apr 22 17:05:17 2026 +0000, Alfred Agrell wrote:
gdiplus:graphicspath:0518 done (-1073741784) in 0s 4473B That's a crash (more specifically, STATUS_BAD_STACK), which means test suite failure. The missing highlight is indeed rather unhelpful. Other MRs are failing with the same symptoms (https://gitlab.winehq.org/wine/wine/-/jobs/256670, https://gitlab.winehq.org/wine/wine/-/jobs/256659, https://gitlab.winehq.org/wine/wine/-/jobs/256585), so whatever's wrong in gdiplus, it's not your fault. Alfred,
Thanks for the quick feedback. I will give it another go either today or tomorrow. Regards, Craig -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10365#note_137494
participants (3)
-
Alfred Agrell (@Alcaro) -
Craig Schulstad -
Craig Schulstad (@NoDakker)