Skip to content
⌘ NSIS Forum Archive

Windows Built-In strings

46 posts

OldGrumpy#
I'll have a look at howto make an NSIS plugin in the next days. I'm quite busy with my job but I think I can find a little time to get it done.
CancerFace#
Wow, I am impressed 🙂
I went away for half an hour and now there is already the possibility for a plugin!

Let me slow things down just for a sec here. I am almost convinced that the function itself is appending that space at the end of the string. Its syntax is quite strange as every parameter that you pass depends on the others. For example, there is the possibility to define the size of the buffer allocated for the string (if you don't use the FORMAT_MESSAGE_ALLOCATE_BUFFER flag) however I may be allocating the wrong amount of memory and that's why I am not getting anything out. If I call the function once then I can get the size of the string:
!define LOAD_LIBRARY_AS_DATAFILE	0x00000002
!define FORMAT_MESSAGE_ALLOCATE_BUFFER 0x00000100
!define FORMAT_MESSAGE_FROM_HMODULE 0x00000800
!define FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF
System::Call 'kernel32::LoadLibraryExW(w "$SYSDIR\SAMSRV.DLL", i n, i ${LOAD_LIBRARY_AS_DATAFILE}) i .R0'
StrCpy $0 ${FORMAT_MESSAGE_FROM_HMODULE}
IntOp $0 $0 + ${FORMAT_MESSAGE_ALLOCATE_BUFFER}
IntOp $0 $0 + ${FORMAT_MESSAGE_MAX_WIDTH_MASK}
StrCpy $1 8192 ; messageID for string 'Administrator'
StrCpy $2 0
StrCpy $3 0
System::Call 'kernel32::FormatMessageW(i r0, i R0, i r1, i r2, *w .R3, i r3, i n) i .R4'
Now $R3 contains the string with a space at the end and $R4 is the length (in TCHARS) of the string. For the above example, the string is 'Administrator' on my system, and the function reports 14 TCHARS. Is that 13 + 1 (1 being the space)? If yes then I can call the function a second time, only now I will not allow it to allocate the buffer but I will create one and use it:
StrCpy $0 ${FORMAT_MESSAGE_FROM_HMODULE}
IntOp $0 $0 + ${FORMAT_MESSAGE_MAX_WIDTH_MASK}
IntOp $9 $R4 - 1 ; # TCHARS of string
IntOp $3 $9 * 2 ; # of bytes for the string
System::Alloc $3
Pop $R3
System::Call 'kernel32::FormatMessageW(i r0, i R0, i r1, i r2, i R3, i r9, i n) i .R4 ?e'
Pop $R8
The above code does not work however and I am not sure why. The error is 122 (ERROR_INSUFFICIENT_BUFFER).

Any ideas?

CF
Afrow UK#
I've got the code in a plugin and trimmed the new lines. What do you need to do with the string after that point?

-Stu
CancerFace#edited
Thanks a lot by the way 🙂 I guess I'll have to dive into making plugins myself soon ...

Is it possible to pass it in a buffer so that I can directly pipe it to another function? I do not want to have it in a variable because it may contain unicode characters.

Ideally I would like to pass to the plugin:
1. the library name
2. the # of the string to pull out
3. (optionaly) the system language (or you could use GetSystemDefaultLangID to query the system language and then pass it to FormatMessage)

and then I would get the string in a buffer so that I can use it directly for another API call ...

Where you able to get rid of the space appended at the end of the string?


CF

[Edit] This plugin can have a more general use I guess. You could pass the string to a buffer and to some variable, or think about dealing with message strings as well instead of strings from tables etc ...
Afrow UK#
I didn't use FORMAT_MESSAGE_MAX_WIDTH_MASK so there was a \r\n and no space. It isn't possible to pass a string from one plugin to another without storing it in an NSIS variable in between. You'd have to do everything in the same plugin.

If I use GetSystemDefaultLangID the FormatMessage call fails.

-Stu
OldGrumpy#edited
What about calling the plugin (Get)SystemMessageStrings? I don't know if with our without "Get" is better 🙂 Specifying the system language for the plugin seems to be superfluous to me, but it could be useful to distinguish between the system language and the user language. So we could pass a flag to the plugin that decides which of the two will be used.
Another question would be who allocates and frees the buffer memory. I'd suggest that the plugin allocates the memory, and frees it when it gets unloaded. That way, it can be ensured that no memory leaks occur. (I hope...)

Edit: I think GetResourceString is a better name 🙂
2nd edit: I think the plugin should be able to convert unicode to ansi (using the system functions) optionally to enhance the comfort for nsis components. As long as nsis can't cope with unicode properly, that could be a huge help. It isn't a problem to put the string into a buffer and let the system convert it back later when needed. From 2K onwards, the system converts all ansi strings to unicode internally, anyway 🙂
CancerFace#
GOT IT!!!!! 🙂

After a lot of trial-and-error runs here is what worked for me:
!define FORMAT_MESSAGE_IGNORE_INSERTS	0x00000200
!define FORMAT_MESSAGE_FROM_HMODULE 0x00000800
!define FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF


# Get the system Language ID
System::Call 'kernel32::GetSystemDefaultLangID(i v)i .R0'

# First load the library
System::Call 'kernel32::LoadLibraryExW(w "$SYSDIR\SAMSRV.DLL", i n, i ${LOAD_LIBRARY_AS_DATAFILE}) i .R1'

# get the string out ($R2) and get its size on $R3
StrCpy $0 ${FORMAT_MESSAGE_FROM_HMODULE}
IntOp $0 $0 + ${FORMAT_MESSAGE_IGNORE_INSERTS}
IntOp $0 $0 + ${FORMAT_MESSAGE_MAX_WIDTH_MASK}
StrCpy $1 8192
StrCpy $2 ${NSIS_MAX_STRLEN}

# prepare a buffer to accept that string
System::Call '*(&w${NSIS_MAX_STRLEN})i.R2'
System::Call 'kernel32::FormatMessageW(i r0, i R1, i r1, i R0, i R2, i r2, i n) i .R3'

; Free the DLL handle
System::Call 'kernel32::FreeLibrary(i R1)i .R8'
Now the string is stored in $R2 but if I try to get it out, say using
System::Call '*$R2(w.R9)'
my program fails. However if I pass this buffer directly to another API it works! Well at least in XP ... Not sure why I am not able to get the string out in $R9 though.

I passed the buffer to the NetUserAdd API and managed to create a user called 'Administrator'

🙂

I will test it in 2k as well and then report back ...

CF

[Edit] Oups, I was a bit too fast getting excited 🙁
It works but the name still contains a space at the end ... It works in 2k as well ...
CancerFace#
Yes I am freeing the buffer after I pass it to the next API.
I am also still interested in your plugin Stu since I can't get rid of that !$@!#$ space at the end of the string 🙁

CF
CancerFace#
Problem solved!

The trick is to copy the buffer (as galil pointed out at some point) although it looks like the buffer does not contain anything:
!define FORMAT_MESSAGE_IGNORE_INSERTS	0x00000200
!define FORMAT_MESSAGE_FROM_HMODULE 0x00000800
!define FORMAT_MESSAGE_MAX_WIDTH_MASK 0x000000FF


# Get the system Language ID
System::Call 'kernel32::GetSystemDefaultLangID(i v)i .R0'

# First load the library
System::Call 'kernel32::LoadLibraryExW(w "$SYSDIR\SAMSRV.DLL", i n, i ${LOAD_LIBRARY_AS_DATAFILE}) i .R1'

# get the string out ($R2) and get its size on $R3
StrCpy $0 ${FORMAT_MESSAGE_FROM_HMODULE}
IntOp $0 $0 + ${FORMAT_MESSAGE_IGNORE_INSERTS}
IntOp $0 $0 + ${FORMAT_MESSAGE_MAX_WIDTH_MASK}
StrCpy $1 8192
StrCpy $2 ${NSIS_MAX_STRLEN}

# prepare a buffer to accept that string
System::Call '*(&w${NSIS_MAX_STRLEN})i.R2'
System::Call 'kernel32::FormatMessageW(i r0, i R1, i r1, i R0, i R2, i r2, i n) i .R3'

# trim one character and copy to another buffer
IntOp $8 $R3 - 1
IntOp $9 $8 * 2
System::Alloc $9
Pop $R6
System::Copy /$9 $R6 $R2

; Free the DLL handle
System::Call 'kernel32::FreeLibrary(i R1)i .R8'
Now $R6 contains the string with the exact size without any space at the end. Passing this buffer to another API works just fine. However if I try to get the string out to some NSIS variable the code fails. I am not sure why, but since it does what I was after I don't really care at this point 🙂

All that remains now is to test this in a non english OS and see if the localized name is picked in the system language from samsrv.dll!

A warm 'thank you' to all who contributed in this thread!

CF
CancerFace#
I saw this thread and I thought that this is relevant info (posted by apmolyneux):
I see you're getting the language of USER.EXE, which is good thinking and will work for legacy Windows versions.

However, it won't work properly for users who are running the MUI (Multilingual User Interface) version of Windows 2000 or XP. USER.EXE is always "English (US)" in a MUI environment. I'm told the Enterprise and Ultimate versions of Vista will also be MUI.
I was not looking for user.exe but for samsrv.dll. I installed the french MUI on a virtual PC and changed the system and user language to french. I run the code shown at my last post and I managed to pick the french equivalent of 'Administrator' using FormatMessage (that's Administrateur 🙂 ). The interesting part though is that resource hacker reports that samsrv.dll has a message table with lang 1033 (English) and not 1036 (French)! A binary comparison of the file from the french MUI and that of the original english version of XP showed that they are the same. 🤨

Does anybody understand how this is working? How can the system pick the correct localized strings from a DLL that doesn't seem to contain them?

There is also a folder present in my $WINDIR ($WINDIR\mui\FALLBACK\040C) which contains the file samsrv.dll.mui, which in fact has a message table with the correct lang/strings. How did the API call get redirected to this file? Has anyone noticed something similar?

CF
Afrow UK#
That is interesting. I guess the English strings are stored in the DLL itself, whereas any other languages are stored in the seperate file.

-Stu