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 # 7z sdk
if(WIN32) if(WIN32 OR APPLE)
add_library(3rdparty_7z STATIC EXCLUDE_FROM_ALL add_library(3rdparty_7z STATIC EXCLUDE_FROM_ALL
src/7zAlloc.c src/7zAlloc.c
src/7zArcIn.c src/7zArcIn.c

View File

@ -357,6 +357,12 @@ namespace fs
return false; 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&) bool device_base::rename(const std::string&, const std::string&)
{ {
g_tls_error = error::readonly; 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_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.is_writable = (attrs.dwFileAttributes & FILE_ATTRIBUTE_READONLY) == 0;
info.size = attrs.nFileSizeLow | (u64{attrs.nFileSizeHigh} << 32); info.size = attrs.nFileSizeLow | (u64{attrs.nFileSizeHigh} << 32);
info.atime = to_time(attrs.ftLastAccessTime); 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_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.is_writable = file_info.st_mode & 0200; // HACK: approximation
info.size = file_info.st_size; info.size = file_info.st_size;
info.atime = file_info.st_atime; info.atime = file_info.st_atime;
@ -648,6 +656,23 @@ bool fs::is_dir(const std::string& path)
return true; 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) bool fs::statfs(const std::string& path, fs::device_stat& info)
{ {
if (auto device = get_virtual_device(path)) if (auto device = get_virtual_device(path))
@ -794,6 +819,33 @@ bool fs::remove_dir(const std::string& path)
return true; 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) bool fs::rename(const std::string& from, const std::string& to, bool overwrite)
{ {
if (from.empty() || to.empty()) if (from.empty() || to.empty())
@ -2105,8 +2157,21 @@ const std::string& fs::get_temp_dir()
dir = wchar_to_utf8(buf); dir = wchar_to_utf8(buf);
#else #else
// TODO const char* tmp_dir = getenv("TMPDIR");
dir = get_cache_dir(); 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 #endif
return dir; return dir;

View File

@ -66,6 +66,7 @@ namespace fs
struct stat_t struct stat_t
{ {
bool is_directory; bool is_directory;
bool is_symlink;
bool is_writable; bool is_writable;
u64 size; u64 size;
s64 atime; s64 atime;
@ -155,6 +156,7 @@ namespace fs
virtual bool statfs(const std::string& path, device_stat& info) = 0; virtual bool statfs(const std::string& path, device_stat& info) = 0;
virtual bool remove_dir(const std::string& path); virtual bool remove_dir(const std::string& path);
virtual bool create_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 rename(const std::string& from, const std::string& to);
virtual bool remove(const std::string& path); virtual bool remove(const std::string& path);
virtual bool trunc(const std::string& path, u64 length); 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 // Check whether the directory exists and is NOT a file
bool is_dir(const std::string& path); 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 // Get filesystem information
bool statfs(const std::string& path, device_stat& info); bool statfs(const std::string& path, device_stat& info);
@ -209,6 +214,9 @@ namespace fs
// Create directories // Create directories
bool create_path(const std::string& path); 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 // Rename (move) file or directory
bool rename(const std::string& from, const std::string& to, bool overwrite); 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) target_compile_definitions(rpcs3 PRIVATE UNICODE _UNICODE)
elseif(APPLE) elseif(APPLE)
add_executable(rpcs3 MACOSX_BUNDLE) 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 set_target_properties(rpcs3
PROPERTIES PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/rpcs3.plist.in") 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) 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; 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]() 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.")); QMessageBox::warning(this, tr("Auto-updater"), tr("The auto-updater isn't available for your OS currently."));
return; return;
#endif #endif

View File

@ -20,16 +20,19 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QThread> #include <QThread>
#if defined(_WIN32) || defined(__APPLE__)
#include <7z.h>
#include <7zAlloc.h>
#include <7zCrc.h>
#include <7zFile.h>
#endif
#if defined(_WIN32) #if defined(_WIN32)
#ifndef NOMINMAX #ifndef NOMINMAX
#define NOMINMAX #define NOMINMAX
#endif #endif
#include <windows.h> #include <windows.h>
#include <CpuArch.h> #include <CpuArch.h>
#include <7z.h>
#include <7zAlloc.h>
#include <7zCrc.h>
#include <7zFile.h>
#ifndef PATH_MAX #ifndef PATH_MAX
#define PATH_MAX MAX_PATH #define PATH_MAX MAX_PATH
@ -143,6 +146,8 @@ bool update_manager::handle_json(bool automatic, bool check_only, bool auto_acce
os = "windows"; os = "windows";
#elif defined(__linux__) #elif defined(__linux__)
os = "linux"; os = "linux";
#elif defined(__APPLE__)
os = "mac";
#else #else
update_log.error("Your OS isn't currently supported by the auto-updater"); update_log.error("Your OS isn't currently supported by the auto-updater");
return false; 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")); 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())) if (m_expected_size != static_cast<u64>(data.size()))
{ {
update_log.error("Download size mismatch: %d expected: %d", data.size(), m_expected_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; return false;
} }
#ifdef _WIN32 #if defined(_WIN32) || defined(__APPLE__)
// Get executable path // Get executable path
const std::string exe_dir = fs::get_executable_dir(); 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::wstring wchar_orig_path = utf8_to_wchar(orig_path);
const std::string tmpfile_path = fs::get_temp_dir() + "\\rpcs3_update.7z"; 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); fs::file tmpfile(tmpfile_path, fs::read + fs::write + fs::create + fs::trunc);
if (!tmpfile) if (!tmpfile)
@ -482,16 +484,31 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
Byte* outBuffer = nullptr; Byte* outBuffer = nullptr;
usz outBufferSize = 0; 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/"; 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); fs::create_dir(tmp_folder);
for (UInt32 i = 0; i < db.NumFiles; i++) for (UInt32 i = 0; i < db.NumFiles; i++)
{ {
usz offset = 0; usz offset = 0;
usz outSizeProcessed = 0; usz outSizeProcessed = 0;
const bool isDir = SzArEx_IsDir(&db, i); const bool isDir = SzArEx_IsDir(&db, i);
const usz len = SzArEx_GetFileNameUtf16(&db, i, nullptr); 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) 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[index] = static_cast<u8>(temp_u16[index]);
} }
temp_u8[len] = 0; 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) if (!isDir)
{ {
@ -537,6 +559,14 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
continue; 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); fs::file outfile(name, fs::read + fs::write + fs::create + fs::trunc);
if (!outfile) if (!outfile)
{ {
@ -575,6 +605,11 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
break; break;
} }
outfile.close(); outfile.close();
#ifndef _WIN32
// Apply correct file permissions.
chmod(name.c_str(), permissions);
#endif
} }
error_free7z(); error_free7z();
@ -644,6 +679,12 @@ bool update_manager::handle_rpcs3(const QByteArray& data, bool auto_accept)
#ifdef _WIN32 #ifdef _WIN32
const int ret = _wexecl(wchar_orig_path.data(), wchar_orig_path.data(), L"--updating", nullptr); 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 #else
// execv is used for compatibility with checkrt // execv is used for compatibility with checkrt
const char * const params[3] = { replace_path.c_str(), "--updating", nullptr }; 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; 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