1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

rpcs3qt: Add macOS support to the updater.

This commit is contained in:
Steveice10 2024-01-22 23:36:49 -08:00 committed by Megamouse
parent 5fe36872c5
commit 3ef48cbdd5
7 changed files with 152 additions and 24 deletions

View File

@ -1,5 +1,5 @@
# 7z sdk
if(WIN32)
if(WIN32 OR APPLE)
add_library(3rdparty_7z STATIC EXCLUDE_FROM_ALL
src/7zAlloc.c
src/7zArcIn.c

View File

@ -357,6 +357,12 @@ namespace fs
return false;
}
bool device_base::create_symlink(const std::string&)
{
g_tls_error = error::readonly;
return false;
}
bool device_base::rename(const std::string&, const std::string&)
{
g_tls_error = error::readonly;
@ -581,6 +587,7 @@ bool fs::get_stat(const std::string& path, stat_t& info)
}
info.is_directory = (attrs.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
info.is_symlink = (attrs.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
info.is_writable = (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0;
info.size = attrs.nFileSizeLow | (u64{attrs.nFileSizeHigh} << 32);
info.atime = to_time(attrs.ftLastAccessTime);
@ -595,6 +602,7 @@ bool fs::get_stat(const std::string& path, stat_t& info)
}
info.is_directory = S_ISDIR(file_info.st_mode);
info.is_symlink = S_ISLNK(file_info.st_mode);
info.is_writable = file_info.st_mode & 0200; // HACK: approximation
info.size = file_info.st_size;
info.atime = file_info.st_atime;
@ -648,6 +656,23 @@ bool fs::is_dir(const std::string& path)
return true;
}
bool fs::is_symlink(const std::string& path)
{
fs::stat_t info{};
if (!fs::get_stat(path, info))
{
return false;
}
if (!info.is_symlink)
{
g_tls_error = error::exist;
return false;
}
return true;
}
bool fs::statfs(const std::string& path, fs::device_stat& info)
{
if (auto device = get_virtual_device(path))
@ -794,6 +819,33 @@ bool fs::remove_dir(const std::string& path)
return true;
}
bool fs::create_symlink(const std::string& path, const std::string& target)
{
if (auto device = get_virtual_device(path))
{
return device->create_symlink(path);
}
#ifdef _WIN32
const DWORD flags = is_dir(target) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0;
if (!CreateSymbolicLinkW(to_wchar(path).get(), to_wchar(target).get(), flags))
{
g_tls_error = to_error(GetLastError());
return false;
}
return true;
#else
if (::symlink(target.c_str(), path.c_str()) != 0)
{
g_tls_error = to_error(errno);
return false;
}
return true;
#endif
}
bool fs::rename(const std::string& from, const std::string& to, bool overwrite)
{
if (from.empty() || to.empty())
@ -2105,8 +2157,21 @@ const std::string& fs::get_temp_dir()
dir = wchar_to_utf8(buf);
#else
// TODO
const char* tmp_dir = getenv("TMPDIR");
if (tmp_dir == nullptr || tmp_dir[0] == '\0')
{
// Fall back to cache directory
dir = get_cache_dir();
}
else
{
dir = tmp_dir;
if (!dir.ends_with("/"))
{
// Ensure path ends with a separator
dir += "/";
}
}
#endif
return dir;

View File

@ -66,6 +66,7 @@ namespace fs
struct stat_t
{
bool is_directory;
bool is_symlink;
bool is_writable;
u64 size;
s64 atime;
@ -155,6 +156,7 @@ namespace fs
virtual bool statfs(const std::string& path, device_stat& info) = 0;
virtual bool remove_dir(const std::string& path);
virtual bool create_dir(const std::string& path);
virtual bool create_symlink(const std::string& path);
virtual bool rename(const std::string& from, const std::string& to);
virtual bool remove(const std::string& path);
virtual bool trunc(const std::string& path, u64 length);
@ -197,6 +199,9 @@ namespace fs
// Check whether the directory exists and is NOT a file
bool is_dir(const std::string& path);
// Check whether the path points to an existing symlink
bool is_symlink(const std::string& path);
// Get filesystem information
bool statfs(const std::string& path, device_stat& info);
@ -209,6 +214,9 @@ namespace fs
// Create directories
bool create_path(const std::string& path);
// Create symbolic link
bool create_symlink(const std::string& path, const std::string& target);
// Rename (move) file or directory
bool rename(const std::string& from, const std::string& to, bool overwrite);

View File

@ -45,7 +45,8 @@ if(WIN32)
target_compile_definitions(rpcs3 PRIVATE UNICODE _UNICODE)
elseif(APPLE)
add_executable(rpcs3 MACOSX_BUNDLE)
target_sources(rpcs3 PRIVATE rpcs3.icns)
target_sources(rpcs3 PRIVATE rpcs3.icns update_helper.sh)
set_source_files_properties(update_helper.sh PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set_target_properties(rpcs3
PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/rpcs3.plist.in")

View File

@ -241,7 +241,7 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot)
}
});
#if defined(_WIN32) || defined(__linux__)
#if defined(_WIN32) || defined(__linux__) || defined(__APPLE__)
if (const auto update_value = m_gui_settings->GetValue(gui::m_check_upd_start).toString(); update_value != gui::update_off)
{
const bool in_background = with_cli_boot || update_value == gui::update_bkg;
@ -3028,7 +3028,7 @@ void main_window::CreateConnects()
connect(ui->updateAct, &QAction::triggered, this, [this]()
{
#if !defined(_WIN32) && !defined(__linux__)
#if !defined(_WIN32) && !defined(__linux__) && !defined(__APPLE__)
QMessageBox::warning(this, tr("Auto-updater"), tr("The auto-updater isn't available for your OS currently."));
return;
#endif

View File

@ -20,16 +20,19 @@
#include <QJsonDocument>
#include <QThread>
#if defined(_WIN32) || defined(__APPLE__)
#include <7z.h>
#include <7zAlloc.h>
#include <7zCrc.h>
#include <7zFile.h>
#endif
#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <CpuArch.h>
#include <7z.h>
#include <7zAlloc.h>
#include <7zCrc.h>
#include <7zFile.h>
#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
@ -143,6 +146,8 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce
os = "windows";
#elif defined(__linux__)
os = "linux";
#elif defined(__APPLE__)
os = "mac";
#else
update_log.error("Your OS isn't currently supported by the auto-updater");
return false;
@ -370,13 +375,6 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
{
m_downloader->update_progress_dialog(tr("Updating RPCS3"));
#ifdef __APPLE__
Q_UNUSED(data);
Q_UNUSED(auto_accept);
update_log.error("Unsupported operating system.");
return false;
#else
if (m_expected_size != static_cast<u64>(data.size()))
{
update_log.error("Download size mismatch: %d expected: %d", data.size(), m_expected_size);
@ -390,13 +388,17 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
return false;
}
#ifdef _WIN32
#if defined(_WIN32) || defined(__APPLE__)
// Get executable path
const std::string exe_dir = fs::get_executable_dir();
const std::string orig_path = exe_dir + "rpcs3.exe";
const std::string orig_path = fs::get_executable_path();
#ifdef _WIN32
const std::wstring wchar_orig_path = utf8_to_wchar(orig_path);
const std::string tmpfile_path = fs::get_temp_dir() + "\\rpcs3_update.7z";
#else
const std::string tmpfile_path = fs::get_temp_dir() + "rpcs3_update.7z";
#endif
fs::file tmpfile(tmpfile_path, fs::read + fs::write + fs::create + fs::trunc);
if (!tmpfile)
@ -482,8 +484,13 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
Byte* outBuffer = nullptr;
usz outBufferSize = 0;
// Creates temp folder for moving active files
#ifdef _WIN32
// Create temp folder for moving active files
const std::string tmp_folder = exe_dir + "rpcs3_old/";
#else
// Create temp folder for extracting the new app
const std::string tmp_folder = fs::get_temp_dir() + "rpcs3_new/";
#endif
fs::create_dir(tmp_folder);
for (UInt32 i = 0; i < db.NumFiles; i++)
@ -491,6 +498,16 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
usz offset = 0;
usz outSizeProcessed = 0;
const bool isDir = SzArEx_IsDir(&db, i);
const DWORD attribs = SzBitWithVals_Check(&db.Attribs, i) ? db.Attribs.Vals[i] : 0;
#ifdef _WIN32
// This is commented out for now as we shouldn't need it and symlinks
// aren't well supported on Windows. Left in case it is needed in the future.
// const bool is_symlink = (attribs & FILE_ATTRIBUTE_REPARSE_POINT) != 0;
const bool is_symlink = false;
#else
const DWORD permissions = (attribs >> 16) & (S_IRWXU | S_IRWXG | S_IRWXO);
const bool is_symlink = (attribs & FILE_ATTRIBUTE_UNIX_EXTENSION) != 0 && S_ISLNK(attribs >> 16);
#endif
const usz len = SzArEx_GetFileNameUtf16(&db, i, nullptr);
if (len >= PATH_MAX)
@ -515,7 +532,12 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
temp_u8[index] = static_cast<u8>(temp_u16[index]);
}
temp_u8[len] = 0;
const std::string name = exe_dir + std::string(reinterpret_cast<char*>(temp_u8));
const std::string archived_name = std::string(reinterpret_cast<char*>(temp_u8));
#ifdef __APPLE__
const std::string name = tmp_folder + archived_name;
#else
const std::string name = exe_dir + archived_name;
#endif
if (!isDir)
{
@ -537,6 +559,14 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
continue;
}
if (is_symlink)
{
const std::string link_target(reinterpret_cast<const char*>(outBuffer + offset), outSizeProcessed);
update_log.trace("Creating symbolic link: %s -> %s", name, link_target);
fs::create_symlink(name, link_target);
continue;
}
fs::file outfile(name, fs::read + fs::write + fs::create + fs::trunc);
if (!outfile)
{
@ -575,6 +605,11 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
break;
}
outfile.close();
#ifndef _WIN32
// Apply correct file permissions.
chmod(name.c_str(), permissions);
#endif
}
error_free7z();
@ -644,6 +679,12 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
#ifdef _WIN32
const int ret = _wexecl(wchar_orig_path.data(), wchar_orig_path.data(), L"--updating", nullptr);
#elif defined(__APPLE__)
// Execute helper script to replace the app and relaunch
const std::string helper_script = fmt::format("%s/Contents/Resources/update_helper.sh", orig_path);
const std::string extracted_app = fmt::format("%s/RPCS3.app", tmp_folder);
update_log.notice("Executing update helper script: '%s %s %s'", helper_script, extracted_app, orig_path);
const int ret = execl(helper_script.c_str(), helper_script.c_str(), extracted_app.c_str(), orig_path.c_str(), nullptr);
#else
// execv is used for compatibility with checkrt
const char * const params[3] = { replace_path.c_str(), "--updating", nullptr };
@ -656,5 +697,4 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
}
return true;
#endif //def __APPLE__
}

14
rpcs3/update_helper.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/sh
# This script copies the new app over the old app and launches it.
# This is required since invalidating the code signing of an app by
# replacing it while it is running can result in the app being killed.
if [ "$#" -ne 2 ]; then
echo "Usage: update_helper.sh <new_app> <old_app>"
exit 1
fi
new_app="$1/"
old_app="$2/"
cp -Rf -p "$new_app" "$old_app"
open -n -a "$2" --args --updating