mirror of
				https://github.com/velopack/velopack.git
				synced 2025-10-25 15:19:22 +00:00 
			
		
		
		
	New setup architecture; more cross platform friendly and no need to load everything into memory
This commit is contained in:
		
							
								
								
									
										50
									
								
								Squirrel.sln
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								Squirrel.sln
									
									
									
									
									
								
							| @@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SquirrelCli", "src\Squirrel | ||||
| EndProject | ||||
| Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Setup", "src\Setup\Setup.vcxproj", "{6B406985-B2E1-4FED-A405-BD0694D68E93}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Squirrel.Shared", "src\Squirrel.Shared\Squirrel.Shared.csproj", "{352C15EA-622F-4132-80D8-9B6E3C83404E}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		CIBuild|Any CPU = CIBuild|Any CPU | ||||
| @@ -340,6 +342,54 @@ Global | ||||
| 		{6B406985-B2E1-4FED-A405-BD0694D68E93}.Release|x64.Build.0 = Release|x64 | ||||
| 		{6B406985-B2E1-4FED-A405-BD0694D68E93}.Release|x86.ActiveCfg = Release|Win32 | ||||
| 		{6B406985-B2E1-4FED-A405-BD0694D68E93}.Release|x86.Build.0 = Release|Win32 | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|Mixed Platforms.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|Mixed Platforms.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|x64.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.CIBuild|x86.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|Mixed Platforms.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|Mixed Platforms.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|x64.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Coverage|x86.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|Mixed Platforms.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|Mixed Platforms.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|Mixed Platforms.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|x64.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Mono Release|x86.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|Mixed Platforms.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{352C15EA-622F-4132-80D8-9B6E3C83404E}.Release|x86.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
|   | ||||
| @@ -2,54 +2,12 @@ | ||||
| #include <versionhelpers.h> | ||||
| #include <string> | ||||
| #include <functional> | ||||
| #include <tchar.h> | ||||
| #include "unzip.h" | ||||
| #include "bundle_marker.h" | ||||
| #include "platform_util.h" | ||||
|  | ||||
| using namespace std; | ||||
|  | ||||
| wstring getTempExePath() | ||||
| { | ||||
|     wchar_t tempFolderBuf[MAX_PATH]; | ||||
|     DWORD cTempFolder = GetTempPath(MAX_PATH, tempFolderBuf); | ||||
|     wchar_t tempFileBuf[MAX_PATH]; | ||||
|     GetTempFileName(tempFolderBuf, L"squirrel", 0, tempFileBuf); | ||||
|     DeleteFile(tempFileBuf); | ||||
|     wstring tempFile(tempFileBuf); | ||||
|     tempFile += L".exe"; | ||||
|     return tempFile; | ||||
| } | ||||
|  | ||||
| BYTE* getByteResource(int idx, DWORD* cBuf) | ||||
| { | ||||
|     auto f = FindResource(NULL, MAKEINTRESOURCE(idx), L"DATA"); | ||||
|     if (!f) throw wstring(L"Unable to find resource " + to_wstring(idx)); | ||||
|  | ||||
|     auto r = LoadResource(NULL, f); | ||||
|     if (!r) throw wstring(L"Unable to load resource " + to_wstring(idx)); | ||||
|  | ||||
|     *cBuf = SizeofResource(NULL, f); | ||||
|     return (BYTE*)LockResource(r); | ||||
| } | ||||
|  | ||||
| wstring getCurrentExecutablePath() | ||||
| { | ||||
|     wchar_t ourFile[MAX_PATH]; | ||||
|     HMODULE hMod = GetModuleHandle(NULL); | ||||
|     GetModuleFileName(hMod, ourFile, _countof(ourFile)); | ||||
|     return wstring(ourFile); | ||||
| } | ||||
|  | ||||
| wstring getNameFromPath(wstring path) | ||||
| { | ||||
|     auto idx = path.find_last_of('\\'); | ||||
|  | ||||
|     // if we can't find last \ or the name is too short, default to 'Setup' | ||||
|     if (idx == wstring::npos || path.length() < idx + 3) | ||||
|         return L"Setup"; | ||||
|  | ||||
|     return path.substr(idx + 1); | ||||
| } | ||||
|  | ||||
| // https://stackoverflow.com/a/874160/184746 | ||||
| bool hasEnding(std::wstring const& fullString, std::wstring const& ending) | ||||
| { | ||||
| @@ -90,96 +48,51 @@ void unzipSingleFile(BYTE* zipBuf, DWORD cZipBuf, wstring fileLocation, std::fun | ||||
|     if (!unzipSuccess) throw wstring(L"Unable to extract embedded package (predicate not found)."); | ||||
| } | ||||
|  | ||||
| // https://stackoverflow.com/a/17387176/184746 | ||||
| void throwLastWin32Error() | ||||
| { | ||||
|     DWORD errorMessageID = ::GetLastError(); | ||||
|     if (errorMessageID == 0) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     LPWSTR messageBuffer = nullptr; | ||||
|     size_t size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||||
|                                 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); | ||||
|  | ||||
|     std::wstring message(messageBuffer, size); | ||||
|     throw message; | ||||
| } | ||||
|  | ||||
| void wexec(const wchar_t* cmd) | ||||
| { | ||||
|     LPTSTR szCmdline = _tcsdup(cmd); // https://stackoverflow.com/a/10044348/184746 | ||||
|  | ||||
|     STARTUPINFO si = { 0 }; | ||||
|     si.cb = sizeof(STARTUPINFO); | ||||
|     si.wShowWindow = SW_SHOW; | ||||
|     si.dwFlags = STARTF_USESHOWWINDOW; | ||||
|  | ||||
|     PROCESS_INFORMATION pi = { 0 }; | ||||
|     if (!CreateProcess(NULL, szCmdline, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) { | ||||
|         throwLastWin32Error(); | ||||
|     } | ||||
|  | ||||
|     WaitForSingleObject(pi.hProcess, INFINITE); | ||||
|  | ||||
|     DWORD dwExitCode = 0; | ||||
|     if (!GetExitCodeProcess(pi.hProcess, &dwExitCode)) { | ||||
|         dwExitCode = (DWORD)-9; | ||||
|     } | ||||
|  | ||||
|     CloseHandle(pi.hProcess); | ||||
|     CloseHandle(pi.hThread); | ||||
|  | ||||
|     if (dwExitCode != 0) { | ||||
|         throw wstring(L"Process exited with error code: " + to_wstring(dwExitCode)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| int showErrorDialog(wstring msg) | ||||
| { | ||||
|     wstring myPath = getCurrentExecutablePath(); | ||||
|     wstring myName = getNameFromPath(myPath); | ||||
|     wstring errorTitle = myName + L" Error"; | ||||
|     MessageBox(0, msg.c_str(), errorTitle.c_str(), MB_OK | MB_ICONERROR); | ||||
|     return -1; | ||||
| } | ||||
|  | ||||
| int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ PWSTR pCmdLine, _In_ int nCmdShow) | ||||
| { | ||||
|     if (!IsWindows7SP1OrGreater()) { | ||||
|         return showErrorDialog(L"This application requires Windows 7 SP1 or later and cannot be installed on this computer."); | ||||
|         util::show_error_dialog(L"This application requires Windows 7 SP1 or later and cannot be installed on this computer."); | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     wstring myPath = getCurrentExecutablePath(); | ||||
|     wstring updaterPath = getTempExePath(); | ||||
|     wstring myPath = util::get_current_process_path(); | ||||
|     wstring updaterPath = util::get_temp_file_path(L"exe"); | ||||
|  | ||||
|     try { | ||||
|         // locate bundled package | ||||
|         DWORD cZipBuf; | ||||
|         BYTE* zipBuf = getByteResource(205, &cZipBuf); // 205 = BundledPackageBytes | ||||
|         BYTE* memAddr = util::mmap_read(myPath, 0); | ||||
|         int64_t packageOffset, packageLength; | ||||
|         bundle_marker_t::header_offset(&packageOffset, &packageLength); | ||||
|         BYTE* pkgStart = memAddr + packageOffset; | ||||
|         if (packageOffset == 0 || packageLength == 0) { | ||||
|             util::munmap(memAddr); | ||||
|             util::show_error_dialog(L"An error occurred while running setup. The embedded package was not found. Please contact the application author."); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         // extract Squirrel installer | ||||
|         std::function<bool(ZIPENTRY&)> endsWithSquirrel([](ZIPENTRY& z) { | ||||
|             return hasEnding(std::wstring(z.name), L"Squirrel.exe"); | ||||
|         }); | ||||
|         unzipSingleFile(zipBuf, cZipBuf, updaterPath, endsWithSquirrel); | ||||
|         unzipSingleFile(pkgStart, packageLength, updaterPath, endsWithSquirrel); | ||||
|         util::munmap(memAddr); | ||||
|  | ||||
|         // run installer and forward our command line arguments | ||||
|         wstring cmd = L"\"" + updaterPath + L"\" --setup \"" + myPath + L"\" " + pCmdLine; | ||||
|         wexec(cmd.c_str()); | ||||
|         util::wexec(cmd.c_str()); | ||||
|     } | ||||
|     catch (wstring wsx) { | ||||
|         return showErrorDialog(L"An error occurred while running setup: " + wsx + L". Please contact the application author."); | ||||
|         util::show_error_dialog(L"An error occurred while running setup. " + wsx + L". Please contact the application author."); | ||||
|     } | ||||
|     catch (std::exception ex) { | ||||
|         // nasty shit to convert from ascii to wide-char. this will fail if there are multi-byte characters. | ||||
|         // hopefully we remember to throw 'wstring' everywhere instead of 'exception' and it doesn't matter. | ||||
|         string msg = ex.what(); | ||||
|         wstring wsTmp(msg.begin(), msg.end()); | ||||
|         return showErrorDialog(L"An error occurred while running setup: " + wsTmp + L". Please contact the application author."); | ||||
|         util::show_error_dialog(L"An error occurred while running setup. " + wsTmp + L". Please contact the application author."); | ||||
|     } | ||||
|     catch (...) { | ||||
|         return showErrorDialog(L"An unknown error occurred while running setup. Please contact the application author."); | ||||
|         util::show_error_dialog(L"An unknown error occurred while running setup. Please contact the application author."); | ||||
|     } | ||||
|  | ||||
|     // clean-up after ourselves | ||||
|   | ||||
| @@ -87,10 +87,14 @@ | ||||
|     </Manifest> | ||||
|   </ItemDefinitionGroup> | ||||
|   <ItemGroup> | ||||
|     <ClCompile Include="bundle_marker.cpp" /> | ||||
|     <ClCompile Include="platform_util.cpp" /> | ||||
|     <ClCompile Include="Setup.cpp" /> | ||||
|     <ClCompile Include="unzip.cpp" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClInclude Include="bundle_marker.h" /> | ||||
|     <ClInclude Include="platform_util.h" /> | ||||
|     <ClInclude Include="unzip.h" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -21,11 +21,23 @@ | ||||
|     <ClCompile Include="unzip.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="bundle_marker.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|     <ClCompile Include="platform_util.cpp"> | ||||
|       <Filter>Source Files</Filter> | ||||
|     </ClCompile> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <ClInclude Include="unzip.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="bundle_marker.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|     <ClInclude Include="platform_util.h"> | ||||
|       <Filter>Header Files</Filter> | ||||
|     </ClInclude> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <Text Include="compatibility.manifest"> | ||||
|   | ||||
							
								
								
									
										29
									
								
								src/Setup/bundle_marker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Setup/bundle_marker.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #include "bundle_marker.h" | ||||
| #include <windows.h> | ||||
| #include <string> | ||||
|  | ||||
| using namespace std; | ||||
|  | ||||
| void bundle_marker_t::header_offset(int64_t* pOffset, int64_t* pLength) | ||||
| { | ||||
|     // Contains the bundle_placeholder default value at compile time. | ||||
|     // the first 8 bytes are replaced by squirrel with the offset  | ||||
|     // where the package is located. | ||||
|     static volatile uint8_t placeholder[] = | ||||
|     { | ||||
|         // 8 bytes represent the package offset  | ||||
|         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|         // 8 bytes represent the package length  | ||||
|         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|         // 64 bytes represent the bundle signature: SHA-256 for "squirrel bundle" | ||||
|         0x94, 0xf0, 0xb1, 0x7b, 0x68, 0x93, 0xe0, 0x29, | ||||
|         0x37, 0xeb, 0x34, 0xef, 0x53, 0xaa, 0xe7, 0xd4, | ||||
|         0x2b, 0x54, 0xf5, 0x70, 0x7e, 0xf5, 0xd6, 0xf5, | ||||
|         0x78, 0x54, 0x98, 0x3e, 0x5e, 0x94, 0xed, 0x7d | ||||
|     }; | ||||
|  | ||||
|     volatile bundle_marker_t* marker = reinterpret_cast<volatile bundle_marker_t*>(placeholder); | ||||
|     *pOffset = marker->locator.bundle_header_offset; | ||||
|     *pLength = marker->locator.bundle_header_length; | ||||
| } | ||||
|  | ||||
							
								
								
									
										28
									
								
								src/Setup/bundle_marker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/Setup/bundle_marker.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| #ifndef __BUNDLE_MARKER_H__ | ||||
| #define __BUNDLE_MARKER_H__ | ||||
|  | ||||
| #include <cstdint> | ||||
|  | ||||
| #pragma pack(push, 1) | ||||
| union bundle_marker_t | ||||
| { | ||||
| public: | ||||
|     uint8_t placeholder[48]; | ||||
|     struct | ||||
|     { | ||||
|         int64_t bundle_header_offset; | ||||
|         int64_t bundle_header_length; | ||||
|         uint8_t signature[32]; | ||||
|     } locator; | ||||
|  | ||||
|     static void header_offset(int64_t* pOffset, int64_t* pLength); | ||||
|     static bool is_bundle() | ||||
|     { | ||||
|         int64_t offset, length; | ||||
|         header_offset(&offset, &length); | ||||
|         return offset != 0; | ||||
|     } | ||||
| }; | ||||
| #pragma pack(pop) | ||||
|  | ||||
| #endif // __BUNDLE_MARKER_H__ | ||||
							
								
								
									
										148
									
								
								src/Setup/platform_util.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								src/Setup/platform_util.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| #include "platform_util.h" | ||||
| #include <windows.h> | ||||
| #include <tchar.h> | ||||
| #include <string> | ||||
|  | ||||
| using namespace std; | ||||
|  | ||||
| wstring get_filename_from_path(wstring& path) | ||||
| { | ||||
|     auto idx = path.find_last_of('\\'); | ||||
|  | ||||
|     // if we can't find last \ or the name is too short, default to 'Setup' | ||||
|     if (idx == wstring::npos || path.length() < idx + 3) | ||||
|         return L"Setup"; | ||||
|  | ||||
|     return path.substr(idx + 1); | ||||
| } | ||||
|  | ||||
| // https://stackoverflow.com/a/17387176/184746 | ||||
| void throwLastWin32Error(wstring addedInfo) | ||||
| { | ||||
|     DWORD errorMessageID = ::GetLastError(); | ||||
|     if (errorMessageID == 0) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     LPWSTR messageBuffer = nullptr; | ||||
|     size_t size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, | ||||
|                                 NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&messageBuffer, 0, NULL); | ||||
|  | ||||
|     wstring message(messageBuffer, size); | ||||
|  | ||||
|     if (addedInfo.empty()) { | ||||
|         throw message; | ||||
|     } | ||||
|     else { | ||||
|         throw wstring(addedInfo + L" \n" + message); | ||||
|     } | ||||
| } | ||||
|  | ||||
| std::wstring util::get_temp_file_path(wstring extension) | ||||
| { | ||||
|     wchar_t tempFolderBuf[MAX_PATH]; | ||||
|     DWORD cTempFolder = GetTempPath(MAX_PATH, tempFolderBuf); | ||||
|     wchar_t tempFileBuf[MAX_PATH]; | ||||
|     GetTempFileName(tempFolderBuf, L"squirrel", 0, tempFileBuf); | ||||
|     DeleteFile(tempFileBuf); | ||||
|     wstring tempFile(tempFileBuf); | ||||
|  | ||||
|     if (!extension.empty()) | ||||
|         tempFile += L"." + extension; | ||||
|  | ||||
|     return tempFile; | ||||
| } | ||||
|  | ||||
| std::wstring util::get_current_process_path() | ||||
| { | ||||
|     wchar_t ourFile[MAX_PATH]; | ||||
|     HMODULE hMod = GetModuleHandle(NULL); | ||||
|     GetModuleFileName(hMod, ourFile, _countof(ourFile)); | ||||
|     return wstring(ourFile); | ||||
| } | ||||
|  | ||||
| void util::wexec(const wchar_t* cmd) | ||||
| { | ||||
|     LPTSTR szCmdline = _tcsdup(cmd); // https://stackoverflow.com/a/10044348/184746 | ||||
|  | ||||
|     STARTUPINFO si = { 0 }; | ||||
|     si.cb = sizeof(STARTUPINFO); | ||||
|     si.wShowWindow = SW_SHOW; | ||||
|     si.dwFlags = STARTF_USESHOWWINDOW; | ||||
|  | ||||
|     PROCESS_INFORMATION pi = { 0 }; | ||||
|     if (!CreateProcess(NULL, szCmdline, NULL, NULL, false, 0, NULL, NULL, &si, &pi)) { | ||||
|         throwLastWin32Error(L"Unable to start install process."); | ||||
|     } | ||||
|  | ||||
|     WaitForSingleObject(pi.hProcess, INFINITE); | ||||
|  | ||||
|     DWORD dwExitCode = 0; | ||||
|     if (!GetExitCodeProcess(pi.hProcess, &dwExitCode)) { | ||||
|         dwExitCode = (DWORD)-9; | ||||
|     } | ||||
|  | ||||
|     CloseHandle(pi.hProcess); | ||||
|     CloseHandle(pi.hThread); | ||||
|  | ||||
|     if (dwExitCode != 0) { | ||||
|         throw wstring(L"Process exited with error code: " + to_wstring(dwExitCode)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void util::show_error_dialog(std::wstring msg) | ||||
| { | ||||
|     wstring myPath = get_current_process_path(); | ||||
|     wstring myName = get_filename_from_path(myPath); | ||||
|     wstring errorTitle = myName + L" Error"; | ||||
|     MessageBox(0, msg.c_str(), errorTitle.c_str(), MB_OK | MB_ICONERROR); | ||||
| } | ||||
|  | ||||
| // https://github.com/dotnet/runtime/blob/26c9b2883e0b6daaa98304fdc2912abec25dc216/src/native/corehost/hostmisc/pal.windows.cpp#L68 | ||||
| void* map_file_impl(const wstring& path, size_t* length, DWORD mapping_protect, DWORD view_desired_access) | ||||
| { | ||||
|     HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | ||||
|  | ||||
|     if (file == INVALID_HANDLE_VALUE) { | ||||
|         throwLastWin32Error(L"Failed to map file. CreateFileW() failed with error."); | ||||
|     } | ||||
|  | ||||
|     if (length != nullptr) { | ||||
|         LARGE_INTEGER fileSize; | ||||
|         if (GetFileSizeEx(file, &fileSize) == 0) { | ||||
|             CloseHandle(file); | ||||
|             throwLastWin32Error(L"Failed to map file. GetFileSizeEx() failed with error."); | ||||
|         } | ||||
|         *length = (size_t)fileSize.QuadPart; | ||||
|     } | ||||
|  | ||||
|     HANDLE map = CreateFileMappingW(file, NULL, mapping_protect, 0, 0, NULL); | ||||
|  | ||||
|     if (map == NULL) { | ||||
|         CloseHandle(file); | ||||
|         throwLastWin32Error(L"Failed to map file. CreateFileMappingW() failed with error."); | ||||
|     } | ||||
|  | ||||
|     void* address = MapViewOfFile(map, view_desired_access, 0, 0, 0); | ||||
|  | ||||
|     // The file-handle (file) and mapping object handle (map) can be safely closed | ||||
|     // once the file is mapped. The OS keeps the file open if there is an open mapping into the file. | ||||
|     CloseHandle(map); | ||||
|     CloseHandle(file); | ||||
|  | ||||
|     if (address == NULL) { | ||||
|         throwLastWin32Error(L"Failed to map file. MapViewOfFile() failed with error."); | ||||
|     } | ||||
|  | ||||
|     return address; | ||||
| } | ||||
|  | ||||
| uint8_t* util::mmap_read(const std::wstring& filePath, size_t* length) | ||||
| { | ||||
|     return (uint8_t*)map_file_impl(filePath, length, PAGE_READONLY, FILE_MAP_READ); | ||||
| } | ||||
|  | ||||
| bool util::munmap(uint8_t* addr) | ||||
| { | ||||
|     return UnmapViewOfFile(addr) != 0; | ||||
| } | ||||
							
								
								
									
										16
									
								
								src/Setup/platform_util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Setup/platform_util.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| #ifndef __PLATFORM_UTIL_H__ | ||||
| #define __PLATFORM_UTIL_H__ | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| namespace util | ||||
| { | ||||
|     std::wstring get_temp_file_path(std::wstring extension); | ||||
|     std::wstring get_current_process_path(); | ||||
|     void wexec(const wchar_t* cmd); | ||||
|     void show_error_dialog(std::wstring msg); | ||||
|     uint8_t* mmap_read(const std::wstring& filePath, size_t* length); | ||||
|     bool munmap(uint8_t* addr); | ||||
| } | ||||
|  | ||||
| #endif // __PLATFORM_UTIL_H__ | ||||
| @@ -7,7 +7,7 @@ namespace Microsoft.NET.HostModel | ||||
| #if NET5_0_OR_GREATER | ||||
|     [System.Runtime.Versioning.SupportedOSPlatform("windows")] | ||||
| #endif | ||||
|     internal partial class ResourceUpdater | ||||
|     public partial class ResourceUpdater | ||||
|     { | ||||
|         public ResourceUpdater(string peFile, bool bDeleteExistingResources) | ||||
|         { | ||||
| @@ -13,7 +13,7 @@ namespace Microsoft.NET.HostModel | ||||
|     /// in a PE image. It currently only works on Windows, because it | ||||
|     /// requires various kernel32 APIs. | ||||
|     /// </summary> | ||||
|     internal partial class ResourceUpdater : IDisposable | ||||
|     public partial class ResourceUpdater : IDisposable | ||||
|     { | ||||
|         private sealed class Kernel32 | ||||
|         { | ||||
							
								
								
									
										92
									
								
								src/Squirrel.Shared/SetupBundle.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/Squirrel.Shared/SetupBundle.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.IO.MemoryMappedFiles; | ||||
| using System.Threading; | ||||
| using Microsoft.NET.HostModel; | ||||
| using Microsoft.NET.HostModel.AppHost; | ||||
| 
 | ||||
| namespace Squirrel.Shared | ||||
| { | ||||
|     public static class SetupBundle | ||||
|     { | ||||
|         public static bool IsBundle(string setupPath, out long bundleOffset, out long bundleLength) | ||||
|         { | ||||
|             byte[] bundleSignature = { | ||||
|                 // 64 bytes represent the bundle signature: SHA-256 for "squirrel bundle" | ||||
|                 0x94, 0xf0, 0xb1, 0x7b, 0x68, 0x93, 0xe0, 0x29, | ||||
|                 0x37, 0xeb, 0x34, 0xef, 0x53, 0xaa, 0xe7, 0xd4, | ||||
|                 0x2b, 0x54, 0xf5, 0x70, 0x7e, 0xf5, 0xd6, 0xf5, | ||||
|                 0x78, 0x54, 0x98, 0x3e, 0x5e, 0x94, 0xed, 0x7d | ||||
|             }; | ||||
| 
 | ||||
|             long offset = 0; | ||||
|             long length = 0; | ||||
|             void FindBundleHeader() | ||||
|             { | ||||
|                 using (var memoryMappedFile = MemoryMappedFile.CreateFromFile(setupPath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read)) | ||||
|                 using (MemoryMappedViewAccessor accessor = memoryMappedFile.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) { | ||||
|                     int position = BinaryUtils.SearchInFile(accessor, bundleSignature); | ||||
|                     if (position == -1) { | ||||
|                         throw new PlaceHolderNotFoundInAppHostException(bundleSignature); | ||||
|                     } | ||||
| 
 | ||||
|                     offset = accessor.ReadInt64(position - 16); | ||||
|                     length = accessor.ReadInt64(position - 8); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             Utility.Retry(FindBundleHeader); | ||||
| 
 | ||||
|             bundleOffset = offset; | ||||
|             bundleLength = length; | ||||
| 
 | ||||
|             return bundleOffset != 0 && bundleLength != 0; | ||||
|         } | ||||
| 
 | ||||
|         public static void CreatePackageBundle(string setupPath, string packagePath) | ||||
|         { | ||||
|             long bundleOffset, bundleLength; | ||||
|             using (var pkgStream = File.OpenRead(packagePath)) | ||||
|             using (var setupStream = File.Open(setupPath, FileMode.Append, FileAccess.Write)) { | ||||
|                 bundleOffset = setupStream.Position; | ||||
|                 bundleLength = pkgStream.Length; | ||||
|                 pkgStream.CopyTo(setupStream); | ||||
|             } | ||||
| 
 | ||||
|             byte[] placeholder = { | ||||
|                 // 8 bytes represent the package offset  | ||||
|                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|                 // 8 bytes represent the package length  | ||||
|                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | ||||
|                 // 64 bytes represent the bundle signature: SHA-256 for "squirrel bundle" | ||||
|                 0x94, 0xf0, 0xb1, 0x7b, 0x68, 0x93, 0xe0, 0x29, | ||||
|                 0x37, 0xeb, 0x34, 0xef, 0x53, 0xaa, 0xe7, 0xd4, | ||||
|                 0x2b, 0x54, 0xf5, 0x70, 0x7e, 0xf5, 0xd6, 0xf5, | ||||
|                 0x78, 0x54, 0x98, 0x3e, 0x5e, 0x94, 0xed, 0x7d | ||||
|             }; | ||||
| 
 | ||||
|             var data = new byte[16]; | ||||
|             Array.Copy(BitConverter.GetBytes(bundleOffset), data, 8); | ||||
|             Array.Copy(BitConverter.GetBytes(bundleLength), 0, data, 8, 8); | ||||
| 
 | ||||
|             // replace the beginning of the placeholder with the bytes from 'data' | ||||
|             RetryUtil.RetryOnIOError(() => | ||||
|                 BinaryUtils.SearchAndReplace(setupPath, placeholder, data, pad0s: false)); | ||||
| 
 | ||||
|             // memory-mapped write does not updating last write time | ||||
|             RetryUtil.RetryOnIOError(() => | ||||
|                 File.SetLastWriteTimeUtc(setupPath, DateTime.UtcNow)); | ||||
| 
 | ||||
|             if (!IsBundle(setupPath, out var offset, out var length)) | ||||
|                 throw new InvalidOperationException("Internal logic error writing setup bundle."); | ||||
|         } | ||||
| 
 | ||||
|         public static Stream ReadPackageBundle(string setupPath) | ||||
|         { | ||||
|             if (!IsBundle(setupPath, out var offset, out var length)) | ||||
|                 throw new InvalidOperationException("The provided executable has no embedded Squirrel package."); | ||||
| 
 | ||||
|             return new SubStream(File.OpenRead(setupPath), offset, length, new SemaphoreSlim(1), false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								src/Squirrel.Shared/Squirrel.Shared.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/Squirrel.Shared/Squirrel.Shared.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFramework>net6.0</TargetFramework> | ||||
|     <AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Squirrel\Squirrel.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										395
									
								
								src/Squirrel.Shared/SubStream.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										395
									
								
								src/Squirrel.Shared/SubStream.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,395 @@ | ||||
| // from https://raw.githubusercontent.com/Azure/azure-storage-net/master/Lib/Common/Blob/SubStream.cs | ||||
| // Apache License 2.0 | ||||
| 
 | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| 
 | ||||
| namespace Squirrel.Shared | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A wrapper class that creates a logical substream from a region within an existing seekable stream. | ||||
|     /// Allows for concurrent, asynchronous read and seek operations on the wrapped stream. | ||||
|     /// Ensures thread-safe operations between related substream instances via a shared, user-supplied synchronization mutex. | ||||
|     /// This class will buffer read requests to minimize overhead on the underlying stream. | ||||
|     /// </summary> | ||||
|     public sealed class SubStream : Stream | ||||
|     { | ||||
|         // Stream to be logically wrapped. | ||||
|         private Stream wrappedStream; | ||||
| 
 | ||||
|         // Position in the wrapped stream at which the SubStream should logically begin. | ||||
|         private long streamBeginIndex; | ||||
|         private readonly bool leaveOpen; | ||||
| 
 | ||||
|         // Total length of the substream. | ||||
|         private long substreamLength; | ||||
| 
 | ||||
|         // Tracks the current position in the substream. | ||||
|         private long substreamCurrentIndex; | ||||
| 
 | ||||
|         // Stream to manage read buffer, lazily initialized when read or seek operations commence. | ||||
|         private Lazy<MemoryStream> readBufferStream; | ||||
| 
 | ||||
|         // Internal read buffer, lazily initialized when read or seek operations commence. | ||||
|         private Lazy<byte[]> readBuffer; | ||||
| 
 | ||||
|         // Tracks the valid bytes remaining in the readBuffer | ||||
|         private int readBufferLength; | ||||
| 
 | ||||
|         // Determines where to update the position of the readbuffer stream depending on the scenario) | ||||
|         private bool shouldSeek = false; | ||||
| 
 | ||||
|         // Non-blocking semaphore for controlling read operations between related SubStream instances. | ||||
|         public SemaphoreSlim Mutex { get; set; } | ||||
| 
 | ||||
|         public const int DefaultBufferSize = (int) (64 * 1024); | ||||
|         public const int DefaultSubStreamBufferSize = (int) (4 * 1024 * 1024); | ||||
| 
 | ||||
|         // Current relative position in the substream. | ||||
|         public override long Position { | ||||
|             get { | ||||
|                 return this.substreamCurrentIndex; | ||||
|             } | ||||
| 
 | ||||
|             set { | ||||
|                 AssertInBounds("Position", value, 0, this.substreamLength); | ||||
| 
 | ||||
|                 // Check if we can potentially advance substream position without reallocating the read buffer. | ||||
|                 if (value >= this.substreamCurrentIndex) { | ||||
|                     long offset = value - this.substreamCurrentIndex; | ||||
| 
 | ||||
|                     // New position is within the valid bytes stored in the readBuffer. | ||||
|                     if (offset <= this.readBufferLength) { | ||||
|                         this.readBufferLength -= (int) offset; | ||||
|                         if (shouldSeek) { | ||||
|                             this.readBufferStream.Value.Seek(offset, SeekOrigin.Current); | ||||
|                         } | ||||
|                     } else { | ||||
|                         // Resets the read buffer. | ||||
|                         this.readBufferLength = 0; | ||||
|                         this.readBufferStream.Value.Seek(0, SeekOrigin.End); | ||||
|                     } | ||||
|                 } else { | ||||
|                     // Resets the read buffer. | ||||
|                     this.readBufferLength = 0; | ||||
|                     this.readBufferStream.Value.Seek(0, SeekOrigin.End); | ||||
|                 } | ||||
| 
 | ||||
|                 this.substreamCurrentIndex = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Total length of the substream. | ||||
|         public override long Length { | ||||
|             get { return this.substreamLength; } | ||||
|         } | ||||
| 
 | ||||
|         public override bool CanRead { | ||||
|             get { return true; } | ||||
|         } | ||||
| 
 | ||||
|         public override bool CanSeek { | ||||
|             get { return true; } | ||||
|         } | ||||
| 
 | ||||
|         public override bool CanWrite { | ||||
|             get { return false; } | ||||
|         } | ||||
| 
 | ||||
|         private void CheckDisposed() | ||||
|         { | ||||
|             if (this.wrappedStream == null) { | ||||
|                 throw new ObjectDisposedException("SubStreamWrapper"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (!leaveOpen) | ||||
|                 this.wrappedStream.Dispose(); | ||||
| 
 | ||||
|             this.wrappedStream = null; | ||||
|             this.readBufferStream = null; | ||||
|             this.readBuffer = null; | ||||
|         } | ||||
| 
 | ||||
|         public override void Flush() | ||||
|         { | ||||
|             throw new NotSupportedException(); | ||||
|         } | ||||
| 
 | ||||
|         // Initiates the new buffer size to be used for read operations. | ||||
|         public int ReadBufferSize { | ||||
|             get { | ||||
|                 return this.readBuffer.Value.Length; | ||||
|             } | ||||
| 
 | ||||
|             set { | ||||
|                 if (value < 2 * DefaultBufferSize) { | ||||
|                     throw new ArgumentOutOfRangeException("ReadBufferSize", "ArgumentTooSmallError"); | ||||
|                 } | ||||
| 
 | ||||
|                 this.readBuffer = new Lazy<byte[]>(() => new byte[value]); | ||||
|                 this.readBufferStream = new Lazy<MemoryStream>(() => new MemoryStream(this.readBuffer.Value, 0, value, true)); | ||||
|                 this.readBufferStream.Value.Seek(0, SeekOrigin.End); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Creates a new SubStream instance. | ||||
|         /// </summary> | ||||
|         /// <param name="stream">A seekable source stream.</param> | ||||
|         /// <param name="streamBeginIndex">The index in the wrapped stream where the logical SubStream should begin.</param> | ||||
|         /// <param name="substreamLength">The length of the SubStream.</param> | ||||
|         /// <param name="globalSemaphore"> A <see cref="SemaphoreSlim"/> object that is shared between related SubStream instances.</param> | ||||
|         /// <param name="leaveOpen">Whether the underlying stream should be left open or not when this is disposed.</param> | ||||
|         /// <remarks> | ||||
|         /// The source stream to be wrapped must be seekable. | ||||
|         /// The Semaphore object provided must have the initialCount thread parameter set to one to ensure only one concurrent request is granted at a time. | ||||
|         /// </remarks> | ||||
|         public SubStream(Stream stream, long streamBeginIndex, long substreamLength, SemaphoreSlim globalSemaphore, bool leaveOpen) | ||||
|         { | ||||
|             if (stream == null) { | ||||
|                 throw new ArgumentNullException("Stream."); | ||||
|             } else if (!stream.CanSeek) { | ||||
|                 throw new NotSupportedException("Stream must be seekable."); | ||||
|             } else if (globalSemaphore == null) { | ||||
|                 throw new ArgumentNullException("globalSemaphore"); | ||||
|             } | ||||
| 
 | ||||
|             AssertInBounds("streamBeginIndex", streamBeginIndex, 0, stream.Length); | ||||
| 
 | ||||
|             this.streamBeginIndex = streamBeginIndex; | ||||
|             this.wrappedStream = stream; | ||||
|             this.Mutex = globalSemaphore; | ||||
|             this.leaveOpen = leaveOpen; | ||||
|             this.substreamLength = Math.Min(substreamLength, stream.Length - streamBeginIndex); | ||||
|             this.readBufferLength = 0; | ||||
|             this.Position = 0; | ||||
|             this.ReadBufferSize = DefaultSubStreamBufferSize; | ||||
|         } | ||||
| 
 | ||||
| #if !(WINDOWS_RT || NETCORE) | ||||
|         public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) | ||||
|         { | ||||
|             return AsApm(ReadAsync(buffer, offset, count, CancellationToken.None), callback, state); | ||||
|         } | ||||
| 
 | ||||
|         public override int EndRead(IAsyncResult asyncResult) | ||||
|         { | ||||
|             AssertNotNull("AsyncResult", asyncResult); | ||||
|             return RunWithoutSynchronizationContext(() => ((Task<int>) asyncResult).Result); | ||||
|         } | ||||
| #endif | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Reads a block of bytes asynchronously from the substream read buffer. | ||||
|         /// </summary> | ||||
|         /// <param name="buffer">When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param> | ||||
|         /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param> | ||||
|         /// <param name="count">The maximum number of bytes to be read.</param> | ||||
|         /// <param name="cancellationToken">An object of type <see cref="CancellationToken"/> that propagates notification that operation should be canceled.</param> | ||||
|         /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the substream has been reached.</returns> | ||||
|         /// <remarks> | ||||
|         /// If the read request cannot be satisfied because the read buffer is empty or contains less than the requested number of the bytes,  | ||||
|         /// the wrapped stream will be called to refill the read buffer. | ||||
|         /// Only one read request to the underlying wrapped stream will be allowed at a time and concurrent requests will be queued up by effect of the shared semaphore mutex. | ||||
|         /// </remarks> | ||||
|         public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | ||||
|         { | ||||
|             this.CheckDisposed(); | ||||
| 
 | ||||
|             try { | ||||
|                 int readCount = this.CheckAdjustReadCount(count, offset, buffer.Length); | ||||
|                 int bytesRead = await this.readBufferStream.Value.ReadAsync(buffer, offset, Math.Min(readCount, this.readBufferLength), cancellationToken).ConfigureAwait(false); | ||||
|                 int bytesLeft = readCount - bytesRead; | ||||
| 
 | ||||
|                 // must adjust readbufferLength | ||||
|                 this.shouldSeek = false; | ||||
|                 this.Position += bytesRead; | ||||
| 
 | ||||
|                 if (bytesLeft > 0 && readBufferLength == 0) { | ||||
|                     this.readBufferStream.Value.Position = 0; | ||||
|                     int bytesAdded = | ||||
|                         await this.ReadAsyncHelper(this.readBuffer.Value, 0, this.readBuffer.Value.Length, cancellationToken).ConfigureAwait(false); | ||||
|                     this.readBufferLength = bytesAdded; | ||||
|                     if (bytesAdded > 0) { | ||||
|                         bytesLeft = Math.Min(bytesAdded, bytesLeft); | ||||
|                         int secondRead = await this.readBufferStream.Value.ReadAsync(buffer, bytesRead + offset, bytesLeft, cancellationToken).ConfigureAwait(false); | ||||
|                         bytesRead += secondRead; | ||||
|                         this.Position += secondRead; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return bytesRead; | ||||
|             } finally { | ||||
|                 this.shouldSeek = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Reads a block of bytes from the wrapped stream asynchronously and writes the data to the SubStream buffer. | ||||
|         /// </summary> | ||||
|         /// <param name="buffer">When this method returns, the substream read buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source.</param> | ||||
|         /// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream.</param> | ||||
|         /// <param name="count">The maximum number of bytes to be read.</param> | ||||
|         /// <param name="cancellationToken">An object of type <see cref="CancellationToken"/> that propagates notification that operation should be canceled.</param> | ||||
|         /// <returns>The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero if the end of the substream has been reached.</returns> | ||||
|         /// <remarks> | ||||
|         /// This method will allow only one read request to the underlying wrapped stream at a time,  | ||||
|         /// while concurrent requests will be queued up by effect of the shared semaphore mutex. | ||||
|         /// The caller is responsible for adjusting the substream position after a successful read. | ||||
|         /// </remarks> | ||||
|         private async Task<int> ReadAsyncHelper(byte[] buffer, int offset, int count, CancellationToken cancellationToken) | ||||
|         { | ||||
|             await this.Mutex.WaitAsync(cancellationToken).ConfigureAwait(false); | ||||
|             int result = 0; | ||||
|             try { | ||||
|                 this.CheckDisposed(); | ||||
| 
 | ||||
|                 // Check if read is out of range and adjust to read only up to the substream bounds. | ||||
|                 count = this.CheckAdjustReadCount(count, offset, buffer.Length); | ||||
| 
 | ||||
|                 // Only seek if wrapped stream is misaligned with the substream position. | ||||
|                 if (this.wrappedStream.Position != this.streamBeginIndex + this.Position) { | ||||
|                     this.wrappedStream.Seek(this.streamBeginIndex + this.Position, SeekOrigin.Begin); | ||||
|                 } | ||||
| 
 | ||||
|                 result = await this.wrappedStream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); | ||||
|             } finally { | ||||
|                 this.Mutex.Release(); | ||||
|             } | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         public override int Read(byte[] buffer, int offset, int count) | ||||
|         { | ||||
|             return RunWithoutSynchronizationContext(() => this.ReadAsync(buffer, offset, count).Result); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Sets the position within the current substream.  | ||||
|         /// This operation does not perform a seek on the wrapped stream. | ||||
|         /// </summary> | ||||
|         /// <param name="offset">A byte offset relative to the origin parameter.</param> | ||||
|         /// <param name="origin">A value of type System.IO.SeekOrigin indicating the reference point used to obtain the new position.</param> | ||||
|         /// <returns>The new position within the current substream.</returns> | ||||
|         /// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="offset"/> is invalid for SeekOrigin.</exception> | ||||
|         public override long Seek(long offset, SeekOrigin origin) | ||||
|         { | ||||
|             this.CheckDisposed(); | ||||
|             long startIndex; | ||||
| 
 | ||||
|             // Map offset to the specified SeekOrigin of the substream. | ||||
|             switch (origin) { | ||||
|             case SeekOrigin.Begin: | ||||
|                 startIndex = 0; | ||||
|                 break; | ||||
| 
 | ||||
|             case SeekOrigin.Current: | ||||
|                 startIndex = this.Position; | ||||
|                 break; | ||||
| 
 | ||||
|             case SeekOrigin.End: | ||||
|                 startIndex = this.substreamLength; | ||||
|                 break; | ||||
| 
 | ||||
|             default: | ||||
|                 throw new ArgumentOutOfRangeException(); | ||||
|             } | ||||
| 
 | ||||
|             this.Position = startIndex + offset; | ||||
|             return this.Position; | ||||
|         } | ||||
| 
 | ||||
|         public override void SetLength(long value) | ||||
|         { | ||||
|             throw new NotSupportedException(); | ||||
|         } | ||||
| 
 | ||||
|         public override void Write(byte[] buffer, int offset, int count) | ||||
|         { | ||||
|             throw new NotSupportedException(); | ||||
|         } | ||||
| 
 | ||||
|         private int CheckAdjustReadCount(int count, int offset, int bufferLength) | ||||
|         { | ||||
|             if (offset < 0 || count < 0 || offset + count > bufferLength) { | ||||
|                 throw new ArgumentOutOfRangeException(); | ||||
|             } | ||||
| 
 | ||||
|             long currentPos = this.streamBeginIndex + this.Position; | ||||
|             long endPos = this.streamBeginIndex + this.substreamLength; | ||||
|             if (currentPos + count > endPos) { | ||||
|                 return (int) (endPos - currentPos); | ||||
|             } else { | ||||
|                 return count; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         internal static void AssertInBounds<T>(string paramName, T val, T min, T max) | ||||
|    where T : IComparable | ||||
|         { | ||||
|             if (val.CompareTo(min) < 0) { | ||||
|                 throw new ArgumentOutOfRangeException(paramName, "ArgumentTooSmallError"); | ||||
|             } | ||||
| 
 | ||||
|             if (val.CompareTo(max) > 0) { | ||||
|                 throw new ArgumentOutOfRangeException(paramName, "ArgumentTooLargeError"); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         internal static void AssertNotNull(string paramName, object value) | ||||
|         { | ||||
|             if (value == null) { | ||||
|                 throw new ArgumentNullException(paramName); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         internal static void RunWithoutSynchronizationContext(Action actionToRun) | ||||
|         { | ||||
|             SynchronizationContext oldContext = SynchronizationContext.Current; | ||||
|             try { | ||||
|                 SynchronizationContext.SetSynchronizationContext(null); | ||||
|                 actionToRun(); | ||||
|             } finally { | ||||
|                 SynchronizationContext.SetSynchronizationContext(oldContext); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         internal static T RunWithoutSynchronizationContext<T>(Func<T> actionToRun) | ||||
|         { | ||||
|             SynchronizationContext oldContext = SynchronizationContext.Current; | ||||
|             try { | ||||
|                 SynchronizationContext.SetSynchronizationContext(null); | ||||
|                 return actionToRun(); | ||||
|             } finally { | ||||
|                 SynchronizationContext.SetSynchronizationContext(oldContext); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Copied from https://msdn.microsoft.com/en-us/library/hh873178.aspx | ||||
|         internal static IAsyncResult AsApm<T>(Task<T> task, AsyncCallback callback, object state) | ||||
|         { | ||||
|             if (task == null) | ||||
|                 throw new ArgumentNullException("task"); | ||||
| 
 | ||||
|             TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(state); | ||||
|             task.ContinueWith(t => { | ||||
|                 if (t.IsFaulted) | ||||
|                     tcs.TrySetException(t.Exception.InnerExceptions); | ||||
|                 else if (t.IsCanceled) | ||||
|                     tcs.TrySetCanceled(); | ||||
|                 else | ||||
|                     tcs.TrySetResult(t.Result); | ||||
| 
 | ||||
|                 if (callback != null) | ||||
|                     callback(tcs.Task); | ||||
|             }, TaskScheduler.Default); | ||||
|             return tcs.Task; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,76 +0,0 @@ | ||||
| using System; | ||||
| using System.Text; | ||||
| using Microsoft.NET.HostModel; | ||||
| 
 | ||||
| namespace Squirrel.Lib | ||||
| { | ||||
| #if NET5_0_OR_GREATER | ||||
|     [System.Runtime.Versioning.SupportedOSPlatform("windows")] | ||||
| #endif | ||||
|     internal class BundledSetupInfo | ||||
|     { | ||||
|         public string AppId { get; set; } | ||||
|         public string AppFriendlyName { get; set; } = "This Application"; | ||||
|         public string[] RequiredFrameworks { get; set; } = new string[0]; | ||||
|         public string BundledPackageName { get; set; } | ||||
|         public byte[] BundledPackageBytes { get; set; } | ||||
|         public byte[] SplashImageBytes { get; set; } | ||||
|         public byte[] SetupIconBytes { get; set; } | ||||
| 
 | ||||
|         private const string RESOURCE_TYPE = "DATA"; | ||||
|         private static readonly ushort RESOURCE_LANG = 0x0409; | ||||
| 
 | ||||
|         public static BundledSetupInfo ReadFromFile(string exePath) | ||||
|         { | ||||
|             var bundle = new BundledSetupInfo(); | ||||
|             using var reader = new ResourceReader(exePath); | ||||
|             bundle.AppId = ReadString(reader, 200); | ||||
|             bundle.AppFriendlyName = ReadString(reader, 201); | ||||
|             bundle.SplashImageBytes = ReadBytes(reader, 202); | ||||
|             bundle.RequiredFrameworks = ReadString(reader, 203)?.Split(',') ?? new string[0]; | ||||
|             bundle.BundledPackageName = ReadString(reader, 204); | ||||
|             bundle.BundledPackageBytes = ReadBytes(reader, 205); | ||||
|             bundle.SetupIconBytes = ReadBytes(reader, 206); | ||||
| 
 | ||||
|             return bundle; | ||||
|         } | ||||
| 
 | ||||
|         private static byte[] ReadBytes(ResourceReader reader, int idx) | ||||
|         { | ||||
|             return reader.ReadResource(RESOURCE_TYPE, new IntPtr(idx), RESOURCE_LANG); | ||||
|         } | ||||
| 
 | ||||
|         private static string ReadString(ResourceReader reader, int idx) | ||||
|         { | ||||
|             var bytes = ReadBytes(reader, idx); | ||||
|             if (bytes == null) return null; | ||||
|             return Encoding.Unicode.GetString(bytes).TrimEnd('\0'); | ||||
|         } | ||||
| 
 | ||||
|         public void WriteToFile(string exePath) | ||||
|         { | ||||
|             using var writer = new ResourceUpdater(exePath); | ||||
|             WriteValue(writer, 200, AppId); | ||||
|             WriteValue(writer, 201, AppFriendlyName); | ||||
|             WriteValue(writer, 202, SplashImageBytes); | ||||
|             WriteValue(writer, 203, String.Join(",", RequiredFrameworks ?? new string[0])); | ||||
|             WriteValue(writer, 204, BundledPackageName); | ||||
|             WriteValue(writer, 205, BundledPackageBytes); | ||||
|             WriteValue(writer, 206, SetupIconBytes); | ||||
|             writer.Update(); | ||||
|         } | ||||
| 
 | ||||
|         private void WriteValue(ResourceUpdater updater, int idx, string str) | ||||
|         { | ||||
|             if (String.IsNullOrWhiteSpace(str)) return; | ||||
|             var bytes = Encoding.Unicode.GetBytes(String.Concat(str, "\0\0")); | ||||
|             WriteValue(updater, idx, bytes); | ||||
|         } | ||||
| 
 | ||||
|         private void WriteValue(ResourceUpdater updater, int idx, byte[] buf) | ||||
|         { | ||||
|             if (buf == null || buf.Length == 0) return; | ||||
|             updater.AddResource(buf, RESOURCE_TYPE, new IntPtr(idx), RESOURCE_LANG); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System; | ||||
| using System; | ||||
| using System.Diagnostics.Contracts; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| @@ -36,12 +36,7 @@ namespace Squirrel | ||||
|         public string InputPackageFile { get; protected set; } | ||||
|         public string ReleasePackageFile { get; protected set; } | ||||
| 
 | ||||
|         public string SuggestedReleaseFileName { | ||||
|             get { | ||||
|                 var zp = new ZipPackage(InputPackageFile); | ||||
|                 return String.Format("{0}-{1}-full.nupkg", zp.Id, zp.Version); | ||||
|             } | ||||
|         } | ||||
|         public string SuggestedReleaseFileName => new ZipPackage(InputPackageFile).FullReleaseFilename; | ||||
| 
 | ||||
|         public SemanticVersion Version => ReleaseEntry.ParseEntryFileName(InputPackageFile).Version; | ||||
| 
 | ||||
|   | ||||
| @@ -36,6 +36,7 @@ namespace Squirrel.NuGet | ||||
|         public string ProductDescription => Description ?? Summary ?? Title ?? Id; | ||||
|         public string ProductCompany => (Authors.Any() ? String.Join(", ", Authors) : Owners) ?? ProductName; | ||||
|         public string ProductCopyright => Copyright ?? "Copyright © " + DateTime.Now.Year.ToString() + " " + ProductCompany; | ||||
|         public string FullReleaseFilename => String.Format("{0}-{1}-full.nupkg", Id, Version); | ||||
| 
 | ||||
|         public string Id { get; private set; } | ||||
|         public SemanticVersion Version { get; private set; } | ||||
| @@ -51,6 +52,10 @@ namespace Squirrel.NuGet | ||||
|         public IEnumerable<ZipPackageFile> Files { get; private set; } = Enumerable.Empty<ZipPackageFile>(); | ||||
|         public RuntimeCpu MachineArchitecture { get; private set; } | ||||
| 
 | ||||
|         public byte[] SetupSplashBytes { get; private set; } | ||||
|         public byte[] SetupIconBytes { get; private set; } | ||||
|         public byte[] AppIconBytes { get; private set; } | ||||
| 
 | ||||
|         protected string Description { get; private set; } | ||||
|         protected IEnumerable<string> Authors { get; private set; } = Enumerable.Empty<string>(); | ||||
|         protected string Owners { get; private set; } | ||||
| @@ -70,6 +75,27 @@ namespace Squirrel.NuGet | ||||
|             ReadManifest(manifest); | ||||
|             Files = GetPackageFiles(zip).ToArray(); | ||||
|             Frameworks = GetFrameworks(Files); | ||||
| 
 | ||||
|             // we pre-load some images so the zip doesn't need to be opened again later | ||||
|             SetupSplashBytes = ReadFileToBytes(zip, z => Path.GetFileNameWithoutExtension(z.Key) == "splashimage"); | ||||
|             SetupIconBytes = ReadFileToBytes(zip, z => z.Key == "setup.ico"); | ||||
|             AppIconBytes = ReadFileToBytes(zip, z => z.Key == "app.ico") ?? ReadFileToBytes(zip, z => z.Key.EndsWith("app.ico")); | ||||
|         } | ||||
| 
 | ||||
|         private byte[] ReadFileToBytes(ZipArchive archive, Func<ZipArchiveEntry, bool> predicate) | ||||
|         { | ||||
|             var f = archive.Entries.FirstOrDefault(predicate); | ||||
|             if (f == null) | ||||
|                 return null; | ||||
| 
 | ||||
|             using var stream = f.OpenEntryStream(); | ||||
|             if (stream == null) | ||||
|                 return null; | ||||
| 
 | ||||
|             var ms = new MemoryStream(); | ||||
|             stream.CopyTo(ms); | ||||
| 
 | ||||
|             return ms.ToArray(); | ||||
|         } | ||||
| 
 | ||||
|         public static void SetSquirrelMetadata(string nuspecPath, RuntimeCpu architecture, IEnumerable<string> runtimes) | ||||
|   | ||||
| @@ -8,16 +8,18 @@ namespace Squirrel.NuGet | ||||
| { | ||||
|     internal interface IPackageFile : IFrameworkTargetable | ||||
|     { | ||||
|         Uri Key { get; } | ||||
|         string Path { get; } | ||||
|         string EffectivePath { get; } | ||||
|         string TargetFramework { get; } | ||||
|         bool IsLibFile(); | ||||
|         bool IsContentFile(); | ||||
|         Stream GetEntryStream(Stream archiveStream); | ||||
|         //Stream GetEntryStream(Stream archiveStream); | ||||
|     } | ||||
| 
 | ||||
|     internal class ZipPackageFile : IPackageFile, IEquatable<ZipPackageFile> | ||||
|     { | ||||
|         public Uri Key { get; } | ||||
|         public string EffectivePath { get; } | ||||
|         public string TargetFramework { get; } | ||||
|         public string Path { get; } | ||||
| @@ -31,22 +33,21 @@ namespace Squirrel.NuGet | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private readonly Uri _entryKey; | ||||
| 
 | ||||
|         public ZipPackageFile(Uri relpath) | ||||
|         { | ||||
|             _entryKey = relpath; | ||||
|             Key = relpath; | ||||
|             Path = NugetUtil.GetPath(relpath); | ||||
|             TargetFramework = NugetUtil.ParseFrameworkNameFromFilePath(Path, out var effectivePath); | ||||
|             EffectivePath = effectivePath; | ||||
|         } | ||||
| 
 | ||||
|         public Stream GetEntryStream(Stream archiveStream) | ||||
|         { | ||||
|             using var zip = ZipArchive.Open(archiveStream, new() { LeaveStreamOpen = true }); | ||||
|             var entry = zip.Entries.FirstOrDefault(f => new Uri(f.Key, UriKind.Relative) == _entryKey); | ||||
|             return entry?.OpenEntryStream(); | ||||
|         } | ||||
|         //public Stream GetEntryStream(Stream archiveStream) | ||||
|         //{ | ||||
|         //    using var zip = ZipArchive.Open(archiveStream, new() { LeaveStreamOpen = true }); | ||||
|         //    var entry = zip.Entries.FirstOrDefault(f => new Uri(f.Key, UriKind.Relative) == _entryKey); | ||||
|         //    return entry?.OpenEntryStream(); | ||||
|         //} | ||||
| 
 | ||||
|         public bool IsLibFile() => IsFileInTopDirectory(NugetUtil.LibDirectory); | ||||
|         public bool IsContentFile() => IsFileInTopDirectory(NugetUtil.ContentDirectory); | ||||
|   | ||||
| @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; | ||||
| [assembly: InternalsVisibleTo("Squirrel.Tests, PublicKey=" + SNK.SHA1)] | ||||
| [assembly: InternalsVisibleTo("Update, PublicKey=" + SNK.SHA1)] | ||||
| [assembly: InternalsVisibleTo("SquirrelCli, PublicKey=" + SNK.SHA1)] | ||||
| [assembly: InternalsVisibleTo("Squirrel.Shared, PublicKey=" + SNK.SHA1)] | ||||
| [assembly: InternalsVisibleTo("Squirrel, PublicKey=" + SNK.SHA1)] | ||||
| 
 | ||||
| internal static class SNK | ||||
|   | ||||
| @@ -24,7 +24,7 @@ namespace Squirrel | ||||
|             } | ||||
| 
 | ||||
|             const string uninstallRegSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; | ||||
|             public async Task<RegistryKey> CreateUninstallerRegistryEntry(string uninstallCmd, string quietSwitch) | ||||
|             public Task<RegistryKey> CreateUninstallerRegistryEntry(string uninstallCmd, string quietSwitch) | ||||
|             { | ||||
|                 this.Log().Info($"Writing uninstaller registry entry"); | ||||
|                 var releaseContent = File.ReadAllText(Path.Combine(rootAppDirectory, "packages", "RELEASES"), Encoding.UTF8); | ||||
| @@ -45,14 +45,9 @@ namespace Squirrel | ||||
|                 // we will try to find an "app.ico" from the package, write it to the local app dir, and then  | ||||
|                 // use it for the uninstaller icon. If an app.ico does not exist, it will use a SquirrelAwareApp exe icon instead. | ||||
|                 try { | ||||
|                     var appIconEntry = zp.Files | ||||
|                         .FirstOrDefault(f => f.IsLibFile() && f.EffectivePath.Equals("app.ico", StringComparison.InvariantCultureIgnoreCase)); | ||||
|                     if (appIconEntry != null) { | ||||
|                     if (zp.AppIconBytes != null) { | ||||
|                         var targetIco = Path.Combine(rootAppDirectory, "app.ico"); | ||||
|                         using (var pkgStream = File.OpenRead(pkgPath)) | ||||
|                         using (var iconStream = appIconEntry.GetEntryStream(pkgStream)) | ||||
|                         using (var targetStream = File.Open(targetIco, FileMode.Create, FileAccess.Write)) | ||||
|                             await iconStream.CopyToAsync(targetStream).ConfigureAwait(false); | ||||
|                         File.WriteAllBytes(targetIco, zp.AppIconBytes); | ||||
|                         this.Log().Info($"File '{targetIco}' is being used for uninstall icon."); | ||||
|                         key.SetValue("DisplayIcon", targetIco, RegistryValueKind.String); | ||||
|                     } else { | ||||
| @@ -101,7 +96,7 @@ namespace Squirrel | ||||
|                     key.SetValue(kvp.Key, kvp.Value, RegistryValueKind.DWord); | ||||
|                 } | ||||
| 
 | ||||
|                 return key; | ||||
|                 return Task.FromResult(key); | ||||
|             } | ||||
| 
 | ||||
|             public void KillAllProcessesBelongingToPackage() | ||||
|   | ||||
| @@ -93,9 +93,8 @@ namespace SquirrelCli | ||||
|         public string baseUrl { get; private set; } | ||||
|         public string framework { get; private set; } | ||||
|         public string splashImage { get; private set; } | ||||
|         public string updateIcon { get; private set; } | ||||
|         public string icon { get; private set; } | ||||
|         public string appIcon { get; private set; } | ||||
|         public string setupIcon { get; private set; } | ||||
|         public bool noDelta { get; private set; } | ||||
|         public bool allowUnaware { get; private set; } | ||||
|         public string msi { get; private set; } | ||||
| @@ -113,8 +112,7 @@ namespace SquirrelCli | ||||
|             Add("noDelta", "Skip the generation of delta packages", v => noDelta = true); | ||||
|             Add("f=|framework=", "List of required {RUNTIMES} to install during setup\nexample: 'net6,vcredist143'", v => framework = v); | ||||
|             Add("s=|splashImage=", "{PATH} to image/gif displayed during installation", v => splashImage = v); | ||||
|             Add("i=|icon=", "{PATH} to .ico for Setup.exe and Update.exe", | ||||
|                 (v) => { updateIcon = v; setupIcon = v; }); | ||||
|             Add("i=|icon=", "{PATH} to .ico for Setup.exe and Update.exe", v => icon = v); | ||||
|             Add("appIcon=", "{PATH} to .ico for 'Apps and Features' list", v => appIcon = v); | ||||
|             Add("msi=", "Compile a .msi machine-wide deployment tool with the specified {BITNESS}. (either 'x86' or 'x64')", v => msi = v.ToLower()); | ||||
|         } | ||||
| @@ -127,8 +125,7 @@ namespace SquirrelCli | ||||
|         protected virtual void ValidateInternal(bool checkPackage) | ||||
|         { | ||||
|             IsValidFile(nameof(appIcon), ".ico"); | ||||
|             IsValidFile(nameof(setupIcon), ".ico"); | ||||
|             IsValidFile(nameof(updateIcon), ".ico"); | ||||
|             IsValidFile(nameof(icon), ".ico"); | ||||
|             IsValidFile(nameof(splashImage)); | ||||
|             IsValidUrl(nameof(baseUrl)); | ||||
| 
 | ||||
|   | ||||
| @@ -18,6 +18,7 @@ using Squirrel; | ||||
| using Squirrel.Json; | ||||
| using Squirrel.Lib; | ||||
| using Squirrel.NuGet; | ||||
| using Squirrel.Shared; | ||||
| using Squirrel.SimpleSplat; | ||||
| using SquirrelCli.Sources; | ||||
| 
 | ||||
| @@ -138,7 +139,7 @@ namespace SquirrelCli | ||||
|             var baseUrl = options.baseUrl; | ||||
|             var generateDeltas = !options.noDelta; | ||||
|             var backgroundGif = options.splashImage; | ||||
|             var setupIcon = options.setupIcon; | ||||
|             var setupIcon = options.icon ?? options.appIcon; | ||||
| 
 | ||||
|             if (!package.EndsWith(".nupkg", StringComparison.InvariantCultureIgnoreCase)) | ||||
|                 throw new ArgumentException("package must be packed with nuget and end in '.nupkg'"); | ||||
| @@ -153,8 +154,8 @@ namespace SquirrelCli | ||||
|             // update icon for Update.exe if requested | ||||
|             var bundledUpdatePath = HelperExe.UpdatePath(p => Microsoft.NET.HostModel.AppHost.HostWriter.IsBundle(p, out var _hz)); | ||||
|             var updatePath = Path.Combine(tempDir, "Update.exe"); | ||||
|             if (options.updateIcon != null) { | ||||
|                 DotnetUtil.UpdateSingleFileBundleIcon(bundledUpdatePath, updatePath, options.updateIcon).Wait(); | ||||
|             if (setupIcon != null) { | ||||
|                 DotnetUtil.UpdateSingleFileBundleIcon(bundledUpdatePath, updatePath, setupIcon).Wait(); | ||||
|             } else { | ||||
|                 File.Copy(bundledUpdatePath, updatePath, true); | ||||
|             } | ||||
| @@ -275,6 +276,10 @@ namespace SquirrelCli | ||||
|                             ico.Save(fs); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     // copy other images to root (used by setup) | ||||
|                     if (setupIcon != null) File.Copy(setupIcon, Path.Combine(pkgPath, "setup.ico"), true); | ||||
|                     if (backgroundGif != null) File.Copy(backgroundGif, Path.Combine(pkgPath, "splashimage" + Path.GetExtension(backgroundGif))); | ||||
|                 }); | ||||
| 
 | ||||
|                 processed.Add(rp.ReleasePackageFile); | ||||
| @@ -310,18 +315,7 @@ namespace SquirrelCli | ||||
|             var newestReleasePath = Path.Combine(di.FullName, newestFullRelease.Filename); | ||||
| 
 | ||||
|             Log.Info($"Creating Setup bundle"); | ||||
|             var infosave = new BundledSetupInfo() { | ||||
|                 AppId = bundledzp.Id, | ||||
|                 AppFriendlyName = bundledzp.ProductName, | ||||
|                 BundledPackageBytes = File.ReadAllBytes(newestReleasePath), | ||||
|                 BundledPackageName = Path.GetFileName(newestReleasePath), | ||||
|                 RequiredFrameworks = requiredFrameworks.Select(r => r.Id).ToArray(), | ||||
|             }; | ||||
| 
 | ||||
|             if (setupIcon != null) infosave.SetupIconBytes = File.ReadAllBytes(setupIcon); | ||||
|             if (backgroundGif != null) infosave.SplashImageBytes = File.ReadAllBytes(backgroundGif); | ||||
| 
 | ||||
|             infosave.WriteToFile(targetSetupExe); | ||||
|             SetupBundle.CreatePackageBundle(targetSetupExe, newestReleasePath); | ||||
|             options.SignPEFile(targetSetupExe); | ||||
| 
 | ||||
|             if (!String.IsNullOrEmpty(options.msi)) { | ||||
|   | ||||
| @@ -32,7 +32,7 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Squirrel\Squirrel.csproj" /> | ||||
|     <ProjectReference Include="..\Squirrel.Shared\Squirrel.Shared.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -120,7 +120,10 @@ namespace Squirrel.Update | ||||
|         static async Task Setup(string setupPath, bool silentInstall, bool checkInstall) | ||||
|         { | ||||
|             Log.Info($"Extracting bundled app data from '{setupPath}'."); | ||||
|             var info = BundledSetupInfo.ReadFromFile(setupPath); | ||||
| 
 | ||||
|             using var pkgStream = Shared.SetupBundle.ReadPackageBundle(setupPath); | ||||
|             var zp = new ZipPackage(pkgStream, true); | ||||
|             var appname = zp.ProductName; | ||||
| 
 | ||||
|             if (checkInstall) { | ||||
|                 // CS: migrated from MachineInstaller.cpp | ||||
| @@ -136,14 +139,14 @@ namespace Squirrel.Update | ||||
|                 // bailing out | ||||
| 
 | ||||
|                 // C:\Users\Username\AppData\Local\$pkgName | ||||
|                 var localadinstall = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), info.AppId); | ||||
|                 var localadinstall = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), zp.Id); | ||||
|                 if (Directory.Exists(localadinstall)) { | ||||
|                     Log.Info($"App install detected at '{localadinstall}', exiting..."); | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 // C:\ProgramData\$pkgName\$username | ||||
|                 var programdatainstall = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), info.AppId, Environment.UserName); | ||||
|                 var programdatainstall = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), zp.Id, Environment.UserName); | ||||
|                 if (Directory.Exists(programdatainstall)) { | ||||
|                     Log.Info($"App install detected at '{programdatainstall}', exiting..."); | ||||
|                     return; | ||||
| @@ -154,16 +157,15 @@ namespace Squirrel.Update | ||||
|             } | ||||
| 
 | ||||
|             using var _t = Utility.WithTempDirectory(out var tempFolder); | ||||
|             ISplashWindow splash = new Windows.User32SplashWindow(info.AppFriendlyName, silentInstall, info.SetupIconBytes, info.SplashImageBytes); | ||||
|             ISplashWindow splash = new Windows.User32SplashWindow(appname, silentInstall, zp.SetupIconBytes, zp.SetupSplashBytes); | ||||
| 
 | ||||
|             // verify that this package can be installed on this cpu architecture | ||||
|             var zp = new ZipPackage(new MemoryStream(info.BundledPackageBytes)); | ||||
|             if (AssemblyRuntimeInfo.Architecture == RuntimeCpu.X86 && zp.MachineArchitecture == RuntimeCpu.X64) { | ||||
|                 splash.ShowErrorDialog("Incompatible System", "The current operating system uses the x86 cpu architecture, but this package requires an x64 system."); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var missingFrameworks = info.RequiredFrameworks | ||||
|             var missingFrameworks = zp.RuntimeDependencies | ||||
|                 .Select(f => Runtimes.GetRuntimeByName(f)) | ||||
|                 .Where(f => f != null) | ||||
|                 .Where(f => !f.CheckIsInstalled().Result) | ||||
| @@ -172,9 +174,9 @@ namespace Squirrel.Update | ||||
|             // prompt user to install missing dependencies | ||||
|             if (missingFrameworks.Any()) { | ||||
|                 string message = missingFrameworks.Length > 1 | ||||
|                     ? $"{info.AppFriendlyName} is missing the following system components: {String.Join(", ", missingFrameworks.Select(s => s.DisplayName))}. " + | ||||
|                     ? $"{appname} is missing the following system components: {String.Join(", ", missingFrameworks.Select(s => s.DisplayName))}. " + | ||||
|                       $"Would you like to install these now?" | ||||
|                     : $"{info.AppFriendlyName} requires {missingFrameworks.First().DisplayName} installed to continue, would you like to install it now?"; | ||||
|                     : $"{appname} requires {missingFrameworks.First().DisplayName} installed to continue, would you like to install it now?"; | ||||
| 
 | ||||
|                 if (!splash.ShowQuestionDialog("Missing System Components", message)) { | ||||
|                     return; // user cancelled install | ||||
| @@ -215,11 +217,16 @@ namespace Squirrel.Update | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // setup package source directory | ||||
|             Log.Info($"Starting package install from directory " + tempFolder); | ||||
|             splash.SetProgressIndeterminate(); | ||||
|             string packagePath = Path.Combine(tempFolder, info.BundledPackageName); | ||||
|             File.WriteAllBytes(packagePath, info.BundledPackageBytes); | ||||
| 
 | ||||
|             // copy package to directory | ||||
|             string packagePath = Path.Combine(tempFolder, zp.FullReleaseFilename); | ||||
|             pkgStream.Position = 0; | ||||
|             using (var writeStream = File.Open(packagePath, FileMode.Create, FileAccess.ReadWrite)) | ||||
|                 pkgStream.CopyTo(writeStream); | ||||
| 
 | ||||
|             // create RELEASES file for UpdateManager to read | ||||
|             var entry = ReleaseEntry.GenerateFromFile(packagePath); | ||||
|             ReleaseEntry.WriteReleaseFile(new[] { entry }, Path.Combine(tempFolder, "RELEASES")); | ||||
| 
 | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Squirrel\Squirrel.csproj" /> | ||||
|     <ProjectReference Include="..\Squirrel.Shared\Squirrel.Shared.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -60,6 +60,8 @@ namespace Squirrel.Update.Windows | ||||
|             _signal = new ManualResetEvent(false); | ||||
| 
 | ||||
|             try { | ||||
|                 // we only accept a byte array and convert to memorystream because | ||||
|                 // gdi needs to seek and get length which is not supported in DeflateStream | ||||
|                 if (iconBytes?.Length > 0) _icon = new Icon(new MemoryStream(iconBytes)); | ||||
|                 if (splashBytes?.Length > 0) _img = (Bitmap) Bitmap.FromStream(new MemoryStream(splashBytes)); | ||||
|             } catch (Exception ex) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user