diff --git a/Contrib/Library/LibraryLocal/LibraryLocal.cpp b/Contrib/Library/LibraryLocal/LibraryLocal.cpp index fd4df0d4..83ee1607 100644 --- a/Contrib/Library/LibraryLocal/LibraryLocal.cpp +++ b/Contrib/Library/LibraryLocal/LibraryLocal.cpp @@ -27,12 +27,6 @@ int GetTLBVersion(tstring& filepath, DWORD& high, DWORD & low) { #ifdef _WIN32 -#ifdef _countof -#define COUNTOF _countof -#else -#define COUNTOF(a) (sizeof(a)/sizeof(a[0])) -#endif - int found = 0; TCHAR fullpath[1024]; diff --git a/Contrib/Makensisw/makensisw.cpp b/Contrib/Makensisw/makensisw.cpp index 269532b9..075ff752 100644 --- a/Contrib/Makensisw/makensisw.cpp +++ b/Contrib/Makensisw/makensisw.cpp @@ -413,6 +413,14 @@ BOOL CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { DragAcceptFiles(g_sdata.hwnd,TRUE); return TRUE; } + case MakensisAPI::QUERYHOST: { + if (MakensisAPI::QH_OUTPUTCHARSET) { + const UINT reqcp = CP_UTF8; // TODO: UTF16LE will be faster for makensis + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, (LONG_PTR)(1+reqcp)); + return TRUE; + } + return FALSE; + } case WM_NOTIFY: switch (((NMHDR*)lParam)->code ) { case EN_SELCHANGE: @@ -712,12 +720,12 @@ BOOL CALLBACK DialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { DWORD WINAPI MakeNSISProc(LPVOID p) { TCHAR eventnamebuf[100]; - wsprintf(eventnamebuf,MakensisAPI::SigintEventNameFmt,g_sdata.hwnd); + wsprintf(eventnamebuf, MakensisAPI::SigintEventNameFmt, g_sdata.hwnd); if (g_sdata.sigint_event) CloseHandle(g_sdata.sigint_event); - g_sdata.sigint_event = CreateEvent(NULL,FALSE,FALSE,eventnamebuf); + g_sdata.sigint_event = CreateEvent(NULL, FALSE, FALSE, eventnamebuf); if (!g_sdata.sigint_event) { - ErrorMessage(g_sdata.hwnd,_T("There was an error creating the abort event.")); - PostMessage(g_sdata.hwnd,WM_MAKENSIS_PROCESSCOMPLETE,0,0); + ErrorMessage(g_sdata.hwnd, _T("There was an error creating the abort event.")); + PostMessage(g_sdata.hwnd, WM_MAKENSIS_PROCESSCOMPLETE, 0, 0); return 1; } @@ -725,44 +733,21 @@ DWORD WINAPI MakeNSISProc(LPVOID p) { TCHAR buf[1024]; #endif char iobuf[1024]; //i/o buffer - STARTUPINFO si={sizeof(si),}; - SECURITY_ATTRIBUTES sa={sizeof(sa),}; - SECURITY_DESCRIPTOR sd={0,}; - PROCESS_INFORMATION pi={0,}; - HANDLE newstdout=0,read_stdout=0; - HANDLE newstdin=0,read_stdin=0; - OSVERSIONINFO osv={sizeof(osv)}; - GetVersionEx(&osv); - if (osv.dwPlatformId == VER_PLATFORM_WIN32_NT) { - InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION); - SetSecurityDescriptorDacl(&sd,true,NULL,false); - sa.lpSecurityDescriptor = &sd; - } - else sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = true; - if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) { - ErrorMessage(g_sdata.hwnd,_T("There was an error creating the output pipe.")); - PostMessage(g_sdata.hwnd,WM_MAKENSIS_PROCESSCOMPLETE,0,0); + STARTUPINFO si; + HANDLE newstdout,read_stdout; + + if (!InitSpawn(si, read_stdout, newstdout)) { + ErrorMessage(g_sdata.hwnd, _T("There was an error creating the pipe.")); + PostMessage(g_sdata.hwnd, WM_MAKENSIS_PROCESSCOMPLETE, 0, 0); return 1; } - if (!CreatePipe(&read_stdin,&newstdin,&sa,0)) { - ErrorMessage(g_sdata.hwnd,_T("There was an error creating the input pipe.")); - PostMessage(g_sdata.hwnd,WM_MAKENSIS_PROCESSCOMPLETE,0,0); - return 1; - } - GetStartupInfo(&si); - si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdOutput = newstdout; - si.hStdError = newstdout; - si.hStdInput = newstdin; - if (!CreateProcess(NULL,g_sdata.compile_command,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi)) { - TCHAR buf[MAX_STRING]; - wsprintf(buf,_T("Could not execute:\r\n %s."),g_sdata.compile_command); - ErrorMessage(g_sdata.hwnd,buf); - CloseHandle(newstdout); - CloseHandle(read_stdout); - PostMessage(g_sdata.hwnd,WM_MAKENSIS_PROCESSCOMPLETE,0,0); + PROCESS_INFORMATION pi; + if (!CreateProcess(0, g_sdata.compile_command, 0, 0, TRUE, CREATE_NEW_CONSOLE, 0, 0, &si, &pi)) { + TCHAR buf[MAX_STRING]; // BUGBUG: TODO: Too small? + wsprintf(buf,_T("Could not execute:\r\n %s."), g_sdata.compile_command); + ErrorMessage(g_sdata.hwnd, buf); + FreeSpawn(0, read_stdout, newstdout); + PostMessage(g_sdata.hwnd, WM_MAKENSIS_PROCESSCOMPLETE, 0, 0); return 1; } CloseHandle(newstdout); // close this handle (duplicated in subprocess) now so we get ERROR_BROKEN_PIPE @@ -774,8 +759,9 @@ DWORD WINAPI MakeNSISProc(LPVOID p) { #ifdef _UNICODE // this tweak is to prevent LogMessage from cutting in the middle of an UTF-8 sequence // we print only up to the latest \n of the buffer, and keep the remaining for the next loop + // BUGBUG: What if the line is longer than sizeof(iobuf)? char* lastLF = strrchr(iobuf,'\n'); - if (lastLF == NULL) lastLF = iobuf+dwRead-1; + if (!lastLF) lastLF = iobuf+dwRead-1; char ch = *++lastLF; *lastLF = '\0'; MultiByteToWideChar(CP_UTF8,0,iobuf,lastLF+1-iobuf,buf,COUNTOF(buf)); @@ -794,15 +780,9 @@ DWORD WINAPI MakeNSISProc(LPVOID p) { MultiByteToWideChar(CP_UTF8,0,iobuf,dwRead+1,buf,COUNTOF(buf)); LogMessage(g_sdata.hwnd, buf); #endif - DWORD dwExit; - GetExitCodeProcess(pi.hProcess, &dwExit); - g_sdata.retcode = dwExit; - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - CloseHandle(read_stdout); - CloseHandle(newstdin); - CloseHandle(read_stdin); - PostMessage(g_sdata.hwnd,WM_MAKENSIS_PROCESSCOMPLETE,0,0); + FreeSpawn(&pi, read_stdout, 0); + g_sdata.retcode = pi.dwProcessId; + PostMessage(g_sdata.hwnd, WM_MAKENSIS_PROCESSCOMPLETE, 0, 0); return 0; } diff --git a/Contrib/Makensisw/makensisw.h b/Contrib/Makensisw/makensisw.h index e63b3aa2..4dbb39ed 100644 --- a/Contrib/Makensisw/makensisw.h +++ b/Contrib/Makensisw/makensisw.h @@ -85,6 +85,12 @@ namespace MakensisAPI { NOTIFY_ERROR, NOTIFY_OUTPUT }; + enum sndmsg_e { + QUERYHOST = WM_APP + }; + enum QUERYHOST_e { + QH_OUTPUTCHARSET = 1 + }; } typedef enum { diff --git a/Contrib/Makensisw/utils.cpp b/Contrib/Makensisw/utils.cpp index 2bd713cb..78e4b9ea 100644 --- a/Contrib/Makensisw/utils.cpp +++ b/Contrib/Makensisw/utils.cpp @@ -568,57 +568,73 @@ void ResetSymbols() { } } -int InitBranding() { - TCHAR *s; - TCHAR opt[] = _T(" /version"); - s = (TCHAR *)GlobalAlloc(GPTR,(lstrlen(EXENAME)+lstrlen(opt)+1)*sizeof(TCHAR)); - lstrcpy(s, EXENAME); - lstrcat(s, opt); - { - STARTUPINFO si={sizeof(si),}; - SECURITY_ATTRIBUTES sa={sizeof(sa),}; - SECURITY_DESCRIPTOR sd={0,}; - PROCESS_INFORMATION pi={0,}; - HANDLE newstdout=0,read_stdout=0; +void FreeSpawn(PROCESS_INFORMATION *pPI, HANDLE hRd, HANDLE hWr) { + if (pPI) { + GetExitCodeProcess(pPI->hProcess, &pPI->dwProcessId); + CloseHandle(pPI->hProcess); + CloseHandle(pPI->hThread); + } + CloseHandle(hRd); + CloseHandle(hWr); +} +BOOL InitSpawn(STARTUPINFO &si, HANDLE &hRd, HANDLE &hWr) { + OSVERSIONINFO osv = {sizeof(osv)}; + GetVersionEx(&osv); + const bool winnt = VER_PLATFORM_WIN32_NT == osv.dwPlatformId; - OSVERSIONINFO osv={sizeof(osv)}; - GetVersionEx(&osv); - if (osv.dwPlatformId == VER_PLATFORM_WIN32_NT) { - InitializeSecurityDescriptor(&sd,SECURITY_DESCRIPTOR_REVISION); - SetSecurityDescriptorDacl(&sd,true,NULL,false); - sa.lpSecurityDescriptor = &sd; - } - else sa.lpSecurityDescriptor = NULL; - sa.bInheritHandle = true; - if (!CreatePipe(&read_stdout,&newstdout,&sa,0)) { + memset(&si, 0, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + GetStartupInfo(&si); + si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + + SECURITY_ATTRIBUTES sa={sizeof(sa)}; + SECURITY_DESCRIPTOR sd; + if (winnt) { + InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&sd, true, NULL, false); + sa.lpSecurityDescriptor = &sd; + } + sa.bInheritHandle = true; + BOOL okp = CreatePipe(&hRd, &hWr, &sa, 0); + si.hStdOutput = hWr, si.hStdError = hWr; + si.hStdInput = INVALID_HANDLE_VALUE; + return okp; +} + +int InitBranding() { + const TCHAR *opt = _T(" /version"); + UINT cch = lstrlen(EXENAME) + lstrlen(opt) + 1; + TCHAR *s = (TCHAR *)GlobalAlloc(GPTR, cch*sizeof(TCHAR)); + if (s) { + lstrcpy(s, EXENAME); + lstrcat(s, opt); + STARTUPINFO si; + HANDLE newstdout, read_stdout; + if (!InitSpawn(si, read_stdout, newstdout)) return 0; + PROCESS_INFORMATION pi; + if (!CreateProcess(0, s, 0, 0, TRUE, CREATE_NEW_CONSOLE, 0, 0, &si, &pi)) { + FreeSpawn(0, read_stdout, newstdout); return 0; } - GetStartupInfo(&si); - si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdOutput = newstdout; - si.hStdError = newstdout; - if (!CreateProcess(NULL,s,NULL,NULL,TRUE,CREATE_NEW_CONSOLE,NULL,NULL,&si,&pi)) { - CloseHandle(newstdout); - CloseHandle(read_stdout); - return 0; + char szBuf[1024], retval = 0; + DWORD dwRead = 0; + if (WAIT_OBJECT_0 == WaitForSingleObject(pi.hProcess, 10000)) { + ReadFile(read_stdout, szBuf, sizeof(szBuf)-1, &dwRead, NULL); + retval = 1; } - char szBuf[1024]; - DWORD dwRead = 1; - if (WaitForSingleObject(pi.hProcess,10000)!=WAIT_OBJECT_0) { - return 0; - } - ReadFile(read_stdout, szBuf, sizeof(szBuf)-1, &dwRead, NULL); + FreeSpawn(&pi, read_stdout, newstdout); szBuf[dwRead] = 0; int len = lstrlenA(szBuf); - if (len==0) return 0; - g_sdata.branding = (TCHAR *)GlobalAlloc(GPTR,(len+6)*sizeof(TCHAR)); - wsprintf(g_sdata.branding,_T("NSIS %hs"),szBuf); - g_sdata.brandingv = (char *)GlobalAlloc(GPTR,len+1); - lstrcpyA(g_sdata.brandingv,szBuf); + if (len==0) retval = 0; + g_sdata.branding = (TCHAR *)GlobalAlloc(GPTR, (len+6)*sizeof(TCHAR)); // LEAKED + wsprintf(g_sdata.branding, _T("NSIS %hs"), szBuf); + g_sdata.brandingv = (char *)GlobalAlloc(GPTR, len+1); // LEAKED + lstrcpyA(g_sdata.brandingv, szBuf); GlobalFree(s); + return retval; } - return 1; + return 0; } @@ -627,7 +643,7 @@ void InitTooltips(HWND h) { memset(&g_tip,0,sizeof(NTOOLTIP)); g_tip.tip_p = h; INITCOMMONCONTROLSEX icx; - icx.dwSize = sizeof(icx); + icx.dwSize = sizeof(icx); icx.dwICC = ICC_BAR_CLASSES; InitCommonControlsEx(&icx); DWORD dwStyle = WS_POPUP | WS_BORDER | TTS_ALWAYSTIP; diff --git a/Contrib/Makensisw/utils.h b/Contrib/Makensisw/utils.h index 5fcbe8cf..0d294ec7 100644 --- a/Contrib/Makensisw/utils.h +++ b/Contrib/Makensisw/utils.h @@ -30,6 +30,9 @@ #define MRU_LIST_SIZE 5 #define MRU_DISPLAY_LENGTH 40 +void FreeSpawn(PROCESS_INFORMATION *pPI, HANDLE hRd, HANDLE hWr); +BOOL InitSpawn(STARTUPINFO &si, HANDLE &hRd, HANDLE &hWr); + int SetArgv(const TCHAR *cmdLine, TCHAR ***argv); void SetTitle(HWND hwnd,const TCHAR *substr); void SetBranding(HWND hwnd); diff --git a/Contrib/SubStart/substart.c b/Contrib/SubStart/substart.c index 32924d36..92134811 100644 --- a/Contrib/SubStart/substart.c +++ b/Contrib/SubStart/substart.c @@ -87,7 +87,7 @@ int main( int argc, char *argv[] ) WARNING: The subprocess can't parse GetCommandLine() to find its own path since we pass the wrong path! */ if ( CreateProcess( szPath, GetCommandLine(), NULL, NULL, - FALSE, 0, NULL, NULL, &si, &pi ) ) + TRUE, 0, NULL, NULL, &si, &pi ) ) { WaitForSingleObject( pi.hProcess, INFINITE ); GetExitCodeProcess( pi.hProcess, (DWORD*) &err ); diff --git a/Docs/src/config.but b/Docs/src/config.but index 8dd894b2..3228ba50 100644 --- a/Docs/src/config.but +++ b/Docs/src/config.but @@ -12,7 +12,9 @@ \define{NsisACPcp} system default ANSI codepage (ACP) -\define{NsisInputCharset} ACP|OEM|CP#|UTF8|UTF16LE|UTF16BE +\define{NsisInputCharset} ACP|OEM|CP#|UTF8|UTF16 + +\define{NsisOutputCharset} ACP|OEM|CP#|UTF8[SIG]|UTF16[BOM] \define{NsisWarnBlockContainerBegin} \\
diff --git a/Docs/src/usage.but b/Docs/src/usage.but index 2b1ce6d0..eca32f3c 100644 --- a/Docs/src/usage.but +++ b/Docs/src/usage.but @@ -31,6 +31,8 @@ If you want to use MakeNSIS on the command line, the syntax of the makensis comm \b /INPUTCHARSET allows you to specify a specific codepage for files without a BOM. (\NsisInputCharset) +\b /OUTPUTCHARSET allows you to specify the codepage used by stdout when the output is redirected. (\NsisOutputCharset) + \b Using the /D switch one or more times will add to symbols to the globally defined list (See !define). \b Using the /X switch one or more times will execute the code you specify following it. Example: "/XAutoCloseWindow false" diff --git a/Source/SConscript b/Source/SConscript index d7e190ba..3f9af5b0 100644 --- a/Source/SConscript +++ b/Source/SConscript @@ -26,7 +26,6 @@ makensis_files = Split(""" tstring.cpp utf.cpp util.cpp - validateunicode.cpp winchar.cpp writer.cpp """) @@ -71,6 +70,7 @@ AddZLib(env, env['PLATFORM'], 'install-compiler') ##### Defines +env.Append(CPPDEFINES = ['MAKENSIS']) env.Append(CPPDEFINES = ['_WIN32_IE=0x0500']) ##### Set PCH diff --git a/Source/build.cpp b/Source/build.cpp index eea1de3a..1c5dd9e8 100644 --- a/Source/build.cpp +++ b/Source/build.cpp @@ -474,8 +474,7 @@ int CEXEBuild::add_string(const TCHAR *string, int process/*=1*/, UINT codepage/ init_shellconstantvalues(); if ((unsigned)-2 == codepage) { - assert(curlinereader); - codepage = curlinereader->StreamEncoding().GetCodepage(); + codepage = curlinereader ? curlinereader->StreamEncoding().GetCodepage() : CP_UTF8; // If the current source file is Unicode we have to pick a real codepage for ANSI! // It might not be the correct codepage but its the best we can do. // Not using CP_ACP to avoid heisenbugs when compiled on a different system. diff --git a/Source/build.h b/Source/build.h index d38a8432..2af951eb 100644 --- a/Source/build.h +++ b/Source/build.h @@ -80,6 +80,14 @@ namespace MakensisAPI { NOTIFY_ERROR, NOTIFY_OUTPUT // generated .exe file }; +#ifdef _WIN32 + enum sndmsg_e { + QUERYHOST = WM_APP // QUERYHOST_e in wParam + }; + enum QUERYHOST_e { + QH_OUTPUTCHARSET = 1 // return (wincodepage+1) or 0 for default + }; +#endif } #define PAGE_CUSTOM 0 diff --git a/Source/makenssi.cpp b/Source/makenssi.cpp index 17064e55..1e1ef761 100644 --- a/Source/makenssi.cpp +++ b/Source/makenssi.cpp @@ -36,12 +36,24 @@ using namespace std; -int g_noconfig=0; +bool g_dopause=false; int g_display_errors=1; -FILE *g_output=stdout; +FILE *g_output; +#ifdef _WIN32 +UINT g_wincon_orgoutcp; #ifdef _UNICODE -UINT g_initialCodepage; +WINSIO_OSDATA g_osdata_stdout; #endif +#endif + +static void dopause(void) +{ + if (!g_dopause) return; + if (g_display_errors) _ftprintf(g_output,_T("MakeNSIS done - hit enter to close...")); + fflush(stdout); + int a; + while ((a=_gettchar()) != _T('\r') && a != _T('\n') && a != 27/*esc*/); +} void quit() { @@ -59,9 +71,7 @@ static void myatexit() ResetPrintColor(); if (g_output != stdout && g_output) fclose(g_output); #ifdef _WIN32 -#ifdef _UNICODE - SetConsoleOutputCP(g_initialCodepage); -#endif + SetConsoleOutputCP(g_wincon_orgoutcp); #endif } @@ -160,6 +170,7 @@ static void print_usage() _T(" ") OPT_STR _T("NOCONFIG disables inclusion of ") PLATFORM_PATH_SEPARATOR_STR _T("nsisconf.nsh\n") _T(" ") OPT_STR _T("NOCD disabled the current directory change to that of the .nsi file\n") _T(" ") OPT_STR _T("INPUTCHARSET <") TSTR_INPUTCHARSET _T(">\n") + _T(" ") OPT_STR _T("OUTPUTCHARSET <") TSTR_OUTPUTCHARSET _T(">\n") _T(" ") OPT_STR _T("Ddefine[=value] defines the symbol \"define\" for the script [to value]\n") _T(" ") OPT_STR _T("Xscriptcmd executes scriptcmd in script (i.e. \"") OPT_STR _T("XOutFile poop.exe\")\n") _T(" ") _T(" parameters are processed by order (") OPT_STR _T("Ddef ins.nsi != ins.nsi ") OPT_STR _T("Ddef)\n") @@ -242,11 +253,11 @@ static int change_to_script_dir(CEXEBuild& build, tstring& script) return 0; } -static inline bool HasReqParam(TCHAR**argv,int argi,int argc) +static inline bool HasReqParam(TCHAR**argv,int argi,int argc,bool silent=false) { if (argi>=argc || !*argv[argi]) { - PrintColorFmtMsg_ERR(_T("Error: Missing required parameter!\n")); + if (!silent) PrintColorFmtMsg_ERR(_T("Error: Missing required parameter!\n")); return false; } return true; @@ -263,29 +274,111 @@ int _tmain(int argc, TCHAR **argv) #ifdef NSIS_HPUX_ALLOW_UNALIGNED_DATA_ACCESS allow_unaligned_data_access(); #endif + assert(sizeof(wchar_t) > 1 && sizeof(wchar_t) <= 4 && sizeof(WORD) == 2); + + HWND hostnotifyhandle=0; + const TCHAR*stdoutredirname=0; + NStreamEncoding inputenc, outputenc; + int argpos=0; + bool in_files=false; + bool do_cd=true; + bool no_logo=false; + bool initialparsefail=false; + bool noconfig=false; +#ifdef _WIN32 + signed char outputbom=1; +#endif + + // Some parameters have to be parsed early so we can initialize stdout and the "host API". + while (++argpos < argc && !initialparsefail) + { + if (!IS_OPT(argv[argpos])) break; // must be a filename, stop parsing + if (!_tcscmp(argv[argpos], _T("--"))) break; // stop parsing + if (_T('-') == argv[argpos][0] && !argv[argpos][1]) continue; // stdin + + const TCHAR *swname = &argv[argpos][1]; + if (!_tcsicmp(swname,_T("VERSION"))) argc=0; + else if (!_tcsicmp(swname,_T("NOTIFYHWND"))) + { + initialparsefail=!HasReqParam(argv,++argpos,argc,true); + if (initialparsefail) break; + hostnotifyhandle=(HWND)_ttol(argv[argpos]); +#ifdef _WIN32 + if (!IsWindow(hostnotifyhandle)) hostnotifyhandle=0; +#endif + } + else if (!_tcsicmp(swname,_T("OUTPUTCHARSET")) || !_tcsicmp(swname,_T("OCS"))) + { + initialparsefail=!HasReqParam(argv,++argpos,argc,true); + if (initialparsefail) break; +#ifdef _WIN32 + bool bom; + WORD cp=GetEncodingFromString(argv[argpos],bom); + if (NStreamEncoding::UNKNOWN == cp) + { + ++initialparsefail; + } + else + { + outputbom=bom ? 1 : -1; + outputenc.SetCodepage(cp); + } +#else + outputenc.SetCodepage(NStreamEncoding::UNKNOWN); +#endif + } +#ifdef _WIN32 + else if (!_tcsicmp(swname,_T("RAW"))) + { + // Emulate the scratchpaper.com fork and its /RAW switch. + // NOTE: Unlike the fork, we print \r\n and not just \n. + outputbom=0; + outputenc.SetCodepage(NStreamEncoding::UTF16LE); + } +#endif + else if (S7IsChEqualI('v',swname[0]) && swname[1] && !swname[2]) + { + no_logo=swname[1] >= _T('0') && swname[1] <= _T('2'); + } + // This must be parsed last because it will eat other switches + else if (S7IsChEqualI('o',swname[0]) && swname[1]) stdoutredirname=swname+1; + } + + +#ifdef _WIN32 + g_wincon_orgoutcp = GetConsoleOutputCP(); +#endif + + init_signals(hostnotifyhandle); + + + FILE*stdoutredir=stdout; + if (stdoutredirname) stdoutredir=my_fopen(stdoutredirname,"w"); + g_output=stdoutredir; + if (!g_output) g_output=stdout; +#if defined(_WIN32) && defined(_UNICODE) + if (hostnotifyhandle) + { + // The host can override the output format if they want to + LPARAM lp=MAKELONG(outputenc.GetCodepage(),outputbom); + LRESULT mr=SendMessage(hostnotifyhandle,MakensisAPI::QUERYHOST,MakensisAPI::QH_OUTPUTCHARSET,lp); + if (mr) outputenc.SetCodepage((WORD)--mr), outputbom = -1; + } + + if (!WinStdIO_OStreamInit(g_osdata_stdout,g_output,outputenc.GetCodepage(),outputbom)) + { + assert(!"StdIO init failed"); + return 1; + } +#endif + // g_output is now initialized and Print*/_[f]tprintf can be used + if (!stdoutredir) PrintColorFmtMsg_WARN(_T("Error opening output log for writing! Using stdout.\n")); + + unsigned int nousage=0; + unsigned int files_processed=0; + unsigned int cmds_processed=0; CEXEBuild build; - NStreamEncoding inputenc; - bool outputtried=0; - bool in_files=0; - bool do_cd=1; - bool no_logo=0; - int nousage=0; - int argpos=1; - int tmpargpos=1; - int files_processed=0; - int cmds_processed=0; - -#ifdef _UNICODE -#ifndef _O_U8TEXT - const int _O_U8TEXT=0x40000; // BUGBUG: This is bogus (Makensis will ONLY work on NT6) -#endif - _setmode(_fileno(stdout), _O_U8TEXT); // set stdout to UTF-8 -#ifdef _WIN32 - g_initialCodepage = GetConsoleOutputCP(); - SetConsoleOutputCP(CP_UTF8); // set console output to UTF-8 (especially useful for subprocesses like !system) -#endif -#endif try { build.initialize(argv[0]); @@ -296,66 +389,107 @@ int _tmain(int argc, TCHAR **argv) return 1; } - if (argc > 1 && IS_OPT(argv[tmpargpos]) && !_tcsicmp(&argv[tmpargpos][1],_T("VERSION"))) +#ifdef _WIN32 + build.notify_hwnd=hostnotifyhandle; +#else + const TCHAR*const badnonwinswitchfmt=OPT_STR _T("%s is disabled for non Win32 platforms."); + if (hostnotifyhandle) + build.warning(badnonwinswitchfmt,_T("NOTIFYHWND")); + if (NStreamEncoding::UNKNOWN==outputenc.GetCodepage()) + build.warning(badnonwinswitchfmt,_T("OUTPUTCHARSET")); +#endif // ~_WIN32 + + if (!argc) { _ftprintf(g_output,NSIS_VERSION); fflush(g_output); return 0; } - if (argc > 1 && IS_OPT(argv[tmpargpos]) && S7IsChEqualI('v',argv[tmpargpos][1])) - { - if (argv[tmpargpos][2] <= _T('2') && argv[tmpargpos][2] >= _T('0')) - { - no_logo=1; - } - tmpargpos++; - } - - if (!no_logo) - { - if (argc > tmpargpos && IS_OPT(argv[tmpargpos]) && S7IsChEqualI('o',argv[tmpargpos][1]) && argv[tmpargpos][2]) - { - g_output=FOPENTEXT(argv[tmpargpos]+2,"w"); - if (!g_output) - { - g_output=stdout; // Needs to be set before calling PrintColorFmtMsg* - PrintColorFmtMsg_WARN(_T("Error opening output log for writing. Using stdout.\n")); - } - outputtried=1; - } - print_logo(); - } - if (!g_output) g_output=stdout; - - // Look for /NOTIFYHWND so we can init_signals() - const int orgargpos=argpos; - while (argpos < argc) - { - if (!_tcscmp(argv[argpos], _T("--"))) break; - if (!IS_OPT(argv[argpos]) || !_tcscmp(argv[argpos], _T("-"))) break; - if (!_tcsicmp(&argv[argpos][1],_T("NOTIFYHWND"))) - { - if (!HasReqParam(argv, ++argpos, argc)) break; -#ifdef _WIN32 - build.notify_hwnd=(HWND)_ttol(argv[argpos]); - if (!IsWindow(build.notify_hwnd)) build.notify_hwnd=0; -#else - build.warning(OPT_STR _T("NOTIFYHWND is disabled for non Win32 platforms.")); -#endif - } - argpos++; - } - argpos=orgargpos; + if (!no_logo) print_logo(); - init_signals(build.notify_hwnd); + argpos=initialparsefail ? argc : 1; while (argpos < argc) { if (!_tcscmp(argv[argpos], _T("--"))) in_files=1; else if (IS_OPT(argv[argpos]) && _tcscmp(argv[argpos], _T("-")) && !in_files) { - if (S7IsChEqualI('d',argv[argpos][1]) && argv[argpos][2]) + if (!_tcsicmp(&argv[argpos][1],_T("NOCD"))) do_cd=false; + else if (!_tcsicmp(&argv[argpos][1],_T("NOCONFIG"))) noconfig=true; + else if (!_tcsicmp(&argv[argpos][1],_T("PAUSE"))) g_dopause=true; + else if (!_tcsicmp(&argv[argpos][1],_T("LICENSE"))) + { + if (build.display_info) print_license(); + nousage++; + } + else if (!_tcsicmp(&argv[argpos][1],_T("CMDHELP"))) + { + if (argpos < argc-1) + build.print_help(argv[++argpos]); + else + build.print_help(NULL); + nousage++; + } + else if (!_tcsicmp(&argv[argpos][1],_T("HDRINFO"))) + { + print_stub_info(build); + nousage++; + } + else if (!_tcsicmp(&argv[argpos][1],_T("INPUTCHARSET")) || !_tcsicmp(&argv[argpos][1],_T("ICS"))) + { + if (!HasReqParam(argv, ++argpos, argc)) break; + WORD cp = GetEncodingFromString(argv[argpos]); + if (NStreamEncoding::UNKNOWN == cp) + { + if (_tcsicmp(argv[argpos], _T("AUTO"))) + build.warning(OPT_STR _T("INPUTCHARSET: Ignoring invalid charset %s"), argv[argpos]); + cp = NStreamEncoding::AUTO; + } + inputenc.SafeSetCodepage(cp); + } + else if (S7IsChEqualI('v',argv[argpos][1]) && + argv[argpos][2] >= _T('0') && argv[argpos][2] <= _T('4') && !argv[argpos][3]) + { + int v=argv[argpos][2]-_T('0'); + build.display_script=v>3; + build.display_info=v>2; + build.display_warnings=v>1; + build.display_errors=v>0; + g_display_errors=build.display_errors; + } + else if (S7IsChEqualI('p',argv[argpos][1]) && + argv[argpos][2] >= _T('0') && argv[argpos][2] <= _T('5') && !argv[argpos][3]) + { +#ifdef _WIN32 + // priority setting added 01-2007 by Comm@nder21 + int p=argv[argpos][2]-_T('0'); + HANDLE hProc = GetCurrentProcess(); + struct + { + DWORD priority, fallback; + } static const classes[] = { + {IDLE_PRIORITY_CLASS, IDLE_PRIORITY_CLASS}, + {BELOW_NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS}, + {NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS}, + {ABOVE_NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS}, + {HIGH_PRIORITY_CLASS, HIGH_PRIORITY_CLASS}, + {REALTIME_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS} + }; + if (SetPriorityClass(hProc, classes[p].priority) == FALSE) + { + SetPriorityClass(hProc, classes[p].fallback); + } + if (p == 5) build.warning(_T("makensis is running in REALTIME priority mode!")); +#else + build.warning(badnonwinswitchfmt,_T("Px")); +#endif + } + // Already parsed these (must adjust argpos) + else if (!_tcsicmp(&argv[argpos][1],_T("NOTIFYHWND"))) ++argpos; + else if (!_tcsicmp(&argv[argpos][1],_T("OUTPUTCHARSET")) || !_tcsicmp(&argv[argpos][1],_T("OCS"))) ++argpos; + // These must be parsed last because they will eat other switches + else if (S7IsChEqualI('d',argv[argpos][1]) && argv[argpos][2]) { TCHAR *p=argv[argpos]+2; TCHAR *s=_tcsdup(p),*v; @@ -373,102 +507,12 @@ int _tmain(int argc, TCHAR **argv) } cmds_processed++; } - else if (S7IsChEqualI('o',argv[argpos][1]) && argv[argpos][2]) - { - if (!outputtried) - { - g_output=FOPENTEXT(argv[argpos]+2,"w"); - if (!g_output) - { - g_output=stdout; // Needs to be set before calling PrintColorFmtMsg* - if (build.display_errors) PrintColorFmtMsg_WARN(_T("Error opening output log for writing. Using stdout.\n")); - } - outputtried=1; - } - } - else if (!_tcsicmp(&argv[argpos][1],_T("NOCD"))) do_cd=0; - else if (S7IsChEqualI('v',argv[argpos][1]) && - argv[argpos][2] >= _T('0') && argv[argpos][2] <= _T('4') && !argv[argpos][3]) - { - int v=argv[argpos][2]-_T('0'); - build.display_script=v>3; - build.display_info=v>2; - build.display_warnings=v>1; - build.display_errors=v>0; - g_display_errors=build.display_errors; - } - else if (!_tcsicmp(&argv[argpos][1],_T("NOCONFIG"))) g_noconfig=1; - else if (!_tcsicmp(&argv[argpos][1],_T("PAUSE"))) g_dopause=1; - else if (!_tcsicmp(&argv[argpos][1],_T("LICENSE"))) - { - if (build.display_info) - { - print_license(); - } - nousage++; - } - else if (!_tcsicmp(&argv[argpos][1],_T("CMDHELP"))) - { - if (argpos < argc-1) - build.print_help(argv[++argpos]); - else - build.print_help(NULL); - nousage++; - } - else if (!_tcsicmp(&argv[argpos][1],_T("NOTIFYHWND"))) - { - ++argpos; // already parsed this - } - else if (!_tcsicmp(&argv[argpos][1],_T("HDRINFO"))) - { - print_stub_info(build); - nousage++; - } - else if (S7IsChEqualI('p',argv[argpos][1]) && - argv[argpos][2] >= _T('0') && argv[argpos][2] <= _T('5') && !argv[argpos][3]) - { + // Already parsed these #ifdef _WIN32 - // priority setting added 01-2007 by Comm@nder21 - int p=argv[argpos][2]-_T('0'); - HANDLE hProc = GetCurrentProcess(); - - struct - { - DWORD priority; - DWORD fallback; - } classes[] = { - {IDLE_PRIORITY_CLASS, IDLE_PRIORITY_CLASS}, - {BELOW_NORMAL_PRIORITY_CLASS, IDLE_PRIORITY_CLASS}, - {NORMAL_PRIORITY_CLASS, NORMAL_PRIORITY_CLASS}, - {ABOVE_NORMAL_PRIORITY_CLASS, HIGH_PRIORITY_CLASS}, - {HIGH_PRIORITY_CLASS, HIGH_PRIORITY_CLASS}, - {REALTIME_PRIORITY_CLASS, REALTIME_PRIORITY_CLASS} - }; - - if (SetPriorityClass(hProc, classes[p].priority) == FALSE) - { - SetPriorityClass(hProc, classes[p].fallback); - } - - if (p == 5) - build.warning(_T("makensis is running in REALTIME priority mode!")); - -#else - build.warning(OPT_STR _T("Px is disabled for non Win32 platforms.")); + else if (!_tcsicmp(&argv[argpos][1],_T("RAW"))) {} #endif - } - else if (!_tcsicmp(&argv[argpos][1],_T("INPUTCHARSET")) || !_tcsicmp(&argv[argpos][1],_T("ICS"))) - { - if (!HasReqParam(argv, ++argpos, argc)) break; - WORD cp = GetEncodingFromString(argv[argpos]); - if (NStreamEncoding::UNKNOWN == cp) - { - if (_tcsicmp(argv[argpos], _T("AUTO"))) - build.warning(OPT_STR _T("INPUTCHARSET: Ignoring invalid charset %s"), argv[argpos]); - cp = NStreamEncoding::AUTO; - } - inputenc.SafeSetCodepage(cp); - } + else if (!_tcsicmp(&argv[argpos][1],_T("VERSION"))) {} + else if (S7IsChEqualI('o',argv[argpos][1]) && argv[argpos][2]) {} else break; } @@ -476,10 +520,10 @@ int _tmain(int argc, TCHAR **argv) { files_processed++; if (!_tcscmp(argv[argpos],_T("-")) && !in_files) - g_dopause=0; - if (!g_noconfig) + g_dopause=false; + if (!noconfig) { - g_noconfig=1; + noconfig=true; tstring main_conf; TCHAR* env_var = _tgetenv(_T("NSISCONFDIR")); if(env_var == NULL) diff --git a/Source/script.cpp b/Source/script.cpp index 4feecac4..76e44d51 100644 --- a/Source/script.cpp +++ b/Source/script.cpp @@ -3241,6 +3241,9 @@ int CEXEBuild::doCommand(int which_token, LineParser &line) TCHAR *exec=line.gettoken_str(1); SCRIPT_MSG(_T("!execute: \"%s\"\n"),exec); #ifdef _WIN32 +#ifdef _UNICODE + RunChildProcessRedirected(0,exec); +#else PROCESS_INFORMATION pi; STARTUPINFO si={sizeof(STARTUPINFO),}; if (CreateProcess(NULL,exec,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi)) @@ -3249,6 +3252,7 @@ int CEXEBuild::doCommand(int which_token, LineParser &line) CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } +#endif #else TCHAR *execfixed = my_convert(exec); system(execfixed); diff --git a/Source/tstring.cpp b/Source/tstring.cpp index c9b08e8d..56f0da75 100644 --- a/Source/tstring.cpp +++ b/Source/tstring.cpp @@ -15,91 +15,10 @@ #ifdef _UNICODE #include "tstring.h" -#include "validateunicode.h" #include "util.h" #include #include -FILE* FileOpenUnicodeText(const TCHAR* file, const TCHAR* mode, BOOL* unicode) -{ - extern FILE *g_output; - CValidateUnicode::FILE_TYPE ftype = CValidateUnicode::UTF_8; // default file format is UTF-8 - if (unicode) *unicode = TRUE; - - // If we are reading an existing file, check to see what type of file it - // is first. - if (_tcsstr(mode, _T("w+")) || - _tcsstr(mode, _T("r"))) - { - FILE* fp = _tfopen(file, _T("rb")); - - if (fp) - { - MANAGE_WITH(fp, fclose); - fseek(fp, 0, SEEK_END); - size_t fileSize = ftell(fp); - if (fileSize == 0) - { - // Empty files are treated as UTF-8. - ftype = CValidateUnicode::UTF_8; - } - else - { - std::vector buffer(fileSize); - fseek(fp, 0, SEEK_SET); - fread(&buffer[0], sizeof(unsigned char), fileSize, fp); - - ftype = CValidateUnicode::CheckBOM(&buffer[0], buffer.size()); - - switch (ftype) - { - case CValidateUnicode::UTF_8: - case CValidateUnicode::UTF_16LE: - case CValidateUnicode::UTF_16BE: - break; - case CValidateUnicode::UTF_32LE: - case CValidateUnicode::UTF_32BE: - PrintColorFmtMsg_ERR(_T("File '%s' has a BOM marked as %s which is not supported at this time.\n"), - file, CValidateUnicode::TypeToName(ftype)); - exit(-1); - break; - case CValidateUnicode::UNKNOWN: - // If unknown, let's see if it's not just UTF_8 without a BOM. - if (CValidateUnicode::ValidateUTF8(&buffer[0], buffer.size()) == 2) - { - // contains UTF-8 characters sequences - _ftprintf(g_output, _T("File '%s' has no BOM but seems to be UTF-8.\n"), file); - ftype = CValidateUnicode::UTF_8; - } - break; - default: - PrintColorFmtMsg_ERR(_T("CValidateUnicode::CheckBOM() for file '%s' returned an unknown return value: %d\n"), - file, ftype); - exit(-1); - break; - } - } - } - } - - tstring strMode(mode); - - switch (ftype) - { - case CValidateUnicode::UTF_8: - strMode.append(_T(", ccs=UTF-8")); - break; - case CValidateUnicode::UTF_16LE: - strMode.append(_T(", ccs=UTF-16LE")); - break; - default: - // Looks like fopen() doesn't support other encodings of Unicode. - if (unicode) *unicode = FALSE; - break; - } - - return _tfopen(file, strMode.c_str()); -} CtoTString::CtoTString(const char* str) { diff --git a/Source/tstring.h b/Source/tstring.h index e8fd0e75..0094ae72 100644 --- a/Source/tstring.h +++ b/Source/tstring.h @@ -28,17 +28,10 @@ typedef std::wstring tstring; typedef std::wofstream tofstream; typedef std::wifstream tifstream; -// Use the following macros to open text files. -FILE* FileOpenUnicodeText(const TCHAR* file, const TCHAR* mode, BOOL* unicode); -#define FOPENTEXT(file, mode) FileOpenUnicodeText(file, _T(mode), NULL) -#define FOPENTEXT2(file, mode, unicode) FileOpenUnicodeText(file, _T(mode), unicode) #else typedef std::string tstring; typedef std::ofstream tofstream; typedef std::ifstream tifstream; -// Use the following macros to open text files. -#define FOPENTEXT(file, mode) fopen(file, mode) -#define FOPENTEXT2(file, mode, unicode) (*(unicode)=FALSE, fopen(file, mode)) #endif #ifndef _UNICODE diff --git a/Source/utf.cpp b/Source/utf.cpp index 7557b6d6..b901f2ad 100644 --- a/Source/utf.cpp +++ b/Source/utf.cpp @@ -16,6 +16,7 @@ */ #include "utf.h" +#include "util.h" #define FIX_ENDIAN_INT16LETOHOST_INPLACE FIX_ENDIAN_INT16_INPLACE @@ -259,10 +260,27 @@ bool WCToUTF16LEHlpr::Create(const TCHAR*in) } #endif +UINT DetectUTFBOM(void*Buffer, UINT cb) +{ + unsigned char *b = (unsigned char*) Buffer; + if (cb >= 3 && 0xef == b[0] && 0xbb == b[1] && 0xbf == b[2]) + return NStreamEncoding::UTF8; + if (cb >= 2) + { + if (cb >= 4 && !b[0] && !b[1] && 0xfe == b[2] && 0xff == b[3]) + return NStreamEncoding::UTF32BE; + if (0xff == b[0] && 0xfe == b[1]) + return (cb >= 4 && !b[2] && !b[3]) ? NStreamEncoding::UTF32LE : NStreamEncoding::UTF16LE; + if (0xfe == b[0] && 0xff == b[1]) + return NStreamEncoding::UTF16BE; + } + return 0; +} UINT DetectUTFBOM(FILE*strm) { /*\ - Tries to detect a BOM at the start of a stream. If a BOM is found it is eaten. + Tries to detect a BOM at the current position in a stream. + If a BOM is found it is eaten. NOTE: ungetc is only guaranteed to support 1 pushback, lets hope no MBCS file starts with parts of a BOM. \*/ @@ -358,7 +376,8 @@ bool NBaseStream::Attach(FILE*hFile, WORD enc, bool Seek /*= true*/) { Close(); m_hFile = hFile; - if (!m_hFile || !NStream::SetBinaryMode(m_hFile)) return false; + if (!m_hFile) return false; + if (!NStream::SetBinaryMode(m_hFile) && m_hFile != stdin) return false; fpos_t pos; if (Seek && !fgetpos(m_hFile, &pos)) rewind(m_hFile); else Seek = false; WORD cp = DetectUTFBOM(m_hFile); @@ -377,10 +396,36 @@ bool NOStream::WriteString(const wchar_t*Str, size_t cch /*= -1*/) CharEncConv cec; if (!cec.Initialize(m_Enc.GetCodepage(), -1)) return false; cec.SetAllowOptimizedReturn(true); + if ((unsigned)-1 != cch) cch *= sizeof(wchar_t); // cec.Convert wants byte count size_t cbConv; char *p = (char*) cec.Convert(Str, cch, &cbConv); return p && WriteOctets(p, cbConv); } +bool NOStream::WritePlatformNLString(const wchar_t*Str, size_t cch /*= -1*/) +{ +#ifdef _WIN32 + size_t cch2 = 0, nlcount = 0; + for(; cch2 < cch && Str[cch2]; ++cch2) if (L'\n' == Str[cch2]) ++nlcount; + if (nlcount) + { + cch = cch2 + nlcount; + wchar_t chPrev = 0, *buf = (wchar_t*) malloc(cch * sizeof(wchar_t)); + if (!buf) return false; + for(size_t s = 0, d = 0; d < cch; ++s, ++d) + { + if (L'\n' == Str[s]) + { + if (L'\r' != chPrev) buf[d++] = L'\r'; else --cch; + } + buf[d] = chPrev = Str[s]; + } + bool retval = WriteString(buf, cch); + free(buf); + return retval; + } +#endif + return WriteString(Str, cch); +} tstring NStreamLineReader::GetErrorMessage(UINT Error, const TCHAR*Filename, UINT Line) { @@ -450,26 +495,7 @@ l_restart: else #endif { - if (0xC0 == (0xC0 & chU8[0])) - { - ++cb; - if (0xE0 == (0xE0 & chU8[0])) - { - ++cb; - if (0xF0 == (0xF0 & chU8[0])) - { - ++cb; - if (0xF8 == (0xF8 & chU8[0])) - { - ++cb; - if (0xFC == (0xFE & chU8[0])) - ++cb; - else - goto l_badutf; - } - } - } - } + if (!UTF8_GetTrailCount(chU8[0], cb)) goto l_badutf; for(BYTE moreU8 = 0; moreU8 < cb;) { BYTE b; diff --git a/Source/utf.h b/Source/utf.h index 5512a5b5..2d4ef44e 100644 --- a/Source/utf.h +++ b/Source/utf.h @@ -19,22 +19,12 @@ #define NSIS_UTF_H #include "Platform.h" -#include -#include -#include "util.h" // For my_fopen -#ifdef _WIN32 -#include // For _setmode -#include // For _O_BINARY -#endif const WORD UNICODE_REPLACEMENT_CHARACTER = 0xfffd; #define TSTR_INPUTCHARSET _T("ACP|OEM|CP#|UTF8|UTF16") #define TSTR_OUTPUTCHARSET _T("ACP|OEM|CP#|UTF8[SIG]|UTF16[BOM]") - -void RawTStrToASCII(const TCHAR*in,char*out,UINT maxcch); - template T S7ChLwr(T c) { return c>='A' && c<='Z' ? (T)(c|32) : c; } template T S7ChUpr(T c) { return c>='a' && c<='z' ? (T)(c-'a'+'A') : c; } template bool S7IsChEqualI(char ch,T cmp) @@ -58,12 +48,51 @@ inline UINT32 CodePointFromUTF16SurrogatePair(unsigned short lea,unsigned short return ((UINT32)lea << 10) + tra + surrogate_offset; } +inline bool UTF8_GetTrailCount(unsigned char chFirst, unsigned char &cb) +{ + // This function should only be used to get a rough idea of how large the encoded + // codepoint is, just because it returns true does not mean that it is valid UTF-8! + cb = 0; + if (0xC0 == (0xC0 & chFirst)) + { + ++cb; + if (0xE0 == (0xE0 & chFirst)) + { + ++cb; + if (0xF0 == (0xF0 & chFirst)) + { + ++cb; + if (0xF8 == (0xF8 & chFirst)) + { + ++cb; + if (0xFC == (0xFE & chFirst)) ++cb; else return false; + } + } + } + } + return true; +} + +#ifdef MAKENSIS +#include +#include +#include "tstring.h" +#ifdef _WIN32 +#include // For _setmode +#include // For _O_BINARY +#endif + +FILE* my_fopen(const TCHAR *path, const char *mode); // from util.h + +void RawTStrToASCII(const TCHAR*in,char*out,UINT maxcch); + void UTF16InplaceEndianSwap(void*Buffer, UINT cch); UINT StrLenUTF16(const void*str); bool StrSetUTF16LE(tstring&dest, const void*src); UINT WCFromCodePoint(wchar_t*Dest,UINT cchDest,UINT32 CodPt); wchar_t* DupWCFromBytes(void*Buffer,UINT cbBuffer,WORD SrcCP); +UINT DetectUTFBOM(void*Buffer,UINT cb); UINT DetectUTFBOM(FILE*strm); WORD GetEncodingFromString(const TCHAR*s, bool&BOM); WORD GetEncodingFromString(const TCHAR*s); @@ -220,8 +249,9 @@ protected: NStreamEncoding m_Enc; public: - NBaseStream() : m_hFile(0) {} + NBaseStream(FILE *hFile = 0) : m_hFile(hFile) {} ~NBaseStream() { Close(); } + FILE* GetHandle() const { return m_hFile; } NStreamEncoding& StreamEncoding() { return m_Enc; } bool IsEOF() const { return feof(m_hFile) != 0; } @@ -280,6 +310,8 @@ public: class NOStream : public NBaseStream { public: + NOStream(FILE *hFile = 0) : NBaseStream(hFile) {} + bool CreateFileForWriting(const TCHAR* Path, WORD enc = NStreamEncoding::AUTO) { return Attach(my_fopen(Path, "w+b"), enc); @@ -314,6 +346,7 @@ public: return false; } bool WriteString(const wchar_t*Str, size_t cch = -1); + bool WritePlatformNLString(const wchar_t*Str, size_t cch = -1); }; class NStreamLineReader { @@ -343,4 +376,5 @@ protected: } }; +#endif // MAKENSIS #endif // NSIS_UTF_H diff --git a/Source/util.cpp b/Source/util.cpp index 973f62af..96d33c81 100644 --- a/Source/util.cpp +++ b/Source/util.cpp @@ -26,6 +26,7 @@ #include "util.h" #include "strlist.h" #include "winchar.h" +#include "utf.h" #ifndef _WIN32 # include @@ -50,21 +51,9 @@ namespace Apple { // defines struct section using namespace std; -int g_dopause=0; extern int g_display_errors; extern FILE *g_output; -void dopause(void) -{ - if (g_dopause) - { - if (g_display_errors) _ftprintf(g_output,_T("MakeNSIS done - hit enter to close...")); - fflush(stdout); - int a; - while ((a=_gettchar()) != _T('\r') && a != _T('\n') && a != 27/*esc*/); - } -} - double my_wtof(const wchar_t *str) { char buf[100]; @@ -639,8 +628,119 @@ size_t ExpandoStrFmtVaList(wchar_t*Stack, size_t cchStack, wchar_t**ppMalloc, co return cch; } +#if defined(_WIN32) && defined(_UNICODE) +int RunChildProcessRedirected(LPCWSTR cmdprefix, LPCWSTR cmdmain) +{ + // We have to deliver the requested output encoding to our host (if any) and the + // only way to do that is to convert the pipe content from what we hope is UTF-8. + // The reason we need a pipe in the first place is because we cannot trust the + // child to call GetConsoleOutputCP(), and even if we could, UTF-16 is not valid there. + UINT cp = CP_UTF8, mbtwcf = MB_ERR_INVALID_CHARS; + errno = ENOMEM; + if (!cmdprefix) cmdprefix = _T(""); + UINT cch1 = _tcslen(cmdprefix), cch2 = _tcslen(cmdmain); + WCHAR *cmd = (WCHAR*) malloc( (cch1 + cch2 + 1) * sizeof(WCHAR) ); + if (!cmd) return -1; + _tcscpy(cmd, cmdprefix); + _tcscat(cmd, cmdmain); + SECURITY_DESCRIPTOR sd = {1, 0, SE_DACL_PRESENT, NULL, }; + SECURITY_ATTRIBUTES sa = {sizeof(sa), &sd, true}; + const UINT orgwinconcp = GetConsoleCP(), orgwinconoutcp = GetConsoleOutputCP(); + HANDLE hPipRd, hPipWr; + PROCESS_INFORMATION pi; + BOOL ok = CreatePipe(&hPipRd, &hPipWr, &sa, 0); + if (!ok) + hPipRd = 0, hPipWr = 0; + else + { + STARTUPINFO si = {sizeof(si)}; + si.dwFlags = STARTF_USESTDHANDLES|STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdOutput = si.hStdError = hPipWr; + si.hStdInput = INVALID_HANDLE_VALUE; + errno = ECHILD; + SetConsoleOutputCP(cp); + ok = CreateProcess(0, cmd, 0, 0, TRUE, 0, 0, 0, &si, &pi); + CloseHandle(hPipWr); // We want ERROR_BROKEN_PIPE when the child is done + } + free(cmd); + DWORD childec = -1; + if (ok) + { + bool utf8 = true, okt; + char iobuf[512]; + DWORD cbRead, cbOfs = 0, cchwb = 0; + WCHAR wbuf[100], wchbuf[2+1]; // A surrogate pair + \0 + for(;;) + { + BOOL okr = ReadFile(hPipRd, iobuf+cbOfs, sizeof(iobuf)-cbOfs, &cbRead, 0); + cbRead += cbOfs, cbOfs = 0; + unsigned char cbTrail, cch; + for(DWORD i = 0; i < cbRead;) + { + cch = 0; + if (utf8) + { + okt = UTF8_GetTrailCount(iobuf[i], cbTrail); + if (!okt) // Not UTF-8? Switching to ACP + { +switchtoacp:cp = CP_ACP, mbtwcf = 0, utf8 = false; + SetConsoleOutputCP(cp); + continue; + } + if (!cbTrail) cch++, wchbuf[0] = iobuf[i]; // ASCII + } + else + { + cbTrail = !!IsDBCSLeadByteEx(cp, iobuf[i]); + } + if (i+cbTrail >= cbRead) // Read more first? + { + memmove(iobuf, iobuf+i, cbOfs = cbRead - i); + if (okr) break; else i = 0; + } + if (!cch) + { + cch = MultiByteToWideChar(cp, mbtwcf, &iobuf[i], 1+cbTrail, wchbuf, COUNTOF(wchbuf)-1); + if (!cch) + { + if (utf8) goto switchtoacp; + cch++, wchbuf[0] = UNICODE_REPLACEMENT_CHARACTER; + } + } + i += 1+cbTrail; + if (0xfeff == wchbuf[0] && 1 == cch) cch = 0; // MakeNsisW is not a fan of the BOM, eat it. + if (!cch) continue; + wbuf[cchwb++] = wchbuf[0]; + if (--cch) wbuf[cchwb++] = wchbuf[1]; + const bool fullbuf = cchwb+cch >= COUNTOF(wbuf)-1; // cch is 1 for surrogate pairs + if (!okr || fullbuf || L'\n' == wchbuf[0]) // Stop on \n so \r\n conversion has enough context (...\r\n vs ...\n) + { +#ifdef MAKENSIS + extern WINSIO_OSDATA g_osdata_stdout; + WinStdIO_OStreamWrite(g_osdata_stdout, wbuf, cchwb); // Faster than _ftprintf +#else + wbuf[cchwb] = L'\0'; + _ftprintf(g_output, _T("%s"), wbuf); +#endif + cchwb = 0; + } + } + if (!okr) break; + } + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &childec); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + SetConsoleCP(orgwinconcp); SetConsoleOutputCP(orgwinconoutcp); + CloseHandle(hPipRd); + return childec; +} +#endif -int sane_system(const TCHAR *command) { +int sane_system(const TCHAR *command) +{ #ifdef _WIN32 // workaround for bug #1509909 @@ -653,17 +753,119 @@ int sane_system(const TCHAR *command) { // `program files\nsis\makensis.exe" "args` // which obviously fails... // - // to avoid the stripping, a harmless string is prefixed - // to the command line. - tstring command_s = _T("IF 1==1 "); - command_s += command; - return _tsystem(command_s.c_str()); + // to avoid the stripping, a harmless string is prefixed to the command line. + const TCHAR* prefix = _T("IF 1==1 "); +#ifdef _UNICODE + if (!command) return 0; + if (!*command) return 1; + tstring fixedcmd = _tgetenv(_T("COMSPEC")); + if (!fixedcmd.length()) fixedcmd = _T("CMD.EXE"); + fixedcmd += _T(" /C "); + fixedcmd += prefix; + return RunChildProcessRedirected(fixedcmd.c_str(), command); +#else + tstring fixedcmd = prefix + _T("") + command; + return _tsystem(fixedcmd.c_str()); +#endif #else return _tsystem(command); #endif } +#ifdef _WIN32 +bool GetFileSize64(HANDLE hFile, ULARGE_INTEGER &uli) +{ + uli.LowPart = GetFileSize(hFile, &uli.HighPart); + return INVALID_FILE_SIZE != uli.LowPart || !GetLastError(); +} +#endif +#if defined(_WIN32) && defined(_UNICODE) && defined(MAKENSIS) +#include // for _get_osfhandle +bool WINAPI WinStdIO_OStreamInit(WINSIO_OSDATA&osd, FILE*strm, WORD cp, int bom) +{ + // bom < 0: override cp if UTF detected but never write BOM + // bom = 0: ignore BOM and force cp + // bom > 0: override cp if UTF detected, write BOM if it does not already exist + const int fd = _fileno(strm); + osd.mode = 0, osd.hCRT = strm, osd.hNative = (HANDLE) _get_osfhandle(fd); + if (INVALID_HANDLE_VALUE == osd.hNative) return false; + DWORD conmode; + if (GetConsoleMode(osd.hNative, &conmode)) osd.mode++; else osd.mode--; + bool succ = NStream::SetBinaryMode(fd); + DWORD cbio = 0; + ULARGE_INTEGER uli; + if (succ && GetFileSize64(osd.hNative, uli) && uli.QuadPart) + { + OVERLAPPED olap = {0}; // Used to read from start of file + unsigned char bufbom[4]; + if (ReadFile(osd.hNative, bufbom, sizeof(bufbom), &cbio, &olap)) + { + UINT detbom = DetectUTFBOM(bufbom, cbio); + if (detbom) cp = (WORD) detbom, bom = 0; + } + SetFilePointer(osd.hNative, 0, 0, FILE_END); + } + osd.mustwritebom = bom > 0 && !cbio, osd.cp = cp; + return succ || (sizeof(TCHAR)-1 && WinStdIO_IsConsole(osd)); // Don't care about BOM for WriteConsoleW +} +bool WINAPI WinStdIO_OStreamWrite(WINSIO_OSDATA&osd, const wchar_t *Str, UINT cch) +{ + if ((unsigned)-1 == cch) cch = _tcslen(Str); + DWORD cbio; + if (WinStdIO_IsConsole(osd)) + return !!WriteConsoleW(osd.hNative, Str, cch, &cbio, 0) || !cch; + NOStream strm(osd.hCRT); + NStreamEncoding &enc = strm.StreamEncoding(); + enc.SetCodepage(osd.cp); + bool retval = false; + if (osd.mustwritebom) + { + osd.mustwritebom = false; + if (enc.IsUnicode() && !strm.WriteBOM(enc)) + { + osd.mode = 1, osd.hNative = 0; // Something is wrong, stop writing! + goto end; + } + } + retval = strm.WritePlatformNLString(Str, cch); +end: + strm.Detach(); + return retval; +} +int WINAPI WinStdIO_vfwprintf(FILE*strm, const wchar_t*Fmt, va_list val) +{ + if (g_output == strm && Fmt) + { + extern WINSIO_OSDATA g_osdata_stdout; + ExpandoString buf; + errno = ENOMEM; + UINT cch = buf.StrFmt(Fmt, val, false); + if (cch && !WinStdIO_OStreamWrite(g_osdata_stdout, buf, cch)) + { + cch = 0, errno = EIO; + } + return cch ? cch : (*Fmt ? -1 : 0); + } + return vfwprintf(strm, Fmt, val); +} +int WinStdIO_fwprintf(FILE*strm, const wchar_t*Fmt, ...) +{ + va_list val; + va_start(val, Fmt); + int rv = _vftprintf(strm, Fmt, val); + va_end(val); + return rv; +} +int WinStdIO_wprintf(const wchar_t*Fmt, ...) +{ + va_list val; + va_start(val, Fmt); + int rv = _vftprintf(g_output, Fmt, val); + va_end(val); + return rv; +} +#endif void PrintColorFmtMsg(unsigned int type, const TCHAR *fmtstr, va_list args) { @@ -693,6 +895,9 @@ gottxtattrbak: case 1: txtattr = FOREGROUND_INTENSITY|FOREGROUND_GREEN|FOREGROUND_RED; break; case 2: txtattr = FOREGROUND_INTENSITY|FOREGROUND_RED; break; } + // Use original background color if our text will still be readable + if ((contxtattrbak & 0xF0) != (txtattr<<4)) txtattr |= (contxtattrbak & 0xF0); + if ((txtattr & 0xFF) == 0xFE) txtattr &= ~FOREGROUND_INTENSITY; // BrightYellow on BrightWhite is hard to read SetConsoleTextAttribute(hWin32Con, txtattr); } #endif diff --git a/Source/util.h b/Source/util.h index 0df8227f..9b945143 100644 --- a/Source/util.h +++ b/Source/util.h @@ -30,13 +30,9 @@ # include #endif - #include +#include -// these are the standard pause-before-quit stuff. -extern int g_dopause; -extern void dopause(void); - extern double my_wtof(const wchar_t *str); extern unsigned int my_strncpy(TCHAR*Dest, const TCHAR*Src, unsigned int cchMax); @@ -72,10 +68,10 @@ public: if (!p) throw std::bad_alloc(); m_heap = (T*) p; } - size_t StrFmt(const T*FmtStr, va_list Args) + size_t StrFmt(const T*FmtStr, va_list Args, bool throwonerr = true) { size_t n = ExpandoStrFmtVaList(m_stack, COUNTOF(m_stack), &m_heap, FmtStr, Args); - if (!n && *FmtStr) throw std::bad_alloc(); + if (throwonerr && !n && *FmtStr) throw std::bad_alloc(); return n; } T* GetPtr() { return m_heap ? m_heap : m_stack; } @@ -87,6 +83,34 @@ int sane_system(const TCHAR *command); void PrintColorFmtMsg(unsigned int type, const TCHAR *fmtstr, va_list args); void FlushOutputAndResetPrintColor(); #ifdef _WIN32 +#ifdef _UNICODE +int RunChildProcessRedirected(LPCWSTR cmdprefix, LPCWSTR cmdmain); +#ifdef MAKENSIS +typedef struct { + HANDLE hNative; + FILE*hCRT; + WORD cp; + signed char mode; // -1 = redirected, 0 = unknown, 1 = console + bool mustwritebom; +} WINSIO_OSDATA; +inline bool WinStdIO_IsConsole(WINSIO_OSDATA&osd) { return osd.mode > 0; } +inline bool WinStdIO_IsRedirected(WINSIO_OSDATA&osd) { return osd.mode < 0; } +bool WINAPI WinStdIO_OStreamInit(WINSIO_OSDATA&osd, FILE*strm, WORD cp, int bom = 1); +bool WINAPI WinStdIO_OStreamWrite(WINSIO_OSDATA&osd, const wchar_t *Str, UINT cch = -1); +int WINAPI WinStdIO_vfwprintf(FILE*strm, const wchar_t*Fmt, va_list val); +int WinStdIO_fwprintf(FILE*strm, const wchar_t*Fmt, ...); +int WinStdIO_wprintf(const wchar_t*Fmt, ...); +// We don't hook fflush since the native handle is only used with WriteConsoleW +#undef _vsntprintf +#define _vsntprintf Error: TODO +#undef _tprintf +#define _tprintf WinStdIO_wprintf +#undef _ftprintf +#define _ftprintf WinStdIO_fwprintf +#undef _vftprintf +#define _vftprintf WinStdIO_vfwprintf +#endif // ~MAKENSIS +#endif // ~_UNICODE #define ResetPrintColor() FlushOutputAndResetPrintColor() // For reset ONLY use PrintColorFmtMsg(0,NULL ... #define SetPrintColorWARN() PrintColorFmtMsg(1|0x10, NULL, (va_list)NULL) #define SetPrintColorERR() PrintColorFmtMsg(2|0x10, NULL, (va_list)NULL) @@ -94,7 +118,7 @@ void FlushOutputAndResetPrintColor(); #define ResetPrintColor() #define SetPrintColorWARN() #define SetPrintColorERR() -#endif +#endif // ~_WIN32 inline void PrintColorFmtMsg_WARN(const TCHAR *fmtstr, ...) { va_list val;