From 12bef7caa20e303176072c412f646363c0d5e14d Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Thu, 6 Aug 2020 19:33:33 +1000 Subject: [PATCH] Add update installer program (Windows only for now) --- README.md | 4 +- duckstation.sln | 18 ++ src/CMakeLists.txt | 3 + src/updater/CMakeLists.txt | 12 + src/updater/updater.cpp | 289 +++++++++++++++++++++ src/updater/updater.h | 38 +++ src/updater/updater.manifest | 22 ++ src/updater/updater.vcxproj | 382 ++++++++++++++++++++++++++++ src/updater/updater.vcxproj.filters | 13 + src/updater/win32_main.cpp | 93 +++++++ 10 files changed, 872 insertions(+), 2 deletions(-) create mode 100644 src/updater/CMakeLists.txt create mode 100644 src/updater/updater.cpp create mode 100644 src/updater/updater.h create mode 100644 src/updater/updater.manifest create mode 100644 src/updater/updater.vcxproj create mode 100644 src/updater/updater.vcxproj.filters create mode 100644 src/updater/win32_main.cpp diff --git a/README.md b/README.md index b747fbf86..3076e3285 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Binaries of DuckStation for Windows 64-bit, x86_64 Linux x86_64 (in AppImage for **Windows 10 is the only version of Windows supported by the developer.** Windows 7/8 may work, but is not supported. I am aware some users are still using Windows 7, but it is no longer supported by Microsoft and too much effort to get running on modern hardware. Game bugs are unlikely to be affected by the operating system, however performance issues should be verified on Windows 10 before reporting. To download: - - Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download the Windows x64 build. This is a 7-Zip archive containing the prebuilt binary. - - Alternatively, direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.7z + - Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download the Windows x64 build. This is a zip archive containing the prebuilt binary. + - Alternatively, direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip - Extract the archive **to a subdirectory**. The archive has no root subdirectory, so extracting to the current directory will drop a bunch of files in your download directory if you do not extract to a subdirectory. Once downloaded and extracted, you can launch the Qt frontend from `duckstation-qt-x64-ReleaseLTCG.exe`, or the SDL frontend from `duckstation-sdl-x64-ReleaseLTCG.exe`. diff --git a/duckstation.sln b/duckstation.sln index 8179f7aee..d54874a34 100644 --- a/duckstation.sln +++ b/duckstation.sln @@ -57,6 +57,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glslang", "dep\glslang\glsl EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vulkan-loader", "dep\vulkan-loader\vulkan-loader.vcxproj", "{9C8DDEB0-2B8F-4F5F-BA86-127CDF27F035}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updater", "src\updater\updater.vcxproj", "{32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -485,6 +487,22 @@ Global {9C8DDEB0-2B8F-4F5F-BA86-127CDF27F035}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 {9C8DDEB0-2B8F-4F5F-BA86-127CDF27F035}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32 {9C8DDEB0-2B8F-4F5F-BA86-127CDF27F035}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Debug|x64.ActiveCfg = Debug|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Debug|x64.Build.0 = Debug|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Debug|x86.ActiveCfg = Debug|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Debug|x86.Build.0 = Debug|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast|x64.ActiveCfg = DebugFast|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast|x64.Build.0 = DebugFast|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast|x86.ActiveCfg = DebugFast|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.DebugFast|x86.Build.0 = DebugFast|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release|x64.ActiveCfg = Release|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release|x64.Build.0 = Release|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release|x86.ActiveCfg = Release|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.Release|x86.Build.0 = Release|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.ReleaseLTCG|x64.ActiveCfg = ReleaseLTCG|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.ReleaseLTCG|x64.Build.0 = ReleaseLTCG|x64 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.ReleaseLTCG|x86.ActiveCfg = ReleaseLTCG|Win32 + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5}.ReleaseLTCG|x86.Build.0 = ReleaseLTCG|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b50b19f9d..e91fb70a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,6 +8,9 @@ add_subdirectory(scmversion) if(NOT BUILD_LIBRETRO_CORE) add_subdirectory(common-tests) + if(WIN32) + add_subdirectory(updater) + endif() endif() if(ANDROID OR BUILD_SDL_FRONTEND OR BUILD_QT_FRONTEND OR BUILD_LIBRETRO_CORE) diff --git a/src/updater/CMakeLists.txt b/src/updater/CMakeLists.txt new file mode 100644 index 000000000..50e6b5744 --- /dev/null +++ b/src/updater/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(updater + updater.cpp + updater.h +) + +target_link_libraries(updater PRIVATE common minizip zlib) + +if(WIN32) + target_sources(updater PRIVATE + win32_main.cpp + ) +endif() diff --git a/src/updater/updater.cpp b/src/updater/updater.cpp new file mode 100644 index 000000000..764457f19 --- /dev/null +++ b/src/updater/updater.cpp @@ -0,0 +1,289 @@ +#include "updater.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/minizip_helpers.h" +#include "common/string_util.h" +#include "common/win32_progress_callback.h" +#include +#include +#include +#include +#include +#include +#include + +Updater::Updater(ProgressCallback* progress) : m_progress(progress) +{ + progress->SetTitle("DuckStation Update Installer"); +} + +Updater::~Updater() +{ + if (m_zf) + unzClose(m_zf); +} + +bool Updater::Initialize(std::string destination_directory) +{ + m_destination_directory = std::move(destination_directory); + m_staging_directory = StringUtil::StdStringFromFormat("%s%c%s", m_destination_directory.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER, "UPDATE_STAGING"); + m_progress->DisplayFormattedDebugMessage("Destination directory: '%s'", m_destination_directory.c_str()); + m_progress->DisplayFormattedDebugMessage("Staging directory: '%s'", m_staging_directory.c_str()); + + // log everything to file as well + Log::SetFileOutputParams(true, StringUtil::StdStringFromFormat("%s%cupdater.log", m_destination_directory.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER) + .c_str()); + + return true; +} + +bool Updater::OpenUpdateZip(const char* path) +{ + m_zf = MinizipHelpers::OpenUnzFile(path); + if (!m_zf) + return false; + + return ParseZip(); +} + +bool Updater::ParseZip() +{ + if (unzGoToFirstFile(m_zf) != UNZ_OK) + { + m_progress->ModalError("unzGoToFirstFile() failed"); + return {}; + } + + for (;;) + { + char zip_filename_buffer[256]; + unz_file_info64 file_info; + if (unzGetCurrentFileInfo64(m_zf, &file_info, zip_filename_buffer, sizeof(zip_filename_buffer), nullptr, 0, nullptr, + 0) != UNZ_OK) + { + m_progress->ModalError("unzGetCurrentFileInfo64() failed"); + return false; + } + + FileToUpdate entry; + entry.original_zip_filename = zip_filename_buffer; + + // replace forward slashes with backslashes + size_t len = std::strlen(zip_filename_buffer); + for (size_t i = 0; i < len; i++) + { + if (zip_filename_buffer[i] == '/' || zip_filename_buffer[i] == '\\') + zip_filename_buffer[i] = FS_OSPATH_SEPERATOR_CHARACTER; + } + + // should never have a leading slash. just in case. + while (zip_filename_buffer[0] == FS_OSPATH_SEPERATOR_CHARACTER) + std::memmove(&zip_filename_buffer[1], &zip_filename_buffer[0], --len); + + // skip directories (we sort them out later) + if (len > 0 && zip_filename_buffer[len - 1] != FS_OSPATH_SEPERATOR_CHARACTER) + { + // skip updater itself, since it was already pre-extracted. + if (StringUtil::Strcasecmp(zip_filename_buffer, "updater.exe") == 0) + continue; + + entry.destination_filename = zip_filename_buffer; + m_progress->DisplayFormattedDebugMessage("Found file in zip: '%s'", entry.destination_filename.c_str()); + m_update_paths.push_back(std::move(entry)); + } + + int res = unzGoToNextFile(m_zf); + if (res == UNZ_END_OF_LIST_OF_FILE) + break; + if (res != UNZ_OK) + { + m_progress->ModalError("unzGoToNextFile() failed"); + return false; + } + } + + if (m_update_paths.empty()) + { + m_progress->ModalError("No files found in update zip."); + return false; + } + + for (const FileToUpdate& ftu : m_update_paths) + { + const size_t len = ftu.destination_filename.length(); + for (size_t i = 0; i < len; i++) + { + if (ftu.destination_filename[i] == FS_OSPATH_SEPERATOR_CHARACTER) + { + std::string dir(ftu.destination_filename.begin(), ftu.destination_filename.begin() + i); + while (!dir.empty() && dir[dir.length() - 1] == FS_OSPATH_SEPERATOR_CHARACTER) + dir.erase(dir.length() - 1); + + if (std::find(m_update_directories.begin(), m_update_directories.end(), dir) == m_update_directories.end()) + m_update_directories.push_back(std::move(dir)); + } + } + } + + std::sort(m_update_directories.begin(), m_update_directories.end()); + for (const std::string& dir : m_update_directories) + m_progress->DisplayFormattedDebugMessage("Directory: %s", dir.c_str()); + + return true; +} + +bool Updater::PrepareStagingDirectory() +{ + if (FileSystem::DirectoryExists(m_staging_directory.c_str())) + { + m_progress->DisplayFormattedWarning("Update staging directory already exists, removing"); + FileSystem::DeleteDirectory(m_staging_directory.c_str(), true); + if (FileSystem::DirectoryExists(m_staging_directory.c_str())) + { + m_progress->ModalError("Failed to remove old staging directory"); + return false; + } + } + if (!FileSystem::CreateDirectory(m_staging_directory.c_str(), false)) + { + m_progress->DisplayFormattedModalError("Failed to create staging directory %s", m_staging_directory.c_str()); + return false; + } + + // create subdirectories in staging directory + for (const std::string& subdir : m_update_directories) + { + m_progress->DisplayFormattedInformation("Creating subdirectory in staging: %s", subdir.c_str()); + + const std::string staging_subdir = StringUtil::StdStringFromFormat("%s%c%s", m_staging_directory.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER, subdir.c_str()); + if (!FileSystem::CreateDirectory(staging_subdir.c_str(), false)) + { + m_progress->DisplayFormattedModalError("Failed to create staging subdirectory %s", staging_subdir.c_str()); + return false; + } + } + + return true; +} + +bool Updater::StageUpdate() +{ + m_progress->SetProgressRange(static_cast(m_update_paths.size())); + m_progress->SetProgressValue(0); + + for (const FileToUpdate& ftu : m_update_paths) + { + m_progress->SetFormattedStatusText("Extracting '%s'...", ftu.original_zip_filename.c_str()); + + if (unzLocateFile(m_zf, ftu.original_zip_filename.c_str(), 0) != UNZ_OK) + { + m_progress->DisplayFormattedModalError("Unable to locate file '%s' in zip", ftu.original_zip_filename.c_str()); + return false; + } + else if (unzOpenCurrentFile(m_zf) != UNZ_OK) + { + m_progress->DisplayFormattedModalError("Failed to open file '%s' in zip", ftu.original_zip_filename.c_str()); + return false; + } + + m_progress->DisplayFormattedInformation("Extracting '%s'...", ftu.destination_filename.c_str()); + + const std::string destination_file = StringUtil::StdStringFromFormat( + "%s%c%s", m_staging_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, ftu.destination_filename.c_str()); + std::FILE* fp = FileSystem::OpenCFile(destination_file.c_str(), "wb"); + if (!fp) + { + m_progress->DisplayFormattedModalError("Failed to open staging output file '%s'", destination_file.c_str()); + unzCloseCurrentFile(m_zf); + return false; + } + + static constexpr u32 CHUNK_SIZE = 4096; + u8 buffer[CHUNK_SIZE]; + for (;;) + { + int byte_count = unzReadCurrentFile(m_zf, buffer, CHUNK_SIZE); + if (byte_count < 0) + { + m_progress->DisplayFormattedModalError("Failed to read file '%s' from zip", ftu.original_zip_filename.c_str()); + std::fclose(fp); + FileSystem::DeleteFile(destination_file.c_str()); + unzCloseCurrentFile(m_zf); + return false; + } + else if (byte_count == 0) + { + // end of file + break; + } + + if (std::fwrite(buffer, static_cast(byte_count), 1, fp) != 1) + { + m_progress->DisplayFormattedModalError("Failed to write to file '%s'", destination_file.c_str()); + std::fclose(fp); + FileSystem::DeleteFile(destination_file.c_str()); + unzCloseCurrentFile(m_zf); + return false; + } + } + + std::fclose(fp); + unzCloseCurrentFile(m_zf); + m_progress->IncrementProgressValue(); + } + + return true; +} + +bool Updater::CommitUpdate() +{ + m_progress->SetStatusText("Committing update..."); + + // create directories in target + for (const std::string& subdir : m_update_directories) + { + const std::string dest_subdir = StringUtil::StdStringFromFormat("%s%c%s", m_destination_directory.c_str(), + FS_OSPATH_SEPERATOR_CHARACTER, subdir.c_str()); + + if (!FileSystem::DirectoryExists(dest_subdir.c_str()) && !FileSystem::CreateDirectory(dest_subdir.c_str(), false)) + { + m_progress->DisplayFormattedModalError("Failed to create target directory '%s'", dest_subdir.c_str()); + return false; + } + } + + // move files to target + for (const FileToUpdate& ftu : m_update_paths) + { + const std::string staging_file_name = StringUtil::StdStringFromFormat( + "%s%c%s", m_staging_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, ftu.destination_filename.c_str()); + const std::string dest_file_name = StringUtil::StdStringFromFormat( + "%s%c%s", m_destination_directory.c_str(), FS_OSPATH_SEPERATOR_CHARACTER, ftu.destination_filename.c_str()); + m_progress->DisplayFormattedDebugMessage("Moving '%s' to '%s'", staging_file_name.c_str(), dest_file_name.c_str()); +#ifdef WIN32 + const bool result = + MoveFileExW(StringUtil::UTF8StringToWideString(staging_file_name).c_str(), + StringUtil::UTF8StringToWideString(dest_file_name).c_str(), MOVEFILE_REPLACE_EXISTING); +#else + const bool result = (rename(staging_file_name.c_str(), dest_file_name.c_str()) == 0); +#endif + if (!result) + { + m_progress->DisplayFormattedModalError("Failed to rename '%s' to '%s'", staging_file_name.c_str(), + dest_file_name.c_str()); + return false; + } + } + + return true; +} + +void Updater::CleanupStagingDirectory() +{ + // remove staging directory itself + if (!FileSystem::DeleteDirectory(m_staging_directory.c_str(), true)) + m_progress->DisplayFormattedError("Failed to remove staging directory '%s'", m_staging_directory.c_str()); +} diff --git a/src/updater/updater.h b/src/updater/updater.h new file mode 100644 index 000000000..d0dd69493 --- /dev/null +++ b/src/updater/updater.h @@ -0,0 +1,38 @@ +#pragma once +#include "common/progress_callback.h" +#include "unzip.h" +#include +#include + +class Updater +{ +public: + Updater(ProgressCallback* progress); + ~Updater(); + + bool Initialize(std::string destination_directory); + + bool OpenUpdateZip(const char* path); + bool PrepareStagingDirectory(); + bool StageUpdate(); + bool CommitUpdate(); + void CleanupStagingDirectory(); + +private: + struct FileToUpdate + { + std::string original_zip_filename; + std::string destination_filename; + }; + + bool ParseZip(); + + std::string m_destination_directory; + std::string m_staging_directory; + + std::vector m_update_paths; + std::vector m_update_directories; + + ProgressCallback* m_progress; + unzFile m_zf = nullptr; +}; diff --git a/src/updater/updater.manifest b/src/updater/updater.manifest new file mode 100644 index 000000000..59496dfcc --- /dev/null +++ b/src/updater/updater.manifest @@ -0,0 +1,22 @@ + + + +DuckStation Updater + + + + + + \ No newline at end of file diff --git a/src/updater/updater.vcxproj b/src/updater/updater.vcxproj new file mode 100644 index 000000000..84850c71c --- /dev/null +++ b/src/updater/updater.vcxproj @@ -0,0 +1,382 @@ + + + + + DebugFast + Win32 + + + DebugFast + x64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseLTCG + Win32 + + + ReleaseLTCG + x64 + + + Release + Win32 + + + Release + x64 + + + + + {8bda439c-6358-45fb-9994-2ff083babe06} + + + {7ff9fdb9-d504-47db-a16a-b08071999620} + + + {ee054e08-3799-4a59-a422-18259c105ffd} + + + + + + + + + + + + + + {32EEAF44-57F8-4C6C-A6F0-DE5667123DD5} + Win32Proj + updater + 10.0 + + + + Application + true + v142 + NotSet + + + Application + true + v142 + NotSet + + + Application + true + v142 + NotSet + + + Application + true + v142 + NotSet + + + Application + false + v142 + true + NotSet + false + + + Application + false + v142 + true + NotSet + false + + + Application + false + v142 + true + NotSet + false + + + Application + false + v142 + true + NotSet + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)bin\$(Platform)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + + + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + true + $(SolutionDir)bin\$(Platform)\ + + + true + $(SolutionDir)bin\$(Platform)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + + + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + true + $(SolutionDir)bin\$(Platform)\ + + + $(SolutionDir)bin\$(Platform)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + + + false + $(SolutionDir)bin\$(Platform)\ + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + + + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + $(SolutionDir)bin\$(Platform)\ + + + $(SolutionDir)build\$(ProjectName)-$(Platform)-$(Configuration)\ + $(ProjectName)-$(Platform)-$(Configuration) + false + $(SolutionDir)bin\$(Platform)\ + + + + + + Level4 + Disabled + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + + + Windows + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + + + + + + + Level4 + Disabled + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + + + Windows + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + + + + + + + Level4 + Disabled + _ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + Default + true + false + stdcpp17 + false + OnlyExplicitInline + true + + + Windows + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + + + + + + + Level4 + Disabled + _ITERATOR_DEBUG_LEVEL=1;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUGFAST;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + true + ProgramDatabase + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + Default + true + false + stdcpp17 + false + OnlyExplicitInline + true + + + Windows + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + + + + + Level4 + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + + + Windows + true + true + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Default + + + + + Level4 + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + true + stdcpp17 + true + true + + + Windows + true + true + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + Level4 + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + + + Windows + true + true + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + Default + + + + + Level4 + + + MaxSpeed + true + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions) + $(SolutionDir)dep\zlib\include;$(SolutionDir)dep\minizip\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + true + true + stdcpp17 + true + true + + + Windows + true + true + true + d3d11.lib;dxgi.lib;%(AdditionalDependencies) + UseLinkTimeCodeGeneration + + + + + + \ No newline at end of file diff --git a/src/updater/updater.vcxproj.filters b/src/updater/updater.vcxproj.filters new file mode 100644 index 000000000..3f55d5df3 --- /dev/null +++ b/src/updater/updater.vcxproj.filters @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/updater/win32_main.cpp b/src/updater/win32_main.cpp new file mode 100644 index 000000000..2cf335f2e --- /dev/null +++ b/src/updater/win32_main.cpp @@ -0,0 +1,93 @@ +#include "common/file_system.h" +#include "common/log.h" +#include "common/string_util.h" +#include "common/win32_progress_callback.h" +#include "common/windows_headers.h" +#include "updater.h" +#include + +static void WaitForProcessToExit(int process_id) +{ + HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, process_id); + if (!hProcess) + return; + + WaitForSingleObject(hProcess, INFINITE); + CloseHandle(hProcess); +} + +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + Log::SetConsoleOutputParams(true); + + Win32ProgressCallback progress; + + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(lpCmdLine, &argc); + if (!argv || argc <= 0) + { + progress.ModalError("Failed to parse command line."); + return 1; + } + if (argc != 4) + { + progress.ModalError("Expected 4 arguments: parent process id, output directory, update zip, program to launch."); + LocalFree(argv); + return 1; + } + + const int parent_process_id = StringUtil::FromChars(StringUtil::WideStringToUTF8String(argv[0])).value_or(0); + const std::string destination_directory = StringUtil::WideStringToUTF8String(argv[1]); + const std::string zip_path = StringUtil::WideStringToUTF8String(argv[2]); + const std::string program_to_launch = StringUtil::WideStringToUTF8String(argv[3]); + LocalFree(argv); + + if (parent_process_id <= 0 || destination_directory.empty() || zip_path.empty() || program_to_launch.empty()) + { + progress.ModalError("One or more parameters is empty."); + return 1; + } + + progress.SetFormattedStatusText("Waiting for parent process %d to exit...", parent_process_id); + WaitForProcessToExit(parent_process_id); + + Updater updater(&progress); + if (!updater.Initialize(destination_directory)) + { + progress.ModalError("Failed to initialize updater."); + return 1; + } + + if (!updater.OpenUpdateZip(zip_path.c_str())) + { + progress.DisplayFormattedModalError("Could not open update zip '%s'. Update not installed.", zip_path.c_str()); + return 1; + } + + if (!updater.PrepareStagingDirectory()) + { + progress.ModalError("Failed to prepare staging directory. Update not installed."); + return 1; + } + + if (!updater.StageUpdate()) + { + progress.ModalError("Failed to stage update. Update not installed."); + return 1; + } + + if (!updater.CommitUpdate()) + { + progress.ModalError( + "Failed to commit update. Your installation may be corrupted, please re-download a fresh version from GitHub."); + return 1; + } + + updater.CleanupStagingDirectory(); + + progress.ModalInformation("Update complete."); + + progress.DisplayFormattedInformation("Launching '%s'...", program_to_launch.c_str()); + ShellExecuteA(nullptr, "open", program_to_launch.c_str(), nullptr, nullptr, SW_SHOWNORMAL); + return 0; +}