mirror of https://github.com/PCSX2/pcsx2.git
Qt: Add update extractor source
This commit is contained in:
parent
a3f6efecb8
commit
a289723f66
|
@ -42,6 +42,7 @@ add_subdirectory(pcsx2)
|
|||
|
||||
if (QT_BUILD)
|
||||
add_subdirectory(pcsx2-qt)
|
||||
add_subdirectory(updater)
|
||||
endif()
|
||||
|
||||
# tests
|
||||
|
|
14
PCSX2_qt.sln
14
PCSX2_qt.sln
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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
|
||||
}
|
|
@ -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());
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
|
@ -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>
|
|
@ -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
|
||||
|
|
@ -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>
|
|
@ -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>
|
Loading…
Reference in New Issue