First Commit

This commit is contained in:
2025-11-18 14:18:26 -07:00
parent 33eb6e3707
commit 27277ec342
6106 changed files with 3571167 additions and 0 deletions

31
updater/CMakeLists.txt Normal file
View File

@@ -0,0 +1,31 @@
add_executable(updater
SZErrors.h
Updater.cpp
Updater.h
UpdaterExtractor.h
)
target_link_libraries(updater PRIVATE common fmt::fmt)
target_include_directories(updater PRIVATE .)
if(WIN32)
target_sources(updater PRIVATE ../pcsx2-qt/VCRuntimeChecker.cpp)
target_link_libraries(updater PRIVATE
LZMA::LZMA
Comctl32.lib
)
target_sources(updater PRIVATE
Windows/WindowsUpdater.cpp
Windows/resource.h
Windows/updater.rc
Windows/updater.manifest
)
set_target_properties(updater PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
if(NOT PACKAGE_MODE)
install(TARGETS updater DESTINATION ${CMAKE_SOURCE_DIR}/bin)
if(MSVC)
install(FILES $<TARGET_PDB_FILE:updater> DESTINATION ${CMAKE_SOURCE_DIR}/bin)
endif()
endif()

31
updater/SZErrors.h Normal file
View File

@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#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
}

432
updater/Updater.cpp Normal file
View File

@@ -0,0 +1,432 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "Updater.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <memory>
#include <set>
#include <string>
#include <vector>
#ifdef _WIN32
#include "common/RedtapeWilCom.h"
#include <Shobjidl.h>
#include <shellapi.h>
#endif
#ifdef _WIN32
#include "7zAlloc.h"
#include "7zCrc.h"
#include "SZErrors.h"
static constexpr size_t kInputBufSize = ((size_t)1 << 18);
static constexpr ISzAlloc g_Alloc = {SzAlloc, SzFree};
#endif
Updater::Updater(ProgressCallback* progress)
: m_progress(progress)
{
progress->SetTitle("PCSX2 Update Installer");
}
Updater::~Updater()
{
CloseUpdateZip();
}
void Updater::SetupLogging(ProgressCallback* progress, const std::string& destination_directory)
{
Log::SetDebugOutputLevel(LOGLEVEL_DEBUG);
std::string log_path = Path::Combine(destination_directory, "updater.log");
if (!Log::SetFileOutputLevel(LOGLEVEL_DEBUG, std::move(log_path)))
progress->DisplayFormattedModalError("Failed to open log file '%s'", log_path.c_str());
}
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)
{
#ifdef _WIN32
FileInStream_CreateVTable(&m_archive_stream);
LookToRead2_CreateVTable(&m_look_stream, False);
CrcGenerateTable();
m_zip_path = path;
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, FileSystem::GetWin32Path(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();
#else
return false;
#endif
}
void Updater::CloseUpdateZip()
{
#ifdef _WIN32
if (m_archive_opened)
{
SzArEx_Free(&m_archive, &g_Alloc);
m_archive_opened = false;
}
if (m_look_stream.buf)
{
ISzAlloc_Free(&g_Alloc, m_look_stream.buf);
m_look_stream.buf = nullptr;
}
if (m_file_opened)
{
File_Close(&m_archive_stream.file);
m_file_opened = false;
}
#endif
}
bool Updater::RecursiveDeleteDirectory(const char* path)
{
#ifdef _WIN32
wil::com_ptr_nothrow<IFileOperation> fo;
HRESULT hr = CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(fo.put()));
if (FAILED(hr))
{
m_progress->DisplayFormattedError("CoCreateInstance() for IFileOperation failed: %08X", hr);
return false;
}
wil::com_ptr_nothrow<IShellItem> item;
hr = SHCreateItemFromParsingName(StringUtil::UTF8StringToWideString(path).c_str(), NULL, IID_PPV_ARGS(item.put()));
if (FAILED(hr))
{
m_progress->DisplayFormattedError("SHCreateItemFromParsingName() for delete failed: %08X", hr);
return false;
}
hr = fo->SetOperationFlags(FOF_NOCONFIRMATION | FOF_SILENT);
if (FAILED(hr))
m_progress->DisplayFormattedWarning("IFileOperation::SetOperationFlags() failed: %08X", hr);
hr = fo->DeleteItem(item.get(), nullptr);
if (FAILED(hr))
{
m_progress->DisplayFormattedError("IFileOperation::DeleteItem() failed: %08X", hr);
return false;
}
item.reset();
hr = fo->PerformOperations();
if (FAILED(hr))
{
m_progress->DisplayFormattedError("IFileOperation::PerformOperations() failed: %08X", hr);
return false;
}
return true;
#else
return FileSystem::RecursiveDeleteDirectory(path);
#endif
}
bool Updater::ParseZip()
{
#ifdef _WIN32
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.
// also skips portable.ini to not mess with future non-portable installs.
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;
#else
return false;
#endif
}
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);
#ifdef _WIN32
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;
#else
return false;
#endif
}
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(FileSystem::GetWin32Path(staging_file_name).c_str(),
FileSystem::GetWin32Path(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());
}
void Updater::RemoveUpdateZip()
{
if (m_zip_path.empty())
return;
CloseUpdateZip();
if (!FileSystem::DeleteFilePath(m_zip_path.c_str()))
m_progress->DisplayFormattedError("Failed to remove update zip '%s'", m_zip_path.c_str());
}
std::string Updater::FindPCSX2Exe() const
{
for (const FileToUpdate& file : m_update_paths)
{
const std::string& name = file.destination_filename;
if (name.find(FS_OSPATH_SEPARATOR_CHARACTER) != name.npos)
continue; // Main exe is expected to be at the top level
if (!StringUtil::StartsWithNoCase(name, "pcsx2"))
continue;
if (!StringUtil::EndsWithNoCase(name, "exe"))
continue;
return name;
}
return {};
}

66
updater/Updater.h Normal file
View File

@@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "common/ProgressCallback.h"
#ifdef _WIN32
#include "common/RedtapeWindows.h"
#include "7z.h"
#include "7zFile.h"
#endif
#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();
void RemoveUpdateZip();
std::string FindPCSX2Exe() const;
private:
bool RecursiveDeleteDirectory(const char* path);
void CloseUpdateZip();
struct FileToUpdate
{
u32 file_index;
std::string destination_filename;
};
bool ParseZip();
std::string m_zip_path;
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;
#ifdef _WIN32
CFileInStream m_archive_stream = {};
CLookToRead2 m_look_stream = {};
CSzArEx m_archive = {};
bool m_file_opened = false;
bool m_archive_opened = false;
#endif
};

153
updater/UpdaterExtractor.h Normal file
View File

@@ -0,0 +1,153 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#pragma once
#include "common/FileSystem.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include "fmt/format.h"
#if defined(_WIN32)
#include "7z.h"
#include "7zAlloc.h"
#include "7zCrc.h"
#include "7zFile.h"
#include "SZErrors.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, FileSystem::GetWin32Path(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,521 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "Updater.h"
#include "Windows/resource.h"
#include "common/FileSystem.h"
#include "common/Console.h"
#include "common/ScopedGuard.h"
#include "common/StringUtil.h"
#include "common/ProgressCallback.h"
#include "common/RedtapeWilCom.h"
#include <CommCtrl.h>
#include <shellapi.h>
#include <Shobjidl.h>
#include <thread>
#include <wil/resource.h>
#include <wil/win32_helpers.h>
#pragma comment(lib, "synchronization.lib")
class Win32ProgressCallback final : public BaseProgressCallback
{
public:
Win32ProgressCallback();
~Win32ProgressCallback() override;
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 SetProgressState(ProgressState state) 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);
void UIThreadProc(wil::slim_event_manual_reset* creationEvent);
std::thread m_ui_thread;
HWND m_window_hwnd{};
HWND m_text_hwnd{};
HWND m_progress_hwnd{};
HWND m_list_box_hwnd{};
wil::com_ptr_nothrow<ITaskbarList3> m_taskbar_list;
int m_last_progress_percent = -1;
bool m_com_initialized = false;
static inline const UINT s_uTBBC = RegisterWindowMessageW(L"TaskbarButtonCreated");
static constexpr UINT WMAPP_SETTASKBARPROGRESS = WM_USER+0;
static constexpr UINT WMAPP_SETTASKBARSTATE = WM_USER+1;
};
Win32ProgressCallback::Win32ProgressCallback()
: BaseProgressCallback()
{
wil::slim_event_manual_reset creationEvent;
m_ui_thread = std::thread(&Win32ProgressCallback::UIThreadProc, this, &creationEvent);
creationEvent.wait();
}
Win32ProgressCallback::~Win32ProgressCallback()
{
if (m_window_hwnd)
PostMessageW(m_window_hwnd, WM_CLOSE, 0, 0);
m_ui_thread.join();
}
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);
}
void Win32ProgressCallback::SetProgressState(ProgressState state)
{
BaseProgressCallback::SetProgressState(state);
Redraw(true);
}
void Win32ProgressCallback::UIThreadProc(wil::slim_event_manual_reset* creationEvent)
{
if (Create())
{
creationEvent->SetEvent();
MSG msg;
while (GetMessageW(&msg, m_window_hwnd, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
Destroy();
}
else
{
creationEvent->SetEvent();
}
}
bool Win32ProgressCallback::Create()
{
static const wchar_t* CLASS_NAME = L"PCSX2Win32ProgressCallbackWindow";
static bool class_registered = false;
if (!class_registered)
{
InitCommonControls();
WNDCLASSEX wc = {sizeof(wc)};
wc.lpfnWndProc = WndProcThunk;
wc.hInstance = wil::GetModuleInstanceHandle();
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.style = CS_NOCLOSE;
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_com_initialized = SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
m_window_hwnd =
CreateWindowExW(WS_EX_CLIENTEDGE, CLASS_NAME, L"Win32ProgressCallback", WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, CW_USEDEFAULT,
CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, nullptr, nullptr, wil::GetModuleInstanceHandle(), this);
if (!m_window_hwnd)
{
MessageBoxW(nullptr, L"Failed to create window", L"Error", MB_OK);
return false;
}
ShowWindow(m_window_hwnd, SW_SHOW);
// Pump messages manually just this one time to give the chance for the taskbar icon to be created etc.
PumpMessages();
return true;
}
void Win32ProgressCallback::Destroy()
{
m_taskbar_list.reset();
if (m_window_hwnd)
{
DestroyWindow(m_window_hwnd);
m_window_hwnd = {};
m_text_hwnd = {};
m_progress_hwnd = {};
}
if (m_com_initialized)
CoUninitialize();
}
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)
{
return;
}
m_last_progress_percent = percent;
const LONG_PTR style = GetWindowLongPtrW(m_progress_hwnd, GWL_STYLE);
LONG_PTR newStyle = style;
if (m_progress_state == ProgressState::Normal)
{
newStyle = style & ~(PBS_MARQUEE);
SendMessageW(m_window_hwnd, WMAPP_SETTASKBARPROGRESS, m_progress_value, m_progress_range);
}
else if (m_progress_state == ProgressState::Indeterminate)
{
newStyle = style | PBS_MARQUEE;
SendMessageW(m_window_hwnd, WMAPP_SETTASKBARSTATE, TBPF_INDETERMINATE, 0);
}
if (style != newStyle)
{
SetWindowLongPtrW(m_progress_hwnd, GWL_STYLE, newStyle);
SendMessageW(m_progress_hwnd, PBM_SETMARQUEE, m_progress_state == ProgressState::Indeterminate, 0);
}
if (m_progress_state != ProgressState::Indeterminate)
{
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());
}
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;
// In case the application is run elevated, allow the
// TaskbarButtonCreated message through.
ChangeWindowMessageFilterEx(hwnd, s_uTBBC, MSGFLT_ALLOW, nullptr);
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WMAPP_SETTASKBARPROGRESS:
{
if (m_taskbar_list)
m_taskbar_list->SetProgressValue(m_window_hwnd, wparam, lparam);
}
break;
case WMAPP_SETTASKBARSTATE:
{
if (m_taskbar_list)
m_taskbar_list->SetProgressState(m_window_hwnd, static_cast<TBPFLAG>(wparam));
}
break;
default:
if (msg == s_uTBBC)
{
if (m_com_initialized)
{
m_taskbar_list = wil::CoCreateInstanceNoThrow<ITaskbarList3>(CLSID_TaskbarList);
if (m_taskbar_list)
{
if (FAILED(m_taskbar_list->HrInit()))
{
m_taskbar_list.reset();
}
}
}
return 0;
}
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);
}
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);
}
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);
}
void Win32ProgressCallback::DisplayDebugMessage(const char* message)
{
Console.WriteLn(message);
}
void Win32ProgressCallback::ModalError(const char* message)
{
MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Error", MB_ICONERROR | MB_OK);
}
bool Win32ProgressCallback::ModalConfirmation(const char* message)
{
return MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Confirmation", MB_ICONQUESTION | MB_YESNO) == IDYES;
}
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);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
Win32ProgressCallback progress;
const bool com_initialized = SUCCEEDED(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
const ScopedGuard com_guard = [com_initialized]() {
if (com_initialized)
CoUninitialize();
};
int argc = 0;
wil::unique_hlocal_ptr<LPWSTR[]> argv(CommandLineToArgvW(GetCommandLineW(), &argc));
if (!argv || argc <= 0)
{
progress.ModalError("Failed to parse command line.");
return 1;
}
if (argc != 5)
{
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 main PCSX2 application and "
"click Help->Check for Updates.");
return 1;
}
const int parent_process_id = StringUtil::FromChars<int>(StringUtil::WideStringToUTF8String(argv[1])).value_or(0);
const std::string destination_directory = StringUtil::WideStringToUTF8String(argv[2]);
const std::string zip_path = StringUtil::WideStringToUTF8String(argv[3]);
const std::wstring program_to_launch(argv[4]);
argv.reset();
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);
Updater updater(&progress);
progress.SetFormattedStatusText("Waiting for parent process %d to exit...", parent_process_id);
progress.SetProgressState(ProgressCallback::ProgressState::Indeterminate);
WaitForProcessToExit(parent_process_id);
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 pcsx2.net.");
return 1;
}
updater.CleanupStagingDirectory();
updater.RemoveUpdateZip();
// Rename the new executable to match the existing one
if (std::string actual_exe = updater.FindPCSX2Exe(); !actual_exe.empty())
{
const std::string full_path = destination_directory + FS_OSPATH_SEPARATOR_STR + actual_exe;
progress.DisplayFormattedInformation("Moving '%s' to '%S'", full_path.c_str(), program_to_launch.c_str());
const bool ok = MoveFileExW(FileSystem::GetWin32Path(full_path).c_str(),
FileSystem::GetWin32Path(StringUtil::WideStringToUTF8String(program_to_launch)).c_str(),
MOVEFILE_REPLACE_EXISTING);
if (!ok)
{
progress.DisplayFormattedModalError("Failed to rename '%s' to %S", full_path.c_str(), program_to_launch.c_str());
return 1;
}
}
else
{
progress.ModalError("Couldn't find PCSX2 in update package, please re-download a fresh version from GitHub.");
return 1;
}
progress.DisplayFormattedInformation("Launching '%s'...",
StringUtil::WideStringToUTF8String(program_to_launch).c_str());
ShellExecuteW(nullptr, L"open", program_to_launch.c_str(), L"-updatecleanup", nullptr, SW_SHOWNORMAL);
return 0;
}

View File

@@ -0,0 +1,16 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by updater.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: 30 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="net.pcsx2.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

75
updater/updater.vcxproj Normal file
View File

@@ -0,0 +1,75 @@
<?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 Condition="!$(Configuration.Contains(Clang))">$(DefaultPlatformToolset)</PlatformToolset>
<PlatformToolset Condition="$(Configuration.Contains(Clang))">ClangCL</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 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\fmt\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\lzma\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\fast_float\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\winwil\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</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" />
<ClCompile Include="$(SolutionDir)pcsx2-qt\VCRuntimeChecker.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,38 @@
<?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">
<ClCompile Include="$(SolutionDir)pcsx2-qt\VCRuntimeChecker.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>