I need to get SMPlayer working and out of the door before tomorrow (I know I'm a procrastinating dev :P) because SMPlayer still hogs CPU and crashes. I was thinking that since SMPlayer uses CSIDL to get folder paths, I could redirect CSIDL like haustin said he would implement into PortableApps.nsh (BTW what happened to it?).
I found a WinAPI article about it, but I'm not entirely sure if it's safe to use. Does it only change it in its own enviroment like the Enviroment Variables, or does it affect the entire system?
Please help and thanks in advance!
Edit: Another WinAPI article
You are here
CSIDL Redirection (ASAP)
This looks like a permanent redirect unless you change it back. Not sure if admin rights are needed for this. *Probably* not if you're redirecting a user's folder.
This is classified as a "method", I'm not sure how that works in NSIS / AutoIt yet. I'll see if I can figure something out.
EDIT - that Redirect method is Vista only. I don't know how to use it yet, but the IKnownFolderManager interface (of which Redirect is a method) is Vista only.
I guess Vista won't be supported
Now SMPlayer is going to be bulky
It doesn't matter anyway. As far as I can tell, there's no way to call that Redirect method. Even in AutoIt which has COM Object support (NSIS does not), I cannot find a class to load the IKnownFolderManager interface, so the methods that it covers are unavailable. I'm sure there's a way to do it in a real programming language however.
on the upside that can't be the only way to redirect...
I'll search.
I'm pretty sure that it's stored somewhere in registry, but I highly recommend to skip it.
-It's gonna mess up local programs.
-In case of a crash / system restart while running etc. these problems won't be temporary. You'll permanently screw host computer.
-It can cause system instability.
Instead, I suggest a hook on SHGetFolderPath. I can offer some help with it.
some help would do great.
Please, nobody reply to this post until digitxp contacts me and I remove my email.
EDIT: I didn't receive any email from digitxp, but lococobra at portablefreeware.com found another solution.
I suggest to do the other solution well. It will take more time, but should be less error prone.
You posted when I was asleep ;)...
But that'll work too.
Edit: I already tried Enviroment Variables, but it didn't work
Maybe you made a mistake somewhere?
Or maybe it behaves differently for some CSIDL values?
Can you post the code that didn't work?
Env vars have no effect on the csidl values, at least not on any tests I have tried. I have some AutoIt code I can post if you wanna give it a shot...
Please, do it.
int __cdecl _tmain(int argc, TCHAR ** argv)
{
TCHAR buf[MAX_PATH]={0};
if(FAILED(SHGetFolderPath( 0,CSIDL_PERSONAL|CSIDL_FLAG_CREATE,0,0,buf)))
_tprintf(_T("Failed, error=%d"),GetLastError());
_putts(buf);
return 0;
}
This C code reacts to %USERPROFILE% changes.
Perhaps I only tried %USERPROFILE% and not %HOMEDRIVE%%HOMEPATH% or %SYSTEMDRIVE%%HOMEPATH% or %APPDATA%\.. or something. The code is just like any redirection code...
Are your %USERPROFILE% changes permanent or temporary? We're talking temporary environment changes in this context, so that may make a difference.
AutoIt:
#include ;********************************************************* ;* SHGetFolderPath Global Constants * ;* combine Global Constants using BitOR() * ;********************************************************* Global Const $CSIDL_DESKTOP = 0x0000 Global Const $CSIDL_INTERNET = 0x0001 Global Const $CSIDL_PROGRAMS = 0x0002 Global Const $CSIDL_CONTROLS = 0x0003 Global Const $CSIDL_PRINTERS = 0x0004 Global Const $CSIDL_PERSONAL = 0x0005 Global Const $CSIDL_FAVORITES = 0x0006 Global Const $CSIDL_STARTUP = 0x0007 Global Const $CSIDL_RECENT = 0x0008 Global Const $CSIDL_SENDTO = 0x0009 Global Const $CSIDL_BITBUCKET = 0x000A Global Const $CSIDL_STARTMENU = 0x000B Global Const $CSIDL_MYDOCUMENTS = 0x000C Global Const $CSIDL_MYMUSIC = 0x000D Global Const $CSIDL_MYVIDEO = 0x000E Global Const $CSIDL_DIRECTORY = 0x0010 Global Const $CSIDL_DRIVES = 0x0011 Global Const $CSIDL_NETWORK = 0x0012 Global Const $CSIDL_NETHOOD = 0x0013 Global Const $CSIDL_FONTS = 0x0014 Global Const $CSIDL_TEMPLATES = 0x0015 Global Const $CSIDL_COMMON_STARTMENU = 0x016 Global Const $CSIDL_COMMON_PROGRAMS = 0x0017 Global Const $CSIDL_COMMON_STARTUP = 0x0018 Global Const $CSIDL_COMMON_DESKTOPDIRECTORY = 0x0019 Global Const $CSIDL_APPDATA = 0x001A Global Const $CSIDL_PRINTHOOD = 0x001B Global Const $CSIDL_LOCAL_APPDATA = 0x001C Global Const $CSIDL_ALTSTARTUP = 0x001D Global Const $CSIDL_COMMON_ALTSTARTUP = 0x001E Global Const $CSIDL_COMMON_FAVORITES = 0x001F Global Const $CSIDL_INTERNET_CACHE = 0x0020 Global Const $CSIDL_COOKIES = 0x0021 Global Const $CSIDL_HISTORY = 0x0022 Global Const $CSIDL_COMMON_APPDATA = 0x0023 Global Const $CSIDL_WINDOWS = 0x0024 Global Const $CSIDL_SYSTEM = 0x0025 Global Const $CSIDL_PROGRAM_FILES = 0x0026 Global Const $CSIDL_MYPICTURES = 0x0027 Global Const $CSIDL_PROFILE = 0x0028 Global Const $CSIDL_SYSTEMX86 = 0x0029 Global Const $CSIDL_PROGRAM_FILESX86 = 0x002A Global Const $CSIDL_PROGRAM_FILES_COMMON = 0x002B Global Const $CSIDL_PROGRAM_FILES_COMMONX86 = 0x002C Global Const $CSIDL_COMMON_TEMPLATES = 0x002D Global Const $CSIDL_COMMON_DOCUMENTS = 0x002E Global Const $CSIDL_COMMON_ADMINTOOLS = 0x002F Global Const $CSIDL_ADMINTOOLS = 0x0030 Global Const $CSIDL_CONNECTIONS = 0x0031 Global Const $CSIDL_COMMON_MUSIC = 0x0035 Global Const $CSIDL_COMMON_PICTURES = 0x0036 Global Const $CSIDL_COMMON_VIDEO = 0x0037 Global Const $CSIDL_RESOURCES = 0x0038 Global Const $CSIDL_RESOURCES_LOCALIZED = 0x0039 Global Const $CSIDL_COMMON_OEM_LINKS = 0x003A Global Const $CSIDL_CDBURN_AREA = 0x003B Global Const $CSIDL_COMPUTERSNEARME = 0x003D ; combine with CSIDL_ value to force folder creation in SHGetFolderPath() Global Const $CSIDL_FLAG_CREATE = 0x8000 ; combine with CSIDL_ value to return an unverified folder path Global Const $CSIDL_FLAG_DONT_VERIFY = 0x4000 ; combine with CSIDL_ value to insure non-alias versions of the pidl Global Const $CSIDL_FLAG_NO_ALIAS = 0x1000 ; combine with CSIDL_ value to indicate per-user init (eg. upgrade) Global Const $CSIDL_FLAG_PER_USER_INIT = 0x0800 ; mask for all possible flag values Global Const $CSIDL_FLAG_MASK = 0xFF00 ; dwFlags values for use with SHGetFolderPath ; current value for user, verify it exists Global Const $SHGFP_TYPE_CURRENT = 0x0000 ; default value Global Const $SHGFP_TYPE_DEFAULT = 0x0001 Func _WinAPI_SHGetFolderPath($nFolder, $dwFlags = $SHGFP_TYPE_CURRENT) Local $folder $folder = DllCall("shell32.dll", "int", "SHGetFolderPath", _ "hwnd", Chr(0), _ "int", $nFolder, _ "ptr", Chr(0), _ "dword", $dwFlags, _ "str", "" _ ) Return $folder[5] EndFunc ConsoleWrite(_WinAPI_SHGetFolderPath(BitOR($CSIDL_FLAG_NO_ALIAS, $CSIDL_APPDATA)) & @CRLF) ConsoleWrite(EnvGet("APPDATA") & @CRLF) EnvSet("APPDATA", "C:\test") ConsoleWrite(EnvGet("APPDATA") & @CRLF) ConsoleWrite(_WinAPI_SHGetFolderPath(BitOR($CSIDL_FLAG_NO_ALIAS, $CSIDL_APPDATA)) & @CRLF)
I'll quote my own words from the topic I linked to previously:
However, be careful when you do it this way, i.e. getting "My Documents" fails unless "%USERPROFILE%\My Documents" exists...and I guess that exact folder name is language specific. This can lead to instable work of the program. I'm sure it can be made to work well, however doing this well is much more complicated than what you posted.
In your case %USERPROFILE%\Application Data has to exist, then everything works.
So First, you have to determine all directories that have to be created (By using all CSIDL values, better option would be using all KNOWNFOLDERID values because CSIDL may not be enough for some "Vista or later" programs)
Then you have to find where are their localized names stored in the registry.
Then create these folders in \Data and write a launcher that renames them on startup if locale changed.
Well, I retried my example first with $CSIDL_FLAG_CREATE, then after manually creating "C:\test". In all trials, EnvGet("APPDATA") returned "C:\test" correctly, and SHGetFolderPath returned "C:\Users\Erik\AppData\Roaming" (Vista laptop).
So I don't see where the existance of "C:\test" made any difference.
Maybe Vista changed the behaviour, it totally undocumented. Try the following batch (assumes your test program to be named a.exe):
@echo off set userprofile=c:\test md c:\test\AppData md c:\test\AppData\Roaming a.exe rd /q /s c:\test\AppData
And try it w/out the "create" flag. In my case, when folder exists, it doesn't change anything, it's needed in case that it doesn't. But you cannot assume that app uses this flag, that's why I said to create %USERPROFILE%\Application Data.
Ok, very strange results:
EnvGet("APPDATA") = C:\Users\Erik\AppData\Roaming
EnvGet("USERPROFILE") = c:\test
CSIDL_APPDATA = c:\test\AppData\Roaming
CSIDL_LOCAL_APPDATA = c:\test\AppData\Local
CSIDL_PROFILE = C:\Users\Erik
BTW, there's a CSIDL_FLAG_DONT_VERIFY so you don't have to create the directories (for testing purposes).
So it seems that CSIDL_*APPDATA uses the environment var %USERPROFILE%, while CSIDL_PROFILE and environment var %APPDATA% do not. Could MS make this any more confusing?
BTW, there's a CSIDL_FLAG_DONT_VERIFY so you don't have to create the directories (for testing purposes).
For testing purposes - OK, but you need to make it work with all possible sets of flags.
So it seems that CSIDL_*APPDATA uses the environment var %USERPROFILE%, while CSIDL_PROFILE and environment var %APPDATA% do not. Could MS make this any more confusing?
That's why they didn't document how does it work. :D:D
There are still 2 ways. Either play with env vars, but it looks like a daunting task. And will require huge amount of testing, practically with every Windows version.
Hook is a very dirty solution and sometimes antiviruses don't like it. But it can be done in a few hours.