diff --git a/CMakeLists.txt b/CMakeLists.txt index cffa6c7e76..b071261467 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,7 @@ find_package(ZLIB REQUIRED) if(APPLE) find_package(Security) + find_package(libarchive REQUIRED) elseif(UNIX) find_package(LIBSECRET) find_package(Uuid REQUIRED) diff --git a/Sources/Plasma/Apps/plClient/CMakeLists.txt b/Sources/Plasma/Apps/plClient/CMakeLists.txt index 21be8e81cf..804c597f42 100644 --- a/Sources/Plasma/Apps/plClient/CMakeLists.txt +++ b/Sources/Plasma/Apps/plClient/CMakeLists.txt @@ -242,6 +242,7 @@ target_link_libraries( CURL::libcurl "$<$:-framework Cocoa>" "$<$:-framework QuartzCore>" + "$<$:archive>" ) target_include_directories(plClient PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.h b/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.h index 43fa529572..08cf3d6770 100644 --- a/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.h +++ b/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.h @@ -50,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)patcher:(PLSPatcher*)patcher beganDownloadOfFile:(NSString*)file; - (void)patcher:(PLSPatcher*)patcher updatedProgress:(NSString*)progressMessage withBytes:(NSUInteger)bytes outOf:(uint64_t)totalBytes; -- (void)patcherCompleted:(PLSPatcher*)patcher; +- (void)patcherCompleted:(PLSPatcher*)patcher didSelfPatch:(BOOL)selfPatched; - (void)patcherCompletedWithError:(PLSPatcher*)patcher error:(NSError*)error; @end @@ -60,6 +60,7 @@ NS_ASSUME_NONNULL_BEGIN @property(weak) id delegate; @property(readonly) BOOL selfPatched; +- (NSURL *)completeSelfPatch; - (void)start; @end diff --git a/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.mm b/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.mm index 552df1776e..7416bf42eb 100644 --- a/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.mm +++ b/Sources/Plasma/Apps/plClient/Mac-Cocoa/PLSPatcher.mm @@ -43,6 +43,8 @@ #import "PLSPatcher.h" #import "NSString+StringTheory.h" +#include +#include #include #include @@ -61,6 +63,7 @@ void IOnPatchComplete(ENetError result, const ST::string& msg); void IOnProgressTick(uint64_t curBytes, uint64_t totalBytes, const ST::string& status); void IOnDownloadBegin(const plFileName& file); + void ISelfPatch(const plFileName& file); }; @interface PLSPatcher () @@ -68,6 +71,7 @@ @interface PLSPatcher () @property pfPatcher* patcher; @property NSTimer* networkPumpTimer; @property Patcher cppPatcher; +@property NSURL* updatedClientURL; @end @implementation PLSPatcher @@ -88,6 +92,7 @@ - (id)init _patcher->OnCompletion(std::bind(&Patcher::IOnPatchComplete, _cppPatcher, std::placeholders::_1, std::placeholders::_2)); _patcher->OnFileDownloadDesired(IApproveDownload); + _patcher->OnSelfPatch(std::bind(&Patcher::ISelfPatch, _cppPatcher, std::placeholders::_1)); self.networkPumpTimer = [NSTimer timerWithTimeInterval:1.0 / 1000.0 repeats:true @@ -106,6 +111,35 @@ - (void)start self.patcher->Start(); } +- (NSURL *)completeSelfPatch +{ + NSString* destinationPath = [NSString stringWithSTString:plManifest::PatcherExecutable().AsString()]; + NSURL *destinationURL = [NSURL fileURLWithPath:[NSString stringWithSTString:plManifest::PatcherExecutable().AsString()]]; + + if ([NSFileManager.defaultManager fileExistsAtPath:destinationPath]) { + // need to swap + + char originalPath[PATH_MAX] = {0}; + [self.updatedClientURL.path getFileSystemRepresentation:originalPath maxLength:sizeof(originalPath)]; + + char newPath[PATH_MAX] = {0}; + [destinationURL.path getFileSystemRepresentation:newPath maxLength:sizeof(newPath)]; + + renamex_np(newPath, originalPath, RENAME_SWAP); + + // delete the old version - this is very likely us + // we want to terminate after. Our bundle will no longer be valid. + [NSFileManager.defaultManager removeItemAtURL:self.updatedClientURL error:nil]; + return destinationURL; + } else { + // no executable already present! Just move things into place. + [NSFileManager.defaultManager moveItemAtURL:self.updatedClientURL toURL:destinationURL error:nil]; + return destinationURL; + } + + return nil; +} + void Patcher::IOnDownloadBegin(const plFileName& file) { NSString* fileName = [NSString stringWithSTString:file.AsString()]; @@ -139,13 +173,103 @@ bool IApproveDownload(const plFileName& file) return extExcludeList.find(file.GetFileExt()) == extExcludeList.end(); } +static la_ssize_t copy_data(struct archive *ar, struct archive *aw) +{ + la_ssize_t r; + const void *buff; + size_t size; + la_int64_t offset; + + for (;;) { + r = archive_read_data_block(ar, &buff, &size, &offset); + if (r == ARCHIVE_EOF) + return (ARCHIVE_OK); + if (r < ARCHIVE_OK) + return (r); + r = archive_write_data_block(aw, buff, size, offset); + if (r < ARCHIVE_OK) { + fprintf(stderr, "%s\n", archive_error_string(aw)); + return (r); + } + } +} + +void Patcher::ISelfPatch(const plFileName& file) +{ + struct archive *a; + struct archive *ext; + struct archive_entry *entry; + int flags; + la_ssize_t r; + + /* Select which attributes we want to restore. */ + flags = ARCHIVE_EXTRACT_TIME; + flags |= ARCHIVE_EXTRACT_PERM; + flags |= ARCHIVE_EXTRACT_ACL; + flags |= ARCHIVE_EXTRACT_FFLAGS; + + a = archive_read_new(); + archive_read_support_format_all(a); + archive_read_support_filter_all(a); + ext = archive_write_disk_new(); + archive_write_disk_set_options(ext, flags); + archive_write_disk_set_standard_lookup(ext); + if ((r = archive_read_open_filename(a, file.GetFileName().c_str(), 10240))) + exit(1); + + plFileSystem::Unlink(plManifest::PatcherExecutable()); + + NSURL *tempDirectory = [NSFileManager.defaultManager URLForDirectory:NSItemReplacementDirectory inDomain:NSUserDomainMask appropriateForURL:[NSURL fileURLWithPath:NSFileManager.defaultManager.currentDirectoryPath] create:YES error:nil]; + NSURL *outputURL = [tempDirectory URLByAppendingPathComponent:[NSString stringWithSTString:plManifest::PatcherExecutable().GetFileName()]]; + [NSFileManager.defaultManager createDirectoryAtURL:outputURL withIntermediateDirectories:false attributes:nil error:nil]; + ST::string outputPath = [outputURL.path STString]; + + for (;;) { + r = archive_read_next_header(a, &entry); + if (r == ARCHIVE_EOF) + break; + if (r < ARCHIVE_OK) + fprintf(stderr, "%s\n", archive_error_string(a)); + if (r < ARCHIVE_WARN) + exit(1); + const char* currentFile = archive_entry_pathname(entry); + auto fullOutputPath = outputPath + "/" + currentFile; + archive_entry_set_pathname(entry, fullOutputPath.c_str()); + r = archive_write_header(ext, entry); + if (r < ARCHIVE_OK) + fprintf(stderr, "%s\n", archive_error_string(ext)); + else if (archive_entry_size(entry) > 0) { + r = copy_data(a, ext); + if (r < ARCHIVE_OK) + fprintf(stderr, "%s\n", archive_error_string(ext)); + if (r < ARCHIVE_WARN) + exit(1); + } + r = archive_write_finish_entry(ext); + if (r < ARCHIVE_OK) + fprintf(stderr, "%s\n", archive_error_string(ext)); + if (r < ARCHIVE_WARN) + exit(1); + } + archive_read_close(a); + archive_read_free(a); + archive_write_close(ext); + archive_write_free(ext); + + plFileSystem::Unlink(file); + + PLSPatcher* patcher = parent; + parent.updatedClientURL = outputURL; +} + void Patcher::IOnPatchComplete(ENetError result, const ST::string& msg) { [parent.networkPumpTimer invalidate]; if (IS_NET_SUCCESS(result)) { PLSPatcher* patcher = parent; dispatch_async(dispatch_get_main_queue(), ^{ - [patcher.delegate patcherCompleted:patcher]; + [patcher.delegate patcherCompleted:patcher + didSelfPatch:(patcher.updatedClientURL != nil)]; }); } else { NSString* msgString = [NSString stringWithSTString:msg]; diff --git a/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm b/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm index 0101728f20..347c559eca 100644 --- a/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm +++ b/Sources/Plasma/Apps/plClient/Mac-Cocoa/main.mm @@ -152,7 +152,8 @@ @interface AppDelegate : NSWindowController