Qt: Add update extractor source

This commit is contained in:
Connor McLaughlin 2022-05-12 20:23:01 +10:00 committed by refractionpcsx2
parent a3f6efecb8
commit a289723f66
14 changed files with 1358 additions and 0 deletions

View File

@ -42,6 +42,7 @@ add_subdirectory(pcsx2)
if (QT_BUILD)
add_subdirectory(pcsx2-qt)
add_subdirectory(updater)
endif()
# tests

View File

@ -64,6 +64,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "d3d12memalloc", "3rdparty\d
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lzma", "3rdparty\lzma\lzma.vcxproj", "{A4323327-3F2B-4271-83D9-7F9A3C66B6B2}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "updater", "updater\updater.vcxproj", "{90BBDC04-CC44-4006-B893-06A4FEA8ED47}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug AVX2|x64 = Debug AVX2|x64
@ -398,6 +400,18 @@ Global
{A4323327-3F2B-4271-83D9-7F9A3C66B6B2}.Release AVX2|x64.Build.0 = Release|x64
{A4323327-3F2B-4271-83D9-7F9A3C66B6B2}.Release|x64.ActiveCfg = Release|x64
{A4323327-3F2B-4271-83D9-7F9A3C66B6B2}.Release|x64.Build.0 = Release|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Debug AVX2|x64.ActiveCfg = Debug|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Debug AVX2|x64.Build.0 = Debug|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Debug|x64.ActiveCfg = Debug|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Debug|x64.Build.0 = Debug|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Devel AVX2|x64.ActiveCfg = Devel|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Devel AVX2|x64.Build.0 = Devel|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Devel|x64.ActiveCfg = Devel|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Devel|x64.Build.0 = Devel|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Release AVX2|x64.ActiveCfg = Release|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Release AVX2|x64.Build.0 = Release|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Release|x64.ActiveCfg = Release|x64
{90BBDC04-CC44-4006-B893-06A4FEA8ED47}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

12
updater/CMakeLists.txt Normal file
View File

@ -0,0 +1,12 @@
add_executable(updater
Updater.cpp
Updater.h
)
target_link_libraries(updater PRIVATE common fmt::fmt lzma)
if(WIN32)
target_sources(updater PRIVATE
Win32Update.cpp
)
endif()

43
updater/SZErrors.h Normal file
View File

@ -0,0 +1,43 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "7zTypes.h"
static inline const char* SZErrorToString(SRes res)
{
// clang-format off
switch (res)
{
case SZ_OK: return "SZ_OK";
case SZ_ERROR_DATA: return "SZ_ERROR_DATA";
case SZ_ERROR_MEM: return "SZ_ERROR_MEM";
case SZ_ERROR_CRC: return "SZ_ERROR_CRC";
case SZ_ERROR_UNSUPPORTED: return "SZ_ERROR_UNSUPPORTED";
case SZ_ERROR_PARAM: return "SZ_ERROR_PARAM";
case SZ_ERROR_INPUT_EOF: return "SZ_ERROR_INPUT_EOF";
case SZ_ERROR_OUTPUT_EOF: return "SZ_ERROR_OUTPUT_EOF";
case SZ_ERROR_READ: return "SZ_ERROR_READ";
case SZ_ERROR_WRITE: return "SZ_ERROR_WRITE";
case SZ_ERROR_PROGRESS: return "SZ_ERROR_PROGRESS";
case SZ_ERROR_FAIL: return "SZ_ERROR_FAIL";
case SZ_ERROR_THREAD: return "SZ_ERROR_THREAD";
case SZ_ERROR_ARCHIVE: return "SZ_ERROR_ARCHIVE";
case SZ_ERROR_NO_ARCHIVE: return "SZ_ERROR_NO_ARCHIVE";
default: return "SZ_UNKNOWN";
}
// clang-format on
}

392
updater/Updater.cpp Normal file
View File

@ -0,0 +1,392 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "Updater.h"
#include "SZErrors.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include "7zAlloc.h"
#include "7zCrc.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <memory>
#include <set>
#include <string>
#include <vector>
#ifdef _WIN32
#include <shellapi.h>
#endif
static constexpr size_t kInputBufSize = ((size_t)1 << 18);
static constexpr ISzAlloc g_Alloc = {SzAlloc, SzFree};
static std::FILE* s_file_console_stream;
static constexpr IConsoleWriter s_file_console_writer = {
[](const wxString& fmt) { // WriteRaw
auto buf = fmt.ToUTF8();
std::fwrite(buf.data(), buf.length(), 1, s_file_console_stream);
std::fflush(s_file_console_stream);
},
[](const wxString& fmt) { // DoWriteLn
auto buf = fmt.ToUTF8();
std::fwrite(buf.data(), buf.length(), 1, s_file_console_stream);
std::fputc('\n', s_file_console_stream);
std::fflush(s_file_console_stream);
},
[](ConsoleColors) { // DoSetColor
},
[](const wxString& fmt) { // DoWriteFromStdout
auto buf = fmt.ToUTF8();
std::fwrite(buf.data(), buf.length(), 1, s_file_console_stream);
std::fflush(s_file_console_stream);
},
[]() { // Newline
std::fputc('\n', s_file_console_stream);
std::fflush(s_file_console_stream);
},
[](const wxString&) { // SetTitle
}};
static void CloseConsoleFile()
{
if (s_file_console_stream)
std::fclose(s_file_console_stream);
}
Updater::Updater(ProgressCallback* progress)
: m_progress(progress)
{
progress->SetTitle("PCSX2 Update Installer");
}
Updater::~Updater()
{
if (m_archive_opened)
SzArEx_Free(&m_archive, &g_Alloc);
ISzAlloc_Free(&g_Alloc, m_look_stream.buf);
if (m_file_opened)
File_Close(&m_archive_stream.file);
}
void Updater::SetupLogging(ProgressCallback* progress, const std::string& destination_directory)
{
const std::string log_path(Path::CombineStdString(destination_directory, "updater.log"));
s_file_console_stream = FileSystem::OpenCFile(log_path.c_str(), "w");
if (!s_file_console_stream)
{
progress->DisplayFormattedModalError("Failed to open log file '%s'", log_path.c_str());
return;
}
Console_SetActiveHandler(s_file_console_writer);
std::atexit(CloseConsoleFile);
}
bool Updater::Initialize(std::string destination_directory)
{
m_destination_directory = std::move(destination_directory);
m_staging_directory = StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "%s",
m_destination_directory.c_str(), "UPDATE_STAGING");
m_progress->DisplayFormattedInformation("Destination directory: '%s'", m_destination_directory.c_str());
m_progress->DisplayFormattedInformation("Staging directory: '%s'", m_staging_directory.c_str());
return true;
}
bool Updater::OpenUpdateZip(const char* path)
{
FileInStream_CreateVTable(&m_archive_stream);
LookToRead2_CreateVTable(&m_look_stream, False);
CrcGenerateTable();
m_look_stream.buf = (Byte*)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
if (!m_look_stream.buf)
{
m_progress->DisplayFormattedError("Failed to allocate input buffer?!");
return false;
}
m_look_stream.bufSize = kInputBufSize;
m_look_stream.realStream = &m_archive_stream.vt;
LookToRead2_Init(&m_look_stream);
#ifdef _WIN32
WRes wres = InFile_OpenW(&m_archive_stream.file, StringUtil::UTF8StringToWideString(path).c_str());
#else
WRes wres = InFile_Open(&m_archive_stream.file, path);
#endif
if (wres != 0)
{
m_progress->DisplayFormattedModalError("Failed to open '%s': %d", path, wres);
return false;
}
m_file_opened = true;
SzArEx_Init(&m_archive);
SRes res = SzArEx_Open(&m_archive, &m_look_stream.vt, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
{
m_progress->DisplayFormattedModalError("SzArEx_Open() failed: %s [%d]", SZErrorToString(res), res);
return false;
}
m_archive_opened = true;
m_progress->SetStatusText("Parsing update zip...");
return ParseZip();
}
bool Updater::RecursiveDeleteDirectory(const char* path)
{
#ifdef _WIN32
// making this safer on Win32...
std::wstring wpath(StringUtil::UTF8StringToWideString(path));
wpath += L'\0';
SHFILEOPSTRUCTW op = {};
op.wFunc = FO_DELETE;
op.pFrom = wpath.c_str();
op.fFlags = FOF_NOCONFIRMATION;
return (SHFileOperationW(&op) == 0 && !op.fAnyOperationsAborted);
#else
return FileSystem::DeleteDirectory(path, true);
#endif
}
bool Updater::ParseZip()
{
std::vector<UInt16> filename_buffer;
for (u32 file_index = 0; file_index < m_archive.NumFiles; file_index++)
{
// skip directories, we handle them ourselves
if (SzArEx_IsDir(&m_archive, file_index))
continue;
size_t filename_len = SzArEx_GetFileNameUtf16(&m_archive, file_index, nullptr);
if (filename_len <= 1)
continue;
filename_buffer.resize(filename_len);
SzArEx_GetFileNameUtf16(&m_archive, file_index, filename_buffer.data());
// TODO: This won't work on Linux (4-byte wchar_t).
FileToUpdate entry;
entry.file_index = file_index;
entry.destination_filename = StringUtil::WideStringToUTF8String(reinterpret_cast<wchar_t*>(filename_buffer.data()));
if (entry.destination_filename.empty())
continue;
// replace forward slashes with backslashes
for (size_t i = 0; i < entry.destination_filename.length(); i++)
{
if (entry.destination_filename[i] == '/' || entry.destination_filename[i] == '\\')
entry.destination_filename[i] = FS_OSPATH_SEPARATOR_CHARACTER;
}
// should never have a leading slash. just in case.
while (entry.destination_filename[0] == FS_OSPATH_SEPARATOR_CHARACTER)
entry.destination_filename.erase(0, 1);
// skip directories (we sort them out later)
if (!entry.destination_filename.empty() && entry.destination_filename.back() != FS_OSPATH_SEPARATOR_CHARACTER)
{
// skip updater itself, since it was already pre-extracted.
if (StringUtil::Strcasecmp(entry.destination_filename.c_str(), "updater.exe") != 0)
{
m_progress->DisplayFormattedInformation("Found file in zip: '%s'", entry.destination_filename.c_str());
m_update_paths.push_back(std::move(entry));
}
}
}
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_SEPARATOR_CHARACTER)
{
std::string dir(ftu.destination_filename.begin(), ftu.destination_filename.begin() + i);
while (!dir.empty() && dir[dir.length() - 1] == FS_OSPATH_SEPARATOR_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");
if (!RecursiveDeleteDirectory(m_staging_directory.c_str()) ||
FileSystem::DirectoryExists(m_staging_directory.c_str()))
{
m_progress->ModalError("Failed to remove old staging directory");
return false;
}
}
if (!FileSystem::CreateDirectoryPath(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" FS_OSPATH_SEPARATOR_STR "%s", m_staging_directory.c_str(), subdir.c_str());
if (!FileSystem::CreateDirectoryPath(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<u32>(m_update_paths.size()));
m_progress->SetProgressValue(0);
UInt32 block_index = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
Byte* out_buffer = 0; /* it must be 0 before first call for each new archive. */
size_t out_buffer_size = 0; /* it can have any value before first call (if outBuffer = 0) */
ScopedGuard out_buffer_guard([&out_buffer]() {
if (out_buffer)
ISzAlloc_Free(&g_Alloc, out_buffer);
});
for (const FileToUpdate& ftu : m_update_paths)
{
m_progress->SetFormattedStatusText("Extracting '%s'...", ftu.destination_filename.c_str());
m_progress->DisplayFormattedInformation("Decompressing '%s'...", ftu.destination_filename.c_str());
size_t out_offset = 0;
size_t extracted_size = 0;
SRes res = SzArEx_Extract(&m_archive, &m_look_stream.vt, ftu.file_index,
&block_index, &out_buffer, &out_buffer_size, &out_offset, &extracted_size, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
{
m_progress->DisplayFormattedModalError("Failed to decompress file '%s' from 7z (file index=%u, error=%s)",
ftu.destination_filename.c_str(), ftu.file_index, SZErrorToString(res));
return false;
}
m_progress->DisplayFormattedInformation("Writing '%s' to staging (%zu bytes)...", ftu.destination_filename.c_str(), extracted_size);
const std::string destination_file = StringUtil::StdStringFromFormat(
"%s" FS_OSPATH_SEPARATOR_STR "%s", m_staging_directory.c_str(), 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());
return false;
}
const bool wrote_completely = std::fwrite(out_buffer + out_offset, extracted_size, 1, fp) == 1 && std::fflush(fp) == 0;
if (std::fclose(fp) != 0 || !wrote_completely)
{
m_progress->DisplayFormattedModalError("Failed to write output file '%s'", destination_file.c_str());
FileSystem::DeleteFilePath(destination_file.c_str());
return false;
}
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" FS_OSPATH_SEPARATOR_STR "%s",
m_destination_directory.c_str(), subdir.c_str());
if (!FileSystem::DirectoryExists(dest_subdir.c_str()) && !FileSystem::CreateDirectoryPath(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" FS_OSPATH_SEPARATOR_STR "%s", m_staging_directory.c_str(), ftu.destination_filename.c_str());
const std::string dest_file_name = StringUtil::StdStringFromFormat(
"%s" FS_OSPATH_SEPARATOR_STR "%s", m_destination_directory.c_str(), ftu.destination_filename.c_str());
m_progress->DisplayFormattedInformation("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 (!RecursiveDeleteDirectory(m_staging_directory.c_str()))
m_progress->DisplayFormattedError("Failed to remove staging directory '%s'", m_staging_directory.c_str());
}

66
updater/Updater.h Normal file
View File

@ -0,0 +1,66 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/ProgressCallback.h"
#include "7z.h"
#include "7zFile.h"
#include <string>
#include <vector>
class Updater
{
public:
Updater(ProgressCallback* progress);
~Updater();
static void SetupLogging(ProgressCallback* progress, const std::string& destination_directory);
bool Initialize(std::string destination_directory);
bool OpenUpdateZip(const char* path);
bool PrepareStagingDirectory();
bool StageUpdate();
bool CommitUpdate();
void CleanupStagingDirectory();
private:
static bool RecursiveDeleteDirectory(const char* path);
struct FileToUpdate
{
u32 file_index;
std::string destination_filename;
};
bool ParseZip();
std::string m_destination_directory;
std::string m_staging_directory;
std::vector<FileToUpdate> m_update_paths;
std::vector<std::string> m_update_directories;
ProgressCallback* m_progress;
CFileInStream m_archive_stream = {};
CLookToRead2 m_look_stream = {};
CSzArEx m_archive = {};
bool m_file_opened = false;
bool m_archive_opened = false;
};

166
updater/UpdaterExtractor.h Normal file
View File

@ -0,0 +1,166 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "SZErrors.h"
#include "common/FileSystem.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include "fmt/core.h"
#if defined(_WIN32)
#include "7z.h"
#include "7zAlloc.h"
#include "7zCrc.h"
#include "7zFile.h"
#endif
#include <cstdio>
#include <string>
#include <vector>
#ifdef _WIN32
static constexpr char UPDATER_EXECUTABLE[] = "updater.exe";
static constexpr char UPDATER_ARCHIVE_NAME[] = "update.7z";
#endif
static inline bool ExtractUpdater(const char* archive_path, const char* destination_path, std::string* error)
{
#if defined(_WIN32)
static constexpr size_t kInputBufSize = ((size_t)1 << 18);
static constexpr ISzAlloc g_Alloc = {SzAlloc, SzFree};
CFileInStream instream = {};
CLookToRead2 lookstream = {};
CSzArEx archive = {};
FileInStream_CreateVTable(&instream);
LookToRead2_CreateVTable(&lookstream, False);
CrcGenerateTable();
lookstream.buf = (Byte*)ISzAlloc_Alloc(&g_Alloc, kInputBufSize);
if (!lookstream.buf)
{
*error = "Failed to allocate input buffer?!";
return false;
}
lookstream.bufSize = kInputBufSize;
lookstream.realStream = &instream.vt;
LookToRead2_Init(&lookstream);
ScopedGuard buffer_guard([&lookstream]() {
ISzAlloc_Free(&g_Alloc, lookstream.buf);
});
#ifdef _WIN32
WRes wres = InFile_OpenW(&instream.file, StringUtil::UTF8StringToWideString(archive_path).c_str());
#else
WRes wres = InFile_Open(&instream.file, archive_path);
#endif
if (wres != 0)
{
*error = fmt::format("Failed to open '{0}': {1}", archive_path, wres);
return false;
}
ScopedGuard file_guard([&instream]() {
File_Close(&instream.file);
});
SzArEx_Init(&archive);
SRes res = SzArEx_Open(&archive, &lookstream.vt, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
{
*error = fmt::format("SzArEx_Open() failed: {0} [{1}]", SZErrorToString(res), res);
return false;
}
ScopedGuard archive_guard([&archive]() {
SzArEx_Free(&archive, &g_Alloc);
});
std::vector<UInt16> filename_buffer;
u32 updater_file_index = archive.NumFiles;
for (u32 file_index = 0; file_index < archive.NumFiles; file_index++)
{
if (SzArEx_IsDir(&archive, file_index))
continue;
size_t filename_len = SzArEx_GetFileNameUtf16(&archive, file_index, nullptr);
if (filename_len <= 1)
continue;
filename_buffer.resize(filename_len);
filename_len = SzArEx_GetFileNameUtf16(&archive, file_index, filename_buffer.data());
// TODO: This won't work on Linux (4-byte wchar_t).
const std::string filename(StringUtil::WideStringToUTF8String(reinterpret_cast<wchar_t*>(filename_buffer.data())));
if (filename != UPDATER_EXECUTABLE)
continue;
updater_file_index = file_index;
break;
}
if (updater_file_index == archive.NumFiles)
{
*error = fmt::format("Updater executable ({}) not found in archive.", UPDATER_EXECUTABLE);
return false;
}
UInt32 block_index = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
Byte* out_buffer = 0; /* it must be 0 before first call for each new archive. */
size_t out_buffer_size = 0; /* it can have any value before first call (if outBuffer = 0) */
ScopedGuard out_buffer_guard([&out_buffer]() {
if (out_buffer)
ISzAlloc_Free(&g_Alloc, out_buffer);
});
size_t out_offset = 0;
size_t extracted_size = 0;
res = SzArEx_Extract(&archive, &lookstream.vt, updater_file_index,
&block_index, &out_buffer, &out_buffer_size, &out_offset, &extracted_size, &g_Alloc, &g_Alloc);
if (res != SZ_OK)
{
*error = fmt::format("Failed to decompress {0} from 7z (file index=%u, error=%s)",
UPDATER_EXECUTABLE, updater_file_index, SZErrorToString(res));
return false;
}
std::FILE* fp = FileSystem::OpenCFile(destination_path, "wb");
if (!fp)
{
*error = fmt::format("Failed to open '{0}' for writing.", destination_path);
return false;
}
const bool wrote_completely = std::fwrite(out_buffer + out_offset, extracted_size, 1, fp) == 1 && std::fflush(fp) == 0;
if (std::fclose(fp) != 0 || !wrote_completely)
{
*error = fmt::format("Failed to write output file '{}'", destination_path);
FileSystem::DeleteFilePath(destination_path);
return false;
}
error->clear();
return true;
#else
*error = "Not supported on this platform";
return false;
#endif
}

View File

@ -0,0 +1,395 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2022 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "Updater.h"
#include "Windows/resource.h"
#include "common/FileSystem.h"
#include "common/Console.h"
#include "common/StringUtil.h"
#include "common/ProgressCallback.h"
#include "common/RedtapeWindows.h"
#include <CommCtrl.h>
#include <shellapi.h>
class Win32ProgressCallback final : public BaseProgressCallback
{
public:
Win32ProgressCallback();
void PushState() override;
void PopState() override;
void SetCancellable(bool cancellable) override;
void SetTitle(const char* title) override;
void SetStatusText(const char* text) override;
void SetProgressRange(u32 range) override;
void SetProgressValue(u32 value) override;
void DisplayError(const char* message) override;
void DisplayWarning(const char* message) override;
void DisplayInformation(const char* message) override;
void DisplayDebugMessage(const char* message) override;
void ModalError(const char* message) override;
bool ModalConfirmation(const char* message) override;
void ModalInformation(const char* message) override;
private:
enum : int
{
WINDOW_WIDTH = 600,
WINDOW_HEIGHT = 300,
WINDOW_MARGIN = 10,
SUBWINDOW_WIDTH = WINDOW_WIDTH - 20 - WINDOW_MARGIN - WINDOW_MARGIN,
};
bool Create();
void Destroy();
void Redraw(bool force);
void PumpMessages();
static LRESULT CALLBACK WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);
HWND m_window_hwnd{};
HWND m_text_hwnd{};
HWND m_progress_hwnd{};
HWND m_list_box_hwnd{};
int m_last_progress_percent = -1;
};
Win32ProgressCallback::Win32ProgressCallback()
: BaseProgressCallback()
{
Create();
}
void Win32ProgressCallback::PushState()
{
BaseProgressCallback::PushState();
}
void Win32ProgressCallback::PopState()
{
BaseProgressCallback::PopState();
Redraw(true);
}
void Win32ProgressCallback::SetCancellable(bool cancellable)
{
BaseProgressCallback::SetCancellable(cancellable);
Redraw(true);
}
void Win32ProgressCallback::SetTitle(const char* title)
{
SetWindowTextW(m_window_hwnd, StringUtil::UTF8StringToWideString(title).c_str());
}
void Win32ProgressCallback::SetStatusText(const char* text)
{
BaseProgressCallback::SetStatusText(text);
Redraw(true);
}
void Win32ProgressCallback::SetProgressRange(u32 range)
{
BaseProgressCallback::SetProgressRange(range);
Redraw(false);
}
void Win32ProgressCallback::SetProgressValue(u32 value)
{
BaseProgressCallback::SetProgressValue(value);
Redraw(false);
}
bool Win32ProgressCallback::Create()
{
static const wchar_t* CLASS_NAME = L"PCSX2Win32ProgressCallbackWindow";
static bool class_registered = false;
if (!class_registered)
{
InitCommonControls();
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProcThunk;
wc.hInstance = GetModuleHandle(nullptr);
wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor(NULL, IDC_WAIT);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = CLASS_NAME;
if (!RegisterClassExW(&wc))
{
MessageBoxW(nullptr, L"Failed to register window class", L"Error", MB_OK);
return false;
}
class_registered = true;
}
m_window_hwnd =
CreateWindowExW(WS_EX_CLIENTEDGE, CLASS_NAME, L"Win32ProgressCallback", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, nullptr, nullptr, GetModuleHandle(nullptr), this);
if (!m_window_hwnd)
{
MessageBoxW(nullptr, L"Failed to create window", L"Error", MB_OK);
return false;
}
SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
ShowWindow(m_window_hwnd, SW_SHOW);
PumpMessages();
return true;
}
void Win32ProgressCallback::Destroy()
{
if (!m_window_hwnd)
return;
DestroyWindow(m_window_hwnd);
m_window_hwnd = {};
m_text_hwnd = {};
m_progress_hwnd = {};
}
void Win32ProgressCallback::PumpMessages()
{
MSG msg;
while (PeekMessageW(&msg, m_window_hwnd, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
void Win32ProgressCallback::Redraw(bool force)
{
const int percent =
static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
if (percent == m_last_progress_percent && !force)
{
PumpMessages();
return;
}
m_last_progress_percent = percent;
SendMessageW(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range));
SendMessageW(m_progress_hwnd, PBM_SETPOS, static_cast<WPARAM>(m_progress_value), 0);
SetWindowTextW(m_text_hwnd, StringUtil::UTF8StringToWideString(m_status_text).c_str());
RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE);
PumpMessages();
}
LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
Win32ProgressCallback* cb;
if (msg == WM_CREATE)
{
const CREATESTRUCTW* cs = reinterpret_cast<CREATESTRUCTW*>(lparam);
cb = static_cast<Win32ProgressCallback*>(cs->lpCreateParams);
}
else
{
cb = reinterpret_cast<Win32ProgressCallback*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
}
return cb->WndProc(hwnd, msg, wparam, lparam);
}
LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_CREATE:
{
const CREATESTRUCTA* cs = reinterpret_cast<CREATESTRUCTA*>(lparam);
HFONT default_font = reinterpret_cast<HFONT>(GetStockObject(ANSI_VAR_FONT));
SendMessageW(hwnd, WM_SETFONT, WPARAM(default_font), TRUE);
int y = WINDOW_MARGIN;
m_text_hwnd = CreateWindowExW(0, L"Static", nullptr, WS_VISIBLE | WS_CHILD, WINDOW_MARGIN, y, SUBWINDOW_WIDTH, 16,
hwnd, nullptr, cs->hInstance, nullptr);
SendMessageW(m_text_hwnd, WM_SETFONT, WPARAM(default_font), TRUE);
y += 16 + WINDOW_MARGIN;
m_progress_hwnd = CreateWindowExW(0, PROGRESS_CLASSW, nullptr, WS_VISIBLE | WS_CHILD, WINDOW_MARGIN, y,
SUBWINDOW_WIDTH, 32, hwnd, nullptr, cs->hInstance, nullptr);
y += 32 + WINDOW_MARGIN;
m_list_box_hwnd =
CreateWindowExW(0, L"LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL,
WINDOW_MARGIN, y, SUBWINDOW_WIDTH, 170, hwnd, nullptr, cs->hInstance, nullptr);
SendMessageW(m_list_box_hwnd, WM_SETFONT, WPARAM(default_font), TRUE);
y += 170;
}
break;
default:
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
return 0;
}
void Win32ProgressCallback::DisplayError(const char* message)
{
Console.Error(message);
SendMessageW(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
SendMessageW(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
PumpMessages();
}
void Win32ProgressCallback::DisplayWarning(const char* message)
{
Console.Warning(message);
SendMessageW(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
SendMessageW(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
PumpMessages();
}
void Win32ProgressCallback::DisplayInformation(const char* message)
{
Console.WriteLn(message);
SendMessageW(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
SendMessageW(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
PumpMessages();
}
void Win32ProgressCallback::DisplayDebugMessage(const char* message)
{
Console.WriteLn(message);
}
void Win32ProgressCallback::ModalError(const char* message)
{
PumpMessages();
MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Error", MB_ICONERROR | MB_OK);
PumpMessages();
}
bool Win32ProgressCallback::ModalConfirmation(const char* message)
{
PumpMessages();
bool result = MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Confirmation", MB_ICONQUESTION | MB_YESNO) == IDYES;
PumpMessages();
return result;
}
void Win32ProgressCallback::ModalInformation(const char* message)
{
MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information", MB_ICONINFORMATION | MB_OK);
}
static void WaitForProcessToExit(int process_id)
{
HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, process_id);
if (!hProcess)
return;
WaitForSingleObject(hProcess, INFINITE);
CloseHandle(hProcess);
}
#include "UpdaterExtractor.h"
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
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.\n\nThis program is not intended to be run manually, please use the Qt frontend and "
"click Help->Check for Updates.");
LocalFree(argv);
return 1;
}
const int parent_process_id = StringUtil::FromChars<int>(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::wstring program_to_launch(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;
}
Updater::SetupLogging(&progress, destination_directory);
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'...",
StringUtil::WideStringToUTF8String(program_to_launch).c_str());
ShellExecuteW(nullptr, L"open", program_to_launch.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
return 0;
}

View File

@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by duckstation-qt.rc
//
#define IDI_ICON1 102
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 103
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif

BIN
updater/Windows/updater.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="com.github.stenzek.duckstation.updater"
type="win32"
/>
<description>PCSX2 Updater</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

110
updater/Windows/updater.rc Normal file
View File

@ -0,0 +1,110 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (Australia) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENA)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_AUS
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "0c0904b0"
BEGIN
VALUE "CompanyName", "PCSX2"
VALUE "FileDescription", "PCSX2"
VALUE "FileVersion", "2.0"
VALUE "InternalName", "updater.exe"
VALUE "LegalCopyright", "Copyright (C) 2022 PCSX2 Dev Team"
VALUE "OriginalFilename", "updater.exe"
VALUE "ProductName", "PCSX2 Update Installer"
VALUE "ProductVersion", "2.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0xc09, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "updater.ico"
#endif // English (Australia) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

84
updater/updater.vcxproj Normal file
View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)common\vsprops\BaseProjectConfig.props" />
<Import Project="$(SolutionDir)common\vsprops\WinSDK.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>{90BBDC04-CC44-4006-B893-06A4FEA8ED47}</ProjectGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<WholeProgramOptimization Condition="$(Configuration.Contains(Release))">true</WholeProgramOptimization>
<UseDebugLibraries Condition="$(Configuration.Contains(Debug))">true</UseDebugLibraries>
<UseDebugLibraries Condition="!$(Configuration.Contains(Debug))">false</UseDebugLibraries>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="$(SolutionDir)common\vsprops\common.props" />
<Import Project="$(SolutionDir)common\vsprops\BaseProperties.props" />
<Import Project="$(SolutionDir)common\vsprops\3rdpartyDeps.props" />
<Import Condition="$(Configuration.Contains(Debug))" Project="$(SolutionDir)common\vsprops\CodeGen_Debug.props" />
<Import Condition="$(Configuration.Contains(Devel))" Project="$(SolutionDir)common\vsprops\CodeGen_Devel.props" />
<Import Condition="$(Configuration.Contains(Release))" Project="$(SolutionDir)common\vsprops\CodeGen_Release.props" />
<Import Condition="!$(Configuration.Contains(Release))" Project="$(SolutionDir)common\vsprops\IncrementalLinking.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<TargetName>updater$(BuildString)</TargetName>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ExceptionHandling>Async</ExceptionHandling>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<EnableEnhancedInstructionSet>NotSet</EnableEnhancedInstructionSet>
<MinimalRebuild>false</MinimalRebuild>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ConformanceMode>true</ConformanceMode>
<AdditionalOptions>/Zc:__cplusplus /Zo /utf-8%(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<LargeAddressAware>Yes</LargeAddressAware>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="$(SolutionDir)3rdparty\fmt\fmt.vcxproj">
<Project>{449ad25e-424a-4714-babc-68706cdcc33b}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)3rdparty\lzma\lzma.vcxproj">
<Project>{a4323327-3f2b-4271-83d9-7f9a3c66b6b2}</Project>
</ProjectReference>
<ProjectReference Include="$(SolutionDir)common\common.vcxproj">
<Project>{4639972e-424e-4e13-8b07-ca403c481346}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Updater.cpp" />
<ClCompile Include="Windows\WindowsUpdater.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="SZErrors.h" />
<ClInclude Include="Updater.h" />
<ClInclude Include="UpdaterExtractor.h" />
<ClInclude Include="Windows\resource.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="Windows\updater.manifest" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Windows\updater.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="Windows\updater.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="Updater.cpp" />
<ClCompile Include="Windows\WindowsUpdater.cpp">
<Filter>Windows</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Updater.h" />
<ClInclude Include="SZErrors.h" />
<ClInclude Include="Windows\resource.h">
<Filter>Windows</Filter>
</ClInclude>
<ClInclude Include="UpdaterExtractor.h" />
</ItemGroup>
<ItemGroup>
<Filter Include="Windows">
<UniqueIdentifier>{bdeccfd9-a573-4076-b112-e013516c30c8}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<Image Include="Windows\updater.ico">
<Filter>Windows</Filter>
</Image>
</ItemGroup>
<ItemGroup>
<Manifest Include="Windows\updater.manifest">
<Filter>Windows</Filter>
</Manifest>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Windows\updater.rc">
<Filter>Windows</Filter>
</ResourceCompile>
</ItemGroup>
</Project>