From 726db94fbca382e950123389fe7e8977d792cfa2 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 15 Nov 2024 00:34:00 +0800 Subject: [PATCH 01/32] zsign POC --- .gitignore | 3 +- LiveContainerSwiftUI/LCAppBanner.swift | 9 +- LiveContainerSwiftUI/LCAppListView.swift | 18 +- LiveContainerSwiftUI/LCAppModel.swift | 2 + LiveContainerSwiftUI/LCAppSettingsView.swift | 16 + LiveContainerSwiftUI/LCSettingsView.swift | 15 + LiveContainerSwiftUI/Localizable.xcstrings | 4 +- LiveContainerUI/LCAppInfo.h | 4 +- LiveContainerUI/LCAppInfo.m | 38 +- LiveContainerUI/LCUtils.h | 7 +- LiveContainerUI/LCUtils.m | 32 + Makefile | 5 +- download_openssl.sh | 22 + zsign/Makefile | 14 + zsign/Utils.hpp | 26 + zsign/Utils.mm | 24 + zsign/archo.cpp | 860 +++++ zsign/archo.h | 42 + zsign/bundle.cpp | 710 ++++ zsign/bundle.h | 42 + zsign/common/base64.cpp | 203 ++ zsign/common/base64.h | 27 + zsign/common/common.cpp | 848 +++++ zsign/common/common.h | 163 + zsign/common/json.cpp | 3061 ++++++++++++++++++ zsign/common/json.h | 414 +++ zsign/common/mach-o.h | 580 ++++ zsign/macho.cpp | 350 ++ zsign/macho.h | 34 + zsign/openssl.cpp | 944 ++++++ zsign/openssl.h | 28 + zsign/signing.cpp | 875 +++++ zsign/signing.h | 34 + zsign/zsign.hpp | 44 + zsign/zsign.mm | 232 ++ zsign/zsigner.h | 11 + zsign/zsigner.m | 22 + 37 files changed, 9749 insertions(+), 14 deletions(-) create mode 100644 download_openssl.sh create mode 100644 zsign/Makefile create mode 100644 zsign/Utils.hpp create mode 100644 zsign/Utils.mm create mode 100644 zsign/archo.cpp create mode 100644 zsign/archo.h create mode 100644 zsign/bundle.cpp create mode 100644 zsign/bundle.h create mode 100644 zsign/common/base64.cpp create mode 100644 zsign/common/base64.h create mode 100644 zsign/common/common.cpp create mode 100644 zsign/common/common.h create mode 100644 zsign/common/json.cpp create mode 100644 zsign/common/json.h create mode 100644 zsign/common/mach-o.h create mode 100644 zsign/macho.cpp create mode 100644 zsign/macho.h create mode 100644 zsign/openssl.cpp create mode 100644 zsign/openssl.h create mode 100644 zsign/signing.cpp create mode 100644 zsign/signing.h create mode 100644 zsign/zsign.hpp create mode 100644 zsign/zsign.mm create mode 100644 zsign/zsigner.h create mode 100644 zsign/zsigner.m diff --git a/.gitignore b/.gitignore index a0dbe6d..5b6a548 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ packages/ .DS_Store LiveContainer.xcodeproj project.xcworkspace -xcuserdata \ No newline at end of file +xcuserdata +Resources/Frameworks/OpenSSL.framework \ No newline at end of file diff --git a/LiveContainerSwiftUI/LCAppBanner.swift b/LiveContainerSwiftUI/LCAppBanner.swift index 9c086be..5682d5c 100644 --- a/LiveContainerSwiftUI/LCAppBanner.swift +++ b/LiveContainerSwiftUI/LCAppBanner.swift @@ -239,9 +239,12 @@ struct LCAppBanner : View { Text("lc.appBanner.waitForJitMsg".loc) } - .alert("lc.common.error".loc, isPresented: $errorShow) { + .alert("lc.common.error".loc, isPresented: $errorShow){ Button("lc.common.ok".loc, action: { }) + Button("lc.common.copy".loc, action: { + copyError() + }) } message: { Text(errorInfo) } @@ -386,6 +389,10 @@ struct LCAppBanner : View { return ans } + + func copyError() { + UIPasteboard.general.string = errorInfo + } } diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 555b9a7..7e7e9b0 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -194,8 +194,14 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } .navigationViewStyle(StackNavigationViewStyle()) - .alert(isPresented: $errorShow){ - Alert(title: Text("lc.common.error".loc), message: Text(errorInfo)) + .alert("lc.common.error".loc, isPresented: $errorShow){ + Button("lc.common.ok".loc, action: { + }) + Button("lc.common.copy".loc, action: { + copyError() + }) + } message: { + Text(errorInfo) } .fileImporter(isPresented: $choosingIPA, allowedContentTypes: [.ipa]) { result in Task { await startInstallApp(result) } @@ -400,11 +406,13 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { if sameBundleIdApp.count > 0 && !sharedModel.isHiddenAppUnlocked { do { if !(try await LCUtils.authenticateUser()) { + self.installprogressVisible = false return } } catch { errorInfo = error.localizedDescription errorShow = true + self.installprogressVisible = false return } } @@ -451,6 +459,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } var signError : String? = nil await withCheckedContinuation({ c in + finalNewApp.signer = Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))! finalNewApp.patchExecAndSignIfNeed(completionHandler: { error in signError = error c.resume() @@ -473,6 +482,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { finalNewApp.doSymlinkInbox = appToReplace.appInfo.doSymlinkInbox finalNewApp.setDataUUID(appToReplace.appInfo.getDataUUIDNoAssign()) finalNewApp.setTweakFolder(appToReplace.appInfo.tweakFolder()) + finalNewApp.signer = appToReplace.appInfo.signer } DispatchQueue.main.async { if let appToReplace { @@ -602,4 +612,8 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { isNavigationActive = false navigateTo = nil } + + func copyError() { + UIPasteboard.general.string = errorInfo + } } diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index b02a970..daa9e2d 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -22,6 +22,7 @@ class LCAppModel: ObservableObject, Hashable { @Published var uiTweakFolder : String? @Published var uiDoSymlinkInbox : Bool @Published var uiBypassAssertBarrierOnQueue : Bool + @Published var uiSigner : Signer var jitAlert : YesNoHelper? = nil @@ -43,6 +44,7 @@ class LCAppModel: ObservableObject, Hashable { self.uiTweakFolder = appInfo.tweakFolder() self.uiDoSymlinkInbox = appInfo.doSymlinkInbox self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue + self.uiSigner = appInfo.signer } static func == (lhs: LCAppModel, rhs: LCAppModel) -> Bool { diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index ab95643..67f5107 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -178,6 +178,16 @@ struct LCAppSettingsView : View{ } } + Picker(selection: $model.uiSigner) { + Text("ZSign").tag(Signer.ZSign) + Text("AltSigner").tag(Signer.AltSigner) + + } label: { + Text("Signer") + } + .onChange(of: model.uiSigner, perform: { newValue in + Task { await setSigner(newValue) } + }) Section { Toggle(isOn: $model.uiDoSymlinkInbox) { @@ -399,6 +409,12 @@ struct LCAppSettingsView : View{ } + func setSigner(_ signer: Signer) async { + appInfo.signer = signer + model.uiSigner = signer + + } + func setSimlinkInbox(_ simlinkInbox : Bool) async { appInfo.doSymlinkInbox = simlinkInbox model.uiDoSymlinkInbox = simlinkInbox diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index 8a3feeb..d46b7d1 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -26,6 +26,7 @@ struct LCSettingsView: View { @State private var isAltStorePatched = false @State var isJitLessEnabled = false + @State var defaultSigner = Signer.ZSign @State var isAltCertIgnored = false @State var frameShortIcon = false @@ -43,6 +44,8 @@ struct LCSettingsView: View { init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>) { _isJitLessEnabled = State(initialValue: LCUtils.certificatePassword() != nil) + _defaultSigner = State(initialValue: Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))!) + _isAltCertIgnored = State(initialValue: UserDefaults.standard.bool(forKey: "LCIgnoreALTCertificate")) _frameShortIcon = State(initialValue: UserDefaults.standard.bool(forKey: "LCFrameShortcutIcons")) _silentSwitchApp = State(initialValue: UserDefaults.standard.bool(forKey: "LCSwitchAppWithoutAsking")) @@ -95,6 +98,15 @@ struct LCSettingsView: View { } } } + + Picker(selection: $defaultSigner) { + Text("ZSign").tag(Signer.ZSign) + Text("AltSigner").tag(Signer.AltSigner) + + } label: { + Text("Default Signer") + } + } header: { Text("lc.settings.jitLess".loc) @@ -339,6 +351,9 @@ struct LCSettingsView: View { .onChange(of: sideJITServerAddress) { newValue in saveAppGroupItem(key: "LCSideJITServerAddress", val: newValue) } + .onChange(of: defaultSigner) { newValue in + saveAppGroupItem(key: "LCDefaultSigner", val: newValue.rawValue) + } } .navigationViewStyle(StackNavigationViewStyle()) diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 2788033..c0f8f10 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -660,13 +660,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Bundle Identifier" + "value" : "Bundle Folder" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "包名" + "value" : "包文件名" } } } diff --git a/LiveContainerUI/LCAppInfo.h b/LiveContainerUI/LCAppInfo.h index 52a697b..6a8e395 100644 --- a/LiveContainerUI/LCAppInfo.h +++ b/LiveContainerUI/LCAppInfo.h @@ -1,5 +1,6 @@ #import #import +#import "LCUtils.h" @interface LCAppInfo : NSObject { NSMutableDictionary* _info; @@ -13,6 +14,7 @@ @property bool doSymlinkInbox; @property bool bypassAssertBarrierOnQueue; @property UIColor* cachedColor; +@property Signer signer; - (void)setBundlePath:(NSString*)newBundlePath; - (NSMutableDictionary*)info; @@ -30,5 +32,5 @@ - (instancetype)initWithBundlePath:(NSString*)bundlePath; - (NSDictionary *)generateWebClipConfig; - (void)save; -- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* errorInfo))progressHandler forceSign:(BOOL)forceSign; +- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign; @end diff --git a/LiveContainerUI/LCAppInfo.m b/LiveContainerUI/LCAppInfo.m index 7a0b0bf..f0834ed 100644 --- a/LiveContainerUI/LCAppInfo.m +++ b/LiveContainerUI/LCAppInfo.m @@ -181,7 +181,7 @@ - (void)preprocessBundleBeforeSiging:(NSURL *)bundleURL completion:(dispatch_blo } // return "SignNeeded" if sign is needed, other wise return an error -- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* errorInfo))progressHandler forceSign:(BOOL)forceSign { +- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign { NSString *appPath = self.bundlePath; NSString *infoPath = [NSString stringWithFormat:@"%@/Info.plist", appPath]; NSMutableDictionary *info = _info; @@ -243,9 +243,8 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo info[@"CFBundleIdentifier"] = info[@"LCBundleIdentifier"]; [info removeObjectForKey:@"LCBundleExecutable"]; [info removeObjectForKey:@"LCBundleIdentifier"]; - - __block NSProgress *progress = [LCUtils signAppBundle:appPathURL - completionHandler:^(BOOL success, NSError *_Nullable error) { + + void (^signCompletionHandler)(BOOL success, NSError *error) = ^(BOOL success, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (!error) { info[@"LCJITLessSignID"] = @(signID); @@ -267,7 +266,25 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo } }); - }]; + }; + + __block NSProgress *progress; + + switch ([self signer]) { + case ZSign: + NSLog(@"[LC] using ZSign"); + progress = [LCUtils signAppBundleWithZSign:appPathURL execName:info[@"CFBundleExecutable"] completionHandler:signCompletionHandler]; + break; + case AltSigner: + NSLog(@"[LC] using AltSigner"); + progress = [LCUtils signAppBundle:appPathURL completionHandler:signCompletionHandler]; + break; + + default: + completetionHandler(@"Signer Not Found"); + break; + } + if (progress) { progressHandler(progress); } @@ -345,6 +362,17 @@ - (void)setBypassAssertBarrierOnQueue:(bool)enabled { } +- (Signer)signer { + return (Signer) [((NSNumber*) _info[@"signer"]) intValue]; + +} +- (void)setSigner:(Signer)newSigner { + _info[@"signer"] = [NSNumber numberWithInt:(int) newSigner]; + NSLog(@"[LC] new signer = %d", (int) newSigner); + [self save]; + +} + - (UIColor*)cachedColor { if(_info[@"cachedColor"] != nil) { NSData *colorData = _info[@"cachedColor"]; diff --git a/LiveContainerUI/LCUtils.h b/LiveContainerUI/LCUtils.h index ce019a0..2e7b7ab 100644 --- a/LiveContainerUI/LCUtils.h +++ b/LiveContainerUI/LCUtils.h @@ -7,6 +7,11 @@ typedef NS_ENUM(NSInteger, Store){ AltStore }; +typedef NS_ENUM(NSInteger, Signer){ + ZSign = 0, + AltSigner = 1 +}; + NSString *LCParseMachO(const char *path, LCParseMachOCallback callback); void LCPatchAddRPath(const char *path, struct mach_header_64 *header); void LCPatchExecSlice(const char *path, struct mach_header_64 *header); @@ -37,7 +42,7 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL; + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; - ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; + (BOOL)isAppGroupAltStoreLike; + (Store)store; + (NSString *)appGroupID; diff --git a/LiveContainerUI/LCUtils.m b/LiveContainerUI/LCUtils.m index 2a58607..387285a 100644 --- a/LiveContainerUI/LCUtils.m +++ b/LiveContainerUI/LCUtils.m @@ -5,6 +5,7 @@ #import "AltStoreCore/ALTSigner.h" #import "LCUtils.h" #import "LCVersionInfo.h" +#import "../zsign/zsigner.h" @implementation LCUtils @@ -134,6 +135,16 @@ + (void)loadStoreFrameworksWithError:(NSError **)error { loaded = YES; } ++ (void)loadStoreFrameworksWithError2:(NSError **)error { + // too lazy to use dispatch_once + static BOOL loaded = NO; + if (loaded) return; + + dlopen("@executable_path/Frameworks/ZSign.dylib", RTLD_GLOBAL); + + loaded = YES; +} + + (NSString *)storeBundleID { // Assuming this format never changes... // group.BUNDLEID.YOURTEAMID @@ -233,6 +244,27 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc return [signer signAppAtURL:path provisioningProfiles:@[(id)profile] completionHandler:completionHandler]; } ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { + NSError *error; + + // use zsign as our signer~ + NSURL *profilePath = [NSBundle.mainBundle URLForResource:@"embedded" withExtension:@"mobileprovision"]; + NSData *profileData = [NSData dataWithContentsOfURL:profilePath]; + // Load libraries from Documents, yeah + [self loadStoreFrameworksWithError2:&error]; + + if (error) { + completionHandler(NO, error); + return nil; + } + + NSLog(@"[LC] starting signing..."); + + NSProgress* ans = [NSClassFromString(@"ZSigner") signWithAppPath:[path path] execName:execName prov:profileData key: self.certificateData pass:self.certificatePassword completionHandler:completionHandler]; + + return ans; +} + #pragma mark Setup + (NSString *)appGroupID { diff --git a/Makefile b/Makefile index ebdb21b..5b6c0ed 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ $(APPLICATION_NAME)_FRAMEWORKS = UIKit include $(THEOS_MAKE_PATH)/application.mk -SUBPROJECTS += LiveContainerUI TweakLoader TestJITLess LiveContainerSwiftUI AltStoreTweak +SUBPROJECTS += ZSign LiveContainerUI TweakLoader TestJITLess LiveContainerSwiftUI AltStoreTweak include $(THEOS_MAKE_PATH)/aggregate.mk # Make the executable name longer so we have space to overwrite it with the guest app's name @@ -29,3 +29,6 @@ before-package:: @cp $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/JITLessSetup @ldid -Sentitlements_setup.xml $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/JITLessSetup @mv $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou + +before-all:: + @sh ./download_openssl.sh \ No newline at end of file diff --git a/download_openssl.sh b/download_openssl.sh new file mode 100644 index 0000000..0e02696 --- /dev/null +++ b/download_openssl.sh @@ -0,0 +1,22 @@ +#!/bin/bash +if [ -d "$THEOS/lib/OpenSSL.framework" ]; then + echo "OpenSSL.framework already exists in $THEOS/lib" +else + echo "OpenSSL.framework not found. Downloading..." + + curl -L "https://github.com/HAHALOSAH/OpenSSL-Swift/releases/download/3.1.5004/OpenSSL.xcframework.zip" -o "OpenSSL.xcframework.zip" + + unzip -q "OpenSSL.xcframework.zip" -d . + + mkdir -p "$THEOS/lib" + mv "OpenSSL.xcframework/ios-arm64/OpenSSL.framework" "$THEOS/lib" + + rm -f "OpenSSL.xcframework.zip" + rm -rf "OpenSSL.xcframework" + + echo "OpenSSL.framework has been installed to $THEOS/lib" +fi + +if [ ! -d "./Resources/Frameworks/OpenSSL.framework" ]; then + rsync -av --exclude 'Headers' "$THEOS/lib/OpenSSL.framework" "./Resources/Frameworks" +fi \ No newline at end of file diff --git a/zsign/Makefile b/zsign/Makefile new file mode 100644 index 0000000..605f331 --- /dev/null +++ b/zsign/Makefile @@ -0,0 +1,14 @@ +TARGET := iphone:clang:latest:7.0 +ARCHS := arm64 +include $(THEOS)/makefiles/common.mk + +LIBRARY_NAME = ZSign + +ZSign_FILES = $(shell find . -name '*.cpp') $(shell find . -name '*.mm') zsigner.m +ZSign_CFLAGS = -fobjc-arc -Wno-deprecated -Wno-unused-variable -Wno-unused-but-set-variable -Wno-module-import-in-extern-c +ZSign_CCFLAGS = -std=c++11 +ZSign_FRAMEWORKS = OpenSSL +ZSign_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks + +include $(THEOS_MAKE_PATH)/library.mk + diff --git a/zsign/Utils.hpp b/zsign/Utils.hpp new file mode 100644 index 0000000..cdd257e --- /dev/null +++ b/zsign/Utils.hpp @@ -0,0 +1,26 @@ +// +// Utils.hpp +// feather +// +// Created by samara on 30.09.2024. +// + +#ifndef Utils_hpp +#define Utils_hpp + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +const char* getDocumentsDirectory(); +void writeToNSLog(const char* msg); + + +#ifdef __cplusplus +} +#endif + +#endif /* zsign_hpp */ diff --git a/zsign/Utils.mm b/zsign/Utils.mm new file mode 100644 index 0000000..c657835 --- /dev/null +++ b/zsign/Utils.mm @@ -0,0 +1,24 @@ +// +// Utils.cpp +// feather +// +// Created by samara on 30.09.2024. +// + +#include "Utils.hpp" +#import + +extern "C" { + +const char* getDocumentsDirectory() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths firstObject]; + const char *documentsPath = [documentsDirectory UTF8String]; + return documentsPath; +} + +void writeToNSLog(const char* msg) { + NSLog(@"[LC] singner msg: %s", msg); +} + +} diff --git a/zsign/archo.cpp b/zsign/archo.cpp new file mode 100644 index 0000000..f3de7d4 --- /dev/null +++ b/zsign/archo.cpp @@ -0,0 +1,860 @@ +#include "common/common.h" +#include "common/json.h" +#include "archo.h" +#include "signing.h" + +static uint64_t execSegLimit = 0; + +ZArchO::ZArchO() +{ + m_pBase = NULL; + m_uLength = 0; + m_uCodeLength = 0; + m_pSignBase = NULL; + m_uSignLength = 0; + m_pHeader = NULL; + m_uHeaderSize = 0; + m_bEncrypted = false; + m_b64 = false; + m_bBigEndian = false; + m_bEnoughSpace = true; + m_pCodeSignSegment = NULL; + m_pLinkEditSegment = NULL; + m_uLoadCommandsFreeSpace = 0; +} + +bool ZArchO::Init(uint8_t *pBase, uint32_t uLength) +{ + if (NULL == pBase || uLength <= 0) + { + return false; + } + + m_pBase = pBase; + m_uLength = uLength; + m_uCodeLength = (uLength % 16 == 0) ? uLength : uLength + 16 - (uLength % 16); + m_pHeader = (mach_header *)m_pBase; + if (MH_MAGIC != m_pHeader->magic && MH_CIGAM != m_pHeader->magic && MH_MAGIC_64 != m_pHeader->magic && MH_CIGAM_64 != m_pHeader->magic) + { + return false; + } + + m_b64 = (MH_MAGIC_64 == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false; + m_bBigEndian = (MH_CIGAM == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false; + m_uHeaderSize = m_b64 ? sizeof(mach_header_64) : sizeof(mach_header); + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + switch (BO(plc->cmd)) + { + case LC_SEGMENT: + { + segment_command *seglc = (segment_command *)pLoadCommand; + if (0 == strcmp("__TEXT", seglc->segname)) + { + execSegLimit = seglc->vmsize; + for (uint32_t j = 0; j < BO(seglc->nsects); j++) + { + section *sect = (section *)((pLoadCommand + sizeof(segment_command)) + sizeof(section) * j); + if (0 == strcmp("__text", sect->sectname)) + { + if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize)) + { + m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize; + } + } + else if (0 == strcmp("__info_plist", sect->sectname)) + { + m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO(sect->size)); + } + } + } + else if (0 == strcmp("__LINKEDIT", seglc->segname)) + { + m_pLinkEditSegment = pLoadCommand; + } + } + break; + case LC_SEGMENT_64: + { + segment_command_64 *seglc = (segment_command_64 *)pLoadCommand; + if (0 == strcmp("__TEXT", seglc->segname)) + { + execSegLimit = seglc->vmsize; + for (uint32_t j = 0; j < BO(seglc->nsects); j++) + { + section_64 *sect = (section_64 *)((pLoadCommand + sizeof(segment_command_64)) + sizeof(section_64) * j); + if (0 == strcmp("__text", sect->sectname)) + { + if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize)) + { + m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize; + } + } + else if (0 == strcmp("__info_plist", sect->sectname)) + { + m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO((uint32_t)sect->size)); + } + } + } + else if (0 == strcmp("__LINKEDIT", seglc->segname)) + { + m_pLinkEditSegment = pLoadCommand; + } + } + break; + case LC_ENCRYPTION_INFO: + case LC_ENCRYPTION_INFO_64: + { + encryption_info_command *crypt_cmd = (encryption_info_command *)pLoadCommand; + if (BO(crypt_cmd->cryptid) >= 1) + { + m_bEncrypted = true; + } + } + break; + case LC_CODE_SIGNATURE: + { + codesignature_command *pcslc = (codesignature_command *)pLoadCommand; + m_pCodeSignSegment = pLoadCommand; + m_uCodeLength = BO(pcslc->dataoff); + m_pSignBase = m_pBase + m_uCodeLength; + m_uSignLength = GetCodeSignatureLength(m_pSignBase); + } + break; + } + + pLoadCommand += BO(plc->cmdsize); + } + + return true; +} + +const char *ZArchO::GetArch(int cpuType, int cpuSubType) +{ + switch (cpuType) + { + case CPU_TYPE_ARM: + { + switch (cpuSubType) + { + case CPU_SUBTYPE_ARM_V6: + return "armv6"; + break; + case CPU_SUBTYPE_ARM_V7: + return "armv7"; + break; + case CPU_SUBTYPE_ARM_V7S: + return "armv7s"; + break; + case CPU_SUBTYPE_ARM_V7K: + return "armv7k"; + break; + case CPU_SUBTYPE_ARM_V8: + return "armv8"; + break; + } + } + break; + case CPU_TYPE_ARM64: + { + switch (cpuSubType) + { + case CPU_SUBTYPE_ARM64_ALL: + return "arm64"; + break; + case CPU_SUBTYPE_ARM64_V8: + return "arm64v8"; + break; + case 2: + return "arm64e"; + break; + } + } + break; + case CPU_TYPE_ARM64_32: + { + switch (cpuSubType) + { + case CPU_SUBTYPE_ARM64_ALL: + return "arm64_32"; + break; + case CPU_SUBTYPE_ARM64_32_V8: + return "arm64e_32"; + break; + } + } + break; + case CPU_TYPE_X86: + { + switch (cpuSubType) + { + default: + return "x86_32"; + break; + } + } + break; + case CPU_TYPE_X86_64: + { + switch (cpuSubType) + { + default: + return "x86_64"; + break; + } + } + break; + } + return "unknown"; +} + +const char *ZArchO::GetFileType(uint32_t uFileType) +{ + switch (uFileType) + { + case MH_OBJECT: + return "MH_OBJECT"; + break; + case MH_EXECUTE: + return "MH_EXECUTE"; + break; + case MH_FVMLIB: + return "MH_FVMLIB"; + break; + case MH_CORE: + return "MH_CORE"; + break; + case MH_PRELOAD: + return "MH_PRELOAD"; + break; + case MH_DYLIB: + return "MH_DYLIB"; + break; + case MH_DYLINKER: + return "MH_DYLINKER"; + break; + case MH_BUNDLE: + return "MH_BUNDLE"; + break; + case MH_DYLIB_STUB: + return "MH_DYLIB_STUB"; + break; + case MH_DSYM: + return "MH_DSYM"; + break; + case MH_KEXT_BUNDLE: + return "MH_KEXT_BUNDLE"; + break; + } + return "MH_UNKNOWN"; +} + +uint32_t ZArchO::BO(uint32_t uValue) +{ + return m_bBigEndian ? LE(uValue) : uValue; +} + +bool ZArchO::IsExecute() +{ + if (NULL != m_pHeader) + { + return (MH_EXECUTE == BO(m_pHeader->filetype)); + } + return false; +} + +void ZArchO::PrintInfo() +{ + if (NULL == m_pHeader) + { + return; + } + + ZLog::Print("------------------------------------------------------------------\n"); + ZLog::Print(">>> MachO Info: \n"); + ZLog::PrintV("\tFileType: \t%s\n", GetFileType(BO(m_pHeader->filetype))); + ZLog::PrintV("\tTotalSize: \t%u (%s)\n", m_uLength, FormatSize(m_uLength).c_str()); + ZLog::PrintV("\tPlatform: \t%u\n", m_b64 ? 64 : 32); + ZLog::PrintV("\tCPUArch: \t%s\n", GetArch(BO(m_pHeader->cputype), BO(m_pHeader->cpusubtype))); + ZLog::PrintV("\tCPUType: \t0x%x\n", BO(m_pHeader->cputype)); + ZLog::PrintV("\tCPUSubType: \t0x%x\n", BO(m_pHeader->cpusubtype)); + ZLog::PrintV("\tBigEndian: \t%d\n", m_bBigEndian); + ZLog::PrintV("\tEncrypted: \t%d\n", m_bEncrypted); + ZLog::PrintV("\tCommandCount: \t%d\n", BO(m_pHeader->ncmds)); + ZLog::PrintV("\tCodeLength: \t%d (%s)\n", m_uCodeLength, FormatSize(m_uCodeLength).c_str()); + ZLog::PrintV("\tSignLength: \t%d (%s)\n", m_uSignLength, FormatSize(m_uSignLength).c_str()); + ZLog::PrintV("\tSpareLength: \t%d (%s)\n", m_uLength - m_uCodeLength - m_uSignLength, FormatSize(m_uLength - m_uCodeLength - m_uSignLength).c_str()); + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + if (LC_VERSION_MIN_IPHONEOS == BO(plc->cmd)) + { + ZLog::PrintV("\tMIN_IPHONEOS: \t0x%x\n", *((uint32_t *)(pLoadCommand + sizeof(load_command)))); + } + else if (LC_RPATH == BO(plc->cmd)) + { + ZLog::PrintV("\tLC_RPATH: \t%s\n", (char *)(pLoadCommand + sizeof(load_command) + 4)); + } + pLoadCommand += BO(plc->cmdsize); + } + + bool bHasWeakDylib = false; + ZLog::PrintV("\tLC_LOAD_DYLIB: \n"); + pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + if (LC_LOAD_DYLIB == BO(plc->cmd)) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + ZLog::PrintV("\t\t\t%s\n", szDyLib); + } + else if (LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) + { + bHasWeakDylib = true; + } + pLoadCommand += BO(plc->cmdsize); + } + + if (bHasWeakDylib) + { + ZLog::PrintV("\tLC_LOAD_WEAK_DYLIB: \n"); + pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + if (LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + ZLog::PrintV("\t\t\t%s (weak)\n", szDyLib); + } + pLoadCommand += BO(plc->cmdsize); + } + } + + if (!m_strInfoPlist.empty()) + { + ZLog::Print("\n>>> Embedded Info.plist: \n"); + ZLog::PrintV("\tlength: \t%lu\n", m_strInfoPlist.size()); + + string strInfoPlist = m_strInfoPlist; + PWriter::StringReplace(strInfoPlist, "\n", "\n\t\t\t"); + ZLog::PrintV("\tcontent: \t%s\n", strInfoPlist.c_str()); + + PrintDataSHASum("\tSHA-1: \t", E_SHASUM_TYPE_1, m_strInfoPlist); + PrintDataSHASum("\tSHA-256:\t", E_SHASUM_TYPE_256, m_strInfoPlist); + } + + if (NULL == m_pSignBase || m_uSignLength <= 0) + { + ZLog::Warn(">>> Can't Find CodeSignature Segment!\n"); + } + else + { + ParseCodeSignature(m_pSignBase); + } + + ZLog::Print("------------------------------------------------------------------\n"); +} + +bool ZArchO::BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput) +{ + string strRequirementsSlot; + string strEntitlementsSlot; + string strDerEntitlementsSlot; + + // modified, we don't need Entitlement in LiveContainer. +// string strEmptyEntitlements = "\n\n\n\n\n"; + string strEmptyEntitlements = ""; + SlotBuildRequirements(strBundleId, pSignAsset->m_strSubjectCN, strRequirementsSlot); + SlotBuildEntitlements(IsExecute() ? pSignAsset->m_strEntitlementsData : strEmptyEntitlements, strEntitlementsSlot); + SlotBuildDerEntitlements(IsExecute() ? pSignAsset->m_strEntitlementsData : "", strDerEntitlementsSlot); + + string strRequirementsSlotSHA1; + string strRequirementsSlotSHA256; + if (strRequirementsSlot.empty()) + { //empty + strRequirementsSlotSHA1.append(20, 0); + strRequirementsSlotSHA256.append(32, 0); + } + else + { + SHASum(strRequirementsSlot, strRequirementsSlotSHA1, strRequirementsSlotSHA256); + } + + string strEntitlementsSlotSHA1; + string strEntitlementsSlotSHA256; + if (strEntitlementsSlot.empty()) + { //empty + strEntitlementsSlotSHA1.append(20, 0); + strEntitlementsSlotSHA256.append(32, 0); + } + else + { + SHASum(strEntitlementsSlot, strEntitlementsSlotSHA1, strEntitlementsSlotSHA256); + } + + string strDerEntitlementsSlotSHA1; + string strDerEntitlementsSlotSHA256; + if (strDerEntitlementsSlot.empty()) + { //empty + strDerEntitlementsSlotSHA1.append(20, 0); + strDerEntitlementsSlotSHA256.append(32, 0); + } + else + { + SHASum(strDerEntitlementsSlot, strDerEntitlementsSlotSHA1, strDerEntitlementsSlotSHA256); + } + + uint8_t *pCodeSlots1Data = NULL; + uint8_t *pCodeSlots256Data = NULL; + uint32_t uCodeSlots1DataLength = 0; + uint32_t uCodeSlots256DataLength = 0; + if (!bForce) + { + GetCodeSignatureExistsCodeSlotsData(m_pSignBase, pCodeSlots1Data, uCodeSlots1DataLength, pCodeSlots256Data, uCodeSlots256DataLength); + } + + uint64_t execSegFlags = 0; + if (NULL != strstr(strEntitlementsSlot.data() + 8, "get-task-allow")) + { + // TODO: Check if get-task-allow is actually set to true + execSegFlags = CS_EXECSEG_MAIN_BINARY | CS_EXECSEG_ALLOW_UNSIGNED; + } + + string strCMSSignatureSlot; + string strCodeDirectorySlot; + string strAltnateCodeDirectorySlot; + SlotBuildCodeDirectory(false, + m_pBase, + m_uCodeLength, + pCodeSlots1Data, + uCodeSlots1DataLength, + execSegLimit, + execSegFlags, + strBundleId, + pSignAsset->m_strTeamId, + strInfoPlistSHA1, + strRequirementsSlotSHA1, + strCodeResourcesSHA1, + strEntitlementsSlotSHA1, + strDerEntitlementsSlotSHA1, + IsExecute(), + strCodeDirectorySlot); + SlotBuildCodeDirectory(true, + m_pBase, + m_uCodeLength, + pCodeSlots256Data, + uCodeSlots256DataLength, + execSegLimit, + execSegFlags, + strBundleId, + pSignAsset->m_strTeamId, + strInfoPlistSHA256, + strRequirementsSlotSHA256, + strCodeResourcesSHA256, + strEntitlementsSlotSHA256, + strDerEntitlementsSlotSHA256, + IsExecute(), + strAltnateCodeDirectorySlot); + SlotBuildCMSSignature(pSignAsset, + strCodeDirectorySlot, + strAltnateCodeDirectorySlot, + strCMSSignatureSlot); + + uint32_t uCodeDirectorySlotLength = (uint32_t)strCodeDirectorySlot.size(); + uint32_t uRequirementsSlotLength = (uint32_t)strRequirementsSlot.size(); + uint32_t uEntitlementsSlotLength = (uint32_t)strEntitlementsSlot.size(); + uint32_t uDerEntitlementsLength = (uint32_t)strDerEntitlementsSlot.size(); + uint32_t uAltnateCodeDirectorySlotLength = (uint32_t)strAltnateCodeDirectorySlot.size(); + uint32_t uCMSSignatureSlotLength = (uint32_t)strCMSSignatureSlot.size(); + + uint32_t uCodeSignBlobCount = 0; + uCodeSignBlobCount += (uCodeDirectorySlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uRequirementsSlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uEntitlementsSlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uDerEntitlementsLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uAltnateCodeDirectorySlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uCMSSignatureSlotLength > 0) ? 1 : 0; + + uint32_t uSuperBlobHeaderLength = sizeof(CS_SuperBlob) + uCodeSignBlobCount * sizeof(CS_BlobIndex); + uint32_t uCodeSignLength = uSuperBlobHeaderLength + + uCodeDirectorySlotLength + + uRequirementsSlotLength + + uEntitlementsSlotLength + + uDerEntitlementsLength + + uAltnateCodeDirectorySlotLength + + uCMSSignatureSlotLength; + + vector arrBlobIndexes; + if (uCodeDirectorySlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_CODEDIRECTORY); + blob.offset = BE(uSuperBlobHeaderLength); + arrBlobIndexes.push_back(blob); + } + if (uRequirementsSlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_REQUIREMENTS); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength); + arrBlobIndexes.push_back(blob); + } + if (uEntitlementsSlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_ENTITLEMENTS); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength); + arrBlobIndexes.push_back(blob); + } + if (uDerEntitlementsLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_DER_ENTITLEMENTS); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength); + arrBlobIndexes.push_back(blob); + } + if (uAltnateCodeDirectorySlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_ALTERNATE_CODEDIRECTORIES); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength + uDerEntitlementsLength); + arrBlobIndexes.push_back(blob); + } + if (uCMSSignatureSlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_SIGNATURESLOT); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength + uDerEntitlementsLength + uAltnateCodeDirectorySlotLength); + arrBlobIndexes.push_back(blob); + } + + CS_SuperBlob superblob; + superblob.magic = BE(CSMAGIC_EMBEDDED_SIGNATURE); + superblob.length = BE(uCodeSignLength); + superblob.count = BE(uCodeSignBlobCount); + + strOutput.clear(); + strOutput.reserve(uCodeSignLength); + strOutput.append((const char *)&superblob, sizeof(superblob)); + for (size_t i = 0; i < arrBlobIndexes.size(); i++) + { + CS_BlobIndex &blob = arrBlobIndexes[i]; + strOutput.append((const char *)&blob, sizeof(blob)); + } + strOutput += strCodeDirectorySlot; + strOutput += strRequirementsSlot; + strOutput += strEntitlementsSlot; + strOutput += strDerEntitlementsSlot; + strOutput += strAltnateCodeDirectorySlot; + strOutput += strCMSSignatureSlot; + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Requirements.slot.new", strRequirementsSlot); + WriteFile("./.zsign_debug/Entitlements.slot.new", strEntitlementsSlot); + WriteFile("./.zsign_debug/Entitlements.der.slot.new", strDerEntitlementsSlot); + WriteFile("./.zsign_debug/Entitlements.plist.new", strEntitlementsSlot.data() + 8, strEntitlementsSlot.size() - 8); + WriteFile("./.zsign_debug/CodeDirectory_SHA1.slot.new", strCodeDirectorySlot); + WriteFile("./.zsign_debug/CodeDirectory_SHA256.slot.new", strAltnateCodeDirectorySlot); + WriteFile("./.zsign_debug/CMSSignature.slot.new", strCMSSignatureSlot); + WriteFile("./.zsign_debug/CMSSignature.der.new", strCMSSignatureSlot.data() + 8, strCMSSignatureSlot.size() - 8); + WriteFile("./.zsign_debug/CodeSignature.blob.new", strOutput); + } + + return true; +} + +bool ZArchO::Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData) +{ + if (NULL == m_pSignBase) + { + m_bEnoughSpace = false; + ZLog::Warn(">>> Can't Find CodeSignature Segment!\n"); + return false; + } + + string strCodeResourcesSHA1; + string strCodeResourcesSHA256; + if (strCodeResourcesData.empty()) + { + strCodeResourcesSHA1.append(20, 0); + strCodeResourcesSHA256.append(32, 0); + } + else + { + SHASum(strCodeResourcesData, strCodeResourcesSHA1, strCodeResourcesSHA256); + } + + string strCodeSignBlob; + BuildCodeSignature(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesSHA1, strCodeResourcesSHA256, strCodeSignBlob); + if (strCodeSignBlob.empty()) + { + ZLog::Error(">>> Build CodeSignature Failed!\n"); + return false; + } + + int nSpaceLength = (int)m_uLength - (int)m_uCodeLength - (int)strCodeSignBlob.size(); + if (nSpaceLength < 0) + { + m_bEnoughSpace = false; + ZLog::WarnV(">>> No Enough CodeSignature Space. Length => Now: %d, Need: %d\n", (int)m_uLength - (int)m_uCodeLength, (int)strCodeSignBlob.size()); + return false; + } + + memcpy(m_pBase + m_uCodeLength, strCodeSignBlob.data(), strCodeSignBlob.size()); + //memset(m_pBase + m_uCodeLength + strCodeSignBlob.size(), 0, nSpaceLength); + return true; +} + +uint32_t ZArchO::ReallocCodeSignSpace(const string &strNewFile) +{ + RemoveFile(strNewFile.c_str()); + + uint32_t uNewLength = m_uCodeLength + ByteAlign(((m_uCodeLength / 4096) + 1) * (20 + 32), 4096) + 16384; //16K May Be Enough + if (NULL == m_pLinkEditSegment || uNewLength <= m_uLength) + { + return 0; + } + + load_command *pseglc = (load_command *)m_pLinkEditSegment; + switch (BO(pseglc->cmd)) + { + case LC_SEGMENT: + { + segment_command *seglc = (segment_command *)m_pLinkEditSegment; + seglc->vmsize = ByteAlign(BO(seglc->vmsize) + (uNewLength - m_uLength), 4096); + seglc->vmsize = BO(seglc->vmsize); + seglc->filesize = uNewLength - BO(seglc->fileoff); + seglc->filesize = BO(seglc->filesize); + } + break; + case LC_SEGMENT_64: + { + segment_command_64 *seglc = (segment_command_64 *)m_pLinkEditSegment; + seglc->vmsize = ByteAlign(BO((uint32_t)seglc->vmsize) + (uNewLength - m_uLength), 4096); + seglc->vmsize = BO((uint32_t)seglc->vmsize); + seglc->filesize = uNewLength - BO((uint32_t)seglc->fileoff); + seglc->filesize = BO((uint32_t)seglc->filesize); + } + break; + } + + codesignature_command *pcslc = (codesignature_command *)m_pCodeSignSegment; + if (NULL == pcslc) + { + if (m_uLoadCommandsFreeSpace < 4) + { + ZLog::Error(">>> Can't Find Free Space Of LoadCommands For CodeSignature!\n"); + return 0; + } + + pcslc = (codesignature_command *)(m_pBase + m_uHeaderSize + BO(m_pHeader->sizeofcmds)); + pcslc->cmd = BO(LC_CODE_SIGNATURE); + pcslc->cmdsize = BO((uint32_t)sizeof(codesignature_command)); + pcslc->dataoff = BO(m_uCodeLength); + m_pHeader->ncmds = BO(BO(m_pHeader->ncmds) + 1); + m_pHeader->sizeofcmds = BO(BO(m_pHeader->sizeofcmds) + sizeof(codesignature_command)); + } + pcslc->datasize = BO(uNewLength - m_uCodeLength); + + if (!AppendFile(strNewFile.c_str(), (const char *)m_pBase, m_uLength)) + { + return 0; + } + + string strPadding; + strPadding.append(uNewLength - m_uLength, 0); + if (!AppendFile(strNewFile.c_str(), strPadding)) + { + RemoveFile(strNewFile.c_str()); + return 0; + } + + return uNewLength; +} + +bool ZArchO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate) +{ + if (NULL == m_pHeader) + { + return false; + } + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + uint32_t uLoadType = BO(plc->cmd); + if (LC_LOAD_DYLIB == uLoadType || LC_LOAD_WEAK_DYLIB == uLoadType) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + if (0 == strcmp(szDyLib, szDyLibPath)) + { + if ((bWeakInject && (LC_LOAD_WEAK_DYLIB != uLoadType)) || (!bWeakInject && (LC_LOAD_DYLIB != uLoadType))) + { + dlc->cmd = BO((uint32_t)(bWeakInject ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB)); + ZLog::WarnV(">>> DyLib Load Type Changed! %s -> %s\n", (LC_LOAD_DYLIB == uLoadType) ? "LC_LOAD_DYLIB" : "LC_LOAD_WEAK_DYLIB", bWeakInject ? "LC_LOAD_WEAK_DYLIB" : "LC_LOAD_DYLIB"); + } + else + { + ZLog::WarnV(">>> DyLib Is Already Existed! %s\n"); + } + return true; + } + } + pLoadCommand += BO(plc->cmdsize); + } + + uint32_t uDylibPathLength = (uint32_t)strlen(szDyLibPath); + uint32_t uDylibPathPadding = (8 - uDylibPathLength % 8); + uint32_t uDyLibCommandSize = sizeof(dylib_command) + uDylibPathLength + uDylibPathPadding; + if (m_uLoadCommandsFreeSpace > 0 && m_uLoadCommandsFreeSpace < uDyLibCommandSize) // some bin doesn't have '__text' + { + ZLog::Error(">>> Can't Find Free Space Of LoadCommands For LC_LOAD_DYLIB Or LC_LOAD_WEAK_DYLIB!\n"); + return false; + } + + //add + dylib_command *dlc = (dylib_command *)(m_pBase + m_uHeaderSize + BO(m_pHeader->sizeofcmds)); + dlc->cmd = BO((uint32_t)(bWeakInject ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB)); + dlc->cmdsize = BO(uDyLibCommandSize); + dlc->dylib.name.offset = BO((uint32_t)sizeof(dylib_command)); + dlc->dylib.timestamp = BO((uint32_t)2); + dlc->dylib.current_version = 0; + dlc->dylib.compatibility_version = 0; + + string strDylibPath = szDyLibPath; + strDylibPath.append(uDylibPathPadding, 0); + + uint8_t *pDyLibPath = (uint8_t *)dlc + sizeof(dylib_command); + memcpy(pDyLibPath, strDylibPath.data(), uDylibPathLength + uDylibPathPadding); + + m_pHeader->ncmds = BO(BO(m_pHeader->ncmds) + 1); + m_pHeader->sizeofcmds = BO(BO(m_pHeader->sizeofcmds) + uDyLibCommandSize); + + bCreate = true; + return true; +} + +bool ZArchO::ChangeDylibPath(const char *oldPath, const char *newPath) { + if (NULL == m_pHeader) { + return false; + } + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + bool pathChanged = false; + uint32_t oldPathLength = (uint32_t)strlen(oldPath); + uint32_t newPathLength = (uint32_t)strlen(newPath); + uint32_t oldPathPadding = (8 - oldPathLength % 8) % 8; + uint32_t newPathPadding = (8 - newPathLength % 8) % 8; + uint32_t newLoadCommandSize = 0; + + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) { + load_command *plc = (load_command *)pLoadCommand; + uint32_t uLoadType = BO(plc->cmd); + + if (LC_LOAD_DYLIB == uLoadType || LC_LOAD_WEAK_DYLIB == uLoadType) { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + + if (strcmp(szDyLib, oldPath) == 0) { + uint32_t dylibPathOffset = sizeof(dylib_command); + uint32_t dylibPathSize = newPathLength + newPathPadding; + if (dylibPathOffset + dylibPathSize > BO(plc->cmdsize)) { + ZLog::Error(">>> Insufficient space to update dylib path!\n"); + return false; + } + + memcpy(pLoadCommand + dylibPathOffset, newPath, newPathLength); + memset(pLoadCommand + dylibPathOffset + newPathLength, 0, newPathPadding); + + ZLog::PrintV(">>> Dylib Path Changed: %s -> %s\n", oldPath, newPath); + + pathChanged = true; + } + } + + pLoadCommand += BO(plc->cmdsize); + } + + if (!pathChanged) { + ZLog::PrintV(">>> Old Dylib Path Not Found: %s\n", oldPath); + } + + return pathChanged; +} + + + + + + +void ZArchO::uninstallDylibs(set dylibNames) +{ + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + uint32_t old_load_command_size = m_pHeader->sizeofcmds; + uint8_t *new_load_command_data = (uint8_t*)malloc(old_load_command_size); + memset(new_load_command_data,0,old_load_command_size); + uint32_t new_load_command_size = 0; + uint32_t clear_num = 0; + uint32_t clear_data_size = 0; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + uint32_t load_command_size = BO(plc->cmdsize); + if (LC_LOAD_DYLIB == BO(plc->cmd) || LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + string dylibName = szDyLib; + if(dylibNames.count(dylibName)>0){ + ZLog::PrintV("\t\t\t%s\tclear\n", szDyLib); + clear_num++; + clear_data_size+=load_command_size; + pLoadCommand += BO(plc->cmdsize); + continue; + } + ZLog::PrintV("\t\t\t%s\n", szDyLib); + } + new_load_command_size+=load_command_size; + memcpy(new_load_command_data,pLoadCommand,load_command_size); + new_load_command_data += load_command_size; + pLoadCommand += BO(plc->cmdsize); + } + pLoadCommand -= m_pHeader->sizeofcmds; + + m_pHeader->ncmds -= clear_num; + m_pHeader->sizeofcmds -= clear_data_size; + new_load_command_data -=new_load_command_size; + memset(pLoadCommand,0,old_load_command_size); + memcpy(pLoadCommand,new_load_command_data,new_load_command_size); + free(new_load_command_data); +} + +std::vector ZArchO::ListDylibs() { + std::vector dylibList; + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) { + load_command *plc = (load_command *)pLoadCommand; + if (LC_LOAD_DYLIB == BO(plc->cmd) || LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + dylibList.push_back(std::string(szDyLib)); + } + pLoadCommand += BO(plc->cmdsize); + } + + return dylibList; +} + diff --git a/zsign/archo.h b/zsign/archo.h new file mode 100644 index 0000000..b9dc27d --- /dev/null +++ b/zsign/archo.h @@ -0,0 +1,42 @@ +#pragma once +#include "common/mach-o.h" +#include "openssl.h" +#include +class ZArchO +{ +public: + ZArchO(); + bool Init(uint8_t *pBase, uint32_t uLength); + +public: + bool Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData); + void PrintInfo(); + bool IsExecute(); + bool InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate); + uint32_t ReallocCodeSignSpace(const string &strNewFile); + void uninstallDylibs(set dylibNames); + bool ChangeDylibPath(const char *oldPath, const char *newPath); + std::vector ListDylibs(); +private: + uint32_t BO(uint32_t uVal); + const char *GetFileType(uint32_t uFileType); + const char *GetArch(int cpuType, int cpuSubType); + bool BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput); + +public: + uint8_t *m_pBase; + uint32_t m_uLength; + uint32_t m_uCodeLength; + uint8_t *m_pSignBase; + uint32_t m_uSignLength; + string m_strInfoPlist; + bool m_bEncrypted; + bool m_b64; + bool m_bBigEndian; + bool m_bEnoughSpace; + uint8_t *m_pCodeSignSegment; + uint8_t *m_pLinkEditSegment; + uint32_t m_uLoadCommandsFreeSpace; + mach_header *m_pHeader; + uint32_t m_uHeaderSize; +}; diff --git a/zsign/bundle.cpp b/zsign/bundle.cpp new file mode 100644 index 0000000..cc2a45f --- /dev/null +++ b/zsign/bundle.cpp @@ -0,0 +1,710 @@ +#include "bundle.h" +#include "macho.h" +#include "sys/stat.h" +#include "sys/types.h" +#include "common/base64.h" +#include "common/common.h" + +ZAppBundle::ZAppBundle() +{ + m_pSignAsset = NULL; + m_bForceSign = false; + m_bWeakInject = false; +} + +bool ZAppBundle::FindAppFolder(const string &strFolder, string &strAppFolder) +{ + if (IsPathSuffix(strFolder, ".app") || IsPathSuffix(strFolder, ".appex")) + { + strAppFolder = strFolder; + return true; + } + + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..") && 0 != strcmp(ptr->d_name, "__MACOSX")) + { + bool isdir = false; + if (DT_DIR == ptr->d_type) + { + isdir = true; + } + else if (DT_UNKNOWN == ptr->d_type) + { + // Entry type can be unknown depending on the underlying file system + ZLog::DebugV(">>> Unknown directory entry type for %s, falling back to POSIX-compatible check\n", strFolder.c_str()); + struct stat statbuf; + stat(strFolder.c_str(), &statbuf); + if (S_ISDIR(statbuf.st_mode)) + { + isdir = true; + } + } + if (isdir) + { + string strSubFolder = strFolder; + strSubFolder += "/"; + strSubFolder += ptr->d_name; + if (FindAppFolder(strSubFolder, strAppFolder)) + { + return true; + } + } + } + ptr = readdir(dir); + } + closedir(dir); + } + return false; +} + +bool ZAppBundle::GetSignFolderInfo(const string &strFolder, JValue &jvNode, bool bGetName) +{ + JValue jvInfo; + string strInfoPlistData; + string strInfoPlistPath = strFolder + "/Info.plist"; + ReadFile(strInfoPlistPath.c_str(), strInfoPlistData); + jvInfo.readPList(strInfoPlistData); + string strBundleId = jvInfo["CFBundleIdentifier"]; + string strBundleExe = jvInfo["CFBundleExecutable"]; + string strBundleVersion = jvInfo["CFBundleVersion"]; + if (strBundleId.empty() || strBundleExe.empty()) + { + return false; + } + + string strInfoPlistSHA1Base64; + string strInfoPlistSHA256Base64; + SHASumBase64(strInfoPlistData, strInfoPlistSHA1Base64, strInfoPlistSHA256Base64); + + jvNode["bid"] = strBundleId; + jvNode["bver"] = strBundleVersion; + jvNode["exec"] = strBundleExe; + jvNode["sha1"] = strInfoPlistSHA1Base64; + jvNode["sha2"] = strInfoPlistSHA256Base64; + + if (bGetName) + { + string strBundleName = jvInfo["CFBundleDisplayName"]; + if (strBundleName.empty()) + { + strBundleName = jvInfo["CFBundleName"].asCString(); + } + jvNode["name"] = strBundleName; + } + + return true; +} + +bool ZAppBundle::GetObjectsToSign(const string &strFolder, JValue &jvInfo) +{ + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) + { + string strNode = strFolder + "/" + ptr->d_name; + if (DT_DIR == ptr->d_type) + { + if (IsPathSuffix(strNode, ".app") || IsPathSuffix(strNode, ".appex") || IsPathSuffix(strNode, ".framework") || IsPathSuffix(strNode, ".xctest")) + { + JValue jvNode; + jvNode["path"] = strNode.substr(m_strAppFolder.size() + 1); + if (GetSignFolderInfo(strNode, jvNode)) + { + if (GetObjectsToSign(strNode, jvNode)) + { + jvInfo["folders"].push_back(jvNode); + } + } + } + else + { + GetObjectsToSign(strNode, jvInfo); + } + } + else if (DT_REG == ptr->d_type) + { + if (IsPathSuffix(strNode, ".dylib")) + { + jvInfo["files"].push_back(strNode.substr(m_strAppFolder.size() + 1)); + } + } + } + ptr = readdir(dir); + } + closedir(dir); + } + return true; +} + +void ZAppBundle::GetFolderFiles(const string &strFolder, const string &strBaseFolder, set &setFiles) +{ + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) + { + string strNode = strFolder; + strNode += "/"; + strNode += ptr->d_name; + if (DT_DIR == ptr->d_type) + { + GetFolderFiles(strNode, strBaseFolder, setFiles); + } + else if (DT_REG == ptr->d_type) + { + setFiles.insert(strNode.substr(strBaseFolder.size() + 1)); + } + } + ptr = readdir(dir); + } + closedir(dir); + } +} + +bool ZAppBundle::GenerateCodeResources(const string &strFolder, JValue &jvCodeRes) +{ + jvCodeRes.clear(); + + set setFiles; + GetFolderFiles(strFolder, strFolder, setFiles); + + JValue jvInfo; + string strInfoPlistPath = strFolder + "/Info.plist"; + jvInfo.readPListFile(strInfoPlistPath.c_str()); + string strBundleExe = jvInfo["CFBundleExecutable"]; + setFiles.erase(strBundleExe); + setFiles.erase("_CodeSignature/CodeResources"); + + jvCodeRes["files"] = JValue(JValue::E_OBJECT); + jvCodeRes["files2"] = JValue(JValue::E_OBJECT); + + for (set::iterator it = setFiles.begin(); it != setFiles.end(); it++) + { + string strKey = *it; + string strFile = strFolder + "/" + strKey; + string strFileSHA1Base64; + string strFileSHA256Base64; + SHASumBase64File(strFile.c_str(), strFileSHA1Base64, strFileSHA256Base64); + + bool bomit1 = false; + bool bomit2 = false; + + if ("Info.plist" == strKey || "PkgInfo" == strKey) + { + bomit2 = true; + } + + if (IsPathSuffix(strKey, ".DS_Store")) + { + bomit2 = true; + } + + if (IsPathSuffix(strKey, ".lproj/locversion.plist")) + { + bomit1 = true; + bomit2 = true; + } + + if (!bomit1) + { + if (string::npos != strKey.rfind(".lproj/")) + { + jvCodeRes["files"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files"][strKey]["optional"] = true; + } + else + { + jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; + } + } + + if (!bomit2) + { + jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; + if (string::npos != strKey.rfind(".lproj/")) + { + jvCodeRes["files2"][strKey]["optional"] = true; + } + } + } + + jvCodeRes["rules"]["^.*"] = true; + jvCodeRes["rules"]["^.*\\.lproj/"]["optional"] = true; + jvCodeRes["rules"]["^.*\\.lproj/"]["weight"] = 1000.0; + jvCodeRes["rules"]["^.*\\.lproj/locversion.plist$"]["omit"] = true; + jvCodeRes["rules"]["^.*\\.lproj/locversion.plist$"]["weight"] = 1100.0; + jvCodeRes["rules"]["^Base\\.lproj/"]["weight"] = 1010.0; + jvCodeRes["rules"]["^version.plist$"] = true; + + jvCodeRes["rules2"]["^.*"] = true; + jvCodeRes["rules2"][".*\\.dSYM($|/)"]["weight"] = 11.0; + jvCodeRes["rules2"]["^(.*/)?\\.DS_Store$"]["omit"] = true; + jvCodeRes["rules2"]["^(.*/)?\\.DS_Store$"]["weight"] = 2000.0; + jvCodeRes["rules2"]["^.*\\.lproj/"]["optional"] = true; + jvCodeRes["rules2"]["^.*\\.lproj/"]["weight"] = 1000.0; + jvCodeRes["rules2"]["^.*\\.lproj/locversion.plist$"]["omit"] = true; + jvCodeRes["rules2"]["^.*\\.lproj/locversion.plist$"]["weight"] = 1100.0; + jvCodeRes["rules2"]["^Base\\.lproj/"]["weight"] = 1010.0; + jvCodeRes["rules2"]["^Info\\.plist$"]["omit"] = true; + jvCodeRes["rules2"]["^Info\\.plist$"]["weight"] = 20.0; + jvCodeRes["rules2"]["^PkgInfo$"]["omit"] = true; + jvCodeRes["rules2"]["^PkgInfo$"]["weight"] = 20.0; + jvCodeRes["rules2"]["^embedded\\.provisionprofile$"]["weight"] = 20.0; + jvCodeRes["rules2"]["^version\\.plist$"]["weight"] = 20.0; + + return true; +} + +void ZAppBundle::GetChangedFiles(JValue &jvNode, vector &arrChangedFiles) +{ + if (jvNode.has("files")) + { + for (size_t i = 0; i < jvNode["files"].size(); i++) + { + arrChangedFiles.push_back(jvNode["files"][i]); + } + } + + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + JValue &jvSubNode = jvNode["folders"][i]; + GetChangedFiles(jvSubNode, arrChangedFiles); + string strPath = jvSubNode["path"]; + arrChangedFiles.push_back(strPath + "/_CodeSignature/CodeResources"); + arrChangedFiles.push_back(strPath + "/" + jvSubNode["exec"].asString()); + } + } +} + +void ZAppBundle::GetNodeChangedFiles(JValue &jvNode, bool dontGenerateEmbeddedMobileProvision) +{ + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + GetNodeChangedFiles(jvNode["folders"][i], dontGenerateEmbeddedMobileProvision); + } + } + + vector arrChangedFiles; + GetChangedFiles(jvNode, arrChangedFiles); + for (size_t i = 0; i < arrChangedFiles.size(); i++) + { + jvNode["changed"].push_back(arrChangedFiles[i]); + } +// TODO: try + if (dontGenerateEmbeddedMobileProvision) { + if ("/" == jvNode["path"]) + { //root + jvNode["changed"].push_back("embedded.mobileprovision"); + } + } +} + +int ZAppBundle::GetSignCount(JValue &jvNode) { + int ans = 1; + if (jvNode.has("files")) + { + ans += jvNode["files"].size(); + } + + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + ans += GetSignCount(jvNode["folders"][i]); + } + } + return ans; +} + +bool ZAppBundle::SignNode(JValue &jvNode) +{ + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + if (!SignNode(jvNode["folders"][i])) + { + return false; + } + } + } + + if (jvNode.has("files")) + { + for (size_t i = 0; i < jvNode["files"].size(); i++) + { + const char *szFile = jvNode["files"][i].asCString(); + ZLog::PrintV(">>> SignFile: \t%s\n", szFile); + ZMachO macho; + if (!macho.InitV("%s/%s", m_strAppFolder.c_str(), szFile)) + { + return false; + } + + if (!macho.Sign(m_pSignAsset, m_bForceSign, mainBundleIdentifier, "", "", "")) + { + return false; + } + if(progressHandler) { + progressHandler(); + } + } + } + + ZBase64 b64; + string strInfoPlistSHA1; + string strInfoPlistSHA256; + string strFolder = jvNode["path"]; + string strBundleId = jvNode["bid"]; + string strBundleExe = jvNode["exec"]; + b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); + b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); + if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) + { + ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); + return false; + } + + string strBaseFolder = m_strAppFolder; + if ("/" != strFolder) + { + strBaseFolder += "/"; + strBaseFolder += strFolder; + } + + string strExePath = strBaseFolder + "/" + strBundleExe; + ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); + + ZMachO macho; + if (!macho.Init(strExePath.c_str())) + { + ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); + return false; + } + + RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); + CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); + string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; + + JValue jvCodeRes; + if (!m_bForceSign) + { + jvCodeRes.readPListFile(strCodeResFile.c_str()); + } + + if (m_bForceSign || jvCodeRes.isNull()) + { //create + if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) + { + ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); + return false; + } + } + else if (jvNode.has("changed")) + { //use existsed + for (size_t i = 0; i < jvNode["changed"].size(); i++) + { + string strFile = jvNode["changed"][i].asCString(); + string strRealFile = m_strAppFolder + "/" + strFile; + + string strFileSHA1Base64; + string strFileSHA256Base64; + if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) + { + ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); + return false; + } + + string strKey = strFile; + if ("/" != strFolder) + { + strKey = strFile.substr(strFolder.size() + 1); + } + jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; + + ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); + } + } + + string strCodeResData; + jvCodeRes.writePList(strCodeResData); + if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) + { + ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); + return false; + } + + bool bForceSign = m_bForceSign; + if ("/" == strFolder && !m_strDyLibPath.empty()) + { //inject dylib + macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); + } + + if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) + { + return false; + } + + if(progressHandler) { + progressHandler(); + } + return true; +} + +void ZAppBundle::GetPlugIns(const string &strFolder, vector &arrPlugIns) +{ + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) + { + if (DT_DIR == ptr->d_type) + { + string strSubFolder = strFolder; + strSubFolder += "/"; + strSubFolder += ptr->d_name; + if (IsPathSuffix(strSubFolder, ".app") || IsPathSuffix(strSubFolder, ".appex")) + { + arrPlugIns.push_back(strSubFolder); + } + GetPlugIns(strSubFolder, arrPlugIns); + } + } + ptr = readdir(dir); + } + closedir(dir); + } +} + +bool ZAppBundle::ConfigureFolderSign(ZSignAsset *pSignAsset, + const string &strFolder, + const string &execName, + const string &strBundleID, + const string &strBundleVersion, + const string &strDisplayName, + const string &strDyLibFile, + bool bForce, + bool bWeakInject, + bool bEnableCache, + bool dontGenerateEmbeddedMobileProvision + ) +{ + m_bForceSign = bForce; + m_pSignAsset = pSignAsset; + m_bWeakInject = bWeakInject; + if (NULL == m_pSignAsset) + { + return false; + } + + if (!FindAppFolder(strFolder, m_strAppFolder)) + { + ZLog::ErrorV(">>> Can't Find App Folder! %s\n", strFolder.c_str()); + return false; + } + + JValue jvInfoPlist; + bool jvInfoReadSuccess = jvInfoPlist.readPListPath("%s/Info.plist", m_strAppFolder.c_str()); + if (!strBundleID.empty() || !strDisplayName.empty() || !strBundleVersion.empty()) + { //modify bundle id + + if (jvInfoReadSuccess) + { + m_bForceSign = true; + if (!strBundleID.empty()) + { + string strOldBundleID = jvInfoPlist["CFBundleIdentifier"]; + jvInfoPlist["CFBundleIdentifier"] = strBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s\n", strOldBundleID.c_str(), strBundleID.c_str()); + + //modify plugins bundle id + vector arrPlugIns; + GetPlugIns(m_strAppFolder, arrPlugIns); + for (size_t i = 0; i < arrPlugIns.size(); i++) + { + string &strPlugin = arrPlugIns[i]; + JValue jvPlugInInfoPlist; + if (jvPlugInInfoPlist.readPListPath("%s/Info.plist", strPlugin.c_str())) + { + string strOldPlugInBundleID = jvPlugInInfoPlist["CFBundleIdentifier"]; + string strNewPlugInBundleID = strOldPlugInBundleID; + StringReplace(strNewPlugInBundleID, strOldBundleID, strBundleID); + jvPlugInInfoPlist["CFBundleIdentifier"] = strNewPlugInBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s, PlugIn\n", strOldPlugInBundleID.c_str(), strNewPlugInBundleID.c_str()); + + if (jvPlugInInfoPlist.has("WKCompanionAppBundleIdentifier")) + { + string strOldWKCBundleID = jvPlugInInfoPlist["WKCompanionAppBundleIdentifier"]; + string strNewWKCBundleID = strOldWKCBundleID; + StringReplace(strNewWKCBundleID, strOldBundleID, strBundleID); + jvPlugInInfoPlist["WKCompanionAppBundleIdentifier"] = strNewWKCBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s, PlugIn-WKCompanionAppBundleIdentifier\n", strOldWKCBundleID.c_str(), strNewWKCBundleID.c_str()); + } + + if (jvPlugInInfoPlist.has("NSExtension")) + { + if (jvPlugInInfoPlist["NSExtension"].has("NSExtensionAttributes")) + { + if (jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"].has("WKAppBundleIdentifier")) + { + string strOldWKBundleID = jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"]; + string strNewWKBundleID = strOldWKBundleID; + StringReplace(strNewWKBundleID, strOldBundleID, strBundleID); + jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = strNewWKBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s, NSExtension-NSExtensionAttributes-WKAppBundleIdentifier\n", strOldWKBundleID.c_str(), strNewWKBundleID.c_str()); + } + } + } + + jvPlugInInfoPlist.writePListPath("%s/Info.plist", strPlugin.c_str()); + } + } + } + + if (!strDisplayName.empty()) + { + string strOldDisplayName = jvInfoPlist["CFBundleDisplayName"]; + jvInfoPlist["CFBundleName"] = strDisplayName; + jvInfoPlist["CFBundleDisplayName"] = strDisplayName; + ZLog::PrintV(">>> BundleName: %s -> %s\n", strOldDisplayName.c_str(), strDisplayName.c_str()); + } + + if (!strBundleVersion.empty()) + { + string strOldBundleVersion = jvInfoPlist["CFBundleVersion"]; + jvInfoPlist["CFBundleVersion"] = strBundleVersion; + jvInfoPlist["CFBundleShortVersionString"] = strBundleVersion; + ZLog::PrintV(">>> BundleVersion: %s -> %s\n", strOldBundleVersion.c_str(), strBundleVersion.c_str()); + } + + jvInfoPlist.writePListPath("%s/Info.plist", m_strAppFolder.c_str()); + } + else + { + ZLog::ErrorV(">>> Can't Find App's Info.plist! %s\n", strFolder.c_str()); + return false; + } + } + + if (!strDisplayName.empty()) + { + m_bForceSign = true; + JValue jvInfoPlistStrings; + if (jvInfoPlistStrings.readPListPath("%s/zh_CN.lproj/InfoPlist.strings", m_strAppFolder.c_str())) + { + jvInfoPlistStrings["CFBundleName"] = strDisplayName; + jvInfoPlistStrings["CFBundleDisplayName"] = strDisplayName; + jvInfoPlistStrings.writePListPath("%s/zh_CN.lproj/InfoPlist.strings", m_strAppFolder.c_str()); + } + jvInfoPlistStrings.clear(); + if (jvInfoPlistStrings.readPListPath("%s/zh-Hans.lproj/InfoPlist.strings", m_strAppFolder.c_str())) + { + jvInfoPlistStrings["CFBundleName"] = strDisplayName; + jvInfoPlistStrings["CFBundleDisplayName"] = strDisplayName; + jvInfoPlistStrings.writePListPath("%s/zh-Hans.lproj/InfoPlist.strings", m_strAppFolder.c_str()); + } + } + if (dontGenerateEmbeddedMobileProvision) { + if (!WriteFile(pSignAsset->m_strProvisionData, "%s/embedded.mobileprovision", m_strAppFolder.c_str())) + { //embedded.mobileprovision + ZLog::ErrorV(">>> Can't Write embedded.mobileprovision!\n"); + return false; + } + } + + if (!strDyLibFile.empty()) + { //inject dylib + string strDyLibData; + ReadFile(strDyLibFile.c_str(), strDyLibData); + if (!strDyLibData.empty()) + { + string strFileName = basename((char *)strDyLibFile.c_str()); + if (WriteFile(strDyLibData, "%s/%s", m_strAppFolder.c_str(), strFileName.c_str())) + { + StringFormat(m_strDyLibPath, "@executable_path/%s", strFileName.c_str()); + } + } + } + + string strCacheName; + SHA1Text(m_strAppFolder, strCacheName); + if (!IsFileExistsV("%s/zsign_cache.json", m_strAppFolder.c_str())) + { + m_bForceSign = true; + } + mainBundleIdentifier = string(jvInfoPlist["CFBundleIdentifier"]); + ZLog::PrintV("mainBundleIdentifier = %s", mainBundleIdentifier.c_str()); + + JValue jvRoot; + if (m_bForceSign) + { + jvRoot["path"] = "/"; + jvRoot["root"] = m_strAppFolder; + if (!GetSignFolderInfo(m_strAppFolder, jvRoot, true)) + { + ZLog::ErrorV(">>> Can't Get BundleID, BundleVersion, or BundleExecute in Info.plist! %s\n", m_strAppFolder.c_str()); + return false; + } + if (!GetObjectsToSign(m_strAppFolder, jvRoot)) + { + return false; + } + jvRoot["files"].push_back(execName); + GetNodeChangedFiles(jvRoot, dontGenerateEmbeddedMobileProvision); + } + else + { + jvRoot.readPath("%s/zsign_cache.json", m_strAppFolder.c_str()); + } + + ZLog::PrintV(">>> Signing: \t%s ...\n", m_strAppFolder.c_str()); + ZLog::PrintV(">>> AppName: \t%s\n", jvRoot["name"].asCString()); + ZLog::PrintV(">>> BundleId: \t%s\n", jvRoot["bid"].asCString()); + ZLog::PrintV(">>> BundleVer: \t%s\n", jvRoot["bver"].asCString()); + ZLog::PrintV(">>> TeamId: \t%s\n", m_pSignAsset->m_strTeamId.c_str()); + ZLog::PrintV(">>> SubjectCN: \t%s\n", m_pSignAsset->m_strSubjectCN.c_str()); + ZLog::PrintV(">>> ReadCache: \t%s\n", m_bForceSign ? "NO" : "YES"); + ZLog::PrintV(">>> Exclude MobileProvision: \t%s\n", dontGenerateEmbeddedMobileProvision ? "NO" : "YES"); + + config = jvRoot; + + return true; +} + +int ZAppBundle::GetSignCount() { + return GetSignCount(config); +} + +bool ZAppBundle::StartSign(bool enableCache) { + if (SignNode(config)) + { + if (enableCache) + { + config.styleWritePath("%s/zsign_cache.json", m_strAppFolder.c_str()); + } + return true; + } + return false; +} diff --git a/zsign/bundle.h b/zsign/bundle.h new file mode 100644 index 0000000..83d21db --- /dev/null +++ b/zsign/bundle.h @@ -0,0 +1,42 @@ +#pragma once +#include "common/common.h" +#include "common/json.h" +#include "openssl.h" + +class ZAppBundle +{ +public: + ZAppBundle(); + +public: + bool ConfigureFolderSign(ZSignAsset *pSignAsset, const string &strFolder, const string &execName, const string &strBundleID, const string &strBundleVersion, const string &strDisplayName, const string &strDyLibFile, bool bForce, bool bWeakInject, bool bEnableCache, bool dontGenerateEmbeddedMobileProvision); + bool StartSign(bool enableCache); + int GetSignCount(); +private: + bool SignNode(JValue &jvNode); + void GetNodeChangedFiles(JValue &jvNode, bool dontGenerateEmbeddedMobileProvision); + void GetChangedFiles(JValue &jvNode, vector &arrChangedFiles); + void GetPlugIns(const string &strFolder, vector &arrPlugIns); + int GetSignCount(JValue &jvNode); + +private: + bool FindAppFolder(const string &strFolder, string &strAppFolder); + bool GetObjectsToSign(const string &strFolder, JValue &jvInfo); + bool GetSignFolderInfo(const string &strFolder, JValue &jvNode, bool bGetName = false); + +private: + bool GenerateCodeResources(const string &strFolder, JValue &jvCodeRes); + void GetFolderFiles(const string &strFolder, const string &strBaseFolder, set &setFiles); + +private: + bool m_bForceSign; + bool m_bWeakInject; + string m_strDyLibPath; + ZSignAsset *m_pSignAsset; + string mainBundleIdentifier; + JValue config; + +public: + string m_strAppFolder; + std::function progressHandler; +}; diff --git a/zsign/common/base64.cpp b/zsign/common/base64.cpp new file mode 100644 index 0000000..bde88ef --- /dev/null +++ b/zsign/common/base64.cpp @@ -0,0 +1,203 @@ +#include "base64.h" +#include + +#define B0(a) (a & 0xFF) +#define B1(a) (a >> 8 & 0xFF) +#define B2(a) (a >> 16 & 0xFF) +#define B3(a) (a >> 24 & 0xFF) + +ZBase64::ZBase64(void) +{ +} + +ZBase64::~ZBase64(void) +{ + if (!m_arrEnc.empty()) + { + for (size_t i = 0; i < m_arrEnc.size(); i++) + { + delete[] m_arrEnc[i]; + } + m_arrEnc.clear(); + } + + if (!m_arrDec.empty()) + { + for (size_t i = 0; i < m_arrDec.size(); i++) + { + delete[] m_arrDec[i]; + } + m_arrDec.clear(); + } +} + +char ZBase64::GetB64char(int nIndex) +{ + static const char szTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (nIndex >= 0 && nIndex < 64) + { + return szTable[nIndex]; + } + return '='; +} + +int ZBase64::GetB64Index(char ch) +{ + int index = -1; + if (ch >= 'A' && ch <= 'Z') + { + index = ch - 'A'; + } + else if (ch >= 'a' && ch <= 'z') + { + index = ch - 'a' + 26; + } + else if (ch >= '0' && ch <= '9') + { + index = ch - '0' + 52; + } + else if (ch == '+') + { + index = 62; + } + else if (ch == '/') + { + index = 63; + } + return index; +} + +const char *ZBase64::Encode(const char *szSrc, int nSrcLen) +{ + if (0 == nSrcLen) + { + nSrcLen = (int)strlen(szSrc); + } + + if (nSrcLen <= 0) + { + return ""; + } + + char *szEnc = new char[nSrcLen * 3 + 128]; + m_arrEnc.push_back(szEnc); + + int i = 0; + int len = 0; + unsigned char *psrc = (unsigned char *)szSrc; + char *p64 = szEnc; + for (i = 0; i < nSrcLen - 3; i += 3) + { + unsigned long ulTmp = *(unsigned long *)psrc; + int b0 = GetB64char((B0(ulTmp) >> 2) & 0x3F); + int b1 = GetB64char((B0(ulTmp) << 6 >> 2 | B1(ulTmp) >> 4) & 0x3F); + int b2 = GetB64char((B1(ulTmp) << 4 >> 2 | B2(ulTmp) >> 6) & 0x3F); + int b3 = GetB64char((B2(ulTmp) << 2 >> 2) & 0x3F); + *((unsigned long *)p64) = b0 | b1 << 8 | b2 << 16 | b3 << 24; + len += 4; + p64 += 4; + psrc += 3; + } + + if (i < nSrcLen) + { + int rest = nSrcLen - i; + unsigned long ulTmp = 0; + for (int j = 0; j < rest; ++j) + { + *(((unsigned char *)&ulTmp) + j) = *psrc++; + } + p64[0] = GetB64char((B0(ulTmp) >> 2) & 0x3F); + p64[1] = GetB64char((B0(ulTmp) << 6 >> 2 | B1(ulTmp) >> 4) & 0x3F); + p64[2] = rest > 1 ? GetB64char((B1(ulTmp) << 4 >> 2 | B2(ulTmp) >> 6) & 0x3F) : '='; + p64[3] = rest > 2 ? GetB64char((B2(ulTmp) << 2 >> 2) & 0x3F) : '='; + p64 += 4; + len += 4; + } + *p64 = '\0'; + return szEnc; +} + +const char *ZBase64::Encode(const string &strInput) +{ + return Encode(strInput.data(), strInput.size()); +} + +const char *ZBase64::Decode(const char *szSrc, int nSrcLen, int *pDecLen) +{ + if (0 == nSrcLen) + { + nSrcLen = (int)strlen(szSrc); + } + + if (nSrcLen <= 0) + { + return ""; + } + + char *szDec = new char[nSrcLen]; + m_arrDec.push_back(szDec); + + int i = 0; + int len = 0; + unsigned char *psrc = (unsigned char *)szSrc; + char *pbuf = szDec; + for (i = 0; i < nSrcLen - 4; i += 4) + { + unsigned long ulTmp = *(unsigned long *)psrc; + + int b0 = (GetB64Index((char)B0(ulTmp)) << 2 | GetB64Index((char)B1(ulTmp)) << 2 >> 6) & 0xFF; + int b1 = (GetB64Index((char)B1(ulTmp)) << 4 | GetB64Index((char)B2(ulTmp)) << 2 >> 4) & 0xFF; + int b2 = (GetB64Index((char)B2(ulTmp)) << 6 | GetB64Index((char)B3(ulTmp)) << 2 >> 2) & 0xFF; + + *((unsigned long *)pbuf) = b0 | b1 << 8 | b2 << 16; + psrc += 4; + pbuf += 3; + len += 3; + } + + if (i < nSrcLen) + { + int rest = nSrcLen - i; + unsigned long ulTmp = 0; + for (int j = 0; j < rest; ++j) + { + *(((unsigned char *)&ulTmp) + j) = *psrc++; + } + + int b0 = (GetB64Index((char)B0(ulTmp)) << 2 | GetB64Index((char)B1(ulTmp)) << 2 >> 6) & 0xFF; + *pbuf++ = b0; + len++; + + if ('=' != B1(ulTmp) && '=' != B2(ulTmp)) + { + int b1 = (GetB64Index((char)B1(ulTmp)) << 4 | GetB64Index((char)B2(ulTmp)) << 2 >> 4) & 0xFF; + *pbuf++ = b1; + len++; + } + + if ('=' != B2(ulTmp) && '=' != B3(ulTmp)) + { + int b2 = (GetB64Index((char)B2(ulTmp)) << 6 | GetB64Index((char)B3(ulTmp)) << 2 >> 2) & 0xFF; + *pbuf++ = b2; + len++; + } + } + *pbuf = '\0'; + + if (NULL != pDecLen) + { + *pDecLen = (int)(pbuf - szDec); + } + + return szDec; +} + +const char *ZBase64::Decode(const char *szSrc, string &strOutput) +{ + strOutput.clear(); + int nDecLen = 0; + const char *p = Decode(szSrc, 0, &nDecLen); + strOutput.append(p, nDecLen); + return strOutput.data(); +} diff --git a/zsign/common/base64.h b/zsign/common/base64.h new file mode 100644 index 0000000..3b38dc1 --- /dev/null +++ b/zsign/common/base64.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +using namespace std; + +class ZBase64 +{ +public: + ZBase64(void); + ~ZBase64(void); + +public: + const char *Encode(const char *szSrc, int nSrcLen = 0); + const char *Encode(const string &strInput); + const char *Decode(const char *szSrc, int nSrcLen = 0, int *pDecLen = NULL); + const char *Decode(const char *szSrc, string &strOutput); + +private: + inline int GetB64Index(char ch); + inline char GetB64char(int nIndex); + +private: + vector m_arrDec; + vector m_arrEnc; +}; diff --git a/zsign/common/common.cpp b/zsign/common/common.cpp new file mode 100644 index 0000000..cedb7fb --- /dev/null +++ b/zsign/common/common.cpp @@ -0,0 +1,848 @@ +#include "common.h" +#include "base64.h" +#include +#include +#include +#include +#include "../Utils.hpp" +#include + +#define PARSEVALIST(szFormatArgs, szArgs) \ + ZBuffer buffer; \ + char szBuffer[PATH_MAX] = {0}; \ + char *szArgs = szBuffer; \ + va_list args; \ + va_start(args, szFormatArgs); \ + int nRet = vsnprintf(szArgs, PATH_MAX, szFormatArgs, args); \ + va_end(args); \ + if (nRet > PATH_MAX - 1) \ + { \ + char *szNewBuffer = buffer.GetBuffer(nRet + 1); \ + if (NULL != szNewBuffer) \ + { \ + szArgs = szNewBuffer; \ + va_start(args, szFormatArgs); \ + vsnprintf(szArgs, nRet + 1, szFormatArgs, args); \ + va_end(args); \ + } \ + } + +bool IsRegularFile(const char *file) +{ + struct stat info; + stat(file, &info); + return S_ISREG(info.st_mode); +} + +void *MapFile(const char *path, size_t offset, size_t size, size_t *psize, bool ro) +{ + int fd = open(path, ro ? O_RDONLY : O_RDWR); + if (fd <= 0) + { + return NULL; + } + + if (0 == size) + { + struct stat stat; + fstat(fd, &stat); + size = stat.st_size; + } + + if (NULL != psize) + { + *psize = size; + } + + void *base = mmap(NULL, size, ro ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); + close(fd); + + if (MAP_FAILED == base) + { + base = NULL; + } + + return base; +} + +bool WriteFile(const char *szFile, const char *szData, size_t sLen) +{ + if (NULL == szFile) + { + return false; + } + + FILE *fp = fopen(szFile, "wb"); + if (NULL != fp) + { + int64_t towrite = sLen; + if (NULL != szData) + { + while (towrite > 0) + { + int64_t nwrite = fwrite(szData + (sLen - towrite), 1, towrite, fp); + if (nwrite <= 0) + { + break; + } + towrite -= nwrite; + } + } + fclose(fp); + return (towrite > 0) ? false : true; + } + else + { + ZLog::ErrorV("WriteFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); + } + + return false; +} + +bool WriteFile(const char *szFile, const string &strData) +{ + return WriteFile(szFile, strData.data(), strData.size()); +} + +bool WriteFile(string &strData, const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szPath) + return WriteFile(szPath, strData); +} + +bool WriteFile(const char *szData, size_t sLen, const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szPath) + return WriteFile(szPath, szData, sLen); +} + +bool ReadFile(const char *szFile, string &strData) +{ + strData.clear(); + + if (!IsFileExists(szFile)) + { + return false; + } + + FILE *fp = fopen(szFile, "rb"); + if (NULL != fp) + { + strData.reserve(GetFileSize(fileno(fp))); + + char buf[4096] = {0}; + size_t nread = fread(buf, 1, 4096, fp); + while (nread > 0) + { + strData.append(buf, nread); + nread = fread(buf, 1, 4096, fp); + } + fclose(fp); + return true; + } + else + { + ZLog::ErrorV("ReadFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); + } + + return false; +} + +bool ReadFile(string &strData, const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szPath) + return ReadFile(szPath, strData); +} + +bool AppendFile(const char *szFile, const char *szData, size_t sLen) +{ + FILE *fp = fopen(szFile, "ab+"); + if (NULL != fp) + { + int64_t towrite = sLen; + while (towrite > 0) + { + int64_t nwrite = fwrite(szData + (sLen - towrite), 1, towrite, fp); + if (nwrite <= 0) + { + break; + } + towrite -= nwrite; + } + + fclose(fp); + return (towrite > 0) ? false : true; + } + else + { + ZLog::ErrorV("AppendFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); + } + return false; +} + +bool AppendFile(const char *szFile, const string &strData) +{ + return AppendFile(szFile, strData.data(), strData.size()); +} + +bool IsFolder(const char *szFolder) +{ + struct stat st; + stat(szFolder, &st); + return S_ISDIR(st.st_mode); +} + +bool IsFolderV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFolder) + return IsFolder(szFolder); +} + +bool CreateFolder(const char *szFolder) +{ + if (!IsFolder(szFolder)) + { +#if defined(WINDOWS) + return (0 == mkdir(szFolder)); +#else + return (0 == mkdir(szFolder, 0755)); +#endif + } + return false; +} + +bool CreateFolderV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFolder) + return CreateFolder(szFolder); +} + +int RemoveFolderCallBack(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + int ret = remove(fpath); + if (ret) + { + perror(fpath); + } + return ret; +} + +bool RemoveFolder(const char *szFolder) +{ + if (!IsFolder(szFolder)) + { + RemoveFile(szFolder); + return true; + } + return nftw(szFolder, RemoveFolderCallBack, 64, FTW_DEPTH | FTW_PHYS); +} + +bool RemoveFolderV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFolder) + return RemoveFolder(szFolder); +} + +bool RemoveFile(const char *szFile) +{ + return (0 == remove(szFile)); +} + +bool RemoveFileV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFile); + return RemoveFile(szFile); +} + +bool IsFileExists(const char *szFile) +{ + if (NULL == szFile) + { + return false; + } + return (0 == access(szFile, F_OK)); +} + +bool IsFileExistsV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFile) + return IsFileExists(szFile); +} + +bool IsZipFile(const char *szFile) +{ + if (NULL != szFile && !IsFolder(szFile)) + { + FILE *fp = fopen(szFile, "rb"); + if (NULL != fp) + { + uint8_t buf[2] = {0}; + fread(buf, 1, 2, fp); + fclose(fp); + return (0 == memcmp("PK", buf, 2)); + } + } + return false; +} +#define PATH_BUFFER_LENGTH 1024 + +string GetCanonicalizePath(const char *szPath) +{ + string strPath = szPath; + if (!strPath.empty()) + { + if ('/' != szPath[0]) + { + char path[PATH_MAX] = {0}; + +#if defined(WINDOWS) + + if (NULL != _fullpath((char *)"./", path, PATH_BUFFER_LENGTH)) + { + strPath = path; + strPath += "/"; + strPath += szPath; + } +#else + if (NULL != realpath("./", path)) + { + strPath = path; + strPath += "/"; + strPath += szPath; + } +#endif + } + StringReplace(strPath, "/./", "/"); + } + return strPath; +} + +int64_t GetFileSize(int fd) +{ + int64_t nSize = 0; + struct stat stbuf; + if (0 == fstat(fd, &stbuf)) + { + if (S_ISREG(stbuf.st_mode)) + { + nSize = stbuf.st_size; + } + } + return (nSize < 0 ? 0 : nSize); +} + +int64_t GetFileSize(const char *szFile) +{ + int64_t nSize = 0; + int fd = open(szFile, O_RDONLY); + if (fd >= 0) + { + nSize = GetFileSize(fd); + close(fd); + } + return nSize; +} + +int64_t GetFileSizeV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFile) + return GetFileSize(szFile); +} + +string GetFileSizeString(const char *szFile) +{ + return FormatSize(GetFileSize(szFile), 1024); +} + +string FormatSize(int64_t size, int64_t base) +{ + double fsize = 0; + char ret[64] = {0}; + if (size > base * base * base * base) + { + fsize = (size * 1.0) / (base * base * base * base); + sprintf(ret, "%.2f TB", fsize); + } + else if (size > base * base * base) + { + fsize = (size * 1.0) / (base * base * base); + sprintf(ret, "%.2f GB", fsize); + } + else if (size > base * base) + { + fsize = (size * 1.0) / (base * base); + sprintf(ret, "%.2f MB", fsize); + } + else if (size > base) + { + fsize = (size * 1.0) / (base); + sprintf(ret, "%.2f KB", fsize); + } + else + { + sprintf(ret, "%" PRId64 " B", size); + } + return ret; +} + +bool IsPathSuffix(const string &strPath, const char *suffix) +{ + size_t nPos = strPath.rfind(suffix); + if (string::npos != nPos) + { + if (nPos == (strPath.size() - strlen(suffix))) + { + return true; + } + } + return false; +} + +time_t GetUnixStamp() +{ + time_t ustime = 0; + time(&ustime); + return ustime; +} + +uint64_t GetMicroSecond() +{ + struct timeval tv = {0}; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +bool SystemExec(const char *szFormatCmd, ...) +{ + /*PARSEVALIST(szFormatCmd, szCmd) + + if (strlen(szCmd) <= 0) + { + return false; + } + + int status = system(szCmd); + + if (-1 == status) + { + ZLog::ErrorV("SystemExec: \"%s\", Error!\n", szCmd); + return false; + } + else + { +#if !defined(WINDOWS) + if (WIFEXITED(status)) + { + if (0 == WEXITSTATUS(status)) + { + return true; + } + else + { + ZLog::ErrorV("SystemExec: \"%s\", Failed! Exit-Status: %d\n", szCmd, WEXITSTATUS(status)); + return false; + } + } + else + { + return true; + } +#endif + } + */ + return false; +} + +uint16_t _Swap(uint16_t value) +{ + return ((value >> 8) & 0x00ff) | + ((value << 8) & 0xff00); +} + +uint32_t _Swap(uint32_t value) +{ + value = ((value >> 8) & 0x00ff00ff) | + ((value << 8) & 0xff00ff00); + value = ((value >> 16) & 0x0000ffff) | + ((value << 16) & 0xffff0000); + return value; +} + +uint64_t _Swap(uint64_t value) +{ + value = (value & 0x00000000ffffffffULL) << 32 | (value & 0xffffffff00000000ULL) >> 32; + value = (value & 0x0000ffff0000ffffULL) << 16 | (value & 0xffff0000ffff0000ULL) >> 16; + value = (value & 0x00ff00ff00ff00ffULL) << 8 | (value & 0xff00ff00ff00ff00ULL) >> 8; + return value; +} + +uint32_t ByteAlign(uint32_t uValue, uint32_t uAlign) +{ + return (uValue + (uAlign - uValue % uAlign)); +} + +const char *StringFormat(string &strFormat, const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szFormat) + strFormat = szFormat; + return strFormat.c_str(); +} + +string &StringReplace(string &context, const string &from, const string &to) +{ + size_t lookHere = 0; + size_t foundHere; + while ((foundHere = context.find(from, lookHere)) != string::npos) + { + context.replace(foundHere, from.size(), to); + lookHere = foundHere + to.size(); + } + return context; +} + +void StringSplit(const string &src, const string &split, vector &dest) +{ + size_t oldPos = 0; + size_t newPos = src.find(split, oldPos); + while (newPos != string::npos) + { + dest.push_back(src.substr(oldPos, newPos - oldPos)); + oldPos = newPos + split.size(); + newPos = src.find(split, oldPos); + } + if (oldPos < src.size()) + { + dest.push_back(src.substr(oldPos)); + } +} + +bool SHA1Text(const string &strData, string &strOutput) +{ + string strSHASum; + SHASum(E_SHASUM_TYPE_1, strData, strSHASum); + + strOutput.clear(); + char buf[16] = {0}; + for (size_t i = 0; i < strSHASum.size(); i++) + { + sprintf(buf, "%02x", (uint8_t)strSHASum[i]); + strOutput += buf; + } + return (!strOutput.empty()); +} + +void PrintSHASum(const char *prefix, const uint8_t *hash, uint32_t size, const char *suffix) +{ + ZLog::PrintV("%s", prefix); + for (uint32_t i = 0; i < size; i++) + { + ZLog::PrintV("%02x", hash[i]); + } + ZLog::PrintV("%s", suffix); +} + +void PrintSHASum(const char *prefix, const string &strSHASum, const char *suffix) +{ + PrintSHASum(prefix, (const uint8_t *)strSHASum.data(), strSHASum.size(), suffix); +} + +void PrintDataSHASum(const char *prefix, int nSumType, const string &strData, const char *suffix) +{ + string strSHASum; + SHASum(nSumType, strData, strSHASum); + PrintSHASum(prefix, strSHASum, suffix); +} + +void PrintDataSHASum(const char *prefix, int nSumType, uint8_t *data, size_t size, const char *suffix) +{ + string strSHASum; + SHASum(nSumType, data, size, strSHASum); + PrintSHASum(prefix, strSHASum, suffix); +} + +bool SHASum(int nSumType, uint8_t *data, size_t size, string &strOutput) +{ + strOutput.clear(); + if (1 == nSumType) + { + uint8_t hash[20]; + memset(hash, 0, 20); + SHA1(data, size, hash); + strOutput.append((const char *)hash, 20); + } + else + { + uint8_t hash[32]; + memset(hash, 0, 32); + SHA256(data, size, hash); + strOutput.append((const char *)hash, 32); + } + return true; +} + +bool SHASum(int nSumType, const string &strData, string &strOutput) +{ + return SHASum(nSumType, (uint8_t *)strData.data(), strData.size(), strOutput); +} + +bool SHASum(const string &strData, string &strSHA1, string &strSHA256) +{ + SHASum(E_SHASUM_TYPE_1, strData, strSHA1); + SHASum(E_SHASUM_TYPE_256, strData, strSHA256); + return (!strSHA1.empty() && !strSHA256.empty()); +} + +bool SHASumFile(const char *szFile, string &strSHA1, string &strSHA256) +{ + size_t sSize = 0; + uint8_t *pBase = (uint8_t *)MapFile(szFile, 0, 0, &sSize, true); + + SHASum(E_SHASUM_TYPE_1, pBase, sSize, strSHA1); + SHASum(E_SHASUM_TYPE_256, pBase, sSize, strSHA256); + + if (NULL != pBase && sSize > 0) + { + munmap(pBase, sSize); + } + return (!strSHA1.empty() && !strSHA256.empty()); +} + +bool SHASumBase64(const string &strData, string &strSHA1Base64, string &strSHA256Base64) +{ + ZBase64 b64; + string strSHA1; + string strSHA256; + SHASum(strData, strSHA1, strSHA256); + strSHA1Base64 = b64.Encode(strSHA1); + strSHA256Base64 = b64.Encode(strSHA256); + return (!strSHA1Base64.empty() && !strSHA256Base64.empty()); +} + +bool SHASumBase64File(const char *szFile, string &strSHA1Base64, string &strSHA256Base64) +{ + ZBase64 b64; + string strSHA1; + string strSHA256; + SHASumFile(szFile, strSHA1, strSHA256); + strSHA1Base64 = b64.Encode(strSHA1); + strSHA256Base64 = b64.Encode(strSHA256); + return (!strSHA1Base64.empty() && !strSHA256Base64.empty()); +} + +ZBuffer::ZBuffer() +{ + m_pData = NULL; + m_uSize = 0; +} + +ZBuffer::~ZBuffer() +{ + Free(); +} + +char *ZBuffer::GetBuffer(uint32_t uSize) +{ + if (uSize <= m_uSize) + { + return m_pData; + } + + char *pData = (char *)realloc(m_pData, uSize); + if (NULL == pData) + { + Free(); + return NULL; + } + + m_pData = pData; + m_uSize = uSize; + return m_pData; +} + +void ZBuffer::Free() +{ + if (NULL != m_pData) + { + free(m_pData); + } + m_pData = NULL; + m_uSize = 0; +} + +ZTimer::ZTimer() +{ + Reset(); +} + +uint64_t ZTimer::Reset() +{ + m_uBeginTime = GetMicroSecond(); + return m_uBeginTime; +} + +uint64_t ZTimer::Print(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szFormat) + uint64_t uElapse = GetMicroSecond() - m_uBeginTime; + ZLog::PrintV("%s (%.03fs, %lluus)\n", szFormat, uElapse / 1000000.0, uElapse); + return Reset(); +} + +uint64_t ZTimer::PrintResult(bool bSuccess, const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szFormat) + uint64_t uElapse = GetMicroSecond() - m_uBeginTime; + ZLog::PrintResultV(bSuccess, "%s (%.03fs, %lluus)\n", szFormat, uElapse / 1000000.0, uElapse); + return Reset(); +} + +int ZLog::g_nLogLevel = ZLog::E_INFO; +vector ZLog::logs; + +void ZLog::SetLogLever(int nLogLevel) +{ + g_nLogLevel = nLogLevel; +} + +void ZLog::writeToLogFile(const std::string& message) { +// const char* documentsPath = getDocumentsDirectory(); +// std::string logFilePath = std::string(documentsPath) + "/logs.txt"; +// +// std::ofstream logFile(logFilePath, std::ios_base::app); +// if (logFile.is_open()) { +// logFile << message; +// logFile.close(); +// } else { +// std::cerr << "Failed to open log file: " << logFilePath << std::endl; +// } + writeToNSLog(message.data()); + logs.push_back(message); +} + +void ZLog::Print(int nLevel, const char *szLog) +{ + if (g_nLogLevel >= nLevel) + { + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::PrintV(int nLevel, const char *szFormatArgs, ...) { + if (g_nLogLevel >= nLevel) { + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +bool ZLog::Error(const char *szLog) +{ + write(STDOUT_FILENO, "\033[31m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +bool ZLog::ErrorV(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, "\033[31m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +bool ZLog::Success(const char *szLog) +{ + write(STDOUT_FILENO, "\033[32m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return true; +} + +bool ZLog::SuccessV(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, "\033[32m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return true; +} + +bool ZLog::PrintResult(bool bSuccess, const char *szLog) +{ + return bSuccess ? Success(szLog) : Error(szLog); +} + +bool ZLog::PrintResultV(bool bSuccess, const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + return bSuccess ? Success(szLog) : Error(szLog); +} + +bool ZLog::Warn(const char *szLog) +{ + write(STDOUT_FILENO, "\033[33m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +bool ZLog::WarnV(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, "\033[33m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +void ZLog::Print(const char *szLog) +{ + if (g_nLogLevel >= E_INFO) + { + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::PrintV(const char *szFormatArgs, ...) +{ + if (g_nLogLevel >= E_INFO) + { + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::Debug(const char *szLog) +{ + if (g_nLogLevel >= E_DEBUG) + { + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::DebugV(const char *szFormatArgs, ...) +{ + if (g_nLogLevel >= E_DEBUG) + { + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +bool ZLog::IsDebug() +{ + return (E_DEBUG == g_nLogLevel); +} diff --git a/zsign/common/common.h b/zsign/common/common.h new file mode 100644 index 0000000..d84a2e4 --- /dev/null +++ b/zsign/common/common.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +using namespace std; + +#define LE(x) _Swap(x) +#define BE(x) _Swap(x) + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +#endif + +uint16_t _Swap(uint16_t value); +uint32_t _Swap(uint32_t value); +uint64_t _Swap(uint64_t value); + +bool ReadFile(const char *szFile, string &strData); +bool ReadFile(string &strData, const char *szFormatPath, ...); +bool WriteFile(const char *szFile, const string &strData); +bool WriteFile(const char *szFile, const char *szData, size_t sLen); +bool WriteFile(string &strData, const char *szFormatPath, ...); +bool WriteFile(const char *szData, size_t sLen, const char *szFormatPath, ...); +bool AppendFile(const char *szFile, const string &strData); +bool AppendFile(const char *szFile, const char *szData, size_t sLen); +bool AppendFile(const string &strData, const char *szFormatPath, ...); +bool IsRegularFile(const char *szFile); +bool IsFolder(const char *szFolder); +bool IsFolderV(const char *szFormatPath, ...); +bool CreateFolder(const char *szFolder); +bool CreateFolderV(const char *szFormatPath, ...); +bool RemoveFile(const char *szFile); +bool RemoveFileV(const char *szFormatPath, ...); +bool RemoveFolder(const char *szFolder); +bool RemoveFolderV(const char *szFormatPath, ...); +bool IsFileExists(const char *szFile); +bool IsFileExistsV(const char *szFormatPath, ...); +int64_t GetFileSize(int fd); +int64_t GetFileSize(const char *szFile); +int64_t GetFileSizeV(const char *szFormatPath, ...); +string GetFileSizeString(const char *szFile); +bool IsZipFile(const char *szFile); +string GetCanonicalizePath(const char *szPath); +void *MapFile(const char *path, size_t offset, size_t size, size_t *psize, bool ro); +bool IsPathSuffix(const string &strPath, const char *suffix); + +const char *StringFormat(string &strFormat, const char *szFormatArgs, ...); +string &StringReplace(string &context, const string &from, const string &to); +void StringSplit(const string &src, const string &split, vector &dest); + +string FormatSize(int64_t size, int64_t base = 1024); +time_t GetUnixStamp(); +uint64_t GetMicroSecond(); +bool SystemExec(const char *szFormatCmd, ...); +uint32_t ByteAlign(uint32_t uValue, uint32_t uAlign); + +enum +{ + E_SHASUM_TYPE_1 = 1, + E_SHASUM_TYPE_256 = 2, +}; + +bool SHASum(int nSumType, uint8_t *data, size_t size, string &strOutput); +bool SHASum(int nSumType, const string &strData, string &strOutput); +bool SHASum(const string &strData, string &strSHA1, string &strSHA256); +bool SHA1Text(const string &strData, string &strOutput); +bool SHASumFile(const char *szFile, string &strSHA1, string &strSHA256); +bool SHASumBase64(const string &strData, string &strSHA1Base64, string &strSHA256Base64); +bool SHASumBase64File(const char *szFile, string &strSHA1Base64, string &strSHA256Base64); +void PrintSHASum(const char *prefix, const uint8_t *hash, uint32_t size, const char *suffix = "\n"); +void PrintSHASum(const char *prefix, const string &strSHASum, const char *suffix = "\n"); +void PrintDataSHASum(const char *prefix, int nSumType, const string &strData, const char *suffix = "\n"); +void PrintDataSHASum(const char *prefix, int nSumType, uint8_t *data, size_t size, const char *suffix = "\n"); + +class ZBuffer +{ +public: + ZBuffer(); + ~ZBuffer(); + +public: + char *GetBuffer(uint32_t uSize); + +private: + void Free(); + +private: + char *m_pData; + uint32_t m_uSize; +}; + +class ZTimer +{ +public: + ZTimer(); + +public: + uint64_t Reset(); + uint64_t Print(const char *szFormatArgs, ...); + uint64_t PrintResult(bool bSuccess, const char *szFormatArgs, ...); + +private: + uint64_t m_uBeginTime; +}; + +class ZLog +{ +public: + enum eLogType + { + E_NONE = 0, + E_ERROR = 1, + E_WARN = 2, + E_INFO = 3, + E_DEBUG = 4 + }; + +public: + static bool IsDebug(); + static void Print(const char *szLog); + static void PrintV(const char *szFormatArgs, ...); + static void Debug(const char *szLog); + static void DebugV(const char *szFormatArgs, ...); + static bool Warn(const char *szLog); + static bool WarnV(const char *szFormatArgs, ...); + static bool Error(const char *szLog); + static bool ErrorV(const char *szFormatArgs, ...); + static bool Success(const char *szLog); + static bool SuccessV(const char *szFormatArgs, ...); + static bool PrintResult(bool bSuccess, const char *szLog); + static bool PrintResultV(bool bSuccess, const char *szFormatArgs, ...); + static void Print(int nLevel, const char *szLog); + static void PrintV(int nLevel, const char *szFormatArgs, ...); + static void SetLogLever(int nLogLevel); + static vector logs; + +private: + static int g_nLogLevel; + static void writeToLogFile(const std::string& message); + +}; diff --git a/zsign/common/json.cpp b/zsign/common/json.cpp new file mode 100644 index 0000000..99721bb --- /dev/null +++ b/zsign/common/json.cpp @@ -0,0 +1,3061 @@ +#include "json.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "base64.h" + +#ifndef WIN32 +#define _atoi64(val) strtoll(val, NULL, 10) +#endif + +const JValue JValue::null; +const string JValue::nullData; + +JValue::JValue(TYPE type) : m_eType(type) +{ + m_Value.vFloat = 0; +} + +JValue::JValue(int val) : m_eType(E_INT) +{ + m_Value.vInt64 = val; +} + +JValue::JValue(int64_t val) : m_eType(E_INT) +{ + m_Value.vInt64 = val; +} + +JValue::JValue(bool val) : m_eType(E_BOOL) +{ + m_Value.vBool = val; +} + +JValue::JValue(double val) : m_eType(E_FLOAT) +{ + m_Value.vFloat = val; +} + +JValue::JValue(const char *val) : m_eType(E_STRING) +{ + m_Value.vString = NewString(val); +} + +JValue::JValue(const string &val) : m_eType(E_STRING) +{ + m_Value.vString = NewString(val.c_str()); +} + +JValue::JValue(const JValue &other) +{ + CopyValue(other); +} + +JValue::JValue(const char *val, size_t len) : m_eType(E_DATA) +{ + m_Value.vData = new string(); + m_Value.vData->append(val, len); +} + +JValue::~JValue() +{ + Free(); +} + +void JValue::clear() +{ + Free(); +} + +bool JValue::isInt() const +{ + return (E_INT == m_eType); +} + +bool JValue::isNull() const +{ + return (E_NULL == m_eType); +} + +bool JValue::isBool() const +{ + return (E_BOOL == m_eType); +} + +bool JValue::isFloat() const +{ + return (E_FLOAT == m_eType); +} + +bool JValue::isString() const +{ + return (E_STRING == m_eType); +} + +bool JValue::isArray() const +{ + return (E_ARRAY == m_eType); +} + +bool JValue::isObject() const +{ + return (E_OBJECT == m_eType); +} + +bool JValue::isEmpty() const +{ + switch (m_eType) + { + case E_NULL: + return true; + break; + case E_INT: + return (0 == m_Value.vInt64); + break; + case E_BOOL: + return (false == m_Value.vBool); + break; + case E_FLOAT: + return (0 == m_Value.vFloat); + break; + case E_ARRAY: + case E_OBJECT: + return (0 == size()); + break; + case E_STRING: + return (0 == strlen(asCString())); + case E_DATE: + return (0 == m_Value.vDate); + break; + case E_DATA: + return (NULL == m_Value.vData) ? true : m_Value.vData->empty(); + break; + } + return true; +} + +JValue::operator const char *() const +{ + return asCString(); +} + +JValue::operator int() const +{ + return asInt(); +} + +JValue::operator int64_t() const +{ + return asInt64(); +} + +JValue::operator double() const +{ + return asFloat(); +} + +JValue::operator string() const +{ + return asCString(); +} + +JValue::operator bool() const +{ + return asBool(); +} + +char *JValue::NewString(const char *cstr) +{ + char *str = NULL; + if (NULL != cstr) + { + size_t len = (strlen(cstr) + 1) * sizeof(char); + str = (char *)malloc(len); + memcpy(str, cstr, len); + } + return str; +} + +void JValue::CopyValue(const JValue &src) +{ + m_eType = src.m_eType; + switch (m_eType) + { + case E_ARRAY: + m_Value.vArray = (NULL == src.m_Value.vArray) ? NULL : new vector(*(src.m_Value.vArray)); + break; + case E_OBJECT: + m_Value.vObject = (NULL == src.m_Value.vObject) ? NULL : new map(*(src.m_Value.vObject)); + break; + case E_STRING: + m_Value.vString = (NULL == src.m_Value.vString) ? NULL : NewString(src.m_Value.vString); + break; + case E_DATA: + { + if (NULL != src.m_Value.vData) + { + m_Value.vData = new string(); + *m_Value.vData = *src.m_Value.vData; + } + else + { + m_Value.vData = NULL; + } + } + break; + default: + m_Value = src.m_Value; + break; + } +} + +void JValue::Free() +{ + switch (m_eType) + { + case E_INT: + { + m_Value.vInt64 = 0; + } + break; + case E_BOOL: + { + m_Value.vBool = false; + } + break; + case E_FLOAT: + { + m_Value.vFloat = 0.0; + } + break; + case E_STRING: + { + if (NULL != m_Value.vString) + { + free(m_Value.vString); + m_Value.vString = NULL; + } + } + break; + case E_ARRAY: + { + if (NULL != m_Value.vArray) + { + delete m_Value.vArray; + m_Value.vArray = NULL; + } + } + break; + case E_OBJECT: + { + if (NULL != m_Value.vObject) + { + delete m_Value.vObject; + m_Value.vObject = NULL; + } + } + break; + case E_DATE: + { + m_Value.vDate = 0; + } + break; + case E_DATA: + { + if (NULL != m_Value.vData) + { + delete m_Value.vData; + m_Value.vData = NULL; + } + } + break; + default: + break; + } + m_eType = E_NULL; +} + +JValue &JValue::operator=(const JValue &other) +{ + if (this != &other) + { + Free(); + CopyValue(other); + } + return (*this); +} + +JValue::TYPE JValue::type() const +{ + return m_eType; +} + +int JValue::asInt() const +{ + return (int)asInt64(); +} + +int64_t JValue::asInt64() const +{ + switch (m_eType) + { + case E_INT: + return m_Value.vInt64; + break; + case E_BOOL: + return m_Value.vBool ? 1 : 0; + break; + case E_FLOAT: + return int(m_Value.vFloat); + break; + case E_STRING: + return _atoi64(asCString()); + break; + default: + break; + } + return 0; +} + +double JValue::asFloat() const +{ + switch (m_eType) + { + case E_INT: + return double(m_Value.vInt64); + break; + case E_BOOL: + return m_Value.vBool ? 1.0 : 0.0; + break; + case E_FLOAT: + return m_Value.vFloat; + break; + case E_STRING: + return atof(asCString()); + break; + default: + break; + } + return 0.0; +} + +bool JValue::asBool() const +{ + switch (m_eType) + { + case E_BOOL: + return m_Value.vBool; + break; + case E_INT: + return (0 != m_Value.vInt64); + break; + case E_FLOAT: + return (0.0 != m_Value.vFloat); + break; + case E_ARRAY: + return (NULL == m_Value.vArray) ? false : (m_Value.vArray->size() > 0); + break; + case E_OBJECT: + return (NULL == m_Value.vObject) ? false : (m_Value.vObject->size() > 0); + break; + case E_STRING: + return (NULL == m_Value.vString) ? false : (strlen(m_Value.vString) > 0); + break; + case E_DATE: + return (m_Value.vDate > 0); + break; + case E_DATA: + return (NULL == m_Value.vData) ? false : (m_Value.vData->size() > 0); + break; + default: + break; + } + return false; +} + +string JValue::asString() const +{ + switch (m_eType) + { + case E_BOOL: + return m_Value.vBool ? "true" : "false"; + break; + case E_INT: + { + char buf[256]; + sprintf(buf, "%" PRId64, m_Value.vInt64); + return buf; + } + break; + case E_FLOAT: + { + char buf[256]; + sprintf(buf, "%lf", m_Value.vFloat); + return buf; + } + break; + case E_ARRAY: + return "array"; + break; + case E_OBJECT: + return "object"; + break; + case E_STRING: + return (NULL == m_Value.vString) ? "" : m_Value.vString; + break; + case E_DATE: + return "date"; + break; + case E_DATA: + return "data"; + break; + default: + break; + } + return ""; +} + +const char *JValue::asCString() const +{ + if (E_STRING == m_eType && NULL != m_Value.vString) + { + return m_Value.vString; + } + return ""; +} + +size_t JValue::size() const +{ + switch (m_eType) + { + case E_ARRAY: + return (NULL == m_Value.vArray) ? 0 : m_Value.vArray->size(); + break; + case E_OBJECT: + return (NULL == m_Value.vObject) ? 0 : m_Value.vObject->size(); + break; + case E_DATA: + return (NULL == m_Value.vData) ? 0 : m_Value.vData->size(); + break; + default: + break; + } + return 0; +} + +JValue &JValue::operator[](int index) +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +const JValue &JValue::operator[](int index) const +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +JValue &JValue::operator[](int64_t index) +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +const JValue &JValue::operator[](int64_t index) const +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +JValue &JValue::operator[](size_t index) +{ + if (E_ARRAY != m_eType || NULL == m_Value.vArray) + { + Free(); + m_eType = E_ARRAY; + m_Value.vArray = new vector(); + } + + size_t sum = m_Value.vArray->size(); + if (sum <= index) + { + size_t fill = index - sum; + for (size_t i = 0; i <= fill; i++) + { + m_Value.vArray->push_back(null); + } + } + + return m_Value.vArray->at(index); +} + +const JValue &JValue::operator[](size_t index) const +{ + if (E_ARRAY == m_eType && NULL != m_Value.vArray) + { + if (index < m_Value.vArray->size()) + { + return m_Value.vArray->at(index); + } + } + return null; +} + +JValue &JValue::operator[](const string &key) +{ + return (*this)[key.c_str()]; +} + +const JValue &JValue::operator[](const string &key) const +{ + return (*this)[key.c_str()]; +} + +JValue &JValue::operator[](const char *key) +{ + map::iterator it; + if (E_OBJECT != m_eType || NULL == m_Value.vObject) + { + Free(); + m_eType = E_OBJECT; + m_Value.vObject = new map(); + } + else + { + it = m_Value.vObject->find(key); + if (it != m_Value.vObject->end()) + { + return it->second; + } + } + it = m_Value.vObject->insert(m_Value.vObject->end(), make_pair(key, null)); + return it->second; +} + +const JValue &JValue::operator[](const char *key) const +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + map::const_iterator it = m_Value.vObject->find(key); + if (it != m_Value.vObject->end()) + { + return it->second; + } + } + return null; +} + +bool JValue::has(const char *key) const +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + if (m_Value.vObject->end() != m_Value.vObject->find(key)) + { + return true; + } + } + + return false; +} + +int JValue::index(const char *ele) const +{ + if (E_ARRAY == m_eType && NULL != m_Value.vArray) + { + for (size_t i = 0; i < m_Value.vArray->size(); i++) + { + if (ele == (*m_Value.vArray)[i].asString()) + { + return (int)i; + } + } + } + + return -1; +} + +JValue &JValue::at(int index) +{ + return (*this)[index]; +} + +JValue &JValue::at(size_t index) +{ + return (*this)[index]; +} + +JValue &JValue::at(const char *key) +{ + return (*this)[key]; +} + +bool JValue::remove(int index) +{ + if (index >= 0) + { + return remove((size_t)index); + } + return false; +} + +bool JValue::remove(size_t index) +{ + if (E_ARRAY == m_eType && NULL != m_Value.vArray) + { + if (index < m_Value.vArray->size()) + { + m_Value.vArray->erase(m_Value.vArray->begin() + index); + return true; + } + } + return false; +} + +bool JValue::remove(const char *key) +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + if (m_Value.vObject->end() != m_Value.vObject->find(key)) + { + m_Value.vObject->erase(key); + return !has(key); + } + } + return false; +} + +bool JValue::keys(vector &arrKeys) const +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + arrKeys.reserve(m_Value.vObject->size()); + map::iterator itbeg = m_Value.vObject->begin(); + map::iterator itend = m_Value.vObject->end(); + for (; itbeg != itend; itbeg++) + { + arrKeys.push_back((itbeg->first).c_str()); + } + return true; + } + return false; +} + +string JValue::write() const +{ + string strDoc; + return write(strDoc); +} + +const char *JValue::write(string &strDoc) const +{ + strDoc.clear(); + JWriter::FastWrite((*this), strDoc); + return strDoc.c_str(); +} + +bool JValue::read(const string &strdoc, string *pstrerr) +{ + return read(strdoc.c_str(), pstrerr); +} + +bool JValue::read(const char *pdoc, string *pstrerr) +{ + JReader reader; + bool bret = reader.parse(pdoc, *this); + if (!bret) + { + if (NULL != pstrerr) + { + reader.error(*pstrerr); + } + } + return bret; +} + +JValue &JValue::front() +{ + if (E_ARRAY == m_eType) + { + if (size() > 0) + { + return *(m_Value.vArray->begin()); + } + } + else if (E_OBJECT == m_eType) + { + if (size() > 0) + { + return m_Value.vObject->begin()->second; + } + } + return (*this); +} + +JValue &JValue::back() +{ + if (E_ARRAY == m_eType) + { + if (size() > 0) + { + return *(m_Value.vArray->rbegin()); + } + } + else if (E_OBJECT == m_eType) + { + if (size() > 0) + { + return m_Value.vObject->rbegin()->second; + } + } + return (*this); +} + +bool JValue::join(JValue &jv) +{ + if ((E_OBJECT == m_eType || E_NULL == m_eType) && E_OBJECT == jv.type()) + { + vector arrKeys; + jv.keys(arrKeys); + for (size_t i = 0; i < arrKeys.size(); i++) + { + (*this)[arrKeys[i]] = jv[arrKeys[i]]; + } + return true; + } + else if ((E_ARRAY == m_eType || E_NULL == m_eType) && E_ARRAY == jv.type()) + { + size_t count = this->size(); + for (size_t i = 0; i < jv.size(); i++) + { + (*this)[count] = jv[i]; + count++; + } + return true; + } + + return false; +} + +bool JValue::append(JValue &jv) +{ + if (E_ARRAY == m_eType || E_NULL == m_eType) + { + (*this)[((this->size() > 0) ? this->size() : 0)] = jv; + return true; + } + return false; +} + +bool JValue::push_back(int val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(bool val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(double val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(int64_t val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(const char *val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(const string &val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(const JValue &jval) +{ + if (E_ARRAY == m_eType || E_NULL == m_eType) + { + (*this)[size()] = jval; + return true; + } + return false; +} + +bool JValue::push_back(const char *val, size_t len) +{ + return push_back(JValue(val, len)); +} + +std::string JValue::styleWrite() const +{ + string strDoc; + return styleWrite(strDoc); +} + +const char *JValue::styleWrite(string &strDoc) const +{ + strDoc.clear(); + JWriter jw; + strDoc = jw.StyleWrite(*this); + return strDoc.c_str(); +} + +void JValue::assignDate(time_t val) +{ + Free(); + m_eType = E_DATE; + m_Value.vDate = val; +} + +void JValue::assignData(const char *val, size_t size) +{ + Free(); + m_eType = E_DATA; + m_Value.vData = new string(); + m_Value.vData->append(val, size); +} + +void JValue::assignDateString(time_t val) +{ + Free(); + m_eType = E_STRING; + m_Value.vString = NewString(JWriter::d2s(val).c_str()); +} + +time_t JValue::asDate() const +{ + switch (m_eType) + { + case E_DATE: + return m_Value.vDate; + break; + case E_STRING: + { + if (isDateString()) + { + tm ft = {0}; + sscanf(m_Value.vString + 5, "%04d-%02d-%02dT%02d:%02d:%02dZ", &ft.tm_year, &ft.tm_mon, &ft.tm_mday, &ft.tm_hour, &ft.tm_min, &ft.tm_sec); + ft.tm_mon -= 1; + ft.tm_year -= 1900; + return mktime(&ft); + } + } + break; + default: + break; + } + return 0; +} + +string JValue::asData() const +{ + switch (m_eType) + { + case E_DATA: + return (NULL == m_Value.vData) ? nullData : *m_Value.vData; + break; + case E_STRING: + { + if (isDataString()) + { + ZBase64 b64; + int nDataLen = 0; + const char *pdata = b64.Decode(m_Value.vString + 5, 0, &nDataLen); + string strdata; + strdata.append(pdata, nDataLen); + return strdata; + } + } + break; + default: + break; + } + + return nullData; +} + +bool JValue::isData() const +{ + return (E_DATA == m_eType); +} + +bool JValue::isDate() const +{ + return (E_DATE == m_eType); +} + +bool JValue::isDataString() const +{ + if (E_STRING == m_eType) + { + if (NULL != m_Value.vString) + { + if (strlen(m_Value.vString) >= 5) + { + if (0 == memcmp(m_Value.vString, "data:", 5)) + { + return true; + } + } + } + } + + return false; +} + +bool JValue::isDateString() const +{ + if (E_STRING == m_eType) + { + if (NULL != m_Value.vString) + { + if (25 == strlen(m_Value.vString)) + { + if (0 == memcmp(m_Value.vString, "date:", 5)) + { + const char *pdate = m_Value.vString + 5; + if ('T' == pdate[10] && 'Z' == pdate[19]) + { + return true; + } + } + } + } + } + + return false; +} + +bool JValue::readPList(const string &strdoc, string *pstrerr /*= NULL*/) +{ + return readPList(strdoc.data(), strdoc.size(), pstrerr); +} + +bool JValue::readPList(const char *pdoc, size_t len /*= 0*/, string *pstrerr /*= NULL*/) +{ + if (NULL == pdoc) + { + return false; + } + + if (0 == len) + { + len = strlen(pdoc); + } + + PReader reader; + bool bret = reader.parse(pdoc, len, *this); + if (!bret) + { + if (NULL != pstrerr) + { + reader.error(*pstrerr); + } + } + + return bret; +} + +bool JValue::readFile(const char *file, string *pstrerr /*= NULL*/) +{ + if (NULL != file) + { + FILE *fp = fopen(file, "rb"); + if (NULL != fp) + { + string strdata; + struct stat stbuf; + if (0 == fstat(fileno(fp), &stbuf)) + { + if (S_ISREG(stbuf.st_mode)) + { + strdata.reserve(stbuf.st_size); + } + } + + char buf[4096] = {0}; + int nread = (int)fread(buf, 1, 4096, fp); + while (nread > 0) + { + strdata.append(buf, nread); + nread = (int)fread(buf, 1, 4096, fp); + } + fclose(fp); + return read(strdata, pstrerr); + } + } + + return false; +} + +bool JValue::readPListFile(const char *file, string *pstrerr /*= NULL*/) +{ + if (NULL != file) + { + FILE *fp = fopen(file, "rb"); + if (NULL != fp) + { + string strdata; + struct stat stbuf; + if (0 == fstat(fileno(fp), &stbuf)) + { + if (S_ISREG(stbuf.st_mode)) + { + strdata.reserve(stbuf.st_size); + } + } + + char buf[4096] = {0}; + int nread = (int)fread(buf, 1, 4096, fp); + while (nread > 0) + { + strdata.append(buf, nread); + nread = (int)fread(buf, 1, 4096, fp); + } + fclose(fp); + return readPList(strdata, pstrerr); + } + } + + return false; +} + +bool JValue::WriteDataToFile(const char *file, const char *data, size_t len) +{ + if (NULL == file || NULL == data || len <= 0) + { + return false; + } + + FILE *fp = fopen(file, "wb"); + if (NULL != fp) + { + int towrite = (int)len; + while (towrite > 0) + { + int nwrite = (int)fwrite(data + (len - towrite), 1, towrite, fp); + if (nwrite <= 0) + { + break; + } + towrite -= nwrite; + } + + fclose(fp); + return (towrite > 0) ? false : true; + } + + return false; +} + +bool JValue::writeFile(const char *file) +{ + string strdata; + write(strdata); + return WriteDataToFile(file, strdata.data(), strdata.size()); +} + +bool JValue::writePListFile(const char *file) +{ + string strdata; + writePList(strdata); + return WriteDataToFile(file, strdata.data(), strdata.size()); +} + +bool JValue::styleWriteFile(const char *file) +{ + string strdata; + styleWrite(strdata); + return WriteDataToFile(file, strdata.data(), strdata.size()); +} + +bool JValue::readPath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return readFile(file); +} + +bool JValue::readPListPath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return readPListFile(file); +} + +bool JValue::writePath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return writeFile(file); +} + +bool JValue::writePListPath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return writePListFile(file); +} + +bool JValue::styleWritePath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return styleWriteFile(file); +} + +string JValue::writePList() const +{ + string strDoc; + return writePList(strDoc); +} + +const char *JValue::writePList(string &strDoc) const +{ + strDoc.clear(); + PWriter::FastWrite((*this), strDoc); + return strDoc.c_str(); +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// +bool JReader::parse(const char *pdoc, JValue &root) +{ + root.clear(); + if (NULL != pdoc) + { + m_pBeg = pdoc; + m_pEnd = m_pBeg + strlen(pdoc); + m_pCur = m_pBeg; + m_pErr = m_pBeg; + m_strErr = "null"; + return readValue(root); + } + return false; +} + +bool JReader::readValue(JValue &jval) +{ + Token token; + readToken(token); + switch (token.type) + { + case Token::E_True: + jval = true; + break; + case Token::E_False: + jval = false; + break; + case Token::E_Null: + jval = JValue(); + break; + case Token::E_Number: + return decodeNumber(token, jval); + break; + case Token::E_ArrayBegin: + return readArray(jval); + break; + case Token::E_ObjectBegin: + return readObject(jval); + break; + case Token::E_String: + { + string strval; + bool bok = decodeString(token, strval); + if (bok) + { + jval = strval.c_str(); + } + return bok; + } + break; + default: + return addError("Syntax error: value, object or array expected.", token.pbeg); + break; + } + return true; +} + +bool JReader::readToken(Token &token) +{ + skipSpaces(); + token.pbeg = m_pCur; + switch (GetNextChar()) + { + case '{': + token.type = Token::E_ObjectBegin; + break; + case '}': + token.type = Token::E_ObjectEnd; + break; + case '[': + token.type = Token::E_ArrayBegin; + break; + case ']': + token.type = Token::E_ArrayEnd; + break; + case ',': + token.type = Token::E_ArraySeparator; + break; + case ':': + token.type = Token::E_MemberSeparator; + break; + case 0: + token.type = Token::E_End; + break; + case '"': + token.type = readString() ? Token::E_String : Token::E_Error; + break; + case '/': + case '#': + case ';': + { + skipComment(); + return readToken(token); + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + { + token.type = Token::E_Number; + readNumber(); + } + break; + case 't': + token.type = match("rue", 3) ? Token::E_True : Token::E_Error; + break; + case 'f': + token.type = match("alse", 4) ? Token::E_False : Token::E_Error; + break; + case 'n': + token.type = match("ull", 3) ? Token::E_Null : Token::E_Error; + break; + default: + token.type = Token::E_Error; + break; + } + token.pend = m_pCur; + return true; +} + +void JReader::skipSpaces() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + { + m_pCur++; + } + else + { + break; + } + } +} + +bool JReader::match(const char *pattern, int patternLength) +{ + if (m_pEnd - m_pCur < patternLength) + { + return false; + } + int index = patternLength; + while (index--) + { + if (m_pCur[index] != pattern[index]) + { + return false; + } + } + m_pCur += patternLength; + return true; +} + +void JReader::skipComment() +{ + char c = GetNextChar(); + if (c == '*') + { + while (m_pCur != m_pEnd) + { + char c = GetNextChar(); + if (c == '*' && *m_pCur == '/') + { + break; + } + } + } + else if (c == '/') + { + while (m_pCur != m_pEnd) + { + char c = GetNextChar(); + if (c == '\r' || c == '\n') + { + break; + } + } + } +} + +void JReader::readNumber() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if ((c >= '0' && c <= '9') || (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) + { + ++m_pCur; + } + else + { + break; + } + } +} + +bool JReader::readString() +{ + char c = 0; + while (m_pCur != m_pEnd) + { + c = GetNextChar(); + if ('\\' == c) + { + GetNextChar(); + } + else if ('"' == c) + { + break; + } + } + return ('"' == c); +} + +bool JReader::readObject(JValue &jval) +{ + string name; + Token tokenName; + jval = JValue(JValue::E_OBJECT); + while (readToken(tokenName)) + { + if (Token::E_ObjectEnd == tokenName.type) + { //empty + return true; + } + + if (Token::E_String != tokenName.type) + { + break; + } + + if (!decodeString(tokenName, name)) + { + return false; + } + + Token colon; + readToken(colon); + if (Token::E_MemberSeparator != colon.type) + { + return addError("Missing ':' after object member name", colon.pbeg); + } + + if (!readValue(jval[name.c_str()])) + { // error already set + return false; + } + + Token comma; + readToken(comma); + if (Token::E_ObjectEnd == comma.type) + { + return true; + } + + if (Token::E_ArraySeparator != comma.type) + { + return addError("Missing ',' or '}' in object declaration", comma.pbeg); + } + } + return addError("Missing '}' or object member name", tokenName.pbeg); +} + +bool JReader::readArray(JValue &jval) +{ + jval = JValue(JValue::E_ARRAY); + skipSpaces(); + if (']' == *m_pCur) // empty array + { + Token endArray; + readToken(endArray); + return true; + } + + size_t index = 0; + while (true) + { + if (!readValue(jval[index++])) + { //error already set + return false; + } + + Token token; + readToken(token); + if (Token::E_ArrayEnd == token.type) + { + break; + } + if (Token::E_ArraySeparator != token.type) + { + return addError("Missing ',' or ']' in array declaration", token.pbeg); + } + } + return true; +} + +bool JReader::decodeNumber(Token &token, JValue &jval) +{ + int64_t val = 0; + bool isNeg = false; + const char *pcur = token.pbeg; + if ('-' == *pcur) + { + pcur++; + isNeg = true; + } + for (const char *p = pcur; p != token.pend; p++) + { + char c = *p; + if ('.' == c || 'e' == c || 'E' == c) + { + return decodeDouble(token, jval); + } + else if (c < '0' || c > '9') + { + return addError("'" + string(token.pbeg, token.pend) + "' is not a number.", token.pbeg); + } + else + { + val = val * 10 + (c - '0'); + } + } + jval = isNeg ? -val : val; + return true; +} + +bool JReader::decodeDouble(Token &token, JValue &jval) +{ + const size_t szbuf = 512; + size_t len = size_t(token.pend - token.pbeg); + if (len <= szbuf) + { + char buf[szbuf]; + memcpy(buf, token.pbeg, len); + buf[len] = 0; + double val = 0; + if (1 == sscanf(buf, "%lf", &val)) + { + jval = val; + return true; + } + } + return addError("'" + string(token.pbeg, token.pend) + "' is too large or not a number.", token.pbeg); +} + +bool JReader::decodeString(Token &token, string &strdec) +{ + strdec = ""; + const char *pcur = token.pbeg + 1; + const char *pend = token.pend - 1; + strdec.reserve(size_t(token.pend - token.pbeg)); + while (pcur != pend) + { + char c = *pcur++; + if ('\\' == c) + { + if (pcur != pend) + { + char escape = *pcur++; + switch (escape) + { + case '"': + strdec += '"'; + break; + case '\\': + strdec += '\\'; + break; + case 'b': + strdec += '\b'; + break; + case 'f': + strdec += '\f'; + break; + case 'n': + strdec += '\n'; + break; + case 'r': + strdec += '\r'; + break; + case 't': + strdec += '\t'; + break; + case '/': + strdec += '/'; + break; + case 'u': + { // based on description from http://en.wikipedia.org/wiki/UTF-8 + + string strUnic; + strUnic.append(pcur, 4); + + pcur += 4; + + unsigned int cp = 0; + if (1 != sscanf(strUnic.c_str(), "%x", &cp)) + { + return addError("Bad escape sequence in string", pcur); + } + + string strUTF8; + + if (cp <= 0x7f) + { + strUTF8.resize(1); + strUTF8[0] = static_cast(cp); + } + else if (cp <= 0x7FF) + { + strUTF8.resize(2); + strUTF8[1] = static_cast(0x80 | (0x3f & cp)); + strUTF8[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) + { + strUTF8.resize(3); + strUTF8[2] = static_cast(0x80 | (0x3f & cp)); + strUTF8[1] = 0x80 | static_cast((0x3f & (cp >> 6))); + strUTF8[0] = 0xE0 | static_cast((0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) + { + strUTF8.resize(4); + strUTF8[3] = static_cast(0x80 | (0x3f & cp)); + strUTF8[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + strUTF8[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + strUTF8[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + strdec += strUTF8; + } + break; + default: + return addError("Bad escape sequence in string", pcur); + break; + } + } + else + { + return addError("Empty escape sequence in string", pcur); + } + } + else if ('"' == c) + { + break; + } + else + { + strdec += c; + } + } + return true; +} + +bool JReader::addError(const string &message, const char *ploc) +{ + m_pErr = ploc; + m_strErr = message; + return false; +} + +char JReader::GetNextChar() +{ + return (m_pCur == m_pEnd) ? 0 : *m_pCur++; +} + +void JReader::error(string &strmsg) const +{ + strmsg = ""; + int row = 1; + const char *pcur = m_pBeg; + const char *plast = m_pBeg; + while (pcur < m_pErr && pcur <= m_pEnd) + { + char c = *pcur++; + if (c == '\r' || c == '\n') + { + if (c == '\r' && *pcur == '\n') + { + pcur++; + } + row++; + plast = pcur; + } + } + char msg[64]; + sprintf(msg, "Error: Line %d, Column %d, ", row, int(m_pErr - plast) + 1); + strmsg += msg + m_strErr + "\n"; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +void JWriter::FastWrite(const JValue &jval, string &strDoc) +{ + strDoc = ""; + FastWriteValue(jval, strDoc); + //strDoc += "\n"; +} + +void JWriter::FastWriteValue(const JValue &jval, string &strDoc) +{ + switch (jval.type()) + { + case JValue::E_NULL: + strDoc += "null"; + break; + case JValue::E_INT: + strDoc += v2s(jval.asInt64()); + break; + case JValue::E_BOOL: + strDoc += jval.asBool() ? "true" : "false"; + break; + case JValue::E_FLOAT: + strDoc += v2s(jval.asFloat()); + break; + case JValue::E_STRING: + strDoc += v2s(jval.asCString()); + break; + case JValue::E_ARRAY: + { + strDoc += "["; + size_t usize = jval.size(); + for (size_t i = 0; i < usize; i++) + { + strDoc += (i > 0) ? "," : ""; + FastWriteValue(jval[i], strDoc); + } + strDoc += "]"; + } + break; + case JValue::E_OBJECT: + { + strDoc += "{"; + vector arrKeys; + jval.keys(arrKeys); + size_t usize = arrKeys.size(); + for (size_t i = 0; i < usize; i++) + { + const string &name = arrKeys[i]; + strDoc += (i > 0) ? "," : ""; + strDoc += v2s(name.c_str()) + ":"; + FastWriteValue(jval[name.c_str()], strDoc); + } + strDoc += "}"; + } + break; + case JValue::E_DATE: + { + strDoc += "\"date:"; + strDoc += d2s(jval.asDate()); + strDoc += "\""; + } + break; + case JValue::E_DATA: + { + strDoc += "\"data:"; + const string &strData = jval.asData(); + ZBase64 b64; + strDoc += b64.Encode(strData.data(), (int)strData.size()); + strDoc += "\""; + } + break; + } +} + +const string &JWriter::StyleWrite(const JValue &jval) +{ + m_strDoc = ""; + m_strTab = ""; + m_bAddChild = false; + StyleWriteValue(jval); + m_strDoc += "\n"; + return m_strDoc; +} + +void JWriter::StyleWriteValue(const JValue &jval) +{ + switch (jval.type()) + { + case JValue::E_NULL: + PushValue("null"); + break; + case JValue::E_INT: + PushValue(v2s(jval.asInt64())); + break; + case JValue::E_BOOL: + PushValue(jval.asBool() ? "true" : "false"); + break; + case JValue::E_FLOAT: + PushValue(v2s(jval.asFloat())); + break; + case JValue::E_STRING: + PushValue(v2s(jval.asCString())); + break; + case JValue::E_ARRAY: + StyleWriteArrayValue(jval); + break; + case JValue::E_OBJECT: + { + vector arrKeys; + jval.keys(arrKeys); + if (!arrKeys.empty()) + { + m_strDoc += '\n' + m_strTab + "{"; + m_strTab += '\t'; + size_t usize = arrKeys.size(); + for (size_t i = 0; i < usize; i++) + { + const string &name = arrKeys[i]; + m_strDoc += (i > 0) ? "," : ""; + m_strDoc += '\n' + m_strTab + v2s(name.c_str()) + " : "; + StyleWriteValue(jval[name]); + } + m_strTab.resize(m_strTab.size() - 1); + m_strDoc += '\n' + m_strTab + "}"; + } + else + { + PushValue("{}"); + } + } + break; + case JValue::E_DATE: + { + string strDoc; + strDoc += "\"date:"; + strDoc += d2s(jval.asDate()); + strDoc += "\""; + PushValue(strDoc); + } + break; + case JValue::E_DATA: + { + string strDoc; + strDoc += "\"data:"; + const string &strData = jval.asData(); + ZBase64 b64; + strDoc += b64.Encode(strData.data(), (int)strData.size()); + strDoc += "\""; + PushValue(strDoc); + } + break; + } +} + +void JWriter::StyleWriteArrayValue(const JValue &jval) +{ + size_t usize = jval.size(); + if (usize > 0) + { + bool isArrayMultiLine = isMultineArray(jval); + if (isArrayMultiLine) + { + m_strDoc += '\n' + m_strTab + "["; + m_strTab += '\t'; + bool hasChildValue = !m_childValues.empty(); + for (size_t i = 0; i < usize; i++) + { + m_strDoc += (i > 0) ? "," : ""; + if (hasChildValue) + { + m_strDoc += '\n' + m_strTab + m_childValues[i]; + } + else + { + m_strDoc += '\n' + m_strTab; + StyleWriteValue(jval[i]); + } + } + m_strTab.resize(m_strTab.size() - 1); + m_strDoc += '\n' + m_strTab + "]"; + } + else + { + m_strDoc += "[ "; + for (size_t i = 0; i < usize; ++i) + { + m_strDoc += (i > 0) ? ", " : ""; + m_strDoc += m_childValues[i]; + } + m_strDoc += " ]"; + } + } + else + { + PushValue("[]"); + } +} + +bool JWriter::isMultineArray(const JValue &jval) +{ + m_childValues.clear(); + size_t usize = jval.size(); + bool isMultiLine = (usize >= 25); + if (!isMultiLine) + { + for (size_t i = 0; i < usize; i++) + { + if (jval[i].size() > 0) + { + isMultiLine = true; + break; + } + } + } + if (!isMultiLine) + { + m_bAddChild = true; + m_childValues.reserve(usize); + size_t lineLength = 4 + (usize - 1) * 2; // '[ ' + ', '*n + ' ]' + for (size_t i = 0; i < usize; i++) + { + StyleWriteValue(jval[i]); + lineLength += m_childValues[i].length(); + } + m_bAddChild = false; + isMultiLine = lineLength >= 75; + } + return isMultiLine; +} + +void JWriter::PushValue(const string &strval) +{ + if (!m_bAddChild) + { + m_strDoc += strval; + } + else + { + m_childValues.push_back(strval); + } +} + +string JWriter::v2s(int64_t val) +{ + char buf[32]; + sprintf(buf, "%" PRId64, val); + return buf; +} + +string JWriter::v2s(double val) +{ + char buf[512]; + sprintf(buf, "%g", val); + return buf; +} + +string JWriter::d2s(time_t t) +{ + //t = (t > 0x7933F8EFF) ? (0x7933F8EFF - 1) : t; + + tm ft = {0}; + +#ifdef _WIN32 + localtime_s(&ft, &t); +#else + localtime_r(&t, &ft); +#endif + + ft.tm_year = (ft.tm_year < 0) ? 0 : ft.tm_year; + ft.tm_mon = (ft.tm_mon < 0) ? 0 : ft.tm_mon; + ft.tm_mday = (ft.tm_mday < 0) ? 0 : ft.tm_mday; + ft.tm_hour = (ft.tm_hour < 0) ? 0 : ft.tm_hour; + ft.tm_min = (ft.tm_min < 0) ? 0 : ft.tm_min; + ft.tm_sec = (ft.tm_sec < 0) ? 0 : ft.tm_sec; + + char szDate[64] = {0}; + sprintf(szDate, "%04d-%02d-%02dT%02d:%02d:%02dZ", ft.tm_year + 1900, ft.tm_mon + 1, ft.tm_mday, ft.tm_hour, ft.tm_min, ft.tm_sec); + return szDate; +} + +string JWriter::v2s(const char *pstr) +{ + if (NULL != strpbrk(pstr, "\"\\\b\f\n\r\t")) + { + string ret; + ret.reserve(strlen(pstr) * 2 + 3); + ret += "\""; + for (const char *c = pstr; 0 != *c; c++) + { + switch (*c) + { + case '\\': + { + c++; + bool bUnicode = false; + if ('u' == *c) + { + bool bFlag = true; + for (int i = 1; i <= 4; i++) + { + if (!isdigit(*(c + i))) + { + bFlag = false; + break; + } + } + bUnicode = bFlag; + } + + if (true == bUnicode) + { + ret += "\\u"; + } + else + { + ret += "\\\\"; + c--; + } + } + break; + case '\"': + ret += "\\\""; + break; + case '\b': + ret += "\\b"; + break; + case '\f': + ret += "\\f"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\t': + ret += "\\t"; + break; + default: + ret += *c; + break; + } + } + ret += "\""; + return ret; + } + else + { + return string("\"") + pstr + "\""; + } +} + +std::string JWriter::vstring2s(const char *pstr) +{ + return string("\\\"") + pstr + "\\\""; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#define BE16TOH(x) ((((x)&0xFF00) >> 8) | (((x)&0x00FF) << 8)) + +#define BE32TOH(x) ((((x)&0xFF000000) >> 24) | (((x)&0x00FF0000) >> 8) | (((x)&0x0000FF00) << 8) | (((x)&0x000000FF) << 24)) + +#define BE64TOH(x) ((((x)&0xFF00000000000000ull) >> 56) | (((x)&0x00FF000000000000ull) >> 40) | (((x)&0x0000FF0000000000ull) >> 24) | (((x)&0x000000FF00000000ull) >> 8) | (((x)&0x00000000FF000000ull) << 8) | (((x)&0x0000000000FF0000ull) << 24) | (((x)&0x000000000000FF00ull) << 40) | (((x)&0x00000000000000FFull) << 56)) + +////////////////////////////////////////////////////////////////////////// +PReader::PReader() +{ + //xml + m_pBeg = NULL; + m_pEnd = NULL; + m_pCur = NULL; + m_pErr = NULL; + + //binary + m_pTrailer = NULL; + m_uObjects = 0; + m_uOffsetSize = 0; + m_pOffsetTable = 0; + m_uDictParamSize = 0; +} + +bool PReader::parse(const char *pdoc, size_t len, JValue &root) +{ + root.clear(); + if (NULL == pdoc) + { + return false; + } + + if (len < 30) + { + return false; + } + + if (0 == memcmp(pdoc, "bplist00", 8)) + { + return parseBinary(pdoc, len, root); + } + else + { + m_pBeg = pdoc; + m_pEnd = m_pBeg + len; + m_pCur = m_pBeg; + m_pErr = m_pBeg; + m_strErr = "null"; + + Token token; + readToken(token); + return readValue(root, token); + } +} + +bool PReader::readValue(JValue &pval, Token &token) +{ + switch (token.type) + { + case Token::E_True: + pval = true; + break; + case Token::E_False: + pval = false; + break; + case Token::E_Null: + pval = JValue(); + break; + case Token::E_Integer: + return decodeNumber(token, pval); + break; + case Token::E_Real: + return decodeDouble(token, pval); + break; + case Token::E_ArrayNull: + pval = JValue(JValue::E_ARRAY); + break; + case Token::E_ArrayBegin: + return readArray(pval); + break; + case Token::E_DictionaryNull: + pval = JValue(JValue::E_OBJECT); + break; + case Token::E_DictionaryBegin: + return readDictionary(pval); + break; + case Token::E_Date: + { + string strval; + decodeString(token, strval); + + tm ft = {0}; + sscanf(strval.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &ft.tm_year, &ft.tm_mon, &ft.tm_mday, &ft.tm_hour, &ft.tm_min, &ft.tm_sec); + ft.tm_mon -= 1; + ft.tm_year -= 1900; + pval.assignDate(mktime(&ft)); + } + break; + case Token::E_Data: + { + string strval; + decodeString(token, strval); + + ZBase64 b64; + int nDecLen = 0; + const char *data = b64.Decode(strval.data(), (int)strval.size(), &nDecLen); + pval.assignData(data, nDecLen); + } + break; + case Token::E_String: + { + string strval; + decodeString(token, strval, false); + XMLUnescape(strval); + pval = strval.c_str(); + } + break; + default: + return addError("Syntax error: value, dictionary or array expected.", token.pbeg); + break; + } + return true; +} + +bool PReader::readLabel(string &label) +{ + skipSpaces(); + + char c = *m_pCur++; + if ('<' != c) + { + return false; + } + + label.clear(); + label.reserve(10); + label += c; + + bool bEnd = false; + while (m_pCur != m_pEnd) + { + c = *m_pCur++; + if ('>' == c) + { + if ('/' == *(m_pCur - 1) || '?' == *(m_pCur - 1)) + { + label += *(m_pCur - 1); + } + + label += c; + break; + } + else if (' ' == c) + { + bEnd = true; + } + else if (!bEnd) + { + label += c; + } + } + + if ('>' != c) + { + label.clear(); + return false; + } + + return (!label.empty()); +} + +void PReader::endLabel(Token &token, const char *szLabel) +{ + string label; + readLabel(label); + if (szLabel != label) + { + token.type = Token::E_Error; + } +} + +bool PReader::readToken(Token &token) +{ + string label; + if (!readLabel(label)) + { + token.type = Token::E_Error; + return false; + } + + if ('?' == label.at(1) || '!' == label.at(1)) + { + return readToken(token); + } + + if ("" == label) + { + token.type = Token::E_DictionaryBegin; + } + else if ("" == label) + { + token.type = Token::E_DictionaryEnd; + } + else if ("" == label) + { + token.type = Token::E_ArrayBegin; + } + else if ("" == label) + { + token.type = Token::E_ArrayEnd; + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_Key : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.type = Token::E_Key; + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_String : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_Date : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_Data : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readNumber() ? Token::E_Integer : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readNumber() ? Token::E_Real : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.type = Token::E_True; + } + else if ("" == label) + { + token.type = Token::E_False; + } + else if ("" == label) + { + token.type = Token::E_ArrayNull; + } + else if ("" == label) + { + token.type = Token::E_DictionaryNull; + } + else if ("" == label || "" == label || "" == label || "" == label || "" == label) + { + token.type = Token::E_Null; + } + else if ("" == label) + { + return readToken(token); + } + else if ("" == label || "" == label) + { + token.type = Token::E_End; + } + else + { + token.type = Token::E_Error; + } + + return true; +} + +void PReader::skipSpaces() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + { + m_pCur++; + } + else + { + break; + } + } +} + +bool PReader::readNumber() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if ((c >= '0' && c <= '9') || (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) + { + ++m_pCur; + } + else + { + break; + } + } + return true; +} + +bool PReader::readString() +{ + while (m_pCur != m_pEnd) + { + if ('<' == *m_pCur) + { + break; + } + m_pCur++; + } + return ('<' == *m_pCur); +} + +bool PReader::readDictionary(JValue &pval) +{ + Token key; + string strKey; + pval = JValue(JValue::E_OBJECT); + while (readToken(key)) + { + if (Token::E_DictionaryEnd == key.type) + { //empty + return true; + } + + if (Token::E_Key != key.type) + { + break; + } + + strKey = ""; + if (!decodeString(key, strKey)) + { + return false; + } + XMLUnescape(strKey); + + Token val; + readToken(val); + if (!readValue(pval[strKey.c_str()], val)) + { + return false; + } + } + return addError("Missing '' or dictionary member name", key.pbeg); +} + +bool PReader::readArray(JValue &pval) +{ + pval = JValue(JValue::E_ARRAY); + + size_t index = 0; + while (true) + { + Token token; + readToken(token); + if (Token::E_ArrayEnd == token.type) + { + return true; + } + + if (!readValue(pval[index++], token)) + { + return false; + } + } + + return true; +} + +bool PReader::decodeNumber(Token &token, JValue &pval) +{ + int64_t val = 0; + bool isNeg = false; + const char *pcur = token.pbeg; + if ('-' == *pcur) + { + pcur++; + isNeg = true; + } + for (const char *p = pcur; p != token.pend; p++) + { + char c = *p; + if ('.' == c || 'e' == c || 'E' == c) + { + return decodeDouble(token, pval); + } + else if (c < '0' || c > '9') + { + return addError("'" + string(token.pbeg, token.pend) + "' is not a number.", token.pbeg); + } + else + { + val = val * 10 + (c - '0'); + } + } + pval = isNeg ? -val : val; + return true; +} + +bool PReader::decodeDouble(Token &token, JValue &pval) +{ + const size_t szbuf = 512; + size_t len = size_t(token.pend - token.pbeg); + if (len <= szbuf) + { + char buf[szbuf]; + memcpy(buf, token.pbeg, len); + buf[len] = 0; + double val = 0; + if (1 == sscanf(buf, "%lf", &val)) + { + pval = val; + return true; + } + } + return addError("'" + string(token.pbeg, token.pend) + "' is too large or not a number.", token.pbeg); +} + +bool PReader::decodeString(Token &token, string &strdec, bool filter) +{ + const char *pcur = token.pbeg; + const char *pend = token.pend; + strdec.reserve(size_t(token.pend - token.pbeg) + 6); + while (pcur != pend) + { + char c = *pcur++; + if (filter && ('\n' == c || '\r' == c || '\t' == c)) + { + continue; + } + strdec += c; + } + return true; +} + +bool PReader::addError(const string &message, const char *ploc) +{ + m_pErr = ploc; + m_strErr = message; + return false; +} + +void PReader::error(string &strmsg) const +{ + strmsg = ""; + int row = 1; + const char *pcur = m_pBeg; + const char *plast = m_pBeg; + while (pcur < m_pErr && pcur <= m_pEnd) + { + char c = *pcur++; + if (c == '\r' || c == '\n') + { + if (c == '\r' && *pcur == '\n') + { + pcur++; + } + row++; + plast = pcur; + } + } + char msg[64]; + sprintf(msg, "Error: Line %d, Column %d, ", row, int(m_pErr - plast) + 1); + strmsg += msg + m_strErr + "\n"; +} + +////////////////////////////////////////////////////////////////////////// +uint32_t PReader::getUInt24FromBE(const char *v) +{ + uint32_t ret = 0; + uint8_t *tmp = (uint8_t *)&ret; + memcpy(tmp, v, 3 * sizeof(char)); + byteConvert(tmp, sizeof(uint32_t)); + return ret; +} + +uint64_t PReader::getUIntVal(const char *v, size_t size) +{ + if (8 == size) + return BE64TOH(*((uint64_t *)v)); + else if (4 == size) + return BE32TOH(*((uint32_t *)v)); + else if (3 == size) + return getUInt24FromBE(v); + else if (2 == size) + return BE16TOH(*((uint16_t *)v)); + else + return *((uint8_t *)v); +} + +void PReader::byteConvert(uint8_t *v, size_t size) +{ + uint8_t tmp = 0; + for (size_t i = 0, j = 0; i < (size / 2); i++) + { + tmp = v[i]; + j = (size - 1) - i; + v[i] = v[j]; + v[j] = tmp; + } +} + +bool PReader::readUIntSize(const char *&pcur, size_t &size) +{ + JValue temp; + readBinaryValue(pcur, temp); + if (temp.isInt()) + { + size = (size_t)temp.asInt64(); + return true; + } + + assert(0); + return false; +} + +bool PReader::readUnicode(const char *pcur, size_t size, JValue &pv) +{ + if (0 == size) + { + pv = ""; + return false; + } + + uint16_t *unistr = (uint16_t *)malloc(2 * size); + memcpy(unistr, pcur, 2 * size); + for (size_t i = 0; i < size; i++) + { + byteConvert((uint8_t *)(unistr + i), 2); + } + + char *outbuf = (char *)malloc(3 * (size + 1)); + + size_t p = 0; + size_t i = 0; + uint16_t wc = 0; + while (i < size) + { + wc = unistr[i++]; + if (wc >= 0x800) + { + outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF)); + outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F)); + outbuf[p++] = (char)(0x80 + (wc & 0x3F)); + } + else if (wc >= 0x80) + { + outbuf[p++] = (char)(0xC0 + ((wc >> 6) & 0x1F)); + outbuf[p++] = (char)(0x80 + (wc & 0x3F)); + } + else + { + outbuf[p++] = (char)(wc & 0x7F); + } + } + + outbuf[p] = 0; + + pv = outbuf; + + free(outbuf); + outbuf = NULL; + free(unistr); + unistr = NULL; + + return true; +} + +bool PReader::readBinaryValue(const char *&pcur, JValue &pv) +{ + enum + { + BPLIST_NULL = 0x00, + BPLIST_FALSE = 0x08, + BPLIST_TRUE = 0x09, + BPLIST_FILL = 0x0F, + BPLIST_UINT = 0x10, + BPLIST_REAL = 0x20, + BPLIST_DATE = 0x30, + BPLIST_DATA = 0x40, + BPLIST_STRING = 0x50, + BPLIST_UNICODE = 0x60, + BPLIST_UNK_0x70 = 0x70, + BPLIST_UID = 0x80, + BPLIST_ARRAY = 0xA0, + BPLIST_SET = 0xC0, + BPLIST_DICT = 0xD0, + BPLIST_MASK = 0xF0 + }; + + uint8_t c = *pcur++; + uint8_t key = c & 0xF0; + uint8_t val = c & 0x0F; + + switch (key) + { + case BPLIST_NULL: + { + switch (val) + { + case BPLIST_TRUE: + { + pv = true; + } + break; + case BPLIST_FALSE: + { + pv = false; + } + break; + case BPLIST_NULL: + { + } + break; + default: + { + assert(0); + return false; + } + break; + } + } + break; + case BPLIST_UID: + case BPLIST_UINT: + { + size_t size = 1 << val; + switch (size) + { + case sizeof(uint8_t): + case sizeof(uint16_t): + case sizeof(uint32_t): + case sizeof(uint64_t): + { + pv = (int64_t)getUIntVal(pcur, size); + } + break; + default: + { + assert(0); + return false; + } + break; + }; + + pcur += size; + } + break; + case BPLIST_REAL: + { + size_t size = 1 << val; + + uint8_t *buf = (uint8_t *)malloc(size); + memcpy(buf, pcur, size); + byteConvert(buf, size); + + switch (size) + { + case sizeof(float): + pv = (double)(*(float *)buf); + case sizeof(double): + pv = (*(double *)buf); + break; + default: + { + assert(0); + free(buf); + return false; + } + break; + } + + free(buf); + } + break; + + case BPLIST_DATE: + { + if (3 == val) + { + size_t size = 1 << val; + uint8_t *buf = (uint8_t *)malloc(size); + memcpy(buf, pcur, size); + byteConvert(buf, size); + pv.assignDate(((time_t)(*(double *)buf)) + 978278400); + free(buf); + } + else + { + assert(0); + return false; + } + } + break; + + case BPLIST_DATA: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + pv.assignData(pcur, size); + } + break; + + case BPLIST_STRING: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + string strval; + strval.append(pcur, size); + strval.append(1, 0); + pv = strval.c_str(); + } + break; + + case BPLIST_UNICODE: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + readUnicode(pcur, size, pv); + } + break; + case BPLIST_ARRAY: + case BPLIST_UNK_0x70: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + for (size_t i = 0; i < size; i++) + { + uint64_t uIndex = getUIntVal((const char *)pcur + i * m_uDictParamSize, m_uDictParamSize); + if (uIndex < m_uObjects) + { + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uIndex * m_uOffsetSize, m_uOffsetSize)); + readBinaryValue(pval, pv[i]); + } + else + { + assert(0); + return false; + } + } + } + break; + + case BPLIST_SET: + case BPLIST_DICT: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + for (size_t i = 0; i < size; i++) + { + JValue pvKey; + JValue pvVal; + + uint64_t uKeyIndex = getUIntVal((const char *)pcur + i * m_uDictParamSize, m_uDictParamSize); + uint64_t uValIndex = getUIntVal((const char *)pcur + (i + size) * m_uDictParamSize, m_uDictParamSize); + + if (uKeyIndex < m_uObjects) + { + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uKeyIndex * m_uOffsetSize, m_uOffsetSize)); + readBinaryValue(pval, pvKey); + } + + if (uValIndex < m_uObjects) + { + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uValIndex * m_uOffsetSize, m_uOffsetSize)); + readBinaryValue(pval, pvVal); + } + + if (pvKey.isString() && !pvVal.isNull()) + { + pv[pvKey.asCString()] = pvVal; + } + } + } + break; + default: + { + assert(0); + return false; + } + } + + return true; +} + +bool PReader::parseBinary(const char *pbdoc, size_t len, JValue &pv) +{ + m_pBeg = pbdoc; + + m_pTrailer = m_pBeg + len - 26; + + m_uOffsetSize = m_pTrailer[0]; + m_uDictParamSize = m_pTrailer[1]; + m_uObjects = getUIntVal(m_pTrailer + 2, 8); + + if (0 == m_uObjects) + { + return false; + } + + m_pOffsetTable = m_pBeg + getUIntVal(m_pTrailer + 18, 8); + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable, m_uOffsetSize)); + return readBinaryValue(pval, pv); +} + +void PReader::XMLUnescape(string &strval) +{ + PWriter::StringReplace(strval, "&", "&"); + PWriter::StringReplace(strval, "<", "<"); + //PWriter::StringReplace(strval,">", ">"); //optional + //PWriter::StringReplace(strval, "'", "'"); //optional + //PWriter::StringReplace(strval, """, "\""); //optional +} + +////////////////////////////////////////////////////////////////////////// +void PWriter::FastWrite(const JValue &pval, string &strdoc) +{ + strdoc.clear(); + strdoc = "\n" + "\n" + "\n"; + + string strindent; + FastWriteValue(pval, strdoc, strindent); + + strdoc += ""; +} + +void PWriter::FastWriteValue(const JValue &pval, string &strdoc, string &strindent) +{ + if (pval.isObject()) + { + strdoc += strindent; + if (pval.isEmpty()) + { + strdoc += "\n"; + return; + } + strdoc += "\n"; + vector arrKeys; + if (pval.keys(arrKeys)) + { + strindent.push_back('\t'); + for (size_t i = 0; i < arrKeys.size(); i++) + { + if (!pval[arrKeys[i].c_str()].isNull()) + { + string strkey = arrKeys[i]; + XMLEscape(strkey); + strdoc += strindent; + strdoc += ""; + strdoc += strkey; + strdoc += "\n"; + FastWriteValue(pval[arrKeys[i].c_str()], strdoc, strindent); + } + } + strindent.erase(strindent.end() - 1); + } + strdoc += strindent; + strdoc += "\n"; + } + else if (pval.isArray()) + { + strdoc += strindent; + if (pval.isEmpty()) + { + strdoc += "\n"; + return; + } + strdoc += "\n"; + strindent.push_back('\t'); + for (size_t i = 0; i < pval.size(); i++) + { + FastWriteValue(pval[i], strdoc, strindent); + } + strindent.erase(strindent.end() - 1); + strdoc += strindent; + strdoc += "\n"; + } + else if (pval.isDate()) + { + strdoc += strindent; + strdoc += ""; + strdoc += JWriter::d2s(pval.asDate()); + strdoc += "\n"; + } + else if (pval.isData()) + { + ZBase64 b64; + string strdata = pval.asData(); + strdoc += strindent; + strdoc += "\n"; + strdoc += strindent; + strdoc += b64.Encode(strdata.data(), (int)strdata.size()); + strdoc += "\n"; + strdoc += strindent; + strdoc += "\n"; + } + else if (pval.isString()) + { + strdoc += strindent; + if (pval.isDateString()) + { + strdoc += ""; + strdoc += pval.asString().c_str() + 5; + strdoc += "\n"; + } + else if (pval.isDataString()) + { + strdoc += "\n"; + strdoc += strindent; + strdoc += pval.asString().c_str() + 5; + strdoc += "\n"; + strdoc += strindent; + strdoc += "\n"; + } + else + { + string strval = pval.asCString(); + XMLEscape(strval); + strdoc += ""; + strdoc += strval; + strdoc += "\n"; + } + } + else if (pval.isBool()) + { + strdoc += strindent; + strdoc += (pval.asBool() ? "\n" : "\n"); + } + else if (pval.isInt()) + { + strdoc += strindent; + strdoc += ""; + char temp[32] = {0}; + sprintf(temp, "%" PRId64, pval.asInt64()); + strdoc += temp; + strdoc += "\n"; + } + else if (pval.isFloat()) + { + strdoc += strindent; + strdoc += ""; + + double v = pval.asFloat(); + if (numeric_limits::infinity() == v) + { + strdoc += "+infinity"; + } + else + { + char temp[32] = {0}; + if (floor(v) == v) + { + sprintf(temp, "%" PRId64, (int64_t)v); + } + else + { + sprintf(temp, "%.15lf", v); + } + strdoc += temp; + } + + strdoc += "\n"; + } +} + +void PWriter::XMLEscape(string &strval) +{ + StringReplace(strval, "&", "&"); + StringReplace(strval, "<", "<"); + //StringReplace(strval, ">", ">"); //option + //StringReplace(strval, "'", "'"); //option + //StringReplace(strval, "\"", """); //option +} + +string &PWriter::StringReplace(string &context, const string &from, const string &to) +{ + size_t lookHere = 0; + size_t foundHere; + while ((foundHere = context.find(from, lookHere)) != string::npos) + { + context.replace(foundHere, from.size(), to); + lookHere = foundHere + to.size(); + } + return context; +} diff --git a/zsign/common/json.h b/zsign/common/json.h new file mode 100644 index 0000000..9ef6dd1 --- /dev/null +++ b/zsign/common/json.h @@ -0,0 +1,414 @@ +#ifndef JSON_INCLUDED +#define JSON_INCLUDED + +#ifdef _WIN32 + +typedef signed char int8_t; +typedef short int int16_t; +typedef int int32_t; +typedef long long int int64_t; +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long int uint64_t; + +#else + +#include +#include +#include + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + + + +class JValue +{ +public: + enum TYPE + { + E_NULL = 0, + E_INT, + E_BOOL, + E_FLOAT, + E_ARRAY, + E_OBJECT, + E_STRING, + E_DATE, + E_DATA, + }; + +public: + JValue(TYPE type = E_NULL); + JValue(int val); + JValue(bool val); + JValue(double val); + JValue(int64_t val); + JValue(const char *val); + JValue(const string &val); + JValue(const JValue &other); + JValue(const char *val, size_t len); + ~JValue(); + +public: + int asInt() const; + bool asBool() const; + double asFloat() const; + int64_t asInt64() const; + string asString() const; + const char *asCString() const; + time_t asDate() const; + string asData() const; + + void assignData(const char *val, size_t size); + void assignDate(time_t val); + void assignDateString(time_t val); + + TYPE type() const; + size_t size() const; + void clear(); + + JValue &at(int index); + JValue &at(size_t index); + JValue &at(const char *key); + + bool has(const char *key) const; + int index(const char *ele) const; + bool keys(vector &arrKeys) const; + + bool join(JValue &jv); + bool append(JValue &jv); + + bool remove(int index); + bool remove(size_t index); + bool remove(const char *key); + + JValue &back(); + JValue &front(); + + bool push_back(int val); + bool push_back(bool val); + bool push_back(double val); + bool push_back(int64_t val); + bool push_back(const char *val); + bool push_back(const string &val); + bool push_back(const JValue &jval); + bool push_back(const char *val, size_t len); + + bool isInt() const; + bool isNull() const; + bool isBool() const; + bool isFloat() const; + bool isArray() const; + bool isObject() const; + bool isString() const; + bool isEmpty() const; + bool isData() const; + bool isDate() const; + bool isDataString() const; + bool isDateString() const; + + operator int() const; + operator bool() const; + operator double() const; + operator int64_t() const; + operator string() const; + operator const char *() const; + + JValue &operator=(const JValue &other); + + JValue &operator[](int index); + const JValue &operator[](int index) const; + + JValue &operator[](size_t index); + const JValue &operator[](size_t index) const; + + JValue &operator[](int64_t index); + const JValue &operator[](int64_t index) const; + + JValue &operator[](const char *key); + const JValue &operator[](const char *key) const; + + JValue &operator[](const string &key); + const JValue &operator[](const string &key) const; + + friend bool operator==(const JValue &jv, const char *psz) + { + return (0 == strcmp(jv.asCString(), psz)); + } + + friend bool operator==(const char *psz, const JValue &jv) + { + return (0 == strcmp(jv.asCString(), psz)); + } + + friend bool operator!=(const JValue &jv, const char *psz) + { + return (0 != strcmp(jv.asCString(), psz)); + } + + friend bool operator!=(const char *psz, const JValue &jv) + { + return (0 != strcmp(jv.asCString(), psz)); + } + +private: + void Free(); + char *NewString(const char *cstr); + void CopyValue(const JValue &src); + bool WriteDataToFile(const char *file, const char *data, size_t len); + +public: + static const JValue null; + static const string nullData; + +private: + union HOLD { + bool vBool; + double vFloat; + int64_t vInt64; + char *vString; + vector *vArray; + map *vObject; + time_t vDate; + string *vData; + wchar_t *vUnicode; + } m_Value; + + TYPE m_eType; + +public: + string write() const; + const char *write(string &strDoc) const; + + string styleWrite() const; + const char *styleWrite(string &strDoc) const; + + bool read(const char *pdoc, string *pstrerr = NULL); + bool read(const string &strdoc, string *pstrerr = NULL); + + string writePList() const; + const char *writePList(string &strDoc) const; + + bool readPList(const string &strdoc, string *pstrerr = NULL); + bool readPList(const char *pdoc, size_t len = 0, string *pstrerr = NULL); + + bool readFile(const char *file, string *pstrerr = NULL); + bool readPListFile(const char *file, string *pstrerr = NULL); + + bool writeFile(const char *file); + bool writePListFile(const char *file); + bool styleWriteFile(const char *file); + + bool readPath(const char *path, ...); + bool readPListPath(const char *path, ...); + bool writePath(const char *path, ...); + bool writePListPath(const char *path, ...); + bool styleWritePath(const char *path, ...); +}; + +class JReader +{ +public: + bool parse(const char *pdoc, JValue &root); + void error(string &strmsg) const; + +private: + struct Token + { + enum TYPE + { + E_Error = 0, + E_End, + E_Null, + E_True, + E_False, + E_Number, + E_String, + E_ArrayBegin, + E_ArrayEnd, + E_ObjectBegin, + E_ObjectEnd, + E_ArraySeparator, + E_MemberSeparator + }; + TYPE type; + const char *pbeg; + const char *pend; + }; + + void skipSpaces(); + void skipComment(); + + bool match(const char *pattern, int patternLength); + + bool readToken(Token &token); + bool readValue(JValue &jval); + bool readArray(JValue &jval); + void readNumber(); + + bool readString(); + bool readObject(JValue &jval); + + bool decodeNumber(Token &token, JValue &jval); + bool decodeString(Token &token, string &decoded); + bool decodeDouble(Token &token, JValue &jval); + + char GetNextChar(); + bool addError(const string &message, const char *ploc); + +private: + const char *m_pBeg; + const char *m_pEnd; + const char *m_pCur; + const char *m_pErr; + string m_strErr; +}; + +class JWriter +{ +public: + static void FastWrite(const JValue &jval, string &strDoc); + static void FastWriteValue(const JValue &jval, string &strDoc); + +public: + const string &StyleWrite(const JValue &jval); + +private: + void PushValue(const string &strval); + void StyleWriteValue(const JValue &jval); + void StyleWriteArrayValue(const JValue &jval); + bool isMultineArray(const JValue &jval); + +public: + static string v2s(double val); + static string v2s(int64_t val); + static string v2s(const char *val); + + static string vstring2s(const char *val); + static string d2s(time_t t); + +private: + string m_strDoc; + string m_strTab; + bool m_bAddChild; + vector m_childValues; +}; + +////////////////////////////////////////////////////////////////////////// +class PReader +{ +public: + PReader(); + +public: + bool parse(const char *pdoc, size_t len, JValue &root); + void error(string &strmsg) const; + +private: + struct Token + { + enum TYPE + { + E_Error = 0, + E_End, + E_Null, + E_True, + E_False, + E_Key, + E_Data, + E_Date, + E_Integer, + E_Real, + E_String, + E_ArrayBegin, + E_ArrayEnd, + E_ArrayNull, + E_DictionaryBegin, + E_DictionaryEnd, + E_DictionaryNull, + E_ArraySeparator, + E_MemberSeparator + }; + + Token() + { + pbeg = NULL; + pend = NULL; + type = E_Error; + } + + TYPE type; + const char *pbeg; + const char *pend; + }; + + bool readToken(Token &token); + bool readLabel(string &label); + bool readValue(JValue &jval, Token &token); + bool readArray(JValue &jval); + bool readNumber(); + + bool readString(); + bool readDictionary(JValue &jval); + + void endLabel(Token &token, const char *szLabel); + + bool decodeNumber(Token &token, JValue &jval); + bool decodeString(Token &token, string &decoded, bool filter = true); + bool decodeDouble(Token &token, JValue &jval); + + void skipSpaces(); + bool addError(const string &message, const char *ploc); + +public: + bool parseBinary(const char *pbdoc, size_t len, JValue &pv); + +private: + uint32_t getUInt24FromBE(const char *v); + void byteConvert(uint8_t *v, size_t size); + uint64_t getUIntVal(const char *v, size_t size); + bool readUIntSize(const char *&pcur, size_t &size); + bool readBinaryValue(const char *&pcur, JValue &pv); + bool readUnicode(const char *pcur, size_t size, JValue &pv); + +public: + static void XMLUnescape(string &strval); + +private: //xml + const char *m_pBeg; + const char *m_pEnd; + const char *m_pCur; + const char *m_pErr; + string m_strErr; + +private: //binary + const char *m_pTrailer; + uint64_t m_uObjects; + uint8_t m_uOffsetSize; + const char *m_pOffsetTable; + uint8_t m_uDictParamSize; +}; + +class PWriter +{ +public: + static void FastWrite(const JValue &pval, string &strdoc); + static void FastWriteValue(const JValue &pval, string &strdoc, string &strindent); + +public: + static void XMLEscape(string &strval); + static string &StringReplace(string &context, const string &from, const string &to); +}; + +#endif // JSON_INCLUDED diff --git a/zsign/common/mach-o.h b/zsign/common/mach-o.h new file mode 100644 index 0000000..7c5deb9 --- /dev/null +++ b/zsign/common/mach-o.h @@ -0,0 +1,580 @@ +#pragma once +#include +// typedef int cpu_type_t; +// typedef int cpu_subtype_t; +// typedef int vm_prot_t; + +// /* +// * Capability bits used in the definition of cpu_type. +// */ +// #define CPU_ARCH_MASK 0xff000000 /* mask for architecture bits */ +// #define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */ +// #define CPU_ARCH_ABI64_32 0x02000000 + +// /* +// * Machine types known by all. +// */ +// #define CPU_TYPE_ANY -1 +// #define CPU_TYPE_VAX 1 +// #define CPU_TYPE_MC680x0 6 +// #define CPU_TYPE_X86 7 +// #define CPU_TYPE_I386 CPU_TYPE_X86 /* compatibility */ +// #define CPU_TYPE_MIPS 8 +// #define CPU_TYPE_MC98000 10 +// #define CPU_TYPE_HPPA 11 +// #define CPU_TYPE_ARM 12 +// #define CPU_TYPE_MC88000 13 +// #define CPU_TYPE_SPARC 14 +// #define CPU_TYPE_I860 15 +// #define CPU_TYPE_ALPHA 16 +// #define CPU_TYPE_POWERPC 18 +// #define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64) +// #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) +// #define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) +// #define CPU_TYPE_POWERPC64 (CPU_TYPE_POWERPC | CPU_ARCH_ABI64) + +// /* +// * Machine subtypes (these are defined here, instead of in a machine +// * dependent directory, so that any program can get all definitions +// * regardless of where is it compiled). +// */ + +// /* +// * Capability bits used in the definition of cpu_subtype. +// */ +// #define CPU_SUBTYPE_MASK 0xff000000 /* mask for feature flags */ +// #define CPU_SUBTYPE_LIB64 0x80000000 /* 64 bit libraries */ + +// /* +// * Object files that are hand-crafted to run on any +// * implementation of an architecture are tagged with +// * CPU_SUBTYPE_MULTIPLE. This functions essentially the same as +// * the "ALL" subtype of an architecture except that it allows us +// * to easily find object files that may need to be modified +// * whenever a new implementation of an architecture comes out. +// * +// * It is the responsibility of the implementor to make sure the +// * software handles unsupported implementations elegantly. +// */ +// #define CPU_SUBTYPE_MULTIPLE -1 +// #define CPU_SUBTYPE_LITTLE_ENDIAN 0 +// #define CPU_SUBTYPE_BIG_ENDIAN 1 + +// /* +// * I386 subtypes +// */ + +// #define CPU_SUBTYPE_INTEL(f, m) ((f) + ((m) << 4)) + +// #define CPU_SUBTYPE_I386_ALL CPU_SUBTYPE_INTEL(3, 0) +// #define CPU_SUBTYPE_386 CPU_SUBTYPE_INTEL(3, 0) +// #define CPU_SUBTYPE_486 CPU_SUBTYPE_INTEL(4, 0) +// #define CPU_SUBTYPE_486SX CPU_SUBTYPE_INTEL(4, 8) // 8 << 4 = 128 +// #define CPU_SUBTYPE_586 CPU_SUBTYPE_INTEL(5, 0) +// #define CPU_SUBTYPE_PENT CPU_SUBTYPE_INTEL(5, 0) +// #define CPU_SUBTYPE_PENTPRO CPU_SUBTYPE_INTEL(6, 1) +// #define CPU_SUBTYPE_PENTII_M3 CPU_SUBTYPE_INTEL(6, 3) +// #define CPU_SUBTYPE_PENTII_M5 CPU_SUBTYPE_INTEL(6, 5) +// #define CPU_SUBTYPE_CELERON CPU_SUBTYPE_INTEL(7, 6) +// #define CPU_SUBTYPE_CELERON_MOBILE CPU_SUBTYPE_INTEL(7, 7) +// #define CPU_SUBTYPE_PENTIUM_3 CPU_SUBTYPE_INTEL(8, 0) +// #define CPU_SUBTYPE_PENTIUM_3_M CPU_SUBTYPE_INTEL(8, 1) +// #define CPU_SUBTYPE_PENTIUM_3_XEON CPU_SUBTYPE_INTEL(8, 2) +// #define CPU_SUBTYPE_PENTIUM_M CPU_SUBTYPE_INTEL(9, 0) +// #define CPU_SUBTYPE_PENTIUM_4 CPU_SUBTYPE_INTEL(10, 0) +// #define CPU_SUBTYPE_PENTIUM_4_M CPU_SUBTYPE_INTEL(10, 1) +// #define CPU_SUBTYPE_ITANIUM CPU_SUBTYPE_INTEL(11, 0) +// #define CPU_SUBTYPE_ITANIUM_2 CPU_SUBTYPE_INTEL(11, 1) +// #define CPU_SUBTYPE_XEON CPU_SUBTYPE_INTEL(12, 0) +// #define CPU_SUBTYPE_XEON_MP CPU_SUBTYPE_INTEL(12, 1) + +// #define CPU_SUBTYPE_INTEL_FAMILY(x) ((x) & 15) +// #define CPU_SUBTYPE_INTEL_FAMILY_MAX 15 + +// #define CPU_SUBTYPE_INTEL_MODEL(x) ((x) >> 4) +// #define CPU_SUBTYPE_INTEL_MODEL_ALL 0 + +// /* +// * X86 subtypes. +// */ + +// #define CPU_SUBTYPE_X86_ALL 3 +// #define CPU_SUBTYPE_X86_64_ALL 3 +// #define CPU_SUBTYPE_X86_ARCH1 4 +// #define CPU_SUBTYPE_X86_64_H 8 + +// #define CPU_SUBTYPE_ARM_ALL 0 +// #define CPU_SUBTYPE_ARM_A500_ARCH 1 +// #define CPU_SUBTYPE_ARM_A500 2 +// #define CPU_SUBTYPE_ARM_A440 3 +// #define CPU_SUBTYPE_ARM_M4 4 +// #define CPU_SUBTYPE_ARM_V4T 5 +// #define CPU_SUBTYPE_ARM_V6 6 +// #define CPU_SUBTYPE_ARM_V5 7 +// #define CPU_SUBTYPE_ARM_V5TEJ 7 +// #define CPU_SUBTYPE_ARM_XSCALE 8 +// #define CPU_SUBTYPE_ARM_V7 9 +// #define CPU_SUBTYPE_ARM_V7S 11 +// #define CPU_SUBTYPE_ARM_V7K 12 +// #define CPU_SUBTYPE_ARM_V8 13 +// #define CPU_SUBTYPE_ARM_V6M 14 +// #define CPU_SUBTYPE_ARM_V7M 15 +// #define CPU_SUBTYPE_ARM_V7EM 16 + +// #define CPU_SUBTYPE_ARM64_ALL 0 +// #define CPU_SUBTYPE_ARM64_V8 1 +// #define CPU_SUBTYPE_ARM64E 2 +// #define CPU_SUBTYPE_ARM64_32_V8 1 + + +#define FAT_MAGIC 0xcafebabe +#define FAT_CIGAM 0xbebafeca + +#define MH_MAGIC 0xfeedface +#define MH_CIGAM 0xcefaedfe +#define MH_MAGIC_64 0xfeedfacf +#define MH_CIGAM_64 0xcffaedfe + + +/* Constants for the cmd field of new load commands, the type */ + +#define MH_OBJECT 0x1 /* relocatable object file */ +#define MH_EXECUTE 0x2 /* demand paged executable file */ +#define MH_FVMLIB 0x3 /* fixed VM shared library file */ +#define MH_CORE 0x4 /* core file */ +#define MH_PRELOAD 0x5 /* preloaded executable file */ +#define MH_DYLIB 0x6 /* dynamicly bound shared library file*/ +#define MH_DYLINKER 0x7 /* dynamic link editor */ +#define MH_BUNDLE 0x8 /* dynamicly bound bundle file */ +#define MH_DYLIB_STUB 0x9 // Dynamic shared library stub +#define MH_DSYM 0xa // Companion debug sections file +#define MH_KEXT_BUNDLE 0xb // Kernel extension + +/* Constants for the flags field of the mach_header */ +#define MH_NOUNDEFS 0x00000001 /* the object file has no undefined references, can be executed */ +#define MH_INCRLINK 0x00000002 /* the object file is the output of an incremental link against a base file and can't be link edited again */ +#define MH_DYLDLINK 0x00000004 /* the object file is input for the dynamic linker and can't be staticly link edited again */ +#define MH_BINDATLOAD 0x00000008 /* the object file's undefined references are bound by the dynamic linker when loaded. */ +#define MH_PREBOUND 0x00000010 /* the file has it's dynamic undefined references prebound. */ +#define MH_SPLIT_SEGS 0x00000020 +#define MH_LAZY_INIT 0x00000040 +#define MH_TWOLEVEL 0x00000080 +#define MH_FORCE_FLAT 0x00000100 +#define MH_NOMULTIDEFS 0x00000200 +#define MH_NOFIXPREBINDING 0x00000400 +#define MH_PREBINDABLE 0x00000800 +#define MH_ALLMODSBOUND 0x00001000 +#define MH_SUBSECTIONS_VIA_SYMBOLS 0x00002000 +#define MH_CANONICAL 0x00004000 +#define MH_WEAK_DEFINES 0x00008000 +#define MH_BINDS_TO_WEAK 0x00010000 +#define MH_ALLOW_STACK_EXECUTION 0x00020000 +#define MH_ROOT_SAFE 0x00040000 +#define MH_SETUID_SAFE 0x00080000 +#define MH_NO_REEXPORTED_DYLIBS 0x00100000 +#define MH_PIE 0x00200000 +#define MH_DEAD_STRIPPABLE_DYLIB 0x00400000 +#define MH_HAS_TLV_DESCRIPTORS 0x00800000 +#define MH_NO_HEAP_EXECUTION 0x01000000 +#define MH_APP_EXTENSION_SAFE 0x02000000 + + +/* Constants for the cmd field of all load commands, the type */ +#define LC_SEGMENT 0x00000001 /* segment of this file to be mapped */ +#define LC_SYMTAB 0x00000002 /* link-edit stab symbol table info */ +#define LC_SYMSEG 0x00000003 /* link-edit gdb symbol table info (obsolete) */ +#define LC_THREAD 0x00000004 /* thread */ +#define LC_UNIXTHREAD 0x00000005 /* unix thread (includes a stack) */ +#define LC_LOADFVMLIB 0x00000006 /* load a specified fixed VM shared library */ +#define LC_IDFVMLIB 0x00000007 /* fixed VM shared library identification */ +#define LC_IDENT 0x00000008 /* object identification info (obsolete) */ +#define LC_FVMFILE 0x00000009 /* fixed VM file inclusion (internal use) */ +#define LC_PREPAGE 0x0000000a /* prepage command (internal use) */ +#define LC_DYSYMTAB 0x0000000b /* dynamic link-edit symbol table info */ +#define LC_LOAD_DYLIB 0x0000000c /* load a dynamicly linked shared library */ +#define LC_ID_DYLIB 0x0000000d /* dynamicly linked shared lib identification */ +#define LC_LOAD_DYLINKER 0x0000000e /* load a dynamic linker */ +#define LC_ID_DYLINKER 0x0000000f /* dynamic linker identification */ +#define LC_PREBOUND_DYLIB 0x00000010 /* modules prebound for a dynamicly */ +#define LC_ROUTINES 0x00000011 +#define LC_SUB_FRAMEWORK 0x00000012 +#define LC_SUB_UMBRELLA 0x00000013 +#define LC_SUB_CLIENT 0x00000014 +#define LC_SUB_LIBRARY 0x00000015 +#define LC_TWOLEVEL_HINTS 0x00000016 +#define LC_PREBIND_CKSUM 0x00000017 +#define LC_LOAD_WEAK_DYLIB 0x80000018 +#define LC_SEGMENT_64 0x00000019 +#define LC_ROUTINES_64 0x0000001A +#define LC_UUID 0x0000001B +#define LC_RPATH 0x8000001C +#define LC_CODE_SIGNATURE 0x0000001D +#define LC_SEGMENT_SPLIT_INFO 0x0000001E +#define LC_REEXPORT_DYLIB 0x8000001F +#define LC_LAZY_LOAD_DYLIB 0x00000020 +#define LC_ENCRYPTION_INFO 0x00000021 +#define LC_DYLD_INFO 0x00000022 +#define LC_DYLD_INFO_ONLY 0x80000022 +#define LC_LOAD_UPWARD_DYLIB 0x80000023 +#define LC_VERSION_MIN_MACOSX 0x00000024 +#define LC_VERSION_MIN_IPHONEOS 0x00000025 +#define LC_FUNCTION_STARTS 0x00000026 +#define LC_DYLD_ENVIRONMENT 0x00000027 +#define LC_MAIN 0x80000028 +#define LC_DATA_IN_CODE 0x00000029 +#define LC_SOURCE_VERSION 0x0000002A +#define LC_DYLIB_CODE_SIGN_DRS 0x0000002B +#define LC_ENCRYPTION_INFO_64 0x0000002C +#define LC_LINKER_OPTION 0x0000002D +#define LC_LINKER_OPTIMIZATION_HINT 0x0000002E +#define LC_VERSION_MIN_TVOS 0x0000002F +#define LC_VERSION_MIN_WATCHOS 0x00000030 + +// /* Constants for the flags field of the segment_command */ +// #define SG_HIGHVM 0x00000001 /* the file contents for this segment is for +// the high part of the VM space, the low part +// is zero filled (for stacks in core files) */ +// #define SG_FVMLIB 0x00000002 /* this segment is the VM that is allocated by +// a fixed VM library, for overlap checking in +// the link editor */ +// #define SG_NORELOC 0x00000004 /* this segment has nothing that was relocated +// in it and nothing relocated to it, that is +// it maybe safely replaced without relocation*/ +// #define SG_PROTECTED_VERSION_1 0x00000008 // Segment is encryption protected + + +// // Section flag masks +// #define SECTION_TYPE 0x000000ff // Section type mask +// #define SECTION_ATTRIBUTES 0xffffff00 // Section attributes mask + +// // Section type (use SECTION_TYPE mask) + +// #define S_REGULAR 0x00 +// #define S_ZEROFILL 0x01 +// #define S_CSTRING_LITERALS 0x02 +// #define S_4BYTE_LITERALS 0x03 +// #define S_8BYTE_LITERALS 0x04 +// #define S_LITERAL_POINTERS 0x05 +// #define S_NON_LAZY_SYMBOL_POINTERS 0x06 +// #define S_LAZY_SYMBOL_POINTERS 0x07 +// #define S_SYMBOL_STUBS 0x08 +// #define S_MOD_INIT_FUNC_POINTERS 0x09 +// #define S_MOD_TERM_FUNC_POINTERS 0x0a +// #define S_COALESCED 0x0b +// #define S_GB_ZEROFILL 0x0c +// #define S_INTERPOSING 0x0d +// #define S_16BYTE_LITERALS 0x0e +// #define S_DTRACE_DOF 0x0f +// #define S_LAZY_DYLIB_SYMBOL_POINTERS 0x10 +// #define S_THREAD_LOCAL_REGULAR 0x11 +// #define S_THREAD_LOCAL_ZEROFILL 0x12 +// #define S_THREAD_LOCAL_VARIABLES 0x13 +// #define S_THREAD_LOCAL_VARIABLE_POINTERS 0x14 +// #define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS 0x15 + +// // Section attributes (use SECTION_ATTRIBUTES mask) + +// #define S_ATTR_PURE_INSTRUCTIONS 0x80000000 // Only pure instructions +// #define S_ATTR_NO_TOC 0x40000000 // Contains coalesced symbols +// #define S_ATTR_STRIP_STATIC_SYMS 0x20000000 // Can strip static symbols +// #define S_ATTR_NO_DEAD_STRIP 0x10000000 // No dead stripping +// #define S_ATTR_LIVE_SUPPORT 0x08000000 // Live blocks support +// #define S_ATTR_SELF_MODIFYING_CODE 0x04000000 // Self modifying code +// #define S_ATTR_DEBUG 0x02000000 // Debug section +// #define S_ATTR_SOME_INSTRUCTIONS 0x00000400 // Some machine instructions +// #define S_ATTR_EXT_RELOC 0x00000200 // Has external relocations +// #define S_ATTR_LOC_RELOC 0x00000100 // Has local relocations + + +//struct define + +#pragma pack(push, 1) + +struct fat_header +{ + uint32_t magic; /* FAT_MAGIC */ + uint32_t nfat_arch; /* number of structs that follow */ +}; + +struct fat_arch +{ + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint32_t offset; /* file offset to this object file */ + uint32_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ +}; + +struct mach_header +{ + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; + +struct mach_header_64 +{ + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; + +struct load_command +{ + uint32_t cmd; /* type of load command */ + uint32_t cmdsize; /* total size of command in bytes */ +}; + +struct uuid_command { + uint32_t cmd; + uint32_t cmdsize; + uint8_t uuid[16]; +}; + +struct entry_point_command { + uint32_t cmd; + uint32_t cmdsize; + uint64_t entryoff; + uint64_t stacksize; +}; + +struct codesignature_command { + uint32_t cmd; + uint32_t cmdsize; + uint32_t dataoff; + uint32_t datasize; +}; + +struct encryption_info_command { + uint32_t cmd; + uint32_t cmdsize; + uint32_t cryptoff; + uint32_t cryptsize; + uint32_t cryptid; +}; + +struct encryption_info_command_64 +{ + uint32_t cmd; + uint32_t cmdsize; + uint32_t cryptoff; + uint32_t cryptsize; + uint32_t cryptid; + uint32_t pad; +}; + +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +struct section { /* for 32-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint32_t addr; /* memory address of this section */ + uint32_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved */ + uint32_t reserved2; /* reserved */ +}; + +struct section_64 { /* for 64-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint64_t addr; /* memory address of this section */ + uint64_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved (for offset or index) */ + uint32_t reserved2; /* reserved (for count or sizeof) */ + uint32_t reserved3; /* reserved */ +}; + +union lc_str { + uint32_t offset; /* offset to the string */ +}; + +struct dylib { + union lc_str name; /* library's path name */ + uint32_t timestamp; /* library's build time stamp */ + uint32_t current_version; /* library's current version number */ + uint32_t compatibility_version; /* library's compatibility vers number*/ +}; + +struct dylib_command { + uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,LC_REEXPORT_DYLIB */ + uint32_t cmdsize; /* includes pathname string */ + struct dylib dylib; /* the library identification */ +}; + +#pragma pack(pop) + +//////CodeSignature + +enum { + CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ + CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ + CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ + CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ + CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ + CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, /* der format entitlements */ + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ + CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ + CS_SUPPORTSSCATTER = 0x20100, + CS_SUPPORTSTEAMID = 0x20200, + CS_SUPPORTSCODELIMIT64 = 0x20300, + CS_SUPPORTSEXECSEG = 0x20400, + CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ + CSSLOT_INFOSLOT = 1, + CSSLOT_REQUIREMENTS = 2, + CSSLOT_RESOURCEDIR = 3, + CSSLOT_APPLICATION = 4, + CSSLOT_ENTITLEMENTS = 5, + CSSLOT_DER_ENTITLEMENTS = 7, /* der format entitlement type */ + CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ + CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ + CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ + CSSLOT_IDENTIFICATIONSLOT = 0x10001, + CSSLOT_TICKETSLOT = 0x10002, + CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ + CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ + CS_HASHTYPE_SHA1 = 1, + CS_HASHTYPE_SHA256 = 2, + CS_HASHTYPE_SHA256_TRUNCATED = 3, + CS_HASHTYPE_SHA384 = 4, + CS_SHA1_LEN = 20, + CS_SHA256_LEN = 32, + CS_SHA256_TRUNCATED_LEN = 20, + CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ + CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ + CS_EXECSEG_MAIN_BINARY = 0x1, + CS_EXECSEG_ALLOW_UNSIGNED = 0x10, + +/* + * Currently only to support Legacy VPN plugins, + * but intended to replace all the various platform code, dev code etc. bits. + */ + CS_SIGNER_TYPE_UNKNOWN = 0, + CS_SIGNER_TYPE_LEGACYVPN = 5, + +}; + +#pragma pack(push, 1) + +/* + * Structure of an embedded-signature SuperBlob + */ +struct CS_BlobIndex { + uint32_t type; /* type of entry */ + uint32_t offset; /* offset of entry */ +}; + +struct CS_SuperBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of SuperBlob */ + uint32_t count; /* number of index entries following */ + //CS_BlobIndex index[]; /* (count) entries */ + /* followed by Blobs in no particular order as indicated by offsets in index */ +}; + +/* + * C form of a CodeDirectory. + */ +struct CS_CodeDirectory { + uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ + uint32_t length; /* total length of CodeDirectory blob */ + uint32_t version; /* compatibility version */ + uint32_t flags; /* setup and mode flags */ + uint32_t hashOffset; /* offset of hash slot element at index zero */ + uint32_t identOffset; /* offset of identifier string */ + uint32_t nSpecialSlots; /* number of special hash slots */ + uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ + uint32_t codeLimit; /* limit to main image signature range */ + uint8_t hashSize; /* size of each hash in bytes */ + uint8_t hashType; /* type of hash (cdHashType* constants) */ + uint8_t spare1; /* unused (must be zero) */ + uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ + uint32_t spare2; /* unused (must be zero) */ + //char end_earliest[0]; + + /* Version 0x20100 */ + uint32_t scatterOffset; /* offset of optional scatter vector */ + //char end_withScatter[0]; + + /* Version 0x20200 */ + uint32_t teamOffset; /* offset of optional team identifier */ + //char end_withTeam[0]; + + /* Version 0x20300 */ + uint32_t spare3; /* unused (must be zero) */ + uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ + //char end_withCodeLimit64[0]; + + /* Version 0x20400 */ + uint64_t execSegBase; /* offset of executable segment */ + uint64_t execSegLimit; /* limit of executable segment */ + uint64_t execSegFlags; /* executable segment flags */ + //char end_withExecSeg[0]; + + /* followed by dynamic content as located by offset fields above */ +}; + +struct CS_Entitlement { + uint32_t magic; + uint32_t length; +}; + +struct CS_GenericBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of blob */ +}; + +struct CS_Scatter { + uint32_t count; // number of pages; zero for sentinel (only) + uint32_t base; // first page number + uint64_t targetOffset; // offset in target + uint64_t spare; // reserved +}; + +#pragma pack(pop) diff --git a/zsign/macho.cpp b/zsign/macho.cpp new file mode 100644 index 0000000..745b132 --- /dev/null +++ b/zsign/macho.cpp @@ -0,0 +1,350 @@ +#include "common/common.h" +#include "common/json.h" +#include "common/mach-o.h" +#include "openssl.h" +#include "signing.h" +#include "macho.h" + +ZMachO::ZMachO() +{ + m_pBase = NULL; + m_sSize = 0; + m_bCSRealloced = false; +} + +ZMachO::~ZMachO() +{ + FreeArchOes(); +} + +bool ZMachO::Init(const char *szFile) +{ + m_strFile = szFile; + return OpenFile(szFile); +} + +bool ZMachO::InitV(const char *szFormatPath, ...) +{ + char szFile[PATH_MAX] = {0}; + va_list args; + va_start(args, szFormatPath); + vsnprintf(szFile, PATH_MAX, szFormatPath, args); + va_end(args); + + return Init(szFile); +} + +bool ZMachO::Free() +{ + FreeArchOes(); + return CloseFile(); +} + +bool ZMachO::NewArchO(uint8_t *pBase, uint32_t uLength) +{ + ZArchO *archo = new ZArchO(); + if (archo->Init(pBase, uLength)) + { + m_arrArchOes.push_back(archo); + return true; + } + delete archo; + return false; +} + +void ZMachO::FreeArchOes() +{ + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + ZArchO *archo = m_arrArchOes[i]; + delete archo; + } + m_pBase = NULL; + m_sSize = 0; + m_arrArchOes.clear(); +} + +bool ZMachO::OpenFile(const char *szPath) +{ + FreeArchOes(); + + m_sSize = 0; + m_pBase = (uint8_t *)MapFile(szPath, 0, 0, &m_sSize, false); + if (NULL != m_pBase) + { + uint32_t magic = *((uint32_t *)m_pBase); + if (FAT_CIGAM == magic || FAT_MAGIC == magic) + { + fat_header *pFatHeader = (fat_header *)m_pBase; + int nFatArch = (FAT_MAGIC == magic) ? pFatHeader->nfat_arch : LE(pFatHeader->nfat_arch); + for (int i = 0; i < nFatArch; i++) + { + fat_arch *pFatArch = (fat_arch *)(m_pBase + sizeof(fat_header) + sizeof(fat_arch) * i); + uint8_t *pArchBase = m_pBase + ((FAT_MAGIC == magic) ? pFatArch->offset : LE(pFatArch->offset)); + uint32_t uArchLength = (FAT_MAGIC == magic) ? pFatArch->size : LE(pFatArch->size); + if (!NewArchO(pArchBase, uArchLength)) + { + ZLog::ErrorV(">>> Invalid Arch File In Fat Macho File!\n"); + return false; + } + } + } + else if (MH_MAGIC == magic || MH_CIGAM == magic || MH_MAGIC_64 == magic || MH_CIGAM_64 == magic) + { + if (!NewArchO(m_pBase, (uint32_t)m_sSize)) + { + ZLog::ErrorV(">>> Invalid Macho File!\n"); + return false; + } + } + else + { + ZLog::ErrorV(">>> Invalid Macho File (2)!\n"); + return false; + } + } + + return (!m_arrArchOes.empty()); +} + +bool ZMachO::CloseFile() +{ + if (NULL == m_pBase || m_sSize <= 0) + { + return false; + } + + if ((munmap((void *)m_pBase, m_sSize)) < 0) + { + ZLog::ErrorV(">>> CodeSign Write(munmap) Failed! Error: %p, %lu, %s\n", m_pBase, m_sSize, strerror(errno)); + return false; + } + return true; +} + +void ZMachO::PrintInfo() +{ + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + ZArchO *archo = m_arrArchOes[i]; + archo->PrintInfo(); + } +} + +bool ZMachO::Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData) +{ + if (NULL == m_pBase || m_arrArchOes.empty()) + { + return false; + } + + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + ZArchO *archo = m_arrArchOes[i]; + if (strBundleId.empty()) + { + JValue jvInfo; + jvInfo.readPList(archo->m_strInfoPlist); + strBundleId = jvInfo["CFBundleIdentifier"].asCString(); + if (strBundleId.empty()) + { + strBundleId = basename((char *)m_strFile.c_str()); + } + } + + if (strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) + { + if (archo->m_strInfoPlist.empty()) + { + strInfoPlistSHA1.append(20, 0); + strInfoPlistSHA256.append(32, 0); + } + else + { + SHASum(archo->m_strInfoPlist, strInfoPlistSHA1, strInfoPlistSHA256); + } + } + + if (!archo->Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData)) + { + if (!archo->m_bEnoughSpace && !m_bCSRealloced) + { + m_bCSRealloced = true; + if (ReallocCodeSignSpace()) + { + return Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData); + } + } + return false; + } + } + + return CloseFile(); +} + +bool ZMachO::ReallocCodeSignSpace() +{ + ZLog::Warn(">>> Realloc CodeSignature Space... \n"); + + vector arrMachOesSizes; + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + string strNewArchOFile; + StringFormat(strNewArchOFile, "%s.archo.%d", m_strFile.c_str(), i); + uint32_t uNewLength = m_arrArchOes[i]->ReallocCodeSignSpace(strNewArchOFile); + if (uNewLength <= 0) + { + ZLog::Error(">>> Failed!\n"); + return false; + } + arrMachOesSizes.push_back(uNewLength); + } + ZLog::Warn(">>> Success!\n"); + + if (1 == m_arrArchOes.size()) + { + CloseFile(); + RemoveFile(m_strFile.c_str()); + string strNewArchOFile = m_strFile + ".archo.0"; + if (0 == rename(strNewArchOFile.c_str(), m_strFile.c_str())) + { + return OpenFile(m_strFile.c_str()); + } + } + else + { //fat + uint32_t uAlign = 16384; + vector arrArches; + fat_header fath = *((fat_header *)m_pBase); + int nFatArch = (FAT_MAGIC == fath.magic) ? fath.nfat_arch : LE(fath.nfat_arch); + for (int i = 0; i < nFatArch; i++) + { + fat_arch arch = *((fat_arch *)(m_pBase + sizeof(fat_header) + sizeof(fat_arch) * i)); + arrArches.push_back(arch); + } + CloseFile(); + + if (arrArches.size() != m_arrArchOes.size()) + { + return false; + } + + uint32_t uFatHeaderSize = sizeof(fat_header) + arrArches.size() * sizeof(fat_arch); + uint32_t uPadding1 = (uAlign - uFatHeaderSize % uAlign); + uint32_t uOffset = uFatHeaderSize + uPadding1; + for (size_t i = 0; i < arrArches.size(); i++) + { + fat_arch &arch = arrArches[i]; + uint32_t &uMachOSize = arrMachOesSizes[i]; + + arch.align = (FAT_MAGIC == fath.magic) ? 14 : BE((uint32_t)14); + arch.offset = (FAT_MAGIC == fath.magic) ? uOffset : BE(uOffset); + arch.size = (FAT_MAGIC == fath.magic) ? uMachOSize : BE(uMachOSize); + + uOffset += uMachOSize; + uOffset = uOffset + (uAlign - uOffset % uAlign); + } + + string strNewFatMachOFile = m_strFile + ".fato"; + + string strFatHeader; + strFatHeader.append((const char *)&fath, sizeof(fat_header)); + for (size_t i = 0; i < arrArches.size(); i++) + { + fat_arch &arch = arrArches[i]; + strFatHeader.append((const char *)&arch, sizeof(fat_arch)); + } + + string strPadding1; + strPadding1.append(uPadding1, 0); + + AppendFile(strNewFatMachOFile.c_str(), strFatHeader); + AppendFile(strNewFatMachOFile.c_str(), strPadding1); + + for (size_t i = 0; i < arrArches.size(); i++) + { + size_t sSize = 0; + string strNewArchOFile = m_strFile + ".archo." + JValue((int)i).asString(); + uint8_t *pData = (uint8_t *)MapFile(strNewArchOFile.c_str(), 0, 0, &sSize, true); + if (NULL == pData) + { + RemoveFile(strNewFatMachOFile.c_str()); + return false; + } + string strPadding; + strPadding.append((uAlign - sSize % uAlign), 0); + + AppendFile(strNewFatMachOFile.c_str(), (const char *)pData, sSize); + AppendFile(strNewFatMachOFile.c_str(), strPadding); + + munmap((void *)pData, sSize); + RemoveFile(strNewArchOFile.c_str()); + } + + RemoveFile(m_strFile.c_str()); + if (0 == rename(strNewFatMachOFile.c_str(), m_strFile.c_str())) + { + return OpenFile(m_strFile.c_str()); + } + } + + return false; +} + +bool ZMachO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate) +{ + ZLog::WarnV(">>> Inject DyLib: %s ... \n", szDyLibPath); + + vector arrMachOesSizes; + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + if (!m_arrArchOes[i]->InjectDyLib(bWeakInject, szDyLibPath, bCreate)) + { + ZLog::Error(">>> Failed!\n"); + return false; + } + } + ZLog::Warn(">>> Success!\n"); + return true; +} + +bool ZMachO::ChangeDylibPath(const char *oldPath, const char *newPath) { + ZLog::WarnV(">>> Change DyLib Path: %s -> %s ... \n", oldPath, newPath); + + bool pathChanged = true; + for (size_t i = 0; i < m_arrArchOes.size(); i++) { + if (!m_arrArchOes[i]->ChangeDylibPath(oldPath, newPath)) { + ZLog::Error(">>> Failed to change path in one of the architectures!\n"); + pathChanged = false; + } + } + + if (pathChanged) { + ZLog::Warn(">>> Successfully changed all dylib paths!\n"); + } + return pathChanged; +} + +std::vector ZMachO::ListDylibs() { + std::vector dylibList; + + for (size_t i = 0; i < m_arrArchOes.size(); i++) { + std::vector archDylibs = m_arrArchOes[i]->ListDylibs(); + dylibList.insert(dylibList.end(), archDylibs.begin(), archDylibs.end()); + } + + ZLog::WarnV(">>> Found %zu dylibs:\n", dylibList.size()); + + return dylibList; +} +bool ZMachO::RemoveDylib(const std::set &dylibNames) { + ZLog::Warn(">>> Removing specified dylibs...\n"); + + bool removalSuccessful = true; + for (size_t i = 0; i < m_arrArchOes.size(); i++) { + m_arrArchOes[i]->uninstallDylibs(dylibNames); + } + + ZLog::Warn(">>> Finished removing specified dylibs!\n"); + return removalSuccessful; +} diff --git a/zsign/macho.h b/zsign/macho.h new file mode 100644 index 0000000..e8bf3da --- /dev/null +++ b/zsign/macho.h @@ -0,0 +1,34 @@ +#pragma once +#include "archo.h" + +class ZMachO +{ +public: + ZMachO(); + ~ZMachO(); + +public: + bool Init(const char *szFile); + bool InitV(const char *szFormatPath, ...); + bool Free(); + void PrintInfo(); + bool Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData); + bool InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate); + bool ChangeDylibPath(const char *oldPath, const char *newPath); + std::vector ListDylibs(); + bool RemoveDylib(const std::set &dylibNames); +private: + bool OpenFile(const char *szPath); + bool CloseFile(); + + bool NewArchO(uint8_t *pBase, uint32_t uLength); + void FreeArchOes(); + bool ReallocCodeSignSpace(); + +private: + size_t m_sSize; + string m_strFile; + uint8_t *m_pBase; + bool m_bCSRealloced; + vector m_arrArchOes; +}; diff --git a/zsign/openssl.cpp b/zsign/openssl.cpp new file mode 100644 index 0000000..7147b06 --- /dev/null +++ b/zsign/openssl.cpp @@ -0,0 +1,944 @@ +#include "common/common.h" +#include "common/base64.h" +#include "openssl.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class COpenSSLInit +{ +public: + COpenSSLInit() + { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +#endif + }; +}; + +COpenSSLInit g_OpenSSLInit; + +const char *appleDevCACert = "" + "-----BEGIN CERTIFICATE-----\n" + "MIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE\n" + "BhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRp\n" + "ZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEz\n" + "MDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVTMRMwEQYD\n" + "VQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv\n" + "cGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3Bl\n" + "ciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3\n" + "DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0U3rOfGOA\n" + "YXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkVCBmsqtsq\n" + "Mu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8V25nNYB2\n" + "NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHld0WNUEi6\n" + "Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1qarunFjVg\n" + "0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGjgaYwgaMw\n" + "HQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQFMAMBAf8w\n" + "HwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGg\n" + "H4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGG\n" + "MBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Zviz1smwv\n" + "j+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/Nw0Uwj6OD\n" + "Dc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJTleMa1s8\n" + "Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1VAKmuu0sw\n" + "ruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur+cmV6U/k\n" + "TecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxRpVzscYqC\n" + "tGwPDBUf\n" + "-----END CERTIFICATE-----\n"; + +const char *appleDevCACertG3 = "" + "-----BEGIN CERTIFICATE-----\n" + "MIIEUTCCAzmgAwIBAgIQfK9pCiW3Of57m0R6wXjF7jANBgkqhkiG9w0BAQsFADBi\n" + "MQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBw\n" + "bGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3Qg\n" + "Q0EwHhcNMjAwMjE5MTgxMzQ3WhcNMzAwMjIwMDAwMDAwWjB1MUQwQgYDVQQDDDtB\n" + "cHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9u\n" + "IEF1dGhvcml0eTELMAkGA1UECwwCRzMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJ\n" + "BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PWJ/KhZ\n" + "C4fHTJEuLVaQ03gdpDDppUjvC0O/LYT7JF1FG+XrWTYSXFRknmxiLbTGl8rMPPbW\n" + "BpH85QKmHGq0edVny6zpPwcR4YS8Rx1mjjmi6LRJ7TrS4RBgeo6TjMrA2gzAg9Dj\n" + "+ZHWp4zIwXPirkbRYp2SqJBgN31ols2N4Pyb+ni743uvLRfdW/6AWSN1F7gSwe0b\n" + "5TTO/iK1nkmw5VW/j4SiPKi6xYaVFuQAyZ8D0MyzOhZ71gVcnetHrg21LYwOaU1A\n" + "0EtMOwSejSGxrC5DVDDOwYqGlJhL32oNP/77HK6XF8J4CjDgXx9UO0m3JQAaN4LS\n" + "VpelUkl8YDib7wIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0j\n" + "BBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wRAYIKwYBBQUHAQEEODA2MDQGCCsG\n" + "AQUFBzABhihodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNh\n" + "MC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwuYXBwbGUuY29tL3Jvb3QuY3Js\n" + "MB0GA1UdDgQWBBQJ/sAVkPmvZAqSErkmKGMMl+ynsjAOBgNVHQ8BAf8EBAMCAQYw\n" + "EAYKKoZIhvdjZAYCAQQCBQAwDQYJKoZIhvcNAQELBQADggEBAK1lE+j24IF3RAJH\n" + "Qr5fpTkg6mKp/cWQyXMT1Z6b0KoPjY3L7QHPbChAW8dVJEH4/M/BtSPp3Ozxb8qA\n" + "HXfCxGFJJWevD8o5Ja3T43rMMygNDi6hV0Bz+uZcrgZRKe3jhQxPYdwyFot30ETK\n" + "XXIDMUacrptAGvr04NM++i+MZp+XxFRZ79JI9AeZSWBZGcfdlNHAwWx/eCHvDOs7\n" + "bJmCS1JgOLU5gm3sUjFTvg+RTElJdI+mUcuER04ddSduvfnSXPN/wmwLCTbiZOTC\n" + "NwMUGdXqapSqqdv+9poIZ4vvK7iqF0mDr8/LvOnP6pVxsLRFoszlh6oKw0E6eVza\n" + "UDSdlTs=\n" + "-----END CERTIFICATE-----\n"; + +const char *appleRootCACert = "" + "-----BEGIN CERTIFICATE-----\n" + "MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET\n" + "MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv\n" + "biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0\n" + "MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw\n" + "bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx\n" + "FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" + "ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+\n" + "+FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1\n" + "XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w\n" + "tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW\n" + "q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM\n" + "aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E\n" + "BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3\n" + "R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE\n" + "ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93\n" + "d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl\n" + "IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0\n" + "YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj\n" + "b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp\n" + "Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc\n" + "NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP\n" + "y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7\n" + "R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg\n" + "xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP\n" + "IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX\n" + "UKqK1drk/NAJBzewdXUh\n" + "-----END CERTIFICATE-----\n"; + +bool CMSError() +{ + ERR_print_errors_fp(stdout); + return false; +} + +ASN1_TYPE *_GenerateASN1Type(const string &value) +{ + long errline = -1; + char *genstr = NULL; + BIO *ldapbio = BIO_new(BIO_s_mem()); + CONF *cnf = NCONF_new(NULL); + + if (cnf == NULL) { + ZLog::Error(">>> NCONF_new failed\n"); + BIO_free(ldapbio); + } + string a = "asn1=SEQUENCE:A\n[A]\nC=OBJECT:sha256\nB=FORMAT:HEX,OCT:" + value + "\n"; + int code = BIO_puts(ldapbio, a.c_str()); + if (NCONF_load_bio(cnf, ldapbio, &errline) <= 0) { + BIO_free(ldapbio); + NCONF_free(cnf); + ZLog::PrintV(">>> NCONF_load_bio failed %d\n", errline); + } + BIO_free(ldapbio); + genstr = NCONF_get_string(cnf, "default", "asn1"); + + if (genstr == NULL) { + ZLog::Error(">>> NCONF_get_string failed\n"); + NCONF_free(cnf); + } + ASN1_TYPE *ret = ASN1_generate_nconf(genstr, cnf); + NCONF_free(cnf); + return ret; +} + +bool _GenerateCMS(X509 *scert, EVP_PKEY *spkey, const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput) +{ + if (!scert || !spkey) + { + return CMSError(); + } + + BIO *bother1; + unsigned long issuerHash = X509_issuer_name_hash(scert); + if (0x817d2f7a == issuerHash) + { + bother1 = BIO_new_mem_buf(appleDevCACert, (int)strlen(appleDevCACert)); + } + else if (0x9b16b75c == issuerHash) + { + bother1 = BIO_new_mem_buf(appleDevCACertG3, (int)strlen(appleDevCACertG3)); + } + else + { + ZLog::Error(">>> Unknown Issuer Hash!\n"); + return false; + } + + BIO *bother2 = BIO_new_mem_buf(appleRootCACert, (int)strlen(appleRootCACert)); + if (!bother1 || !bother2) + { + return CMSError(); + } + + X509 *ocert1 = PEM_read_bio_X509(bother1, NULL, 0, NULL); + X509 *ocert2 = PEM_read_bio_X509(bother2, NULL, 0, NULL); + if (!ocert1 || !ocert2) + { + return CMSError(); + } + + STACK_OF(X509) *otherCerts = sk_X509_new_null(); + if (!otherCerts) + { + return CMSError(); + } + + if (!sk_X509_push(otherCerts, ocert1)) + { + return CMSError(); + } + + if (!sk_X509_push(otherCerts, ocert2)) + { + return CMSError(); + } + + BIO *in = BIO_new_mem_buf(strCDHashData.c_str(), (int)strCDHashData.size()); + if (!in) + { + return CMSError(); + } + + int nFlags = CMS_PARTIAL | CMS_DETACHED | CMS_NOSMIMECAP | CMS_BINARY; + CMS_ContentInfo *cms = CMS_sign(NULL, NULL, otherCerts, NULL, nFlags); + if (!cms) + { + return CMSError(); + } + + CMS_SignerInfo * si = CMS_add1_signer(cms, scert, spkey, EVP_sha256(), nFlags); +// CMS_add1_signer(cms, NULL, NULL, EVP_sha1(), nFlags); + if (!si) { + return CMSError(); + } + + // add plist + ASN1_OBJECT * obj = OBJ_txt2obj("1.2.840.113635.100.9.1", 1); + if (!obj) { + return CMSError(); + } + + int addHashPlist = CMS_signed_add1_attr_by_OBJ(si, obj, 0x4, strCDHashesPlist.c_str(), (int)strCDHashesPlist.size()); + + if (!addHashPlist) { + return CMSError(); + } + + // add CDHashes + string sha256; + char buf[16] = {0}; + for (size_t i = 0; i < strAltnateCodeDirectorySlot256.size(); i++) + { + sprintf(buf, "%02x", (uint8_t)strAltnateCodeDirectorySlot256[i]); + sha256 += buf; + } + transform(sha256.begin(), sha256.end(), sha256.begin(), ::toupper); + + ASN1_OBJECT * obj2 = OBJ_txt2obj("1.2.840.113635.100.9.2", 1); + if (!obj2) { + return CMSError(); + } + + X509_ATTRIBUTE *attr = X509_ATTRIBUTE_new(); + X509_ATTRIBUTE_set1_object(attr, obj2); + + ASN1_TYPE *type_256 = _GenerateASN1Type(sha256); + X509_ATTRIBUTE_set1_data(attr, V_ASN1_SEQUENCE, + type_256->value.asn1_string->data, type_256->value.asn1_string->length); + int addHashSHA = CMS_signed_add1_attr(si, attr); + if (!addHashSHA) { + return CMSError(); + } + + if (!CMS_final(cms, in, NULL, nFlags)) + { + return CMSError(); + } + + BIO *out = BIO_new(BIO_s_mem()); + if (!out) + { + return CMSError(); + } + + //PEM_write_bio_CMS(out, cms); + if (!i2d_CMS_bio(out, cms)) + { + return CMSError(); + } + + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(out, &bptr); + if (!bptr) + { + return CMSError(); + } + + strCMSOutput.clear(); + strCMSOutput.append(bptr->data, bptr->length); + ASN1_TYPE_free(type_256); + return (!strCMSOutput.empty()); +} + +bool GenerateCMS(const string &strSignerCertData, const string &strSignerPKeyData, const string &strCDHashData, const string &strCDHashesPlist, string &strCMSOutput) +{ + BIO *bcert = BIO_new_mem_buf(strSignerCertData.c_str(), (int)strSignerCertData.size()); + BIO *bpkey = BIO_new_mem_buf(strSignerPKeyData.c_str(), (int)strSignerPKeyData.size()); + + if (!bcert || !bpkey) + { + return CMSError(); + } + + X509 *scert = PEM_read_bio_X509(bcert, NULL, 0, NULL); + EVP_PKEY *spkey = PEM_read_bio_PrivateKey(bpkey, NULL, 0, NULL); + if (!scert || !spkey) + { + return CMSError(); + } + + return ::_GenerateCMS(scert, spkey, strCDHashData, strCDHashesPlist, "", "", strCMSOutput); +} + +bool GetCMSContent(const string &strCMSDataInput, string &strContentOutput) +{ + if (strCMSDataInput.empty()) + { + return false; + } + + BIO *in = BIO_new(BIO_s_mem()); + OPENSSL_assert((size_t)BIO_write(in, strCMSDataInput.data(), (int)strCMSDataInput.size()) == strCMSDataInput.size()); + CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); + if (!cms) + { + return CMSError(); + } + + ASN1_OCTET_STRING **pos = CMS_get0_content(cms); + if (!pos) + { + return CMSError(); + } + + if (!(*pos)) + { + return CMSError(); + } + + strContentOutput.clear(); + strContentOutput.append((const char *)(*pos)->data, (*pos)->length); + return (!strContentOutput.empty()); +} + +bool GetCMSContent2(const void* strCMSDataInput, int size, string &strContentOutput) +{ + if (size == 0) + { + return false; + } + + BIO *in = BIO_new(BIO_s_mem()); + OPENSSL_assert((size_t)BIO_write(in, strCMSDataInput, size) == size); + CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); + if (!cms) + { + return CMSError(); + } + + ASN1_OCTET_STRING **pos = CMS_get0_content(cms); + if (!pos) + { + return CMSError(); + } + + if (!(*pos)) + { + return CMSError(); + } + + strContentOutput.clear(); + strContentOutput.append((const char *)(*pos)->data, (*pos)->length); + return (!strContentOutput.empty()); +} + +bool GetCertSubjectCN(X509 *cert, string &strSubjectCN) +{ + if (!cert) + { + return CMSError(); + } + + X509_NAME *name = X509_get_subject_name(cert); + + int common_name_loc = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + if (common_name_loc < 0) + { + return CMSError(); + } + + X509_NAME_ENTRY *common_name_entry = X509_NAME_get_entry(name, common_name_loc); + if (common_name_entry == NULL) + { + return CMSError(); + } + + ASN1_STRING *common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 == NULL) + { + return CMSError(); + } + + strSubjectCN.clear(); + strSubjectCN.append((const char *)common_name_asn1->data, common_name_asn1->length); + return (!strSubjectCN.empty()); +} + +bool GetCertSubjectCN(const string &strCertData, string &strSubjectCN) +{ + if (strCertData.empty()) + { + return false; + } + + BIO *bcert = BIO_new_mem_buf(strCertData.c_str(), strCertData.size()); + if (!bcert) + { + return CMSError(); + } + + X509 *cert = PEM_read_bio_X509(bcert, NULL, 0, NULL); + if (!cert) + { + return CMSError(); + } + + return GetCertSubjectCN(cert, strSubjectCN); +} + +void ParseCertSubject(const string &strSubject, JValue &jvSubject) +{ + vector arrNodes; + StringSplit(strSubject, "/", arrNodes); + for (size_t i = 0; i < arrNodes.size(); i++) + { + vector arrLines; + StringSplit(arrNodes[i], "=", arrLines); + if (2 == arrLines.size()) + { + jvSubject[arrLines[0]] = arrLines[1]; + } + } +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +string ASN1_TIMEtoString(ASN1_TIME *time) +{ +#else +string ASN1_TIMEtoString(const ASN1_TIME *time) +{ +#endif + BIO *out = BIO_new(BIO_s_mem()); + if (!out) + { + CMSError(); + return ""; + } + + ASN1_TIME_print(out, time); + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(out, &bptr); + if (!bptr) + { + CMSError(); + return ""; + } + string strTime; + strTime.append(bptr->data, bptr->length); + return strTime; +} + +bool GetCertInfo(X509 *cert, JValue &jvCertInfo) +{ + if (!cert) + { + return CMSError(); + } + + jvCertInfo["Version"] = (int)X509_get_version(cert); + + ASN1_INTEGER *asn1_i = X509_get_serialNumber(cert); + if (asn1_i) + { + BIGNUM *bignum = ASN1_INTEGER_to_BN(asn1_i, NULL); + if (bignum) + { + jvCertInfo["SerialNumber"] = BN_bn2hex(bignum); + } + } + + jvCertInfo["SignatureAlgorithm"] = OBJ_nid2ln(X509_get_signature_nid(cert)); + + EVP_PKEY *pubkey = X509_get_pubkey(cert); + int type = EVP_PKEY_id(pubkey); + jvCertInfo["PublicKey"]["Algorithm"] = OBJ_nid2ln(type); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + jvCertInfo["Validity"]["NotBefore"] = ASN1_TIMEtoString(X509_get_notBefore(cert)); + jvCertInfo["Validity"]["NotAfter"] = ASN1_TIMEtoString(X509_get_notAfter(cert)); +#else + jvCertInfo["Validity"]["NotBefore"] = ASN1_TIMEtoString(X509_get0_notBefore(cert)); + jvCertInfo["Validity"]["NotAfter"] = ASN1_TIMEtoString(X509_get0_notAfter(cert)); +#endif + + string strIssuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); + string strSubject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + + ParseCertSubject(strIssuer, jvCertInfo["Issuer"]); + ParseCertSubject(strSubject, jvCertInfo["Subject"]); + + return (!strIssuer.empty() && !strSubject.empty()); +} + +bool GetCMSInfo(uint8_t *pCMSData, uint32_t uCMSLength, JValue &jvOutput) +{ + BIO *in = BIO_new(BIO_s_mem()); + OPENSSL_assert((size_t)BIO_write(in, pCMSData, uCMSLength) == uCMSLength); + CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); + if (!cms) + { + return CMSError(); + } + + int detached = CMS_is_detached(cms); + jvOutput["detached"] = detached; + + const ASN1_OBJECT *obj = CMS_get0_type(cms); + const char *sn = OBJ_nid2ln(OBJ_obj2nid(obj)); + jvOutput["contentType"] = sn; + + ASN1_OCTET_STRING **pos = CMS_get0_content(cms); + if (pos) + { + if ((*pos)) + { + ZBase64 b64; + jvOutput["content"] = b64.Encode((const char *)(*pos)->data, (*pos)->length); + } + } + + STACK_OF(X509) *certs = CMS_get1_certs(cms); + for (int i = 0; i < sk_X509_num(certs); i++) + { + JValue jvCertInfo; + if (GetCertInfo(sk_X509_value(certs, i), jvCertInfo)) + { + jvOutput["certs"].push_back(jvCertInfo); + } + } + + STACK_OF(CMS_SignerInfo) *sis = CMS_get0_SignerInfos(cms); + for (int i = 0; i < sk_CMS_SignerInfo_num(sis); i++) + { + CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sis, i); + //int CMS_SignerInfo_get0_signer_id(CMS_SignerInfo *si, ASN1_OCTET_STRING **keyid, X509_NAME **issuer, ASN1_INTEGER **sno); + + int nSignedAttsCount = CMS_signed_get_attr_count(si); + for (int j = 0; j < nSignedAttsCount; j++) + { + X509_ATTRIBUTE *attr = CMS_signed_get_attr(si, j); + if (!attr) + { + continue; + } + int nCount = X509_ATTRIBUTE_count(attr); + if (nCount <= 0) + { + continue; + } + + ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); + if (!obj) + { + continue; + } + + char txtobj[128] = {0}; + OBJ_obj2txt(txtobj, 128, obj, 1); + + if (0 == strcmp("1.2.840.113549.1.9.3", txtobj)) + { //V_ASN1_OBJECT + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + jvOutput["attrs"]["ContentType"]["obj"] = txtobj; + jvOutput["attrs"]["ContentType"]["data"] = OBJ_nid2ln(OBJ_obj2nid(av->value.object)); + } + } + else if (0 == strcmp("1.2.840.113549.1.9.4", txtobj)) + { //V_ASN1_OCTET_STRING + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + string strSHASum; + char buf[16] = {0}; + for (int m = 0; m < av->value.octet_string->length; m++) + { + sprintf(buf, "%02x", (uint8_t)av->value.octet_string->data[m]); + strSHASum += buf; + } + jvOutput["attrs"]["MessageDigest"]["obj"] = txtobj; + jvOutput["attrs"]["MessageDigest"]["data"] = strSHASum; + } + } + else if (0 == strcmp("1.2.840.113549.1.9.5", txtobj)) + { //V_ASN1_UTCTIME + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + BIO *mem = BIO_new(BIO_s_mem()); + ASN1_UTCTIME_print(mem, av->value.utctime); + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(mem, &bptr); + BIO_set_close(mem, BIO_NOCLOSE); + string strTime; + strTime.append(bptr->data, bptr->length); + BIO_free_all(mem); + + jvOutput["attrs"]["SigningTime"]["obj"] = txtobj; + jvOutput["attrs"]["SigningTime"]["data"] = strTime; + } + } + else if (0 == strcmp("1.2.840.113635.100.9.2", txtobj)) + { //V_ASN1_SEQUENCE + jvOutput["attrs"]["CDHashes2"]["obj"] = txtobj; + for (int m = 0; m < nCount; m++) + { + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, m); + if (NULL != av) + { + ASN1_STRING *s = av->value.sequence; + + BIO *mem = BIO_new(BIO_s_mem()); + + ASN1_parse_dump(mem, s->data, s->length, 2, 0); + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(mem, &bptr); + BIO_set_close(mem, BIO_NOCLOSE); + string strData; + strData.append(bptr->data, bptr->length); + BIO_free_all(mem); + + string strSHASum; + size_t pos1 = strData.find("[HEX DUMP]:"); + if (string::npos != pos1) + { + size_t pos2 = strData.find("\n", pos1); + if (string::npos != pos2) + { + strSHASum = strData.substr(pos1 + 11, pos2 - pos1 - 11); + } + } + transform(strSHASum.begin(), strSHASum.end(), strSHASum.begin(), ::tolower); + jvOutput["attrs"]["CDHashes2"]["data"].push_back(strSHASum); + } + } + } + else if (0 == strcmp("1.2.840.113635.100.9.1", txtobj)) + { //V_ASN1_OCTET_STRING + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + string strPList; + strPList.append((const char *)av->value.octet_string->data, av->value.octet_string->length); + jvOutput["attrs"]["CDHashes"]["obj"] = txtobj; + jvOutput["attrs"]["CDHashes"]["data"] = strPList; + } + } + else + { + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + JValue jvAttr; + jvAttr["obj"] = txtobj; + jvAttr["name"] = OBJ_nid2ln(OBJ_obj2nid(obj)); + jvAttr["type"] = av->type; + jvAttr["count"] = nCount; + jvOutput["attrs"]["unknown"].push_back(jvAttr); + } + } + } + } + + return true; +} + +ZSignAsset::ZSignAsset() +{ + m_evpPKey = NULL; + m_x509Cert = NULL; +} + +bool ZSignAsset::Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword) +{ + ReadFile(strProvisionFile.c_str(), m_strProvisionData); + ReadFile(strEntitlementsFile.c_str(), m_strEntitlementsData); + if (m_strProvisionData.empty()) + { + ZLog::Error(">>> Can't Find Provision File!\n"); + return false; + } + + JValue jvProv; + string strProvContent; + if (GetCMSContent(m_strProvisionData, strProvContent)) + { + if (jvProv.readPList(strProvContent)) + { + m_strTeamId = jvProv["TeamIdentifier"][0].asCString(); + if (m_strEntitlementsData.empty()) + { + jvProv["Entitlements"].writePList(m_strEntitlementsData); + } + } + } + + if (m_strTeamId.empty()) + { + ZLog::Error(">>> Can't Find TeamId!\n"); + return false; + } + + X509 *x509Cert = NULL; + EVP_PKEY *evpPKey = NULL; + BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); + if (NULL != bioPKey) + { + evpPKey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str()); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + evpPKey = d2i_PrivateKey_bio(bioPKey, NULL); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + OSSL_PROVIDER_load(NULL, "legacy"); + PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL); + if (NULL != p12) + { + if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPKey, &x509Cert, NULL)) + { + CMSError(); + } + PKCS12_free(p12); + } + } + } + BIO_free(bioPKey); + } + + if (NULL == evpPKey) + { + ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n"); + return false; + } + + if (NULL == x509Cert && !strSignerCertFile.empty()) + { + BIO *bioCert = BIO_new_file(strSignerCertFile.c_str(), "r"); + if (NULL != bioCert) + { + x509Cert = PEM_read_bio_X509(bioCert, NULL, 0, NULL); + if (NULL == x509Cert) + { + BIO_reset(bioCert); + x509Cert = d2i_X509_bio(bioCert, NULL); + } + BIO_free(bioCert); + } + } + + if (NULL != x509Cert) + { + if (!X509_check_private_key(x509Cert, evpPKey)) + { + X509_free(x509Cert); + x509Cert = NULL; + } + } + + if (NULL == x509Cert) + { + for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++) + { + string strCertData = jvProv["DeveloperCertificates"][i].asData(); + BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), (int)strCertData.size()); + if (NULL != bioCert) + { + x509Cert = d2i_X509_bio(bioCert, NULL); + if (NULL != x509Cert) + { + if (X509_check_private_key(x509Cert, evpPKey)) + { + break; + } + X509_free(x509Cert); + x509Cert = NULL; + } + BIO_free(bioCert); + } + } + } + + if (NULL == x509Cert) + { + ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n"); + return false; + } + + if (!GetCertSubjectCN(x509Cert, m_strSubjectCN)) + { + ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n"); + return false; + } + + m_evpPKey = evpPKey; + m_x509Cert = x509Cert; + return true; +} + +bool ZSignAsset::GenerateCMS(const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput) +{ + return ::_GenerateCMS((X509 *)m_x509Cert, (EVP_PKEY *)m_evpPKey, strCDHashData, strCDHashesPlist, strCodeDirectorySlotSHA1, strAltnateCodeDirectorySlot256, strCMSOutput); +} + + std::string binary_to_hex(const char* data, int len) { + std::ostringstream oss; + for (int i = 0; i < len; ++i) { + uint8_t byte = data[i]; + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); + } + return oss.str(); + } + +bool ZSignAsset::InitSimple(const void* strSignerPKeyData, int strSignerPKeyDataSize, const void* strProvisionData, int strProvisionDataSize, const string &strPassword){ + + JValue jvProv; + string strProvContent; + m_strEntitlementsData = ""; + if (GetCMSContent2(strProvisionData, strProvisionDataSize, strProvContent)) + { + if (jvProv.readPList(strProvContent)) + { + m_strTeamId = jvProv["TeamIdentifier"][0].asCString(); + if (m_strEntitlementsData.empty()) + { + jvProv["Entitlements"].writePList(m_strEntitlementsData); + } + } + } + + if (m_strTeamId.empty()) + { + ZLog::Error(">>> Can't Find TeamId!\n"); + return false; + } + + X509 *x509Cert = NULL; + EVP_PKEY *evpPKey = NULL; + + // BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); + char* digest = new char[16]; + MD5((unsigned char*) strSignerPKeyData, strSignerPKeyDataSize, (unsigned char*)digest); + ZLog::Error(binary_to_hex(digest, 16).data()); + delete[] digest; + + + BIO *bioPKey = BIO_new_mem_buf(strSignerPKeyData, strSignerPKeyDataSize); + if (NULL != bioPKey) + { + evpPKey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str()); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + evpPKey = d2i_PrivateKey_bio(bioPKey, NULL); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + OSSL_PROVIDER_load(NULL, "legacy"); + PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL); + if (NULL != p12) + { + if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPKey, &x509Cert, NULL)) + { + CMSError(); + } + PKCS12_free(p12); + } + } + } + BIO_free(bioPKey); + } + + if (NULL == evpPKey) + { + ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n"); + return false; + } + + if (NULL != x509Cert) + { + if (!X509_check_private_key(x509Cert, evpPKey)) + { + X509_free(x509Cert); + x509Cert = NULL; + } + } + + if (NULL == x509Cert) + { + for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++) + { + string strCertData = jvProv["DeveloperCertificates"][i].asData(); + BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), (int)strCertData.size()); + if (NULL != bioCert) + { + x509Cert = d2i_X509_bio(bioCert, NULL); + if (NULL != x509Cert) + { + if (X509_check_private_key(x509Cert, evpPKey)) + { + break; + } + X509_free(x509Cert); + x509Cert = NULL; + } + BIO_free(bioCert); + } + } + } + + if (NULL == x509Cert) + { + ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n"); + return false; + } + + if (!GetCertSubjectCN(x509Cert, m_strSubjectCN)) + { + ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n"); + return false; + } + + m_evpPKey = evpPKey; + m_x509Cert = x509Cert; + return true; +} diff --git a/zsign/openssl.h b/zsign/openssl.h new file mode 100644 index 0000000..6e4dfec --- /dev/null +++ b/zsign/openssl.h @@ -0,0 +1,28 @@ +#pragma once +#include "common/json.h" + +bool GetCertSubjectCN(const string &strCertData, string &strSubjectCN); +bool GetCMSInfo(uint8_t *pCMSData, uint32_t uCMSLength, JValue &jvOutput); +bool GetCMSContent(const string &strCMSDataInput, string &strContentOutput); +bool GenerateCMS(const string &strSignerCertData, const string &strSignerPKeyData, const string &strCDHashData, const string &strCDHashPlist, string &strCMSOutput); + +class ZSignAsset +{ +public: + ZSignAsset(); + +public: + bool GenerateCMS(const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput); + bool Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword); + bool InitSimple(const void* strSignerPKeyData, int strSignerPKeyDataSize, const void* strProvisionData, int strProvisionDataSize, const string &strPassword); + +public: + string m_strTeamId; + string m_strSubjectCN; + string m_strProvisionData; + string m_strEntitlementsData; + +private: + void *m_evpPKey; + void *m_x509Cert; +}; diff --git a/zsign/signing.cpp b/zsign/signing.cpp new file mode 100644 index 0000000..64d5dd7 --- /dev/null +++ b/zsign/signing.cpp @@ -0,0 +1,875 @@ +#include "common/common.h" +#include "common/json.h" +#include "common/mach-o.h" +#include "openssl.h" + +static void _DERLength(string &strBlob, uint64_t uLength) +{ + if (uLength < 128) + { + strBlob.append(1, (char)uLength); + } + else + { + uint32_t sLength = (64 - __builtin_clzll(uLength) + 7) / 8; + strBlob.append(1, (char)(0x80 | sLength)); + sLength *= 8; + do + { + strBlob.append(1, (char)(uLength >> (sLength -= 8))); + } while (sLength != 0); + } +} + +static string _DER(const JValue &data) +{ + string strOutput; + if (data.isBool()) + { + strOutput.append(1, 0x01); + strOutput.append(1, 1); + strOutput.append(1, data.asBool() ? 1 : 0); + } + else if (data.isInt()) + { + uint64_t uVal = data.asInt64(); + strOutput.append(1, 0x02); + _DERLength(strOutput, uVal); + + uint32_t sLength = (64 - __builtin_clzll(uVal) + 7) / 8; + sLength *= 8; + do + { + strOutput.append(1, (char)(uVal >> (sLength -= 8))); + } while (sLength != 0); + } + else if (data.isString()) + { + string strVal = data.asCString(); + strOutput.append(1, 0x0c); + _DERLength(strOutput, strVal.size()); + strOutput += strVal; + } + else if (data.isArray()) + { + string strArray; + size_t size = data.size(); + for (size_t i = 0; i < size; i++) + { + strArray += _DER(data[i]); + } + strOutput.append(1, 0x30); + _DERLength(strOutput, strArray.size()); + strOutput += strArray; + } + else if (data.isObject()) + { + string strDict; + vector arrKeys; + data.keys(arrKeys); + for (size_t i = 0; i < arrKeys.size(); i++) + { + string &strKey = arrKeys[i]; + string strVal = _DER(data[strKey]); + + strDict.append(1, 0x30); + _DERLength(strDict, (2 + strKey.size() + strVal.size())); + + strDict.append(1, 0x0c); + _DERLength(strDict, strKey.size()); + strDict += strKey; + + strDict += strVal; + } + + strOutput.append(1, 0x31); + _DERLength(strOutput, strDict.size()); + strOutput += strDict; + } + else if (data.isFloat()) + { + assert(false); + } + else if (data.isDate()) + { + assert(false); + } + else if (data.isData()) + { + assert(false); + } + else + { + assert(false && "Unsupported Entitlements DER Type"); + } + + return strOutput; +} + +uint32_t SlotParseGeneralHeader(const char *szSlotName, uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = LE(*(((uint32_t *)pSlotBase) + 1)); + ZLog::PrintV("\n > %s: \n", szSlotName); + ZLog::PrintV("\ttype: \t\t0x%x\n", LE(pbi->type)); + ZLog::PrintV("\toffset: \t%u\n", LE(pbi->offset)); + ZLog::PrintV("\tmagic: \t\t0x%x\n", LE(*((uint32_t *)pSlotBase))); + ZLog::PrintV("\tlength: \t%u\n", uSlotLength); + return uSlotLength; +} + +void SlotParseGeneralTailer(uint8_t *pSlotBase, uint32_t uSlotLength) +{ + PrintDataSHASum("\tSHA-1: \t", E_SHASUM_TYPE_1, pSlotBase, uSlotLength); + PrintDataSHASum("\tSHA-256:\t", E_SHASUM_TYPE_256, pSlotBase, uSlotLength); +} + +bool SlotParseRequirements(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_REQUIREMENTS", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + if (IsFileExists("/usr/bin/csreq")) + { + string strTempFile; + StringFormat(strTempFile, "/tmp/Requirements_%llu.blob", GetMicroSecond()); + WriteFile(strTempFile.c_str(), (const char *)pSlotBase, uSlotLength); + + string strCommand; + StringFormat(strCommand, "/usr/bin/csreq -r '%s' -t ", strTempFile.c_str()); + char result[1024] = {0}; + FILE *cmd = popen(strCommand.c_str(), "r"); + while (NULL != fgets(result, sizeof(result), cmd)) + { + printf("\treqtext: \t%s", result); + } + pclose(cmd); + RemoveFile(strTempFile.c_str()); + } + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Requirements.slot", (const char *)pSlotBase, uSlotLength); + } + return true; +} + +bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput) +{ + strOutput.clear(); + if (strBundleID.empty() || strSubjectCN.empty()) + { //ldid + strOutput = "\xfa\xde\x0c\x01\x00\x00\x00\x0c\x00\x00\x00\x00"; + return true; + } + + string strPaddedBundleID = strBundleID; + strPaddedBundleID.append(((strBundleID.size() % 4) ? (4 - (strBundleID.size() % 4)) : 0), 0); + + string strPaddedSubjectID = strSubjectCN; + strPaddedSubjectID.append(((strSubjectCN.size() % 4) ? (4 - (strSubjectCN.size() % 4)) : 0), 0); + + uint8_t magic1[] = {0xfa, 0xde, 0x0c, 0x01}; + uint32_t uLength1 = 0; + uint8_t pack1[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14}; + uint8_t magic2[] = {0xfa, 0xde, 0x0c, 0x00}; + uint32_t uLength2 = 0; + uint8_t pack2[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02}; + uint32_t uBundldIDLength = (uint32_t)strBundleID.size(); + //string strPaddedBundleID + uint8_t pack3[] = { + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x73, + 0x75, + 0x62, + 0x6a, + 0x65, + 0x63, + 0x74, + 0x2e, + 0x43, + 0x4e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + }; + uint32_t uSubjectCNLength = (uint32_t)strSubjectCN.size(); + //string strPaddedSubjectID + uint8_t pack4[] = { + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x2a, + 0x86, + 0x48, + 0x86, + 0xf7, + 0x63, + 0x64, + 0x06, + 0x02, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + }; + + uLength2 += sizeof(magic2) + sizeof(uLength2) + sizeof(pack2); + uLength2 += sizeof(uBundldIDLength) + strPaddedBundleID.size(); + uLength2 += sizeof(pack3); + uLength2 += sizeof(uSubjectCNLength) + strPaddedSubjectID.size(); + uLength2 += sizeof(pack4); + + uLength1 += sizeof(magic1) + sizeof(uLength1) + sizeof(pack1); + uLength1 += uLength2; + + uLength1 = BE(uLength1); + uLength2 = BE(uLength2); + uBundldIDLength = BE(uBundldIDLength); + uSubjectCNLength = BE(uSubjectCNLength); + + strOutput.append((const char *)magic1, sizeof(magic1)); + strOutput.append((const char *)&uLength1, sizeof(uLength1)); + strOutput.append((const char *)pack1, sizeof(pack1)); + strOutput.append((const char *)magic2, sizeof(magic2)); + strOutput.append((const char *)&uLength2, sizeof(uLength2)); + strOutput.append((const char *)pack2, sizeof(pack2)); + strOutput.append((const char *)&uBundldIDLength, sizeof(uBundldIDLength)); + strOutput.append(strPaddedBundleID.data(), strPaddedBundleID.size()); + strOutput.append((const char *)pack3, sizeof(pack3)); + strOutput.append((const char *)&uSubjectCNLength, sizeof(uSubjectCNLength)); + strOutput.append(strPaddedSubjectID.data(), strPaddedSubjectID.size()); + strOutput.append((const char *)pack4, sizeof(pack4)); + + return true; +} + +bool SlotParseEntitlements(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_ENTITLEMENTS", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + string strEntitlements = "\t\t\t"; + strEntitlements.append((const char *)pSlotBase + 8, uSlotLength - 8); + PWriter::StringReplace(strEntitlements, "\n", "\n\t\t\t"); + ZLog::PrintV("\tentitlements: \n%s\n", strEntitlements.c_str()); + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Entitlements.slot", (const char *)pSlotBase, uSlotLength); + WriteFile("./.zsign_debug/Entitlements.plist", (const char *)pSlotBase + 8, uSlotLength - 8); + } + return true; +} + +bool SlotParseDerEntitlements(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_DER_ENTITLEMENTS", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Entitlements.der.slot", (const char *)pSlotBase, uSlotLength); + } + return true; +} + +bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput) +{ + strOutput.clear(); + if (strEntitlements.empty()) + { + return false; + } + + uint32_t uMagic = BE(CSMAGIC_EMBEDDED_ENTITLEMENTS); + uint32_t uLength = BE((uint32_t)strEntitlements.size() + 8); + + strOutput.append((const char *)&uMagic, sizeof(uMagic)); + strOutput.append((const char *)&uLength, sizeof(uLength)); + strOutput.append(strEntitlements.data(), strEntitlements.size()); + + return true; +} + +bool SlotBuildDerEntitlements(const string &strEntitlements, string &strOutput) +{ + strOutput.clear(); + if (strEntitlements.empty()) + { + return false; + } + + JValue jvInfo; + jvInfo.readPList(strEntitlements); + + string strRawEntitlementsData = _DER(jvInfo); + uint32_t uMagic = BE(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS); + uint32_t uLength = BE((uint32_t)strRawEntitlementsData.size() + 8); + + strOutput.append((const char *)&uMagic, sizeof(uMagic)); + strOutput.append((const char *)&uLength, sizeof(uLength)); + strOutput.append(strRawEntitlementsData.data(), strRawEntitlementsData.size()); + + return true; +} + +bool SlotParseCodeDirectory(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_CODEDIRECTORY", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + vector arrCodeSlots; + vector arrSpecialSlots; + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + uint8_t *pHashes = pSlotBase + LE(cdHeader.hashOffset); + for (uint32_t i = 0; i < LE(cdHeader.nCodeSlots); i++) + { + arrCodeSlots.push_back(pHashes + cdHeader.hashSize * i); + } + for (uint32_t i = 0; i < LE(cdHeader.nSpecialSlots); i++) + { + arrSpecialSlots.push_back(pHashes - cdHeader.hashSize * (i + 1)); + } + + ZLog::PrintV("\tversion: \t0x%x\n", LE(cdHeader.version)); + ZLog::PrintV("\tflags: \t\t%u\n", LE(cdHeader.flags)); + ZLog::PrintV("\thashOffset: \t%u\n", LE(cdHeader.hashOffset)); + ZLog::PrintV("\tidentOffset: \t%u\n", LE(cdHeader.identOffset)); + ZLog::PrintV("\tnSpecialSlots: \t%u\n", LE(cdHeader.nSpecialSlots)); + ZLog::PrintV("\tnCodeSlots: \t%u\n", LE(cdHeader.nCodeSlots)); + ZLog::PrintV("\tcodeLimit: \t%u\n", LE(cdHeader.codeLimit)); + ZLog::PrintV("\thashSize: \t%u\n", cdHeader.hashSize); + ZLog::PrintV("\thashType: \t%u\n", cdHeader.hashType); + ZLog::PrintV("\tspare1: \t%u\n", cdHeader.spare1); + ZLog::PrintV("\tpageSize: \t%u\n", cdHeader.pageSize); + ZLog::PrintV("\tspare2: \t%u\n", LE(cdHeader.spare2)); + + uint32_t uVersion = LE(cdHeader.version); + if (uVersion >= 0x20100) + { + ZLog::PrintV("\tscatterOffset: \t%u\n", LE(cdHeader.scatterOffset)); + } + if (uVersion >= 0x20200) + { + ZLog::PrintV("\tteamOffset: \t%u\n", LE(cdHeader.teamOffset)); + } + if (uVersion >= 0x20300) + { + ZLog::PrintV("\tspare3: \t%u\n", LE(cdHeader.spare3)); + ZLog::PrintV("\tcodeLimit64: \t%llu\n", LE(cdHeader.codeLimit64)); + } + if (uVersion >= 0x20400) + { + ZLog::PrintV("\texecSegBase: \t%llu\n", LE(cdHeader.execSegBase)); + ZLog::PrintV("\texecSegLimit: \t%llu\n", LE(cdHeader.execSegLimit)); + ZLog::PrintV("\texecSegFlags: \t%llu\n", LE(cdHeader.execSegFlags)); + } + + ZLog::PrintV("\tidentifier: \t%s\n", pSlotBase + LE(cdHeader.identOffset)); + if (uVersion >= 0x20200) + { + ZLog::PrintV("\tteamid: \t%s\n", pSlotBase + LE(cdHeader.teamOffset)); + } + + ZLog::PrintV("\tSpecialSlots:\n"); + for (int i = LE(cdHeader.nSpecialSlots) - 1; i >= 0; i--) + { + const char *suffix = "\t\n"; + switch (i) + { + case 0: + suffix = "\tInfo.plist\n"; + break; + case 1: + suffix = "\tRequirements Slot\n"; + break; + case 2: + suffix = "\tCodeResources\n"; + break; + case 3: + suffix = "\tApplication Specific\n"; + break; + case 4: + suffix = "\tEntitlements Slot\n"; + break; + case 6: + suffix = "\tEntitlements(DER) Slot\n"; + break; + } + PrintSHASum("\t\t\t", arrSpecialSlots[i], cdHeader.hashSize, suffix); + } + + if (ZLog::IsDebug()) + { + ZLog::Print("\tCodeSlots:\n"); + for (uint32_t i = 0; i < LE(cdHeader.nCodeSlots); i++) + { + PrintSHASum("\t\t\t", arrCodeSlots[i], cdHeader.hashSize); + } + } + else + { + ZLog::Print("\tCodeSlots: \tomitted. (use -d option for details)\n"); + } + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + if (1 == cdHeader.hashType) + { + WriteFile("./.zsign_debug/CodeDirectory_SHA1.slot", (const char *)pSlotBase, uSlotLength); + } + else if (2 == cdHeader.hashType) + { + WriteFile("./.zsign_debug/CodeDirectory_SHA256.slot", (const char *)pSlotBase, uSlotLength); + } + } + + return true; +} + +bool SlotBuildCodeDirectory(bool bAlternate, + uint8_t *pCodeBase, + uint32_t uCodeLength, + uint8_t *pCodeSlotsData, + uint32_t uCodeSlotsDataLength, + uint64_t execSegLimit, + uint64_t execSegFlags, + const string &strBundleId, + const string &strTeamId, + const string &strInfoPlistSHA, + const string &strRequirementsSlotSHA, + const string &strCodeResourcesSHA, + const string &strEntitlementsSlotSHA, + const string &strDerEntitlementsSlotSHA, + bool isExecuteArch, + string &strOutput) +{ + strOutput.clear(); + if (NULL == pCodeBase || uCodeLength <= 0 || strBundleId.empty() || strTeamId.empty()) + { + return false; + } + + uint32_t uVersion = 0x20400; + + CS_CodeDirectory cdHeader; + memset(&cdHeader, 0, sizeof(cdHeader)); + cdHeader.magic = BE(CSMAGIC_CODEDIRECTORY); + cdHeader.length = 0; + cdHeader.version = BE(uVersion); + cdHeader.flags = 0; + cdHeader.hashOffset = 0; + cdHeader.identOffset = 0; + cdHeader.nSpecialSlots = 0; + cdHeader.nCodeSlots = 0; + cdHeader.codeLimit = BE(uCodeLength); + cdHeader.hashSize = bAlternate ? 32 : 20; + cdHeader.hashType = bAlternate ? 2 : 1; + cdHeader.spare1 = 0; + cdHeader.pageSize = 12; + cdHeader.spare2 = 0; + cdHeader.scatterOffset = 0; + cdHeader.teamOffset = 0; + cdHeader.execSegBase = 0; + cdHeader.execSegLimit = BE(execSegLimit); + cdHeader.execSegFlags = BE(execSegFlags); + + string strEmptySHA; + strEmptySHA.append(cdHeader.hashSize, 0); + vector arrSpecialSlots; + + if (isExecuteArch) + { + arrSpecialSlots.push_back(strDerEntitlementsSlotSHA.empty() ? strEmptySHA : strDerEntitlementsSlotSHA); + arrSpecialSlots.push_back(strEmptySHA); + } + arrSpecialSlots.push_back(strEntitlementsSlotSHA.empty() ? strEmptySHA : strEntitlementsSlotSHA); + arrSpecialSlots.push_back(strEmptySHA); + arrSpecialSlots.push_back(strCodeResourcesSHA.empty() ? strEmptySHA : strCodeResourcesSHA); + arrSpecialSlots.push_back(strRequirementsSlotSHA.empty() ? strEmptySHA : strRequirementsSlotSHA); + arrSpecialSlots.push_back(strInfoPlistSHA.empty() ? strEmptySHA : strInfoPlistSHA); + + uint32_t uPageSize = (uint32_t)pow(2, cdHeader.pageSize); + uint32_t uPages = uCodeLength / uPageSize; + uint32_t uRemain = uCodeLength % uPageSize; + uint32_t uCodeSlots = uPages + (uRemain > 0 ? 1 : 0); + + uint32_t uHeaderLength = 44; + if (uVersion >= 0x20100) + { + uHeaderLength += sizeof(cdHeader.scatterOffset); + } + if (uVersion >= 0x20200) + { + uHeaderLength += sizeof(cdHeader.teamOffset); + } + if (uVersion >= 0x20300) + { + uHeaderLength += sizeof(cdHeader.spare3); + uHeaderLength += sizeof(cdHeader.codeLimit64); + } + if (uVersion >= 0x20400) + { + uHeaderLength += sizeof(cdHeader.execSegBase); + uHeaderLength += sizeof(cdHeader.execSegLimit); + uHeaderLength += sizeof(cdHeader.execSegFlags); + } + + uint32_t uBundleIDLength = strBundleId.size() + 1; + uint32_t uTeamIDLength = strTeamId.size() + 1; + uint32_t uSpecialSlotsLength = arrSpecialSlots.size() * cdHeader.hashSize; + uint32_t uCodeSlotsLength = uCodeSlots * cdHeader.hashSize; + + uint32_t uSlotLength = uHeaderLength + uBundleIDLength + uSpecialSlotsLength + uCodeSlotsLength; + if (uVersion >= 0x20100) + { + //todo + } + if (uVersion >= 0x20200) + { + uSlotLength += uTeamIDLength; + } + + cdHeader.length = BE(uSlotLength); + cdHeader.identOffset = BE(uHeaderLength); + cdHeader.nSpecialSlots = BE((uint32_t)arrSpecialSlots.size()); + cdHeader.nCodeSlots = BE(uCodeSlots); + + uint32_t uHashOffset = uHeaderLength + uBundleIDLength + uSpecialSlotsLength; + if (uVersion >= 0x20100) + { + //todo + } + if (uVersion >= 0x20200) + { + uHashOffset += uTeamIDLength; + cdHeader.teamOffset = BE(uHeaderLength + uBundleIDLength); + } + cdHeader.hashOffset = BE(uHashOffset); + + strOutput.append((const char *)&cdHeader, uHeaderLength); + strOutput.append(strBundleId.data(), strBundleId.size() + 1); + if (uVersion >= 0x20100) + { + //todo + } + if (uVersion >= 0x20200) + { + strOutput.append(strTeamId.data(), strTeamId.size() + 1); + } + + for (uint32_t i = 0; i < LE(cdHeader.nSpecialSlots); i++) + { + strOutput.append(arrSpecialSlots[i].data(), arrSpecialSlots[i].size()); + } + + if (NULL != pCodeSlotsData && (uCodeSlotsDataLength == uCodeSlots * cdHeader.hashSize)) + { //use exists + strOutput.append((const char *)pCodeSlotsData, uCodeSlotsDataLength); + } + else + { + for (uint32_t i = 0; i < uPages; i++) + { + string strSHASum; + SHASum(cdHeader.hashType, pCodeBase + uPageSize * i, uPageSize, strSHASum); + strOutput.append(strSHASum.data(), strSHASum.size()); + } + if (uRemain > 0) + { + string strSHASum; + SHASum(cdHeader.hashType, pCodeBase + uPageSize * uPages, uRemain, strSHASum); + strOutput.append(strSHASum.data(), strSHASum.size()); + } + } + + return true; +} + +bool SlotParseCMSSignature(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_SIGNATURESLOT", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + JValue jvInfo; + GetCMSInfo(pSlotBase + 8, uSlotLength - 8, jvInfo); + //ZLog::PrintV("%s\n", jvInfo.styleWrite().c_str()); + + ZLog::Print("\tCertificates: \n"); + for (size_t i = 0; i < jvInfo["certs"].size(); i++) + { + ZLog::PrintV("\t\t\t%s\t<=\t%s\n", jvInfo["certs"][i]["Subject"]["CN"].asCString(), jvInfo["certs"][i]["Issuer"]["CN"].asCString()); + } + + ZLog::Print("\tSignedAttrs: \n"); + if (jvInfo["attrs"].has("ContentType")) + { + ZLog::PrintV("\t ContentType: \t%s => %s\n", jvInfo["attrs"]["ContentType"]["obj"].asCString(), jvInfo["attrs"]["ContentType"]["data"].asCString()); + } + + if (jvInfo["attrs"].has("SigningTime")) + { + ZLog::PrintV("\t SigningTime: \t%s => %s\n", jvInfo["attrs"]["SigningTime"]["obj"].asCString(), jvInfo["attrs"]["SigningTime"]["data"].asCString()); + } + + if (jvInfo["attrs"].has("MessageDigest")) + { + ZLog::PrintV("\t MsgDigest: \t%s => %s\n", jvInfo["attrs"]["MessageDigest"]["obj"].asCString(), jvInfo["attrs"]["MessageDigest"]["data"].asCString()); + } + + if (jvInfo["attrs"].has("CDHashes")) + { + string strData = jvInfo["attrs"]["CDHashes"]["data"].asCString(); + StringReplace(strData, "\n", "\n\t\t\t\t"); + ZLog::PrintV("\t CDHashes: \t%s => \n\t\t\t\t%s\n", jvInfo["attrs"]["CDHashes"]["obj"].asCString(), strData.c_str()); + } + + if (jvInfo["attrs"].has("CDHashes2")) + { + ZLog::PrintV("\t CDHashes2: \t%s => \n", jvInfo["attrs"]["CDHashes2"]["obj"].asCString()); + for (size_t i = 0; i < jvInfo["attrs"]["CDHashes2"]["data"].size(); i++) + { + ZLog::PrintV("\t\t\t\t%s\n", jvInfo["attrs"]["CDHashes2"]["data"][i].asCString()); + } + } + + for (size_t i = 0; i < jvInfo["attrs"]["unknown"].size(); i++) + { + JValue &jvAttr = jvInfo["attrs"]["unknown"][i]; + ZLog::PrintV("\t UnknownAttr: \t%s => %s, type: %d, count: %d\n", jvAttr["obj"].asCString(), jvAttr["name"].asCString(), jvAttr["type"].asInt(), jvAttr["count"].asInt()); + } + ZLog::Print("\n"); + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/CMSSignature.slot", (const char *)pSlotBase, uSlotLength); + WriteFile("./.zsign_debug/CMSSignature.der", (const char *)pSlotBase + 8, uSlotLength - 8); + } + return true; +} + +bool SlotBuildCMSSignature(ZSignAsset *pSignAsset, + const string &strCodeDirectorySlot, + const string &strAltnateCodeDirectorySlot, + string &strOutput) +{ + strOutput.clear(); + + JValue jvHashes; + string strCDHashesPlist; + string strCodeDirectorySlotSHA1; + string strAltnateCodeDirectorySlot256; + SHASum(E_SHASUM_TYPE_1, strCodeDirectorySlot, strCodeDirectorySlotSHA1); + SHASum(E_SHASUM_TYPE_256, strAltnateCodeDirectorySlot, strAltnateCodeDirectorySlot256); + + size_t cdHashSize = strCodeDirectorySlotSHA1.size(); + jvHashes["cdhashes"][0].assignData(strCodeDirectorySlotSHA1.data(), cdHashSize); + jvHashes["cdhashes"][1].assignData(strAltnateCodeDirectorySlot256.data(), cdHashSize); + jvHashes.writePList(strCDHashesPlist); + + string strCMSData; + if (!pSignAsset->GenerateCMS(strCodeDirectorySlot, strCDHashesPlist, strCodeDirectorySlotSHA1, strAltnateCodeDirectorySlot256, strCMSData)) + { + return false; + } + + uint32_t uMagic = BE(CSMAGIC_BLOBWRAPPER); + uint32_t uLength = BE((uint32_t)strCMSData.size() + 8); + + strOutput.append((const char *)&uMagic, sizeof(uMagic)); + strOutput.append((const char *)&uLength, sizeof(uLength)); + strOutput.append(strCMSData.data(), strCMSData.size()); + return true; +} + +uint32_t GetCodeSignatureLength(uint8_t *pCSBase) +{ + CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; + if (NULL != psb && CSMAGIC_EMBEDDED_SIGNATURE == LE(psb->magic)) + { + return LE(psb->length); + } + return 0; +} + +bool ParseCodeSignature(uint8_t *pCSBase) +{ + CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; + if (NULL == psb || CSMAGIC_EMBEDDED_SIGNATURE != LE(psb->magic)) + { + return false; + } + + ZLog::PrintV("\n>>> CodeSignature Segment: \n"); + ZLog::PrintV("\tmagic: \t\t0x%x\n", LE(psb->magic)); + ZLog::PrintV("\tlength: \t%d\n", LE(psb->length)); + ZLog::PrintV("\tslots: \t\t%d\n", LE(psb->count)); + + CS_BlobIndex *pbi = (CS_BlobIndex *)(pCSBase + sizeof(CS_SuperBlob)); + for (uint32_t i = 0; i < LE(psb->count); i++, pbi++) + { + uint8_t *pSlotBase = pCSBase + LE(pbi->offset); + switch (LE(pbi->type)) + { + case CSSLOT_CODEDIRECTORY: + SlotParseCodeDirectory(pSlotBase, pbi); + break; + case CSSLOT_REQUIREMENTS: + SlotParseRequirements(pSlotBase, pbi); + break; + case CSSLOT_ENTITLEMENTS: + SlotParseEntitlements(pSlotBase, pbi); + break; + case CSSLOT_DER_ENTITLEMENTS: + SlotParseDerEntitlements(pSlotBase, pbi); + break; + case CSSLOT_ALTERNATE_CODEDIRECTORIES: + SlotParseCodeDirectory(pSlotBase, pbi); + break; + case CSSLOT_SIGNATURESLOT: + SlotParseCMSSignature(pSlotBase, pbi); + break; + case CSSLOT_IDENTIFICATIONSLOT: + SlotParseGeneralHeader("CSSLOT_IDENTIFICATIONSLOT", pSlotBase, pbi); + break; + case CSSLOT_TICKETSLOT: + SlotParseGeneralHeader("CSSLOT_TICKETSLOT", pSlotBase, pbi); + break; + default: + SlotParseGeneralTailer(pSlotBase, SlotParseGeneralHeader("CSSLOT_UNKNOWN", pSlotBase, pbi)); + break; + } + } + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/CodeSignature.blob", (const char *)pCSBase, LE(psb->length)); + } + return true; +} + +bool SlotGetCodeSlotsData(uint8_t *pSlotBase, uint8_t *&pCodeSlots, uint32_t &uCodeSlotsLength) +{ + uint32_t uSlotLength = LE(*(((uint32_t *)pSlotBase) + 1)); + if (uSlotLength < 8) + { + return false; + } + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + pCodeSlots = pSlotBase + LE(cdHeader.hashOffset); + uCodeSlotsLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; + return true; +} + +bool GetCodeSignatureExistsCodeSlotsData(uint8_t *pCSBase, + uint8_t *&pCodeSlots1Data, + uint32_t &uCodeSlots1DataLength, + uint8_t *&pCodeSlots256Data, + uint32_t &uCodeSlots256DataLength) +{ + pCodeSlots1Data = NULL; + pCodeSlots256Data = NULL; + uCodeSlots1DataLength = 0; + uCodeSlots256DataLength = 0; + CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; + if (NULL == psb || CSMAGIC_EMBEDDED_SIGNATURE != LE(psb->magic)) + { + return false; + } + + CS_BlobIndex *pbi = (CS_BlobIndex *)(pCSBase + sizeof(CS_SuperBlob)); + for (uint32_t i = 0; i < LE(psb->count); i++, pbi++) + { + uint8_t *pSlotBase = pCSBase + LE(pbi->offset); + switch (LE(pbi->type)) + { + case CSSLOT_CODEDIRECTORY: + { + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + if (LE(cdHeader.length) > 8) + { + pCodeSlots1Data = pSlotBase + LE(cdHeader.hashOffset); + uCodeSlots1DataLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; + } + } + break; + case CSSLOT_ALTERNATE_CODEDIRECTORIES: + { + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + if (LE(cdHeader.length) > 8) + { + pCodeSlots256Data = pSlotBase + LE(cdHeader.hashOffset); + uCodeSlots256DataLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; + } + } + break; + default: + break; + } + } + + return ((NULL != pCodeSlots1Data) && (NULL != pCodeSlots256Data) && uCodeSlots1DataLength > 0 && uCodeSlots256DataLength > 0); +} diff --git a/zsign/signing.h b/zsign/signing.h new file mode 100644 index 0000000..df026fc --- /dev/null +++ b/zsign/signing.h @@ -0,0 +1,34 @@ +#pragma once +#include "openssl.h" + +bool ParseCodeSignature(uint8_t *pCSBase); +bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput); +bool SlotBuildDerEntitlements(const string &strEntitlements, string &strOutput); +bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput); +bool GetCodeSignatureCodeSlotsData(uint8_t *pCSBase, uint8_t *&pCodeSlots1, uint32_t &uCodeSlots1Length, uint8_t *&pCodeSlots256, uint32_t &uCodeSlots256Length); +bool SlotBuildCodeDirectory(bool bAlternate, + uint8_t *pCodeBase, + uint32_t uCodeLength, + uint8_t *pCodeSlotsData, + uint32_t uCodeSlotsDataLength, + uint64_t execSegLimit, + uint64_t execSegFlags, + const string &strBundleId, + const string &strTeamId, + const string &strInfoPlistSHA, + const string &strRequirementsSlotSHA, + const string &strCodeResourcesSHA, + const string &strEntitlementsSlotSHA, + const string &strDerEntitlementsSlotSHA, + bool isExecuteArch, + string &strOutput); +bool SlotBuildCMSSignature(ZSignAsset *pSignAsset, + const string &strCodeDirectorySlot, + const string &strAltnateCodeDirectorySlot, + string &strOutput); +bool GetCodeSignatureExistsCodeSlotsData(uint8_t *pCSBase, + uint8_t *&pCodeSlots1Data, + uint32_t &uCodeSlots1DataLength, + uint8_t *&pCodeSlots256Data, + uint32_t &uCodeSlots256DataLength); +uint32_t GetCodeSignatureLength(uint8_t *pCSBase); diff --git a/zsign/zsign.hpp b/zsign/zsign.hpp new file mode 100644 index 0000000..e3446c2 --- /dev/null +++ b/zsign/zsign.hpp @@ -0,0 +1,44 @@ +// +// zsign.hpp +// feather +// +// Created by HAHALOSAH on 5/22/24. +// + +#ifndef zsign_hpp +#define zsign_hpp + +#include +#import + +#ifdef __cplusplus +extern "C" { +#endif + +bool InjectDyLib(NSString *filePath, + NSString *dylibPath, + bool weakInject, + bool bCreate); + +bool ChangeDylibPath(NSString *filePath, + NSString *oldPath, + NSString *newPath); + +bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray); +bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray); + +void zsign(NSString *appPath, + NSString* execName, + NSData *prov, + NSData *key, + NSString *pass, + NSProgress* progress, + void(^completionHandler)(BOOL success, NSError *error) + ); + + +#ifdef __cplusplus +} +#endif + +#endif /* zsign_hpp */ diff --git a/zsign/zsign.mm b/zsign/zsign.mm new file mode 100644 index 0000000..62b055e --- /dev/null +++ b/zsign/zsign.mm @@ -0,0 +1,232 @@ +#include "zsign.hpp" +#include "common/common.h" +#include "common/json.h" +#include "openssl.h" +#include "macho.h" +#include "bundle.h" +#include +#include +#include +#include + +NSString* getTmpDir() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + return [[[paths objectAtIndex:0] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"tmp"]; +} + +extern "C" { + +bool InjectDyLib(NSString *filePath, NSString *dylibPath, bool weakInject, bool bCreate) { + ZTimer gtimer; + @autoreleasepool { + // Convert NSString to std::string + std::string filePathStr = [filePath UTF8String]; + std::string dylibPathStr = [dylibPath UTF8String]; + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + bool success = machO.InjectDyLib(weakInject, dylibPathStr.c_str(), bCreate); + + machO.Free(); + + if (success) { + gtimer.Print(">>> Dylib injected successfully!"); + return true; + } else { + gtimer.Print(">>> Failed to inject dylib."); + return false; + } + } +} + +bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray) { + ZTimer gtimer; + @autoreleasepool { + // Convert NSString to std::string + std::string filePathStr = [filePath UTF8String]; + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + std::vector dylibPaths = machO.ListDylibs(); + + if (!dylibPaths.empty()) { + gtimer.Print(">>> List of dylibs in the Mach-O file:"); + for (vector::iterator it = dylibPaths.begin(); it < dylibPaths.end(); ++it) { + std::string dylibPath = *it; + NSString *dylibPathStr = [NSString stringWithUTF8String:dylibPath.c_str()]; + [dylibPathsArray addObject:dylibPathStr]; + } + } else { + gtimer.Print(">>> No dylibs found in the Mach-O file."); + } + + machO.Free(); + + return true; + } +} + +bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray) { + ZTimer gtimer; + @autoreleasepool { + std::string filePathStr = [filePath UTF8String]; + std::set dylibsToRemove; + + for (NSString *dylibPath in dylibPathsArray) { + dylibsToRemove.insert([dylibPath UTF8String]); + } + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + machO.RemoveDylib(dylibsToRemove); + + machO.Free(); + + gtimer.Print(">>> Dylibs uninstalled successfully!"); + return true; + } +} + + + +bool ChangeDylibPath(NSString *filePath, NSString *oldPath, NSString *newPath) { + ZTimer gtimer; + @autoreleasepool { + // Convert NSString to std::string + std::string filePathStr = [filePath UTF8String]; + std::string oldPathStr = [oldPath UTF8String]; + std::string newPathStr = [newPath UTF8String]; + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + bool success = machO.ChangeDylibPath(oldPathStr.c_str(), newPathStr.c_str()); + + machO.Free(); + + if (success) { + gtimer.Print(">>> Dylib path changed successfully!"); + return true; + } else { + gtimer.Print(">>> Failed to change dylib path."); + return false; + } + } +} + +NSError* makeErrorFromLog(const std::vector& vec) { + NSMutableString *result = [NSMutableString string]; + + for (size_t i = 0; i < vec.size(); ++i) { + // Convert each std::string to NSString + NSString *str = [NSString stringWithUTF8String:vec[i].c_str()]; + [result appendString:str]; + + // Append newline if it's not the last element + if (i != vec.size() - 1) { + [result appendString:@"\n"]; + } + } + + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : result + }; + return [NSError errorWithDomain:@"Failed to Sign" code:-1 userInfo:userInfo]; +} + +ZSignAsset zSignAsset; + +void zsign(NSString *appPath, + NSString* execName, + NSData *prov, + NSData *key, + NSString *pass, + NSProgress* progress, + void(^completionHandler)(BOOL success, NSError *error) + ) +{ + ZTimer gtimer; + ZTimer timer; + timer.Reset(); + + bool bForce = false; + bool bWeakInject = false; + bool bDontGenerateEmbeddedMobileProvision = YES; + + string strPassword; + + string strDyLibFile; + string strOutputFile; + + string strEntitlementsFile; + + bForce = true; + const char* strPKeyFileData = (const char*)[key bytes]; + const char* strProvFileData = (const char*)[prov bytes]; + strPassword = [pass cStringUsingEncoding:NSUTF8StringEncoding]; + + + string strPath = [appPath cStringUsingEncoding:NSUTF8StringEncoding]; + string execNameStr = [execName cStringUsingEncoding:NSUTF8StringEncoding]; + + bool _ = ZLog::logs.empty(); + + __block ZSignAsset zSignAsset; + + if (!zSignAsset.InitSimple(strPKeyFileData, (int)[key length], strProvFileData, (int)[prov length], strPassword)) { + completionHandler(NO, makeErrorFromLog(ZLog::logs)); + bool _ = ZLog::logs.empty(); + return; + } + + bool bEnableCache = true; + string strFolder = strPath; + + __block ZAppBundle bundle; + bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, execNameStr, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); + + if(!success) { + completionHandler(NO, makeErrorFromLog(ZLog::logs)); + bool _ = ZLog::logs.empty(); + return; + } + + int filesNeedToSign = bundle.GetSignCount(); + [progress setTotalUnitCount:filesNeedToSign]; + bundle.progressHandler = [&progress] { + [progress setCompletedUnitCount:progress.completedUnitCount + 1]; + }; + + + + + ZLog::PrintV(">>> Files Need to Sign: \t%d\n", filesNeedToSign); + bool bRet = bundle.StartSign(bEnableCache); + timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed"); + gtimer.Print(">>> Done."); + completionHandler(YES, nil); + _ = ZLog::logs.empty(); + + return; +} + +} diff --git a/zsign/zsigner.h b/zsign/zsigner.h new file mode 100644 index 0000000..8c5ce4d --- /dev/null +++ b/zsign/zsigner.h @@ -0,0 +1,11 @@ +// +// zsigner.h +// LiveContainer +// +// Created by s s on 2024/11/10. +// +#import + +@interface ZSigner : NSObject ++ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; +@end diff --git a/zsign/zsigner.m b/zsign/zsigner.m new file mode 100644 index 0000000..ffc7def --- /dev/null +++ b/zsign/zsigner.m @@ -0,0 +1,22 @@ +// +// zsigner.m +// LiveContainer +// +// Created by s s on 2024/11/10. +// + +#import "zsigner.h" +#import "zsign.hpp" + +NSProgress* currentZSignProgress; + +@implementation ZSigner ++ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { + NSProgress* ans = [NSProgress progressWithTotalUnitCount:1000]; + NSLog(@"[LC] init sign!"); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + zsign(appPath, execName, prov, key, pass, ans, completionHandler); + }); + return ans; +} +@end From d86a501c89d4abf831c2b9f18632b0fd337b6307 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 15 Nov 2024 20:49:06 +0800 Subject: [PATCH 02/32] new jitless mode & remove unused files --- AltStoreTweak/Makefile | 2 +- AltStoreTweak/Tweak.m | 82 +- LiveContainerSwiftUI/LCAppSettingsView.swift | 5 +- LiveContainerSwiftUI/LCSettingsView.swift | 69 +- LiveContainerSwiftUI/Localizable.xcstrings | 95 ++- LiveContainerSwiftUI/Shared.swift | 11 + LiveContainerUI/LCAppDelegate.h | 8 - LiveContainerUI/LCAppDelegate.m | 38 - LiveContainerUI/LCAppInfo.m | 13 +- LiveContainerUI/LCAppListViewController.h | 6 - LiveContainerUI/LCAppListViewController.m | 734 ------------------ .../LCJITLessSetupViewController.h | 14 - .../LCJITLessSetupViewController.m | 118 --- LiveContainerUI/LCSettingsListController.h | 6 - LiveContainerUI/LCSettingsListController.m | 62 -- LiveContainerUI/LCTabBarController.h | 7 - LiveContainerUI/LCTabBarController.m | 35 - LiveContainerUI/LCTweakListViewController.h | 4 - LiveContainerUI/LCTweakListViewController.m | 362 --------- LiveContainerUI/LCUtils.h | 6 +- LiveContainerUI/LCUtils.m | 76 +- LiveContainerUI/LCWebView.h | 11 - LiveContainerUI/LCWebView.m | 150 ---- LiveContainerUI/MBRoundProgressView.h | 6 - LiveContainerUI/MBRoundProgressView.m | 69 -- LiveContainerUI/Makefile | 3 +- LiveContainerUI/UIViewController+LCAlert.h | 10 - LiveContainerUI/UIViewController+LCAlert.m | 58 -- Makefile | 2 - entitlements_setup.xml | 20 - main.m | 1 - 31 files changed, 213 insertions(+), 1870 deletions(-) delete mode 100644 LiveContainerUI/LCAppDelegate.h delete mode 100644 LiveContainerUI/LCAppDelegate.m delete mode 100644 LiveContainerUI/LCAppListViewController.h delete mode 100644 LiveContainerUI/LCAppListViewController.m delete mode 100644 LiveContainerUI/LCJITLessSetupViewController.h delete mode 100644 LiveContainerUI/LCJITLessSetupViewController.m delete mode 100644 LiveContainerUI/LCSettingsListController.h delete mode 100644 LiveContainerUI/LCSettingsListController.m delete mode 100644 LiveContainerUI/LCTabBarController.h delete mode 100644 LiveContainerUI/LCTabBarController.m delete mode 100644 LiveContainerUI/LCTweakListViewController.h delete mode 100644 LiveContainerUI/LCTweakListViewController.m delete mode 100644 LiveContainerUI/LCWebView.h delete mode 100644 LiveContainerUI/LCWebView.m delete mode 100644 LiveContainerUI/MBRoundProgressView.h delete mode 100644 LiveContainerUI/MBRoundProgressView.m delete mode 100644 LiveContainerUI/UIViewController+LCAlert.h delete mode 100644 LiveContainerUI/UIViewController+LCAlert.m delete mode 100644 entitlements_setup.xml diff --git a/AltStoreTweak/Makefile b/AltStoreTweak/Makefile index c531d02..9054a3d 100644 --- a/AltStoreTweak/Makefile +++ b/AltStoreTweak/Makefile @@ -5,7 +5,7 @@ include $(THEOS)/makefiles/common.mk LIBRARY_NAME = AltStoreTweak AltStoreTweak_FILES = Tweak.m -AltStoreTweak_CFLAGS = -fobjc-arc +AltStoreTweak_CFLAGS = -fobjc-arc -DCONFIG_COMMIT=\"$(CONFIG_COMMIT)\" AltStoreTweak_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks include $(THEOS_MAKE_PATH)/library.mk diff --git a/AltStoreTweak/Tweak.m b/AltStoreTweak/Tweak.m index f9a4607..ddf6c67 100644 --- a/AltStoreTweak/Tweak.m +++ b/AltStoreTweak/Tweak.m @@ -1,64 +1,66 @@ @import Foundation; @import Security; -void LCCopyKeychainItems(NSString *oldService, NSString *newService) { - // Query to find all keychain items with the old service +NSData* getKeyChainItemFromService(NSString* key, NSString* service) { NSDictionary *query = @{ - (__bridge id)kSecAttrService: oldService, + (__bridge id)kSecAttrService: service, + (__bridge id)kSecAttrAccount: key, (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, (__bridge id)kSecReturnData: @YES, (__bridge id)kSecAttrSynchronizable: (__bridge id)kSecAttrSynchronizableAny, - (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll, - (__bridge id)kSecReturnAttributes: @YES + (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne, }; CFTypeRef result = NULL; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &result); if (status == errSecSuccess) { - NSArray *items = (__bridge NSArray *)result; + NSData *data = (__bridge NSData *)result; + return data; - for (NSDictionary *item in items) { - // Retrieve attributes and data of the keychain item - NSString *account = item[(id)kSecAttrAccount]; - NSData *passwordData = item[(id)kSecValueData]; - - NSDictionary *deleteQuery = @{ - (__bridge id)kSecAttrService: newService, - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrSynchronizable: (__bridge id)kSecAttrSynchronizableAny, - (__bridge id)kSecAttrAccount: account - }; - - OSStatus deleteStatus = SecItemDelete((CFDictionaryRef)deleteQuery); - if (deleteStatus != errSecSuccess && deleteStatus != errSecItemNotFound) { - NSLog(@"[LC] Delete failed %d", (int)deleteStatus); - continue; - } - - // Create a new keychain entry with the new service name - NSDictionary *newItem = @{ - (__bridge id)kSecAttrService: newService, - (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, - (__bridge id)kSecAttrSynchronizable: (__bridge id)kSecAttrSynchronizableAny, - (__bridge id)kSecAttrAccount: account, - (__bridge id)kSecValueData: passwordData - }; - OSStatus addStatus = SecItemAdd((CFDictionaryRef)newItem, NULL); - if (addStatus != errSecSuccess) { - NSLog(@"[LC] Add item failed %d", (int)deleteStatus); - } - } } else { NSLog(@"[LC] Error retrieving keychain items: %d", (int)status); + return nil; } } BOOL synced = NO; __attribute__((constructor)) static void LCAltstoreHookInit(void) { - if (!synced) { - LCCopyKeychainItems(@"com.rileytestut.AltStore", [NSBundle.mainBundle bundleIdentifier]); - synced = YES; + if (synced) { + return; + } + NSLog(@"[LC] LiveContainer AltStore Tweak build %s", CONFIG_COMMIT); + NSString* bundleId = [NSBundle.mainBundle bundleIdentifier]; + NSString* serviceName; + NSArray* appGroups = [NSBundle.mainBundle.infoDictionary objectForKey:@"ALTAppGroups"]; + if(appGroups == nil || [appGroups count] == 0) { + NSLog(@"[LC] Invalid install method! Failed to find App Group ID."); + return; + } + + NSString* appGroupId = appGroups.firstObject; + if([bundleId containsString:@"SideStore"]) { + serviceName = @"com.SideStore.SideStore"; + } else if ([bundleId containsString:@"AltStore"]) { + serviceName = @"com.rileytestut.AltStore"; + } else { + NSLog(@"[LC] Failed to figure out which store this is!"); + return; + } + NSData *certData = getKeyChainItemFromService(@"signingCertificate", serviceName); + if(certData == nil) { + NSLog(@"[LC] Failed to retrive certificate data!"); + return; + } + NSData *certPassword = getKeyChainItemFromService(@"signingCertificatePassword", serviceName); + if(certPassword == nil) { + NSLog(@"[LC] Failed to retrive certificate password!"); + return; } + NSUserDefaults* appGroupUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appGroupId]; + [appGroupUserDefaults setObject:certData forKey:@"LCCertificateData"]; + [appGroupUserDefaults setObject:[NSString stringWithUTF8String:certPassword.bytes] forKey:@"LCCertificatePassword"]; + NSLog(@"[LC] Successfully updated JIT-Less certificate!"); + synced = YES; } diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index 67f5107..149f571 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -179,11 +179,10 @@ struct LCAppSettingsView : View{ } Picker(selection: $model.uiSigner) { + Text("AltSign").tag(Signer.AltSign) Text("ZSign").tag(Signer.ZSign) - Text("AltSigner").tag(Signer.AltSigner) - } label: { - Text("Signer") + Text("lc.appSettings.signer".loc) } .onChange(of: model.uiSigner, perform: { newValue in Task { await setSigner(newValue) } diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index d46b7d1..dceeb17 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -38,10 +38,12 @@ struct LCSettingsView: View { @State var sideJITServerAddress : String @State var deviceUDID: String - @State var isSideStore : Bool + @State var isSideStore : Bool = true @EnvironmentObject private var sharedModel : SharedModel + let storeName = LCUtils.getStoreName() + init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>) { _isJitLessEnabled = State(initialValue: LCUtils.certificatePassword() != nil) _defaultSigner = State(initialValue: Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))!) @@ -77,41 +79,36 @@ struct LCSettingsView: View { Form { if sharedModel.multiLCStatus != 2 { Section{ + Button { - setupJitLess() + Task { await patchAltStore() } } label: { - if isJitLessEnabled { - Text("lc.settings.renewJitLess".loc) + if isAltStorePatched { + Text("lc.settings.patchStoreAgain %@".localizeWithFormat(storeName)) } else { - Text("lc.settings.setupJitLess".loc) + Text("lc.settings.patchStore %@".localizeWithFormat(storeName)) } } - if !isSideStore { + if isAltStorePatched { Button { - Task { await patchAltStore() } + testJITLessMode() } label: { - if isAltStorePatched { - Text("lc.settings.patchAltstoreAgain".loc) - } else { - Text("lc.settings.patchAltstore".loc) - } + Text("lc.settings.testJitLess".loc) } } Picker(selection: $defaultSigner) { + Text("AltSign").tag(Signer.AltSign) Text("ZSign").tag(Signer.ZSign) - Text("AltSigner").tag(Signer.AltSigner) - } label: { - Text("Default Signer") + Text("lc.settings.defaultSigner") } - } header: { Text("lc.settings.jitLess".loc) } footer: { - Text("lc.settings.jitLessDesc".loc) + Text("lc.settings.jitLessDesc".loc + "\n" + "lc.settings.signer.desc".loc) } } @@ -317,7 +314,7 @@ struct LCSettingsView: View { } message: { Text("lc.settings.cleanKeychainDesc".loc) } - .alert("lc.settings.patchAltstore".loc, isPresented: $patchAltStoreAlert.show) { + .alert("lc.settings.patchStore %@".localizeWithFormat(LCUtils.getStoreName()), isPresented: $patchAltStoreAlert.show) { Button(role: .destructive) { patchAltStoreAlert.close(result: true) } label: { @@ -328,7 +325,7 @@ struct LCSettingsView: View { patchAltStoreAlert.close(result: false) } } message: { - Text("lc.settings.patchAltstoreDesc".loc) + Text("lc.settings.patchStoreDesc %@ %@ %@".localizeWithFormat(storeName, storeName, storeName)) } .onChange(of: isAltCertIgnored) { newValue in saveItem(key: "LCIgnoreALTCertificate", val: newValue) @@ -367,26 +364,27 @@ struct LCSettingsView: View { LCUtils.appGroupUserDefault.setValue(val, forKey: key) } - func setupJitLess() { + func testJITLessMode() { if !LCUtils.isAppGroupAltStoreLike() { errorInfo = "lc.settings.unsupportedInstallMethod".loc errorShow = true return; } - if LCUtils.store() == .AltStore && !isAltStorePatched { - errorInfo = "lc.settings.error.altstoreNotPatched".loc + if !isAltStorePatched { + errorInfo = "lc.settings.error.storeNotPatched %@".localizeWithFormat(storeName) errorShow = true return; } - do { - let packedIpaUrl = try LCUtils.archiveIPA(withSetupMode: true) - let storeInstallUrl = String(format: LCUtils.storeInstallURLScheme(), packedIpaUrl.absoluteString) - UIApplication.shared.open(URL(string: storeInstallUrl)!) - } catch { - errorInfo = error.localizedDescription - errorShow = true + LCUtils.validateJITLessSetup { success, error in + if success { + successInfo = "lc.jitlessSetup.success".loc + successShow = true + } else { + errorInfo = "lc.jitlessSetup.error.testLibLoadFailed %@ %@ %@".localizeWithFormat(storeName, storeName, storeName) + "\n" + (error?.localizedDescription ?? "") + errorShow = true + } } } @@ -599,16 +597,19 @@ struct LCSettingsView: View { func updateSideStorePatchStatus() { let fm = FileManager() - if LCUtils.store() == .SideStore { - isAltStorePatched = true - return - } guard let appGroupPath = LCUtils.appGroupPath() else { isAltStorePatched = false return } - if(fm.fileExists(atPath: appGroupPath.appendingPathComponent("Apps/com.rileytestut.AltStore/App.app/Frameworks/AltStoreTweak.dylib").path)) { + var patchDylibPath : String; + if (LCUtils.store() == .AltStore) { + patchDylibPath = appGroupPath.appendingPathComponent("Apps/com.rileytestut.AltStore/App.app/Frameworks/AltStoreTweak.dylib").path + } else { + patchDylibPath = appGroupPath.appendingPathComponent("Apps/com.SideStore.SideStore/App.app/Frameworks/AltStoreTweak.dylib").path + } + + if(fm.fileExists(atPath: patchDylibPath)) { isAltStorePatched = true } else { isAltStorePatched = false diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index c0f8f10..e38515e 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -943,6 +943,23 @@ } } }, + "lc.appSettings.signer" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Signer" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "签名工具" + } + } + } + }, "lc.appSettings.toPrivateApp" : { "extractionState" : "manual", "localizations" : { @@ -1402,19 +1419,19 @@ } } }, - "lc.jitlessSetup.error.testLibLoadFailed" : { + "lc.jitlessSetup.error.testLibLoadFailed %@ %@ %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "The test library has failed to load. This means your certificate may be having issue. LiveContainer will try to repair it. SideStore will refresh LiveContainer and then you will try again. Press OK to continue." + "value" : "The test library has failed to load. This means your certificate may be having issue. Please try to: 1. Reopen %@; 2. Refresh all apps in %@; 3. Re-patch %@ and try again." } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "无法加载测试动态链接库,这可能是由于证书出现问题。可通过刷新LiveContainer解决。点击按钮进行刷新。" + "value" : "无法加载测试动态链接库,这可能是由于证书出现问题。请尝试:1. 重新打开%@;2. 刷新%@;3. 如果仍有问题,请尝试重新给%@打补丁。" } } } @@ -1425,13 +1442,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Done. Press OK to finish setting up." + "value" : "JIT-Less Mode Test Passed" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "设置成功,点击按钮继续。" + "value" : "免JIT模式测试通过。" } } } @@ -1647,6 +1664,23 @@ } } }, + "lc.settings.defaultSigner" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Default Signer" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "默认签名工具" + } + } + } + }, "lc.settings.dynamicColors" : { "extractionState" : "manual", "localizations" : { @@ -1681,19 +1715,19 @@ } } }, - "lc.settings.error.altstoreNotPatched" : { + "lc.settings.error.storeNotPatched %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "You must patch AltStore before setting up JIT-Less mode." + "value" : "You must patch %@ before testing JIT-Less mode." } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "在设置免JIT模式前,你必须要先给AltStore打补丁。" + "value" : "在设测试JIT模式前,你必须要先给%@打补丁。" } } } @@ -1874,13 +1908,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "JIT-less allows you to use LiveContainer without having to enable JIT. Requires AltStore or SideStore." + "value" : "JIT-less allows you to use LiveContainer without having to enable JIT. Patch AltStore or SideStore to enable." } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "配置免JIT模式后,LiveContainer无需JIT即可使用。需要SideStore或AltStore。" + "value" : "配置免JIT模式后,LiveContainer无需JIT即可使用。给SideStore或AltStore打补丁后即可使用。" } } } @@ -2122,53 +2156,53 @@ } } }, - "lc.settings.patchAltstore" : { + "lc.settings.patchStore %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Patch AltStore" + "value" : "Patch %@" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "给AltStore打补丁" + "value" : "给%@打补丁" } } } }, - "lc.settings.patchAltstoreAgain" : { + "lc.settings.patchStoreAgain %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Patch AltStore Again" + "value" : "Patch %@ Again" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "重新给AltStore打补丁" + "value" : "重新给%@打补丁" } } } }, - "lc.settings.patchAltstoreDesc" : { + "lc.settings.patchStoreDesc %@ %@ %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "To use JIT-Less mode with AltStore, you must patch AltStore in order to retrive keychain. AltStore's functions will not be affected. Continue?" + "value" : "To use JIT-Less mode with %@, you must patch %@ in order to retrieve certificate. %@’s functions will not be affected. Continue?" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "为了通过AltStore获取证书以配置免JIT模式,你必须要给AltStore打补丁。AltStore的功能不会受到任何影响。要继续吗?" + "value" : "为了通过%@获取证书以配置免JIT模式,你必须要给%@打补丁。%@的功能不会受到任何影响。要继续吗?" } } } @@ -2190,19 +2224,19 @@ } } }, - "lc.settings.setupJitLess" : { + "lc.settings.signer.desc" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Setup JIT-less certificate" + "value" : "If AltSign reports an error during installation, please try ZSign." } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "设置免JIT模式证书" + "value" : "安装App时,如果AltSign报错,可尝试使用ZSign。" } } } @@ -2275,6 +2309,23 @@ } } }, + "lc.settings.testJitLess" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Test JIT-Less Mode" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "测试免JIT模式" + } + } + } + }, "lc.settings.unsupportedInstallMethod" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index c6965c8..05afe0d 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -431,4 +431,15 @@ extension LCUtils { } return true } + + public static func getStoreName() -> String { + switch LCUtils.store() { + case .AltStore: + return "AltStore" + case .SideStore: + return "SideStore" + default: + return "Unknown Store" + } + } } diff --git a/LiveContainerUI/LCAppDelegate.h b/LiveContainerUI/LCAppDelegate.h deleted file mode 100644 index 3b1d160..0000000 --- a/LiveContainerUI/LCAppDelegate.h +++ /dev/null @@ -1,8 +0,0 @@ -#import - -@interface LCAppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; -@property (nonatomic, strong) UIViewController *rootViewController; - -@end diff --git a/LiveContainerUI/LCAppDelegate.m b/LiveContainerUI/LCAppDelegate.m deleted file mode 100644 index 4594e09..0000000 --- a/LiveContainerUI/LCAppDelegate.m +++ /dev/null @@ -1,38 +0,0 @@ -#import "LCAppDelegate.h" -#import "LCJITLessSetupViewController.h" -#import "LCTabBarController.h" -#import "LCUtils.h" -#import - -@implementation LCAppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - UIViewController *viewController; - if ([NSBundle.mainBundle.executablePath.lastPathComponent isEqualToString:@"JITLessSetup"]) { - viewController = [LCJITLessSetupViewController new]; - _rootViewController = [[UINavigationController alloc] initWithRootViewController:viewController]; - } else { - _rootViewController = [LCTabBarController new]; - } - _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - _window.rootViewController = _rootViewController; - [_window makeKeyAndVisible]; - return YES; -} - -- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { - // handle page open request from URL scheme - if([url.host isEqualToString:@"open-web-page"]) { - NSURLComponents* urlComponent = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - if(urlComponent.queryItems.count == 0){ - return YES; - } - - NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:urlComponent.queryItems[0].value options:0]; - NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; - [((LCTabBarController*)_rootViewController) openWebPage:decodedUrl]; - } - return [LCUtils launchToGuestAppWithURL:url]; -} - -@end diff --git a/LiveContainerUI/LCAppInfo.m b/LiveContainerUI/LCAppInfo.m index f0834ed..338e914 100644 --- a/LiveContainerUI/LCAppInfo.m +++ b/LiveContainerUI/LCAppInfo.m @@ -107,6 +107,10 @@ - (UIImage*)icon { icon = [UIImage imageNamed:[_info valueForKeyPath:@"CFBundleIconFiles"][0] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; } + if(!icon) { + icon = [UIImage imageNamed:[_info valueForKeyPath:@"CFBundleIcons~ipad"][@"CFBundlePrimaryIcon"][@"CFBundleIconName"] inBundle:[[NSBundle alloc] initWithPath: _bundlePath] compatibleWithTraitCollection:nil]; + } + if(!icon) { icon = [UIImage imageNamed:@"DefaultIcon"]; } @@ -175,7 +179,10 @@ - (void)preprocessBundleBeforeSiging:(NSURL *)bundleURL completion:(dispatch_blo // Remove PlugIns folder [NSFileManager.defaultManager removeItemAtURL:[bundleURL URLByAppendingPathComponent:@"PlugIns"] error:nil]; // Remove code signature from all library files - [LCUtils removeCodeSignatureFromBundleURL:bundleURL]; + if([self signer] == AltSign) { + [LCUtils removeCodeSignatureFromBundleURL:bundleURL]; + } + dispatch_async(dispatch_get_main_queue(), completion); }); } @@ -272,11 +279,9 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo switch ([self signer]) { case ZSign: - NSLog(@"[LC] using ZSign"); progress = [LCUtils signAppBundleWithZSign:appPathURL execName:info[@"CFBundleExecutable"] completionHandler:signCompletionHandler]; break; - case AltSigner: - NSLog(@"[LC] using AltSigner"); + case AltSign: progress = [LCUtils signAppBundle:appPathURL completionHandler:signCompletionHandler]; break; diff --git a/LiveContainerUI/LCAppListViewController.h b/LiveContainerUI/LCAppListViewController.h deleted file mode 100644 index 56822ed..0000000 --- a/LiveContainerUI/LCAppListViewController.h +++ /dev/null @@ -1,6 +0,0 @@ -#import - -@interface LCAppListViewController : UITableViewController -@property(nonatomic) NSString* acError; -- (void) openWebViewByURLString:(NSString*) urlString; -@end diff --git a/LiveContainerUI/LCAppListViewController.m b/LiveContainerUI/LCAppListViewController.m deleted file mode 100644 index 50131ff..0000000 --- a/LiveContainerUI/LCAppListViewController.m +++ /dev/null @@ -1,734 +0,0 @@ -@import CommonCrypto; -@import Darwin; -@import MachO; -@import SafariServices; -@import UniformTypeIdentifiers; - -#import "LCAppInfo.h" -#import "LCAppListViewController.h" -#import "LCUtils.h" -#import "MBRoundProgressView.h" -#import "UIKitPrivate.h" -#import "UIViewController+LCAlert.h" -#import "unarchive.h" -#import "LCWebView.h" - -@implementation NSURL(hack) -- (BOOL)safari_isHTTPFamilyURL { - // Screw it, Apple - return YES; -} -@end - -@interface LCAppListViewController () -@property(atomic) NSMutableArray *objects; -@property(nonatomic) NSString *bundlePath, *docPath, *tweakPath; -@property(atomic) NSString* pageUrlToOpen; -@property(nonatomic) MBRoundProgressView *progressView; - -@end - -@implementation LCAppListViewController - -- (void)loadView { - [super loadView]; - - NSString *appError = [NSUserDefaults.standardUserDefaults stringForKey:@"error"]; - if (appError) { - [NSUserDefaults.standardUserDefaults removeObjectForKey:@"error"]; - [self showDialogTitle:@"Error" message:appError]; - } - - NSFileManager *fm = [NSFileManager defaultManager]; - self.docPath = [fm URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject.path; - - self.bundlePath = [NSString stringWithFormat:@"%@/Applications", self.docPath]; - [fm createDirectoryAtPath:self.bundlePath withIntermediateDirectories:YES attributes:nil error:nil]; - self.objects = [[fm contentsOfDirectoryAtPath:self.bundlePath error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { - return [object hasSuffix:@".app"]; - }]].mutableCopy; - [self.objects sortUsingSelector:@selector(caseInsensitiveCompare:)]; - - // Setup tweak directory - self.tweakPath = [NSString stringWithFormat:@"%@/Tweaks", self.docPath]; - [NSFileManager.defaultManager createDirectoryAtPath:self.tweakPath withIntermediateDirectories:NO attributes:nil error:nil]; - - // Setup action bar - self.navigationItem.rightBarButtonItems = @[ - [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(launchButtonTapped)], - [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addButtonTapped)] - ]; - - UIButton* openLinkButton = [UIButton buttonWithType:UIButtonTypeCustom]; - openLinkButton.enabled = !!LCUtils.certificatePassword; - openLinkButton.frame = CGRectMake(0, 0, 40, 40); - [openLinkButton setImage:[UIImage systemImageNamed:@"link"] forState:UIControlStateNormal]; - [openLinkButton addTarget:self action:@selector(openUrlButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - - self.navigationItem.leftBarButtonItems = @[ - [[UIBarButtonItem alloc] initWithCustomView:openLinkButton] - ]; - - self.progressView = [[MBRoundProgressView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)]; - if(self.pageUrlToOpen) { - [self openWebViewByURLString:self.pageUrlToOpen]; - self.pageUrlToOpen = nil; - } -} - -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; - [self.tableView reloadData]; - - NSString* webpageToOpen = [NSUserDefaults.standardUserDefaults objectForKey:@"webPageToOpen"]; - if(webpageToOpen) { - [NSUserDefaults.standardUserDefaults removeObjectForKey:@"webPageToOpen"]; - [self openWebViewByURLString:webpageToOpen]; - } -} - -- (void)addButtonTapped { - UIDocumentPickerViewController* documentPickerVC = [[UIDocumentPickerViewController alloc] initForOpeningContentTypes:@[[UTType typeWithFilenameExtension:@"ipa" conformingToType:UTTypeData]]]; - documentPickerVC.allowsMultipleSelection = YES; - documentPickerVC.delegate = self; - [self presentViewController:documentPickerVC animated:YES completion:nil]; -} - -- (void)launchButtonTapped { - UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:self.tableView.indexPathForSelectedRow]; - if (!cell.userInteractionEnabled) { - return; - } - - if ([LCUtils launchToGuestApp]) return; - - [self showDialogTitle:@"Instruction" message:@"To use this button, you need a build of SideStore that supports enabling JIT through URL scheme. Otherwise, you need to manually enable it." - handler:^(UIAlertAction * action) { - [UIApplication.sharedApplication suspend]; - exit(0); - }]; -} - -- (NSString *)performInstallIPA:(NSURL *)url progress:(NSProgress *)progress { - if(![url startAccessingSecurityScopedResource]) { - return @"Failed to access IPA"; - } - NSError *error = nil; - NSFileManager *fm = NSFileManager.defaultManager; - NSString* temp = NSTemporaryDirectory(); - extract(url.path, temp, progress); - [url stopAccessingSecurityScopedResource]; - - NSArray* payloadContents = [[fm contentsOfDirectoryAtPath:[temp stringByAppendingPathComponent: @"Payload"] error:nil] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) { - return [object hasSuffix:@".app"]; - }]]; - if (!payloadContents.firstObject) { - return @"App bundle not found"; - } - - NSString *appName = payloadContents[0]; - NSString *oldAppName = appName; - NSString *outPath = [self.bundlePath stringByAppendingPathComponent: appName]; - - __block int selectedAction = -1; - if ([fm fileExistsAtPath:outPath isDirectory:nil]) { - dispatch_group_t group = dispatch_group_create(); - dispatch_group_enter(group); - dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"Install" - message:@"There is an existing application with the same bundle folder name, what would you like to do?" - preferredStyle:UIAlertControllerStyleAlert]; - id handler = ^(UIAlertAction *action) { - selectedAction = [alert.actions indexOfObject:action]; - if (selectedAction == 0) { // Replace - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[self.objects indexOfObject:appName] inSection:0]; - [self.objects removeObjectAtIndex:indexPath.row]; - [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - } - dispatch_group_leave(group); - }; - for (NSString *action in @[@"Replace", @"Keep both, share data", @"Keep both, don't share data"]) { - [alert addAction:[UIAlertAction actionWithTitle:action style:UIAlertActionStyleDefault handler:handler]]; - } - [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:handler]]; - [self presentViewController:alert animated:YES completion:nil]; - }); - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - } - - LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath:outPath]; - NSString *dataUUID = appInfo.dataUUID; - NSString *tweakFolder = appInfo.tweakFolder ?: @""; - switch (selectedAction) { - case 0: // Replace, handled in the action block - [NSFileManager.defaultManager removeItemAtPath:outPath error:nil]; - break; - case 2: // Keep both, don't share data - dataUUID = NSUUID.UUID.UUIDString; - case 1: // Keep both, share data - appName = [NSString stringWithFormat:@"%@%ld.app", [appName substringToIndex:appName.length-4], (long)CFAbsoluteTimeGetCurrent()]; - outPath = [self.bundlePath stringByAppendingPathComponent:appName]; - break; - } - NSString *payloadPath = [temp stringByAppendingPathComponent:@"Payload"]; - if (selectedAction != 3) { // Did not cancel - self.objects[0] = appName; - [fm moveItemAtPath:[payloadPath stringByAppendingPathComponent:oldAppName] toPath:outPath error:&error]; - // Reconstruct AppInfo with the new Info.plist - appInfo = [[LCAppInfo alloc] initWithBundlePath:outPath]; - // Write data UUID - appInfo.dataUUID = dataUUID; - appInfo.tweakFolder = tweakFolder; - } - [fm removeItemAtPath:payloadPath error:(selectedAction==3 ? &error : nil)]; - if (error) { - return error.localizedDescription; - } - - if (selectedAction == 3) { - return @""; // Cancelled - } else { - return nil; // Succeeded - } -} -- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { - NSProgress *progress = [NSProgress new]; - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - NSMutableString *errorString = [NSMutableString string]; - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; - for (NSURL* url in urls) { - dispatch_async(dispatch_get_main_queue(), ^{ - [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil]; - progress.completedUnitCount = 0; - progress.totalUnitCount = 0; - [self.objects insertObject:@"" atIndex:0]; - [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - }); - - NSString *error = [self performInstallIPA:url progress:progress]; - if (error.length > 0) { - [errorString appendFormat:@"%@: %@\n", url.lastPathComponent, error]; - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [self.progressView removeFromSuperview]; - [progress removeObserver:self forKeyPath:@"fractionCompleted"]; - if (error) { - [self.objects removeObjectAtIndex:indexPath.row]; - [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - return; - } - [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - [self patchExecAndSignIfNeed:indexPath shouldSort:YES]; - }); - } - - if (errorString.length == 0) return; - dispatch_async(dispatch_get_main_queue(), ^{ - [self showDialogTitle:@"Error" message:errorString]; - }); - }); -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"fractionCompleted"]) { - NSProgress *progress = (NSProgress *)object; - dispatch_async(dispatch_get_main_queue(), ^{ - self.progressView.progress = progress.fractionCompleted; - }); - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; -} - -- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { - return [NSString stringWithFormat:@"Version %@-%s (%s/%s)", - NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"], - CONFIG_TYPE, CONFIG_BRANCH, CONFIG_COMMIT]; -} - -- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section { - UITableViewHeaderFooterView *header = [UITableViewHeaderFooterView new]; - header.text = [self tableView:tableView titleForHeaderInSection:section]; - UIButton *hiddenHeaderButton = [[UIButton alloc] initWithFrame:header.frame]; - hiddenHeaderButton.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - hiddenHeaderButton.menu = [UIMenu menuWithChildren:@[ - [UIAction - actionWithTitle:@"Copy" - image:[UIImage systemImageNamed:@"doc.on.clipboard"] - identifier:nil - handler:^(UIAction *action) { - UIPasteboard.generalPasteboard.string = header.textLabel.text; - }], - ]]; - [header addSubview:hiddenHeaderButton]; - return header; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return _objects.count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"]; - cell.preservesSuperviewLayoutMargins = NO; - cell.separatorInset = UIEdgeInsetsZero; - cell.layoutMargins = UIEdgeInsetsZero; - cell.detailTextLabel.numberOfLines = 0; - cell.detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping; - cell.imageView.layer.borderWidth = 1; - cell.imageView.layer.borderColor = [UIColor.labelColor colorWithAlphaComponent:0.1].CGColor; - cell.imageView.layer.cornerRadius = 13.5; - cell.imageView.layer.masksToBounds = YES; - cell.imageView.layer.cornerCurve = kCACornerCurveContinuous; - } - - cell.userInteractionEnabled = self.objects[indexPath.row].length > 0; - if (!cell.userInteractionEnabled) { - cell.textLabel.text = @"Installing"; - cell.detailTextLabel.text = nil; - cell.imageView.image = [[UIImage imageNamed:@"DefaultIcon"] _imageWithSize:CGSizeMake(60, 60)]; - [cell.imageView addSubview:self.progressView]; - return cell; - } - LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath: [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[indexPath.row]]]; - cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ - %@\n%@", [appInfo version], [appInfo bundleIdentifier], [appInfo dataUUID]]; - cell.textLabel.text = [appInfo displayName]; - cell.imageView.image = [[appInfo icon] _imageWithSize:CGSizeMake(60, 60)]; - return cell; -} - -- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return 80.0f; -} - -- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void(^)(BOOL actionPerformed))handler { - NSString *path = [self.bundlePath stringByAppendingPathComponent:self.objects[indexPath.row]]; - LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath:path]; - [self showConfirmationDialogTitle:@"Confirm Uninstallation" - message:[NSString stringWithFormat:@"Are you sure you want to uninstall %@?", appInfo.displayName] - destructive:YES - confirmButtonTitle:@"Uninstall" - handler:^(UIAlertAction * action) { - if (action.style != UIAlertActionStyleCancel) { - NSError *error = nil; - [NSFileManager.defaultManager removeItemAtPath:path error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - } else { - [self.objects removeObjectAtIndex:indexPath.row]; - [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - [self showConfirmationDialogTitle:@"Delete Data Folder" - message:[NSString stringWithFormat:@"Do you also want to delete data folder of %@? You can keep it for future use.", appInfo.displayName] - destructive:YES - confirmButtonTitle:@"Delete" - handler:^(UIAlertAction * action) { - if (action.style != UIAlertActionStyleCancel) { - NSError *error = nil; - NSString* dataFolderPath = [NSString stringWithFormat:@"%@/Data/Application/%@", self.docPath, [appInfo dataUUID]]; - [NSFileManager.defaultManager removeItemAtPath:dataFolderPath error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - } - } - }]; - } - } - handler(YES); - }]; -} - -- (UISwipeActionsConfiguration *) tableView:(UITableView *)tableView -trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { - return [UISwipeActionsConfiguration configurationWithActions:@[ - [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive - title:@"Delete" handler:^(UIContextualAction *action, __kindof UIView *sourceView, void (^completionHandler)(BOOL actionPerformed)) { - [self deleteItemAtIndexPath:indexPath completionHandler:completionHandler]; - }] - ]]; -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - self.navigationItem.leftBarButtonItems[0].enabled = YES; - [NSUserDefaults.standardUserDefaults setObject:self.objects[indexPath.row] forKey:@"selected"]; - [self patchExecAndSignIfNeed:indexPath shouldSort:NO]; -} - -- (void)preprocessBundleBeforeSiging:(NSURL *)bundleURL completion:(dispatch_block_t)completion { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // Remove faulty file - [NSFileManager.defaultManager removeItemAtURL:[bundleURL URLByAppendingPathComponent:@"LiveContainer"] error:nil]; - // Remove PlugIns folder - [NSFileManager.defaultManager removeItemAtURL:[bundleURL URLByAppendingPathComponent:@"PlugIns"] error:nil]; - // Remove code signature from all library files - [LCUtils removeCodeSignatureFromBundleURL:bundleURL]; - dispatch_async(dispatch_get_main_queue(), completion); - }); -} - -- (void)patchExecAndSignIfNeed:(NSIndexPath *)indexPath shouldSort:(BOOL)sortNames { - NSString *appPath = [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[indexPath.row]]; - NSString *infoPath = [NSString stringWithFormat:@"%@/Info.plist", appPath]; - NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath]; - if (!info) { - [self showDialogTitle:@"Error" message:@"Info.plist not found"]; - return; - } - - // Setup data directory - NSString *dataPath = [NSString stringWithFormat:@"%@/Data/Application/%@", self.docPath, info[@"LCDataUUID"]]; - [NSFileManager.defaultManager createDirectoryAtPath:dataPath withIntermediateDirectories:YES attributes:nil error:nil]; - - // Update patch - int currentPatchRev = 5; - if ([info[@"LCPatchRevision"] intValue] < currentPatchRev) { - NSString *execPath = [NSString stringWithFormat:@"%@/%@", appPath, info[@"CFBundleExecutable"]]; - NSString *error = LCParseMachO(execPath.UTF8String, ^(const char *path, struct mach_header_64 *header) { - LCPatchExecSlice(path, header); - }); - if (error) { - [self showDialogTitle:@"Error" message:error]; - return; - } - info[@"LCPatchRevision"] = @(currentPatchRev); - [info writeToFile:infoPath atomically:YES]; - } - - if (!LCUtils.certificatePassword) { - if (sortNames) { - [self.objects sortUsingSelector:@selector(caseInsensitiveCompare:)]; - [self.tableView reloadData]; - } - return; - } - - int signRevision = 1; - - // We're only getting the first 8 bytes for comparison - NSUInteger signID; - if (LCUtils.certificateData) { - uint8_t digest[CC_SHA1_DIGEST_LENGTH]; - CC_SHA1(LCUtils.certificateData.bytes, (CC_LONG)LCUtils.certificateData.length, digest); - signID = *(uint64_t *)digest + signRevision; - } else { - [self showDialogTitle:@"Error" message:@"Failed to find ALTCertificate.p12. Please refresh your store and try again." handler:nil]; - return; - } - - // Sign app if JIT-less is set up - if ([info[@"LCJITLessSignID"] unsignedLongValue] != signID) { - NSURL *appPathURL = [NSURL fileURLWithPath:appPath]; - [self preprocessBundleBeforeSiging:appPathURL completion:^{ - // We need to temporarily fake bundle ID and main executable to sign properly - NSString *tmpExecPath = [appPath stringByAppendingPathComponent:@"LiveContainer.tmp"]; - if (!info[@"LCBundleIdentifier"]) { - // Don't let main executable get entitlements - [NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil]; - - info[@"LCBundleExecutable"] = info[@"CFBundleExecutable"]; - info[@"LCBundleIdentifier"] = info[@"CFBundleIdentifier"]; - info[@"CFBundleExecutable"] = tmpExecPath.lastPathComponent; - info[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; - [info writeToFile:infoPath atomically:YES]; - } - info[@"CFBundleExecutable"] = info[@"LCBundleExecutable"]; - info[@"CFBundleIdentifier"] = info[@"LCBundleIdentifier"]; - [info removeObjectForKey:@"LCBundleExecutable"]; - [info removeObjectForKey:@"LCBundleIdentifier"]; - - __block NSProgress *progress = [LCUtils signAppBundle:appPathURL - completionHandler:^(BOOL success, NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (error) { - [self showDialogTitle:@"Error while signing app" message:error.localizedDescription]; - } else { - info[@"LCJITLessSignID"] = @(signID); - } - - // Remove fake main executable - [NSFileManager.defaultManager removeItemAtPath:tmpExecPath error:nil]; - - // Save sign ID and restore bundle ID - [info writeToFile:infoPath atomically:YES]; - - [progress removeObserver:self forKeyPath:@"fractionCompleted"]; - [self.progressView removeFromSuperview]; - if (sortNames) { - [self.objects sortUsingSelector:@selector(caseInsensitiveCompare:)]; - } - [self.tableView reloadData]; - }); - }]; - if (progress) { - [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil]; - UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath]; - cell.textLabel.text = @"Signing"; - [cell.imageView addSubview:self.progressView]; - cell.userInteractionEnabled = NO; - } - }]; - } -} - -- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point { - if (self.objects[indexPath.row].length == 0) return nil; - - NSFileManager *fm = NSFileManager.defaultManager; - LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath:[NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[indexPath.row]]]; - - NSString *dataPath = [NSString stringWithFormat:@"%@/Data/Application", self.docPath]; - NSArray *dataFolderNames = [fm contentsOfDirectoryAtPath:dataPath error:nil]; - NSMutableArray *dataFolderItems = [NSMutableArray array]; - dataFolderItems[0] = [UIAction - actionWithTitle:@"Add data folder" - image:[UIImage systemImageNamed:@"plus"] - identifier:nil - handler:^(UIAction *action) { - [self showInputDialogTitle:dataFolderItems[0].title message:@"Enter name" placeholder:NSUUID.UUID.UUIDString callback:^(NSString *name) { - NSString *path = [dataPath stringByAppendingPathComponent:name]; - NSError *error; - [fm createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:&error]; - if (error) { - return error.localizedDescription; - } - appInfo.dataUUID = name; - [self.tableView reloadData]; - return (NSString *)nil; - }]; - }]; - dataFolderItems[1] = [UIAction - actionWithTitle:@"Rename data folder" - image:[UIImage systemImageNamed:@"pencil"] - identifier:nil - handler:^(UIAction *action) { - [self showInputDialogTitle:dataFolderItems[1].title message:@"Enter name" placeholder:appInfo.dataUUID callback:^(NSString *name) { - if ([name isEqualToString:appInfo.dataUUID]) { - return (NSString *)nil; - } - NSString *source = [dataPath stringByAppendingPathComponent:appInfo.dataUUID]; - NSString *dest = [dataPath stringByAppendingPathComponent:name]; - NSError *error; - [fm moveItemAtPath:source toPath:dest error:&error]; - if (error) { - return error.localizedDescription; - } - appInfo.dataUUID = name; - [self.tableView reloadData]; - return (NSString *)nil; - }]; - }]; - int reservedCount = dataFolderItems.count; - for (int i = 0; i < dataFolderNames.count; i++) { - dataFolderItems[i + reservedCount] = [UIAction - actionWithTitle:dataFolderNames[i] - image:nil identifier:nil - handler:^(UIAction *action) { - appInfo.dataUUID = dataFolderNames[i]; - [self.tableView reloadData]; - }]; - if ([appInfo.dataUUID isEqualToString:dataFolderNames[i]]) { - dataFolderItems[i + reservedCount].state = UIMenuElementStateOn; - } - } - - NSArray *tweakFolderNames = [[fm contentsOfDirectoryAtPath:self.tweakPath error:nil] - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *name, NSDictionary *bindings) { - BOOL isDir = NO; - [fm fileExistsAtPath:[self.tweakPath stringByAppendingPathComponent:name] isDirectory:&isDir]; - return isDir; - }]]; - NSMutableArray *tweakFolderItems = [NSMutableArray array]; - tweakFolderItems[0] = [UIAction - actionWithTitle:@"" - image:nil - identifier:nil - handler:^(UIAction *action) { - appInfo.tweakFolder = @""; - }]; - if (appInfo.tweakFolder.length == 0) { - tweakFolderItems[0].state = UIMenuElementStateOn; - } - - reservedCount = tweakFolderItems.count; - for (int i = 0; i < tweakFolderNames.count; i++) { - tweakFolderItems[i + reservedCount] = [UIAction - actionWithTitle:tweakFolderNames[i] - image:nil identifier:nil - handler:^(UIAction *action) { - appInfo.tweakFolder = tweakFolderNames[i]; - //[self.tableView reloadData]; - }]; - if ([appInfo.tweakFolder isEqualToString:tweakFolderNames[i]]) { - tweakFolderItems[i + reservedCount].state = UIMenuElementStateOn; - } - } - - NSArray *menuItems = @[ - [UIAction - actionWithTitle:@"Add to Home Screen" - image:[UIImage systemImageNamed:@"plus.app"] - identifier:nil - handler:^(UIAction *action) { - NSData *data = [NSPropertyListSerialization dataWithPropertyList:appInfo.generateWebClipConfig format:NSPropertyListXMLFormat_v1_0 options:0 error:0]; - NSString *url = [NSString stringWithFormat:@"data:application/x-apple-aspen-config;base64,%@", [data base64EncodedStringWithOptions:0]]; - SFSafariViewController *svc = [[SFSafariViewController alloc] initWithURL:[NSURL URLWithString:url]]; - [self presentViewController:svc animated:YES completion:nil]; - }], - [UIMenu - menuWithTitle:@"Change tweak folder" - image:[UIImage systemImageNamed:@"gearshape.2"] - identifier:nil - options:0 - children:tweakFolderItems], - [UIMenu - menuWithTitle:@"Change data folder" - image:[UIImage systemImageNamed:@"folder.badge.questionmark"] - identifier:nil - options:0 - children:dataFolderItems], - [UIAction - actionWithTitle:@"Open data folder" - image:[UIImage systemImageNamed:@"folder"] - identifier:nil - handler:^(UIAction *action) { - NSString *url = [NSString stringWithFormat:@"shareddocuments://%@/Data/Application/%@", self.docPath, appInfo.dataUUID]; - [UIApplication.sharedApplication openURL:[NSURL URLWithString:url] options:@{} completionHandler:nil]; - }], - [self - destructiveActionWithTitle:@"Reset settings" - image:[UIImage systemImageNamed:@"trash"] - handler:^(UIAction *action) { - // FIXME: delete non-standard user defaults? - NSError *error; - NSString *prefPath = [NSString stringWithFormat:@"%s/Library/Preferences/%@.plist", getenv("HOME"), appInfo.bundleIdentifier]; - [fm removeItemAtPath:prefPath error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - } - }], - [self - destructiveActionWithTitle:@"Reset app data" - image:[UIImage systemImageNamed:@"trash"] - handler:^(UIAction *action) { - NSError *error; - NSString *uuidPath = [dataPath stringByAppendingPathComponent:appInfo.dataUUID]; - [fm removeItemAtPath:uuidPath error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - return; - } - [fm createDirectoryAtPath:uuidPath withIntermediateDirectories:YES attributes:nil error:nil]; - }] - ]; - - return [UIContextMenuConfiguration - configurationWithIdentifier:nil - previewProvider:nil - actionProvider:^UIMenu *(NSArray *suggestedActions) { - return [UIMenu menuWithTitle:self.objects[indexPath.row] children:menuItems]; - }]; -} - -- (UIMenu *)destructiveActionWithTitle:(NSString *)title image:(UIImage *)image handler:(id)handler { - UIAction *confirmAction = [UIAction - actionWithTitle:title - image:image - identifier:nil - handler:handler]; - confirmAction.attributes = UIMenuElementAttributesDestructive; - UIMenu *menu = [UIMenu - menuWithTitle:title - image:image - identifier:nil - options:UIMenuOptionsDestructive - children:@[confirmAction]]; - return menu; -} - - -- (void) openUrlButtonTapped { - [self showInputDialogTitle:@"Input URL" message:@"Input URL scheme or URL to a web page" placeholder:@"scheme://" callback:^(NSString *ans) { - dispatch_async(dispatch_get_main_queue(), ^(void){ - [self openWebViewByURLString:ans]; - }); - return (NSString*)nil; - }]; - -} - -- (void) openWebViewByURLString:(NSString*) urlString { - // wait for the loadView to call again. - if(!self.isViewLoaded) { - self.pageUrlToOpen = urlString; - return; - } - - NSURLComponents* url = [[NSURLComponents alloc] initWithString:urlString]; - if(!url) { - [self showDialogTitle:@"Invalid URL" message:@"The given URL is invalid. Check it and try again."]; - } else { - NSMutableArray* apps = [[NSMutableArray alloc] init]; - for(int i = 0; i < [self.objects count]; ++i) { - LCAppInfo* appInfo = [[LCAppInfo alloc] initWithBundlePath: [NSString stringWithFormat:@"%@/%@", self.bundlePath, self.objects[i]]]; - appInfo.relativeBundlePath = self.objects[i]; - [apps insertObject:appInfo atIndex:i]; - } - - // use https for http and empty scheme - if([url.scheme length ] == 0) { - url.scheme = @"https"; - } else if (![url.scheme isEqualToString: @"https"] && ![url.scheme isEqualToString: @"http"]){ - [self launchAppByScheme:url apps:apps]; - } - LCWebView *webViewController = [[LCWebView alloc] initWithURL:url.URL apps:apps]; - UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:webViewController]; - navController.navigationBar.translucent = NO; - - navController.modalPresentationStyle = UIModalPresentationFullScreen; - [self presentViewController:navController animated:YES completion:nil]; - } -} - -- (void) launchAppByScheme:(NSURLComponents*)schemeURL apps:(NSMutableArray*)apps { - // find app - NSString* appId = nil; - for(int i = 0; i < [apps count] && !appId; ++i) { - LCAppInfo* nowApp = apps[i]; - NSMutableArray* schemes = [nowApp urlSchemes]; - if(!schemes) continue; - for(int j = 0; j < [schemes count]; ++j) { - NSString* nowScheme = schemes[j]; - if([nowScheme isEqualToString:schemeURL.scheme]) { - appId = [nowApp relativeBundlePath]; - break; - } - } - } - if (!appId) { - [self showDialogTitle:@"Invalid Scheme" message:@"LiveContainer cannot find an app that supports this scheme."]; - return; - } - - - NSString* message = [NSString stringWithFormat:@"You are about to launch %@, continue?", appId]; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - [NSUserDefaults.standardUserDefaults setObject:appId forKey:@"selected"]; - [NSUserDefaults.standardUserDefaults setObject:schemeURL.string forKey:@"launchAppUrlScheme"]; - if ([LCUtils launchToGuestApp]) return; - }]; - [alert addAction:okAction]; - UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - }]; - [alert addAction:cancelAction]; - [self presentViewController:alert animated:YES completion:nil]; -} - -@end diff --git a/LiveContainerUI/LCJITLessSetupViewController.h b/LiveContainerUI/LCJITLessSetupViewController.h deleted file mode 100644 index 34f245c..0000000 --- a/LiveContainerUI/LCJITLessSetupViewController.h +++ /dev/null @@ -1,14 +0,0 @@ -#import - -@interface LCJITLessAppDelegate : UIResponder - -@property (nonatomic, strong) UIWindow *window; -@property (nonatomic, strong) UIViewController *rootViewController; - -@end - - - -@interface LCJITLessSetupViewController : UIViewController - -@end diff --git a/LiveContainerUI/LCJITLessSetupViewController.m b/LiveContainerUI/LCJITLessSetupViewController.m deleted file mode 100644 index 810fc85..0000000 --- a/LiveContainerUI/LCJITLessSetupViewController.m +++ /dev/null @@ -1,118 +0,0 @@ -@import Darwin; -#import "LCJITLessSetupViewController.h" -#import "LCUtils.h" -#import "UIKitPrivate.h" -#import "Localization.h" - -@implementation LCJITLessAppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - UIViewController *viewController; - viewController = [LCJITLessSetupViewController new]; - _rootViewController = [[UINavigationController alloc] initWithRootViewController:viewController]; - _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - _window.rootViewController = _rootViewController; - [_window makeKeyAndVisible]; - return YES; -} - -@end - -@implementation LCJITLessSetupViewController - -- (void)showDialogTitle:(NSString *)title message:(NSString *)message handler:(void(^)(UIAlertAction *))handler { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"lc.common.ok".loc style:UIAlertActionStyleDefault handler:handler]; - [alert addAction:okAction]; - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)loadView { - [super loadView]; - - self.view.backgroundColor = UIColor.systemBackgroundColor; - self.title = @"lc.jitlessSetup.title".loc; - - NSString* storeBundleId; - if([LCUtils store] == AltStore) { - // it's wried, but bundleID of AltStore looks like com.xxxxxxxxxx.com.rileytestut.AltStore - NSString* bundleId = [NSBundle.mainBundle bundleIdentifier]; - NSUInteger len = [bundleId length]; - NSString* teamId = [bundleId substringFromIndex:len - 10]; - storeBundleId = [NSString stringWithFormat:@"com.%@.com.rileytestut.AltStore", teamId]; - } else { - storeBundleId = @"com.SideStore.SideStore"; - } - - NSData *certData = [LCUtils keychainItem:@"signingCertificate" ofStore:storeBundleId]; - if (!certData) { - [self showDialogTitle:@"lc.common.error".loc message:@"lc.jitlessSetup.error.certDataNotFound".loc handler:nil]; - return; - } - LCUtils.certificateData = certData; - - NSData *certPassword = [LCUtils keychainItem:@"signingCertificatePassword".loc ofStore:storeBundleId]; - if (!certPassword) { - [self showDialogTitle:@"lc.common.error".loc message:@"lc.jitlessSetup.error.passwordNotFound".loc handler:nil]; - return; - } - LCUtils.certificatePassword = [NSString stringWithUTF8String:certPassword.bytes]; - - // Verify that the certificate is usable - // Create a test app bundle - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"CertificateValidation"]; - [NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; - NSString *tmpExecPath = [path stringByAppendingPathComponent:@"LiveContainer.tmp"]; - NSString *tmpLibPath = [path stringByAppendingPathComponent:@"TestJITLess.dylib"]; - NSString *tmpInfoPath = [path stringByAppendingPathComponent:@"Info.plist"]; - [NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil]; - [NSFileManager.defaultManager copyItemAtPath:[NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"Frameworks/TestJITLess.dylib"] toPath:tmpLibPath error:nil]; - NSMutableDictionary *info = NSBundle.mainBundle.infoDictionary.mutableCopy; - info[@"CFBundleExecutable"] = @"LiveContainer.tmp"; - [info writeToFile:tmpInfoPath atomically:YES]; - - // Sign the test app bundle - [LCUtils signAppBundle:[NSURL fileURLWithPath:path] - completionHandler:^(BOOL success, NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!success) { - [self showDialogTitle:@"lc.jitlessSetup.error.testFailed".loc message:error.localizedDescription handler:nil]; - } else { - // Attempt to load the signed library - void *handle = dlopen(tmpLibPath.UTF8String, RTLD_LAZY); - [self validateSigningTest:(handle != NULL)]; - } - }); - }]; -} - -- (void)validateSigningTest:(BOOL)loaded { - BOOL success = loaded && getenv("LC_JITLESS_TEST_LOADED"); - - NSError *error; - NSURL *url = [LCUtils archiveIPAWithSetupMode:!success error:&error]; - if (!url) { - [self showDialogTitle:@"lc.common.error".loc message:error.localizedDescription handler:nil]; - return; - } - - if (!success) { - [self showDialogTitle:@"lc.common.error".loc message:@"lc.jitlessSetup.error.testLibLoadFailed".loc - handler:^(UIAlertAction * action) { - // Erase signingCertificate - [LCUtils deleteKeychainItem:@"signingCertificate" ofStore:@"com.rileytestut.AltStore"]; - [LCUtils deleteKeychainItem:@"signingCertificate" ofStore:@"com.SideStore.SideStore"]; - [UIApplication.sharedApplication openURL:[NSURL URLWithString:[NSString stringWithFormat:LCUtils.storeInstallURLScheme, url]] options:@{} completionHandler:nil]; - }]; - return; - } - - [self showDialogTitle:@"lc.common.success".loc message:@"lc.jitlessSetup.success".loc - handler:^(UIAlertAction * action) { - [UIApplication.sharedApplication openURL:[NSURL URLWithString:[NSString stringWithFormat:LCUtils.storeInstallURLScheme, url]] options:@{} completionHandler:nil]; - }]; -} - -@end diff --git a/LiveContainerUI/LCSettingsListController.h b/LiveContainerUI/LCSettingsListController.h deleted file mode 100644 index 603ba23..0000000 --- a/LiveContainerUI/LCSettingsListController.h +++ /dev/null @@ -1,6 +0,0 @@ -#import -#import -#import - -@interface LCSettingsListController : PSListController -@end diff --git a/LiveContainerUI/LCSettingsListController.m b/LiveContainerUI/LCSettingsListController.m deleted file mode 100644 index 313700e..0000000 --- a/LiveContainerUI/LCSettingsListController.m +++ /dev/null @@ -1,62 +0,0 @@ -#import "LCSettingsListController.h" -#import "LCUtils.h" -#import "UIViewController+LCAlert.h" - -@implementation LCSettingsListController - -- (NSMutableArray *)specifiers { - if (!_specifiers) { - _specifiers = [self loadSpecifiersFromPlistName:@"Root" target:self bundle:NSBundle.mainBundle]; - } - - return _specifiers; -} - -- (void)loadView { - [super loadView]; - NSString *setupJITLessButtonName = LCUtils.certificatePassword ? @"Renew JIT-less certificate" : @"Setup JIT-less certificate"; - PSSpecifier *setupJITLessButton = [self specifierForID:@"setup-jitless"]; - setupJITLessButton.name = setupJITLessButtonName; -} - -- (id)readPreferenceValue:(PSSpecifier *)specifier { - return [NSUserDefaults.standardUserDefaults valueForKey:specifier.properties[@"key"]]; -} - -- (void)setPreferenceValue:(id)value specifier:(PSSpecifier *)specifier { - [NSUserDefaults.standardUserDefaults setValue:value forKey:specifier.properties[@"key"]]; -} - -- (void)setupJITLessPressed { - if (!LCUtils.isAppGroupAltStoreLike) { - [self showDialogTitle:@"Error" message:@"Unsupported installation method. Please use AltStore or SideStore to setup this feature."]; - return; - } - - NSError *error; - NSURL *url = [LCUtils archiveIPAWithSetupMode:YES error:&error]; - if (!url) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - return; - } - - [UIApplication.sharedApplication openURL:[NSURL URLWithString:[NSString stringWithFormat:LCUtils.storeInstallURLScheme, url]] options:@{} completionHandler:nil]; -} - -- (void)copyAppGroupPathPressed { - UIPasteboard.generalPasteboard.string = LCUtils.appGroupPath.path; -} - -- (void)copyDocumentsPathPressed { - UIPasteboard.generalPasteboard.string = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject.path; -} - -- (void)openSourceCode { - [UIApplication.sharedApplication openURL:[NSURL URLWithString:@"https://github.com/khanhduytran0/LiveContainer"] options:@{} completionHandler:nil]; -} - -- (void)openTwitter { - [UIApplication.sharedApplication openURL:[NSURL URLWithString:@"https://twitter.com/TranKha50277352"] options:@{} completionHandler:nil]; -} - -@end diff --git a/LiveContainerUI/LCTabBarController.h b/LiveContainerUI/LCTabBarController.h deleted file mode 100644 index 37d7652..0000000 --- a/LiveContainerUI/LCTabBarController.h +++ /dev/null @@ -1,7 +0,0 @@ -#import -#import "LCAppListViewController.h" - -@interface LCTabBarController : UITabBarController -@property() LCAppListViewController* appTableVC; -- (void) openWebPage:(NSString*) urlString; -@end diff --git a/LiveContainerUI/LCTabBarController.m b/LiveContainerUI/LCTabBarController.m deleted file mode 100644 index b3e50a7..0000000 --- a/LiveContainerUI/LCTabBarController.m +++ /dev/null @@ -1,35 +0,0 @@ -#import "LCSettingsListController.h" -#import "LCTweakListViewController.h" -#import "LCTabBarController.h" - -@implementation LCTabBarController - -- (void)loadView { - [super loadView]; - - LCAppListViewController* appTableVC = [LCAppListViewController new]; - appTableVC.title = @"Apps"; - self.appTableVC = appTableVC; - - LCTweakListViewController* tweakTableVC = [LCTweakListViewController new]; - tweakTableVC.title = @"Tweaks"; - - LCSettingsListController* settingsListVC = [LCSettingsListController new]; - settingsListVC.title = @"Settings"; - - UINavigationController* appNavigationController = [[UINavigationController alloc] initWithRootViewController:appTableVC]; - UINavigationController* tweakNavigationController = [[UINavigationController alloc] initWithRootViewController:tweakTableVC]; - UINavigationController* settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:settingsListVC]; - - appNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"square.stack.3d.up.fill"]; - tweakNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"wrench.and.screwdriver"]; - settingsNavigationController.tabBarItem.image = [UIImage systemImageNamed:@"gear"]; - - self.viewControllers = @[appNavigationController, tweakNavigationController, settingsNavigationController]; -} - -- (void) openWebPage:(NSString*) urlString { - [self.appTableVC openWebViewByURLString:urlString]; -} - -@end diff --git a/LiveContainerUI/LCTweakListViewController.h b/LiveContainerUI/LCTweakListViewController.h deleted file mode 100644 index ccd8167..0000000 --- a/LiveContainerUI/LCTweakListViewController.h +++ /dev/null @@ -1,4 +0,0 @@ -@import UIKit; - -@interface LCTweakListViewController : UITableViewController -@end diff --git a/LiveContainerUI/LCTweakListViewController.m b/LiveContainerUI/LCTweakListViewController.m deleted file mode 100644 index ee3ba82..0000000 --- a/LiveContainerUI/LCTweakListViewController.m +++ /dev/null @@ -1,362 +0,0 @@ -@import UniformTypeIdentifiers; - -#import "LCTweakListViewController.h" -#import "LCUtils.h" -#import "MBRoundProgressView.h" -#import "UIKitPrivate.h" -#import "UIViewController+LCAlert.h" - -@interface LCTweakListViewController() -@property(nonatomic) NSString *path; -@property(nonatomic) NSMutableArray *objects; - -@property(nonatomic) UIButton *signButton; -@property(nonatomic) MBRoundProgressView *progressView; -@end - -@implementation LCTweakListViewController - -- (instancetype)init { - return self = [super initWithStyle:UITableViewStyleGrouped]; -} - -- (void)loadView { - [super loadView]; - - if (!self.path) { - NSString *docPath = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject.path; - self.path = [docPath stringByAppendingPathComponent:@"Tweaks"]; - } - [self loadPath]; - - UIMenu *addMenu = [UIMenu menuWithTitle:@"" image:nil identifier:nil - options:UIMenuOptionsDisplayInline - children:@[ - [UIAction - actionWithTitle:@"Tweak" - image:[UIImage systemImageNamed:@"doc"] - identifier:nil handler:^(UIAction *action) { - [self addDylibButtonTapped]; - }], - [UIAction - actionWithTitle:@"Folder" - image:[UIImage systemImageNamed:@"folder"] - identifier:nil handler:^(UIAction *action) { - [self addFolderButtonTapped]; - }] - ]]; - - self.signButton = [UIButton buttonWithType:UIButtonTypeCustom]; - self.signButton.enabled = !!LCUtils.certificatePassword; - self.signButton.frame = CGRectMake(0, 0, 40, 40); - [self.signButton setImage:[UIImage systemImageNamed:@"signature"] forState:UIControlStateNormal]; - [self.signButton addTarget:self action:@selector(signTweaksButtonTapped) forControlEvents:UIControlEventTouchUpInside]; - self.progressView = [MBRoundProgressView new]; - self.progressView.hidden = YES; - [self.signButton addSubview:self.progressView]; - self.navigationItem.rightBarButtonItems = @[ - [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd menu:addMenu], - [[UIBarButtonItem alloc] initWithCustomView:self.signButton] - ]; - - self.refreshControl = [UIRefreshControl new]; - [self.refreshControl addTarget:self action:@selector(loadPath) forControlEvents:UIControlEventValueChanged]; -} - -- (void)loadPath { - self.title = self.path.lastPathComponent; - BOOL reload = self.objects != nil; - - NSMutableArray *directories = [NSMutableArray new]; - NSArray *files = [[NSFileManager.defaultManager contentsOfDirectoryAtPath:self.path error:nil] filteredArrayUsingPredicate: - [NSPredicate predicateWithBlock:^BOOL(NSString *name, NSDictionary *bindings) { - BOOL isDir; - NSString *path = [self.path stringByAppendingPathComponent:name]; - [NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDir]; - if (isDir) { - [directories addObject:name]; - } - return !isDir && [name hasSuffix:@".dylib"]; - }]]; - - self.objects = [NSMutableArray new]; - [self.objects addObjectsFromArray:[directories sortedArrayUsingSelector:@selector(compare:)]]; - [self.objects addObjectsFromArray:[files sortedArrayUsingSelector:@selector(compare:)]]; - - if (reload) { - [self.tableView reloadData]; - [self.refreshControl endRefreshing]; - } -} - -- (void)addDylibButtonTapped { - UIDocumentPickerViewController *documentPickerVC = [[UIDocumentPickerViewController alloc] - initForOpeningContentTypes:@[[UTType typeWithFilenameExtension:@"dylib" conformingToType:UTTypeData]] - asCopy:YES]; - documentPickerVC.allowsMultipleSelection = YES; - documentPickerVC.delegate = self; - [self presentViewController:documentPickerVC animated:YES completion:nil]; -} - -- (void)addFolderButtonTapped { - [self showInputDialogTitle:@"Add folder" message:@"Enter name" placeholder:@"Name" callback:^(NSString *name) { - NSError *error; - NSString *path = [self.path stringByAppendingPathComponent:name]; - [NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:NO attributes:@{} error:&error]; - [self loadPath]; - return error.localizedDescription; - }]; -} - -- (void)signTweaksButtonTapped { - [self showConfirmationDialogTitle:@"Re-sign tweaks" - message:@"Continue will re-sign all files in this folder." - destructive:NO - confirmButtonTitle:@"OK" - handler:^(UIAlertAction *action) { - if (action.style == UIAlertActionStyleCancel) return; - [self signFilesInFolder:self.path completionHandler:nil]; - }]; -} - -- (void)signFilesInFolder:(NSString *)path completionHandler:(void(^)(BOOL success))handler { - NSString *codesignPath = [path stringByAppendingPathComponent:@"_CodeSignature"]; - NSString *provisionPath = [path stringByAppendingPathComponent:@"embedded.mobileprovision"]; - NSString *tmpExecPath = [path stringByAppendingPathComponent:@"LiveContainer.tmp"]; - NSString *tmpInfoPath = [path stringByAppendingPathComponent:@"Info.plist"]; - [NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil]; - NSMutableDictionary *info = NSBundle.mainBundle.infoDictionary.mutableCopy; - info[@"CFBundleExecutable"] = @"LiveContainer.tmp"; - [info writeToFile:tmpInfoPath atomically:YES]; - - __block NSProgress *progress = [LCUtils signAppBundle:[NSURL fileURLWithPath:path] - completionHandler:^(BOOL success, NSError *_Nullable signError) { - dispatch_async(dispatch_get_main_queue(), ^{ - // Cleanup - self.progressView.progress = 0; - [NSFileManager.defaultManager removeItemAtPath:codesignPath error:nil]; - [NSFileManager.defaultManager removeItemAtPath:provisionPath error:nil]; - [NSFileManager.defaultManager removeItemAtPath:tmpExecPath error:nil]; - [NSFileManager.defaultManager removeItemAtPath:tmpInfoPath error:nil]; - - if (handler) { - handler(signError == nil); - } - if (signError) { - [self showDialogTitle:@"Error while signing tweaks" message:signError.localizedDescription]; - } - - [progress removeObserver:self forKeyPath:@"fractionCompleted"]; - self.progressView.hidden = YES; - self.signButton.enabled = YES; - [self loadPath]; - }); - }]; - - if (progress) { - self.progressView.hidden = NO; - self.signButton.enabled = NO; - [progress addObserver:self forKeyPath:@"fractionCompleted" options:NSKeyValueObservingOptionNew context:nil]; - } -} - -- (UIAction *)destructiveActionWithTitle:(NSString *)title image:(UIImage *)image handler:(id)handler { - UIAction *action = [UIAction - actionWithTitle:title - image:image - identifier:nil - handler:handler]; - action.attributes = UIMenuElementAttributesDestructive; - return action; -} - -#pragma mark UITableViewDelegate -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return 1; -} - -- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section { - if (self.navigationController.viewControllers.firstObject == self) { - return @"This is the global folder. All tweaks put here will be injected to all guest apps. Create a new folder if you use app-specific tweaks."; - } else { - return @"This is the app-specific folder. Set the tweak folder and the guest app will pick them up recursively."; - } -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { - return self.objects.count; -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; - if (!cell) { - cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"Cell"]; - } - - UIListContentConfiguration *config = cell.defaultContentConfiguration; - config.text = self.objects[indexPath.row]; - - BOOL isDir; - NSString *path = [self.path stringByAppendingPathComponent:self.objects[indexPath.row]]; - [NSFileManager.defaultManager fileExistsAtPath:path isDirectory:&isDir]; - config.image = [UIImage systemImageNamed:(isDir ? @"folder.fill" : @"doc")]; - - if (isDir) { - config.secondaryText = @"folder"; - } else { - NSDictionary *attrs = [NSFileManager.defaultManager attributesOfItemAtPath:path error:nil]; - NSNumber *size = attrs[NSFileSize]; - config.secondaryText = [NSByteCountFormatter stringFromByteCount:size.unsignedLongLongValue - countStyle:NSByteCountFormatterCountStyleFile]; - } - - cell.contentConfiguration = config; - cell.selectionStyle = isDir ? - UITableViewCellSelectionStyleDefault : - UITableViewCellSelectionStyleNone; - return cell; -} - -- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; - if (cell.selectionStyle == UITableViewCellSelectionStyleNone) { - return nil; - } else { - return indexPath; - } -} - -- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { - [tableView deselectRowAtIndexPath:indexPath animated:NO]; - NSString *name = self.objects[indexPath.row]; - LCTweakListViewController *childVC = [LCTweakListViewController new]; - childVC.path = [self.path stringByAppendingPathComponent:name]; - [self.navigationController pushViewController:childVC animated:YES]; -} - -- (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point { - NSArray *menuItems = @[ - [UIAction - actionWithTitle:@"Rename" - image:[UIImage systemImageNamed:@"pencil"] - identifier:nil - handler:^(UIAction *action) { - [self renameItemAtIndexPath:indexPath]; - }], - [self - destructiveActionWithTitle:@"Delete" - image:[UIImage systemImageNamed:@"trash"] - handler:^(UIAction *action) { - [self deleteItemAtIndexPath:indexPath completionHandler:nil]; - }] - ]; - - return [UIContextMenuConfiguration - configurationWithIdentifier:nil - previewProvider:nil - actionProvider:^UIMenu *(NSArray *suggestedActions) { - return [UIMenu menuWithTitle:self.objects[indexPath.row] children:menuItems]; - }]; -} - -- (void)deleteItemAtIndexPath:(NSIndexPath *)indexPath completionHandler:(void(^)(BOOL actionPerformed))handler { - NSString *name = self.objects[indexPath.row]; - NSString *path = [self.path stringByAppendingPathComponent:name]; - [self showConfirmationDialogTitle:@"Confirm" - message:[NSString stringWithFormat:@"Are you sure you want to delete %@?", name] - destructive:YES - confirmButtonTitle:@"Delete" - handler:^(UIAlertAction * action) { - if (action.style != UIAlertActionStyleCancel) { - NSError *error = nil; - [NSFileManager.defaultManager removeItemAtPath:path error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - } else { - [self.objects removeObjectAtIndex:indexPath.row]; - [self.tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; - } - } - if (handler) handler(YES); - }]; -} - -- (void)renameItemAtIndexPath:(NSIndexPath *)indexPath { - [self showInputDialogTitle:@"Rename" message:@"Enter name" placeholder:self.objects[indexPath.row] callback:^(NSString *name) { - NSError *error; - NSString *fromPath = [self.path stringByAppendingPathComponent:self.objects[indexPath.row]]; - NSString *toPath = [self.path stringByAppendingPathComponent:name]; - [NSFileManager.defaultManager moveItemAtPath:fromPath toPath:toPath error:&error]; - [self loadPath]; - return error.localizedDescription; - }]; -} - -- (UISwipeActionsConfiguration *) tableView:(UITableView *)tableView -trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath { - return [UISwipeActionsConfiguration configurationWithActions:@[ - [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive - title:@"Delete" handler:^(UIContextualAction *action, __kindof UIView *sourceView, void (^completionHandler)(BOOL actionPerformed)) { - [self deleteItemAtIndexPath:indexPath completionHandler:completionHandler]; - }] - ]]; -} - -- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { - NSError *error; - NSString *path = self.path; - if (LCUtils.certificatePassword) { - // Move them to a tmp folder to sign them - path = [self.path stringByAppendingPathComponent:@".tmp"]; - [NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:@{} error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - return; - } - } - - for (NSURL *url in urls) { - NSString *filePath = [path stringByAppendingPathComponent:url.path.lastPathComponent]; - [NSFileManager.defaultManager moveItemAtPath:url.path toPath:filePath error:&error]; - if (error) { - [self showDialogTitle:@"Error" message:error.localizedDescription]; - return; - } - LCParseMachO(filePath.UTF8String, ^(const char *path, struct mach_header_64 *header) { - LCPatchAddRPath(path, header); - }); - } - - if (!LCUtils.certificatePassword) { - // JIT stop here - return; - } - - // Setup a fake app bundle for signing - [self signFilesInFolder:path completionHandler:^(BOOL success){ - if (success) { - // Move tweaks back - for (NSURL *url in urls) { - NSString *fromPath = [path stringByAppendingPathComponent:url.path.lastPathComponent]; - NSString *toPath = [self.path stringByAppendingPathComponent:url.path.lastPathComponent]; - [NSFileManager.defaultManager moveItemAtPath:fromPath toPath:toPath error:nil]; - } - } - - // Remove tmp folder - [NSFileManager.defaultManager removeItemAtPath:path error:nil]; - }]; -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"fractionCompleted"]) { - NSProgress *progress = (NSProgress *)object; - dispatch_async(dispatch_get_main_queue(), ^{ - self.progressView.progress = progress.fractionCompleted; - }); - } else { - [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; - } -} - -@end diff --git a/LiveContainerUI/LCUtils.h b/LiveContainerUI/LCUtils.h index 2e7b7ab..8199a17 100644 --- a/LiveContainerUI/LCUtils.h +++ b/LiveContainerUI/LCUtils.h @@ -8,8 +8,8 @@ typedef NS_ENUM(NSInteger, Store){ }; typedef NS_ENUM(NSInteger, Signer){ - ZSign = 0, - AltSigner = 1 + AltSign = 0, + ZSign = 1 }; NSString *LCParseMachO(const char *path, LCParseMachOCallback callback); @@ -26,7 +26,7 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); @interface LCUtils : NSObject -+ (NSURL *)archiveIPAWithSetupMode:(BOOL)setup error:(NSError **)error; ++ (void)validateJITLessSetupWithCompletionHandler:(void (^)(BOOL success, NSError *error))completionHandler; + (NSURL *)archiveIPAWithBundleName:(NSString*)newBundleName error:(NSError **)error; + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error; + (NSData *)certificateData; diff --git a/LiveContainerUI/LCUtils.m b/LiveContainerUI/LCUtils.m index 387285a..7aa76f6 100644 --- a/LiveContainerUI/LCUtils.m +++ b/LiveContainerUI/LCUtils.m @@ -338,42 +338,27 @@ + (void)writeStoreIDToSetupExecutableWithError:(NSError **)error { [data writeToURL:execPath options:0 error:error]; } -+ (NSURL *)archiveIPAWithSetupMode:(BOOL)setup error:(NSError **)error { - if (setup) { - [self writeStoreIDToSetupExecutableWithError:error]; - if (*error) return nil; - [self changeMainExecutableTo:@"JITLessSetup" error:error]; - } else { - [self changeMainExecutableTo:@"LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou" error:error]; - } - if (*error) return nil; - - NSFileManager *manager = NSFileManager.defaultManager; - NSURL *bundlePath = [self.appGroupPath URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer"]; - - NSURL *tmpPath = [self.appGroupPath URLByAppendingPathComponent:@"tmp"]; - [manager removeItemAtURL:tmpPath error:nil]; - - NSURL *tmpPayloadPath = [tmpPath URLByAppendingPathComponent:@"Payload"]; - NSURL *tmpIPAPath = [self.appGroupPath URLByAppendingPathComponent:@"tmp.ipa"]; - - [manager createDirectoryAtURL:tmpPath withIntermediateDirectories:YES attributes:nil error:error]; - if (*error) return nil; - - [manager copyItemAtURL:bundlePath toURL:tmpPayloadPath error:error]; - if (*error) return nil; - - dlopen("/System/Library/PrivateFrameworks/PassKitCore.framework/PassKitCore", RTLD_GLOBAL); - NSData *zipData = [[NSClassFromString(@"PKZipArchiver") new] zippedDataForURL:tmpPayloadPath.URLByDeletingLastPathComponent]; - if (!zipData) return nil; - - [manager removeItemAtURL:tmpPath error:error]; - if (*error) return nil; - - [zipData writeToURL:tmpIPAPath options:0 error:error]; - if (*error) return nil; - - return tmpIPAPath; ++ (void)validateJITLessSetupWithCompletionHandler:(void (^)(BOOL success, NSError *error))completionHandler { + // Verify that the certificate is usable + // Create a test app bundle + NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"CertificateValidation"]; + [NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; + NSString *tmpExecPath = [path stringByAppendingPathComponent:@"LiveContainer.tmp"]; + NSString *tmpLibPath = [path stringByAppendingPathComponent:@"TestJITLess.dylib"]; + NSString *tmpInfoPath = [path stringByAppendingPathComponent:@"Info.plist"]; + [NSFileManager.defaultManager copyItemAtPath:NSBundle.mainBundle.executablePath toPath:tmpExecPath error:nil]; + [NSFileManager.defaultManager copyItemAtPath:[NSBundle.mainBundle.bundlePath stringByAppendingPathComponent:@"Frameworks/TestJITLess.dylib"] toPath:tmpLibPath error:nil]; + NSMutableDictionary *info = NSBundle.mainBundle.infoDictionary.mutableCopy; + info[@"CFBundleExecutable"] = @"LiveContainer.tmp"; + [info writeToFile:tmpInfoPath atomically:YES]; + + // Sign the test app bundle + [LCUtils signAppBundle:[NSURL fileURLWithPath:path] + completionHandler:^(BOOL success, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(success, error); + }); + }]; } + (NSURL *)archiveIPAWithBundleName:(NSString*)newBundleName error:(NSError **)error { @@ -453,7 +438,12 @@ + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error { NSFileManager *manager = NSFileManager.defaultManager; NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:self.appGroupID]; NSURL *lcBundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.kdt.livecontainer"]; - NSURL *bundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.rileytestut.AltStore"]; + NSURL *bundlePath; + if ([self store] == SideStore) { + bundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.SideStore.SideStore"]; + } else { + bundlePath = [appGroupPath URLByAppendingPathComponent:@"Apps/com.rileytestut.AltStore"]; + } NSURL *tmpPath = [appGroupPath URLByAppendingPathComponent:@"tmp"]; [manager removeItemAtURL:tmpPath error:nil]; @@ -474,9 +464,15 @@ + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error { } [manager copyItemAtURL:[lcBundlePath URLByAppendingPathComponent:@"App.app/Frameworks/AltStoreTweak.dylib"] toURL:tweakToURL error:error]; - NSURL* execToPath = [tmpPayloadPath URLByAppendingPathComponent:@"App.app/AltStore"]; - NSString* errorPatchAltStore = LCParseMachO([execToPath.path UTF8String], ^(const char *path, struct mach_header_64 *header) { - LCPatchAltStore(execToPath.path.UTF8String, header); + NSURL* execToPatch; + if ([self store] == SideStore) { + execToPatch = [tmpPayloadPath URLByAppendingPathComponent:@"App.app/SideStore"]; + } else { + execToPatch = [tmpPayloadPath URLByAppendingPathComponent:@"App.app/AltStore"];; + } + + NSString* errorPatchAltStore = LCParseMachO([execToPatch.path UTF8String], ^(const char *path, struct mach_header_64 *header) { + LCPatchAltStore(execToPatch.path.UTF8String, header); }); if (errorPatchAltStore) { NSMutableDictionary* details = [NSMutableDictionary dictionary]; diff --git a/LiveContainerUI/LCWebView.h b/LiveContainerUI/LCWebView.h deleted file mode 100644 index a977783..0000000 --- a/LiveContainerUI/LCWebView.h +++ /dev/null @@ -1,11 +0,0 @@ -#import -#import -#import "LCAppInfo.h" - -@interface LCWebView : UIViewController -- (instancetype)initWithURL:(NSURL *)url apps:(NSMutableArray*)apps; -- (void)askIfLaunchApp:(NSString*)appId url:(NSURL*)launchUrl; -@property (nonatomic) NSURL *url; -@property (nonatomic) NSMutableArray* apps; -@property (strong, nonatomic) WKWebView *webView; -@end diff --git a/LiveContainerUI/LCWebView.m b/LiveContainerUI/LCWebView.m deleted file mode 100644 index c26aa0f..0000000 --- a/LiveContainerUI/LCWebView.m +++ /dev/null @@ -1,150 +0,0 @@ -// -// LCWebview.m -// jump -// -// Created by s s on 2024/8/18. -// - - -#import "LCWebView.h" -#import "LCUtils.h" - -@interface MySchemeHandler : NSObject -- (instancetype)initWithApp:(NSString*)appId viewController:(LCWebView*)lcController; -@property NSString* appId; -@property LCWebView* lcController; -@end - -@implementation MySchemeHandler - -- (instancetype)initWithApp:(NSString*)appId viewController:(LCWebView*)lcController{ - self = [super init]; - self.appId = appId; - self.lcController = lcController; - return self; -} - -- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id)urlSchemeTask { - [self.lcController askIfLaunchApp:self.appId url: urlSchemeTask.request.URL]; -} - -- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id)urlSchemeTask { - NSLog(@"stopURLScheme"); -} - -@end - - - -@implementation LCWebView - -- (instancetype)initWithURL:(NSURL *)url apps:(NSMutableArray*)apps { - self = [super init]; // Call the superclass's init method - if (self) { - self.apps = apps; - self.url = url; // Store the URL string - - } - return self; -} - - -- (void)viewDidLoad { - [super viewDidLoad]; - - if (!self.webView.superview) { - WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; - - - CGRect webViewSize = self.view.bounds; - webViewSize.size.height -= self.navigationController.navigationBar.frame.size.height; - webViewSize.size.height -= [self.view.window.windowScene.statusBarManager statusBarFrame].size.height; - webViewSize.size.height -= 30; - self.webView = [[WKWebView alloc] initWithFrame:webViewSize configuration:config]; - self.webView.navigationDelegate = self; - self.webView.customUserAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 17_6_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Mobile/15E148 Safari/604.1"; - [self.view addSubview:self.webView]; - } - UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"chevron.backward"] style:UIBarButtonItemStylePlain target:self action:@selector(goBack)]; - UIBarButtonItem *forwardButton = [[UIBarButtonItem alloc] initWithImage:[UIImage systemImageNamed:@"chevron.forward"] style:UIBarButtonItemStylePlain target:self action:@selector(goForward)]; - self.navigationItem.leftBarButtonItems = @[backButton, forwardButton]; - - // Add a refresh button on the right - UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadWebView)]; - UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(close)]; - self.navigationItem.rightBarButtonItems = @[closeButton, refreshButton]; - - // Load the webpage passed via the initializer - if (self.url) { - NSURLRequest *request = [NSURLRequest requestWithURL:self.url]; - [self.webView loadRequest:request]; - } -} - -- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation { - self.title = webView.title; -} - -- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler { - // use private API, get rid of Universal link - decisionHandler((WKNavigationActionPolicy)(WKNavigationActionPolicyAllow + 2)); - NSString* scheme = navigationAction.request.URL.scheme; - if([scheme length] == 0 || [scheme isEqualToString:@"https"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"about"] || [scheme isEqualToString:@"itms-appss"]) { - return; - } - // add a unique urlHandler for each app - LCAppInfo* appToOpen = nil; - for(int i = 0; i < [self.apps count] && !appToOpen; ++i) { - LCAppInfo* nowAppInfo = self.apps[i]; - NSMutableArray* schemes = [nowAppInfo urlSchemes]; - if(!schemes) continue; - for(int j = 0; j < [schemes count]; ++j) { - if([scheme isEqualToString:schemes[j]]) { - appToOpen = nowAppInfo; - break; - } - } - } - if(!appToOpen){ - return; - } - [self askIfLaunchApp:appToOpen.relativeBundlePath url:navigationAction.request.URL]; -} - -- (void)goBack { - if ([self.webView canGoBack]) { - [self.webView goBack]; - } -} - -- (void)goForward { - if ([self.webView canGoForward]) { - [self.webView goForward]; - } -} - -- (void)reloadWebView { - [self.webView reload]; -} - -- (void)close { - [self dismissViewControllerAnimated:YES completion:nil]; -} - -- (void)askIfLaunchApp:(NSString*)appId url:(NSURL*)launchUrl { - NSString* message = [NSString stringWithFormat:@"This web page is trying to launch %@, continue?", appId]; - UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"LiveContainer" message:message preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - [NSUserDefaults.standardUserDefaults setObject:appId forKey:@"selected"]; - [NSUserDefaults.standardUserDefaults setObject:launchUrl.absoluteString forKey:@"launchAppUrlScheme"]; - if ([LCUtils launchToGuestApp]) return; - }]; - [alert addAction:okAction]; - UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) { - - }]; - [alert addAction:cancelAction]; - [self presentViewController:alert animated:YES completion:nil]; -} - -@end diff --git a/LiveContainerUI/MBRoundProgressView.h b/LiveContainerUI/MBRoundProgressView.h deleted file mode 100644 index 71ea0e5..0000000 --- a/LiveContainerUI/MBRoundProgressView.h +++ /dev/null @@ -1,6 +0,0 @@ -#import - -@interface MBRoundProgressView : UIView -- (void)setProgress:(float)progress; -- (float)progress; -@end diff --git a/LiveContainerUI/MBRoundProgressView.m b/LiveContainerUI/MBRoundProgressView.m deleted file mode 100644 index 8756006..0000000 --- a/LiveContainerUI/MBRoundProgressView.m +++ /dev/null @@ -1,69 +0,0 @@ -#import "MBRoundProgressView.h" -#define forcolor(x) x/255.0f - -@interface MBRoundProgressView() { -@private - float _progress; -} -@end - -@implementation MBRoundProgressView - -#pragma mark - -#pragma mark Accessors - -- (float)progress { - return _progress; -} - -- (void)setProgress:(float)progress { - _progress = progress; - [self setNeedsDisplay]; -} - -#pragma mark - -#pragma mark Lifecycle - -- (id)init { - return [self initWithFrame:CGRectMake(0.0f, 0.0f, 37.0f, 37.0f)]; -} - -- (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - self.backgroundColor = [UIColor clearColor]; - self.opaque = NO; - } - return self; -} - -#pragma mark - -#pragma mark Drawing - -- (void)drawRect:(CGRect)rect { - - CGRect allRect = self.bounds; - CGRect circleRect = CGRectInset(allRect, 2.0f, 2.0f); - - CGContextRef context = UIGraphicsGetCurrentContext(); - - // Draw background - CGContextSetRGBStrokeColor(context, forcolor(150.0f), forcolor(150.0f), forcolor(150.0f), 1.0f); // white - CGContextSetRGBFillColor(context, 1.0f, 1.0f, 1.0f, 0.1f); // translucent white - CGContextSetLineWidth(context, 2.0f); - CGContextFillEllipseInRect(context, circleRect); - CGContextStrokeEllipseInRect(context, circleRect); - - // Draw progress - CGPoint center = CGPointMake(allRect.size.width / 2, allRect.size.height / 2); - CGFloat radius = (allRect.size.width - 4) / 2; - CGFloat startAngle = - ((float)M_PI / 2); // 90 degrees - CGFloat endAngle = (self.progress * 2 * (float)M_PI) + startAngle; - CGContextSetRGBFillColor(context, forcolor(150.0f), forcolor(150.0f), forcolor(150.0f), 1.0f); // white - CGContextMoveToPoint(context, center.x, center.y); - CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0); - CGContextClosePath(context); - CGContextFillPath(context); -} - -@end diff --git a/LiveContainerUI/Makefile b/LiveContainerUI/Makefile index 15d6243..e41215d 100644 --- a/LiveContainerUI/Makefile +++ b/LiveContainerUI/Makefile @@ -2,8 +2,7 @@ include $(THEOS)/makefiles/common.mk FRAMEWORK_NAME = LiveContainerUI -#LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCMachOUtils.m LCAppListViewController.m LCSettingsListController.m LCTabBarController.m LCTweakListViewController.m LCUtils.m MBRoundProgressView.m UIViewController+LCAlert.m unarchive.m LCAppInfo.m LCWebView.m -LiveContainerUI_FILES = LCVersionInfo.m LCJITLessSetupViewController.m LCUtils.m LCMachOUtils.m LCAppDelegateSwiftUI.m ../Localization.m +LiveContainerUI_FILES = LCVersionInfo.m LCUtils.m LCMachOUtils.m LCAppDelegateSwiftUI.m ../Localization.m LiveContainerUI_CFLAGS = \ -fobjc-arc \ -DCONFIG_TYPE=\"$(CONFIG_TYPE)\" \ diff --git a/LiveContainerUI/UIViewController+LCAlert.h b/LiveContainerUI/UIViewController+LCAlert.h deleted file mode 100644 index 952d65b..0000000 --- a/LiveContainerUI/UIViewController+LCAlert.h +++ /dev/null @@ -1,10 +0,0 @@ -#import - -@interface UIViewController(LCAlert) - -- (void)showDialogTitle:(NSString *)title message:(NSString *)message; -- (void)showDialogTitle:(NSString *)title message:(NSString *)message handler:(void(^)(UIAlertAction *))handler; -- (void)showConfirmationDialogTitle:(NSString *)title message:(NSString *)message destructive:(BOOL)destructive confirmButtonTitle:(NSString *)confirmBtnTitle handler:(void(^)(UIAlertAction *))handler; -- (void)showInputDialogTitle:(NSString *)title message:(NSString *)message placeholder:(NSString *)placeholder callback:(NSString *(^)(NSString *inputText))callback; - -@end diff --git a/LiveContainerUI/UIViewController+LCAlert.m b/LiveContainerUI/UIViewController+LCAlert.m deleted file mode 100644 index 6fcbd89..0000000 --- a/LiveContainerUI/UIViewController+LCAlert.m +++ /dev/null @@ -1,58 +0,0 @@ -#import "UIKitPrivate.h" -#import "UIViewController+LCAlert.h" - -@implementation UIViewController(LCAlert) - -- (void)showDialogTitle:(NSString *)title message:(NSString *)message { - [self showDialogTitle:title message:message handler:nil]; -} - -- (void)showDialogTitle:(NSString *)title message:(NSString *)message handler:(void(^)(UIAlertAction *))handler { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - UIAlertAction* okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:handler]; - [alert addAction:okAction]; - UIAlertAction* copyAction = [UIAlertAction actionWithTitle:@"Copy" style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - UIPasteboard.generalPasteboard.string = message; - if (handler) handler(action); - }]; - [alert addAction:copyAction]; - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)showConfirmationDialogTitle:(NSString *)title message:(NSString *)message destructive:(BOOL)destructive confirmButtonTitle:(NSString *)confirmBtnTitle handler:(void(^)(UIAlertAction *))handler { - UIAlertController* alert = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:confirmBtnTitle style:(destructive ? UIAlertActionStyleDestructive : UIAlertActionStyleDefault) handler:handler]]; - [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:handler]]; - [self presentViewController:alert animated:YES completion:nil]; -} - -- (void)showInputDialogTitle:(NSString *)title message:(NSString *)message placeholder:(NSString *)placeholder callback:(NSString *(^)(NSString *inputText))callback { - UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; - [alert addTextFieldWithConfigurationHandler:^(UITextField *textField) { - textField.placeholder = placeholder; - textField.clearButtonMode = UITextFieldViewModeWhileEditing; - textField.borderStyle = UITextBorderStyleRoundedRect; - }]; - UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { - UITextField *textField = alert.textFields[0]; - NSString *error = callback(textField.text.length == 0 ? placeholder : textField.text); - if (error) { - alert.message = error; - } else { - [self dismissViewControllerAnimated:YES completion:nil]; - } - }]; - okAction.shouldDismissHandler = ^{ - return NO; - }; - [alert addAction:okAction]; - [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]]; - [self presentViewController:alert animated:YES completion:nil]; -} - -@end diff --git a/Makefile b/Makefile index 5b6c0ed..da94f3c 100644 --- a/Makefile +++ b/Makefile @@ -26,8 +26,6 @@ include $(THEOS_MAKE_PATH)/aggregate.mk before-package:: @/Applications/Xcode.app/Contents/Developer/usr/bin/xcstringstool compile ./LiveContainerSwiftUI/Localizable.xcstrings --output-directory $(THEOS_STAGING_DIR)/Applications/LiveContainer.app @/Applications/Xcode.app/Contents/Developer/usr/bin/actool LiveContainerSwiftUI/Assets.xcassets --compile $(THEOS_STAGING_DIR)/Applications/LiveContainer.app --platform iphoneos --minimum-deployment-target 14.0 - @cp $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/JITLessSetup - @ldid -Sentitlements_setup.xml $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/JITLessSetup @mv $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou before-all:: diff --git a/entitlements_setup.xml b/entitlements_setup.xml deleted file mode 100644 index d1154f3..0000000 --- a/entitlements_setup.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - application-identifier - com.kdt.livecontainer - com.apple.security.application-groups - - group.com.SideStore.SideStore - group.com.rileytestut.AltStore - - get-task-allow - - keychain-access-groups - - *.com.kdt.livecontainer.shared - KeychainAccessGroupWillBeWrittenByLiveContainerAAAAAAAAAAAAAAAAAAAA - - - diff --git a/main.m b/main.m index a37d39b..e7b853b 100644 --- a/main.m +++ b/main.m @@ -1,5 +1,4 @@ #import -#import "LiveContainerUI/LCAppDelegate.h" #import "LCSharedUtils.h" #import "UIKitPrivate.h" #import "utils.h" From f3e9a42a8836f4046d87857f925c0d25fb1c0af0 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 23 Nov 2024 11:40:01 +0800 Subject: [PATCH 03/32] Sign Only On Expiration Option --- LiveContainerSwiftUI/LCAppInfo.m | 18 ++++- LiveContainerSwiftUI/LCSettingsView.swift | 77 +++++++++++++++++----- LiveContainerSwiftUI/LCUtils.h | 4 +- LiveContainerSwiftUI/LCUtils.m | 50 +++++--------- LiveContainerSwiftUI/Localizable.xcstrings | 74 ++++++++++----------- LiveContainerSwiftUI/Shared.swift | 4 +- 6 files changed, 131 insertions(+), 96 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 5fec0d8..06cbd36 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -218,6 +218,15 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo int signRevision = 1; + NSDate* expirationDate = info[@"LCExpirationDate"]; + if(expirationDate && [[[NSUserDefaults alloc] initWithSuiteName:[LCUtils appGroupID]] boolForKey:@"LCSignOnlyOnExpiration"]) { + if([expirationDate laterDate:[NSDate now]] == expirationDate) { + // not expired yet, don't sign again + completetionHandler(nil); + return; + } + } + // We're only getting the first 8 bytes for comparison NSUInteger signID; if (LCUtils.certificateData) { @@ -225,7 +234,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo CC_SHA1(LCUtils.certificateData.bytes, (CC_LONG)LCUtils.certificateData.length, digest); signID = *(uint64_t *)digest + signRevision; } else { - completetionHandler(@"Failed to find ALTCertificate.p12. Please refresh your store and try again."); + completetionHandler(@"Failed to find signing certificate. Please refresh your store and try again."); return; } @@ -250,7 +259,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo [info removeObjectForKey:@"LCBundleExecutable"]; [info removeObjectForKey:@"LCBundleIdentifier"]; - void (^signCompletionHandler)(BOOL success, NSError *error) = ^(BOOL success, NSError *_Nullable error) { + void (^signCompletionHandler)(BOOL success, NSDate* expirationDate, NSError *error) = ^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (!error) { info[@"LCJITLessSignID"] = @(signID); @@ -259,10 +268,13 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo // Remove fake main executable [NSFileManager.defaultManager removeItemAtPath:tmpExecPath error:nil]; + + if(!error && expirationDate) { + info[@"LCExpirationDate"] = expirationDate; + } // Save sign ID and restore bundle ID [self save]; - if(error) { completetionHandler(error.localizedDescription); return; diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index f166ed7..7e0977f 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -27,7 +27,7 @@ struct LCSettingsView: View { @State var isJitLessEnabled = false @State var isJITLessTestInProgress = false - @State var isAltCertIgnored = false + @State var isSignOnlyOnExpiration = true @State var frameShortIcon = false @State var silentSwitchApp = false @State var injectToLCItelf = false @@ -46,7 +46,10 @@ struct LCSettingsView: View { init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>) { _isJitLessEnabled = State(initialValue: LCUtils.certificatePassword() != nil) - _isAltCertIgnored = State(initialValue: UserDefaults.standard.bool(forKey: "LCIgnoreALTCertificate")) + if(LCUtils.appGroupUserDefault.object(forKey: "LCSignOnlyOnExpiration") == nil) { + LCUtils.appGroupUserDefault.set(true, forKey: "LCSignOnlyOnExpiration") + } + _isSignOnlyOnExpiration = State(initialValue: LCUtils.appGroupUserDefault.bool(forKey: "LCSignOnlyOnExpiration")) _frameShortIcon = State(initialValue: UserDefaults.standard.bool(forKey: "LCFrameShortcutIcons")) _silentSwitchApp = State(initialValue: UserDefaults.standard.bool(forKey: "LCSwitchAppWithoutAsking")) _injectToLCItelf = State(initialValue: UserDefaults.standard.bool(forKey: "LCLoadTweaksToSelf")) @@ -95,8 +98,17 @@ struct LCSettingsView: View { Text("lc.settings.testJitLess".loc) } .disabled(isJITLessTestInProgress) + Toggle(isOn: $isSignOnlyOnExpiration) { + Text("lc.settings.signOnlyOnExpiration".loc) + } } +// Button { +// export() +// } label: { +// Text("export cert") +// } + } header: { Text("lc.settings.jitLess".loc) @@ -125,17 +137,6 @@ struct LCSettingsView: View { Text("lc.settings.multiLCDesc".loc) } - if(isSideStore) { - Section { - Toggle(isOn: $isAltCertIgnored) { - Text("lc.settings.ignoreAltCert".loc) - } - } footer: { - Text("lc.settings.ignoreAltCertDesc".loc) - } - } - - Section { HStack { Text("lc.settings.JitAddress".loc) @@ -318,10 +319,10 @@ struct LCSettingsView: View { patchAltStoreAlert.close(result: false) } } message: { - Text("lc.settings.patchStoreDesc %@ %@ %@".localizeWithFormat(storeName, storeName, storeName)) + Text("lc.settings.patchStoreDesc %@ %@ %@ %@".localizeWithFormat(storeName, storeName, storeName, storeName)) } - .onChange(of: isAltCertIgnored) { newValue in - saveItem(key: "LCIgnoreALTCertificate", val: newValue) + .onChange(of: isSignOnlyOnExpiration) { newValue in + saveAppGroupItem(key: "LCSignOnlyOnExpiration", val: newValue) } .onChange(of: silentSwitchApp) { newValue in saveItem(key: "LCSwitchAppWithoutAsking", val: newValue) @@ -622,4 +623,48 @@ struct LCSettingsView: View { } } + +// func export() { +// let fileManager = FileManager.default +// let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! +// +// // 1. Copy embedded.mobileprovision from the main bundle to Documents +// if let embeddedURL = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") { +// let destinationURL = documentsURL.appendingPathComponent("embedded.mobileprovision") +// do { +// try fileManager.copyItem(at: embeddedURL, to: destinationURL) +// print("Successfully copied embedded.mobileprovision to Documents.") +// } catch { +// print("Error copying embedded.mobileprovision: \(error)") +// } +// } else { +// print("embedded.mobileprovision not found in the main bundle.") +// } +// +// // 2. Read "certData" from UserDefaults and save to cert.p12 in Documents +// if let certData = LCUtils.certificateData() { +// let certFileURL = documentsURL.appendingPathComponent("cert.p12") +// do { +// try certData.write(to: certFileURL) +// print("Successfully wrote certData to cert.p12 in Documents.") +// } catch { +// print("Error writing certData to cert.p12: \(error)") +// } +// } else { +// print("certData not found in UserDefaults.") +// } +// +// // 3. Read "certPassword" from UserDefaults and save to pass.txt in Documents +// if let certPassword = LCUtils.certificatePassword() { +// let passwordFileURL = documentsURL.appendingPathComponent("pass.txt") +// do { +// try certPassword.write(to: passwordFileURL, atomically: true, encoding: .utf8) +// print("Successfully wrote certPassword to pass.txt in Documents.") +// } catch { +// print("Error writing certPassword to pass.txt: \(error)") +// } +// } else { +// print("certPassword not found in UserDefaults.") +// } +// } } diff --git a/LiveContainerSwiftUI/LCUtils.h b/LiveContainerSwiftUI/LCUtils.h index bb186f8..765ea59 100644 --- a/LiveContainerSwiftUI/LCUtils.h +++ b/LiveContainerSwiftUI/LCUtils.h @@ -26,8 +26,6 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error; + (NSData *)certificateData; + (NSString *)certificatePassword; -+ (void)setCertificateData:(NSData *)data; -+ (void)setCertificatePassword:(NSString *)password; + (BOOL)deleteKeychainItem:(NSString *)key ofStore:(NSString *)store; + (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store; @@ -36,7 +34,7 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); + (BOOL)launchToGuestAppWithURL:(NSURL *)url; + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL; -+ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; ++ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; + (BOOL)isAppGroupAltStoreLike; + (Store)store; + (NSString *)appGroupID; diff --git a/LiveContainerSwiftUI/LCUtils.m b/LiveContainerSwiftUI/LCUtils.m index 33bd2fd..f57c0ba 100644 --- a/LiveContainerSwiftUI/LCUtils.m +++ b/LiveContainerSwiftUI/LCUtils.m @@ -43,21 +43,7 @@ + (NSData *)keychainItem:(NSString *)key ofStore:(NSString *)store { } } -+ (void)setCertificateData:(NSData *)certData { - [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] setObject:certData forKey:@"LCCertificateData"]; - [NSUserDefaults.standardUserDefaults setObject:certData forKey:@"LCCertificateData"]; -} - -+ (NSData *)certificateDataFile { - // it seems that alstore never changes its ALTCertificate.p12 and the one it shipped with is invalid so we ignore it anyhow. - if ([NSUserDefaults.standardUserDefaults boolForKey:@"LCIgnoreALTCertificate"] || [self store] == AltStore) { - return nil; - } - NSURL *url = [self.storeBundlePath URLByAppendingPathComponent:@"ALTCertificate.p12"]; - return [NSData dataWithContentsOfURL:url]; -} - -+ (NSData *)certificateDataProperty { ++ (NSData *)certificateData { NSData* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificateData"]; if(ans) { return ans; @@ -67,19 +53,9 @@ + (NSData *)certificateDataProperty { } -+ (NSData *)certificateData { - // Prefer certificate file over keychain data - return self.certificateDataFile ?: self.certificateDataProperty; -} - + (NSString *)certificatePassword { - if (self.certificateDataFile) { - NSString* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; - if(ans) { - return ans; - } - return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificatePassword"]; - } else if (self.certificateDataProperty) { + // password of cert retrieved from the store tweak is always @"". We just keep this function so we can check if certificate presents without changing codes. + if([[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]) { return @""; } else { return nil; @@ -199,7 +175,7 @@ + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL { } } -+ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { ++ (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { NSError *error; // I'm too lazy to reimplement signer, so let's borrow everything from SideStore @@ -209,28 +185,32 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc // Load libraries from Documents, yeah [self loadStoreFrameworksWithError:&error]; if (error) { - completionHandler(NO, error); + completionHandler(NO, nil, error); return nil; } ALTCertificate *cert = [[NSClassFromString(@"ALTCertificate") alloc] initWithP12Data:self.certificateData password:self.certificatePassword]; if (!cert) { - error = [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:1 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create ALTCertificate"}]; - completionHandler(NO, error); + error = [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:1 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create ALTCertificate. Please try: 1. make sure your store is patched 2. reopen your store 3. refresh all apps"}]; + completionHandler(NO, nil, error); return nil; } ALTProvisioningProfile *profile = [[NSClassFromString(@"ALTProvisioningProfile") alloc] initWithURL:profilePath]; if (!profile) { - error = [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:2 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create ALTProvisioningProfile"}]; - completionHandler(NO, error); + error = [NSError errorWithDomain:NSBundle.mainBundle.bundleIdentifier code:2 userInfo:@{NSLocalizedDescriptionKey: @"Failed to create ALTProvisioningProfile. Please try: 1. make sure your store is patched 2. reopen your store 3. refresh all apps"}]; + completionHandler(NO, nil, error); return nil; } ALTAccount *account = [NSClassFromString(@"ALTAccount") new]; ALTTeam *team = [[NSClassFromString(@"ALTTeam") alloc] initWithName:@"" identifier:@"" /*profile.teamIdentifier*/ type:ALTTeamTypeUnknown account:account]; ALTSigner *signer = [[NSClassFromString(@"ALTSigner") alloc] initWithTeam:team certificate:cert]; + + void (^signCompletionHandler)(BOOL success, NSError *error) = ^(BOOL success, NSError *_Nullable error) { + completionHandler(success, [profile expirationDate], error); + }; - return [signer signAppAtURL:path provisioningProfiles:@[(id)profile] completionHandler:completionHandler]; + return [signer signAppAtURL:path provisioningProfiles:@[(id)profile] completionHandler:signCompletionHandler]; } #pragma mark Setup @@ -322,7 +302,7 @@ + (void)validateJITLessSetupWithCompletionHandler:(void (^)(BOOL success, NSErro // Sign the test app bundle [LCUtils signAppBundle:[NSURL fileURLWithPath:path] - completionHandler:^(BOOL success, NSError *_Nullable error) { + completionHandler:^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ completionHandler(success, error); }); diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 08e9963..12f5490 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -1732,40 +1732,6 @@ } } }, - "lc.settings.ignoreAltCert" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ignore ALTCertificate.p12" - } - }, - "zh_CN" : { - "stringUnit" : { - "state" : "translated", - "value" : "忽略ALTCertificate.p12" - } - } - } - }, - "lc.settings.ignoreAltCertDesc" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "If you see frequent re-sign, enable this option." - } - }, - "zh_CN" : { - "stringUnit" : { - "state" : "translated", - "value" : "如果App重新签名过于频繁,可尝试启用此项。" - } - } - } - }, "lc.settings.injectLCItself" : { "extractionState" : "manual", "localizations" : { @@ -2156,19 +2122,19 @@ } } }, - "lc.settings.patchStoreDesc %@ %@ %@" : { + "lc.settings.patchStoreDesc %@ %@ %@ %@" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "To use JIT-Less mode with %@, you must patch %@ in order to retrieve certificate. %@’s functions will not be affected. Continue?" + "value" : "To use JIT-Less mode with %@, you must patch %@ in order to retrieve certificate. %@’s functions will not be affected. Please confirm that you can refresh %@ before applying the patch. Continue?" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "为了通过%@获取证书以配置免JIT模式,你必须要给%@打补丁。%@的功能不会受到任何影响。要继续吗?" + "value" : "为了通过%@获取证书以配置免JIT模式,你必须要给%@打补丁。%@的功能不会受到任何影响。请确保在打补丁前%@可以正常刷新。要继续吗?" } } } @@ -2190,6 +2156,40 @@ } } }, + "lc.settings.signOnlyOnExpiration" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Re-Sign Only on Certificate Expires" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "仅在证书过期时重新签名" + } + } + } + }, + "lc.settings.signOnlyOnExpirationDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If you see frequent re-sign, enable this option." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果App重新签名过于频繁,可尝试启用此项。" + } + } + } + }, "lc.settings.silentSwitchApp" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 05afe0d..c66d4e4 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -306,7 +306,7 @@ struct SiteAssociation : Codable { } extension LCUtils { - public static let appGroupUserDefault = UserDefaults.init(suiteName: LCUtils.appGroupID())! + public static let appGroupUserDefault = UserDefaults.init(suiteName: LCUtils.appGroupID()) ?? UserDefaults.standard public static func signFilesInFolder(url: URL, onProgressCreated: (Progress) -> Void) async -> String? { let fm = FileManager() @@ -326,7 +326,7 @@ extension LCUtils { } await withCheckedContinuation { c in - let progress = LCUtils.signAppBundle(url) { success, error in + let progress = LCUtils.signAppBundle(url) { success, expirationDate, error in do { if let error = error { ans = error.localizedDescription From 49b7aec512875ce924dae4d7feb808f8db516f43 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 23 Nov 2024 14:15:36 +0800 Subject: [PATCH 04/32] solve crash issue --- LiveContainerUI/LCUtils.m | 16 +- Resources/Info.plist | 5 + zsign/Makefile | 14 - zsign/Utils.hpp | 26 - zsign/Utils.mm | 24 - zsign/archo.cpp | 860 ----------- zsign/archo.h | 42 - zsign/bundle.cpp | 710 --------- zsign/bundle.h | 42 - zsign/common/base64.cpp | 203 --- zsign/common/base64.h | 27 - zsign/common/common.cpp | 848 ---------- zsign/common/common.h | 163 -- zsign/common/json.cpp | 3061 ------------------------------------- zsign/common/json.h | 414 ----- zsign/common/mach-o.h | 580 ------- zsign/macho.cpp | 350 ----- zsign/macho.h | 34 - zsign/openssl.cpp | 944 ------------ zsign/openssl.h | 28 - zsign/signing.cpp | 875 ----------- zsign/signing.h | 34 - zsign/zsign.hpp | 44 - zsign/zsign.mm | 232 --- zsign/zsigner.h | 11 - zsign/zsigner.m | 22 - 26 files changed, 8 insertions(+), 9601 deletions(-) delete mode 100644 zsign/Makefile delete mode 100644 zsign/Utils.hpp delete mode 100644 zsign/Utils.mm delete mode 100644 zsign/archo.cpp delete mode 100644 zsign/archo.h delete mode 100644 zsign/bundle.cpp delete mode 100644 zsign/bundle.h delete mode 100644 zsign/common/base64.cpp delete mode 100644 zsign/common/base64.h delete mode 100644 zsign/common/common.cpp delete mode 100644 zsign/common/common.h delete mode 100644 zsign/common/json.cpp delete mode 100644 zsign/common/json.h delete mode 100644 zsign/common/mach-o.h delete mode 100644 zsign/macho.cpp delete mode 100644 zsign/macho.h delete mode 100644 zsign/openssl.cpp delete mode 100644 zsign/openssl.h delete mode 100644 zsign/signing.cpp delete mode 100644 zsign/signing.h delete mode 100644 zsign/zsign.hpp delete mode 100644 zsign/zsign.mm delete mode 100644 zsign/zsigner.h delete mode 100644 zsign/zsigner.m diff --git a/LiveContainerUI/LCUtils.m b/LiveContainerUI/LCUtils.m index 7aa76f6..00a75d2 100644 --- a/LiveContainerUI/LCUtils.m +++ b/LiveContainerUI/LCUtils.m @@ -5,7 +5,7 @@ #import "AltStoreCore/ALTSigner.h" #import "LCUtils.h" #import "LCVersionInfo.h" -#import "../zsign/zsigner.h" +#import "../ZSign/zsigner.h" @implementation LCUtils @@ -70,21 +70,11 @@ + (NSData *)certificateDataProperty { + (NSData *)certificateData { // Prefer certificate file over keychain data - return self.certificateDataFile ?: self.certificateDataProperty; + return [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificateData"]; } + (NSString *)certificatePassword { - if (self.certificateDataFile) { - NSString* ans = [[[NSUserDefaults alloc] initWithSuiteName:[self appGroupID]] objectForKey:@"LCCertificatePassword"]; - if(ans) { - return ans; - } - return [NSUserDefaults.standardUserDefaults objectForKey:@"LCCertificatePassword"]; - } else if (self.certificateDataProperty) { - return @""; - } else { - return nil; - } + return @""; } + (void)setCertificatePassword:(NSString *)certPassword { diff --git a/Resources/Info.plist b/Resources/Info.plist index 167d95c..a7e9d2c 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -259,5 +259,10 @@ UISupportsDocumentBrowser + UIBackgroundModes + + remote-notification + audio + diff --git a/zsign/Makefile b/zsign/Makefile deleted file mode 100644 index 605f331..0000000 --- a/zsign/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -TARGET := iphone:clang:latest:7.0 -ARCHS := arm64 -include $(THEOS)/makefiles/common.mk - -LIBRARY_NAME = ZSign - -ZSign_FILES = $(shell find . -name '*.cpp') $(shell find . -name '*.mm') zsigner.m -ZSign_CFLAGS = -fobjc-arc -Wno-deprecated -Wno-unused-variable -Wno-unused-but-set-variable -Wno-module-import-in-extern-c -ZSign_CCFLAGS = -std=c++11 -ZSign_FRAMEWORKS = OpenSSL -ZSign_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks - -include $(THEOS_MAKE_PATH)/library.mk - diff --git a/zsign/Utils.hpp b/zsign/Utils.hpp deleted file mode 100644 index cdd257e..0000000 --- a/zsign/Utils.hpp +++ /dev/null @@ -1,26 +0,0 @@ -// -// Utils.hpp -// feather -// -// Created by samara on 30.09.2024. -// - -#ifndef Utils_hpp -#define Utils_hpp - -#include - - -#ifdef __cplusplus -extern "C" { -#endif - -const char* getDocumentsDirectory(); -void writeToNSLog(const char* msg); - - -#ifdef __cplusplus -} -#endif - -#endif /* zsign_hpp */ diff --git a/zsign/Utils.mm b/zsign/Utils.mm deleted file mode 100644 index c657835..0000000 --- a/zsign/Utils.mm +++ /dev/null @@ -1,24 +0,0 @@ -// -// Utils.cpp -// feather -// -// Created by samara on 30.09.2024. -// - -#include "Utils.hpp" -#import - -extern "C" { - -const char* getDocumentsDirectory() { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - NSString *documentsDirectory = [paths firstObject]; - const char *documentsPath = [documentsDirectory UTF8String]; - return documentsPath; -} - -void writeToNSLog(const char* msg) { - NSLog(@"[LC] singner msg: %s", msg); -} - -} diff --git a/zsign/archo.cpp b/zsign/archo.cpp deleted file mode 100644 index f3de7d4..0000000 --- a/zsign/archo.cpp +++ /dev/null @@ -1,860 +0,0 @@ -#include "common/common.h" -#include "common/json.h" -#include "archo.h" -#include "signing.h" - -static uint64_t execSegLimit = 0; - -ZArchO::ZArchO() -{ - m_pBase = NULL; - m_uLength = 0; - m_uCodeLength = 0; - m_pSignBase = NULL; - m_uSignLength = 0; - m_pHeader = NULL; - m_uHeaderSize = 0; - m_bEncrypted = false; - m_b64 = false; - m_bBigEndian = false; - m_bEnoughSpace = true; - m_pCodeSignSegment = NULL; - m_pLinkEditSegment = NULL; - m_uLoadCommandsFreeSpace = 0; -} - -bool ZArchO::Init(uint8_t *pBase, uint32_t uLength) -{ - if (NULL == pBase || uLength <= 0) - { - return false; - } - - m_pBase = pBase; - m_uLength = uLength; - m_uCodeLength = (uLength % 16 == 0) ? uLength : uLength + 16 - (uLength % 16); - m_pHeader = (mach_header *)m_pBase; - if (MH_MAGIC != m_pHeader->magic && MH_CIGAM != m_pHeader->magic && MH_MAGIC_64 != m_pHeader->magic && MH_CIGAM_64 != m_pHeader->magic) - { - return false; - } - - m_b64 = (MH_MAGIC_64 == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false; - m_bBigEndian = (MH_CIGAM == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false; - m_uHeaderSize = m_b64 ? sizeof(mach_header_64) : sizeof(mach_header); - - uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) - { - load_command *plc = (load_command *)pLoadCommand; - switch (BO(plc->cmd)) - { - case LC_SEGMENT: - { - segment_command *seglc = (segment_command *)pLoadCommand; - if (0 == strcmp("__TEXT", seglc->segname)) - { - execSegLimit = seglc->vmsize; - for (uint32_t j = 0; j < BO(seglc->nsects); j++) - { - section *sect = (section *)((pLoadCommand + sizeof(segment_command)) + sizeof(section) * j); - if (0 == strcmp("__text", sect->sectname)) - { - if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize)) - { - m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize; - } - } - else if (0 == strcmp("__info_plist", sect->sectname)) - { - m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO(sect->size)); - } - } - } - else if (0 == strcmp("__LINKEDIT", seglc->segname)) - { - m_pLinkEditSegment = pLoadCommand; - } - } - break; - case LC_SEGMENT_64: - { - segment_command_64 *seglc = (segment_command_64 *)pLoadCommand; - if (0 == strcmp("__TEXT", seglc->segname)) - { - execSegLimit = seglc->vmsize; - for (uint32_t j = 0; j < BO(seglc->nsects); j++) - { - section_64 *sect = (section_64 *)((pLoadCommand + sizeof(segment_command_64)) + sizeof(section_64) * j); - if (0 == strcmp("__text", sect->sectname)) - { - if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize)) - { - m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize; - } - } - else if (0 == strcmp("__info_plist", sect->sectname)) - { - m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO((uint32_t)sect->size)); - } - } - } - else if (0 == strcmp("__LINKEDIT", seglc->segname)) - { - m_pLinkEditSegment = pLoadCommand; - } - } - break; - case LC_ENCRYPTION_INFO: - case LC_ENCRYPTION_INFO_64: - { - encryption_info_command *crypt_cmd = (encryption_info_command *)pLoadCommand; - if (BO(crypt_cmd->cryptid) >= 1) - { - m_bEncrypted = true; - } - } - break; - case LC_CODE_SIGNATURE: - { - codesignature_command *pcslc = (codesignature_command *)pLoadCommand; - m_pCodeSignSegment = pLoadCommand; - m_uCodeLength = BO(pcslc->dataoff); - m_pSignBase = m_pBase + m_uCodeLength; - m_uSignLength = GetCodeSignatureLength(m_pSignBase); - } - break; - } - - pLoadCommand += BO(plc->cmdsize); - } - - return true; -} - -const char *ZArchO::GetArch(int cpuType, int cpuSubType) -{ - switch (cpuType) - { - case CPU_TYPE_ARM: - { - switch (cpuSubType) - { - case CPU_SUBTYPE_ARM_V6: - return "armv6"; - break; - case CPU_SUBTYPE_ARM_V7: - return "armv7"; - break; - case CPU_SUBTYPE_ARM_V7S: - return "armv7s"; - break; - case CPU_SUBTYPE_ARM_V7K: - return "armv7k"; - break; - case CPU_SUBTYPE_ARM_V8: - return "armv8"; - break; - } - } - break; - case CPU_TYPE_ARM64: - { - switch (cpuSubType) - { - case CPU_SUBTYPE_ARM64_ALL: - return "arm64"; - break; - case CPU_SUBTYPE_ARM64_V8: - return "arm64v8"; - break; - case 2: - return "arm64e"; - break; - } - } - break; - case CPU_TYPE_ARM64_32: - { - switch (cpuSubType) - { - case CPU_SUBTYPE_ARM64_ALL: - return "arm64_32"; - break; - case CPU_SUBTYPE_ARM64_32_V8: - return "arm64e_32"; - break; - } - } - break; - case CPU_TYPE_X86: - { - switch (cpuSubType) - { - default: - return "x86_32"; - break; - } - } - break; - case CPU_TYPE_X86_64: - { - switch (cpuSubType) - { - default: - return "x86_64"; - break; - } - } - break; - } - return "unknown"; -} - -const char *ZArchO::GetFileType(uint32_t uFileType) -{ - switch (uFileType) - { - case MH_OBJECT: - return "MH_OBJECT"; - break; - case MH_EXECUTE: - return "MH_EXECUTE"; - break; - case MH_FVMLIB: - return "MH_FVMLIB"; - break; - case MH_CORE: - return "MH_CORE"; - break; - case MH_PRELOAD: - return "MH_PRELOAD"; - break; - case MH_DYLIB: - return "MH_DYLIB"; - break; - case MH_DYLINKER: - return "MH_DYLINKER"; - break; - case MH_BUNDLE: - return "MH_BUNDLE"; - break; - case MH_DYLIB_STUB: - return "MH_DYLIB_STUB"; - break; - case MH_DSYM: - return "MH_DSYM"; - break; - case MH_KEXT_BUNDLE: - return "MH_KEXT_BUNDLE"; - break; - } - return "MH_UNKNOWN"; -} - -uint32_t ZArchO::BO(uint32_t uValue) -{ - return m_bBigEndian ? LE(uValue) : uValue; -} - -bool ZArchO::IsExecute() -{ - if (NULL != m_pHeader) - { - return (MH_EXECUTE == BO(m_pHeader->filetype)); - } - return false; -} - -void ZArchO::PrintInfo() -{ - if (NULL == m_pHeader) - { - return; - } - - ZLog::Print("------------------------------------------------------------------\n"); - ZLog::Print(">>> MachO Info: \n"); - ZLog::PrintV("\tFileType: \t%s\n", GetFileType(BO(m_pHeader->filetype))); - ZLog::PrintV("\tTotalSize: \t%u (%s)\n", m_uLength, FormatSize(m_uLength).c_str()); - ZLog::PrintV("\tPlatform: \t%u\n", m_b64 ? 64 : 32); - ZLog::PrintV("\tCPUArch: \t%s\n", GetArch(BO(m_pHeader->cputype), BO(m_pHeader->cpusubtype))); - ZLog::PrintV("\tCPUType: \t0x%x\n", BO(m_pHeader->cputype)); - ZLog::PrintV("\tCPUSubType: \t0x%x\n", BO(m_pHeader->cpusubtype)); - ZLog::PrintV("\tBigEndian: \t%d\n", m_bBigEndian); - ZLog::PrintV("\tEncrypted: \t%d\n", m_bEncrypted); - ZLog::PrintV("\tCommandCount: \t%d\n", BO(m_pHeader->ncmds)); - ZLog::PrintV("\tCodeLength: \t%d (%s)\n", m_uCodeLength, FormatSize(m_uCodeLength).c_str()); - ZLog::PrintV("\tSignLength: \t%d (%s)\n", m_uSignLength, FormatSize(m_uSignLength).c_str()); - ZLog::PrintV("\tSpareLength: \t%d (%s)\n", m_uLength - m_uCodeLength - m_uSignLength, FormatSize(m_uLength - m_uCodeLength - m_uSignLength).c_str()); - - uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) - { - load_command *plc = (load_command *)pLoadCommand; - if (LC_VERSION_MIN_IPHONEOS == BO(plc->cmd)) - { - ZLog::PrintV("\tMIN_IPHONEOS: \t0x%x\n", *((uint32_t *)(pLoadCommand + sizeof(load_command)))); - } - else if (LC_RPATH == BO(plc->cmd)) - { - ZLog::PrintV("\tLC_RPATH: \t%s\n", (char *)(pLoadCommand + sizeof(load_command) + 4)); - } - pLoadCommand += BO(plc->cmdsize); - } - - bool bHasWeakDylib = false; - ZLog::PrintV("\tLC_LOAD_DYLIB: \n"); - pLoadCommand = m_pBase + m_uHeaderSize; - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) - { - load_command *plc = (load_command *)pLoadCommand; - if (LC_LOAD_DYLIB == BO(plc->cmd)) - { - dylib_command *dlc = (dylib_command *)pLoadCommand; - const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); - ZLog::PrintV("\t\t\t%s\n", szDyLib); - } - else if (LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) - { - bHasWeakDylib = true; - } - pLoadCommand += BO(plc->cmdsize); - } - - if (bHasWeakDylib) - { - ZLog::PrintV("\tLC_LOAD_WEAK_DYLIB: \n"); - pLoadCommand = m_pBase + m_uHeaderSize; - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) - { - load_command *plc = (load_command *)pLoadCommand; - if (LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) - { - dylib_command *dlc = (dylib_command *)pLoadCommand; - const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); - ZLog::PrintV("\t\t\t%s (weak)\n", szDyLib); - } - pLoadCommand += BO(plc->cmdsize); - } - } - - if (!m_strInfoPlist.empty()) - { - ZLog::Print("\n>>> Embedded Info.plist: \n"); - ZLog::PrintV("\tlength: \t%lu\n", m_strInfoPlist.size()); - - string strInfoPlist = m_strInfoPlist; - PWriter::StringReplace(strInfoPlist, "\n", "\n\t\t\t"); - ZLog::PrintV("\tcontent: \t%s\n", strInfoPlist.c_str()); - - PrintDataSHASum("\tSHA-1: \t", E_SHASUM_TYPE_1, m_strInfoPlist); - PrintDataSHASum("\tSHA-256:\t", E_SHASUM_TYPE_256, m_strInfoPlist); - } - - if (NULL == m_pSignBase || m_uSignLength <= 0) - { - ZLog::Warn(">>> Can't Find CodeSignature Segment!\n"); - } - else - { - ParseCodeSignature(m_pSignBase); - } - - ZLog::Print("------------------------------------------------------------------\n"); -} - -bool ZArchO::BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput) -{ - string strRequirementsSlot; - string strEntitlementsSlot; - string strDerEntitlementsSlot; - - // modified, we don't need Entitlement in LiveContainer. -// string strEmptyEntitlements = "\n\n\n\n\n"; - string strEmptyEntitlements = ""; - SlotBuildRequirements(strBundleId, pSignAsset->m_strSubjectCN, strRequirementsSlot); - SlotBuildEntitlements(IsExecute() ? pSignAsset->m_strEntitlementsData : strEmptyEntitlements, strEntitlementsSlot); - SlotBuildDerEntitlements(IsExecute() ? pSignAsset->m_strEntitlementsData : "", strDerEntitlementsSlot); - - string strRequirementsSlotSHA1; - string strRequirementsSlotSHA256; - if (strRequirementsSlot.empty()) - { //empty - strRequirementsSlotSHA1.append(20, 0); - strRequirementsSlotSHA256.append(32, 0); - } - else - { - SHASum(strRequirementsSlot, strRequirementsSlotSHA1, strRequirementsSlotSHA256); - } - - string strEntitlementsSlotSHA1; - string strEntitlementsSlotSHA256; - if (strEntitlementsSlot.empty()) - { //empty - strEntitlementsSlotSHA1.append(20, 0); - strEntitlementsSlotSHA256.append(32, 0); - } - else - { - SHASum(strEntitlementsSlot, strEntitlementsSlotSHA1, strEntitlementsSlotSHA256); - } - - string strDerEntitlementsSlotSHA1; - string strDerEntitlementsSlotSHA256; - if (strDerEntitlementsSlot.empty()) - { //empty - strDerEntitlementsSlotSHA1.append(20, 0); - strDerEntitlementsSlotSHA256.append(32, 0); - } - else - { - SHASum(strDerEntitlementsSlot, strDerEntitlementsSlotSHA1, strDerEntitlementsSlotSHA256); - } - - uint8_t *pCodeSlots1Data = NULL; - uint8_t *pCodeSlots256Data = NULL; - uint32_t uCodeSlots1DataLength = 0; - uint32_t uCodeSlots256DataLength = 0; - if (!bForce) - { - GetCodeSignatureExistsCodeSlotsData(m_pSignBase, pCodeSlots1Data, uCodeSlots1DataLength, pCodeSlots256Data, uCodeSlots256DataLength); - } - - uint64_t execSegFlags = 0; - if (NULL != strstr(strEntitlementsSlot.data() + 8, "get-task-allow")) - { - // TODO: Check if get-task-allow is actually set to true - execSegFlags = CS_EXECSEG_MAIN_BINARY | CS_EXECSEG_ALLOW_UNSIGNED; - } - - string strCMSSignatureSlot; - string strCodeDirectorySlot; - string strAltnateCodeDirectorySlot; - SlotBuildCodeDirectory(false, - m_pBase, - m_uCodeLength, - pCodeSlots1Data, - uCodeSlots1DataLength, - execSegLimit, - execSegFlags, - strBundleId, - pSignAsset->m_strTeamId, - strInfoPlistSHA1, - strRequirementsSlotSHA1, - strCodeResourcesSHA1, - strEntitlementsSlotSHA1, - strDerEntitlementsSlotSHA1, - IsExecute(), - strCodeDirectorySlot); - SlotBuildCodeDirectory(true, - m_pBase, - m_uCodeLength, - pCodeSlots256Data, - uCodeSlots256DataLength, - execSegLimit, - execSegFlags, - strBundleId, - pSignAsset->m_strTeamId, - strInfoPlistSHA256, - strRequirementsSlotSHA256, - strCodeResourcesSHA256, - strEntitlementsSlotSHA256, - strDerEntitlementsSlotSHA256, - IsExecute(), - strAltnateCodeDirectorySlot); - SlotBuildCMSSignature(pSignAsset, - strCodeDirectorySlot, - strAltnateCodeDirectorySlot, - strCMSSignatureSlot); - - uint32_t uCodeDirectorySlotLength = (uint32_t)strCodeDirectorySlot.size(); - uint32_t uRequirementsSlotLength = (uint32_t)strRequirementsSlot.size(); - uint32_t uEntitlementsSlotLength = (uint32_t)strEntitlementsSlot.size(); - uint32_t uDerEntitlementsLength = (uint32_t)strDerEntitlementsSlot.size(); - uint32_t uAltnateCodeDirectorySlotLength = (uint32_t)strAltnateCodeDirectorySlot.size(); - uint32_t uCMSSignatureSlotLength = (uint32_t)strCMSSignatureSlot.size(); - - uint32_t uCodeSignBlobCount = 0; - uCodeSignBlobCount += (uCodeDirectorySlotLength > 0) ? 1 : 0; - uCodeSignBlobCount += (uRequirementsSlotLength > 0) ? 1 : 0; - uCodeSignBlobCount += (uEntitlementsSlotLength > 0) ? 1 : 0; - uCodeSignBlobCount += (uDerEntitlementsLength > 0) ? 1 : 0; - uCodeSignBlobCount += (uAltnateCodeDirectorySlotLength > 0) ? 1 : 0; - uCodeSignBlobCount += (uCMSSignatureSlotLength > 0) ? 1 : 0; - - uint32_t uSuperBlobHeaderLength = sizeof(CS_SuperBlob) + uCodeSignBlobCount * sizeof(CS_BlobIndex); - uint32_t uCodeSignLength = uSuperBlobHeaderLength + - uCodeDirectorySlotLength + - uRequirementsSlotLength + - uEntitlementsSlotLength + - uDerEntitlementsLength + - uAltnateCodeDirectorySlotLength + - uCMSSignatureSlotLength; - - vector arrBlobIndexes; - if (uCodeDirectorySlotLength > 0) - { - CS_BlobIndex blob; - blob.type = BE(CSSLOT_CODEDIRECTORY); - blob.offset = BE(uSuperBlobHeaderLength); - arrBlobIndexes.push_back(blob); - } - if (uRequirementsSlotLength > 0) - { - CS_BlobIndex blob; - blob.type = BE(CSSLOT_REQUIREMENTS); - blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength); - arrBlobIndexes.push_back(blob); - } - if (uEntitlementsSlotLength > 0) - { - CS_BlobIndex blob; - blob.type = BE(CSSLOT_ENTITLEMENTS); - blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength); - arrBlobIndexes.push_back(blob); - } - if (uDerEntitlementsLength > 0) - { - CS_BlobIndex blob; - blob.type = BE(CSSLOT_DER_ENTITLEMENTS); - blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength); - arrBlobIndexes.push_back(blob); - } - if (uAltnateCodeDirectorySlotLength > 0) - { - CS_BlobIndex blob; - blob.type = BE(CSSLOT_ALTERNATE_CODEDIRECTORIES); - blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength + uDerEntitlementsLength); - arrBlobIndexes.push_back(blob); - } - if (uCMSSignatureSlotLength > 0) - { - CS_BlobIndex blob; - blob.type = BE(CSSLOT_SIGNATURESLOT); - blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength + uDerEntitlementsLength + uAltnateCodeDirectorySlotLength); - arrBlobIndexes.push_back(blob); - } - - CS_SuperBlob superblob; - superblob.magic = BE(CSMAGIC_EMBEDDED_SIGNATURE); - superblob.length = BE(uCodeSignLength); - superblob.count = BE(uCodeSignBlobCount); - - strOutput.clear(); - strOutput.reserve(uCodeSignLength); - strOutput.append((const char *)&superblob, sizeof(superblob)); - for (size_t i = 0; i < arrBlobIndexes.size(); i++) - { - CS_BlobIndex &blob = arrBlobIndexes[i]; - strOutput.append((const char *)&blob, sizeof(blob)); - } - strOutput += strCodeDirectorySlot; - strOutput += strRequirementsSlot; - strOutput += strEntitlementsSlot; - strOutput += strDerEntitlementsSlot; - strOutput += strAltnateCodeDirectorySlot; - strOutput += strCMSSignatureSlot; - - if (ZLog::IsDebug()) - { - WriteFile("./.zsign_debug/Requirements.slot.new", strRequirementsSlot); - WriteFile("./.zsign_debug/Entitlements.slot.new", strEntitlementsSlot); - WriteFile("./.zsign_debug/Entitlements.der.slot.new", strDerEntitlementsSlot); - WriteFile("./.zsign_debug/Entitlements.plist.new", strEntitlementsSlot.data() + 8, strEntitlementsSlot.size() - 8); - WriteFile("./.zsign_debug/CodeDirectory_SHA1.slot.new", strCodeDirectorySlot); - WriteFile("./.zsign_debug/CodeDirectory_SHA256.slot.new", strAltnateCodeDirectorySlot); - WriteFile("./.zsign_debug/CMSSignature.slot.new", strCMSSignatureSlot); - WriteFile("./.zsign_debug/CMSSignature.der.new", strCMSSignatureSlot.data() + 8, strCMSSignatureSlot.size() - 8); - WriteFile("./.zsign_debug/CodeSignature.blob.new", strOutput); - } - - return true; -} - -bool ZArchO::Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData) -{ - if (NULL == m_pSignBase) - { - m_bEnoughSpace = false; - ZLog::Warn(">>> Can't Find CodeSignature Segment!\n"); - return false; - } - - string strCodeResourcesSHA1; - string strCodeResourcesSHA256; - if (strCodeResourcesData.empty()) - { - strCodeResourcesSHA1.append(20, 0); - strCodeResourcesSHA256.append(32, 0); - } - else - { - SHASum(strCodeResourcesData, strCodeResourcesSHA1, strCodeResourcesSHA256); - } - - string strCodeSignBlob; - BuildCodeSignature(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesSHA1, strCodeResourcesSHA256, strCodeSignBlob); - if (strCodeSignBlob.empty()) - { - ZLog::Error(">>> Build CodeSignature Failed!\n"); - return false; - } - - int nSpaceLength = (int)m_uLength - (int)m_uCodeLength - (int)strCodeSignBlob.size(); - if (nSpaceLength < 0) - { - m_bEnoughSpace = false; - ZLog::WarnV(">>> No Enough CodeSignature Space. Length => Now: %d, Need: %d\n", (int)m_uLength - (int)m_uCodeLength, (int)strCodeSignBlob.size()); - return false; - } - - memcpy(m_pBase + m_uCodeLength, strCodeSignBlob.data(), strCodeSignBlob.size()); - //memset(m_pBase + m_uCodeLength + strCodeSignBlob.size(), 0, nSpaceLength); - return true; -} - -uint32_t ZArchO::ReallocCodeSignSpace(const string &strNewFile) -{ - RemoveFile(strNewFile.c_str()); - - uint32_t uNewLength = m_uCodeLength + ByteAlign(((m_uCodeLength / 4096) + 1) * (20 + 32), 4096) + 16384; //16K May Be Enough - if (NULL == m_pLinkEditSegment || uNewLength <= m_uLength) - { - return 0; - } - - load_command *pseglc = (load_command *)m_pLinkEditSegment; - switch (BO(pseglc->cmd)) - { - case LC_SEGMENT: - { - segment_command *seglc = (segment_command *)m_pLinkEditSegment; - seglc->vmsize = ByteAlign(BO(seglc->vmsize) + (uNewLength - m_uLength), 4096); - seglc->vmsize = BO(seglc->vmsize); - seglc->filesize = uNewLength - BO(seglc->fileoff); - seglc->filesize = BO(seglc->filesize); - } - break; - case LC_SEGMENT_64: - { - segment_command_64 *seglc = (segment_command_64 *)m_pLinkEditSegment; - seglc->vmsize = ByteAlign(BO((uint32_t)seglc->vmsize) + (uNewLength - m_uLength), 4096); - seglc->vmsize = BO((uint32_t)seglc->vmsize); - seglc->filesize = uNewLength - BO((uint32_t)seglc->fileoff); - seglc->filesize = BO((uint32_t)seglc->filesize); - } - break; - } - - codesignature_command *pcslc = (codesignature_command *)m_pCodeSignSegment; - if (NULL == pcslc) - { - if (m_uLoadCommandsFreeSpace < 4) - { - ZLog::Error(">>> Can't Find Free Space Of LoadCommands For CodeSignature!\n"); - return 0; - } - - pcslc = (codesignature_command *)(m_pBase + m_uHeaderSize + BO(m_pHeader->sizeofcmds)); - pcslc->cmd = BO(LC_CODE_SIGNATURE); - pcslc->cmdsize = BO((uint32_t)sizeof(codesignature_command)); - pcslc->dataoff = BO(m_uCodeLength); - m_pHeader->ncmds = BO(BO(m_pHeader->ncmds) + 1); - m_pHeader->sizeofcmds = BO(BO(m_pHeader->sizeofcmds) + sizeof(codesignature_command)); - } - pcslc->datasize = BO(uNewLength - m_uCodeLength); - - if (!AppendFile(strNewFile.c_str(), (const char *)m_pBase, m_uLength)) - { - return 0; - } - - string strPadding; - strPadding.append(uNewLength - m_uLength, 0); - if (!AppendFile(strNewFile.c_str(), strPadding)) - { - RemoveFile(strNewFile.c_str()); - return 0; - } - - return uNewLength; -} - -bool ZArchO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate) -{ - if (NULL == m_pHeader) - { - return false; - } - - uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) - { - load_command *plc = (load_command *)pLoadCommand; - uint32_t uLoadType = BO(plc->cmd); - if (LC_LOAD_DYLIB == uLoadType || LC_LOAD_WEAK_DYLIB == uLoadType) - { - dylib_command *dlc = (dylib_command *)pLoadCommand; - const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); - if (0 == strcmp(szDyLib, szDyLibPath)) - { - if ((bWeakInject && (LC_LOAD_WEAK_DYLIB != uLoadType)) || (!bWeakInject && (LC_LOAD_DYLIB != uLoadType))) - { - dlc->cmd = BO((uint32_t)(bWeakInject ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB)); - ZLog::WarnV(">>> DyLib Load Type Changed! %s -> %s\n", (LC_LOAD_DYLIB == uLoadType) ? "LC_LOAD_DYLIB" : "LC_LOAD_WEAK_DYLIB", bWeakInject ? "LC_LOAD_WEAK_DYLIB" : "LC_LOAD_DYLIB"); - } - else - { - ZLog::WarnV(">>> DyLib Is Already Existed! %s\n"); - } - return true; - } - } - pLoadCommand += BO(plc->cmdsize); - } - - uint32_t uDylibPathLength = (uint32_t)strlen(szDyLibPath); - uint32_t uDylibPathPadding = (8 - uDylibPathLength % 8); - uint32_t uDyLibCommandSize = sizeof(dylib_command) + uDylibPathLength + uDylibPathPadding; - if (m_uLoadCommandsFreeSpace > 0 && m_uLoadCommandsFreeSpace < uDyLibCommandSize) // some bin doesn't have '__text' - { - ZLog::Error(">>> Can't Find Free Space Of LoadCommands For LC_LOAD_DYLIB Or LC_LOAD_WEAK_DYLIB!\n"); - return false; - } - - //add - dylib_command *dlc = (dylib_command *)(m_pBase + m_uHeaderSize + BO(m_pHeader->sizeofcmds)); - dlc->cmd = BO((uint32_t)(bWeakInject ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB)); - dlc->cmdsize = BO(uDyLibCommandSize); - dlc->dylib.name.offset = BO((uint32_t)sizeof(dylib_command)); - dlc->dylib.timestamp = BO((uint32_t)2); - dlc->dylib.current_version = 0; - dlc->dylib.compatibility_version = 0; - - string strDylibPath = szDyLibPath; - strDylibPath.append(uDylibPathPadding, 0); - - uint8_t *pDyLibPath = (uint8_t *)dlc + sizeof(dylib_command); - memcpy(pDyLibPath, strDylibPath.data(), uDylibPathLength + uDylibPathPadding); - - m_pHeader->ncmds = BO(BO(m_pHeader->ncmds) + 1); - m_pHeader->sizeofcmds = BO(BO(m_pHeader->sizeofcmds) + uDyLibCommandSize); - - bCreate = true; - return true; -} - -bool ZArchO::ChangeDylibPath(const char *oldPath, const char *newPath) { - if (NULL == m_pHeader) { - return false; - } - - uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; - bool pathChanged = false; - uint32_t oldPathLength = (uint32_t)strlen(oldPath); - uint32_t newPathLength = (uint32_t)strlen(newPath); - uint32_t oldPathPadding = (8 - oldPathLength % 8) % 8; - uint32_t newPathPadding = (8 - newPathLength % 8) % 8; - uint32_t newLoadCommandSize = 0; - - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) { - load_command *plc = (load_command *)pLoadCommand; - uint32_t uLoadType = BO(plc->cmd); - - if (LC_LOAD_DYLIB == uLoadType || LC_LOAD_WEAK_DYLIB == uLoadType) { - dylib_command *dlc = (dylib_command *)pLoadCommand; - const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); - - if (strcmp(szDyLib, oldPath) == 0) { - uint32_t dylibPathOffset = sizeof(dylib_command); - uint32_t dylibPathSize = newPathLength + newPathPadding; - if (dylibPathOffset + dylibPathSize > BO(plc->cmdsize)) { - ZLog::Error(">>> Insufficient space to update dylib path!\n"); - return false; - } - - memcpy(pLoadCommand + dylibPathOffset, newPath, newPathLength); - memset(pLoadCommand + dylibPathOffset + newPathLength, 0, newPathPadding); - - ZLog::PrintV(">>> Dylib Path Changed: %s -> %s\n", oldPath, newPath); - - pathChanged = true; - } - } - - pLoadCommand += BO(plc->cmdsize); - } - - if (!pathChanged) { - ZLog::PrintV(">>> Old Dylib Path Not Found: %s\n", oldPath); - } - - return pathChanged; -} - - - - - - -void ZArchO::uninstallDylibs(set dylibNames) -{ - uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; - uint32_t old_load_command_size = m_pHeader->sizeofcmds; - uint8_t *new_load_command_data = (uint8_t*)malloc(old_load_command_size); - memset(new_load_command_data,0,old_load_command_size); - uint32_t new_load_command_size = 0; - uint32_t clear_num = 0; - uint32_t clear_data_size = 0; - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) - { - load_command *plc = (load_command *)pLoadCommand; - uint32_t load_command_size = BO(plc->cmdsize); - if (LC_LOAD_DYLIB == BO(plc->cmd) || LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) - { - dylib_command *dlc = (dylib_command *)pLoadCommand; - const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); - string dylibName = szDyLib; - if(dylibNames.count(dylibName)>0){ - ZLog::PrintV("\t\t\t%s\tclear\n", szDyLib); - clear_num++; - clear_data_size+=load_command_size; - pLoadCommand += BO(plc->cmdsize); - continue; - } - ZLog::PrintV("\t\t\t%s\n", szDyLib); - } - new_load_command_size+=load_command_size; - memcpy(new_load_command_data,pLoadCommand,load_command_size); - new_load_command_data += load_command_size; - pLoadCommand += BO(plc->cmdsize); - } - pLoadCommand -= m_pHeader->sizeofcmds; - - m_pHeader->ncmds -= clear_num; - m_pHeader->sizeofcmds -= clear_data_size; - new_load_command_data -=new_load_command_size; - memset(pLoadCommand,0,old_load_command_size); - memcpy(pLoadCommand,new_load_command_data,new_load_command_size); - free(new_load_command_data); -} - -std::vector ZArchO::ListDylibs() { - std::vector dylibList; - uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; - - for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) { - load_command *plc = (load_command *)pLoadCommand; - if (LC_LOAD_DYLIB == BO(plc->cmd) || LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) { - dylib_command *dlc = (dylib_command *)pLoadCommand; - const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); - dylibList.push_back(std::string(szDyLib)); - } - pLoadCommand += BO(plc->cmdsize); - } - - return dylibList; -} - diff --git a/zsign/archo.h b/zsign/archo.h deleted file mode 100644 index b9dc27d..0000000 --- a/zsign/archo.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "common/mach-o.h" -#include "openssl.h" -#include -class ZArchO -{ -public: - ZArchO(); - bool Init(uint8_t *pBase, uint32_t uLength); - -public: - bool Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData); - void PrintInfo(); - bool IsExecute(); - bool InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate); - uint32_t ReallocCodeSignSpace(const string &strNewFile); - void uninstallDylibs(set dylibNames); - bool ChangeDylibPath(const char *oldPath, const char *newPath); - std::vector ListDylibs(); -private: - uint32_t BO(uint32_t uVal); - const char *GetFileType(uint32_t uFileType); - const char *GetArch(int cpuType, int cpuSubType); - bool BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput); - -public: - uint8_t *m_pBase; - uint32_t m_uLength; - uint32_t m_uCodeLength; - uint8_t *m_pSignBase; - uint32_t m_uSignLength; - string m_strInfoPlist; - bool m_bEncrypted; - bool m_b64; - bool m_bBigEndian; - bool m_bEnoughSpace; - uint8_t *m_pCodeSignSegment; - uint8_t *m_pLinkEditSegment; - uint32_t m_uLoadCommandsFreeSpace; - mach_header *m_pHeader; - uint32_t m_uHeaderSize; -}; diff --git a/zsign/bundle.cpp b/zsign/bundle.cpp deleted file mode 100644 index cc2a45f..0000000 --- a/zsign/bundle.cpp +++ /dev/null @@ -1,710 +0,0 @@ -#include "bundle.h" -#include "macho.h" -#include "sys/stat.h" -#include "sys/types.h" -#include "common/base64.h" -#include "common/common.h" - -ZAppBundle::ZAppBundle() -{ - m_pSignAsset = NULL; - m_bForceSign = false; - m_bWeakInject = false; -} - -bool ZAppBundle::FindAppFolder(const string &strFolder, string &strAppFolder) -{ - if (IsPathSuffix(strFolder, ".app") || IsPathSuffix(strFolder, ".appex")) - { - strAppFolder = strFolder; - return true; - } - - DIR *dir = opendir(strFolder.c_str()); - if (NULL != dir) - { - dirent *ptr = readdir(dir); - while (NULL != ptr) - { - if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..") && 0 != strcmp(ptr->d_name, "__MACOSX")) - { - bool isdir = false; - if (DT_DIR == ptr->d_type) - { - isdir = true; - } - else if (DT_UNKNOWN == ptr->d_type) - { - // Entry type can be unknown depending on the underlying file system - ZLog::DebugV(">>> Unknown directory entry type for %s, falling back to POSIX-compatible check\n", strFolder.c_str()); - struct stat statbuf; - stat(strFolder.c_str(), &statbuf); - if (S_ISDIR(statbuf.st_mode)) - { - isdir = true; - } - } - if (isdir) - { - string strSubFolder = strFolder; - strSubFolder += "/"; - strSubFolder += ptr->d_name; - if (FindAppFolder(strSubFolder, strAppFolder)) - { - return true; - } - } - } - ptr = readdir(dir); - } - closedir(dir); - } - return false; -} - -bool ZAppBundle::GetSignFolderInfo(const string &strFolder, JValue &jvNode, bool bGetName) -{ - JValue jvInfo; - string strInfoPlistData; - string strInfoPlistPath = strFolder + "/Info.plist"; - ReadFile(strInfoPlistPath.c_str(), strInfoPlistData); - jvInfo.readPList(strInfoPlistData); - string strBundleId = jvInfo["CFBundleIdentifier"]; - string strBundleExe = jvInfo["CFBundleExecutable"]; - string strBundleVersion = jvInfo["CFBundleVersion"]; - if (strBundleId.empty() || strBundleExe.empty()) - { - return false; - } - - string strInfoPlistSHA1Base64; - string strInfoPlistSHA256Base64; - SHASumBase64(strInfoPlistData, strInfoPlistSHA1Base64, strInfoPlistSHA256Base64); - - jvNode["bid"] = strBundleId; - jvNode["bver"] = strBundleVersion; - jvNode["exec"] = strBundleExe; - jvNode["sha1"] = strInfoPlistSHA1Base64; - jvNode["sha2"] = strInfoPlistSHA256Base64; - - if (bGetName) - { - string strBundleName = jvInfo["CFBundleDisplayName"]; - if (strBundleName.empty()) - { - strBundleName = jvInfo["CFBundleName"].asCString(); - } - jvNode["name"] = strBundleName; - } - - return true; -} - -bool ZAppBundle::GetObjectsToSign(const string &strFolder, JValue &jvInfo) -{ - DIR *dir = opendir(strFolder.c_str()); - if (NULL != dir) - { - dirent *ptr = readdir(dir); - while (NULL != ptr) - { - if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) - { - string strNode = strFolder + "/" + ptr->d_name; - if (DT_DIR == ptr->d_type) - { - if (IsPathSuffix(strNode, ".app") || IsPathSuffix(strNode, ".appex") || IsPathSuffix(strNode, ".framework") || IsPathSuffix(strNode, ".xctest")) - { - JValue jvNode; - jvNode["path"] = strNode.substr(m_strAppFolder.size() + 1); - if (GetSignFolderInfo(strNode, jvNode)) - { - if (GetObjectsToSign(strNode, jvNode)) - { - jvInfo["folders"].push_back(jvNode); - } - } - } - else - { - GetObjectsToSign(strNode, jvInfo); - } - } - else if (DT_REG == ptr->d_type) - { - if (IsPathSuffix(strNode, ".dylib")) - { - jvInfo["files"].push_back(strNode.substr(m_strAppFolder.size() + 1)); - } - } - } - ptr = readdir(dir); - } - closedir(dir); - } - return true; -} - -void ZAppBundle::GetFolderFiles(const string &strFolder, const string &strBaseFolder, set &setFiles) -{ - DIR *dir = opendir(strFolder.c_str()); - if (NULL != dir) - { - dirent *ptr = readdir(dir); - while (NULL != ptr) - { - if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) - { - string strNode = strFolder; - strNode += "/"; - strNode += ptr->d_name; - if (DT_DIR == ptr->d_type) - { - GetFolderFiles(strNode, strBaseFolder, setFiles); - } - else if (DT_REG == ptr->d_type) - { - setFiles.insert(strNode.substr(strBaseFolder.size() + 1)); - } - } - ptr = readdir(dir); - } - closedir(dir); - } -} - -bool ZAppBundle::GenerateCodeResources(const string &strFolder, JValue &jvCodeRes) -{ - jvCodeRes.clear(); - - set setFiles; - GetFolderFiles(strFolder, strFolder, setFiles); - - JValue jvInfo; - string strInfoPlistPath = strFolder + "/Info.plist"; - jvInfo.readPListFile(strInfoPlistPath.c_str()); - string strBundleExe = jvInfo["CFBundleExecutable"]; - setFiles.erase(strBundleExe); - setFiles.erase("_CodeSignature/CodeResources"); - - jvCodeRes["files"] = JValue(JValue::E_OBJECT); - jvCodeRes["files2"] = JValue(JValue::E_OBJECT); - - for (set::iterator it = setFiles.begin(); it != setFiles.end(); it++) - { - string strKey = *it; - string strFile = strFolder + "/" + strKey; - string strFileSHA1Base64; - string strFileSHA256Base64; - SHASumBase64File(strFile.c_str(), strFileSHA1Base64, strFileSHA256Base64); - - bool bomit1 = false; - bool bomit2 = false; - - if ("Info.plist" == strKey || "PkgInfo" == strKey) - { - bomit2 = true; - } - - if (IsPathSuffix(strKey, ".DS_Store")) - { - bomit2 = true; - } - - if (IsPathSuffix(strKey, ".lproj/locversion.plist")) - { - bomit1 = true; - bomit2 = true; - } - - if (!bomit1) - { - if (string::npos != strKey.rfind(".lproj/")) - { - jvCodeRes["files"][strKey]["hash"] = "data:" + strFileSHA1Base64; - jvCodeRes["files"][strKey]["optional"] = true; - } - else - { - jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; - } - } - - if (!bomit2) - { - jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; - jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; - if (string::npos != strKey.rfind(".lproj/")) - { - jvCodeRes["files2"][strKey]["optional"] = true; - } - } - } - - jvCodeRes["rules"]["^.*"] = true; - jvCodeRes["rules"]["^.*\\.lproj/"]["optional"] = true; - jvCodeRes["rules"]["^.*\\.lproj/"]["weight"] = 1000.0; - jvCodeRes["rules"]["^.*\\.lproj/locversion.plist$"]["omit"] = true; - jvCodeRes["rules"]["^.*\\.lproj/locversion.plist$"]["weight"] = 1100.0; - jvCodeRes["rules"]["^Base\\.lproj/"]["weight"] = 1010.0; - jvCodeRes["rules"]["^version.plist$"] = true; - - jvCodeRes["rules2"]["^.*"] = true; - jvCodeRes["rules2"][".*\\.dSYM($|/)"]["weight"] = 11.0; - jvCodeRes["rules2"]["^(.*/)?\\.DS_Store$"]["omit"] = true; - jvCodeRes["rules2"]["^(.*/)?\\.DS_Store$"]["weight"] = 2000.0; - jvCodeRes["rules2"]["^.*\\.lproj/"]["optional"] = true; - jvCodeRes["rules2"]["^.*\\.lproj/"]["weight"] = 1000.0; - jvCodeRes["rules2"]["^.*\\.lproj/locversion.plist$"]["omit"] = true; - jvCodeRes["rules2"]["^.*\\.lproj/locversion.plist$"]["weight"] = 1100.0; - jvCodeRes["rules2"]["^Base\\.lproj/"]["weight"] = 1010.0; - jvCodeRes["rules2"]["^Info\\.plist$"]["omit"] = true; - jvCodeRes["rules2"]["^Info\\.plist$"]["weight"] = 20.0; - jvCodeRes["rules2"]["^PkgInfo$"]["omit"] = true; - jvCodeRes["rules2"]["^PkgInfo$"]["weight"] = 20.0; - jvCodeRes["rules2"]["^embedded\\.provisionprofile$"]["weight"] = 20.0; - jvCodeRes["rules2"]["^version\\.plist$"]["weight"] = 20.0; - - return true; -} - -void ZAppBundle::GetChangedFiles(JValue &jvNode, vector &arrChangedFiles) -{ - if (jvNode.has("files")) - { - for (size_t i = 0; i < jvNode["files"].size(); i++) - { - arrChangedFiles.push_back(jvNode["files"][i]); - } - } - - if (jvNode.has("folders")) - { - for (size_t i = 0; i < jvNode["folders"].size(); i++) - { - JValue &jvSubNode = jvNode["folders"][i]; - GetChangedFiles(jvSubNode, arrChangedFiles); - string strPath = jvSubNode["path"]; - arrChangedFiles.push_back(strPath + "/_CodeSignature/CodeResources"); - arrChangedFiles.push_back(strPath + "/" + jvSubNode["exec"].asString()); - } - } -} - -void ZAppBundle::GetNodeChangedFiles(JValue &jvNode, bool dontGenerateEmbeddedMobileProvision) -{ - if (jvNode.has("folders")) - { - for (size_t i = 0; i < jvNode["folders"].size(); i++) - { - GetNodeChangedFiles(jvNode["folders"][i], dontGenerateEmbeddedMobileProvision); - } - } - - vector arrChangedFiles; - GetChangedFiles(jvNode, arrChangedFiles); - for (size_t i = 0; i < arrChangedFiles.size(); i++) - { - jvNode["changed"].push_back(arrChangedFiles[i]); - } -// TODO: try - if (dontGenerateEmbeddedMobileProvision) { - if ("/" == jvNode["path"]) - { //root - jvNode["changed"].push_back("embedded.mobileprovision"); - } - } -} - -int ZAppBundle::GetSignCount(JValue &jvNode) { - int ans = 1; - if (jvNode.has("files")) - { - ans += jvNode["files"].size(); - } - - if (jvNode.has("folders")) - { - for (size_t i = 0; i < jvNode["folders"].size(); i++) - { - ans += GetSignCount(jvNode["folders"][i]); - } - } - return ans; -} - -bool ZAppBundle::SignNode(JValue &jvNode) -{ - if (jvNode.has("folders")) - { - for (size_t i = 0; i < jvNode["folders"].size(); i++) - { - if (!SignNode(jvNode["folders"][i])) - { - return false; - } - } - } - - if (jvNode.has("files")) - { - for (size_t i = 0; i < jvNode["files"].size(); i++) - { - const char *szFile = jvNode["files"][i].asCString(); - ZLog::PrintV(">>> SignFile: \t%s\n", szFile); - ZMachO macho; - if (!macho.InitV("%s/%s", m_strAppFolder.c_str(), szFile)) - { - return false; - } - - if (!macho.Sign(m_pSignAsset, m_bForceSign, mainBundleIdentifier, "", "", "")) - { - return false; - } - if(progressHandler) { - progressHandler(); - } - } - } - - ZBase64 b64; - string strInfoPlistSHA1; - string strInfoPlistSHA256; - string strFolder = jvNode["path"]; - string strBundleId = jvNode["bid"]; - string strBundleExe = jvNode["exec"]; - b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); - b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); - if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) - { - ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); - return false; - } - - string strBaseFolder = m_strAppFolder; - if ("/" != strFolder) - { - strBaseFolder += "/"; - strBaseFolder += strFolder; - } - - string strExePath = strBaseFolder + "/" + strBundleExe; - ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); - - ZMachO macho; - if (!macho.Init(strExePath.c_str())) - { - ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); - return false; - } - - RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); - CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); - string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; - - JValue jvCodeRes; - if (!m_bForceSign) - { - jvCodeRes.readPListFile(strCodeResFile.c_str()); - } - - if (m_bForceSign || jvCodeRes.isNull()) - { //create - if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) - { - ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); - return false; - } - } - else if (jvNode.has("changed")) - { //use existsed - for (size_t i = 0; i < jvNode["changed"].size(); i++) - { - string strFile = jvNode["changed"][i].asCString(); - string strRealFile = m_strAppFolder + "/" + strFile; - - string strFileSHA1Base64; - string strFileSHA256Base64; - if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) - { - ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); - return false; - } - - string strKey = strFile; - if ("/" != strFolder) - { - strKey = strFile.substr(strFolder.size() + 1); - } - jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; - jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; - jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; - - ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); - } - } - - string strCodeResData; - jvCodeRes.writePList(strCodeResData); - if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) - { - ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); - return false; - } - - bool bForceSign = m_bForceSign; - if ("/" == strFolder && !m_strDyLibPath.empty()) - { //inject dylib - macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); - } - - if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) - { - return false; - } - - if(progressHandler) { - progressHandler(); - } - return true; -} - -void ZAppBundle::GetPlugIns(const string &strFolder, vector &arrPlugIns) -{ - DIR *dir = opendir(strFolder.c_str()); - if (NULL != dir) - { - dirent *ptr = readdir(dir); - while (NULL != ptr) - { - if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) - { - if (DT_DIR == ptr->d_type) - { - string strSubFolder = strFolder; - strSubFolder += "/"; - strSubFolder += ptr->d_name; - if (IsPathSuffix(strSubFolder, ".app") || IsPathSuffix(strSubFolder, ".appex")) - { - arrPlugIns.push_back(strSubFolder); - } - GetPlugIns(strSubFolder, arrPlugIns); - } - } - ptr = readdir(dir); - } - closedir(dir); - } -} - -bool ZAppBundle::ConfigureFolderSign(ZSignAsset *pSignAsset, - const string &strFolder, - const string &execName, - const string &strBundleID, - const string &strBundleVersion, - const string &strDisplayName, - const string &strDyLibFile, - bool bForce, - bool bWeakInject, - bool bEnableCache, - bool dontGenerateEmbeddedMobileProvision - ) -{ - m_bForceSign = bForce; - m_pSignAsset = pSignAsset; - m_bWeakInject = bWeakInject; - if (NULL == m_pSignAsset) - { - return false; - } - - if (!FindAppFolder(strFolder, m_strAppFolder)) - { - ZLog::ErrorV(">>> Can't Find App Folder! %s\n", strFolder.c_str()); - return false; - } - - JValue jvInfoPlist; - bool jvInfoReadSuccess = jvInfoPlist.readPListPath("%s/Info.plist", m_strAppFolder.c_str()); - if (!strBundleID.empty() || !strDisplayName.empty() || !strBundleVersion.empty()) - { //modify bundle id - - if (jvInfoReadSuccess) - { - m_bForceSign = true; - if (!strBundleID.empty()) - { - string strOldBundleID = jvInfoPlist["CFBundleIdentifier"]; - jvInfoPlist["CFBundleIdentifier"] = strBundleID; - ZLog::PrintV(">>> BundleId: \t%s -> %s\n", strOldBundleID.c_str(), strBundleID.c_str()); - - //modify plugins bundle id - vector arrPlugIns; - GetPlugIns(m_strAppFolder, arrPlugIns); - for (size_t i = 0; i < arrPlugIns.size(); i++) - { - string &strPlugin = arrPlugIns[i]; - JValue jvPlugInInfoPlist; - if (jvPlugInInfoPlist.readPListPath("%s/Info.plist", strPlugin.c_str())) - { - string strOldPlugInBundleID = jvPlugInInfoPlist["CFBundleIdentifier"]; - string strNewPlugInBundleID = strOldPlugInBundleID; - StringReplace(strNewPlugInBundleID, strOldBundleID, strBundleID); - jvPlugInInfoPlist["CFBundleIdentifier"] = strNewPlugInBundleID; - ZLog::PrintV(">>> BundleId: \t%s -> %s, PlugIn\n", strOldPlugInBundleID.c_str(), strNewPlugInBundleID.c_str()); - - if (jvPlugInInfoPlist.has("WKCompanionAppBundleIdentifier")) - { - string strOldWKCBundleID = jvPlugInInfoPlist["WKCompanionAppBundleIdentifier"]; - string strNewWKCBundleID = strOldWKCBundleID; - StringReplace(strNewWKCBundleID, strOldBundleID, strBundleID); - jvPlugInInfoPlist["WKCompanionAppBundleIdentifier"] = strNewWKCBundleID; - ZLog::PrintV(">>> BundleId: \t%s -> %s, PlugIn-WKCompanionAppBundleIdentifier\n", strOldWKCBundleID.c_str(), strNewWKCBundleID.c_str()); - } - - if (jvPlugInInfoPlist.has("NSExtension")) - { - if (jvPlugInInfoPlist["NSExtension"].has("NSExtensionAttributes")) - { - if (jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"].has("WKAppBundleIdentifier")) - { - string strOldWKBundleID = jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"]; - string strNewWKBundleID = strOldWKBundleID; - StringReplace(strNewWKBundleID, strOldBundleID, strBundleID); - jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = strNewWKBundleID; - ZLog::PrintV(">>> BundleId: \t%s -> %s, NSExtension-NSExtensionAttributes-WKAppBundleIdentifier\n", strOldWKBundleID.c_str(), strNewWKBundleID.c_str()); - } - } - } - - jvPlugInInfoPlist.writePListPath("%s/Info.plist", strPlugin.c_str()); - } - } - } - - if (!strDisplayName.empty()) - { - string strOldDisplayName = jvInfoPlist["CFBundleDisplayName"]; - jvInfoPlist["CFBundleName"] = strDisplayName; - jvInfoPlist["CFBundleDisplayName"] = strDisplayName; - ZLog::PrintV(">>> BundleName: %s -> %s\n", strOldDisplayName.c_str(), strDisplayName.c_str()); - } - - if (!strBundleVersion.empty()) - { - string strOldBundleVersion = jvInfoPlist["CFBundleVersion"]; - jvInfoPlist["CFBundleVersion"] = strBundleVersion; - jvInfoPlist["CFBundleShortVersionString"] = strBundleVersion; - ZLog::PrintV(">>> BundleVersion: %s -> %s\n", strOldBundleVersion.c_str(), strBundleVersion.c_str()); - } - - jvInfoPlist.writePListPath("%s/Info.plist", m_strAppFolder.c_str()); - } - else - { - ZLog::ErrorV(">>> Can't Find App's Info.plist! %s\n", strFolder.c_str()); - return false; - } - } - - if (!strDisplayName.empty()) - { - m_bForceSign = true; - JValue jvInfoPlistStrings; - if (jvInfoPlistStrings.readPListPath("%s/zh_CN.lproj/InfoPlist.strings", m_strAppFolder.c_str())) - { - jvInfoPlistStrings["CFBundleName"] = strDisplayName; - jvInfoPlistStrings["CFBundleDisplayName"] = strDisplayName; - jvInfoPlistStrings.writePListPath("%s/zh_CN.lproj/InfoPlist.strings", m_strAppFolder.c_str()); - } - jvInfoPlistStrings.clear(); - if (jvInfoPlistStrings.readPListPath("%s/zh-Hans.lproj/InfoPlist.strings", m_strAppFolder.c_str())) - { - jvInfoPlistStrings["CFBundleName"] = strDisplayName; - jvInfoPlistStrings["CFBundleDisplayName"] = strDisplayName; - jvInfoPlistStrings.writePListPath("%s/zh-Hans.lproj/InfoPlist.strings", m_strAppFolder.c_str()); - } - } - if (dontGenerateEmbeddedMobileProvision) { - if (!WriteFile(pSignAsset->m_strProvisionData, "%s/embedded.mobileprovision", m_strAppFolder.c_str())) - { //embedded.mobileprovision - ZLog::ErrorV(">>> Can't Write embedded.mobileprovision!\n"); - return false; - } - } - - if (!strDyLibFile.empty()) - { //inject dylib - string strDyLibData; - ReadFile(strDyLibFile.c_str(), strDyLibData); - if (!strDyLibData.empty()) - { - string strFileName = basename((char *)strDyLibFile.c_str()); - if (WriteFile(strDyLibData, "%s/%s", m_strAppFolder.c_str(), strFileName.c_str())) - { - StringFormat(m_strDyLibPath, "@executable_path/%s", strFileName.c_str()); - } - } - } - - string strCacheName; - SHA1Text(m_strAppFolder, strCacheName); - if (!IsFileExistsV("%s/zsign_cache.json", m_strAppFolder.c_str())) - { - m_bForceSign = true; - } - mainBundleIdentifier = string(jvInfoPlist["CFBundleIdentifier"]); - ZLog::PrintV("mainBundleIdentifier = %s", mainBundleIdentifier.c_str()); - - JValue jvRoot; - if (m_bForceSign) - { - jvRoot["path"] = "/"; - jvRoot["root"] = m_strAppFolder; - if (!GetSignFolderInfo(m_strAppFolder, jvRoot, true)) - { - ZLog::ErrorV(">>> Can't Get BundleID, BundleVersion, or BundleExecute in Info.plist! %s\n", m_strAppFolder.c_str()); - return false; - } - if (!GetObjectsToSign(m_strAppFolder, jvRoot)) - { - return false; - } - jvRoot["files"].push_back(execName); - GetNodeChangedFiles(jvRoot, dontGenerateEmbeddedMobileProvision); - } - else - { - jvRoot.readPath("%s/zsign_cache.json", m_strAppFolder.c_str()); - } - - ZLog::PrintV(">>> Signing: \t%s ...\n", m_strAppFolder.c_str()); - ZLog::PrintV(">>> AppName: \t%s\n", jvRoot["name"].asCString()); - ZLog::PrintV(">>> BundleId: \t%s\n", jvRoot["bid"].asCString()); - ZLog::PrintV(">>> BundleVer: \t%s\n", jvRoot["bver"].asCString()); - ZLog::PrintV(">>> TeamId: \t%s\n", m_pSignAsset->m_strTeamId.c_str()); - ZLog::PrintV(">>> SubjectCN: \t%s\n", m_pSignAsset->m_strSubjectCN.c_str()); - ZLog::PrintV(">>> ReadCache: \t%s\n", m_bForceSign ? "NO" : "YES"); - ZLog::PrintV(">>> Exclude MobileProvision: \t%s\n", dontGenerateEmbeddedMobileProvision ? "NO" : "YES"); - - config = jvRoot; - - return true; -} - -int ZAppBundle::GetSignCount() { - return GetSignCount(config); -} - -bool ZAppBundle::StartSign(bool enableCache) { - if (SignNode(config)) - { - if (enableCache) - { - config.styleWritePath("%s/zsign_cache.json", m_strAppFolder.c_str()); - } - return true; - } - return false; -} diff --git a/zsign/bundle.h b/zsign/bundle.h deleted file mode 100644 index 83d21db..0000000 --- a/zsign/bundle.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once -#include "common/common.h" -#include "common/json.h" -#include "openssl.h" - -class ZAppBundle -{ -public: - ZAppBundle(); - -public: - bool ConfigureFolderSign(ZSignAsset *pSignAsset, const string &strFolder, const string &execName, const string &strBundleID, const string &strBundleVersion, const string &strDisplayName, const string &strDyLibFile, bool bForce, bool bWeakInject, bool bEnableCache, bool dontGenerateEmbeddedMobileProvision); - bool StartSign(bool enableCache); - int GetSignCount(); -private: - bool SignNode(JValue &jvNode); - void GetNodeChangedFiles(JValue &jvNode, bool dontGenerateEmbeddedMobileProvision); - void GetChangedFiles(JValue &jvNode, vector &arrChangedFiles); - void GetPlugIns(const string &strFolder, vector &arrPlugIns); - int GetSignCount(JValue &jvNode); - -private: - bool FindAppFolder(const string &strFolder, string &strAppFolder); - bool GetObjectsToSign(const string &strFolder, JValue &jvInfo); - bool GetSignFolderInfo(const string &strFolder, JValue &jvNode, bool bGetName = false); - -private: - bool GenerateCodeResources(const string &strFolder, JValue &jvCodeRes); - void GetFolderFiles(const string &strFolder, const string &strBaseFolder, set &setFiles); - -private: - bool m_bForceSign; - bool m_bWeakInject; - string m_strDyLibPath; - ZSignAsset *m_pSignAsset; - string mainBundleIdentifier; - JValue config; - -public: - string m_strAppFolder; - std::function progressHandler; -}; diff --git a/zsign/common/base64.cpp b/zsign/common/base64.cpp deleted file mode 100644 index bde88ef..0000000 --- a/zsign/common/base64.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "base64.h" -#include - -#define B0(a) (a & 0xFF) -#define B1(a) (a >> 8 & 0xFF) -#define B2(a) (a >> 16 & 0xFF) -#define B3(a) (a >> 24 & 0xFF) - -ZBase64::ZBase64(void) -{ -} - -ZBase64::~ZBase64(void) -{ - if (!m_arrEnc.empty()) - { - for (size_t i = 0; i < m_arrEnc.size(); i++) - { - delete[] m_arrEnc[i]; - } - m_arrEnc.clear(); - } - - if (!m_arrDec.empty()) - { - for (size_t i = 0; i < m_arrDec.size(); i++) - { - delete[] m_arrDec[i]; - } - m_arrDec.clear(); - } -} - -char ZBase64::GetB64char(int nIndex) -{ - static const char szTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - if (nIndex >= 0 && nIndex < 64) - { - return szTable[nIndex]; - } - return '='; -} - -int ZBase64::GetB64Index(char ch) -{ - int index = -1; - if (ch >= 'A' && ch <= 'Z') - { - index = ch - 'A'; - } - else if (ch >= 'a' && ch <= 'z') - { - index = ch - 'a' + 26; - } - else if (ch >= '0' && ch <= '9') - { - index = ch - '0' + 52; - } - else if (ch == '+') - { - index = 62; - } - else if (ch == '/') - { - index = 63; - } - return index; -} - -const char *ZBase64::Encode(const char *szSrc, int nSrcLen) -{ - if (0 == nSrcLen) - { - nSrcLen = (int)strlen(szSrc); - } - - if (nSrcLen <= 0) - { - return ""; - } - - char *szEnc = new char[nSrcLen * 3 + 128]; - m_arrEnc.push_back(szEnc); - - int i = 0; - int len = 0; - unsigned char *psrc = (unsigned char *)szSrc; - char *p64 = szEnc; - for (i = 0; i < nSrcLen - 3; i += 3) - { - unsigned long ulTmp = *(unsigned long *)psrc; - int b0 = GetB64char((B0(ulTmp) >> 2) & 0x3F); - int b1 = GetB64char((B0(ulTmp) << 6 >> 2 | B1(ulTmp) >> 4) & 0x3F); - int b2 = GetB64char((B1(ulTmp) << 4 >> 2 | B2(ulTmp) >> 6) & 0x3F); - int b3 = GetB64char((B2(ulTmp) << 2 >> 2) & 0x3F); - *((unsigned long *)p64) = b0 | b1 << 8 | b2 << 16 | b3 << 24; - len += 4; - p64 += 4; - psrc += 3; - } - - if (i < nSrcLen) - { - int rest = nSrcLen - i; - unsigned long ulTmp = 0; - for (int j = 0; j < rest; ++j) - { - *(((unsigned char *)&ulTmp) + j) = *psrc++; - } - p64[0] = GetB64char((B0(ulTmp) >> 2) & 0x3F); - p64[1] = GetB64char((B0(ulTmp) << 6 >> 2 | B1(ulTmp) >> 4) & 0x3F); - p64[2] = rest > 1 ? GetB64char((B1(ulTmp) << 4 >> 2 | B2(ulTmp) >> 6) & 0x3F) : '='; - p64[3] = rest > 2 ? GetB64char((B2(ulTmp) << 2 >> 2) & 0x3F) : '='; - p64 += 4; - len += 4; - } - *p64 = '\0'; - return szEnc; -} - -const char *ZBase64::Encode(const string &strInput) -{ - return Encode(strInput.data(), strInput.size()); -} - -const char *ZBase64::Decode(const char *szSrc, int nSrcLen, int *pDecLen) -{ - if (0 == nSrcLen) - { - nSrcLen = (int)strlen(szSrc); - } - - if (nSrcLen <= 0) - { - return ""; - } - - char *szDec = new char[nSrcLen]; - m_arrDec.push_back(szDec); - - int i = 0; - int len = 0; - unsigned char *psrc = (unsigned char *)szSrc; - char *pbuf = szDec; - for (i = 0; i < nSrcLen - 4; i += 4) - { - unsigned long ulTmp = *(unsigned long *)psrc; - - int b0 = (GetB64Index((char)B0(ulTmp)) << 2 | GetB64Index((char)B1(ulTmp)) << 2 >> 6) & 0xFF; - int b1 = (GetB64Index((char)B1(ulTmp)) << 4 | GetB64Index((char)B2(ulTmp)) << 2 >> 4) & 0xFF; - int b2 = (GetB64Index((char)B2(ulTmp)) << 6 | GetB64Index((char)B3(ulTmp)) << 2 >> 2) & 0xFF; - - *((unsigned long *)pbuf) = b0 | b1 << 8 | b2 << 16; - psrc += 4; - pbuf += 3; - len += 3; - } - - if (i < nSrcLen) - { - int rest = nSrcLen - i; - unsigned long ulTmp = 0; - for (int j = 0; j < rest; ++j) - { - *(((unsigned char *)&ulTmp) + j) = *psrc++; - } - - int b0 = (GetB64Index((char)B0(ulTmp)) << 2 | GetB64Index((char)B1(ulTmp)) << 2 >> 6) & 0xFF; - *pbuf++ = b0; - len++; - - if ('=' != B1(ulTmp) && '=' != B2(ulTmp)) - { - int b1 = (GetB64Index((char)B1(ulTmp)) << 4 | GetB64Index((char)B2(ulTmp)) << 2 >> 4) & 0xFF; - *pbuf++ = b1; - len++; - } - - if ('=' != B2(ulTmp) && '=' != B3(ulTmp)) - { - int b2 = (GetB64Index((char)B2(ulTmp)) << 6 | GetB64Index((char)B3(ulTmp)) << 2 >> 2) & 0xFF; - *pbuf++ = b2; - len++; - } - } - *pbuf = '\0'; - - if (NULL != pDecLen) - { - *pDecLen = (int)(pbuf - szDec); - } - - return szDec; -} - -const char *ZBase64::Decode(const char *szSrc, string &strOutput) -{ - strOutput.clear(); - int nDecLen = 0; - const char *p = Decode(szSrc, 0, &nDecLen); - strOutput.append(p, nDecLen); - return strOutput.data(); -} diff --git a/zsign/common/base64.h b/zsign/common/base64.h deleted file mode 100644 index 3b38dc1..0000000 --- a/zsign/common/base64.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include -#include - -using namespace std; - -class ZBase64 -{ -public: - ZBase64(void); - ~ZBase64(void); - -public: - const char *Encode(const char *szSrc, int nSrcLen = 0); - const char *Encode(const string &strInput); - const char *Decode(const char *szSrc, int nSrcLen = 0, int *pDecLen = NULL); - const char *Decode(const char *szSrc, string &strOutput); - -private: - inline int GetB64Index(char ch); - inline char GetB64char(int nIndex); - -private: - vector m_arrDec; - vector m_arrEnc; -}; diff --git a/zsign/common/common.cpp b/zsign/common/common.cpp deleted file mode 100644 index cedb7fb..0000000 --- a/zsign/common/common.cpp +++ /dev/null @@ -1,848 +0,0 @@ -#include "common.h" -#include "base64.h" -#include -#include -#include -#include -#include "../Utils.hpp" -#include - -#define PARSEVALIST(szFormatArgs, szArgs) \ - ZBuffer buffer; \ - char szBuffer[PATH_MAX] = {0}; \ - char *szArgs = szBuffer; \ - va_list args; \ - va_start(args, szFormatArgs); \ - int nRet = vsnprintf(szArgs, PATH_MAX, szFormatArgs, args); \ - va_end(args); \ - if (nRet > PATH_MAX - 1) \ - { \ - char *szNewBuffer = buffer.GetBuffer(nRet + 1); \ - if (NULL != szNewBuffer) \ - { \ - szArgs = szNewBuffer; \ - va_start(args, szFormatArgs); \ - vsnprintf(szArgs, nRet + 1, szFormatArgs, args); \ - va_end(args); \ - } \ - } - -bool IsRegularFile(const char *file) -{ - struct stat info; - stat(file, &info); - return S_ISREG(info.st_mode); -} - -void *MapFile(const char *path, size_t offset, size_t size, size_t *psize, bool ro) -{ - int fd = open(path, ro ? O_RDONLY : O_RDWR); - if (fd <= 0) - { - return NULL; - } - - if (0 == size) - { - struct stat stat; - fstat(fd, &stat); - size = stat.st_size; - } - - if (NULL != psize) - { - *psize = size; - } - - void *base = mmap(NULL, size, ro ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); - close(fd); - - if (MAP_FAILED == base) - { - base = NULL; - } - - return base; -} - -bool WriteFile(const char *szFile, const char *szData, size_t sLen) -{ - if (NULL == szFile) - { - return false; - } - - FILE *fp = fopen(szFile, "wb"); - if (NULL != fp) - { - int64_t towrite = sLen; - if (NULL != szData) - { - while (towrite > 0) - { - int64_t nwrite = fwrite(szData + (sLen - towrite), 1, towrite, fp); - if (nwrite <= 0) - { - break; - } - towrite -= nwrite; - } - } - fclose(fp); - return (towrite > 0) ? false : true; - } - else - { - ZLog::ErrorV("WriteFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); - } - - return false; -} - -bool WriteFile(const char *szFile, const string &strData) -{ - return WriteFile(szFile, strData.data(), strData.size()); -} - -bool WriteFile(string &strData, const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szPath) - return WriteFile(szPath, strData); -} - -bool WriteFile(const char *szData, size_t sLen, const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szPath) - return WriteFile(szPath, szData, sLen); -} - -bool ReadFile(const char *szFile, string &strData) -{ - strData.clear(); - - if (!IsFileExists(szFile)) - { - return false; - } - - FILE *fp = fopen(szFile, "rb"); - if (NULL != fp) - { - strData.reserve(GetFileSize(fileno(fp))); - - char buf[4096] = {0}; - size_t nread = fread(buf, 1, 4096, fp); - while (nread > 0) - { - strData.append(buf, nread); - nread = fread(buf, 1, 4096, fp); - } - fclose(fp); - return true; - } - else - { - ZLog::ErrorV("ReadFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); - } - - return false; -} - -bool ReadFile(string &strData, const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szPath) - return ReadFile(szPath, strData); -} - -bool AppendFile(const char *szFile, const char *szData, size_t sLen) -{ - FILE *fp = fopen(szFile, "ab+"); - if (NULL != fp) - { - int64_t towrite = sLen; - while (towrite > 0) - { - int64_t nwrite = fwrite(szData + (sLen - towrite), 1, towrite, fp); - if (nwrite <= 0) - { - break; - } - towrite -= nwrite; - } - - fclose(fp); - return (towrite > 0) ? false : true; - } - else - { - ZLog::ErrorV("AppendFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); - } - return false; -} - -bool AppendFile(const char *szFile, const string &strData) -{ - return AppendFile(szFile, strData.data(), strData.size()); -} - -bool IsFolder(const char *szFolder) -{ - struct stat st; - stat(szFolder, &st); - return S_ISDIR(st.st_mode); -} - -bool IsFolderV(const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szFolder) - return IsFolder(szFolder); -} - -bool CreateFolder(const char *szFolder) -{ - if (!IsFolder(szFolder)) - { -#if defined(WINDOWS) - return (0 == mkdir(szFolder)); -#else - return (0 == mkdir(szFolder, 0755)); -#endif - } - return false; -} - -bool CreateFolderV(const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szFolder) - return CreateFolder(szFolder); -} - -int RemoveFolderCallBack(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) -{ - int ret = remove(fpath); - if (ret) - { - perror(fpath); - } - return ret; -} - -bool RemoveFolder(const char *szFolder) -{ - if (!IsFolder(szFolder)) - { - RemoveFile(szFolder); - return true; - } - return nftw(szFolder, RemoveFolderCallBack, 64, FTW_DEPTH | FTW_PHYS); -} - -bool RemoveFolderV(const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szFolder) - return RemoveFolder(szFolder); -} - -bool RemoveFile(const char *szFile) -{ - return (0 == remove(szFile)); -} - -bool RemoveFileV(const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szFile); - return RemoveFile(szFile); -} - -bool IsFileExists(const char *szFile) -{ - if (NULL == szFile) - { - return false; - } - return (0 == access(szFile, F_OK)); -} - -bool IsFileExistsV(const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szFile) - return IsFileExists(szFile); -} - -bool IsZipFile(const char *szFile) -{ - if (NULL != szFile && !IsFolder(szFile)) - { - FILE *fp = fopen(szFile, "rb"); - if (NULL != fp) - { - uint8_t buf[2] = {0}; - fread(buf, 1, 2, fp); - fclose(fp); - return (0 == memcmp("PK", buf, 2)); - } - } - return false; -} -#define PATH_BUFFER_LENGTH 1024 - -string GetCanonicalizePath(const char *szPath) -{ - string strPath = szPath; - if (!strPath.empty()) - { - if ('/' != szPath[0]) - { - char path[PATH_MAX] = {0}; - -#if defined(WINDOWS) - - if (NULL != _fullpath((char *)"./", path, PATH_BUFFER_LENGTH)) - { - strPath = path; - strPath += "/"; - strPath += szPath; - } -#else - if (NULL != realpath("./", path)) - { - strPath = path; - strPath += "/"; - strPath += szPath; - } -#endif - } - StringReplace(strPath, "/./", "/"); - } - return strPath; -} - -int64_t GetFileSize(int fd) -{ - int64_t nSize = 0; - struct stat stbuf; - if (0 == fstat(fd, &stbuf)) - { - if (S_ISREG(stbuf.st_mode)) - { - nSize = stbuf.st_size; - } - } - return (nSize < 0 ? 0 : nSize); -} - -int64_t GetFileSize(const char *szFile) -{ - int64_t nSize = 0; - int fd = open(szFile, O_RDONLY); - if (fd >= 0) - { - nSize = GetFileSize(fd); - close(fd); - } - return nSize; -} - -int64_t GetFileSizeV(const char *szFormatPath, ...) -{ - PARSEVALIST(szFormatPath, szFile) - return GetFileSize(szFile); -} - -string GetFileSizeString(const char *szFile) -{ - return FormatSize(GetFileSize(szFile), 1024); -} - -string FormatSize(int64_t size, int64_t base) -{ - double fsize = 0; - char ret[64] = {0}; - if (size > base * base * base * base) - { - fsize = (size * 1.0) / (base * base * base * base); - sprintf(ret, "%.2f TB", fsize); - } - else if (size > base * base * base) - { - fsize = (size * 1.0) / (base * base * base); - sprintf(ret, "%.2f GB", fsize); - } - else if (size > base * base) - { - fsize = (size * 1.0) / (base * base); - sprintf(ret, "%.2f MB", fsize); - } - else if (size > base) - { - fsize = (size * 1.0) / (base); - sprintf(ret, "%.2f KB", fsize); - } - else - { - sprintf(ret, "%" PRId64 " B", size); - } - return ret; -} - -bool IsPathSuffix(const string &strPath, const char *suffix) -{ - size_t nPos = strPath.rfind(suffix); - if (string::npos != nPos) - { - if (nPos == (strPath.size() - strlen(suffix))) - { - return true; - } - } - return false; -} - -time_t GetUnixStamp() -{ - time_t ustime = 0; - time(&ustime); - return ustime; -} - -uint64_t GetMicroSecond() -{ - struct timeval tv = {0}; - gettimeofday(&tv, NULL); - return tv.tv_sec * 1000000 + tv.tv_usec; -} - -bool SystemExec(const char *szFormatCmd, ...) -{ - /*PARSEVALIST(szFormatCmd, szCmd) - - if (strlen(szCmd) <= 0) - { - return false; - } - - int status = system(szCmd); - - if (-1 == status) - { - ZLog::ErrorV("SystemExec: \"%s\", Error!\n", szCmd); - return false; - } - else - { -#if !defined(WINDOWS) - if (WIFEXITED(status)) - { - if (0 == WEXITSTATUS(status)) - { - return true; - } - else - { - ZLog::ErrorV("SystemExec: \"%s\", Failed! Exit-Status: %d\n", szCmd, WEXITSTATUS(status)); - return false; - } - } - else - { - return true; - } -#endif - } - */ - return false; -} - -uint16_t _Swap(uint16_t value) -{ - return ((value >> 8) & 0x00ff) | - ((value << 8) & 0xff00); -} - -uint32_t _Swap(uint32_t value) -{ - value = ((value >> 8) & 0x00ff00ff) | - ((value << 8) & 0xff00ff00); - value = ((value >> 16) & 0x0000ffff) | - ((value << 16) & 0xffff0000); - return value; -} - -uint64_t _Swap(uint64_t value) -{ - value = (value & 0x00000000ffffffffULL) << 32 | (value & 0xffffffff00000000ULL) >> 32; - value = (value & 0x0000ffff0000ffffULL) << 16 | (value & 0xffff0000ffff0000ULL) >> 16; - value = (value & 0x00ff00ff00ff00ffULL) << 8 | (value & 0xff00ff00ff00ff00ULL) >> 8; - return value; -} - -uint32_t ByteAlign(uint32_t uValue, uint32_t uAlign) -{ - return (uValue + (uAlign - uValue % uAlign)); -} - -const char *StringFormat(string &strFormat, const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szFormat) - strFormat = szFormat; - return strFormat.c_str(); -} - -string &StringReplace(string &context, const string &from, const string &to) -{ - size_t lookHere = 0; - size_t foundHere; - while ((foundHere = context.find(from, lookHere)) != string::npos) - { - context.replace(foundHere, from.size(), to); - lookHere = foundHere + to.size(); - } - return context; -} - -void StringSplit(const string &src, const string &split, vector &dest) -{ - size_t oldPos = 0; - size_t newPos = src.find(split, oldPos); - while (newPos != string::npos) - { - dest.push_back(src.substr(oldPos, newPos - oldPos)); - oldPos = newPos + split.size(); - newPos = src.find(split, oldPos); - } - if (oldPos < src.size()) - { - dest.push_back(src.substr(oldPos)); - } -} - -bool SHA1Text(const string &strData, string &strOutput) -{ - string strSHASum; - SHASum(E_SHASUM_TYPE_1, strData, strSHASum); - - strOutput.clear(); - char buf[16] = {0}; - for (size_t i = 0; i < strSHASum.size(); i++) - { - sprintf(buf, "%02x", (uint8_t)strSHASum[i]); - strOutput += buf; - } - return (!strOutput.empty()); -} - -void PrintSHASum(const char *prefix, const uint8_t *hash, uint32_t size, const char *suffix) -{ - ZLog::PrintV("%s", prefix); - for (uint32_t i = 0; i < size; i++) - { - ZLog::PrintV("%02x", hash[i]); - } - ZLog::PrintV("%s", suffix); -} - -void PrintSHASum(const char *prefix, const string &strSHASum, const char *suffix) -{ - PrintSHASum(prefix, (const uint8_t *)strSHASum.data(), strSHASum.size(), suffix); -} - -void PrintDataSHASum(const char *prefix, int nSumType, const string &strData, const char *suffix) -{ - string strSHASum; - SHASum(nSumType, strData, strSHASum); - PrintSHASum(prefix, strSHASum, suffix); -} - -void PrintDataSHASum(const char *prefix, int nSumType, uint8_t *data, size_t size, const char *suffix) -{ - string strSHASum; - SHASum(nSumType, data, size, strSHASum); - PrintSHASum(prefix, strSHASum, suffix); -} - -bool SHASum(int nSumType, uint8_t *data, size_t size, string &strOutput) -{ - strOutput.clear(); - if (1 == nSumType) - { - uint8_t hash[20]; - memset(hash, 0, 20); - SHA1(data, size, hash); - strOutput.append((const char *)hash, 20); - } - else - { - uint8_t hash[32]; - memset(hash, 0, 32); - SHA256(data, size, hash); - strOutput.append((const char *)hash, 32); - } - return true; -} - -bool SHASum(int nSumType, const string &strData, string &strOutput) -{ - return SHASum(nSumType, (uint8_t *)strData.data(), strData.size(), strOutput); -} - -bool SHASum(const string &strData, string &strSHA1, string &strSHA256) -{ - SHASum(E_SHASUM_TYPE_1, strData, strSHA1); - SHASum(E_SHASUM_TYPE_256, strData, strSHA256); - return (!strSHA1.empty() && !strSHA256.empty()); -} - -bool SHASumFile(const char *szFile, string &strSHA1, string &strSHA256) -{ - size_t sSize = 0; - uint8_t *pBase = (uint8_t *)MapFile(szFile, 0, 0, &sSize, true); - - SHASum(E_SHASUM_TYPE_1, pBase, sSize, strSHA1); - SHASum(E_SHASUM_TYPE_256, pBase, sSize, strSHA256); - - if (NULL != pBase && sSize > 0) - { - munmap(pBase, sSize); - } - return (!strSHA1.empty() && !strSHA256.empty()); -} - -bool SHASumBase64(const string &strData, string &strSHA1Base64, string &strSHA256Base64) -{ - ZBase64 b64; - string strSHA1; - string strSHA256; - SHASum(strData, strSHA1, strSHA256); - strSHA1Base64 = b64.Encode(strSHA1); - strSHA256Base64 = b64.Encode(strSHA256); - return (!strSHA1Base64.empty() && !strSHA256Base64.empty()); -} - -bool SHASumBase64File(const char *szFile, string &strSHA1Base64, string &strSHA256Base64) -{ - ZBase64 b64; - string strSHA1; - string strSHA256; - SHASumFile(szFile, strSHA1, strSHA256); - strSHA1Base64 = b64.Encode(strSHA1); - strSHA256Base64 = b64.Encode(strSHA256); - return (!strSHA1Base64.empty() && !strSHA256Base64.empty()); -} - -ZBuffer::ZBuffer() -{ - m_pData = NULL; - m_uSize = 0; -} - -ZBuffer::~ZBuffer() -{ - Free(); -} - -char *ZBuffer::GetBuffer(uint32_t uSize) -{ - if (uSize <= m_uSize) - { - return m_pData; - } - - char *pData = (char *)realloc(m_pData, uSize); - if (NULL == pData) - { - Free(); - return NULL; - } - - m_pData = pData; - m_uSize = uSize; - return m_pData; -} - -void ZBuffer::Free() -{ - if (NULL != m_pData) - { - free(m_pData); - } - m_pData = NULL; - m_uSize = 0; -} - -ZTimer::ZTimer() -{ - Reset(); -} - -uint64_t ZTimer::Reset() -{ - m_uBeginTime = GetMicroSecond(); - return m_uBeginTime; -} - -uint64_t ZTimer::Print(const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szFormat) - uint64_t uElapse = GetMicroSecond() - m_uBeginTime; - ZLog::PrintV("%s (%.03fs, %lluus)\n", szFormat, uElapse / 1000000.0, uElapse); - return Reset(); -} - -uint64_t ZTimer::PrintResult(bool bSuccess, const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szFormat) - uint64_t uElapse = GetMicroSecond() - m_uBeginTime; - ZLog::PrintResultV(bSuccess, "%s (%.03fs, %lluus)\n", szFormat, uElapse / 1000000.0, uElapse); - return Reset(); -} - -int ZLog::g_nLogLevel = ZLog::E_INFO; -vector ZLog::logs; - -void ZLog::SetLogLever(int nLogLevel) -{ - g_nLogLevel = nLogLevel; -} - -void ZLog::writeToLogFile(const std::string& message) { -// const char* documentsPath = getDocumentsDirectory(); -// std::string logFilePath = std::string(documentsPath) + "/logs.txt"; -// -// std::ofstream logFile(logFilePath, std::ios_base::app); -// if (logFile.is_open()) { -// logFile << message; -// logFile.close(); -// } else { -// std::cerr << "Failed to open log file: " << logFilePath << std::endl; -// } - writeToNSLog(message.data()); - logs.push_back(message); -} - -void ZLog::Print(int nLevel, const char *szLog) -{ - if (g_nLogLevel >= nLevel) - { - write(STDOUT_FILENO, szLog, strlen(szLog)); - writeToLogFile(szLog); - } -} - -void ZLog::PrintV(int nLevel, const char *szFormatArgs, ...) { - if (g_nLogLevel >= nLevel) { - PARSEVALIST(szFormatArgs, szLog) - write(STDOUT_FILENO, szLog, strlen(szLog)); - writeToLogFile(szLog); - } -} - -bool ZLog::Error(const char *szLog) -{ - write(STDOUT_FILENO, "\033[31m", 5); - write(STDOUT_FILENO, szLog, strlen(szLog)); - write(STDOUT_FILENO, "\033[0m", 4); - writeToLogFile(szLog); - return false; -} - -bool ZLog::ErrorV(const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szLog) - write(STDOUT_FILENO, "\033[31m", 5); - write(STDOUT_FILENO, szLog, strlen(szLog)); - write(STDOUT_FILENO, "\033[0m", 4); - writeToLogFile(szLog); - return false; -} - -bool ZLog::Success(const char *szLog) -{ - write(STDOUT_FILENO, "\033[32m", 5); - write(STDOUT_FILENO, szLog, strlen(szLog)); - write(STDOUT_FILENO, "\033[0m", 4); - writeToLogFile(szLog); - return true; -} - -bool ZLog::SuccessV(const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szLog) - write(STDOUT_FILENO, "\033[32m", 5); - write(STDOUT_FILENO, szLog, strlen(szLog)); - write(STDOUT_FILENO, "\033[0m", 4); - writeToLogFile(szLog); - return true; -} - -bool ZLog::PrintResult(bool bSuccess, const char *szLog) -{ - return bSuccess ? Success(szLog) : Error(szLog); -} - -bool ZLog::PrintResultV(bool bSuccess, const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szLog) - return bSuccess ? Success(szLog) : Error(szLog); -} - -bool ZLog::Warn(const char *szLog) -{ - write(STDOUT_FILENO, "\033[33m", 5); - write(STDOUT_FILENO, szLog, strlen(szLog)); - write(STDOUT_FILENO, "\033[0m", 4); - writeToLogFile(szLog); - return false; -} - -bool ZLog::WarnV(const char *szFormatArgs, ...) -{ - PARSEVALIST(szFormatArgs, szLog) - write(STDOUT_FILENO, "\033[33m", 5); - write(STDOUT_FILENO, szLog, strlen(szLog)); - write(STDOUT_FILENO, "\033[0m", 4); - writeToLogFile(szLog); - return false; -} - -void ZLog::Print(const char *szLog) -{ - if (g_nLogLevel >= E_INFO) - { - write(STDOUT_FILENO, szLog, strlen(szLog)); - writeToLogFile(szLog); - } -} - -void ZLog::PrintV(const char *szFormatArgs, ...) -{ - if (g_nLogLevel >= E_INFO) - { - PARSEVALIST(szFormatArgs, szLog) - write(STDOUT_FILENO, szLog, strlen(szLog)); - writeToLogFile(szLog); - } -} - -void ZLog::Debug(const char *szLog) -{ - if (g_nLogLevel >= E_DEBUG) - { - write(STDOUT_FILENO, szLog, strlen(szLog)); - writeToLogFile(szLog); - } -} - -void ZLog::DebugV(const char *szFormatArgs, ...) -{ - if (g_nLogLevel >= E_DEBUG) - { - PARSEVALIST(szFormatArgs, szLog) - write(STDOUT_FILENO, szLog, strlen(szLog)); - writeToLogFile(szLog); - } -} - -bool ZLog::IsDebug() -{ - return (E_DEBUG == g_nLogLevel); -} diff --git a/zsign/common/common.h b/zsign/common/common.h deleted file mode 100644 index d84a2e4..0000000 --- a/zsign/common/common.h +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -using namespace std; - -#define LE(x) _Swap(x) -#define BE(x) _Swap(x) - -#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) -#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) -#endif - -uint16_t _Swap(uint16_t value); -uint32_t _Swap(uint32_t value); -uint64_t _Swap(uint64_t value); - -bool ReadFile(const char *szFile, string &strData); -bool ReadFile(string &strData, const char *szFormatPath, ...); -bool WriteFile(const char *szFile, const string &strData); -bool WriteFile(const char *szFile, const char *szData, size_t sLen); -bool WriteFile(string &strData, const char *szFormatPath, ...); -bool WriteFile(const char *szData, size_t sLen, const char *szFormatPath, ...); -bool AppendFile(const char *szFile, const string &strData); -bool AppendFile(const char *szFile, const char *szData, size_t sLen); -bool AppendFile(const string &strData, const char *szFormatPath, ...); -bool IsRegularFile(const char *szFile); -bool IsFolder(const char *szFolder); -bool IsFolderV(const char *szFormatPath, ...); -bool CreateFolder(const char *szFolder); -bool CreateFolderV(const char *szFormatPath, ...); -bool RemoveFile(const char *szFile); -bool RemoveFileV(const char *szFormatPath, ...); -bool RemoveFolder(const char *szFolder); -bool RemoveFolderV(const char *szFormatPath, ...); -bool IsFileExists(const char *szFile); -bool IsFileExistsV(const char *szFormatPath, ...); -int64_t GetFileSize(int fd); -int64_t GetFileSize(const char *szFile); -int64_t GetFileSizeV(const char *szFormatPath, ...); -string GetFileSizeString(const char *szFile); -bool IsZipFile(const char *szFile); -string GetCanonicalizePath(const char *szPath); -void *MapFile(const char *path, size_t offset, size_t size, size_t *psize, bool ro); -bool IsPathSuffix(const string &strPath, const char *suffix); - -const char *StringFormat(string &strFormat, const char *szFormatArgs, ...); -string &StringReplace(string &context, const string &from, const string &to); -void StringSplit(const string &src, const string &split, vector &dest); - -string FormatSize(int64_t size, int64_t base = 1024); -time_t GetUnixStamp(); -uint64_t GetMicroSecond(); -bool SystemExec(const char *szFormatCmd, ...); -uint32_t ByteAlign(uint32_t uValue, uint32_t uAlign); - -enum -{ - E_SHASUM_TYPE_1 = 1, - E_SHASUM_TYPE_256 = 2, -}; - -bool SHASum(int nSumType, uint8_t *data, size_t size, string &strOutput); -bool SHASum(int nSumType, const string &strData, string &strOutput); -bool SHASum(const string &strData, string &strSHA1, string &strSHA256); -bool SHA1Text(const string &strData, string &strOutput); -bool SHASumFile(const char *szFile, string &strSHA1, string &strSHA256); -bool SHASumBase64(const string &strData, string &strSHA1Base64, string &strSHA256Base64); -bool SHASumBase64File(const char *szFile, string &strSHA1Base64, string &strSHA256Base64); -void PrintSHASum(const char *prefix, const uint8_t *hash, uint32_t size, const char *suffix = "\n"); -void PrintSHASum(const char *prefix, const string &strSHASum, const char *suffix = "\n"); -void PrintDataSHASum(const char *prefix, int nSumType, const string &strData, const char *suffix = "\n"); -void PrintDataSHASum(const char *prefix, int nSumType, uint8_t *data, size_t size, const char *suffix = "\n"); - -class ZBuffer -{ -public: - ZBuffer(); - ~ZBuffer(); - -public: - char *GetBuffer(uint32_t uSize); - -private: - void Free(); - -private: - char *m_pData; - uint32_t m_uSize; -}; - -class ZTimer -{ -public: - ZTimer(); - -public: - uint64_t Reset(); - uint64_t Print(const char *szFormatArgs, ...); - uint64_t PrintResult(bool bSuccess, const char *szFormatArgs, ...); - -private: - uint64_t m_uBeginTime; -}; - -class ZLog -{ -public: - enum eLogType - { - E_NONE = 0, - E_ERROR = 1, - E_WARN = 2, - E_INFO = 3, - E_DEBUG = 4 - }; - -public: - static bool IsDebug(); - static void Print(const char *szLog); - static void PrintV(const char *szFormatArgs, ...); - static void Debug(const char *szLog); - static void DebugV(const char *szFormatArgs, ...); - static bool Warn(const char *szLog); - static bool WarnV(const char *szFormatArgs, ...); - static bool Error(const char *szLog); - static bool ErrorV(const char *szFormatArgs, ...); - static bool Success(const char *szLog); - static bool SuccessV(const char *szFormatArgs, ...); - static bool PrintResult(bool bSuccess, const char *szLog); - static bool PrintResultV(bool bSuccess, const char *szFormatArgs, ...); - static void Print(int nLevel, const char *szLog); - static void PrintV(int nLevel, const char *szFormatArgs, ...); - static void SetLogLever(int nLogLevel); - static vector logs; - -private: - static int g_nLogLevel; - static void writeToLogFile(const std::string& message); - -}; diff --git a/zsign/common/json.cpp b/zsign/common/json.cpp deleted file mode 100644 index 99721bb..0000000 --- a/zsign/common/json.cpp +++ /dev/null @@ -1,3061 +0,0 @@ -#include "json.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "base64.h" - -#ifndef WIN32 -#define _atoi64(val) strtoll(val, NULL, 10) -#endif - -const JValue JValue::null; -const string JValue::nullData; - -JValue::JValue(TYPE type) : m_eType(type) -{ - m_Value.vFloat = 0; -} - -JValue::JValue(int val) : m_eType(E_INT) -{ - m_Value.vInt64 = val; -} - -JValue::JValue(int64_t val) : m_eType(E_INT) -{ - m_Value.vInt64 = val; -} - -JValue::JValue(bool val) : m_eType(E_BOOL) -{ - m_Value.vBool = val; -} - -JValue::JValue(double val) : m_eType(E_FLOAT) -{ - m_Value.vFloat = val; -} - -JValue::JValue(const char *val) : m_eType(E_STRING) -{ - m_Value.vString = NewString(val); -} - -JValue::JValue(const string &val) : m_eType(E_STRING) -{ - m_Value.vString = NewString(val.c_str()); -} - -JValue::JValue(const JValue &other) -{ - CopyValue(other); -} - -JValue::JValue(const char *val, size_t len) : m_eType(E_DATA) -{ - m_Value.vData = new string(); - m_Value.vData->append(val, len); -} - -JValue::~JValue() -{ - Free(); -} - -void JValue::clear() -{ - Free(); -} - -bool JValue::isInt() const -{ - return (E_INT == m_eType); -} - -bool JValue::isNull() const -{ - return (E_NULL == m_eType); -} - -bool JValue::isBool() const -{ - return (E_BOOL == m_eType); -} - -bool JValue::isFloat() const -{ - return (E_FLOAT == m_eType); -} - -bool JValue::isString() const -{ - return (E_STRING == m_eType); -} - -bool JValue::isArray() const -{ - return (E_ARRAY == m_eType); -} - -bool JValue::isObject() const -{ - return (E_OBJECT == m_eType); -} - -bool JValue::isEmpty() const -{ - switch (m_eType) - { - case E_NULL: - return true; - break; - case E_INT: - return (0 == m_Value.vInt64); - break; - case E_BOOL: - return (false == m_Value.vBool); - break; - case E_FLOAT: - return (0 == m_Value.vFloat); - break; - case E_ARRAY: - case E_OBJECT: - return (0 == size()); - break; - case E_STRING: - return (0 == strlen(asCString())); - case E_DATE: - return (0 == m_Value.vDate); - break; - case E_DATA: - return (NULL == m_Value.vData) ? true : m_Value.vData->empty(); - break; - } - return true; -} - -JValue::operator const char *() const -{ - return asCString(); -} - -JValue::operator int() const -{ - return asInt(); -} - -JValue::operator int64_t() const -{ - return asInt64(); -} - -JValue::operator double() const -{ - return asFloat(); -} - -JValue::operator string() const -{ - return asCString(); -} - -JValue::operator bool() const -{ - return asBool(); -} - -char *JValue::NewString(const char *cstr) -{ - char *str = NULL; - if (NULL != cstr) - { - size_t len = (strlen(cstr) + 1) * sizeof(char); - str = (char *)malloc(len); - memcpy(str, cstr, len); - } - return str; -} - -void JValue::CopyValue(const JValue &src) -{ - m_eType = src.m_eType; - switch (m_eType) - { - case E_ARRAY: - m_Value.vArray = (NULL == src.m_Value.vArray) ? NULL : new vector(*(src.m_Value.vArray)); - break; - case E_OBJECT: - m_Value.vObject = (NULL == src.m_Value.vObject) ? NULL : new map(*(src.m_Value.vObject)); - break; - case E_STRING: - m_Value.vString = (NULL == src.m_Value.vString) ? NULL : NewString(src.m_Value.vString); - break; - case E_DATA: - { - if (NULL != src.m_Value.vData) - { - m_Value.vData = new string(); - *m_Value.vData = *src.m_Value.vData; - } - else - { - m_Value.vData = NULL; - } - } - break; - default: - m_Value = src.m_Value; - break; - } -} - -void JValue::Free() -{ - switch (m_eType) - { - case E_INT: - { - m_Value.vInt64 = 0; - } - break; - case E_BOOL: - { - m_Value.vBool = false; - } - break; - case E_FLOAT: - { - m_Value.vFloat = 0.0; - } - break; - case E_STRING: - { - if (NULL != m_Value.vString) - { - free(m_Value.vString); - m_Value.vString = NULL; - } - } - break; - case E_ARRAY: - { - if (NULL != m_Value.vArray) - { - delete m_Value.vArray; - m_Value.vArray = NULL; - } - } - break; - case E_OBJECT: - { - if (NULL != m_Value.vObject) - { - delete m_Value.vObject; - m_Value.vObject = NULL; - } - } - break; - case E_DATE: - { - m_Value.vDate = 0; - } - break; - case E_DATA: - { - if (NULL != m_Value.vData) - { - delete m_Value.vData; - m_Value.vData = NULL; - } - } - break; - default: - break; - } - m_eType = E_NULL; -} - -JValue &JValue::operator=(const JValue &other) -{ - if (this != &other) - { - Free(); - CopyValue(other); - } - return (*this); -} - -JValue::TYPE JValue::type() const -{ - return m_eType; -} - -int JValue::asInt() const -{ - return (int)asInt64(); -} - -int64_t JValue::asInt64() const -{ - switch (m_eType) - { - case E_INT: - return m_Value.vInt64; - break; - case E_BOOL: - return m_Value.vBool ? 1 : 0; - break; - case E_FLOAT: - return int(m_Value.vFloat); - break; - case E_STRING: - return _atoi64(asCString()); - break; - default: - break; - } - return 0; -} - -double JValue::asFloat() const -{ - switch (m_eType) - { - case E_INT: - return double(m_Value.vInt64); - break; - case E_BOOL: - return m_Value.vBool ? 1.0 : 0.0; - break; - case E_FLOAT: - return m_Value.vFloat; - break; - case E_STRING: - return atof(asCString()); - break; - default: - break; - } - return 0.0; -} - -bool JValue::asBool() const -{ - switch (m_eType) - { - case E_BOOL: - return m_Value.vBool; - break; - case E_INT: - return (0 != m_Value.vInt64); - break; - case E_FLOAT: - return (0.0 != m_Value.vFloat); - break; - case E_ARRAY: - return (NULL == m_Value.vArray) ? false : (m_Value.vArray->size() > 0); - break; - case E_OBJECT: - return (NULL == m_Value.vObject) ? false : (m_Value.vObject->size() > 0); - break; - case E_STRING: - return (NULL == m_Value.vString) ? false : (strlen(m_Value.vString) > 0); - break; - case E_DATE: - return (m_Value.vDate > 0); - break; - case E_DATA: - return (NULL == m_Value.vData) ? false : (m_Value.vData->size() > 0); - break; - default: - break; - } - return false; -} - -string JValue::asString() const -{ - switch (m_eType) - { - case E_BOOL: - return m_Value.vBool ? "true" : "false"; - break; - case E_INT: - { - char buf[256]; - sprintf(buf, "%" PRId64, m_Value.vInt64); - return buf; - } - break; - case E_FLOAT: - { - char buf[256]; - sprintf(buf, "%lf", m_Value.vFloat); - return buf; - } - break; - case E_ARRAY: - return "array"; - break; - case E_OBJECT: - return "object"; - break; - case E_STRING: - return (NULL == m_Value.vString) ? "" : m_Value.vString; - break; - case E_DATE: - return "date"; - break; - case E_DATA: - return "data"; - break; - default: - break; - } - return ""; -} - -const char *JValue::asCString() const -{ - if (E_STRING == m_eType && NULL != m_Value.vString) - { - return m_Value.vString; - } - return ""; -} - -size_t JValue::size() const -{ - switch (m_eType) - { - case E_ARRAY: - return (NULL == m_Value.vArray) ? 0 : m_Value.vArray->size(); - break; - case E_OBJECT: - return (NULL == m_Value.vObject) ? 0 : m_Value.vObject->size(); - break; - case E_DATA: - return (NULL == m_Value.vData) ? 0 : m_Value.vData->size(); - break; - default: - break; - } - return 0; -} - -JValue &JValue::operator[](int index) -{ - return (*this)[(size_t)(index < 0 ? 0 : index)]; -} - -const JValue &JValue::operator[](int index) const -{ - return (*this)[(size_t)(index < 0 ? 0 : index)]; -} - -JValue &JValue::operator[](int64_t index) -{ - return (*this)[(size_t)(index < 0 ? 0 : index)]; -} - -const JValue &JValue::operator[](int64_t index) const -{ - return (*this)[(size_t)(index < 0 ? 0 : index)]; -} - -JValue &JValue::operator[](size_t index) -{ - if (E_ARRAY != m_eType || NULL == m_Value.vArray) - { - Free(); - m_eType = E_ARRAY; - m_Value.vArray = new vector(); - } - - size_t sum = m_Value.vArray->size(); - if (sum <= index) - { - size_t fill = index - sum; - for (size_t i = 0; i <= fill; i++) - { - m_Value.vArray->push_back(null); - } - } - - return m_Value.vArray->at(index); -} - -const JValue &JValue::operator[](size_t index) const -{ - if (E_ARRAY == m_eType && NULL != m_Value.vArray) - { - if (index < m_Value.vArray->size()) - { - return m_Value.vArray->at(index); - } - } - return null; -} - -JValue &JValue::operator[](const string &key) -{ - return (*this)[key.c_str()]; -} - -const JValue &JValue::operator[](const string &key) const -{ - return (*this)[key.c_str()]; -} - -JValue &JValue::operator[](const char *key) -{ - map::iterator it; - if (E_OBJECT != m_eType || NULL == m_Value.vObject) - { - Free(); - m_eType = E_OBJECT; - m_Value.vObject = new map(); - } - else - { - it = m_Value.vObject->find(key); - if (it != m_Value.vObject->end()) - { - return it->second; - } - } - it = m_Value.vObject->insert(m_Value.vObject->end(), make_pair(key, null)); - return it->second; -} - -const JValue &JValue::operator[](const char *key) const -{ - if (E_OBJECT == m_eType && NULL != m_Value.vObject) - { - map::const_iterator it = m_Value.vObject->find(key); - if (it != m_Value.vObject->end()) - { - return it->second; - } - } - return null; -} - -bool JValue::has(const char *key) const -{ - if (E_OBJECT == m_eType && NULL != m_Value.vObject) - { - if (m_Value.vObject->end() != m_Value.vObject->find(key)) - { - return true; - } - } - - return false; -} - -int JValue::index(const char *ele) const -{ - if (E_ARRAY == m_eType && NULL != m_Value.vArray) - { - for (size_t i = 0; i < m_Value.vArray->size(); i++) - { - if (ele == (*m_Value.vArray)[i].asString()) - { - return (int)i; - } - } - } - - return -1; -} - -JValue &JValue::at(int index) -{ - return (*this)[index]; -} - -JValue &JValue::at(size_t index) -{ - return (*this)[index]; -} - -JValue &JValue::at(const char *key) -{ - return (*this)[key]; -} - -bool JValue::remove(int index) -{ - if (index >= 0) - { - return remove((size_t)index); - } - return false; -} - -bool JValue::remove(size_t index) -{ - if (E_ARRAY == m_eType && NULL != m_Value.vArray) - { - if (index < m_Value.vArray->size()) - { - m_Value.vArray->erase(m_Value.vArray->begin() + index); - return true; - } - } - return false; -} - -bool JValue::remove(const char *key) -{ - if (E_OBJECT == m_eType && NULL != m_Value.vObject) - { - if (m_Value.vObject->end() != m_Value.vObject->find(key)) - { - m_Value.vObject->erase(key); - return !has(key); - } - } - return false; -} - -bool JValue::keys(vector &arrKeys) const -{ - if (E_OBJECT == m_eType && NULL != m_Value.vObject) - { - arrKeys.reserve(m_Value.vObject->size()); - map::iterator itbeg = m_Value.vObject->begin(); - map::iterator itend = m_Value.vObject->end(); - for (; itbeg != itend; itbeg++) - { - arrKeys.push_back((itbeg->first).c_str()); - } - return true; - } - return false; -} - -string JValue::write() const -{ - string strDoc; - return write(strDoc); -} - -const char *JValue::write(string &strDoc) const -{ - strDoc.clear(); - JWriter::FastWrite((*this), strDoc); - return strDoc.c_str(); -} - -bool JValue::read(const string &strdoc, string *pstrerr) -{ - return read(strdoc.c_str(), pstrerr); -} - -bool JValue::read(const char *pdoc, string *pstrerr) -{ - JReader reader; - bool bret = reader.parse(pdoc, *this); - if (!bret) - { - if (NULL != pstrerr) - { - reader.error(*pstrerr); - } - } - return bret; -} - -JValue &JValue::front() -{ - if (E_ARRAY == m_eType) - { - if (size() > 0) - { - return *(m_Value.vArray->begin()); - } - } - else if (E_OBJECT == m_eType) - { - if (size() > 0) - { - return m_Value.vObject->begin()->second; - } - } - return (*this); -} - -JValue &JValue::back() -{ - if (E_ARRAY == m_eType) - { - if (size() > 0) - { - return *(m_Value.vArray->rbegin()); - } - } - else if (E_OBJECT == m_eType) - { - if (size() > 0) - { - return m_Value.vObject->rbegin()->second; - } - } - return (*this); -} - -bool JValue::join(JValue &jv) -{ - if ((E_OBJECT == m_eType || E_NULL == m_eType) && E_OBJECT == jv.type()) - { - vector arrKeys; - jv.keys(arrKeys); - for (size_t i = 0; i < arrKeys.size(); i++) - { - (*this)[arrKeys[i]] = jv[arrKeys[i]]; - } - return true; - } - else if ((E_ARRAY == m_eType || E_NULL == m_eType) && E_ARRAY == jv.type()) - { - size_t count = this->size(); - for (size_t i = 0; i < jv.size(); i++) - { - (*this)[count] = jv[i]; - count++; - } - return true; - } - - return false; -} - -bool JValue::append(JValue &jv) -{ - if (E_ARRAY == m_eType || E_NULL == m_eType) - { - (*this)[((this->size() > 0) ? this->size() : 0)] = jv; - return true; - } - return false; -} - -bool JValue::push_back(int val) -{ - return push_back(JValue(val)); -} - -bool JValue::push_back(bool val) -{ - return push_back(JValue(val)); -} - -bool JValue::push_back(double val) -{ - return push_back(JValue(val)); -} - -bool JValue::push_back(int64_t val) -{ - return push_back(JValue(val)); -} - -bool JValue::push_back(const char *val) -{ - return push_back(JValue(val)); -} - -bool JValue::push_back(const string &val) -{ - return push_back(JValue(val)); -} - -bool JValue::push_back(const JValue &jval) -{ - if (E_ARRAY == m_eType || E_NULL == m_eType) - { - (*this)[size()] = jval; - return true; - } - return false; -} - -bool JValue::push_back(const char *val, size_t len) -{ - return push_back(JValue(val, len)); -} - -std::string JValue::styleWrite() const -{ - string strDoc; - return styleWrite(strDoc); -} - -const char *JValue::styleWrite(string &strDoc) const -{ - strDoc.clear(); - JWriter jw; - strDoc = jw.StyleWrite(*this); - return strDoc.c_str(); -} - -void JValue::assignDate(time_t val) -{ - Free(); - m_eType = E_DATE; - m_Value.vDate = val; -} - -void JValue::assignData(const char *val, size_t size) -{ - Free(); - m_eType = E_DATA; - m_Value.vData = new string(); - m_Value.vData->append(val, size); -} - -void JValue::assignDateString(time_t val) -{ - Free(); - m_eType = E_STRING; - m_Value.vString = NewString(JWriter::d2s(val).c_str()); -} - -time_t JValue::asDate() const -{ - switch (m_eType) - { - case E_DATE: - return m_Value.vDate; - break; - case E_STRING: - { - if (isDateString()) - { - tm ft = {0}; - sscanf(m_Value.vString + 5, "%04d-%02d-%02dT%02d:%02d:%02dZ", &ft.tm_year, &ft.tm_mon, &ft.tm_mday, &ft.tm_hour, &ft.tm_min, &ft.tm_sec); - ft.tm_mon -= 1; - ft.tm_year -= 1900; - return mktime(&ft); - } - } - break; - default: - break; - } - return 0; -} - -string JValue::asData() const -{ - switch (m_eType) - { - case E_DATA: - return (NULL == m_Value.vData) ? nullData : *m_Value.vData; - break; - case E_STRING: - { - if (isDataString()) - { - ZBase64 b64; - int nDataLen = 0; - const char *pdata = b64.Decode(m_Value.vString + 5, 0, &nDataLen); - string strdata; - strdata.append(pdata, nDataLen); - return strdata; - } - } - break; - default: - break; - } - - return nullData; -} - -bool JValue::isData() const -{ - return (E_DATA == m_eType); -} - -bool JValue::isDate() const -{ - return (E_DATE == m_eType); -} - -bool JValue::isDataString() const -{ - if (E_STRING == m_eType) - { - if (NULL != m_Value.vString) - { - if (strlen(m_Value.vString) >= 5) - { - if (0 == memcmp(m_Value.vString, "data:", 5)) - { - return true; - } - } - } - } - - return false; -} - -bool JValue::isDateString() const -{ - if (E_STRING == m_eType) - { - if (NULL != m_Value.vString) - { - if (25 == strlen(m_Value.vString)) - { - if (0 == memcmp(m_Value.vString, "date:", 5)) - { - const char *pdate = m_Value.vString + 5; - if ('T' == pdate[10] && 'Z' == pdate[19]) - { - return true; - } - } - } - } - } - - return false; -} - -bool JValue::readPList(const string &strdoc, string *pstrerr /*= NULL*/) -{ - return readPList(strdoc.data(), strdoc.size(), pstrerr); -} - -bool JValue::readPList(const char *pdoc, size_t len /*= 0*/, string *pstrerr /*= NULL*/) -{ - if (NULL == pdoc) - { - return false; - } - - if (0 == len) - { - len = strlen(pdoc); - } - - PReader reader; - bool bret = reader.parse(pdoc, len, *this); - if (!bret) - { - if (NULL != pstrerr) - { - reader.error(*pstrerr); - } - } - - return bret; -} - -bool JValue::readFile(const char *file, string *pstrerr /*= NULL*/) -{ - if (NULL != file) - { - FILE *fp = fopen(file, "rb"); - if (NULL != fp) - { - string strdata; - struct stat stbuf; - if (0 == fstat(fileno(fp), &stbuf)) - { - if (S_ISREG(stbuf.st_mode)) - { - strdata.reserve(stbuf.st_size); - } - } - - char buf[4096] = {0}; - int nread = (int)fread(buf, 1, 4096, fp); - while (nread > 0) - { - strdata.append(buf, nread); - nread = (int)fread(buf, 1, 4096, fp); - } - fclose(fp); - return read(strdata, pstrerr); - } - } - - return false; -} - -bool JValue::readPListFile(const char *file, string *pstrerr /*= NULL*/) -{ - if (NULL != file) - { - FILE *fp = fopen(file, "rb"); - if (NULL != fp) - { - string strdata; - struct stat stbuf; - if (0 == fstat(fileno(fp), &stbuf)) - { - if (S_ISREG(stbuf.st_mode)) - { - strdata.reserve(stbuf.st_size); - } - } - - char buf[4096] = {0}; - int nread = (int)fread(buf, 1, 4096, fp); - while (nread > 0) - { - strdata.append(buf, nread); - nread = (int)fread(buf, 1, 4096, fp); - } - fclose(fp); - return readPList(strdata, pstrerr); - } - } - - return false; -} - -bool JValue::WriteDataToFile(const char *file, const char *data, size_t len) -{ - if (NULL == file || NULL == data || len <= 0) - { - return false; - } - - FILE *fp = fopen(file, "wb"); - if (NULL != fp) - { - int towrite = (int)len; - while (towrite > 0) - { - int nwrite = (int)fwrite(data + (len - towrite), 1, towrite, fp); - if (nwrite <= 0) - { - break; - } - towrite -= nwrite; - } - - fclose(fp); - return (towrite > 0) ? false : true; - } - - return false; -} - -bool JValue::writeFile(const char *file) -{ - string strdata; - write(strdata); - return WriteDataToFile(file, strdata.data(), strdata.size()); -} - -bool JValue::writePListFile(const char *file) -{ - string strdata; - writePList(strdata); - return WriteDataToFile(file, strdata.data(), strdata.size()); -} - -bool JValue::styleWriteFile(const char *file) -{ - string strdata; - styleWrite(strdata); - return WriteDataToFile(file, strdata.data(), strdata.size()); -} - -bool JValue::readPath(const char *path, ...) -{ - char file[1024] = {0}; - va_list args; - va_start(args, path); - vsnprintf(file, 1024, path, args); - va_end(args); - - return readFile(file); -} - -bool JValue::readPListPath(const char *path, ...) -{ - char file[1024] = {0}; - va_list args; - va_start(args, path); - vsnprintf(file, 1024, path, args); - va_end(args); - - return readPListFile(file); -} - -bool JValue::writePath(const char *path, ...) -{ - char file[1024] = {0}; - va_list args; - va_start(args, path); - vsnprintf(file, 1024, path, args); - va_end(args); - - return writeFile(file); -} - -bool JValue::writePListPath(const char *path, ...) -{ - char file[1024] = {0}; - va_list args; - va_start(args, path); - vsnprintf(file, 1024, path, args); - va_end(args); - - return writePListFile(file); -} - -bool JValue::styleWritePath(const char *path, ...) -{ - char file[1024] = {0}; - va_list args; - va_start(args, path); - vsnprintf(file, 1024, path, args); - va_end(args); - - return styleWriteFile(file); -} - -string JValue::writePList() const -{ - string strDoc; - return writePList(strDoc); -} - -const char *JValue::writePList(string &strDoc) const -{ - strDoc.clear(); - PWriter::FastWrite((*this), strDoc); - return strDoc.c_str(); -} - -// Class Reader -// ////////////////////////////////////////////////////////////////// -bool JReader::parse(const char *pdoc, JValue &root) -{ - root.clear(); - if (NULL != pdoc) - { - m_pBeg = pdoc; - m_pEnd = m_pBeg + strlen(pdoc); - m_pCur = m_pBeg; - m_pErr = m_pBeg; - m_strErr = "null"; - return readValue(root); - } - return false; -} - -bool JReader::readValue(JValue &jval) -{ - Token token; - readToken(token); - switch (token.type) - { - case Token::E_True: - jval = true; - break; - case Token::E_False: - jval = false; - break; - case Token::E_Null: - jval = JValue(); - break; - case Token::E_Number: - return decodeNumber(token, jval); - break; - case Token::E_ArrayBegin: - return readArray(jval); - break; - case Token::E_ObjectBegin: - return readObject(jval); - break; - case Token::E_String: - { - string strval; - bool bok = decodeString(token, strval); - if (bok) - { - jval = strval.c_str(); - } - return bok; - } - break; - default: - return addError("Syntax error: value, object or array expected.", token.pbeg); - break; - } - return true; -} - -bool JReader::readToken(Token &token) -{ - skipSpaces(); - token.pbeg = m_pCur; - switch (GetNextChar()) - { - case '{': - token.type = Token::E_ObjectBegin; - break; - case '}': - token.type = Token::E_ObjectEnd; - break; - case '[': - token.type = Token::E_ArrayBegin; - break; - case ']': - token.type = Token::E_ArrayEnd; - break; - case ',': - token.type = Token::E_ArraySeparator; - break; - case ':': - token.type = Token::E_MemberSeparator; - break; - case 0: - token.type = Token::E_End; - break; - case '"': - token.type = readString() ? Token::E_String : Token::E_Error; - break; - case '/': - case '#': - case ';': - { - skipComment(); - return readToken(token); - } - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - { - token.type = Token::E_Number; - readNumber(); - } - break; - case 't': - token.type = match("rue", 3) ? Token::E_True : Token::E_Error; - break; - case 'f': - token.type = match("alse", 4) ? Token::E_False : Token::E_Error; - break; - case 'n': - token.type = match("ull", 3) ? Token::E_Null : Token::E_Error; - break; - default: - token.type = Token::E_Error; - break; - } - token.pend = m_pCur; - return true; -} - -void JReader::skipSpaces() -{ - while (m_pCur != m_pEnd) - { - char c = *m_pCur; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - { - m_pCur++; - } - else - { - break; - } - } -} - -bool JReader::match(const char *pattern, int patternLength) -{ - if (m_pEnd - m_pCur < patternLength) - { - return false; - } - int index = patternLength; - while (index--) - { - if (m_pCur[index] != pattern[index]) - { - return false; - } - } - m_pCur += patternLength; - return true; -} - -void JReader::skipComment() -{ - char c = GetNextChar(); - if (c == '*') - { - while (m_pCur != m_pEnd) - { - char c = GetNextChar(); - if (c == '*' && *m_pCur == '/') - { - break; - } - } - } - else if (c == '/') - { - while (m_pCur != m_pEnd) - { - char c = GetNextChar(); - if (c == '\r' || c == '\n') - { - break; - } - } - } -} - -void JReader::readNumber() -{ - while (m_pCur != m_pEnd) - { - char c = *m_pCur; - if ((c >= '0' && c <= '9') || (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) - { - ++m_pCur; - } - else - { - break; - } - } -} - -bool JReader::readString() -{ - char c = 0; - while (m_pCur != m_pEnd) - { - c = GetNextChar(); - if ('\\' == c) - { - GetNextChar(); - } - else if ('"' == c) - { - break; - } - } - return ('"' == c); -} - -bool JReader::readObject(JValue &jval) -{ - string name; - Token tokenName; - jval = JValue(JValue::E_OBJECT); - while (readToken(tokenName)) - { - if (Token::E_ObjectEnd == tokenName.type) - { //empty - return true; - } - - if (Token::E_String != tokenName.type) - { - break; - } - - if (!decodeString(tokenName, name)) - { - return false; - } - - Token colon; - readToken(colon); - if (Token::E_MemberSeparator != colon.type) - { - return addError("Missing ':' after object member name", colon.pbeg); - } - - if (!readValue(jval[name.c_str()])) - { // error already set - return false; - } - - Token comma; - readToken(comma); - if (Token::E_ObjectEnd == comma.type) - { - return true; - } - - if (Token::E_ArraySeparator != comma.type) - { - return addError("Missing ',' or '}' in object declaration", comma.pbeg); - } - } - return addError("Missing '}' or object member name", tokenName.pbeg); -} - -bool JReader::readArray(JValue &jval) -{ - jval = JValue(JValue::E_ARRAY); - skipSpaces(); - if (']' == *m_pCur) // empty array - { - Token endArray; - readToken(endArray); - return true; - } - - size_t index = 0; - while (true) - { - if (!readValue(jval[index++])) - { //error already set - return false; - } - - Token token; - readToken(token); - if (Token::E_ArrayEnd == token.type) - { - break; - } - if (Token::E_ArraySeparator != token.type) - { - return addError("Missing ',' or ']' in array declaration", token.pbeg); - } - } - return true; -} - -bool JReader::decodeNumber(Token &token, JValue &jval) -{ - int64_t val = 0; - bool isNeg = false; - const char *pcur = token.pbeg; - if ('-' == *pcur) - { - pcur++; - isNeg = true; - } - for (const char *p = pcur; p != token.pend; p++) - { - char c = *p; - if ('.' == c || 'e' == c || 'E' == c) - { - return decodeDouble(token, jval); - } - else if (c < '0' || c > '9') - { - return addError("'" + string(token.pbeg, token.pend) + "' is not a number.", token.pbeg); - } - else - { - val = val * 10 + (c - '0'); - } - } - jval = isNeg ? -val : val; - return true; -} - -bool JReader::decodeDouble(Token &token, JValue &jval) -{ - const size_t szbuf = 512; - size_t len = size_t(token.pend - token.pbeg); - if (len <= szbuf) - { - char buf[szbuf]; - memcpy(buf, token.pbeg, len); - buf[len] = 0; - double val = 0; - if (1 == sscanf(buf, "%lf", &val)) - { - jval = val; - return true; - } - } - return addError("'" + string(token.pbeg, token.pend) + "' is too large or not a number.", token.pbeg); -} - -bool JReader::decodeString(Token &token, string &strdec) -{ - strdec = ""; - const char *pcur = token.pbeg + 1; - const char *pend = token.pend - 1; - strdec.reserve(size_t(token.pend - token.pbeg)); - while (pcur != pend) - { - char c = *pcur++; - if ('\\' == c) - { - if (pcur != pend) - { - char escape = *pcur++; - switch (escape) - { - case '"': - strdec += '"'; - break; - case '\\': - strdec += '\\'; - break; - case 'b': - strdec += '\b'; - break; - case 'f': - strdec += '\f'; - break; - case 'n': - strdec += '\n'; - break; - case 'r': - strdec += '\r'; - break; - case 't': - strdec += '\t'; - break; - case '/': - strdec += '/'; - break; - case 'u': - { // based on description from http://en.wikipedia.org/wiki/UTF-8 - - string strUnic; - strUnic.append(pcur, 4); - - pcur += 4; - - unsigned int cp = 0; - if (1 != sscanf(strUnic.c_str(), "%x", &cp)) - { - return addError("Bad escape sequence in string", pcur); - } - - string strUTF8; - - if (cp <= 0x7f) - { - strUTF8.resize(1); - strUTF8[0] = static_cast(cp); - } - else if (cp <= 0x7FF) - { - strUTF8.resize(2); - strUTF8[1] = static_cast(0x80 | (0x3f & cp)); - strUTF8[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); - } - else if (cp <= 0xFFFF) - { - strUTF8.resize(3); - strUTF8[2] = static_cast(0x80 | (0x3f & cp)); - strUTF8[1] = 0x80 | static_cast((0x3f & (cp >> 6))); - strUTF8[0] = 0xE0 | static_cast((0xf & (cp >> 12))); - } - else if (cp <= 0x10FFFF) - { - strUTF8.resize(4); - strUTF8[3] = static_cast(0x80 | (0x3f & cp)); - strUTF8[2] = static_cast(0x80 | (0x3f & (cp >> 6))); - strUTF8[1] = static_cast(0x80 | (0x3f & (cp >> 12))); - strUTF8[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); - } - - strdec += strUTF8; - } - break; - default: - return addError("Bad escape sequence in string", pcur); - break; - } - } - else - { - return addError("Empty escape sequence in string", pcur); - } - } - else if ('"' == c) - { - break; - } - else - { - strdec += c; - } - } - return true; -} - -bool JReader::addError(const string &message, const char *ploc) -{ - m_pErr = ploc; - m_strErr = message; - return false; -} - -char JReader::GetNextChar() -{ - return (m_pCur == m_pEnd) ? 0 : *m_pCur++; -} - -void JReader::error(string &strmsg) const -{ - strmsg = ""; - int row = 1; - const char *pcur = m_pBeg; - const char *plast = m_pBeg; - while (pcur < m_pErr && pcur <= m_pEnd) - { - char c = *pcur++; - if (c == '\r' || c == '\n') - { - if (c == '\r' && *pcur == '\n') - { - pcur++; - } - row++; - plast = pcur; - } - } - char msg[64]; - sprintf(msg, "Error: Line %d, Column %d, ", row, int(m_pErr - plast) + 1); - strmsg += msg + m_strErr + "\n"; -} - -// Class Writer -// ////////////////////////////////////////////////////////////////// -void JWriter::FastWrite(const JValue &jval, string &strDoc) -{ - strDoc = ""; - FastWriteValue(jval, strDoc); - //strDoc += "\n"; -} - -void JWriter::FastWriteValue(const JValue &jval, string &strDoc) -{ - switch (jval.type()) - { - case JValue::E_NULL: - strDoc += "null"; - break; - case JValue::E_INT: - strDoc += v2s(jval.asInt64()); - break; - case JValue::E_BOOL: - strDoc += jval.asBool() ? "true" : "false"; - break; - case JValue::E_FLOAT: - strDoc += v2s(jval.asFloat()); - break; - case JValue::E_STRING: - strDoc += v2s(jval.asCString()); - break; - case JValue::E_ARRAY: - { - strDoc += "["; - size_t usize = jval.size(); - for (size_t i = 0; i < usize; i++) - { - strDoc += (i > 0) ? "," : ""; - FastWriteValue(jval[i], strDoc); - } - strDoc += "]"; - } - break; - case JValue::E_OBJECT: - { - strDoc += "{"; - vector arrKeys; - jval.keys(arrKeys); - size_t usize = arrKeys.size(); - for (size_t i = 0; i < usize; i++) - { - const string &name = arrKeys[i]; - strDoc += (i > 0) ? "," : ""; - strDoc += v2s(name.c_str()) + ":"; - FastWriteValue(jval[name.c_str()], strDoc); - } - strDoc += "}"; - } - break; - case JValue::E_DATE: - { - strDoc += "\"date:"; - strDoc += d2s(jval.asDate()); - strDoc += "\""; - } - break; - case JValue::E_DATA: - { - strDoc += "\"data:"; - const string &strData = jval.asData(); - ZBase64 b64; - strDoc += b64.Encode(strData.data(), (int)strData.size()); - strDoc += "\""; - } - break; - } -} - -const string &JWriter::StyleWrite(const JValue &jval) -{ - m_strDoc = ""; - m_strTab = ""; - m_bAddChild = false; - StyleWriteValue(jval); - m_strDoc += "\n"; - return m_strDoc; -} - -void JWriter::StyleWriteValue(const JValue &jval) -{ - switch (jval.type()) - { - case JValue::E_NULL: - PushValue("null"); - break; - case JValue::E_INT: - PushValue(v2s(jval.asInt64())); - break; - case JValue::E_BOOL: - PushValue(jval.asBool() ? "true" : "false"); - break; - case JValue::E_FLOAT: - PushValue(v2s(jval.asFloat())); - break; - case JValue::E_STRING: - PushValue(v2s(jval.asCString())); - break; - case JValue::E_ARRAY: - StyleWriteArrayValue(jval); - break; - case JValue::E_OBJECT: - { - vector arrKeys; - jval.keys(arrKeys); - if (!arrKeys.empty()) - { - m_strDoc += '\n' + m_strTab + "{"; - m_strTab += '\t'; - size_t usize = arrKeys.size(); - for (size_t i = 0; i < usize; i++) - { - const string &name = arrKeys[i]; - m_strDoc += (i > 0) ? "," : ""; - m_strDoc += '\n' + m_strTab + v2s(name.c_str()) + " : "; - StyleWriteValue(jval[name]); - } - m_strTab.resize(m_strTab.size() - 1); - m_strDoc += '\n' + m_strTab + "}"; - } - else - { - PushValue("{}"); - } - } - break; - case JValue::E_DATE: - { - string strDoc; - strDoc += "\"date:"; - strDoc += d2s(jval.asDate()); - strDoc += "\""; - PushValue(strDoc); - } - break; - case JValue::E_DATA: - { - string strDoc; - strDoc += "\"data:"; - const string &strData = jval.asData(); - ZBase64 b64; - strDoc += b64.Encode(strData.data(), (int)strData.size()); - strDoc += "\""; - PushValue(strDoc); - } - break; - } -} - -void JWriter::StyleWriteArrayValue(const JValue &jval) -{ - size_t usize = jval.size(); - if (usize > 0) - { - bool isArrayMultiLine = isMultineArray(jval); - if (isArrayMultiLine) - { - m_strDoc += '\n' + m_strTab + "["; - m_strTab += '\t'; - bool hasChildValue = !m_childValues.empty(); - for (size_t i = 0; i < usize; i++) - { - m_strDoc += (i > 0) ? "," : ""; - if (hasChildValue) - { - m_strDoc += '\n' + m_strTab + m_childValues[i]; - } - else - { - m_strDoc += '\n' + m_strTab; - StyleWriteValue(jval[i]); - } - } - m_strTab.resize(m_strTab.size() - 1); - m_strDoc += '\n' + m_strTab + "]"; - } - else - { - m_strDoc += "[ "; - for (size_t i = 0; i < usize; ++i) - { - m_strDoc += (i > 0) ? ", " : ""; - m_strDoc += m_childValues[i]; - } - m_strDoc += " ]"; - } - } - else - { - PushValue("[]"); - } -} - -bool JWriter::isMultineArray(const JValue &jval) -{ - m_childValues.clear(); - size_t usize = jval.size(); - bool isMultiLine = (usize >= 25); - if (!isMultiLine) - { - for (size_t i = 0; i < usize; i++) - { - if (jval[i].size() > 0) - { - isMultiLine = true; - break; - } - } - } - if (!isMultiLine) - { - m_bAddChild = true; - m_childValues.reserve(usize); - size_t lineLength = 4 + (usize - 1) * 2; // '[ ' + ', '*n + ' ]' - for (size_t i = 0; i < usize; i++) - { - StyleWriteValue(jval[i]); - lineLength += m_childValues[i].length(); - } - m_bAddChild = false; - isMultiLine = lineLength >= 75; - } - return isMultiLine; -} - -void JWriter::PushValue(const string &strval) -{ - if (!m_bAddChild) - { - m_strDoc += strval; - } - else - { - m_childValues.push_back(strval); - } -} - -string JWriter::v2s(int64_t val) -{ - char buf[32]; - sprintf(buf, "%" PRId64, val); - return buf; -} - -string JWriter::v2s(double val) -{ - char buf[512]; - sprintf(buf, "%g", val); - return buf; -} - -string JWriter::d2s(time_t t) -{ - //t = (t > 0x7933F8EFF) ? (0x7933F8EFF - 1) : t; - - tm ft = {0}; - -#ifdef _WIN32 - localtime_s(&ft, &t); -#else - localtime_r(&t, &ft); -#endif - - ft.tm_year = (ft.tm_year < 0) ? 0 : ft.tm_year; - ft.tm_mon = (ft.tm_mon < 0) ? 0 : ft.tm_mon; - ft.tm_mday = (ft.tm_mday < 0) ? 0 : ft.tm_mday; - ft.tm_hour = (ft.tm_hour < 0) ? 0 : ft.tm_hour; - ft.tm_min = (ft.tm_min < 0) ? 0 : ft.tm_min; - ft.tm_sec = (ft.tm_sec < 0) ? 0 : ft.tm_sec; - - char szDate[64] = {0}; - sprintf(szDate, "%04d-%02d-%02dT%02d:%02d:%02dZ", ft.tm_year + 1900, ft.tm_mon + 1, ft.tm_mday, ft.tm_hour, ft.tm_min, ft.tm_sec); - return szDate; -} - -string JWriter::v2s(const char *pstr) -{ - if (NULL != strpbrk(pstr, "\"\\\b\f\n\r\t")) - { - string ret; - ret.reserve(strlen(pstr) * 2 + 3); - ret += "\""; - for (const char *c = pstr; 0 != *c; c++) - { - switch (*c) - { - case '\\': - { - c++; - bool bUnicode = false; - if ('u' == *c) - { - bool bFlag = true; - for (int i = 1; i <= 4; i++) - { - if (!isdigit(*(c + i))) - { - bFlag = false; - break; - } - } - bUnicode = bFlag; - } - - if (true == bUnicode) - { - ret += "\\u"; - } - else - { - ret += "\\\\"; - c--; - } - } - break; - case '\"': - ret += "\\\""; - break; - case '\b': - ret += "\\b"; - break; - case '\f': - ret += "\\f"; - break; - case '\n': - ret += "\\n"; - break; - case '\r': - ret += "\\r"; - break; - case '\t': - ret += "\\t"; - break; - default: - ret += *c; - break; - } - } - ret += "\""; - return ret; - } - else - { - return string("\"") + pstr + "\""; - } -} - -std::string JWriter::vstring2s(const char *pstr) -{ - return string("\\\"") + pstr + "\\\""; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -#define BE16TOH(x) ((((x)&0xFF00) >> 8) | (((x)&0x00FF) << 8)) - -#define BE32TOH(x) ((((x)&0xFF000000) >> 24) | (((x)&0x00FF0000) >> 8) | (((x)&0x0000FF00) << 8) | (((x)&0x000000FF) << 24)) - -#define BE64TOH(x) ((((x)&0xFF00000000000000ull) >> 56) | (((x)&0x00FF000000000000ull) >> 40) | (((x)&0x0000FF0000000000ull) >> 24) | (((x)&0x000000FF00000000ull) >> 8) | (((x)&0x00000000FF000000ull) << 8) | (((x)&0x0000000000FF0000ull) << 24) | (((x)&0x000000000000FF00ull) << 40) | (((x)&0x00000000000000FFull) << 56)) - -////////////////////////////////////////////////////////////////////////// -PReader::PReader() -{ - //xml - m_pBeg = NULL; - m_pEnd = NULL; - m_pCur = NULL; - m_pErr = NULL; - - //binary - m_pTrailer = NULL; - m_uObjects = 0; - m_uOffsetSize = 0; - m_pOffsetTable = 0; - m_uDictParamSize = 0; -} - -bool PReader::parse(const char *pdoc, size_t len, JValue &root) -{ - root.clear(); - if (NULL == pdoc) - { - return false; - } - - if (len < 30) - { - return false; - } - - if (0 == memcmp(pdoc, "bplist00", 8)) - { - return parseBinary(pdoc, len, root); - } - else - { - m_pBeg = pdoc; - m_pEnd = m_pBeg + len; - m_pCur = m_pBeg; - m_pErr = m_pBeg; - m_strErr = "null"; - - Token token; - readToken(token); - return readValue(root, token); - } -} - -bool PReader::readValue(JValue &pval, Token &token) -{ - switch (token.type) - { - case Token::E_True: - pval = true; - break; - case Token::E_False: - pval = false; - break; - case Token::E_Null: - pval = JValue(); - break; - case Token::E_Integer: - return decodeNumber(token, pval); - break; - case Token::E_Real: - return decodeDouble(token, pval); - break; - case Token::E_ArrayNull: - pval = JValue(JValue::E_ARRAY); - break; - case Token::E_ArrayBegin: - return readArray(pval); - break; - case Token::E_DictionaryNull: - pval = JValue(JValue::E_OBJECT); - break; - case Token::E_DictionaryBegin: - return readDictionary(pval); - break; - case Token::E_Date: - { - string strval; - decodeString(token, strval); - - tm ft = {0}; - sscanf(strval.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &ft.tm_year, &ft.tm_mon, &ft.tm_mday, &ft.tm_hour, &ft.tm_min, &ft.tm_sec); - ft.tm_mon -= 1; - ft.tm_year -= 1900; - pval.assignDate(mktime(&ft)); - } - break; - case Token::E_Data: - { - string strval; - decodeString(token, strval); - - ZBase64 b64; - int nDecLen = 0; - const char *data = b64.Decode(strval.data(), (int)strval.size(), &nDecLen); - pval.assignData(data, nDecLen); - } - break; - case Token::E_String: - { - string strval; - decodeString(token, strval, false); - XMLUnescape(strval); - pval = strval.c_str(); - } - break; - default: - return addError("Syntax error: value, dictionary or array expected.", token.pbeg); - break; - } - return true; -} - -bool PReader::readLabel(string &label) -{ - skipSpaces(); - - char c = *m_pCur++; - if ('<' != c) - { - return false; - } - - label.clear(); - label.reserve(10); - label += c; - - bool bEnd = false; - while (m_pCur != m_pEnd) - { - c = *m_pCur++; - if ('>' == c) - { - if ('/' == *(m_pCur - 1) || '?' == *(m_pCur - 1)) - { - label += *(m_pCur - 1); - } - - label += c; - break; - } - else if (' ' == c) - { - bEnd = true; - } - else if (!bEnd) - { - label += c; - } - } - - if ('>' != c) - { - label.clear(); - return false; - } - - return (!label.empty()); -} - -void PReader::endLabel(Token &token, const char *szLabel) -{ - string label; - readLabel(label); - if (szLabel != label) - { - token.type = Token::E_Error; - } -} - -bool PReader::readToken(Token &token) -{ - string label; - if (!readLabel(label)) - { - token.type = Token::E_Error; - return false; - } - - if ('?' == label.at(1) || '!' == label.at(1)) - { - return readToken(token); - } - - if ("" == label) - { - token.type = Token::E_DictionaryBegin; - } - else if ("" == label) - { - token.type = Token::E_DictionaryEnd; - } - else if ("" == label) - { - token.type = Token::E_ArrayBegin; - } - else if ("" == label) - { - token.type = Token::E_ArrayEnd; - } - else if ("" == label) - { - token.pbeg = m_pCur; - token.type = readString() ? Token::E_Key : Token::E_Error; - token.pend = m_pCur; - - endLabel(token, ""); - } - else if ("" == label) - { - token.type = Token::E_Key; - } - else if ("" == label) - { - token.pbeg = m_pCur; - token.type = readString() ? Token::E_String : Token::E_Error; - token.pend = m_pCur; - - endLabel(token, ""); - } - else if ("" == label) - { - token.pbeg = m_pCur; - token.type = readString() ? Token::E_Date : Token::E_Error; - token.pend = m_pCur; - - endLabel(token, ""); - } - else if ("" == label) - { - token.pbeg = m_pCur; - token.type = readString() ? Token::E_Data : Token::E_Error; - token.pend = m_pCur; - - endLabel(token, ""); - } - else if ("" == label) - { - token.pbeg = m_pCur; - token.type = readNumber() ? Token::E_Integer : Token::E_Error; - token.pend = m_pCur; - - endLabel(token, ""); - } - else if ("" == label) - { - token.pbeg = m_pCur; - token.type = readNumber() ? Token::E_Real : Token::E_Error; - token.pend = m_pCur; - - endLabel(token, ""); - } - else if ("" == label) - { - token.type = Token::E_True; - } - else if ("" == label) - { - token.type = Token::E_False; - } - else if ("" == label) - { - token.type = Token::E_ArrayNull; - } - else if ("" == label) - { - token.type = Token::E_DictionaryNull; - } - else if ("" == label || "" == label || "" == label || "" == label || "" == label) - { - token.type = Token::E_Null; - } - else if ("" == label) - { - return readToken(token); - } - else if ("" == label || "" == label) - { - token.type = Token::E_End; - } - else - { - token.type = Token::E_Error; - } - - return true; -} - -void PReader::skipSpaces() -{ - while (m_pCur != m_pEnd) - { - char c = *m_pCur; - if (c == ' ' || c == '\t' || c == '\r' || c == '\n') - { - m_pCur++; - } - else - { - break; - } - } -} - -bool PReader::readNumber() -{ - while (m_pCur != m_pEnd) - { - char c = *m_pCur; - if ((c >= '0' && c <= '9') || (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) - { - ++m_pCur; - } - else - { - break; - } - } - return true; -} - -bool PReader::readString() -{ - while (m_pCur != m_pEnd) - { - if ('<' == *m_pCur) - { - break; - } - m_pCur++; - } - return ('<' == *m_pCur); -} - -bool PReader::readDictionary(JValue &pval) -{ - Token key; - string strKey; - pval = JValue(JValue::E_OBJECT); - while (readToken(key)) - { - if (Token::E_DictionaryEnd == key.type) - { //empty - return true; - } - - if (Token::E_Key != key.type) - { - break; - } - - strKey = ""; - if (!decodeString(key, strKey)) - { - return false; - } - XMLUnescape(strKey); - - Token val; - readToken(val); - if (!readValue(pval[strKey.c_str()], val)) - { - return false; - } - } - return addError("Missing '' or dictionary member name", key.pbeg); -} - -bool PReader::readArray(JValue &pval) -{ - pval = JValue(JValue::E_ARRAY); - - size_t index = 0; - while (true) - { - Token token; - readToken(token); - if (Token::E_ArrayEnd == token.type) - { - return true; - } - - if (!readValue(pval[index++], token)) - { - return false; - } - } - - return true; -} - -bool PReader::decodeNumber(Token &token, JValue &pval) -{ - int64_t val = 0; - bool isNeg = false; - const char *pcur = token.pbeg; - if ('-' == *pcur) - { - pcur++; - isNeg = true; - } - for (const char *p = pcur; p != token.pend; p++) - { - char c = *p; - if ('.' == c || 'e' == c || 'E' == c) - { - return decodeDouble(token, pval); - } - else if (c < '0' || c > '9') - { - return addError("'" + string(token.pbeg, token.pend) + "' is not a number.", token.pbeg); - } - else - { - val = val * 10 + (c - '0'); - } - } - pval = isNeg ? -val : val; - return true; -} - -bool PReader::decodeDouble(Token &token, JValue &pval) -{ - const size_t szbuf = 512; - size_t len = size_t(token.pend - token.pbeg); - if (len <= szbuf) - { - char buf[szbuf]; - memcpy(buf, token.pbeg, len); - buf[len] = 0; - double val = 0; - if (1 == sscanf(buf, "%lf", &val)) - { - pval = val; - return true; - } - } - return addError("'" + string(token.pbeg, token.pend) + "' is too large or not a number.", token.pbeg); -} - -bool PReader::decodeString(Token &token, string &strdec, bool filter) -{ - const char *pcur = token.pbeg; - const char *pend = token.pend; - strdec.reserve(size_t(token.pend - token.pbeg) + 6); - while (pcur != pend) - { - char c = *pcur++; - if (filter && ('\n' == c || '\r' == c || '\t' == c)) - { - continue; - } - strdec += c; - } - return true; -} - -bool PReader::addError(const string &message, const char *ploc) -{ - m_pErr = ploc; - m_strErr = message; - return false; -} - -void PReader::error(string &strmsg) const -{ - strmsg = ""; - int row = 1; - const char *pcur = m_pBeg; - const char *plast = m_pBeg; - while (pcur < m_pErr && pcur <= m_pEnd) - { - char c = *pcur++; - if (c == '\r' || c == '\n') - { - if (c == '\r' && *pcur == '\n') - { - pcur++; - } - row++; - plast = pcur; - } - } - char msg[64]; - sprintf(msg, "Error: Line %d, Column %d, ", row, int(m_pErr - plast) + 1); - strmsg += msg + m_strErr + "\n"; -} - -////////////////////////////////////////////////////////////////////////// -uint32_t PReader::getUInt24FromBE(const char *v) -{ - uint32_t ret = 0; - uint8_t *tmp = (uint8_t *)&ret; - memcpy(tmp, v, 3 * sizeof(char)); - byteConvert(tmp, sizeof(uint32_t)); - return ret; -} - -uint64_t PReader::getUIntVal(const char *v, size_t size) -{ - if (8 == size) - return BE64TOH(*((uint64_t *)v)); - else if (4 == size) - return BE32TOH(*((uint32_t *)v)); - else if (3 == size) - return getUInt24FromBE(v); - else if (2 == size) - return BE16TOH(*((uint16_t *)v)); - else - return *((uint8_t *)v); -} - -void PReader::byteConvert(uint8_t *v, size_t size) -{ - uint8_t tmp = 0; - for (size_t i = 0, j = 0; i < (size / 2); i++) - { - tmp = v[i]; - j = (size - 1) - i; - v[i] = v[j]; - v[j] = tmp; - } -} - -bool PReader::readUIntSize(const char *&pcur, size_t &size) -{ - JValue temp; - readBinaryValue(pcur, temp); - if (temp.isInt()) - { - size = (size_t)temp.asInt64(); - return true; - } - - assert(0); - return false; -} - -bool PReader::readUnicode(const char *pcur, size_t size, JValue &pv) -{ - if (0 == size) - { - pv = ""; - return false; - } - - uint16_t *unistr = (uint16_t *)malloc(2 * size); - memcpy(unistr, pcur, 2 * size); - for (size_t i = 0; i < size; i++) - { - byteConvert((uint8_t *)(unistr + i), 2); - } - - char *outbuf = (char *)malloc(3 * (size + 1)); - - size_t p = 0; - size_t i = 0; - uint16_t wc = 0; - while (i < size) - { - wc = unistr[i++]; - if (wc >= 0x800) - { - outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF)); - outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F)); - outbuf[p++] = (char)(0x80 + (wc & 0x3F)); - } - else if (wc >= 0x80) - { - outbuf[p++] = (char)(0xC0 + ((wc >> 6) & 0x1F)); - outbuf[p++] = (char)(0x80 + (wc & 0x3F)); - } - else - { - outbuf[p++] = (char)(wc & 0x7F); - } - } - - outbuf[p] = 0; - - pv = outbuf; - - free(outbuf); - outbuf = NULL; - free(unistr); - unistr = NULL; - - return true; -} - -bool PReader::readBinaryValue(const char *&pcur, JValue &pv) -{ - enum - { - BPLIST_NULL = 0x00, - BPLIST_FALSE = 0x08, - BPLIST_TRUE = 0x09, - BPLIST_FILL = 0x0F, - BPLIST_UINT = 0x10, - BPLIST_REAL = 0x20, - BPLIST_DATE = 0x30, - BPLIST_DATA = 0x40, - BPLIST_STRING = 0x50, - BPLIST_UNICODE = 0x60, - BPLIST_UNK_0x70 = 0x70, - BPLIST_UID = 0x80, - BPLIST_ARRAY = 0xA0, - BPLIST_SET = 0xC0, - BPLIST_DICT = 0xD0, - BPLIST_MASK = 0xF0 - }; - - uint8_t c = *pcur++; - uint8_t key = c & 0xF0; - uint8_t val = c & 0x0F; - - switch (key) - { - case BPLIST_NULL: - { - switch (val) - { - case BPLIST_TRUE: - { - pv = true; - } - break; - case BPLIST_FALSE: - { - pv = false; - } - break; - case BPLIST_NULL: - { - } - break; - default: - { - assert(0); - return false; - } - break; - } - } - break; - case BPLIST_UID: - case BPLIST_UINT: - { - size_t size = 1 << val; - switch (size) - { - case sizeof(uint8_t): - case sizeof(uint16_t): - case sizeof(uint32_t): - case sizeof(uint64_t): - { - pv = (int64_t)getUIntVal(pcur, size); - } - break; - default: - { - assert(0); - return false; - } - break; - }; - - pcur += size; - } - break; - case BPLIST_REAL: - { - size_t size = 1 << val; - - uint8_t *buf = (uint8_t *)malloc(size); - memcpy(buf, pcur, size); - byteConvert(buf, size); - - switch (size) - { - case sizeof(float): - pv = (double)(*(float *)buf); - case sizeof(double): - pv = (*(double *)buf); - break; - default: - { - assert(0); - free(buf); - return false; - } - break; - } - - free(buf); - } - break; - - case BPLIST_DATE: - { - if (3 == val) - { - size_t size = 1 << val; - uint8_t *buf = (uint8_t *)malloc(size); - memcpy(buf, pcur, size); - byteConvert(buf, size); - pv.assignDate(((time_t)(*(double *)buf)) + 978278400); - free(buf); - } - else - { - assert(0); - return false; - } - } - break; - - case BPLIST_DATA: - { - size_t size = val; - if (0x0F == val) - { - if (!readUIntSize(pcur, size)) - { - return false; - } - } - pv.assignData(pcur, size); - } - break; - - case BPLIST_STRING: - { - size_t size = val; - if (0x0F == val) - { - if (!readUIntSize(pcur, size)) - { - return false; - } - } - - string strval; - strval.append(pcur, size); - strval.append(1, 0); - pv = strval.c_str(); - } - break; - - case BPLIST_UNICODE: - { - size_t size = val; - if (0x0F == val) - { - if (!readUIntSize(pcur, size)) - { - return false; - } - } - - readUnicode(pcur, size, pv); - } - break; - case BPLIST_ARRAY: - case BPLIST_UNK_0x70: - { - size_t size = val; - if (0x0F == val) - { - if (!readUIntSize(pcur, size)) - { - return false; - } - } - - for (size_t i = 0; i < size; i++) - { - uint64_t uIndex = getUIntVal((const char *)pcur + i * m_uDictParamSize, m_uDictParamSize); - if (uIndex < m_uObjects) - { - const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uIndex * m_uOffsetSize, m_uOffsetSize)); - readBinaryValue(pval, pv[i]); - } - else - { - assert(0); - return false; - } - } - } - break; - - case BPLIST_SET: - case BPLIST_DICT: - { - size_t size = val; - if (0x0F == val) - { - if (!readUIntSize(pcur, size)) - { - return false; - } - } - - for (size_t i = 0; i < size; i++) - { - JValue pvKey; - JValue pvVal; - - uint64_t uKeyIndex = getUIntVal((const char *)pcur + i * m_uDictParamSize, m_uDictParamSize); - uint64_t uValIndex = getUIntVal((const char *)pcur + (i + size) * m_uDictParamSize, m_uDictParamSize); - - if (uKeyIndex < m_uObjects) - { - const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uKeyIndex * m_uOffsetSize, m_uOffsetSize)); - readBinaryValue(pval, pvKey); - } - - if (uValIndex < m_uObjects) - { - const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uValIndex * m_uOffsetSize, m_uOffsetSize)); - readBinaryValue(pval, pvVal); - } - - if (pvKey.isString() && !pvVal.isNull()) - { - pv[pvKey.asCString()] = pvVal; - } - } - } - break; - default: - { - assert(0); - return false; - } - } - - return true; -} - -bool PReader::parseBinary(const char *pbdoc, size_t len, JValue &pv) -{ - m_pBeg = pbdoc; - - m_pTrailer = m_pBeg + len - 26; - - m_uOffsetSize = m_pTrailer[0]; - m_uDictParamSize = m_pTrailer[1]; - m_uObjects = getUIntVal(m_pTrailer + 2, 8); - - if (0 == m_uObjects) - { - return false; - } - - m_pOffsetTable = m_pBeg + getUIntVal(m_pTrailer + 18, 8); - const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable, m_uOffsetSize)); - return readBinaryValue(pval, pv); -} - -void PReader::XMLUnescape(string &strval) -{ - PWriter::StringReplace(strval, "&", "&"); - PWriter::StringReplace(strval, "<", "<"); - //PWriter::StringReplace(strval,">", ">"); //optional - //PWriter::StringReplace(strval, "'", "'"); //optional - //PWriter::StringReplace(strval, """, "\""); //optional -} - -////////////////////////////////////////////////////////////////////////// -void PWriter::FastWrite(const JValue &pval, string &strdoc) -{ - strdoc.clear(); - strdoc = "\n" - "\n" - "\n"; - - string strindent; - FastWriteValue(pval, strdoc, strindent); - - strdoc += ""; -} - -void PWriter::FastWriteValue(const JValue &pval, string &strdoc, string &strindent) -{ - if (pval.isObject()) - { - strdoc += strindent; - if (pval.isEmpty()) - { - strdoc += "\n"; - return; - } - strdoc += "\n"; - vector arrKeys; - if (pval.keys(arrKeys)) - { - strindent.push_back('\t'); - for (size_t i = 0; i < arrKeys.size(); i++) - { - if (!pval[arrKeys[i].c_str()].isNull()) - { - string strkey = arrKeys[i]; - XMLEscape(strkey); - strdoc += strindent; - strdoc += ""; - strdoc += strkey; - strdoc += "\n"; - FastWriteValue(pval[arrKeys[i].c_str()], strdoc, strindent); - } - } - strindent.erase(strindent.end() - 1); - } - strdoc += strindent; - strdoc += "\n"; - } - else if (pval.isArray()) - { - strdoc += strindent; - if (pval.isEmpty()) - { - strdoc += "\n"; - return; - } - strdoc += "\n"; - strindent.push_back('\t'); - for (size_t i = 0; i < pval.size(); i++) - { - FastWriteValue(pval[i], strdoc, strindent); - } - strindent.erase(strindent.end() - 1); - strdoc += strindent; - strdoc += "\n"; - } - else if (pval.isDate()) - { - strdoc += strindent; - strdoc += ""; - strdoc += JWriter::d2s(pval.asDate()); - strdoc += "\n"; - } - else if (pval.isData()) - { - ZBase64 b64; - string strdata = pval.asData(); - strdoc += strindent; - strdoc += "\n"; - strdoc += strindent; - strdoc += b64.Encode(strdata.data(), (int)strdata.size()); - strdoc += "\n"; - strdoc += strindent; - strdoc += "\n"; - } - else if (pval.isString()) - { - strdoc += strindent; - if (pval.isDateString()) - { - strdoc += ""; - strdoc += pval.asString().c_str() + 5; - strdoc += "\n"; - } - else if (pval.isDataString()) - { - strdoc += "\n"; - strdoc += strindent; - strdoc += pval.asString().c_str() + 5; - strdoc += "\n"; - strdoc += strindent; - strdoc += "\n"; - } - else - { - string strval = pval.asCString(); - XMLEscape(strval); - strdoc += ""; - strdoc += strval; - strdoc += "\n"; - } - } - else if (pval.isBool()) - { - strdoc += strindent; - strdoc += (pval.asBool() ? "\n" : "\n"); - } - else if (pval.isInt()) - { - strdoc += strindent; - strdoc += ""; - char temp[32] = {0}; - sprintf(temp, "%" PRId64, pval.asInt64()); - strdoc += temp; - strdoc += "\n"; - } - else if (pval.isFloat()) - { - strdoc += strindent; - strdoc += ""; - - double v = pval.asFloat(); - if (numeric_limits::infinity() == v) - { - strdoc += "+infinity"; - } - else - { - char temp[32] = {0}; - if (floor(v) == v) - { - sprintf(temp, "%" PRId64, (int64_t)v); - } - else - { - sprintf(temp, "%.15lf", v); - } - strdoc += temp; - } - - strdoc += "\n"; - } -} - -void PWriter::XMLEscape(string &strval) -{ - StringReplace(strval, "&", "&"); - StringReplace(strval, "<", "<"); - //StringReplace(strval, ">", ">"); //option - //StringReplace(strval, "'", "'"); //option - //StringReplace(strval, "\"", """); //option -} - -string &PWriter::StringReplace(string &context, const string &from, const string &to) -{ - size_t lookHere = 0; - size_t foundHere; - while ((foundHere = context.find(from, lookHere)) != string::npos) - { - context.replace(foundHere, from.size(), to); - lookHere = foundHere + to.size(); - } - return context; -} diff --git a/zsign/common/json.h b/zsign/common/json.h deleted file mode 100644 index 9ef6dd1..0000000 --- a/zsign/common/json.h +++ /dev/null @@ -1,414 +0,0 @@ -#ifndef JSON_INCLUDED -#define JSON_INCLUDED - -#ifdef _WIN32 - -typedef signed char int8_t; -typedef short int int16_t; -typedef int int32_t; -typedef long long int int64_t; -typedef unsigned char uint8_t; -typedef unsigned short int uint16_t; -typedef unsigned int uint32_t; -typedef unsigned long long int uint64_t; - -#else - -#include -#include -#include - -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; - - - -class JValue -{ -public: - enum TYPE - { - E_NULL = 0, - E_INT, - E_BOOL, - E_FLOAT, - E_ARRAY, - E_OBJECT, - E_STRING, - E_DATE, - E_DATA, - }; - -public: - JValue(TYPE type = E_NULL); - JValue(int val); - JValue(bool val); - JValue(double val); - JValue(int64_t val); - JValue(const char *val); - JValue(const string &val); - JValue(const JValue &other); - JValue(const char *val, size_t len); - ~JValue(); - -public: - int asInt() const; - bool asBool() const; - double asFloat() const; - int64_t asInt64() const; - string asString() const; - const char *asCString() const; - time_t asDate() const; - string asData() const; - - void assignData(const char *val, size_t size); - void assignDate(time_t val); - void assignDateString(time_t val); - - TYPE type() const; - size_t size() const; - void clear(); - - JValue &at(int index); - JValue &at(size_t index); - JValue &at(const char *key); - - bool has(const char *key) const; - int index(const char *ele) const; - bool keys(vector &arrKeys) const; - - bool join(JValue &jv); - bool append(JValue &jv); - - bool remove(int index); - bool remove(size_t index); - bool remove(const char *key); - - JValue &back(); - JValue &front(); - - bool push_back(int val); - bool push_back(bool val); - bool push_back(double val); - bool push_back(int64_t val); - bool push_back(const char *val); - bool push_back(const string &val); - bool push_back(const JValue &jval); - bool push_back(const char *val, size_t len); - - bool isInt() const; - bool isNull() const; - bool isBool() const; - bool isFloat() const; - bool isArray() const; - bool isObject() const; - bool isString() const; - bool isEmpty() const; - bool isData() const; - bool isDate() const; - bool isDataString() const; - bool isDateString() const; - - operator int() const; - operator bool() const; - operator double() const; - operator int64_t() const; - operator string() const; - operator const char *() const; - - JValue &operator=(const JValue &other); - - JValue &operator[](int index); - const JValue &operator[](int index) const; - - JValue &operator[](size_t index); - const JValue &operator[](size_t index) const; - - JValue &operator[](int64_t index); - const JValue &operator[](int64_t index) const; - - JValue &operator[](const char *key); - const JValue &operator[](const char *key) const; - - JValue &operator[](const string &key); - const JValue &operator[](const string &key) const; - - friend bool operator==(const JValue &jv, const char *psz) - { - return (0 == strcmp(jv.asCString(), psz)); - } - - friend bool operator==(const char *psz, const JValue &jv) - { - return (0 == strcmp(jv.asCString(), psz)); - } - - friend bool operator!=(const JValue &jv, const char *psz) - { - return (0 != strcmp(jv.asCString(), psz)); - } - - friend bool operator!=(const char *psz, const JValue &jv) - { - return (0 != strcmp(jv.asCString(), psz)); - } - -private: - void Free(); - char *NewString(const char *cstr); - void CopyValue(const JValue &src); - bool WriteDataToFile(const char *file, const char *data, size_t len); - -public: - static const JValue null; - static const string nullData; - -private: - union HOLD { - bool vBool; - double vFloat; - int64_t vInt64; - char *vString; - vector *vArray; - map *vObject; - time_t vDate; - string *vData; - wchar_t *vUnicode; - } m_Value; - - TYPE m_eType; - -public: - string write() const; - const char *write(string &strDoc) const; - - string styleWrite() const; - const char *styleWrite(string &strDoc) const; - - bool read(const char *pdoc, string *pstrerr = NULL); - bool read(const string &strdoc, string *pstrerr = NULL); - - string writePList() const; - const char *writePList(string &strDoc) const; - - bool readPList(const string &strdoc, string *pstrerr = NULL); - bool readPList(const char *pdoc, size_t len = 0, string *pstrerr = NULL); - - bool readFile(const char *file, string *pstrerr = NULL); - bool readPListFile(const char *file, string *pstrerr = NULL); - - bool writeFile(const char *file); - bool writePListFile(const char *file); - bool styleWriteFile(const char *file); - - bool readPath(const char *path, ...); - bool readPListPath(const char *path, ...); - bool writePath(const char *path, ...); - bool writePListPath(const char *path, ...); - bool styleWritePath(const char *path, ...); -}; - -class JReader -{ -public: - bool parse(const char *pdoc, JValue &root); - void error(string &strmsg) const; - -private: - struct Token - { - enum TYPE - { - E_Error = 0, - E_End, - E_Null, - E_True, - E_False, - E_Number, - E_String, - E_ArrayBegin, - E_ArrayEnd, - E_ObjectBegin, - E_ObjectEnd, - E_ArraySeparator, - E_MemberSeparator - }; - TYPE type; - const char *pbeg; - const char *pend; - }; - - void skipSpaces(); - void skipComment(); - - bool match(const char *pattern, int patternLength); - - bool readToken(Token &token); - bool readValue(JValue &jval); - bool readArray(JValue &jval); - void readNumber(); - - bool readString(); - bool readObject(JValue &jval); - - bool decodeNumber(Token &token, JValue &jval); - bool decodeString(Token &token, string &decoded); - bool decodeDouble(Token &token, JValue &jval); - - char GetNextChar(); - bool addError(const string &message, const char *ploc); - -private: - const char *m_pBeg; - const char *m_pEnd; - const char *m_pCur; - const char *m_pErr; - string m_strErr; -}; - -class JWriter -{ -public: - static void FastWrite(const JValue &jval, string &strDoc); - static void FastWriteValue(const JValue &jval, string &strDoc); - -public: - const string &StyleWrite(const JValue &jval); - -private: - void PushValue(const string &strval); - void StyleWriteValue(const JValue &jval); - void StyleWriteArrayValue(const JValue &jval); - bool isMultineArray(const JValue &jval); - -public: - static string v2s(double val); - static string v2s(int64_t val); - static string v2s(const char *val); - - static string vstring2s(const char *val); - static string d2s(time_t t); - -private: - string m_strDoc; - string m_strTab; - bool m_bAddChild; - vector m_childValues; -}; - -////////////////////////////////////////////////////////////////////////// -class PReader -{ -public: - PReader(); - -public: - bool parse(const char *pdoc, size_t len, JValue &root); - void error(string &strmsg) const; - -private: - struct Token - { - enum TYPE - { - E_Error = 0, - E_End, - E_Null, - E_True, - E_False, - E_Key, - E_Data, - E_Date, - E_Integer, - E_Real, - E_String, - E_ArrayBegin, - E_ArrayEnd, - E_ArrayNull, - E_DictionaryBegin, - E_DictionaryEnd, - E_DictionaryNull, - E_ArraySeparator, - E_MemberSeparator - }; - - Token() - { - pbeg = NULL; - pend = NULL; - type = E_Error; - } - - TYPE type; - const char *pbeg; - const char *pend; - }; - - bool readToken(Token &token); - bool readLabel(string &label); - bool readValue(JValue &jval, Token &token); - bool readArray(JValue &jval); - bool readNumber(); - - bool readString(); - bool readDictionary(JValue &jval); - - void endLabel(Token &token, const char *szLabel); - - bool decodeNumber(Token &token, JValue &jval); - bool decodeString(Token &token, string &decoded, bool filter = true); - bool decodeDouble(Token &token, JValue &jval); - - void skipSpaces(); - bool addError(const string &message, const char *ploc); - -public: - bool parseBinary(const char *pbdoc, size_t len, JValue &pv); - -private: - uint32_t getUInt24FromBE(const char *v); - void byteConvert(uint8_t *v, size_t size); - uint64_t getUIntVal(const char *v, size_t size); - bool readUIntSize(const char *&pcur, size_t &size); - bool readBinaryValue(const char *&pcur, JValue &pv); - bool readUnicode(const char *pcur, size_t size, JValue &pv); - -public: - static void XMLUnescape(string &strval); - -private: //xml - const char *m_pBeg; - const char *m_pEnd; - const char *m_pCur; - const char *m_pErr; - string m_strErr; - -private: //binary - const char *m_pTrailer; - uint64_t m_uObjects; - uint8_t m_uOffsetSize; - const char *m_pOffsetTable; - uint8_t m_uDictParamSize; -}; - -class PWriter -{ -public: - static void FastWrite(const JValue &pval, string &strdoc); - static void FastWriteValue(const JValue &pval, string &strdoc, string &strindent); - -public: - static void XMLEscape(string &strval); - static string &StringReplace(string &context, const string &from, const string &to); -}; - -#endif // JSON_INCLUDED diff --git a/zsign/common/mach-o.h b/zsign/common/mach-o.h deleted file mode 100644 index 7c5deb9..0000000 --- a/zsign/common/mach-o.h +++ /dev/null @@ -1,580 +0,0 @@ -#pragma once -#include -// typedef int cpu_type_t; -// typedef int cpu_subtype_t; -// typedef int vm_prot_t; - -// /* -// * Capability bits used in the definition of cpu_type. -// */ -// #define CPU_ARCH_MASK 0xff000000 /* mask for architecture bits */ -// #define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */ -// #define CPU_ARCH_ABI64_32 0x02000000 - -// /* -// * Machine types known by all. -// */ -// #define CPU_TYPE_ANY -1 -// #define CPU_TYPE_VAX 1 -// #define CPU_TYPE_MC680x0 6 -// #define CPU_TYPE_X86 7 -// #define CPU_TYPE_I386 CPU_TYPE_X86 /* compatibility */ -// #define CPU_TYPE_MIPS 8 -// #define CPU_TYPE_MC98000 10 -// #define CPU_TYPE_HPPA 11 -// #define CPU_TYPE_ARM 12 -// #define CPU_TYPE_MC88000 13 -// #define CPU_TYPE_SPARC 14 -// #define CPU_TYPE_I860 15 -// #define CPU_TYPE_ALPHA 16 -// #define CPU_TYPE_POWERPC 18 -// #define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64) -// #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) -// #define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) -// #define CPU_TYPE_POWERPC64 (CPU_TYPE_POWERPC | CPU_ARCH_ABI64) - -// /* -// * Machine subtypes (these are defined here, instead of in a machine -// * dependent directory, so that any program can get all definitions -// * regardless of where is it compiled). -// */ - -// /* -// * Capability bits used in the definition of cpu_subtype. -// */ -// #define CPU_SUBTYPE_MASK 0xff000000 /* mask for feature flags */ -// #define CPU_SUBTYPE_LIB64 0x80000000 /* 64 bit libraries */ - -// /* -// * Object files that are hand-crafted to run on any -// * implementation of an architecture are tagged with -// * CPU_SUBTYPE_MULTIPLE. This functions essentially the same as -// * the "ALL" subtype of an architecture except that it allows us -// * to easily find object files that may need to be modified -// * whenever a new implementation of an architecture comes out. -// * -// * It is the responsibility of the implementor to make sure the -// * software handles unsupported implementations elegantly. -// */ -// #define CPU_SUBTYPE_MULTIPLE -1 -// #define CPU_SUBTYPE_LITTLE_ENDIAN 0 -// #define CPU_SUBTYPE_BIG_ENDIAN 1 - -// /* -// * I386 subtypes -// */ - -// #define CPU_SUBTYPE_INTEL(f, m) ((f) + ((m) << 4)) - -// #define CPU_SUBTYPE_I386_ALL CPU_SUBTYPE_INTEL(3, 0) -// #define CPU_SUBTYPE_386 CPU_SUBTYPE_INTEL(3, 0) -// #define CPU_SUBTYPE_486 CPU_SUBTYPE_INTEL(4, 0) -// #define CPU_SUBTYPE_486SX CPU_SUBTYPE_INTEL(4, 8) // 8 << 4 = 128 -// #define CPU_SUBTYPE_586 CPU_SUBTYPE_INTEL(5, 0) -// #define CPU_SUBTYPE_PENT CPU_SUBTYPE_INTEL(5, 0) -// #define CPU_SUBTYPE_PENTPRO CPU_SUBTYPE_INTEL(6, 1) -// #define CPU_SUBTYPE_PENTII_M3 CPU_SUBTYPE_INTEL(6, 3) -// #define CPU_SUBTYPE_PENTII_M5 CPU_SUBTYPE_INTEL(6, 5) -// #define CPU_SUBTYPE_CELERON CPU_SUBTYPE_INTEL(7, 6) -// #define CPU_SUBTYPE_CELERON_MOBILE CPU_SUBTYPE_INTEL(7, 7) -// #define CPU_SUBTYPE_PENTIUM_3 CPU_SUBTYPE_INTEL(8, 0) -// #define CPU_SUBTYPE_PENTIUM_3_M CPU_SUBTYPE_INTEL(8, 1) -// #define CPU_SUBTYPE_PENTIUM_3_XEON CPU_SUBTYPE_INTEL(8, 2) -// #define CPU_SUBTYPE_PENTIUM_M CPU_SUBTYPE_INTEL(9, 0) -// #define CPU_SUBTYPE_PENTIUM_4 CPU_SUBTYPE_INTEL(10, 0) -// #define CPU_SUBTYPE_PENTIUM_4_M CPU_SUBTYPE_INTEL(10, 1) -// #define CPU_SUBTYPE_ITANIUM CPU_SUBTYPE_INTEL(11, 0) -// #define CPU_SUBTYPE_ITANIUM_2 CPU_SUBTYPE_INTEL(11, 1) -// #define CPU_SUBTYPE_XEON CPU_SUBTYPE_INTEL(12, 0) -// #define CPU_SUBTYPE_XEON_MP CPU_SUBTYPE_INTEL(12, 1) - -// #define CPU_SUBTYPE_INTEL_FAMILY(x) ((x) & 15) -// #define CPU_SUBTYPE_INTEL_FAMILY_MAX 15 - -// #define CPU_SUBTYPE_INTEL_MODEL(x) ((x) >> 4) -// #define CPU_SUBTYPE_INTEL_MODEL_ALL 0 - -// /* -// * X86 subtypes. -// */ - -// #define CPU_SUBTYPE_X86_ALL 3 -// #define CPU_SUBTYPE_X86_64_ALL 3 -// #define CPU_SUBTYPE_X86_ARCH1 4 -// #define CPU_SUBTYPE_X86_64_H 8 - -// #define CPU_SUBTYPE_ARM_ALL 0 -// #define CPU_SUBTYPE_ARM_A500_ARCH 1 -// #define CPU_SUBTYPE_ARM_A500 2 -// #define CPU_SUBTYPE_ARM_A440 3 -// #define CPU_SUBTYPE_ARM_M4 4 -// #define CPU_SUBTYPE_ARM_V4T 5 -// #define CPU_SUBTYPE_ARM_V6 6 -// #define CPU_SUBTYPE_ARM_V5 7 -// #define CPU_SUBTYPE_ARM_V5TEJ 7 -// #define CPU_SUBTYPE_ARM_XSCALE 8 -// #define CPU_SUBTYPE_ARM_V7 9 -// #define CPU_SUBTYPE_ARM_V7S 11 -// #define CPU_SUBTYPE_ARM_V7K 12 -// #define CPU_SUBTYPE_ARM_V8 13 -// #define CPU_SUBTYPE_ARM_V6M 14 -// #define CPU_SUBTYPE_ARM_V7M 15 -// #define CPU_SUBTYPE_ARM_V7EM 16 - -// #define CPU_SUBTYPE_ARM64_ALL 0 -// #define CPU_SUBTYPE_ARM64_V8 1 -// #define CPU_SUBTYPE_ARM64E 2 -// #define CPU_SUBTYPE_ARM64_32_V8 1 - - -#define FAT_MAGIC 0xcafebabe -#define FAT_CIGAM 0xbebafeca - -#define MH_MAGIC 0xfeedface -#define MH_CIGAM 0xcefaedfe -#define MH_MAGIC_64 0xfeedfacf -#define MH_CIGAM_64 0xcffaedfe - - -/* Constants for the cmd field of new load commands, the type */ - -#define MH_OBJECT 0x1 /* relocatable object file */ -#define MH_EXECUTE 0x2 /* demand paged executable file */ -#define MH_FVMLIB 0x3 /* fixed VM shared library file */ -#define MH_CORE 0x4 /* core file */ -#define MH_PRELOAD 0x5 /* preloaded executable file */ -#define MH_DYLIB 0x6 /* dynamicly bound shared library file*/ -#define MH_DYLINKER 0x7 /* dynamic link editor */ -#define MH_BUNDLE 0x8 /* dynamicly bound bundle file */ -#define MH_DYLIB_STUB 0x9 // Dynamic shared library stub -#define MH_DSYM 0xa // Companion debug sections file -#define MH_KEXT_BUNDLE 0xb // Kernel extension - -/* Constants for the flags field of the mach_header */ -#define MH_NOUNDEFS 0x00000001 /* the object file has no undefined references, can be executed */ -#define MH_INCRLINK 0x00000002 /* the object file is the output of an incremental link against a base file and can't be link edited again */ -#define MH_DYLDLINK 0x00000004 /* the object file is input for the dynamic linker and can't be staticly link edited again */ -#define MH_BINDATLOAD 0x00000008 /* the object file's undefined references are bound by the dynamic linker when loaded. */ -#define MH_PREBOUND 0x00000010 /* the file has it's dynamic undefined references prebound. */ -#define MH_SPLIT_SEGS 0x00000020 -#define MH_LAZY_INIT 0x00000040 -#define MH_TWOLEVEL 0x00000080 -#define MH_FORCE_FLAT 0x00000100 -#define MH_NOMULTIDEFS 0x00000200 -#define MH_NOFIXPREBINDING 0x00000400 -#define MH_PREBINDABLE 0x00000800 -#define MH_ALLMODSBOUND 0x00001000 -#define MH_SUBSECTIONS_VIA_SYMBOLS 0x00002000 -#define MH_CANONICAL 0x00004000 -#define MH_WEAK_DEFINES 0x00008000 -#define MH_BINDS_TO_WEAK 0x00010000 -#define MH_ALLOW_STACK_EXECUTION 0x00020000 -#define MH_ROOT_SAFE 0x00040000 -#define MH_SETUID_SAFE 0x00080000 -#define MH_NO_REEXPORTED_DYLIBS 0x00100000 -#define MH_PIE 0x00200000 -#define MH_DEAD_STRIPPABLE_DYLIB 0x00400000 -#define MH_HAS_TLV_DESCRIPTORS 0x00800000 -#define MH_NO_HEAP_EXECUTION 0x01000000 -#define MH_APP_EXTENSION_SAFE 0x02000000 - - -/* Constants for the cmd field of all load commands, the type */ -#define LC_SEGMENT 0x00000001 /* segment of this file to be mapped */ -#define LC_SYMTAB 0x00000002 /* link-edit stab symbol table info */ -#define LC_SYMSEG 0x00000003 /* link-edit gdb symbol table info (obsolete) */ -#define LC_THREAD 0x00000004 /* thread */ -#define LC_UNIXTHREAD 0x00000005 /* unix thread (includes a stack) */ -#define LC_LOADFVMLIB 0x00000006 /* load a specified fixed VM shared library */ -#define LC_IDFVMLIB 0x00000007 /* fixed VM shared library identification */ -#define LC_IDENT 0x00000008 /* object identification info (obsolete) */ -#define LC_FVMFILE 0x00000009 /* fixed VM file inclusion (internal use) */ -#define LC_PREPAGE 0x0000000a /* prepage command (internal use) */ -#define LC_DYSYMTAB 0x0000000b /* dynamic link-edit symbol table info */ -#define LC_LOAD_DYLIB 0x0000000c /* load a dynamicly linked shared library */ -#define LC_ID_DYLIB 0x0000000d /* dynamicly linked shared lib identification */ -#define LC_LOAD_DYLINKER 0x0000000e /* load a dynamic linker */ -#define LC_ID_DYLINKER 0x0000000f /* dynamic linker identification */ -#define LC_PREBOUND_DYLIB 0x00000010 /* modules prebound for a dynamicly */ -#define LC_ROUTINES 0x00000011 -#define LC_SUB_FRAMEWORK 0x00000012 -#define LC_SUB_UMBRELLA 0x00000013 -#define LC_SUB_CLIENT 0x00000014 -#define LC_SUB_LIBRARY 0x00000015 -#define LC_TWOLEVEL_HINTS 0x00000016 -#define LC_PREBIND_CKSUM 0x00000017 -#define LC_LOAD_WEAK_DYLIB 0x80000018 -#define LC_SEGMENT_64 0x00000019 -#define LC_ROUTINES_64 0x0000001A -#define LC_UUID 0x0000001B -#define LC_RPATH 0x8000001C -#define LC_CODE_SIGNATURE 0x0000001D -#define LC_SEGMENT_SPLIT_INFO 0x0000001E -#define LC_REEXPORT_DYLIB 0x8000001F -#define LC_LAZY_LOAD_DYLIB 0x00000020 -#define LC_ENCRYPTION_INFO 0x00000021 -#define LC_DYLD_INFO 0x00000022 -#define LC_DYLD_INFO_ONLY 0x80000022 -#define LC_LOAD_UPWARD_DYLIB 0x80000023 -#define LC_VERSION_MIN_MACOSX 0x00000024 -#define LC_VERSION_MIN_IPHONEOS 0x00000025 -#define LC_FUNCTION_STARTS 0x00000026 -#define LC_DYLD_ENVIRONMENT 0x00000027 -#define LC_MAIN 0x80000028 -#define LC_DATA_IN_CODE 0x00000029 -#define LC_SOURCE_VERSION 0x0000002A -#define LC_DYLIB_CODE_SIGN_DRS 0x0000002B -#define LC_ENCRYPTION_INFO_64 0x0000002C -#define LC_LINKER_OPTION 0x0000002D -#define LC_LINKER_OPTIMIZATION_HINT 0x0000002E -#define LC_VERSION_MIN_TVOS 0x0000002F -#define LC_VERSION_MIN_WATCHOS 0x00000030 - -// /* Constants for the flags field of the segment_command */ -// #define SG_HIGHVM 0x00000001 /* the file contents for this segment is for -// the high part of the VM space, the low part -// is zero filled (for stacks in core files) */ -// #define SG_FVMLIB 0x00000002 /* this segment is the VM that is allocated by -// a fixed VM library, for overlap checking in -// the link editor */ -// #define SG_NORELOC 0x00000004 /* this segment has nothing that was relocated -// in it and nothing relocated to it, that is -// it maybe safely replaced without relocation*/ -// #define SG_PROTECTED_VERSION_1 0x00000008 // Segment is encryption protected - - -// // Section flag masks -// #define SECTION_TYPE 0x000000ff // Section type mask -// #define SECTION_ATTRIBUTES 0xffffff00 // Section attributes mask - -// // Section type (use SECTION_TYPE mask) - -// #define S_REGULAR 0x00 -// #define S_ZEROFILL 0x01 -// #define S_CSTRING_LITERALS 0x02 -// #define S_4BYTE_LITERALS 0x03 -// #define S_8BYTE_LITERALS 0x04 -// #define S_LITERAL_POINTERS 0x05 -// #define S_NON_LAZY_SYMBOL_POINTERS 0x06 -// #define S_LAZY_SYMBOL_POINTERS 0x07 -// #define S_SYMBOL_STUBS 0x08 -// #define S_MOD_INIT_FUNC_POINTERS 0x09 -// #define S_MOD_TERM_FUNC_POINTERS 0x0a -// #define S_COALESCED 0x0b -// #define S_GB_ZEROFILL 0x0c -// #define S_INTERPOSING 0x0d -// #define S_16BYTE_LITERALS 0x0e -// #define S_DTRACE_DOF 0x0f -// #define S_LAZY_DYLIB_SYMBOL_POINTERS 0x10 -// #define S_THREAD_LOCAL_REGULAR 0x11 -// #define S_THREAD_LOCAL_ZEROFILL 0x12 -// #define S_THREAD_LOCAL_VARIABLES 0x13 -// #define S_THREAD_LOCAL_VARIABLE_POINTERS 0x14 -// #define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS 0x15 - -// // Section attributes (use SECTION_ATTRIBUTES mask) - -// #define S_ATTR_PURE_INSTRUCTIONS 0x80000000 // Only pure instructions -// #define S_ATTR_NO_TOC 0x40000000 // Contains coalesced symbols -// #define S_ATTR_STRIP_STATIC_SYMS 0x20000000 // Can strip static symbols -// #define S_ATTR_NO_DEAD_STRIP 0x10000000 // No dead stripping -// #define S_ATTR_LIVE_SUPPORT 0x08000000 // Live blocks support -// #define S_ATTR_SELF_MODIFYING_CODE 0x04000000 // Self modifying code -// #define S_ATTR_DEBUG 0x02000000 // Debug section -// #define S_ATTR_SOME_INSTRUCTIONS 0x00000400 // Some machine instructions -// #define S_ATTR_EXT_RELOC 0x00000200 // Has external relocations -// #define S_ATTR_LOC_RELOC 0x00000100 // Has local relocations - - -//struct define - -#pragma pack(push, 1) - -struct fat_header -{ - uint32_t magic; /* FAT_MAGIC */ - uint32_t nfat_arch; /* number of structs that follow */ -}; - -struct fat_arch -{ - cpu_type_t cputype; /* cpu specifier (int) */ - cpu_subtype_t cpusubtype; /* machine specifier (int) */ - uint32_t offset; /* file offset to this object file */ - uint32_t size; /* size of this object file */ - uint32_t align; /* alignment as a power of 2 */ -}; - -struct mach_header -{ - uint32_t magic; /* mach magic number identifier */ - cpu_type_t cputype; /* cpu specifier */ - cpu_subtype_t cpusubtype; /* machine specifier */ - uint32_t filetype; /* type of file */ - uint32_t ncmds; /* number of load commands */ - uint32_t sizeofcmds; /* the size of all the load commands */ - uint32_t flags; /* flags */ -}; - -struct mach_header_64 -{ - uint32_t magic; /* mach magic number identifier */ - cpu_type_t cputype; /* cpu specifier */ - cpu_subtype_t cpusubtype; /* machine specifier */ - uint32_t filetype; /* type of file */ - uint32_t ncmds; /* number of load commands */ - uint32_t sizeofcmds; /* the size of all the load commands */ - uint32_t flags; /* flags */ - uint32_t reserved; /* reserved */ -}; - -struct load_command -{ - uint32_t cmd; /* type of load command */ - uint32_t cmdsize; /* total size of command in bytes */ -}; - -struct uuid_command { - uint32_t cmd; - uint32_t cmdsize; - uint8_t uuid[16]; -}; - -struct entry_point_command { - uint32_t cmd; - uint32_t cmdsize; - uint64_t entryoff; - uint64_t stacksize; -}; - -struct codesignature_command { - uint32_t cmd; - uint32_t cmdsize; - uint32_t dataoff; - uint32_t datasize; -}; - -struct encryption_info_command { - uint32_t cmd; - uint32_t cmdsize; - uint32_t cryptoff; - uint32_t cryptsize; - uint32_t cryptid; -}; - -struct encryption_info_command_64 -{ - uint32_t cmd; - uint32_t cmdsize; - uint32_t cryptoff; - uint32_t cryptsize; - uint32_t cryptid; - uint32_t pad; -}; - -struct segment_command { /* for 32-bit architectures */ - uint32_t cmd; /* LC_SEGMENT */ - uint32_t cmdsize; /* includes sizeof section structs */ - char segname[16]; /* segment name */ - uint32_t vmaddr; /* memory address of this segment */ - uint32_t vmsize; /* memory size of this segment */ - uint32_t fileoff; /* file offset of this segment */ - uint32_t filesize; /* amount to map from the file */ - vm_prot_t maxprot; /* maximum VM protection */ - vm_prot_t initprot; /* initial VM protection */ - uint32_t nsects; /* number of sections in segment */ - uint32_t flags; /* flags */ -}; - -struct segment_command_64 { /* for 64-bit architectures */ - uint32_t cmd; /* LC_SEGMENT_64 */ - uint32_t cmdsize; /* includes sizeof section_64 structs */ - char segname[16]; /* segment name */ - uint64_t vmaddr; /* memory address of this segment */ - uint64_t vmsize; /* memory size of this segment */ - uint64_t fileoff; /* file offset of this segment */ - uint64_t filesize; /* amount to map from the file */ - vm_prot_t maxprot; /* maximum VM protection */ - vm_prot_t initprot; /* initial VM protection */ - uint32_t nsects; /* number of sections in segment */ - uint32_t flags; /* flags */ -}; - -struct section { /* for 32-bit architectures */ - char sectname[16]; /* name of this section */ - char segname[16]; /* segment this section goes in */ - uint32_t addr; /* memory address of this section */ - uint32_t size; /* size in bytes of this section */ - uint32_t offset; /* file offset of this section */ - uint32_t align; /* section alignment (power of 2) */ - uint32_t reloff; /* file offset of relocation entries */ - uint32_t nreloc; /* number of relocation entries */ - uint32_t flags; /* flags (section type and attributes)*/ - uint32_t reserved1; /* reserved */ - uint32_t reserved2; /* reserved */ -}; - -struct section_64 { /* for 64-bit architectures */ - char sectname[16]; /* name of this section */ - char segname[16]; /* segment this section goes in */ - uint64_t addr; /* memory address of this section */ - uint64_t size; /* size in bytes of this section */ - uint32_t offset; /* file offset of this section */ - uint32_t align; /* section alignment (power of 2) */ - uint32_t reloff; /* file offset of relocation entries */ - uint32_t nreloc; /* number of relocation entries */ - uint32_t flags; /* flags (section type and attributes)*/ - uint32_t reserved1; /* reserved (for offset or index) */ - uint32_t reserved2; /* reserved (for count or sizeof) */ - uint32_t reserved3; /* reserved */ -}; - -union lc_str { - uint32_t offset; /* offset to the string */ -}; - -struct dylib { - union lc_str name; /* library's path name */ - uint32_t timestamp; /* library's build time stamp */ - uint32_t current_version; /* library's current version number */ - uint32_t compatibility_version; /* library's compatibility vers number*/ -}; - -struct dylib_command { - uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,LC_REEXPORT_DYLIB */ - uint32_t cmdsize; /* includes pathname string */ - struct dylib dylib; /* the library identification */ -}; - -#pragma pack(pop) - -//////CodeSignature - -enum { - CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ - CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ - CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ - CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ - CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ - CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ - CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, /* der format entitlements */ - CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ - CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ - CS_SUPPORTSSCATTER = 0x20100, - CS_SUPPORTSTEAMID = 0x20200, - CS_SUPPORTSCODELIMIT64 = 0x20300, - CS_SUPPORTSEXECSEG = 0x20400, - CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ - CSSLOT_INFOSLOT = 1, - CSSLOT_REQUIREMENTS = 2, - CSSLOT_RESOURCEDIR = 3, - CSSLOT_APPLICATION = 4, - CSSLOT_ENTITLEMENTS = 5, - CSSLOT_DER_ENTITLEMENTS = 7, /* der format entitlement type */ - CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ - CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ - CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ - CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ - CSSLOT_IDENTIFICATIONSLOT = 0x10001, - CSSLOT_TICKETSLOT = 0x10002, - CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ - CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ - CS_HASHTYPE_SHA1 = 1, - CS_HASHTYPE_SHA256 = 2, - CS_HASHTYPE_SHA256_TRUNCATED = 3, - CS_HASHTYPE_SHA384 = 4, - CS_SHA1_LEN = 20, - CS_SHA256_LEN = 32, - CS_SHA256_TRUNCATED_LEN = 20, - CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ - CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ - CS_EXECSEG_MAIN_BINARY = 0x1, - CS_EXECSEG_ALLOW_UNSIGNED = 0x10, - -/* - * Currently only to support Legacy VPN plugins, - * but intended to replace all the various platform code, dev code etc. bits. - */ - CS_SIGNER_TYPE_UNKNOWN = 0, - CS_SIGNER_TYPE_LEGACYVPN = 5, - -}; - -#pragma pack(push, 1) - -/* - * Structure of an embedded-signature SuperBlob - */ -struct CS_BlobIndex { - uint32_t type; /* type of entry */ - uint32_t offset; /* offset of entry */ -}; - -struct CS_SuperBlob { - uint32_t magic; /* magic number */ - uint32_t length; /* total length of SuperBlob */ - uint32_t count; /* number of index entries following */ - //CS_BlobIndex index[]; /* (count) entries */ - /* followed by Blobs in no particular order as indicated by offsets in index */ -}; - -/* - * C form of a CodeDirectory. - */ -struct CS_CodeDirectory { - uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ - uint32_t length; /* total length of CodeDirectory blob */ - uint32_t version; /* compatibility version */ - uint32_t flags; /* setup and mode flags */ - uint32_t hashOffset; /* offset of hash slot element at index zero */ - uint32_t identOffset; /* offset of identifier string */ - uint32_t nSpecialSlots; /* number of special hash slots */ - uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ - uint32_t codeLimit; /* limit to main image signature range */ - uint8_t hashSize; /* size of each hash in bytes */ - uint8_t hashType; /* type of hash (cdHashType* constants) */ - uint8_t spare1; /* unused (must be zero) */ - uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ - uint32_t spare2; /* unused (must be zero) */ - //char end_earliest[0]; - - /* Version 0x20100 */ - uint32_t scatterOffset; /* offset of optional scatter vector */ - //char end_withScatter[0]; - - /* Version 0x20200 */ - uint32_t teamOffset; /* offset of optional team identifier */ - //char end_withTeam[0]; - - /* Version 0x20300 */ - uint32_t spare3; /* unused (must be zero) */ - uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ - //char end_withCodeLimit64[0]; - - /* Version 0x20400 */ - uint64_t execSegBase; /* offset of executable segment */ - uint64_t execSegLimit; /* limit of executable segment */ - uint64_t execSegFlags; /* executable segment flags */ - //char end_withExecSeg[0]; - - /* followed by dynamic content as located by offset fields above */ -}; - -struct CS_Entitlement { - uint32_t magic; - uint32_t length; -}; - -struct CS_GenericBlob { - uint32_t magic; /* magic number */ - uint32_t length; /* total length of blob */ -}; - -struct CS_Scatter { - uint32_t count; // number of pages; zero for sentinel (only) - uint32_t base; // first page number - uint64_t targetOffset; // offset in target - uint64_t spare; // reserved -}; - -#pragma pack(pop) diff --git a/zsign/macho.cpp b/zsign/macho.cpp deleted file mode 100644 index 745b132..0000000 --- a/zsign/macho.cpp +++ /dev/null @@ -1,350 +0,0 @@ -#include "common/common.h" -#include "common/json.h" -#include "common/mach-o.h" -#include "openssl.h" -#include "signing.h" -#include "macho.h" - -ZMachO::ZMachO() -{ - m_pBase = NULL; - m_sSize = 0; - m_bCSRealloced = false; -} - -ZMachO::~ZMachO() -{ - FreeArchOes(); -} - -bool ZMachO::Init(const char *szFile) -{ - m_strFile = szFile; - return OpenFile(szFile); -} - -bool ZMachO::InitV(const char *szFormatPath, ...) -{ - char szFile[PATH_MAX] = {0}; - va_list args; - va_start(args, szFormatPath); - vsnprintf(szFile, PATH_MAX, szFormatPath, args); - va_end(args); - - return Init(szFile); -} - -bool ZMachO::Free() -{ - FreeArchOes(); - return CloseFile(); -} - -bool ZMachO::NewArchO(uint8_t *pBase, uint32_t uLength) -{ - ZArchO *archo = new ZArchO(); - if (archo->Init(pBase, uLength)) - { - m_arrArchOes.push_back(archo); - return true; - } - delete archo; - return false; -} - -void ZMachO::FreeArchOes() -{ - for (size_t i = 0; i < m_arrArchOes.size(); i++) - { - ZArchO *archo = m_arrArchOes[i]; - delete archo; - } - m_pBase = NULL; - m_sSize = 0; - m_arrArchOes.clear(); -} - -bool ZMachO::OpenFile(const char *szPath) -{ - FreeArchOes(); - - m_sSize = 0; - m_pBase = (uint8_t *)MapFile(szPath, 0, 0, &m_sSize, false); - if (NULL != m_pBase) - { - uint32_t magic = *((uint32_t *)m_pBase); - if (FAT_CIGAM == magic || FAT_MAGIC == magic) - { - fat_header *pFatHeader = (fat_header *)m_pBase; - int nFatArch = (FAT_MAGIC == magic) ? pFatHeader->nfat_arch : LE(pFatHeader->nfat_arch); - for (int i = 0; i < nFatArch; i++) - { - fat_arch *pFatArch = (fat_arch *)(m_pBase + sizeof(fat_header) + sizeof(fat_arch) * i); - uint8_t *pArchBase = m_pBase + ((FAT_MAGIC == magic) ? pFatArch->offset : LE(pFatArch->offset)); - uint32_t uArchLength = (FAT_MAGIC == magic) ? pFatArch->size : LE(pFatArch->size); - if (!NewArchO(pArchBase, uArchLength)) - { - ZLog::ErrorV(">>> Invalid Arch File In Fat Macho File!\n"); - return false; - } - } - } - else if (MH_MAGIC == magic || MH_CIGAM == magic || MH_MAGIC_64 == magic || MH_CIGAM_64 == magic) - { - if (!NewArchO(m_pBase, (uint32_t)m_sSize)) - { - ZLog::ErrorV(">>> Invalid Macho File!\n"); - return false; - } - } - else - { - ZLog::ErrorV(">>> Invalid Macho File (2)!\n"); - return false; - } - } - - return (!m_arrArchOes.empty()); -} - -bool ZMachO::CloseFile() -{ - if (NULL == m_pBase || m_sSize <= 0) - { - return false; - } - - if ((munmap((void *)m_pBase, m_sSize)) < 0) - { - ZLog::ErrorV(">>> CodeSign Write(munmap) Failed! Error: %p, %lu, %s\n", m_pBase, m_sSize, strerror(errno)); - return false; - } - return true; -} - -void ZMachO::PrintInfo() -{ - for (size_t i = 0; i < m_arrArchOes.size(); i++) - { - ZArchO *archo = m_arrArchOes[i]; - archo->PrintInfo(); - } -} - -bool ZMachO::Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData) -{ - if (NULL == m_pBase || m_arrArchOes.empty()) - { - return false; - } - - for (size_t i = 0; i < m_arrArchOes.size(); i++) - { - ZArchO *archo = m_arrArchOes[i]; - if (strBundleId.empty()) - { - JValue jvInfo; - jvInfo.readPList(archo->m_strInfoPlist); - strBundleId = jvInfo["CFBundleIdentifier"].asCString(); - if (strBundleId.empty()) - { - strBundleId = basename((char *)m_strFile.c_str()); - } - } - - if (strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) - { - if (archo->m_strInfoPlist.empty()) - { - strInfoPlistSHA1.append(20, 0); - strInfoPlistSHA256.append(32, 0); - } - else - { - SHASum(archo->m_strInfoPlist, strInfoPlistSHA1, strInfoPlistSHA256); - } - } - - if (!archo->Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData)) - { - if (!archo->m_bEnoughSpace && !m_bCSRealloced) - { - m_bCSRealloced = true; - if (ReallocCodeSignSpace()) - { - return Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData); - } - } - return false; - } - } - - return CloseFile(); -} - -bool ZMachO::ReallocCodeSignSpace() -{ - ZLog::Warn(">>> Realloc CodeSignature Space... \n"); - - vector arrMachOesSizes; - for (size_t i = 0; i < m_arrArchOes.size(); i++) - { - string strNewArchOFile; - StringFormat(strNewArchOFile, "%s.archo.%d", m_strFile.c_str(), i); - uint32_t uNewLength = m_arrArchOes[i]->ReallocCodeSignSpace(strNewArchOFile); - if (uNewLength <= 0) - { - ZLog::Error(">>> Failed!\n"); - return false; - } - arrMachOesSizes.push_back(uNewLength); - } - ZLog::Warn(">>> Success!\n"); - - if (1 == m_arrArchOes.size()) - { - CloseFile(); - RemoveFile(m_strFile.c_str()); - string strNewArchOFile = m_strFile + ".archo.0"; - if (0 == rename(strNewArchOFile.c_str(), m_strFile.c_str())) - { - return OpenFile(m_strFile.c_str()); - } - } - else - { //fat - uint32_t uAlign = 16384; - vector arrArches; - fat_header fath = *((fat_header *)m_pBase); - int nFatArch = (FAT_MAGIC == fath.magic) ? fath.nfat_arch : LE(fath.nfat_arch); - for (int i = 0; i < nFatArch; i++) - { - fat_arch arch = *((fat_arch *)(m_pBase + sizeof(fat_header) + sizeof(fat_arch) * i)); - arrArches.push_back(arch); - } - CloseFile(); - - if (arrArches.size() != m_arrArchOes.size()) - { - return false; - } - - uint32_t uFatHeaderSize = sizeof(fat_header) + arrArches.size() * sizeof(fat_arch); - uint32_t uPadding1 = (uAlign - uFatHeaderSize % uAlign); - uint32_t uOffset = uFatHeaderSize + uPadding1; - for (size_t i = 0; i < arrArches.size(); i++) - { - fat_arch &arch = arrArches[i]; - uint32_t &uMachOSize = arrMachOesSizes[i]; - - arch.align = (FAT_MAGIC == fath.magic) ? 14 : BE((uint32_t)14); - arch.offset = (FAT_MAGIC == fath.magic) ? uOffset : BE(uOffset); - arch.size = (FAT_MAGIC == fath.magic) ? uMachOSize : BE(uMachOSize); - - uOffset += uMachOSize; - uOffset = uOffset + (uAlign - uOffset % uAlign); - } - - string strNewFatMachOFile = m_strFile + ".fato"; - - string strFatHeader; - strFatHeader.append((const char *)&fath, sizeof(fat_header)); - for (size_t i = 0; i < arrArches.size(); i++) - { - fat_arch &arch = arrArches[i]; - strFatHeader.append((const char *)&arch, sizeof(fat_arch)); - } - - string strPadding1; - strPadding1.append(uPadding1, 0); - - AppendFile(strNewFatMachOFile.c_str(), strFatHeader); - AppendFile(strNewFatMachOFile.c_str(), strPadding1); - - for (size_t i = 0; i < arrArches.size(); i++) - { - size_t sSize = 0; - string strNewArchOFile = m_strFile + ".archo." + JValue((int)i).asString(); - uint8_t *pData = (uint8_t *)MapFile(strNewArchOFile.c_str(), 0, 0, &sSize, true); - if (NULL == pData) - { - RemoveFile(strNewFatMachOFile.c_str()); - return false; - } - string strPadding; - strPadding.append((uAlign - sSize % uAlign), 0); - - AppendFile(strNewFatMachOFile.c_str(), (const char *)pData, sSize); - AppendFile(strNewFatMachOFile.c_str(), strPadding); - - munmap((void *)pData, sSize); - RemoveFile(strNewArchOFile.c_str()); - } - - RemoveFile(m_strFile.c_str()); - if (0 == rename(strNewFatMachOFile.c_str(), m_strFile.c_str())) - { - return OpenFile(m_strFile.c_str()); - } - } - - return false; -} - -bool ZMachO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate) -{ - ZLog::WarnV(">>> Inject DyLib: %s ... \n", szDyLibPath); - - vector arrMachOesSizes; - for (size_t i = 0; i < m_arrArchOes.size(); i++) - { - if (!m_arrArchOes[i]->InjectDyLib(bWeakInject, szDyLibPath, bCreate)) - { - ZLog::Error(">>> Failed!\n"); - return false; - } - } - ZLog::Warn(">>> Success!\n"); - return true; -} - -bool ZMachO::ChangeDylibPath(const char *oldPath, const char *newPath) { - ZLog::WarnV(">>> Change DyLib Path: %s -> %s ... \n", oldPath, newPath); - - bool pathChanged = true; - for (size_t i = 0; i < m_arrArchOes.size(); i++) { - if (!m_arrArchOes[i]->ChangeDylibPath(oldPath, newPath)) { - ZLog::Error(">>> Failed to change path in one of the architectures!\n"); - pathChanged = false; - } - } - - if (pathChanged) { - ZLog::Warn(">>> Successfully changed all dylib paths!\n"); - } - return pathChanged; -} - -std::vector ZMachO::ListDylibs() { - std::vector dylibList; - - for (size_t i = 0; i < m_arrArchOes.size(); i++) { - std::vector archDylibs = m_arrArchOes[i]->ListDylibs(); - dylibList.insert(dylibList.end(), archDylibs.begin(), archDylibs.end()); - } - - ZLog::WarnV(">>> Found %zu dylibs:\n", dylibList.size()); - - return dylibList; -} -bool ZMachO::RemoveDylib(const std::set &dylibNames) { - ZLog::Warn(">>> Removing specified dylibs...\n"); - - bool removalSuccessful = true; - for (size_t i = 0; i < m_arrArchOes.size(); i++) { - m_arrArchOes[i]->uninstallDylibs(dylibNames); - } - - ZLog::Warn(">>> Finished removing specified dylibs!\n"); - return removalSuccessful; -} diff --git a/zsign/macho.h b/zsign/macho.h deleted file mode 100644 index e8bf3da..0000000 --- a/zsign/macho.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "archo.h" - -class ZMachO -{ -public: - ZMachO(); - ~ZMachO(); - -public: - bool Init(const char *szFile); - bool InitV(const char *szFormatPath, ...); - bool Free(); - void PrintInfo(); - bool Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData); - bool InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate); - bool ChangeDylibPath(const char *oldPath, const char *newPath); - std::vector ListDylibs(); - bool RemoveDylib(const std::set &dylibNames); -private: - bool OpenFile(const char *szPath); - bool CloseFile(); - - bool NewArchO(uint8_t *pBase, uint32_t uLength); - void FreeArchOes(); - bool ReallocCodeSignSpace(); - -private: - size_t m_sSize; - string m_strFile; - uint8_t *m_pBase; - bool m_bCSRealloced; - vector m_arrArchOes; -}; diff --git a/zsign/openssl.cpp b/zsign/openssl.cpp deleted file mode 100644 index 7147b06..0000000 --- a/zsign/openssl.cpp +++ /dev/null @@ -1,944 +0,0 @@ -#include "common/common.h" -#include "common/base64.h" -#include "openssl.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -class COpenSSLInit -{ -public: - COpenSSLInit() - { -#if OPENSSL_VERSION_NUMBER < 0x10100000L - OpenSSL_add_all_algorithms(); - ERR_load_crypto_strings(); -#endif - }; -}; - -COpenSSLInit g_OpenSSLInit; - -const char *appleDevCACert = "" - "-----BEGIN CERTIFICATE-----\n" - "MIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE\n" - "BhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRp\n" - "ZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEz\n" - "MDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVTMRMwEQYD\n" - "VQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv\n" - "cGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3Bl\n" - "ciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3\n" - "DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0U3rOfGOA\n" - "YXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkVCBmsqtsq\n" - "Mu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8V25nNYB2\n" - "NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHld0WNUEi6\n" - "Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1qarunFjVg\n" - "0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGjgaYwgaMw\n" - "HQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQFMAMBAf8w\n" - "HwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGg\n" - "H4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGG\n" - "MBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Zviz1smwv\n" - "j+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/Nw0Uwj6OD\n" - "Dc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJTleMa1s8\n" - "Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1VAKmuu0sw\n" - "ruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur+cmV6U/k\n" - "TecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxRpVzscYqC\n" - "tGwPDBUf\n" - "-----END CERTIFICATE-----\n"; - -const char *appleDevCACertG3 = "" - "-----BEGIN CERTIFICATE-----\n" - "MIIEUTCCAzmgAwIBAgIQfK9pCiW3Of57m0R6wXjF7jANBgkqhkiG9w0BAQsFADBi\n" - "MQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBw\n" - "bGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3Qg\n" - "Q0EwHhcNMjAwMjE5MTgxMzQ3WhcNMzAwMjIwMDAwMDAwWjB1MUQwQgYDVQQDDDtB\n" - "cHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9u\n" - "IEF1dGhvcml0eTELMAkGA1UECwwCRzMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJ\n" - "BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PWJ/KhZ\n" - "C4fHTJEuLVaQ03gdpDDppUjvC0O/LYT7JF1FG+XrWTYSXFRknmxiLbTGl8rMPPbW\n" - "BpH85QKmHGq0edVny6zpPwcR4YS8Rx1mjjmi6LRJ7TrS4RBgeo6TjMrA2gzAg9Dj\n" - "+ZHWp4zIwXPirkbRYp2SqJBgN31ols2N4Pyb+ni743uvLRfdW/6AWSN1F7gSwe0b\n" - "5TTO/iK1nkmw5VW/j4SiPKi6xYaVFuQAyZ8D0MyzOhZ71gVcnetHrg21LYwOaU1A\n" - "0EtMOwSejSGxrC5DVDDOwYqGlJhL32oNP/77HK6XF8J4CjDgXx9UO0m3JQAaN4LS\n" - "VpelUkl8YDib7wIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0j\n" - "BBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wRAYIKwYBBQUHAQEEODA2MDQGCCsG\n" - "AQUFBzABhihodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNh\n" - "MC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwuYXBwbGUuY29tL3Jvb3QuY3Js\n" - "MB0GA1UdDgQWBBQJ/sAVkPmvZAqSErkmKGMMl+ynsjAOBgNVHQ8BAf8EBAMCAQYw\n" - "EAYKKoZIhvdjZAYCAQQCBQAwDQYJKoZIhvcNAQELBQADggEBAK1lE+j24IF3RAJH\n" - "Qr5fpTkg6mKp/cWQyXMT1Z6b0KoPjY3L7QHPbChAW8dVJEH4/M/BtSPp3Ozxb8qA\n" - "HXfCxGFJJWevD8o5Ja3T43rMMygNDi6hV0Bz+uZcrgZRKe3jhQxPYdwyFot30ETK\n" - "XXIDMUacrptAGvr04NM++i+MZp+XxFRZ79JI9AeZSWBZGcfdlNHAwWx/eCHvDOs7\n" - "bJmCS1JgOLU5gm3sUjFTvg+RTElJdI+mUcuER04ddSduvfnSXPN/wmwLCTbiZOTC\n" - "NwMUGdXqapSqqdv+9poIZ4vvK7iqF0mDr8/LvOnP6pVxsLRFoszlh6oKw0E6eVza\n" - "UDSdlTs=\n" - "-----END CERTIFICATE-----\n"; - -const char *appleRootCACert = "" - "-----BEGIN CERTIFICATE-----\n" - "MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET\n" - "MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv\n" - "biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0\n" - "MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw\n" - "bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx\n" - "FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" - "ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+\n" - "+FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1\n" - "XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w\n" - "tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW\n" - "q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM\n" - "aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E\n" - "BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3\n" - "R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE\n" - "ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93\n" - "d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl\n" - "IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0\n" - "YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj\n" - "b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp\n" - "Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc\n" - "NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP\n" - "y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7\n" - "R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg\n" - "xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP\n" - "IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX\n" - "UKqK1drk/NAJBzewdXUh\n" - "-----END CERTIFICATE-----\n"; - -bool CMSError() -{ - ERR_print_errors_fp(stdout); - return false; -} - -ASN1_TYPE *_GenerateASN1Type(const string &value) -{ - long errline = -1; - char *genstr = NULL; - BIO *ldapbio = BIO_new(BIO_s_mem()); - CONF *cnf = NCONF_new(NULL); - - if (cnf == NULL) { - ZLog::Error(">>> NCONF_new failed\n"); - BIO_free(ldapbio); - } - string a = "asn1=SEQUENCE:A\n[A]\nC=OBJECT:sha256\nB=FORMAT:HEX,OCT:" + value + "\n"; - int code = BIO_puts(ldapbio, a.c_str()); - if (NCONF_load_bio(cnf, ldapbio, &errline) <= 0) { - BIO_free(ldapbio); - NCONF_free(cnf); - ZLog::PrintV(">>> NCONF_load_bio failed %d\n", errline); - } - BIO_free(ldapbio); - genstr = NCONF_get_string(cnf, "default", "asn1"); - - if (genstr == NULL) { - ZLog::Error(">>> NCONF_get_string failed\n"); - NCONF_free(cnf); - } - ASN1_TYPE *ret = ASN1_generate_nconf(genstr, cnf); - NCONF_free(cnf); - return ret; -} - -bool _GenerateCMS(X509 *scert, EVP_PKEY *spkey, const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput) -{ - if (!scert || !spkey) - { - return CMSError(); - } - - BIO *bother1; - unsigned long issuerHash = X509_issuer_name_hash(scert); - if (0x817d2f7a == issuerHash) - { - bother1 = BIO_new_mem_buf(appleDevCACert, (int)strlen(appleDevCACert)); - } - else if (0x9b16b75c == issuerHash) - { - bother1 = BIO_new_mem_buf(appleDevCACertG3, (int)strlen(appleDevCACertG3)); - } - else - { - ZLog::Error(">>> Unknown Issuer Hash!\n"); - return false; - } - - BIO *bother2 = BIO_new_mem_buf(appleRootCACert, (int)strlen(appleRootCACert)); - if (!bother1 || !bother2) - { - return CMSError(); - } - - X509 *ocert1 = PEM_read_bio_X509(bother1, NULL, 0, NULL); - X509 *ocert2 = PEM_read_bio_X509(bother2, NULL, 0, NULL); - if (!ocert1 || !ocert2) - { - return CMSError(); - } - - STACK_OF(X509) *otherCerts = sk_X509_new_null(); - if (!otherCerts) - { - return CMSError(); - } - - if (!sk_X509_push(otherCerts, ocert1)) - { - return CMSError(); - } - - if (!sk_X509_push(otherCerts, ocert2)) - { - return CMSError(); - } - - BIO *in = BIO_new_mem_buf(strCDHashData.c_str(), (int)strCDHashData.size()); - if (!in) - { - return CMSError(); - } - - int nFlags = CMS_PARTIAL | CMS_DETACHED | CMS_NOSMIMECAP | CMS_BINARY; - CMS_ContentInfo *cms = CMS_sign(NULL, NULL, otherCerts, NULL, nFlags); - if (!cms) - { - return CMSError(); - } - - CMS_SignerInfo * si = CMS_add1_signer(cms, scert, spkey, EVP_sha256(), nFlags); -// CMS_add1_signer(cms, NULL, NULL, EVP_sha1(), nFlags); - if (!si) { - return CMSError(); - } - - // add plist - ASN1_OBJECT * obj = OBJ_txt2obj("1.2.840.113635.100.9.1", 1); - if (!obj) { - return CMSError(); - } - - int addHashPlist = CMS_signed_add1_attr_by_OBJ(si, obj, 0x4, strCDHashesPlist.c_str(), (int)strCDHashesPlist.size()); - - if (!addHashPlist) { - return CMSError(); - } - - // add CDHashes - string sha256; - char buf[16] = {0}; - for (size_t i = 0; i < strAltnateCodeDirectorySlot256.size(); i++) - { - sprintf(buf, "%02x", (uint8_t)strAltnateCodeDirectorySlot256[i]); - sha256 += buf; - } - transform(sha256.begin(), sha256.end(), sha256.begin(), ::toupper); - - ASN1_OBJECT * obj2 = OBJ_txt2obj("1.2.840.113635.100.9.2", 1); - if (!obj2) { - return CMSError(); - } - - X509_ATTRIBUTE *attr = X509_ATTRIBUTE_new(); - X509_ATTRIBUTE_set1_object(attr, obj2); - - ASN1_TYPE *type_256 = _GenerateASN1Type(sha256); - X509_ATTRIBUTE_set1_data(attr, V_ASN1_SEQUENCE, - type_256->value.asn1_string->data, type_256->value.asn1_string->length); - int addHashSHA = CMS_signed_add1_attr(si, attr); - if (!addHashSHA) { - return CMSError(); - } - - if (!CMS_final(cms, in, NULL, nFlags)) - { - return CMSError(); - } - - BIO *out = BIO_new(BIO_s_mem()); - if (!out) - { - return CMSError(); - } - - //PEM_write_bio_CMS(out, cms); - if (!i2d_CMS_bio(out, cms)) - { - return CMSError(); - } - - BUF_MEM *bptr = NULL; - BIO_get_mem_ptr(out, &bptr); - if (!bptr) - { - return CMSError(); - } - - strCMSOutput.clear(); - strCMSOutput.append(bptr->data, bptr->length); - ASN1_TYPE_free(type_256); - return (!strCMSOutput.empty()); -} - -bool GenerateCMS(const string &strSignerCertData, const string &strSignerPKeyData, const string &strCDHashData, const string &strCDHashesPlist, string &strCMSOutput) -{ - BIO *bcert = BIO_new_mem_buf(strSignerCertData.c_str(), (int)strSignerCertData.size()); - BIO *bpkey = BIO_new_mem_buf(strSignerPKeyData.c_str(), (int)strSignerPKeyData.size()); - - if (!bcert || !bpkey) - { - return CMSError(); - } - - X509 *scert = PEM_read_bio_X509(bcert, NULL, 0, NULL); - EVP_PKEY *spkey = PEM_read_bio_PrivateKey(bpkey, NULL, 0, NULL); - if (!scert || !spkey) - { - return CMSError(); - } - - return ::_GenerateCMS(scert, spkey, strCDHashData, strCDHashesPlist, "", "", strCMSOutput); -} - -bool GetCMSContent(const string &strCMSDataInput, string &strContentOutput) -{ - if (strCMSDataInput.empty()) - { - return false; - } - - BIO *in = BIO_new(BIO_s_mem()); - OPENSSL_assert((size_t)BIO_write(in, strCMSDataInput.data(), (int)strCMSDataInput.size()) == strCMSDataInput.size()); - CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); - if (!cms) - { - return CMSError(); - } - - ASN1_OCTET_STRING **pos = CMS_get0_content(cms); - if (!pos) - { - return CMSError(); - } - - if (!(*pos)) - { - return CMSError(); - } - - strContentOutput.clear(); - strContentOutput.append((const char *)(*pos)->data, (*pos)->length); - return (!strContentOutput.empty()); -} - -bool GetCMSContent2(const void* strCMSDataInput, int size, string &strContentOutput) -{ - if (size == 0) - { - return false; - } - - BIO *in = BIO_new(BIO_s_mem()); - OPENSSL_assert((size_t)BIO_write(in, strCMSDataInput, size) == size); - CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); - if (!cms) - { - return CMSError(); - } - - ASN1_OCTET_STRING **pos = CMS_get0_content(cms); - if (!pos) - { - return CMSError(); - } - - if (!(*pos)) - { - return CMSError(); - } - - strContentOutput.clear(); - strContentOutput.append((const char *)(*pos)->data, (*pos)->length); - return (!strContentOutput.empty()); -} - -bool GetCertSubjectCN(X509 *cert, string &strSubjectCN) -{ - if (!cert) - { - return CMSError(); - } - - X509_NAME *name = X509_get_subject_name(cert); - - int common_name_loc = X509_NAME_get_index_by_NID(name, NID_commonName, -1); - if (common_name_loc < 0) - { - return CMSError(); - } - - X509_NAME_ENTRY *common_name_entry = X509_NAME_get_entry(name, common_name_loc); - if (common_name_entry == NULL) - { - return CMSError(); - } - - ASN1_STRING *common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); - if (common_name_asn1 == NULL) - { - return CMSError(); - } - - strSubjectCN.clear(); - strSubjectCN.append((const char *)common_name_asn1->data, common_name_asn1->length); - return (!strSubjectCN.empty()); -} - -bool GetCertSubjectCN(const string &strCertData, string &strSubjectCN) -{ - if (strCertData.empty()) - { - return false; - } - - BIO *bcert = BIO_new_mem_buf(strCertData.c_str(), strCertData.size()); - if (!bcert) - { - return CMSError(); - } - - X509 *cert = PEM_read_bio_X509(bcert, NULL, 0, NULL); - if (!cert) - { - return CMSError(); - } - - return GetCertSubjectCN(cert, strSubjectCN); -} - -void ParseCertSubject(const string &strSubject, JValue &jvSubject) -{ - vector arrNodes; - StringSplit(strSubject, "/", arrNodes); - for (size_t i = 0; i < arrNodes.size(); i++) - { - vector arrLines; - StringSplit(arrNodes[i], "=", arrLines); - if (2 == arrLines.size()) - { - jvSubject[arrLines[0]] = arrLines[1]; - } - } -} - -#if OPENSSL_VERSION_NUMBER < 0x10100000L -string ASN1_TIMEtoString(ASN1_TIME *time) -{ -#else -string ASN1_TIMEtoString(const ASN1_TIME *time) -{ -#endif - BIO *out = BIO_new(BIO_s_mem()); - if (!out) - { - CMSError(); - return ""; - } - - ASN1_TIME_print(out, time); - BUF_MEM *bptr = NULL; - BIO_get_mem_ptr(out, &bptr); - if (!bptr) - { - CMSError(); - return ""; - } - string strTime; - strTime.append(bptr->data, bptr->length); - return strTime; -} - -bool GetCertInfo(X509 *cert, JValue &jvCertInfo) -{ - if (!cert) - { - return CMSError(); - } - - jvCertInfo["Version"] = (int)X509_get_version(cert); - - ASN1_INTEGER *asn1_i = X509_get_serialNumber(cert); - if (asn1_i) - { - BIGNUM *bignum = ASN1_INTEGER_to_BN(asn1_i, NULL); - if (bignum) - { - jvCertInfo["SerialNumber"] = BN_bn2hex(bignum); - } - } - - jvCertInfo["SignatureAlgorithm"] = OBJ_nid2ln(X509_get_signature_nid(cert)); - - EVP_PKEY *pubkey = X509_get_pubkey(cert); - int type = EVP_PKEY_id(pubkey); - jvCertInfo["PublicKey"]["Algorithm"] = OBJ_nid2ln(type); - -#if OPENSSL_VERSION_NUMBER < 0x10100000L - jvCertInfo["Validity"]["NotBefore"] = ASN1_TIMEtoString(X509_get_notBefore(cert)); - jvCertInfo["Validity"]["NotAfter"] = ASN1_TIMEtoString(X509_get_notAfter(cert)); -#else - jvCertInfo["Validity"]["NotBefore"] = ASN1_TIMEtoString(X509_get0_notBefore(cert)); - jvCertInfo["Validity"]["NotAfter"] = ASN1_TIMEtoString(X509_get0_notAfter(cert)); -#endif - - string strIssuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); - string strSubject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); - - ParseCertSubject(strIssuer, jvCertInfo["Issuer"]); - ParseCertSubject(strSubject, jvCertInfo["Subject"]); - - return (!strIssuer.empty() && !strSubject.empty()); -} - -bool GetCMSInfo(uint8_t *pCMSData, uint32_t uCMSLength, JValue &jvOutput) -{ - BIO *in = BIO_new(BIO_s_mem()); - OPENSSL_assert((size_t)BIO_write(in, pCMSData, uCMSLength) == uCMSLength); - CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); - if (!cms) - { - return CMSError(); - } - - int detached = CMS_is_detached(cms); - jvOutput["detached"] = detached; - - const ASN1_OBJECT *obj = CMS_get0_type(cms); - const char *sn = OBJ_nid2ln(OBJ_obj2nid(obj)); - jvOutput["contentType"] = sn; - - ASN1_OCTET_STRING **pos = CMS_get0_content(cms); - if (pos) - { - if ((*pos)) - { - ZBase64 b64; - jvOutput["content"] = b64.Encode((const char *)(*pos)->data, (*pos)->length); - } - } - - STACK_OF(X509) *certs = CMS_get1_certs(cms); - for (int i = 0; i < sk_X509_num(certs); i++) - { - JValue jvCertInfo; - if (GetCertInfo(sk_X509_value(certs, i), jvCertInfo)) - { - jvOutput["certs"].push_back(jvCertInfo); - } - } - - STACK_OF(CMS_SignerInfo) *sis = CMS_get0_SignerInfos(cms); - for (int i = 0; i < sk_CMS_SignerInfo_num(sis); i++) - { - CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sis, i); - //int CMS_SignerInfo_get0_signer_id(CMS_SignerInfo *si, ASN1_OCTET_STRING **keyid, X509_NAME **issuer, ASN1_INTEGER **sno); - - int nSignedAttsCount = CMS_signed_get_attr_count(si); - for (int j = 0; j < nSignedAttsCount; j++) - { - X509_ATTRIBUTE *attr = CMS_signed_get_attr(si, j); - if (!attr) - { - continue; - } - int nCount = X509_ATTRIBUTE_count(attr); - if (nCount <= 0) - { - continue; - } - - ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); - if (!obj) - { - continue; - } - - char txtobj[128] = {0}; - OBJ_obj2txt(txtobj, 128, obj, 1); - - if (0 == strcmp("1.2.840.113549.1.9.3", txtobj)) - { //V_ASN1_OBJECT - ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); - if (NULL != av) - { - jvOutput["attrs"]["ContentType"]["obj"] = txtobj; - jvOutput["attrs"]["ContentType"]["data"] = OBJ_nid2ln(OBJ_obj2nid(av->value.object)); - } - } - else if (0 == strcmp("1.2.840.113549.1.9.4", txtobj)) - { //V_ASN1_OCTET_STRING - ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); - if (NULL != av) - { - string strSHASum; - char buf[16] = {0}; - for (int m = 0; m < av->value.octet_string->length; m++) - { - sprintf(buf, "%02x", (uint8_t)av->value.octet_string->data[m]); - strSHASum += buf; - } - jvOutput["attrs"]["MessageDigest"]["obj"] = txtobj; - jvOutput["attrs"]["MessageDigest"]["data"] = strSHASum; - } - } - else if (0 == strcmp("1.2.840.113549.1.9.5", txtobj)) - { //V_ASN1_UTCTIME - ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); - if (NULL != av) - { - BIO *mem = BIO_new(BIO_s_mem()); - ASN1_UTCTIME_print(mem, av->value.utctime); - BUF_MEM *bptr = NULL; - BIO_get_mem_ptr(mem, &bptr); - BIO_set_close(mem, BIO_NOCLOSE); - string strTime; - strTime.append(bptr->data, bptr->length); - BIO_free_all(mem); - - jvOutput["attrs"]["SigningTime"]["obj"] = txtobj; - jvOutput["attrs"]["SigningTime"]["data"] = strTime; - } - } - else if (0 == strcmp("1.2.840.113635.100.9.2", txtobj)) - { //V_ASN1_SEQUENCE - jvOutput["attrs"]["CDHashes2"]["obj"] = txtobj; - for (int m = 0; m < nCount; m++) - { - ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, m); - if (NULL != av) - { - ASN1_STRING *s = av->value.sequence; - - BIO *mem = BIO_new(BIO_s_mem()); - - ASN1_parse_dump(mem, s->data, s->length, 2, 0); - BUF_MEM *bptr = NULL; - BIO_get_mem_ptr(mem, &bptr); - BIO_set_close(mem, BIO_NOCLOSE); - string strData; - strData.append(bptr->data, bptr->length); - BIO_free_all(mem); - - string strSHASum; - size_t pos1 = strData.find("[HEX DUMP]:"); - if (string::npos != pos1) - { - size_t pos2 = strData.find("\n", pos1); - if (string::npos != pos2) - { - strSHASum = strData.substr(pos1 + 11, pos2 - pos1 - 11); - } - } - transform(strSHASum.begin(), strSHASum.end(), strSHASum.begin(), ::tolower); - jvOutput["attrs"]["CDHashes2"]["data"].push_back(strSHASum); - } - } - } - else if (0 == strcmp("1.2.840.113635.100.9.1", txtobj)) - { //V_ASN1_OCTET_STRING - ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); - if (NULL != av) - { - string strPList; - strPList.append((const char *)av->value.octet_string->data, av->value.octet_string->length); - jvOutput["attrs"]["CDHashes"]["obj"] = txtobj; - jvOutput["attrs"]["CDHashes"]["data"] = strPList; - } - } - else - { - ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); - if (NULL != av) - { - JValue jvAttr; - jvAttr["obj"] = txtobj; - jvAttr["name"] = OBJ_nid2ln(OBJ_obj2nid(obj)); - jvAttr["type"] = av->type; - jvAttr["count"] = nCount; - jvOutput["attrs"]["unknown"].push_back(jvAttr); - } - } - } - } - - return true; -} - -ZSignAsset::ZSignAsset() -{ - m_evpPKey = NULL; - m_x509Cert = NULL; -} - -bool ZSignAsset::Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword) -{ - ReadFile(strProvisionFile.c_str(), m_strProvisionData); - ReadFile(strEntitlementsFile.c_str(), m_strEntitlementsData); - if (m_strProvisionData.empty()) - { - ZLog::Error(">>> Can't Find Provision File!\n"); - return false; - } - - JValue jvProv; - string strProvContent; - if (GetCMSContent(m_strProvisionData, strProvContent)) - { - if (jvProv.readPList(strProvContent)) - { - m_strTeamId = jvProv["TeamIdentifier"][0].asCString(); - if (m_strEntitlementsData.empty()) - { - jvProv["Entitlements"].writePList(m_strEntitlementsData); - } - } - } - - if (m_strTeamId.empty()) - { - ZLog::Error(">>> Can't Find TeamId!\n"); - return false; - } - - X509 *x509Cert = NULL; - EVP_PKEY *evpPKey = NULL; - BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); - if (NULL != bioPKey) - { - evpPKey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str()); - if (NULL == evpPKey) - { - BIO_reset(bioPKey); - evpPKey = d2i_PrivateKey_bio(bioPKey, NULL); - if (NULL == evpPKey) - { - BIO_reset(bioPKey); - OSSL_PROVIDER_load(NULL, "legacy"); - PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL); - if (NULL != p12) - { - if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPKey, &x509Cert, NULL)) - { - CMSError(); - } - PKCS12_free(p12); - } - } - } - BIO_free(bioPKey); - } - - if (NULL == evpPKey) - { - ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n"); - return false; - } - - if (NULL == x509Cert && !strSignerCertFile.empty()) - { - BIO *bioCert = BIO_new_file(strSignerCertFile.c_str(), "r"); - if (NULL != bioCert) - { - x509Cert = PEM_read_bio_X509(bioCert, NULL, 0, NULL); - if (NULL == x509Cert) - { - BIO_reset(bioCert); - x509Cert = d2i_X509_bio(bioCert, NULL); - } - BIO_free(bioCert); - } - } - - if (NULL != x509Cert) - { - if (!X509_check_private_key(x509Cert, evpPKey)) - { - X509_free(x509Cert); - x509Cert = NULL; - } - } - - if (NULL == x509Cert) - { - for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++) - { - string strCertData = jvProv["DeveloperCertificates"][i].asData(); - BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), (int)strCertData.size()); - if (NULL != bioCert) - { - x509Cert = d2i_X509_bio(bioCert, NULL); - if (NULL != x509Cert) - { - if (X509_check_private_key(x509Cert, evpPKey)) - { - break; - } - X509_free(x509Cert); - x509Cert = NULL; - } - BIO_free(bioCert); - } - } - } - - if (NULL == x509Cert) - { - ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n"); - return false; - } - - if (!GetCertSubjectCN(x509Cert, m_strSubjectCN)) - { - ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n"); - return false; - } - - m_evpPKey = evpPKey; - m_x509Cert = x509Cert; - return true; -} - -bool ZSignAsset::GenerateCMS(const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput) -{ - return ::_GenerateCMS((X509 *)m_x509Cert, (EVP_PKEY *)m_evpPKey, strCDHashData, strCDHashesPlist, strCodeDirectorySlotSHA1, strAltnateCodeDirectorySlot256, strCMSOutput); -} - - std::string binary_to_hex(const char* data, int len) { - std::ostringstream oss; - for (int i = 0; i < len; ++i) { - uint8_t byte = data[i]; - oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); - } - return oss.str(); - } - -bool ZSignAsset::InitSimple(const void* strSignerPKeyData, int strSignerPKeyDataSize, const void* strProvisionData, int strProvisionDataSize, const string &strPassword){ - - JValue jvProv; - string strProvContent; - m_strEntitlementsData = ""; - if (GetCMSContent2(strProvisionData, strProvisionDataSize, strProvContent)) - { - if (jvProv.readPList(strProvContent)) - { - m_strTeamId = jvProv["TeamIdentifier"][0].asCString(); - if (m_strEntitlementsData.empty()) - { - jvProv["Entitlements"].writePList(m_strEntitlementsData); - } - } - } - - if (m_strTeamId.empty()) - { - ZLog::Error(">>> Can't Find TeamId!\n"); - return false; - } - - X509 *x509Cert = NULL; - EVP_PKEY *evpPKey = NULL; - - // BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); - char* digest = new char[16]; - MD5((unsigned char*) strSignerPKeyData, strSignerPKeyDataSize, (unsigned char*)digest); - ZLog::Error(binary_to_hex(digest, 16).data()); - delete[] digest; - - - BIO *bioPKey = BIO_new_mem_buf(strSignerPKeyData, strSignerPKeyDataSize); - if (NULL != bioPKey) - { - evpPKey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str()); - if (NULL == evpPKey) - { - BIO_reset(bioPKey); - evpPKey = d2i_PrivateKey_bio(bioPKey, NULL); - if (NULL == evpPKey) - { - BIO_reset(bioPKey); - OSSL_PROVIDER_load(NULL, "legacy"); - PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL); - if (NULL != p12) - { - if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPKey, &x509Cert, NULL)) - { - CMSError(); - } - PKCS12_free(p12); - } - } - } - BIO_free(bioPKey); - } - - if (NULL == evpPKey) - { - ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n"); - return false; - } - - if (NULL != x509Cert) - { - if (!X509_check_private_key(x509Cert, evpPKey)) - { - X509_free(x509Cert); - x509Cert = NULL; - } - } - - if (NULL == x509Cert) - { - for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++) - { - string strCertData = jvProv["DeveloperCertificates"][i].asData(); - BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), (int)strCertData.size()); - if (NULL != bioCert) - { - x509Cert = d2i_X509_bio(bioCert, NULL); - if (NULL != x509Cert) - { - if (X509_check_private_key(x509Cert, evpPKey)) - { - break; - } - X509_free(x509Cert); - x509Cert = NULL; - } - BIO_free(bioCert); - } - } - } - - if (NULL == x509Cert) - { - ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n"); - return false; - } - - if (!GetCertSubjectCN(x509Cert, m_strSubjectCN)) - { - ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n"); - return false; - } - - m_evpPKey = evpPKey; - m_x509Cert = x509Cert; - return true; -} diff --git a/zsign/openssl.h b/zsign/openssl.h deleted file mode 100644 index 6e4dfec..0000000 --- a/zsign/openssl.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "common/json.h" - -bool GetCertSubjectCN(const string &strCertData, string &strSubjectCN); -bool GetCMSInfo(uint8_t *pCMSData, uint32_t uCMSLength, JValue &jvOutput); -bool GetCMSContent(const string &strCMSDataInput, string &strContentOutput); -bool GenerateCMS(const string &strSignerCertData, const string &strSignerPKeyData, const string &strCDHashData, const string &strCDHashPlist, string &strCMSOutput); - -class ZSignAsset -{ -public: - ZSignAsset(); - -public: - bool GenerateCMS(const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput); - bool Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword); - bool InitSimple(const void* strSignerPKeyData, int strSignerPKeyDataSize, const void* strProvisionData, int strProvisionDataSize, const string &strPassword); - -public: - string m_strTeamId; - string m_strSubjectCN; - string m_strProvisionData; - string m_strEntitlementsData; - -private: - void *m_evpPKey; - void *m_x509Cert; -}; diff --git a/zsign/signing.cpp b/zsign/signing.cpp deleted file mode 100644 index 64d5dd7..0000000 --- a/zsign/signing.cpp +++ /dev/null @@ -1,875 +0,0 @@ -#include "common/common.h" -#include "common/json.h" -#include "common/mach-o.h" -#include "openssl.h" - -static void _DERLength(string &strBlob, uint64_t uLength) -{ - if (uLength < 128) - { - strBlob.append(1, (char)uLength); - } - else - { - uint32_t sLength = (64 - __builtin_clzll(uLength) + 7) / 8; - strBlob.append(1, (char)(0x80 | sLength)); - sLength *= 8; - do - { - strBlob.append(1, (char)(uLength >> (sLength -= 8))); - } while (sLength != 0); - } -} - -static string _DER(const JValue &data) -{ - string strOutput; - if (data.isBool()) - { - strOutput.append(1, 0x01); - strOutput.append(1, 1); - strOutput.append(1, data.asBool() ? 1 : 0); - } - else if (data.isInt()) - { - uint64_t uVal = data.asInt64(); - strOutput.append(1, 0x02); - _DERLength(strOutput, uVal); - - uint32_t sLength = (64 - __builtin_clzll(uVal) + 7) / 8; - sLength *= 8; - do - { - strOutput.append(1, (char)(uVal >> (sLength -= 8))); - } while (sLength != 0); - } - else if (data.isString()) - { - string strVal = data.asCString(); - strOutput.append(1, 0x0c); - _DERLength(strOutput, strVal.size()); - strOutput += strVal; - } - else if (data.isArray()) - { - string strArray; - size_t size = data.size(); - for (size_t i = 0; i < size; i++) - { - strArray += _DER(data[i]); - } - strOutput.append(1, 0x30); - _DERLength(strOutput, strArray.size()); - strOutput += strArray; - } - else if (data.isObject()) - { - string strDict; - vector arrKeys; - data.keys(arrKeys); - for (size_t i = 0; i < arrKeys.size(); i++) - { - string &strKey = arrKeys[i]; - string strVal = _DER(data[strKey]); - - strDict.append(1, 0x30); - _DERLength(strDict, (2 + strKey.size() + strVal.size())); - - strDict.append(1, 0x0c); - _DERLength(strDict, strKey.size()); - strDict += strKey; - - strDict += strVal; - } - - strOutput.append(1, 0x31); - _DERLength(strOutput, strDict.size()); - strOutput += strDict; - } - else if (data.isFloat()) - { - assert(false); - } - else if (data.isDate()) - { - assert(false); - } - else if (data.isData()) - { - assert(false); - } - else - { - assert(false && "Unsupported Entitlements DER Type"); - } - - return strOutput; -} - -uint32_t SlotParseGeneralHeader(const char *szSlotName, uint8_t *pSlotBase, CS_BlobIndex *pbi) -{ - uint32_t uSlotLength = LE(*(((uint32_t *)pSlotBase) + 1)); - ZLog::PrintV("\n > %s: \n", szSlotName); - ZLog::PrintV("\ttype: \t\t0x%x\n", LE(pbi->type)); - ZLog::PrintV("\toffset: \t%u\n", LE(pbi->offset)); - ZLog::PrintV("\tmagic: \t\t0x%x\n", LE(*((uint32_t *)pSlotBase))); - ZLog::PrintV("\tlength: \t%u\n", uSlotLength); - return uSlotLength; -} - -void SlotParseGeneralTailer(uint8_t *pSlotBase, uint32_t uSlotLength) -{ - PrintDataSHASum("\tSHA-1: \t", E_SHASUM_TYPE_1, pSlotBase, uSlotLength); - PrintDataSHASum("\tSHA-256:\t", E_SHASUM_TYPE_256, pSlotBase, uSlotLength); -} - -bool SlotParseRequirements(uint8_t *pSlotBase, CS_BlobIndex *pbi) -{ - uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_REQUIREMENTS", pSlotBase, pbi); - if (uSlotLength < 8) - { - return false; - } - - if (IsFileExists("/usr/bin/csreq")) - { - string strTempFile; - StringFormat(strTempFile, "/tmp/Requirements_%llu.blob", GetMicroSecond()); - WriteFile(strTempFile.c_str(), (const char *)pSlotBase, uSlotLength); - - string strCommand; - StringFormat(strCommand, "/usr/bin/csreq -r '%s' -t ", strTempFile.c_str()); - char result[1024] = {0}; - FILE *cmd = popen(strCommand.c_str(), "r"); - while (NULL != fgets(result, sizeof(result), cmd)) - { - printf("\treqtext: \t%s", result); - } - pclose(cmd); - RemoveFile(strTempFile.c_str()); - } - - SlotParseGeneralTailer(pSlotBase, uSlotLength); - - if (ZLog::IsDebug()) - { - WriteFile("./.zsign_debug/Requirements.slot", (const char *)pSlotBase, uSlotLength); - } - return true; -} - -bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput) -{ - strOutput.clear(); - if (strBundleID.empty() || strSubjectCN.empty()) - { //ldid - strOutput = "\xfa\xde\x0c\x01\x00\x00\x00\x0c\x00\x00\x00\x00"; - return true; - } - - string strPaddedBundleID = strBundleID; - strPaddedBundleID.append(((strBundleID.size() % 4) ? (4 - (strBundleID.size() % 4)) : 0), 0); - - string strPaddedSubjectID = strSubjectCN; - strPaddedSubjectID.append(((strSubjectCN.size() % 4) ? (4 - (strSubjectCN.size() % 4)) : 0), 0); - - uint8_t magic1[] = {0xfa, 0xde, 0x0c, 0x01}; - uint32_t uLength1 = 0; - uint8_t pack1[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14}; - uint8_t magic2[] = {0xfa, 0xde, 0x0c, 0x00}; - uint32_t uLength2 = 0; - uint8_t pack2[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02}; - uint32_t uBundldIDLength = (uint32_t)strBundleID.size(); - //string strPaddedBundleID - uint8_t pack3[] = { - 0x00, - 0x00, - 0x00, - 0x06, - 0x00, - 0x00, - 0x00, - 0x0f, - 0x00, - 0x00, - 0x00, - 0x06, - 0x00, - 0x00, - 0x00, - 0x0b, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x0a, - 0x73, - 0x75, - 0x62, - 0x6a, - 0x65, - 0x63, - 0x74, - 0x2e, - 0x43, - 0x4e, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x01, - }; - uint32_t uSubjectCNLength = (uint32_t)strSubjectCN.size(); - //string strPaddedSubjectID - uint8_t pack4[] = { - 0x00, - 0x00, - 0x00, - 0x0e, - 0x00, - 0x00, - 0x00, - 0x01, - 0x00, - 0x00, - 0x00, - 0x0a, - 0x2a, - 0x86, - 0x48, - 0x86, - 0xf7, - 0x63, - 0x64, - 0x06, - 0x02, - 0x01, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - }; - - uLength2 += sizeof(magic2) + sizeof(uLength2) + sizeof(pack2); - uLength2 += sizeof(uBundldIDLength) + strPaddedBundleID.size(); - uLength2 += sizeof(pack3); - uLength2 += sizeof(uSubjectCNLength) + strPaddedSubjectID.size(); - uLength2 += sizeof(pack4); - - uLength1 += sizeof(magic1) + sizeof(uLength1) + sizeof(pack1); - uLength1 += uLength2; - - uLength1 = BE(uLength1); - uLength2 = BE(uLength2); - uBundldIDLength = BE(uBundldIDLength); - uSubjectCNLength = BE(uSubjectCNLength); - - strOutput.append((const char *)magic1, sizeof(magic1)); - strOutput.append((const char *)&uLength1, sizeof(uLength1)); - strOutput.append((const char *)pack1, sizeof(pack1)); - strOutput.append((const char *)magic2, sizeof(magic2)); - strOutput.append((const char *)&uLength2, sizeof(uLength2)); - strOutput.append((const char *)pack2, sizeof(pack2)); - strOutput.append((const char *)&uBundldIDLength, sizeof(uBundldIDLength)); - strOutput.append(strPaddedBundleID.data(), strPaddedBundleID.size()); - strOutput.append((const char *)pack3, sizeof(pack3)); - strOutput.append((const char *)&uSubjectCNLength, sizeof(uSubjectCNLength)); - strOutput.append(strPaddedSubjectID.data(), strPaddedSubjectID.size()); - strOutput.append((const char *)pack4, sizeof(pack4)); - - return true; -} - -bool SlotParseEntitlements(uint8_t *pSlotBase, CS_BlobIndex *pbi) -{ - uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_ENTITLEMENTS", pSlotBase, pbi); - if (uSlotLength < 8) - { - return false; - } - - string strEntitlements = "\t\t\t"; - strEntitlements.append((const char *)pSlotBase + 8, uSlotLength - 8); - PWriter::StringReplace(strEntitlements, "\n", "\n\t\t\t"); - ZLog::PrintV("\tentitlements: \n%s\n", strEntitlements.c_str()); - - SlotParseGeneralTailer(pSlotBase, uSlotLength); - - if (ZLog::IsDebug()) - { - WriteFile("./.zsign_debug/Entitlements.slot", (const char *)pSlotBase, uSlotLength); - WriteFile("./.zsign_debug/Entitlements.plist", (const char *)pSlotBase + 8, uSlotLength - 8); - } - return true; -} - -bool SlotParseDerEntitlements(uint8_t *pSlotBase, CS_BlobIndex *pbi) -{ - uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_DER_ENTITLEMENTS", pSlotBase, pbi); - if (uSlotLength < 8) - { - return false; - } - - SlotParseGeneralTailer(pSlotBase, uSlotLength); - - if (ZLog::IsDebug()) - { - WriteFile("./.zsign_debug/Entitlements.der.slot", (const char *)pSlotBase, uSlotLength); - } - return true; -} - -bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput) -{ - strOutput.clear(); - if (strEntitlements.empty()) - { - return false; - } - - uint32_t uMagic = BE(CSMAGIC_EMBEDDED_ENTITLEMENTS); - uint32_t uLength = BE((uint32_t)strEntitlements.size() + 8); - - strOutput.append((const char *)&uMagic, sizeof(uMagic)); - strOutput.append((const char *)&uLength, sizeof(uLength)); - strOutput.append(strEntitlements.data(), strEntitlements.size()); - - return true; -} - -bool SlotBuildDerEntitlements(const string &strEntitlements, string &strOutput) -{ - strOutput.clear(); - if (strEntitlements.empty()) - { - return false; - } - - JValue jvInfo; - jvInfo.readPList(strEntitlements); - - string strRawEntitlementsData = _DER(jvInfo); - uint32_t uMagic = BE(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS); - uint32_t uLength = BE((uint32_t)strRawEntitlementsData.size() + 8); - - strOutput.append((const char *)&uMagic, sizeof(uMagic)); - strOutput.append((const char *)&uLength, sizeof(uLength)); - strOutput.append(strRawEntitlementsData.data(), strRawEntitlementsData.size()); - - return true; -} - -bool SlotParseCodeDirectory(uint8_t *pSlotBase, CS_BlobIndex *pbi) -{ - uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_CODEDIRECTORY", pSlotBase, pbi); - if (uSlotLength < 8) - { - return false; - } - - vector arrCodeSlots; - vector arrSpecialSlots; - CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); - uint8_t *pHashes = pSlotBase + LE(cdHeader.hashOffset); - for (uint32_t i = 0; i < LE(cdHeader.nCodeSlots); i++) - { - arrCodeSlots.push_back(pHashes + cdHeader.hashSize * i); - } - for (uint32_t i = 0; i < LE(cdHeader.nSpecialSlots); i++) - { - arrSpecialSlots.push_back(pHashes - cdHeader.hashSize * (i + 1)); - } - - ZLog::PrintV("\tversion: \t0x%x\n", LE(cdHeader.version)); - ZLog::PrintV("\tflags: \t\t%u\n", LE(cdHeader.flags)); - ZLog::PrintV("\thashOffset: \t%u\n", LE(cdHeader.hashOffset)); - ZLog::PrintV("\tidentOffset: \t%u\n", LE(cdHeader.identOffset)); - ZLog::PrintV("\tnSpecialSlots: \t%u\n", LE(cdHeader.nSpecialSlots)); - ZLog::PrintV("\tnCodeSlots: \t%u\n", LE(cdHeader.nCodeSlots)); - ZLog::PrintV("\tcodeLimit: \t%u\n", LE(cdHeader.codeLimit)); - ZLog::PrintV("\thashSize: \t%u\n", cdHeader.hashSize); - ZLog::PrintV("\thashType: \t%u\n", cdHeader.hashType); - ZLog::PrintV("\tspare1: \t%u\n", cdHeader.spare1); - ZLog::PrintV("\tpageSize: \t%u\n", cdHeader.pageSize); - ZLog::PrintV("\tspare2: \t%u\n", LE(cdHeader.spare2)); - - uint32_t uVersion = LE(cdHeader.version); - if (uVersion >= 0x20100) - { - ZLog::PrintV("\tscatterOffset: \t%u\n", LE(cdHeader.scatterOffset)); - } - if (uVersion >= 0x20200) - { - ZLog::PrintV("\tteamOffset: \t%u\n", LE(cdHeader.teamOffset)); - } - if (uVersion >= 0x20300) - { - ZLog::PrintV("\tspare3: \t%u\n", LE(cdHeader.spare3)); - ZLog::PrintV("\tcodeLimit64: \t%llu\n", LE(cdHeader.codeLimit64)); - } - if (uVersion >= 0x20400) - { - ZLog::PrintV("\texecSegBase: \t%llu\n", LE(cdHeader.execSegBase)); - ZLog::PrintV("\texecSegLimit: \t%llu\n", LE(cdHeader.execSegLimit)); - ZLog::PrintV("\texecSegFlags: \t%llu\n", LE(cdHeader.execSegFlags)); - } - - ZLog::PrintV("\tidentifier: \t%s\n", pSlotBase + LE(cdHeader.identOffset)); - if (uVersion >= 0x20200) - { - ZLog::PrintV("\tteamid: \t%s\n", pSlotBase + LE(cdHeader.teamOffset)); - } - - ZLog::PrintV("\tSpecialSlots:\n"); - for (int i = LE(cdHeader.nSpecialSlots) - 1; i >= 0; i--) - { - const char *suffix = "\t\n"; - switch (i) - { - case 0: - suffix = "\tInfo.plist\n"; - break; - case 1: - suffix = "\tRequirements Slot\n"; - break; - case 2: - suffix = "\tCodeResources\n"; - break; - case 3: - suffix = "\tApplication Specific\n"; - break; - case 4: - suffix = "\tEntitlements Slot\n"; - break; - case 6: - suffix = "\tEntitlements(DER) Slot\n"; - break; - } - PrintSHASum("\t\t\t", arrSpecialSlots[i], cdHeader.hashSize, suffix); - } - - if (ZLog::IsDebug()) - { - ZLog::Print("\tCodeSlots:\n"); - for (uint32_t i = 0; i < LE(cdHeader.nCodeSlots); i++) - { - PrintSHASum("\t\t\t", arrCodeSlots[i], cdHeader.hashSize); - } - } - else - { - ZLog::Print("\tCodeSlots: \tomitted. (use -d option for details)\n"); - } - - SlotParseGeneralTailer(pSlotBase, uSlotLength); - - if (ZLog::IsDebug()) - { - if (1 == cdHeader.hashType) - { - WriteFile("./.zsign_debug/CodeDirectory_SHA1.slot", (const char *)pSlotBase, uSlotLength); - } - else if (2 == cdHeader.hashType) - { - WriteFile("./.zsign_debug/CodeDirectory_SHA256.slot", (const char *)pSlotBase, uSlotLength); - } - } - - return true; -} - -bool SlotBuildCodeDirectory(bool bAlternate, - uint8_t *pCodeBase, - uint32_t uCodeLength, - uint8_t *pCodeSlotsData, - uint32_t uCodeSlotsDataLength, - uint64_t execSegLimit, - uint64_t execSegFlags, - const string &strBundleId, - const string &strTeamId, - const string &strInfoPlistSHA, - const string &strRequirementsSlotSHA, - const string &strCodeResourcesSHA, - const string &strEntitlementsSlotSHA, - const string &strDerEntitlementsSlotSHA, - bool isExecuteArch, - string &strOutput) -{ - strOutput.clear(); - if (NULL == pCodeBase || uCodeLength <= 0 || strBundleId.empty() || strTeamId.empty()) - { - return false; - } - - uint32_t uVersion = 0x20400; - - CS_CodeDirectory cdHeader; - memset(&cdHeader, 0, sizeof(cdHeader)); - cdHeader.magic = BE(CSMAGIC_CODEDIRECTORY); - cdHeader.length = 0; - cdHeader.version = BE(uVersion); - cdHeader.flags = 0; - cdHeader.hashOffset = 0; - cdHeader.identOffset = 0; - cdHeader.nSpecialSlots = 0; - cdHeader.nCodeSlots = 0; - cdHeader.codeLimit = BE(uCodeLength); - cdHeader.hashSize = bAlternate ? 32 : 20; - cdHeader.hashType = bAlternate ? 2 : 1; - cdHeader.spare1 = 0; - cdHeader.pageSize = 12; - cdHeader.spare2 = 0; - cdHeader.scatterOffset = 0; - cdHeader.teamOffset = 0; - cdHeader.execSegBase = 0; - cdHeader.execSegLimit = BE(execSegLimit); - cdHeader.execSegFlags = BE(execSegFlags); - - string strEmptySHA; - strEmptySHA.append(cdHeader.hashSize, 0); - vector arrSpecialSlots; - - if (isExecuteArch) - { - arrSpecialSlots.push_back(strDerEntitlementsSlotSHA.empty() ? strEmptySHA : strDerEntitlementsSlotSHA); - arrSpecialSlots.push_back(strEmptySHA); - } - arrSpecialSlots.push_back(strEntitlementsSlotSHA.empty() ? strEmptySHA : strEntitlementsSlotSHA); - arrSpecialSlots.push_back(strEmptySHA); - arrSpecialSlots.push_back(strCodeResourcesSHA.empty() ? strEmptySHA : strCodeResourcesSHA); - arrSpecialSlots.push_back(strRequirementsSlotSHA.empty() ? strEmptySHA : strRequirementsSlotSHA); - arrSpecialSlots.push_back(strInfoPlistSHA.empty() ? strEmptySHA : strInfoPlistSHA); - - uint32_t uPageSize = (uint32_t)pow(2, cdHeader.pageSize); - uint32_t uPages = uCodeLength / uPageSize; - uint32_t uRemain = uCodeLength % uPageSize; - uint32_t uCodeSlots = uPages + (uRemain > 0 ? 1 : 0); - - uint32_t uHeaderLength = 44; - if (uVersion >= 0x20100) - { - uHeaderLength += sizeof(cdHeader.scatterOffset); - } - if (uVersion >= 0x20200) - { - uHeaderLength += sizeof(cdHeader.teamOffset); - } - if (uVersion >= 0x20300) - { - uHeaderLength += sizeof(cdHeader.spare3); - uHeaderLength += sizeof(cdHeader.codeLimit64); - } - if (uVersion >= 0x20400) - { - uHeaderLength += sizeof(cdHeader.execSegBase); - uHeaderLength += sizeof(cdHeader.execSegLimit); - uHeaderLength += sizeof(cdHeader.execSegFlags); - } - - uint32_t uBundleIDLength = strBundleId.size() + 1; - uint32_t uTeamIDLength = strTeamId.size() + 1; - uint32_t uSpecialSlotsLength = arrSpecialSlots.size() * cdHeader.hashSize; - uint32_t uCodeSlotsLength = uCodeSlots * cdHeader.hashSize; - - uint32_t uSlotLength = uHeaderLength + uBundleIDLength + uSpecialSlotsLength + uCodeSlotsLength; - if (uVersion >= 0x20100) - { - //todo - } - if (uVersion >= 0x20200) - { - uSlotLength += uTeamIDLength; - } - - cdHeader.length = BE(uSlotLength); - cdHeader.identOffset = BE(uHeaderLength); - cdHeader.nSpecialSlots = BE((uint32_t)arrSpecialSlots.size()); - cdHeader.nCodeSlots = BE(uCodeSlots); - - uint32_t uHashOffset = uHeaderLength + uBundleIDLength + uSpecialSlotsLength; - if (uVersion >= 0x20100) - { - //todo - } - if (uVersion >= 0x20200) - { - uHashOffset += uTeamIDLength; - cdHeader.teamOffset = BE(uHeaderLength + uBundleIDLength); - } - cdHeader.hashOffset = BE(uHashOffset); - - strOutput.append((const char *)&cdHeader, uHeaderLength); - strOutput.append(strBundleId.data(), strBundleId.size() + 1); - if (uVersion >= 0x20100) - { - //todo - } - if (uVersion >= 0x20200) - { - strOutput.append(strTeamId.data(), strTeamId.size() + 1); - } - - for (uint32_t i = 0; i < LE(cdHeader.nSpecialSlots); i++) - { - strOutput.append(arrSpecialSlots[i].data(), arrSpecialSlots[i].size()); - } - - if (NULL != pCodeSlotsData && (uCodeSlotsDataLength == uCodeSlots * cdHeader.hashSize)) - { //use exists - strOutput.append((const char *)pCodeSlotsData, uCodeSlotsDataLength); - } - else - { - for (uint32_t i = 0; i < uPages; i++) - { - string strSHASum; - SHASum(cdHeader.hashType, pCodeBase + uPageSize * i, uPageSize, strSHASum); - strOutput.append(strSHASum.data(), strSHASum.size()); - } - if (uRemain > 0) - { - string strSHASum; - SHASum(cdHeader.hashType, pCodeBase + uPageSize * uPages, uRemain, strSHASum); - strOutput.append(strSHASum.data(), strSHASum.size()); - } - } - - return true; -} - -bool SlotParseCMSSignature(uint8_t *pSlotBase, CS_BlobIndex *pbi) -{ - uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_SIGNATURESLOT", pSlotBase, pbi); - if (uSlotLength < 8) - { - return false; - } - - JValue jvInfo; - GetCMSInfo(pSlotBase + 8, uSlotLength - 8, jvInfo); - //ZLog::PrintV("%s\n", jvInfo.styleWrite().c_str()); - - ZLog::Print("\tCertificates: \n"); - for (size_t i = 0; i < jvInfo["certs"].size(); i++) - { - ZLog::PrintV("\t\t\t%s\t<=\t%s\n", jvInfo["certs"][i]["Subject"]["CN"].asCString(), jvInfo["certs"][i]["Issuer"]["CN"].asCString()); - } - - ZLog::Print("\tSignedAttrs: \n"); - if (jvInfo["attrs"].has("ContentType")) - { - ZLog::PrintV("\t ContentType: \t%s => %s\n", jvInfo["attrs"]["ContentType"]["obj"].asCString(), jvInfo["attrs"]["ContentType"]["data"].asCString()); - } - - if (jvInfo["attrs"].has("SigningTime")) - { - ZLog::PrintV("\t SigningTime: \t%s => %s\n", jvInfo["attrs"]["SigningTime"]["obj"].asCString(), jvInfo["attrs"]["SigningTime"]["data"].asCString()); - } - - if (jvInfo["attrs"].has("MessageDigest")) - { - ZLog::PrintV("\t MsgDigest: \t%s => %s\n", jvInfo["attrs"]["MessageDigest"]["obj"].asCString(), jvInfo["attrs"]["MessageDigest"]["data"].asCString()); - } - - if (jvInfo["attrs"].has("CDHashes")) - { - string strData = jvInfo["attrs"]["CDHashes"]["data"].asCString(); - StringReplace(strData, "\n", "\n\t\t\t\t"); - ZLog::PrintV("\t CDHashes: \t%s => \n\t\t\t\t%s\n", jvInfo["attrs"]["CDHashes"]["obj"].asCString(), strData.c_str()); - } - - if (jvInfo["attrs"].has("CDHashes2")) - { - ZLog::PrintV("\t CDHashes2: \t%s => \n", jvInfo["attrs"]["CDHashes2"]["obj"].asCString()); - for (size_t i = 0; i < jvInfo["attrs"]["CDHashes2"]["data"].size(); i++) - { - ZLog::PrintV("\t\t\t\t%s\n", jvInfo["attrs"]["CDHashes2"]["data"][i].asCString()); - } - } - - for (size_t i = 0; i < jvInfo["attrs"]["unknown"].size(); i++) - { - JValue &jvAttr = jvInfo["attrs"]["unknown"][i]; - ZLog::PrintV("\t UnknownAttr: \t%s => %s, type: %d, count: %d\n", jvAttr["obj"].asCString(), jvAttr["name"].asCString(), jvAttr["type"].asInt(), jvAttr["count"].asInt()); - } - ZLog::Print("\n"); - - SlotParseGeneralTailer(pSlotBase, uSlotLength); - - if (ZLog::IsDebug()) - { - WriteFile("./.zsign_debug/CMSSignature.slot", (const char *)pSlotBase, uSlotLength); - WriteFile("./.zsign_debug/CMSSignature.der", (const char *)pSlotBase + 8, uSlotLength - 8); - } - return true; -} - -bool SlotBuildCMSSignature(ZSignAsset *pSignAsset, - const string &strCodeDirectorySlot, - const string &strAltnateCodeDirectorySlot, - string &strOutput) -{ - strOutput.clear(); - - JValue jvHashes; - string strCDHashesPlist; - string strCodeDirectorySlotSHA1; - string strAltnateCodeDirectorySlot256; - SHASum(E_SHASUM_TYPE_1, strCodeDirectorySlot, strCodeDirectorySlotSHA1); - SHASum(E_SHASUM_TYPE_256, strAltnateCodeDirectorySlot, strAltnateCodeDirectorySlot256); - - size_t cdHashSize = strCodeDirectorySlotSHA1.size(); - jvHashes["cdhashes"][0].assignData(strCodeDirectorySlotSHA1.data(), cdHashSize); - jvHashes["cdhashes"][1].assignData(strAltnateCodeDirectorySlot256.data(), cdHashSize); - jvHashes.writePList(strCDHashesPlist); - - string strCMSData; - if (!pSignAsset->GenerateCMS(strCodeDirectorySlot, strCDHashesPlist, strCodeDirectorySlotSHA1, strAltnateCodeDirectorySlot256, strCMSData)) - { - return false; - } - - uint32_t uMagic = BE(CSMAGIC_BLOBWRAPPER); - uint32_t uLength = BE((uint32_t)strCMSData.size() + 8); - - strOutput.append((const char *)&uMagic, sizeof(uMagic)); - strOutput.append((const char *)&uLength, sizeof(uLength)); - strOutput.append(strCMSData.data(), strCMSData.size()); - return true; -} - -uint32_t GetCodeSignatureLength(uint8_t *pCSBase) -{ - CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; - if (NULL != psb && CSMAGIC_EMBEDDED_SIGNATURE == LE(psb->magic)) - { - return LE(psb->length); - } - return 0; -} - -bool ParseCodeSignature(uint8_t *pCSBase) -{ - CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; - if (NULL == psb || CSMAGIC_EMBEDDED_SIGNATURE != LE(psb->magic)) - { - return false; - } - - ZLog::PrintV("\n>>> CodeSignature Segment: \n"); - ZLog::PrintV("\tmagic: \t\t0x%x\n", LE(psb->magic)); - ZLog::PrintV("\tlength: \t%d\n", LE(psb->length)); - ZLog::PrintV("\tslots: \t\t%d\n", LE(psb->count)); - - CS_BlobIndex *pbi = (CS_BlobIndex *)(pCSBase + sizeof(CS_SuperBlob)); - for (uint32_t i = 0; i < LE(psb->count); i++, pbi++) - { - uint8_t *pSlotBase = pCSBase + LE(pbi->offset); - switch (LE(pbi->type)) - { - case CSSLOT_CODEDIRECTORY: - SlotParseCodeDirectory(pSlotBase, pbi); - break; - case CSSLOT_REQUIREMENTS: - SlotParseRequirements(pSlotBase, pbi); - break; - case CSSLOT_ENTITLEMENTS: - SlotParseEntitlements(pSlotBase, pbi); - break; - case CSSLOT_DER_ENTITLEMENTS: - SlotParseDerEntitlements(pSlotBase, pbi); - break; - case CSSLOT_ALTERNATE_CODEDIRECTORIES: - SlotParseCodeDirectory(pSlotBase, pbi); - break; - case CSSLOT_SIGNATURESLOT: - SlotParseCMSSignature(pSlotBase, pbi); - break; - case CSSLOT_IDENTIFICATIONSLOT: - SlotParseGeneralHeader("CSSLOT_IDENTIFICATIONSLOT", pSlotBase, pbi); - break; - case CSSLOT_TICKETSLOT: - SlotParseGeneralHeader("CSSLOT_TICKETSLOT", pSlotBase, pbi); - break; - default: - SlotParseGeneralTailer(pSlotBase, SlotParseGeneralHeader("CSSLOT_UNKNOWN", pSlotBase, pbi)); - break; - } - } - - if (ZLog::IsDebug()) - { - WriteFile("./.zsign_debug/CodeSignature.blob", (const char *)pCSBase, LE(psb->length)); - } - return true; -} - -bool SlotGetCodeSlotsData(uint8_t *pSlotBase, uint8_t *&pCodeSlots, uint32_t &uCodeSlotsLength) -{ - uint32_t uSlotLength = LE(*(((uint32_t *)pSlotBase) + 1)); - if (uSlotLength < 8) - { - return false; - } - CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); - pCodeSlots = pSlotBase + LE(cdHeader.hashOffset); - uCodeSlotsLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; - return true; -} - -bool GetCodeSignatureExistsCodeSlotsData(uint8_t *pCSBase, - uint8_t *&pCodeSlots1Data, - uint32_t &uCodeSlots1DataLength, - uint8_t *&pCodeSlots256Data, - uint32_t &uCodeSlots256DataLength) -{ - pCodeSlots1Data = NULL; - pCodeSlots256Data = NULL; - uCodeSlots1DataLength = 0; - uCodeSlots256DataLength = 0; - CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; - if (NULL == psb || CSMAGIC_EMBEDDED_SIGNATURE != LE(psb->magic)) - { - return false; - } - - CS_BlobIndex *pbi = (CS_BlobIndex *)(pCSBase + sizeof(CS_SuperBlob)); - for (uint32_t i = 0; i < LE(psb->count); i++, pbi++) - { - uint8_t *pSlotBase = pCSBase + LE(pbi->offset); - switch (LE(pbi->type)) - { - case CSSLOT_CODEDIRECTORY: - { - CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); - if (LE(cdHeader.length) > 8) - { - pCodeSlots1Data = pSlotBase + LE(cdHeader.hashOffset); - uCodeSlots1DataLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; - } - } - break; - case CSSLOT_ALTERNATE_CODEDIRECTORIES: - { - CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); - if (LE(cdHeader.length) > 8) - { - pCodeSlots256Data = pSlotBase + LE(cdHeader.hashOffset); - uCodeSlots256DataLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; - } - } - break; - default: - break; - } - } - - return ((NULL != pCodeSlots1Data) && (NULL != pCodeSlots256Data) && uCodeSlots1DataLength > 0 && uCodeSlots256DataLength > 0); -} diff --git a/zsign/signing.h b/zsign/signing.h deleted file mode 100644 index df026fc..0000000 --- a/zsign/signing.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "openssl.h" - -bool ParseCodeSignature(uint8_t *pCSBase); -bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput); -bool SlotBuildDerEntitlements(const string &strEntitlements, string &strOutput); -bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput); -bool GetCodeSignatureCodeSlotsData(uint8_t *pCSBase, uint8_t *&pCodeSlots1, uint32_t &uCodeSlots1Length, uint8_t *&pCodeSlots256, uint32_t &uCodeSlots256Length); -bool SlotBuildCodeDirectory(bool bAlternate, - uint8_t *pCodeBase, - uint32_t uCodeLength, - uint8_t *pCodeSlotsData, - uint32_t uCodeSlotsDataLength, - uint64_t execSegLimit, - uint64_t execSegFlags, - const string &strBundleId, - const string &strTeamId, - const string &strInfoPlistSHA, - const string &strRequirementsSlotSHA, - const string &strCodeResourcesSHA, - const string &strEntitlementsSlotSHA, - const string &strDerEntitlementsSlotSHA, - bool isExecuteArch, - string &strOutput); -bool SlotBuildCMSSignature(ZSignAsset *pSignAsset, - const string &strCodeDirectorySlot, - const string &strAltnateCodeDirectorySlot, - string &strOutput); -bool GetCodeSignatureExistsCodeSlotsData(uint8_t *pCSBase, - uint8_t *&pCodeSlots1Data, - uint32_t &uCodeSlots1DataLength, - uint8_t *&pCodeSlots256Data, - uint32_t &uCodeSlots256DataLength); -uint32_t GetCodeSignatureLength(uint8_t *pCSBase); diff --git a/zsign/zsign.hpp b/zsign/zsign.hpp deleted file mode 100644 index e3446c2..0000000 --- a/zsign/zsign.hpp +++ /dev/null @@ -1,44 +0,0 @@ -// -// zsign.hpp -// feather -// -// Created by HAHALOSAH on 5/22/24. -// - -#ifndef zsign_hpp -#define zsign_hpp - -#include -#import - -#ifdef __cplusplus -extern "C" { -#endif - -bool InjectDyLib(NSString *filePath, - NSString *dylibPath, - bool weakInject, - bool bCreate); - -bool ChangeDylibPath(NSString *filePath, - NSString *oldPath, - NSString *newPath); - -bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray); -bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray); - -void zsign(NSString *appPath, - NSString* execName, - NSData *prov, - NSData *key, - NSString *pass, - NSProgress* progress, - void(^completionHandler)(BOOL success, NSError *error) - ); - - -#ifdef __cplusplus -} -#endif - -#endif /* zsign_hpp */ diff --git a/zsign/zsign.mm b/zsign/zsign.mm deleted file mode 100644 index 62b055e..0000000 --- a/zsign/zsign.mm +++ /dev/null @@ -1,232 +0,0 @@ -#include "zsign.hpp" -#include "common/common.h" -#include "common/json.h" -#include "openssl.h" -#include "macho.h" -#include "bundle.h" -#include -#include -#include -#include - -NSString* getTmpDir() { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); - return [[[paths objectAtIndex:0] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"tmp"]; -} - -extern "C" { - -bool InjectDyLib(NSString *filePath, NSString *dylibPath, bool weakInject, bool bCreate) { - ZTimer gtimer; - @autoreleasepool { - // Convert NSString to std::string - std::string filePathStr = [filePath UTF8String]; - std::string dylibPathStr = [dylibPath UTF8String]; - - ZMachO machO; - bool initSuccess = machO.Init(filePathStr.c_str()); - if (!initSuccess) { - gtimer.Print(">>> Failed to initialize ZMachO."); - return false; - } - - bool success = machO.InjectDyLib(weakInject, dylibPathStr.c_str(), bCreate); - - machO.Free(); - - if (success) { - gtimer.Print(">>> Dylib injected successfully!"); - return true; - } else { - gtimer.Print(">>> Failed to inject dylib."); - return false; - } - } -} - -bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray) { - ZTimer gtimer; - @autoreleasepool { - // Convert NSString to std::string - std::string filePathStr = [filePath UTF8String]; - - ZMachO machO; - bool initSuccess = machO.Init(filePathStr.c_str()); - if (!initSuccess) { - gtimer.Print(">>> Failed to initialize ZMachO."); - return false; - } - - std::vector dylibPaths = machO.ListDylibs(); - - if (!dylibPaths.empty()) { - gtimer.Print(">>> List of dylibs in the Mach-O file:"); - for (vector::iterator it = dylibPaths.begin(); it < dylibPaths.end(); ++it) { - std::string dylibPath = *it; - NSString *dylibPathStr = [NSString stringWithUTF8String:dylibPath.c_str()]; - [dylibPathsArray addObject:dylibPathStr]; - } - } else { - gtimer.Print(">>> No dylibs found in the Mach-O file."); - } - - machO.Free(); - - return true; - } -} - -bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray) { - ZTimer gtimer; - @autoreleasepool { - std::string filePathStr = [filePath UTF8String]; - std::set dylibsToRemove; - - for (NSString *dylibPath in dylibPathsArray) { - dylibsToRemove.insert([dylibPath UTF8String]); - } - - ZMachO machO; - bool initSuccess = machO.Init(filePathStr.c_str()); - if (!initSuccess) { - gtimer.Print(">>> Failed to initialize ZMachO."); - return false; - } - - machO.RemoveDylib(dylibsToRemove); - - machO.Free(); - - gtimer.Print(">>> Dylibs uninstalled successfully!"); - return true; - } -} - - - -bool ChangeDylibPath(NSString *filePath, NSString *oldPath, NSString *newPath) { - ZTimer gtimer; - @autoreleasepool { - // Convert NSString to std::string - std::string filePathStr = [filePath UTF8String]; - std::string oldPathStr = [oldPath UTF8String]; - std::string newPathStr = [newPath UTF8String]; - - ZMachO machO; - bool initSuccess = machO.Init(filePathStr.c_str()); - if (!initSuccess) { - gtimer.Print(">>> Failed to initialize ZMachO."); - return false; - } - - bool success = machO.ChangeDylibPath(oldPathStr.c_str(), newPathStr.c_str()); - - machO.Free(); - - if (success) { - gtimer.Print(">>> Dylib path changed successfully!"); - return true; - } else { - gtimer.Print(">>> Failed to change dylib path."); - return false; - } - } -} - -NSError* makeErrorFromLog(const std::vector& vec) { - NSMutableString *result = [NSMutableString string]; - - for (size_t i = 0; i < vec.size(); ++i) { - // Convert each std::string to NSString - NSString *str = [NSString stringWithUTF8String:vec[i].c_str()]; - [result appendString:str]; - - // Append newline if it's not the last element - if (i != vec.size() - 1) { - [result appendString:@"\n"]; - } - } - - NSDictionary* userInfo = @{ - NSLocalizedDescriptionKey : result - }; - return [NSError errorWithDomain:@"Failed to Sign" code:-1 userInfo:userInfo]; -} - -ZSignAsset zSignAsset; - -void zsign(NSString *appPath, - NSString* execName, - NSData *prov, - NSData *key, - NSString *pass, - NSProgress* progress, - void(^completionHandler)(BOOL success, NSError *error) - ) -{ - ZTimer gtimer; - ZTimer timer; - timer.Reset(); - - bool bForce = false; - bool bWeakInject = false; - bool bDontGenerateEmbeddedMobileProvision = YES; - - string strPassword; - - string strDyLibFile; - string strOutputFile; - - string strEntitlementsFile; - - bForce = true; - const char* strPKeyFileData = (const char*)[key bytes]; - const char* strProvFileData = (const char*)[prov bytes]; - strPassword = [pass cStringUsingEncoding:NSUTF8StringEncoding]; - - - string strPath = [appPath cStringUsingEncoding:NSUTF8StringEncoding]; - string execNameStr = [execName cStringUsingEncoding:NSUTF8StringEncoding]; - - bool _ = ZLog::logs.empty(); - - __block ZSignAsset zSignAsset; - - if (!zSignAsset.InitSimple(strPKeyFileData, (int)[key length], strProvFileData, (int)[prov length], strPassword)) { - completionHandler(NO, makeErrorFromLog(ZLog::logs)); - bool _ = ZLog::logs.empty(); - return; - } - - bool bEnableCache = true; - string strFolder = strPath; - - __block ZAppBundle bundle; - bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, execNameStr, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); - - if(!success) { - completionHandler(NO, makeErrorFromLog(ZLog::logs)); - bool _ = ZLog::logs.empty(); - return; - } - - int filesNeedToSign = bundle.GetSignCount(); - [progress setTotalUnitCount:filesNeedToSign]; - bundle.progressHandler = [&progress] { - [progress setCompletedUnitCount:progress.completedUnitCount + 1]; - }; - - - - - ZLog::PrintV(">>> Files Need to Sign: \t%d\n", filesNeedToSign); - bool bRet = bundle.StartSign(bEnableCache); - timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed"); - gtimer.Print(">>> Done."); - completionHandler(YES, nil); - _ = ZLog::logs.empty(); - - return; -} - -} diff --git a/zsign/zsigner.h b/zsign/zsigner.h deleted file mode 100644 index 8c5ce4d..0000000 --- a/zsign/zsigner.h +++ /dev/null @@ -1,11 +0,0 @@ -// -// zsigner.h -// LiveContainer -// -// Created by s s on 2024/11/10. -// -#import - -@interface ZSigner : NSObject -+ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; -@end diff --git a/zsign/zsigner.m b/zsign/zsigner.m deleted file mode 100644 index ffc7def..0000000 --- a/zsign/zsigner.m +++ /dev/null @@ -1,22 +0,0 @@ -// -// zsigner.m -// LiveContainer -// -// Created by s s on 2024/11/10. -// - -#import "zsigner.h" -#import "zsign.hpp" - -NSProgress* currentZSignProgress; - -@implementation ZSigner -+ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { - NSProgress* ans = [NSProgress progressWithTotalUnitCount:1000]; - NSLog(@"[LC] init sign!"); - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - zsign(appPath, execName, prov, key, pass, ans, completionHandler); - }); - return ans; -} -@end From f2bcee6da4162a07c988f11d2396ede2e63b854a Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 23 Nov 2024 14:16:12 +0800 Subject: [PATCH 05/32] rename esign --- ZSign/Makefile | 14 + ZSign/Utils.hpp | 26 + ZSign/Utils.mm | 38 + ZSign/archo.cpp | 860 +++++++++++ ZSign/archo.h | 42 + ZSign/bundle.cpp | 710 +++++++++ ZSign/bundle.h | 42 + ZSign/common/base64.cpp | 203 +++ ZSign/common/base64.h | 27 + ZSign/common/common.cpp | 849 +++++++++++ ZSign/common/common.h | 163 +++ ZSign/common/json.cpp | 3061 +++++++++++++++++++++++++++++++++++++++ ZSign/common/json.h | 414 ++++++ ZSign/common/mach-o.h | 580 ++++++++ ZSign/macho.cpp | 350 +++++ ZSign/macho.h | 34 + ZSign/openssl.cpp | 944 ++++++++++++ ZSign/openssl.h | 28 + ZSign/signing.cpp | 875 +++++++++++ ZSign/signing.h | 34 + ZSign/zsign.hpp | 44 + ZSign/zsign.mm | 232 +++ ZSign/zsigner.h | 11 + ZSign/zsigner.m | 22 + 24 files changed, 9603 insertions(+) create mode 100644 ZSign/Makefile create mode 100644 ZSign/Utils.hpp create mode 100644 ZSign/Utils.mm create mode 100644 ZSign/archo.cpp create mode 100644 ZSign/archo.h create mode 100644 ZSign/bundle.cpp create mode 100644 ZSign/bundle.h create mode 100644 ZSign/common/base64.cpp create mode 100644 ZSign/common/base64.h create mode 100644 ZSign/common/common.cpp create mode 100644 ZSign/common/common.h create mode 100644 ZSign/common/json.cpp create mode 100644 ZSign/common/json.h create mode 100644 ZSign/common/mach-o.h create mode 100644 ZSign/macho.cpp create mode 100644 ZSign/macho.h create mode 100644 ZSign/openssl.cpp create mode 100644 ZSign/openssl.h create mode 100644 ZSign/signing.cpp create mode 100644 ZSign/signing.h create mode 100644 ZSign/zsign.hpp create mode 100644 ZSign/zsign.mm create mode 100644 ZSign/zsigner.h create mode 100644 ZSign/zsigner.m diff --git a/ZSign/Makefile b/ZSign/Makefile new file mode 100644 index 0000000..605f331 --- /dev/null +++ b/ZSign/Makefile @@ -0,0 +1,14 @@ +TARGET := iphone:clang:latest:7.0 +ARCHS := arm64 +include $(THEOS)/makefiles/common.mk + +LIBRARY_NAME = ZSign + +ZSign_FILES = $(shell find . -name '*.cpp') $(shell find . -name '*.mm') zsigner.m +ZSign_CFLAGS = -fobjc-arc -Wno-deprecated -Wno-unused-variable -Wno-unused-but-set-variable -Wno-module-import-in-extern-c +ZSign_CCFLAGS = -std=c++11 +ZSign_FRAMEWORKS = OpenSSL +ZSign_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks + +include $(THEOS_MAKE_PATH)/library.mk + diff --git a/ZSign/Utils.hpp b/ZSign/Utils.hpp new file mode 100644 index 0000000..d451634 --- /dev/null +++ b/ZSign/Utils.hpp @@ -0,0 +1,26 @@ +// +// Utils.hpp +// feather +// +// Created by samara on 30.09.2024. +// + +#ifndef Utils_hpp +#define Utils_hpp + +#include + + +#ifdef __cplusplus +extern "C" { +#endif + +const char* getDocumentsDirectory(); +void writeToNSLog(const char* msg); +void refreshFile(const char* path); + +#ifdef __cplusplus +} +#endif + +#endif /* zsign_hpp */ diff --git a/ZSign/Utils.mm b/ZSign/Utils.mm new file mode 100644 index 0000000..332a2cc --- /dev/null +++ b/ZSign/Utils.mm @@ -0,0 +1,38 @@ +// +// Utils.cpp +// feather +// +// Created by samara on 30.09.2024. +// + +#include "Utils.hpp" +#import + +extern "C" { + +const char* getDocumentsDirectory() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths firstObject]; + const char *documentsPath = [documentsDirectory UTF8String]; + return documentsPath; +} + +void writeToNSLog(const char* msg) { + NSLog(@"[LC] singner msg: %s", msg); +} + +// copy, remove and rename back the file to prevent crash due to kernel signature cache +// see https://developer.apple.com/documentation/security/updating-mac-software +void refreshFile(const char* path) { + NSString* objcPath = @(path); + if(![NSFileManager.defaultManager fileExistsAtPath:objcPath]) { + return; + } + NSString* newPath = [NSString stringWithFormat:@"%s.tmp", path]; + NSError* error; + [NSFileManager.defaultManager copyItemAtPath:objcPath toPath:newPath error:&error]; + [NSFileManager.defaultManager removeItemAtPath:objcPath error:&error]; + [NSFileManager.defaultManager moveItemAtPath:newPath toPath:objcPath error:&error]; +} + +} diff --git a/ZSign/archo.cpp b/ZSign/archo.cpp new file mode 100644 index 0000000..f3de7d4 --- /dev/null +++ b/ZSign/archo.cpp @@ -0,0 +1,860 @@ +#include "common/common.h" +#include "common/json.h" +#include "archo.h" +#include "signing.h" + +static uint64_t execSegLimit = 0; + +ZArchO::ZArchO() +{ + m_pBase = NULL; + m_uLength = 0; + m_uCodeLength = 0; + m_pSignBase = NULL; + m_uSignLength = 0; + m_pHeader = NULL; + m_uHeaderSize = 0; + m_bEncrypted = false; + m_b64 = false; + m_bBigEndian = false; + m_bEnoughSpace = true; + m_pCodeSignSegment = NULL; + m_pLinkEditSegment = NULL; + m_uLoadCommandsFreeSpace = 0; +} + +bool ZArchO::Init(uint8_t *pBase, uint32_t uLength) +{ + if (NULL == pBase || uLength <= 0) + { + return false; + } + + m_pBase = pBase; + m_uLength = uLength; + m_uCodeLength = (uLength % 16 == 0) ? uLength : uLength + 16 - (uLength % 16); + m_pHeader = (mach_header *)m_pBase; + if (MH_MAGIC != m_pHeader->magic && MH_CIGAM != m_pHeader->magic && MH_MAGIC_64 != m_pHeader->magic && MH_CIGAM_64 != m_pHeader->magic) + { + return false; + } + + m_b64 = (MH_MAGIC_64 == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false; + m_bBigEndian = (MH_CIGAM == m_pHeader->magic || MH_CIGAM_64 == m_pHeader->magic) ? true : false; + m_uHeaderSize = m_b64 ? sizeof(mach_header_64) : sizeof(mach_header); + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + switch (BO(plc->cmd)) + { + case LC_SEGMENT: + { + segment_command *seglc = (segment_command *)pLoadCommand; + if (0 == strcmp("__TEXT", seglc->segname)) + { + execSegLimit = seglc->vmsize; + for (uint32_t j = 0; j < BO(seglc->nsects); j++) + { + section *sect = (section *)((pLoadCommand + sizeof(segment_command)) + sizeof(section) * j); + if (0 == strcmp("__text", sect->sectname)) + { + if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize)) + { + m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize; + } + } + else if (0 == strcmp("__info_plist", sect->sectname)) + { + m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO(sect->size)); + } + } + } + else if (0 == strcmp("__LINKEDIT", seglc->segname)) + { + m_pLinkEditSegment = pLoadCommand; + } + } + break; + case LC_SEGMENT_64: + { + segment_command_64 *seglc = (segment_command_64 *)pLoadCommand; + if (0 == strcmp("__TEXT", seglc->segname)) + { + execSegLimit = seglc->vmsize; + for (uint32_t j = 0; j < BO(seglc->nsects); j++) + { + section_64 *sect = (section_64 *)((pLoadCommand + sizeof(segment_command_64)) + sizeof(section_64) * j); + if (0 == strcmp("__text", sect->sectname)) + { + if (BO(sect->offset) > (BO(m_pHeader->sizeofcmds) + m_uHeaderSize)) + { + m_uLoadCommandsFreeSpace = BO(sect->offset) - BO(m_pHeader->sizeofcmds) - m_uHeaderSize; + } + } + else if (0 == strcmp("__info_plist", sect->sectname)) + { + m_strInfoPlist.append((const char *)m_pBase + BO(sect->offset), BO((uint32_t)sect->size)); + } + } + } + else if (0 == strcmp("__LINKEDIT", seglc->segname)) + { + m_pLinkEditSegment = pLoadCommand; + } + } + break; + case LC_ENCRYPTION_INFO: + case LC_ENCRYPTION_INFO_64: + { + encryption_info_command *crypt_cmd = (encryption_info_command *)pLoadCommand; + if (BO(crypt_cmd->cryptid) >= 1) + { + m_bEncrypted = true; + } + } + break; + case LC_CODE_SIGNATURE: + { + codesignature_command *pcslc = (codesignature_command *)pLoadCommand; + m_pCodeSignSegment = pLoadCommand; + m_uCodeLength = BO(pcslc->dataoff); + m_pSignBase = m_pBase + m_uCodeLength; + m_uSignLength = GetCodeSignatureLength(m_pSignBase); + } + break; + } + + pLoadCommand += BO(plc->cmdsize); + } + + return true; +} + +const char *ZArchO::GetArch(int cpuType, int cpuSubType) +{ + switch (cpuType) + { + case CPU_TYPE_ARM: + { + switch (cpuSubType) + { + case CPU_SUBTYPE_ARM_V6: + return "armv6"; + break; + case CPU_SUBTYPE_ARM_V7: + return "armv7"; + break; + case CPU_SUBTYPE_ARM_V7S: + return "armv7s"; + break; + case CPU_SUBTYPE_ARM_V7K: + return "armv7k"; + break; + case CPU_SUBTYPE_ARM_V8: + return "armv8"; + break; + } + } + break; + case CPU_TYPE_ARM64: + { + switch (cpuSubType) + { + case CPU_SUBTYPE_ARM64_ALL: + return "arm64"; + break; + case CPU_SUBTYPE_ARM64_V8: + return "arm64v8"; + break; + case 2: + return "arm64e"; + break; + } + } + break; + case CPU_TYPE_ARM64_32: + { + switch (cpuSubType) + { + case CPU_SUBTYPE_ARM64_ALL: + return "arm64_32"; + break; + case CPU_SUBTYPE_ARM64_32_V8: + return "arm64e_32"; + break; + } + } + break; + case CPU_TYPE_X86: + { + switch (cpuSubType) + { + default: + return "x86_32"; + break; + } + } + break; + case CPU_TYPE_X86_64: + { + switch (cpuSubType) + { + default: + return "x86_64"; + break; + } + } + break; + } + return "unknown"; +} + +const char *ZArchO::GetFileType(uint32_t uFileType) +{ + switch (uFileType) + { + case MH_OBJECT: + return "MH_OBJECT"; + break; + case MH_EXECUTE: + return "MH_EXECUTE"; + break; + case MH_FVMLIB: + return "MH_FVMLIB"; + break; + case MH_CORE: + return "MH_CORE"; + break; + case MH_PRELOAD: + return "MH_PRELOAD"; + break; + case MH_DYLIB: + return "MH_DYLIB"; + break; + case MH_DYLINKER: + return "MH_DYLINKER"; + break; + case MH_BUNDLE: + return "MH_BUNDLE"; + break; + case MH_DYLIB_STUB: + return "MH_DYLIB_STUB"; + break; + case MH_DSYM: + return "MH_DSYM"; + break; + case MH_KEXT_BUNDLE: + return "MH_KEXT_BUNDLE"; + break; + } + return "MH_UNKNOWN"; +} + +uint32_t ZArchO::BO(uint32_t uValue) +{ + return m_bBigEndian ? LE(uValue) : uValue; +} + +bool ZArchO::IsExecute() +{ + if (NULL != m_pHeader) + { + return (MH_EXECUTE == BO(m_pHeader->filetype)); + } + return false; +} + +void ZArchO::PrintInfo() +{ + if (NULL == m_pHeader) + { + return; + } + + ZLog::Print("------------------------------------------------------------------\n"); + ZLog::Print(">>> MachO Info: \n"); + ZLog::PrintV("\tFileType: \t%s\n", GetFileType(BO(m_pHeader->filetype))); + ZLog::PrintV("\tTotalSize: \t%u (%s)\n", m_uLength, FormatSize(m_uLength).c_str()); + ZLog::PrintV("\tPlatform: \t%u\n", m_b64 ? 64 : 32); + ZLog::PrintV("\tCPUArch: \t%s\n", GetArch(BO(m_pHeader->cputype), BO(m_pHeader->cpusubtype))); + ZLog::PrintV("\tCPUType: \t0x%x\n", BO(m_pHeader->cputype)); + ZLog::PrintV("\tCPUSubType: \t0x%x\n", BO(m_pHeader->cpusubtype)); + ZLog::PrintV("\tBigEndian: \t%d\n", m_bBigEndian); + ZLog::PrintV("\tEncrypted: \t%d\n", m_bEncrypted); + ZLog::PrintV("\tCommandCount: \t%d\n", BO(m_pHeader->ncmds)); + ZLog::PrintV("\tCodeLength: \t%d (%s)\n", m_uCodeLength, FormatSize(m_uCodeLength).c_str()); + ZLog::PrintV("\tSignLength: \t%d (%s)\n", m_uSignLength, FormatSize(m_uSignLength).c_str()); + ZLog::PrintV("\tSpareLength: \t%d (%s)\n", m_uLength - m_uCodeLength - m_uSignLength, FormatSize(m_uLength - m_uCodeLength - m_uSignLength).c_str()); + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + if (LC_VERSION_MIN_IPHONEOS == BO(plc->cmd)) + { + ZLog::PrintV("\tMIN_IPHONEOS: \t0x%x\n", *((uint32_t *)(pLoadCommand + sizeof(load_command)))); + } + else if (LC_RPATH == BO(plc->cmd)) + { + ZLog::PrintV("\tLC_RPATH: \t%s\n", (char *)(pLoadCommand + sizeof(load_command) + 4)); + } + pLoadCommand += BO(plc->cmdsize); + } + + bool bHasWeakDylib = false; + ZLog::PrintV("\tLC_LOAD_DYLIB: \n"); + pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + if (LC_LOAD_DYLIB == BO(plc->cmd)) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + ZLog::PrintV("\t\t\t%s\n", szDyLib); + } + else if (LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) + { + bHasWeakDylib = true; + } + pLoadCommand += BO(plc->cmdsize); + } + + if (bHasWeakDylib) + { + ZLog::PrintV("\tLC_LOAD_WEAK_DYLIB: \n"); + pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + if (LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + ZLog::PrintV("\t\t\t%s (weak)\n", szDyLib); + } + pLoadCommand += BO(plc->cmdsize); + } + } + + if (!m_strInfoPlist.empty()) + { + ZLog::Print("\n>>> Embedded Info.plist: \n"); + ZLog::PrintV("\tlength: \t%lu\n", m_strInfoPlist.size()); + + string strInfoPlist = m_strInfoPlist; + PWriter::StringReplace(strInfoPlist, "\n", "\n\t\t\t"); + ZLog::PrintV("\tcontent: \t%s\n", strInfoPlist.c_str()); + + PrintDataSHASum("\tSHA-1: \t", E_SHASUM_TYPE_1, m_strInfoPlist); + PrintDataSHASum("\tSHA-256:\t", E_SHASUM_TYPE_256, m_strInfoPlist); + } + + if (NULL == m_pSignBase || m_uSignLength <= 0) + { + ZLog::Warn(">>> Can't Find CodeSignature Segment!\n"); + } + else + { + ParseCodeSignature(m_pSignBase); + } + + ZLog::Print("------------------------------------------------------------------\n"); +} + +bool ZArchO::BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput) +{ + string strRequirementsSlot; + string strEntitlementsSlot; + string strDerEntitlementsSlot; + + // modified, we don't need Entitlement in LiveContainer. +// string strEmptyEntitlements = "\n\n\n\n\n"; + string strEmptyEntitlements = ""; + SlotBuildRequirements(strBundleId, pSignAsset->m_strSubjectCN, strRequirementsSlot); + SlotBuildEntitlements(IsExecute() ? pSignAsset->m_strEntitlementsData : strEmptyEntitlements, strEntitlementsSlot); + SlotBuildDerEntitlements(IsExecute() ? pSignAsset->m_strEntitlementsData : "", strDerEntitlementsSlot); + + string strRequirementsSlotSHA1; + string strRequirementsSlotSHA256; + if (strRequirementsSlot.empty()) + { //empty + strRequirementsSlotSHA1.append(20, 0); + strRequirementsSlotSHA256.append(32, 0); + } + else + { + SHASum(strRequirementsSlot, strRequirementsSlotSHA1, strRequirementsSlotSHA256); + } + + string strEntitlementsSlotSHA1; + string strEntitlementsSlotSHA256; + if (strEntitlementsSlot.empty()) + { //empty + strEntitlementsSlotSHA1.append(20, 0); + strEntitlementsSlotSHA256.append(32, 0); + } + else + { + SHASum(strEntitlementsSlot, strEntitlementsSlotSHA1, strEntitlementsSlotSHA256); + } + + string strDerEntitlementsSlotSHA1; + string strDerEntitlementsSlotSHA256; + if (strDerEntitlementsSlot.empty()) + { //empty + strDerEntitlementsSlotSHA1.append(20, 0); + strDerEntitlementsSlotSHA256.append(32, 0); + } + else + { + SHASum(strDerEntitlementsSlot, strDerEntitlementsSlotSHA1, strDerEntitlementsSlotSHA256); + } + + uint8_t *pCodeSlots1Data = NULL; + uint8_t *pCodeSlots256Data = NULL; + uint32_t uCodeSlots1DataLength = 0; + uint32_t uCodeSlots256DataLength = 0; + if (!bForce) + { + GetCodeSignatureExistsCodeSlotsData(m_pSignBase, pCodeSlots1Data, uCodeSlots1DataLength, pCodeSlots256Data, uCodeSlots256DataLength); + } + + uint64_t execSegFlags = 0; + if (NULL != strstr(strEntitlementsSlot.data() + 8, "get-task-allow")) + { + // TODO: Check if get-task-allow is actually set to true + execSegFlags = CS_EXECSEG_MAIN_BINARY | CS_EXECSEG_ALLOW_UNSIGNED; + } + + string strCMSSignatureSlot; + string strCodeDirectorySlot; + string strAltnateCodeDirectorySlot; + SlotBuildCodeDirectory(false, + m_pBase, + m_uCodeLength, + pCodeSlots1Data, + uCodeSlots1DataLength, + execSegLimit, + execSegFlags, + strBundleId, + pSignAsset->m_strTeamId, + strInfoPlistSHA1, + strRequirementsSlotSHA1, + strCodeResourcesSHA1, + strEntitlementsSlotSHA1, + strDerEntitlementsSlotSHA1, + IsExecute(), + strCodeDirectorySlot); + SlotBuildCodeDirectory(true, + m_pBase, + m_uCodeLength, + pCodeSlots256Data, + uCodeSlots256DataLength, + execSegLimit, + execSegFlags, + strBundleId, + pSignAsset->m_strTeamId, + strInfoPlistSHA256, + strRequirementsSlotSHA256, + strCodeResourcesSHA256, + strEntitlementsSlotSHA256, + strDerEntitlementsSlotSHA256, + IsExecute(), + strAltnateCodeDirectorySlot); + SlotBuildCMSSignature(pSignAsset, + strCodeDirectorySlot, + strAltnateCodeDirectorySlot, + strCMSSignatureSlot); + + uint32_t uCodeDirectorySlotLength = (uint32_t)strCodeDirectorySlot.size(); + uint32_t uRequirementsSlotLength = (uint32_t)strRequirementsSlot.size(); + uint32_t uEntitlementsSlotLength = (uint32_t)strEntitlementsSlot.size(); + uint32_t uDerEntitlementsLength = (uint32_t)strDerEntitlementsSlot.size(); + uint32_t uAltnateCodeDirectorySlotLength = (uint32_t)strAltnateCodeDirectorySlot.size(); + uint32_t uCMSSignatureSlotLength = (uint32_t)strCMSSignatureSlot.size(); + + uint32_t uCodeSignBlobCount = 0; + uCodeSignBlobCount += (uCodeDirectorySlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uRequirementsSlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uEntitlementsSlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uDerEntitlementsLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uAltnateCodeDirectorySlotLength > 0) ? 1 : 0; + uCodeSignBlobCount += (uCMSSignatureSlotLength > 0) ? 1 : 0; + + uint32_t uSuperBlobHeaderLength = sizeof(CS_SuperBlob) + uCodeSignBlobCount * sizeof(CS_BlobIndex); + uint32_t uCodeSignLength = uSuperBlobHeaderLength + + uCodeDirectorySlotLength + + uRequirementsSlotLength + + uEntitlementsSlotLength + + uDerEntitlementsLength + + uAltnateCodeDirectorySlotLength + + uCMSSignatureSlotLength; + + vector arrBlobIndexes; + if (uCodeDirectorySlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_CODEDIRECTORY); + blob.offset = BE(uSuperBlobHeaderLength); + arrBlobIndexes.push_back(blob); + } + if (uRequirementsSlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_REQUIREMENTS); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength); + arrBlobIndexes.push_back(blob); + } + if (uEntitlementsSlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_ENTITLEMENTS); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength); + arrBlobIndexes.push_back(blob); + } + if (uDerEntitlementsLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_DER_ENTITLEMENTS); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength); + arrBlobIndexes.push_back(blob); + } + if (uAltnateCodeDirectorySlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_ALTERNATE_CODEDIRECTORIES); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength + uDerEntitlementsLength); + arrBlobIndexes.push_back(blob); + } + if (uCMSSignatureSlotLength > 0) + { + CS_BlobIndex blob; + blob.type = BE(CSSLOT_SIGNATURESLOT); + blob.offset = BE(uSuperBlobHeaderLength + uCodeDirectorySlotLength + uRequirementsSlotLength + uEntitlementsSlotLength + uDerEntitlementsLength + uAltnateCodeDirectorySlotLength); + arrBlobIndexes.push_back(blob); + } + + CS_SuperBlob superblob; + superblob.magic = BE(CSMAGIC_EMBEDDED_SIGNATURE); + superblob.length = BE(uCodeSignLength); + superblob.count = BE(uCodeSignBlobCount); + + strOutput.clear(); + strOutput.reserve(uCodeSignLength); + strOutput.append((const char *)&superblob, sizeof(superblob)); + for (size_t i = 0; i < arrBlobIndexes.size(); i++) + { + CS_BlobIndex &blob = arrBlobIndexes[i]; + strOutput.append((const char *)&blob, sizeof(blob)); + } + strOutput += strCodeDirectorySlot; + strOutput += strRequirementsSlot; + strOutput += strEntitlementsSlot; + strOutput += strDerEntitlementsSlot; + strOutput += strAltnateCodeDirectorySlot; + strOutput += strCMSSignatureSlot; + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Requirements.slot.new", strRequirementsSlot); + WriteFile("./.zsign_debug/Entitlements.slot.new", strEntitlementsSlot); + WriteFile("./.zsign_debug/Entitlements.der.slot.new", strDerEntitlementsSlot); + WriteFile("./.zsign_debug/Entitlements.plist.new", strEntitlementsSlot.data() + 8, strEntitlementsSlot.size() - 8); + WriteFile("./.zsign_debug/CodeDirectory_SHA1.slot.new", strCodeDirectorySlot); + WriteFile("./.zsign_debug/CodeDirectory_SHA256.slot.new", strAltnateCodeDirectorySlot); + WriteFile("./.zsign_debug/CMSSignature.slot.new", strCMSSignatureSlot); + WriteFile("./.zsign_debug/CMSSignature.der.new", strCMSSignatureSlot.data() + 8, strCMSSignatureSlot.size() - 8); + WriteFile("./.zsign_debug/CodeSignature.blob.new", strOutput); + } + + return true; +} + +bool ZArchO::Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData) +{ + if (NULL == m_pSignBase) + { + m_bEnoughSpace = false; + ZLog::Warn(">>> Can't Find CodeSignature Segment!\n"); + return false; + } + + string strCodeResourcesSHA1; + string strCodeResourcesSHA256; + if (strCodeResourcesData.empty()) + { + strCodeResourcesSHA1.append(20, 0); + strCodeResourcesSHA256.append(32, 0); + } + else + { + SHASum(strCodeResourcesData, strCodeResourcesSHA1, strCodeResourcesSHA256); + } + + string strCodeSignBlob; + BuildCodeSignature(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesSHA1, strCodeResourcesSHA256, strCodeSignBlob); + if (strCodeSignBlob.empty()) + { + ZLog::Error(">>> Build CodeSignature Failed!\n"); + return false; + } + + int nSpaceLength = (int)m_uLength - (int)m_uCodeLength - (int)strCodeSignBlob.size(); + if (nSpaceLength < 0) + { + m_bEnoughSpace = false; + ZLog::WarnV(">>> No Enough CodeSignature Space. Length => Now: %d, Need: %d\n", (int)m_uLength - (int)m_uCodeLength, (int)strCodeSignBlob.size()); + return false; + } + + memcpy(m_pBase + m_uCodeLength, strCodeSignBlob.data(), strCodeSignBlob.size()); + //memset(m_pBase + m_uCodeLength + strCodeSignBlob.size(), 0, nSpaceLength); + return true; +} + +uint32_t ZArchO::ReallocCodeSignSpace(const string &strNewFile) +{ + RemoveFile(strNewFile.c_str()); + + uint32_t uNewLength = m_uCodeLength + ByteAlign(((m_uCodeLength / 4096) + 1) * (20 + 32), 4096) + 16384; //16K May Be Enough + if (NULL == m_pLinkEditSegment || uNewLength <= m_uLength) + { + return 0; + } + + load_command *pseglc = (load_command *)m_pLinkEditSegment; + switch (BO(pseglc->cmd)) + { + case LC_SEGMENT: + { + segment_command *seglc = (segment_command *)m_pLinkEditSegment; + seglc->vmsize = ByteAlign(BO(seglc->vmsize) + (uNewLength - m_uLength), 4096); + seglc->vmsize = BO(seglc->vmsize); + seglc->filesize = uNewLength - BO(seglc->fileoff); + seglc->filesize = BO(seglc->filesize); + } + break; + case LC_SEGMENT_64: + { + segment_command_64 *seglc = (segment_command_64 *)m_pLinkEditSegment; + seglc->vmsize = ByteAlign(BO((uint32_t)seglc->vmsize) + (uNewLength - m_uLength), 4096); + seglc->vmsize = BO((uint32_t)seglc->vmsize); + seglc->filesize = uNewLength - BO((uint32_t)seglc->fileoff); + seglc->filesize = BO((uint32_t)seglc->filesize); + } + break; + } + + codesignature_command *pcslc = (codesignature_command *)m_pCodeSignSegment; + if (NULL == pcslc) + { + if (m_uLoadCommandsFreeSpace < 4) + { + ZLog::Error(">>> Can't Find Free Space Of LoadCommands For CodeSignature!\n"); + return 0; + } + + pcslc = (codesignature_command *)(m_pBase + m_uHeaderSize + BO(m_pHeader->sizeofcmds)); + pcslc->cmd = BO(LC_CODE_SIGNATURE); + pcslc->cmdsize = BO((uint32_t)sizeof(codesignature_command)); + pcslc->dataoff = BO(m_uCodeLength); + m_pHeader->ncmds = BO(BO(m_pHeader->ncmds) + 1); + m_pHeader->sizeofcmds = BO(BO(m_pHeader->sizeofcmds) + sizeof(codesignature_command)); + } + pcslc->datasize = BO(uNewLength - m_uCodeLength); + + if (!AppendFile(strNewFile.c_str(), (const char *)m_pBase, m_uLength)) + { + return 0; + } + + string strPadding; + strPadding.append(uNewLength - m_uLength, 0); + if (!AppendFile(strNewFile.c_str(), strPadding)) + { + RemoveFile(strNewFile.c_str()); + return 0; + } + + return uNewLength; +} + +bool ZArchO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate) +{ + if (NULL == m_pHeader) + { + return false; + } + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + uint32_t uLoadType = BO(plc->cmd); + if (LC_LOAD_DYLIB == uLoadType || LC_LOAD_WEAK_DYLIB == uLoadType) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + if (0 == strcmp(szDyLib, szDyLibPath)) + { + if ((bWeakInject && (LC_LOAD_WEAK_DYLIB != uLoadType)) || (!bWeakInject && (LC_LOAD_DYLIB != uLoadType))) + { + dlc->cmd = BO((uint32_t)(bWeakInject ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB)); + ZLog::WarnV(">>> DyLib Load Type Changed! %s -> %s\n", (LC_LOAD_DYLIB == uLoadType) ? "LC_LOAD_DYLIB" : "LC_LOAD_WEAK_DYLIB", bWeakInject ? "LC_LOAD_WEAK_DYLIB" : "LC_LOAD_DYLIB"); + } + else + { + ZLog::WarnV(">>> DyLib Is Already Existed! %s\n"); + } + return true; + } + } + pLoadCommand += BO(plc->cmdsize); + } + + uint32_t uDylibPathLength = (uint32_t)strlen(szDyLibPath); + uint32_t uDylibPathPadding = (8 - uDylibPathLength % 8); + uint32_t uDyLibCommandSize = sizeof(dylib_command) + uDylibPathLength + uDylibPathPadding; + if (m_uLoadCommandsFreeSpace > 0 && m_uLoadCommandsFreeSpace < uDyLibCommandSize) // some bin doesn't have '__text' + { + ZLog::Error(">>> Can't Find Free Space Of LoadCommands For LC_LOAD_DYLIB Or LC_LOAD_WEAK_DYLIB!\n"); + return false; + } + + //add + dylib_command *dlc = (dylib_command *)(m_pBase + m_uHeaderSize + BO(m_pHeader->sizeofcmds)); + dlc->cmd = BO((uint32_t)(bWeakInject ? LC_LOAD_WEAK_DYLIB : LC_LOAD_DYLIB)); + dlc->cmdsize = BO(uDyLibCommandSize); + dlc->dylib.name.offset = BO((uint32_t)sizeof(dylib_command)); + dlc->dylib.timestamp = BO((uint32_t)2); + dlc->dylib.current_version = 0; + dlc->dylib.compatibility_version = 0; + + string strDylibPath = szDyLibPath; + strDylibPath.append(uDylibPathPadding, 0); + + uint8_t *pDyLibPath = (uint8_t *)dlc + sizeof(dylib_command); + memcpy(pDyLibPath, strDylibPath.data(), uDylibPathLength + uDylibPathPadding); + + m_pHeader->ncmds = BO(BO(m_pHeader->ncmds) + 1); + m_pHeader->sizeofcmds = BO(BO(m_pHeader->sizeofcmds) + uDyLibCommandSize); + + bCreate = true; + return true; +} + +bool ZArchO::ChangeDylibPath(const char *oldPath, const char *newPath) { + if (NULL == m_pHeader) { + return false; + } + + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + bool pathChanged = false; + uint32_t oldPathLength = (uint32_t)strlen(oldPath); + uint32_t newPathLength = (uint32_t)strlen(newPath); + uint32_t oldPathPadding = (8 - oldPathLength % 8) % 8; + uint32_t newPathPadding = (8 - newPathLength % 8) % 8; + uint32_t newLoadCommandSize = 0; + + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) { + load_command *plc = (load_command *)pLoadCommand; + uint32_t uLoadType = BO(plc->cmd); + + if (LC_LOAD_DYLIB == uLoadType || LC_LOAD_WEAK_DYLIB == uLoadType) { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + + if (strcmp(szDyLib, oldPath) == 0) { + uint32_t dylibPathOffset = sizeof(dylib_command); + uint32_t dylibPathSize = newPathLength + newPathPadding; + if (dylibPathOffset + dylibPathSize > BO(plc->cmdsize)) { + ZLog::Error(">>> Insufficient space to update dylib path!\n"); + return false; + } + + memcpy(pLoadCommand + dylibPathOffset, newPath, newPathLength); + memset(pLoadCommand + dylibPathOffset + newPathLength, 0, newPathPadding); + + ZLog::PrintV(">>> Dylib Path Changed: %s -> %s\n", oldPath, newPath); + + pathChanged = true; + } + } + + pLoadCommand += BO(plc->cmdsize); + } + + if (!pathChanged) { + ZLog::PrintV(">>> Old Dylib Path Not Found: %s\n", oldPath); + } + + return pathChanged; +} + + + + + + +void ZArchO::uninstallDylibs(set dylibNames) +{ + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + uint32_t old_load_command_size = m_pHeader->sizeofcmds; + uint8_t *new_load_command_data = (uint8_t*)malloc(old_load_command_size); + memset(new_load_command_data,0,old_load_command_size); + uint32_t new_load_command_size = 0; + uint32_t clear_num = 0; + uint32_t clear_data_size = 0; + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) + { + load_command *plc = (load_command *)pLoadCommand; + uint32_t load_command_size = BO(plc->cmdsize); + if (LC_LOAD_DYLIB == BO(plc->cmd) || LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) + { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + string dylibName = szDyLib; + if(dylibNames.count(dylibName)>0){ + ZLog::PrintV("\t\t\t%s\tclear\n", szDyLib); + clear_num++; + clear_data_size+=load_command_size; + pLoadCommand += BO(plc->cmdsize); + continue; + } + ZLog::PrintV("\t\t\t%s\n", szDyLib); + } + new_load_command_size+=load_command_size; + memcpy(new_load_command_data,pLoadCommand,load_command_size); + new_load_command_data += load_command_size; + pLoadCommand += BO(plc->cmdsize); + } + pLoadCommand -= m_pHeader->sizeofcmds; + + m_pHeader->ncmds -= clear_num; + m_pHeader->sizeofcmds -= clear_data_size; + new_load_command_data -=new_load_command_size; + memset(pLoadCommand,0,old_load_command_size); + memcpy(pLoadCommand,new_load_command_data,new_load_command_size); + free(new_load_command_data); +} + +std::vector ZArchO::ListDylibs() { + std::vector dylibList; + uint8_t *pLoadCommand = m_pBase + m_uHeaderSize; + + for (uint32_t i = 0; i < BO(m_pHeader->ncmds); i++) { + load_command *plc = (load_command *)pLoadCommand; + if (LC_LOAD_DYLIB == BO(plc->cmd) || LC_LOAD_WEAK_DYLIB == BO(plc->cmd)) { + dylib_command *dlc = (dylib_command *)pLoadCommand; + const char *szDyLib = (const char *)(pLoadCommand + BO(dlc->dylib.name.offset)); + dylibList.push_back(std::string(szDyLib)); + } + pLoadCommand += BO(plc->cmdsize); + } + + return dylibList; +} + diff --git a/ZSign/archo.h b/ZSign/archo.h new file mode 100644 index 0000000..b9dc27d --- /dev/null +++ b/ZSign/archo.h @@ -0,0 +1,42 @@ +#pragma once +#include "common/mach-o.h" +#include "openssl.h" +#include +class ZArchO +{ +public: + ZArchO(); + bool Init(uint8_t *pBase, uint32_t uLength); + +public: + bool Sign(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesData); + void PrintInfo(); + bool IsExecute(); + bool InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate); + uint32_t ReallocCodeSignSpace(const string &strNewFile); + void uninstallDylibs(set dylibNames); + bool ChangeDylibPath(const char *oldPath, const char *newPath); + std::vector ListDylibs(); +private: + uint32_t BO(uint32_t uVal); + const char *GetFileType(uint32_t uFileType); + const char *GetArch(int cpuType, int cpuSubType); + bool BuildCodeSignature(ZSignAsset *pSignAsset, bool bForce, const string &strBundleId, const string &strInfoPlistSHA1, const string &strInfoPlistSHA256, const string &strCodeResourcesSHA1, const string &strCodeResourcesSHA256, string &strOutput); + +public: + uint8_t *m_pBase; + uint32_t m_uLength; + uint32_t m_uCodeLength; + uint8_t *m_pSignBase; + uint32_t m_uSignLength; + string m_strInfoPlist; + bool m_bEncrypted; + bool m_b64; + bool m_bBigEndian; + bool m_bEnoughSpace; + uint8_t *m_pCodeSignSegment; + uint8_t *m_pLinkEditSegment; + uint32_t m_uLoadCommandsFreeSpace; + mach_header *m_pHeader; + uint32_t m_uHeaderSize; +}; diff --git a/ZSign/bundle.cpp b/ZSign/bundle.cpp new file mode 100644 index 0000000..cc2a45f --- /dev/null +++ b/ZSign/bundle.cpp @@ -0,0 +1,710 @@ +#include "bundle.h" +#include "macho.h" +#include "sys/stat.h" +#include "sys/types.h" +#include "common/base64.h" +#include "common/common.h" + +ZAppBundle::ZAppBundle() +{ + m_pSignAsset = NULL; + m_bForceSign = false; + m_bWeakInject = false; +} + +bool ZAppBundle::FindAppFolder(const string &strFolder, string &strAppFolder) +{ + if (IsPathSuffix(strFolder, ".app") || IsPathSuffix(strFolder, ".appex")) + { + strAppFolder = strFolder; + return true; + } + + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..") && 0 != strcmp(ptr->d_name, "__MACOSX")) + { + bool isdir = false; + if (DT_DIR == ptr->d_type) + { + isdir = true; + } + else if (DT_UNKNOWN == ptr->d_type) + { + // Entry type can be unknown depending on the underlying file system + ZLog::DebugV(">>> Unknown directory entry type for %s, falling back to POSIX-compatible check\n", strFolder.c_str()); + struct stat statbuf; + stat(strFolder.c_str(), &statbuf); + if (S_ISDIR(statbuf.st_mode)) + { + isdir = true; + } + } + if (isdir) + { + string strSubFolder = strFolder; + strSubFolder += "/"; + strSubFolder += ptr->d_name; + if (FindAppFolder(strSubFolder, strAppFolder)) + { + return true; + } + } + } + ptr = readdir(dir); + } + closedir(dir); + } + return false; +} + +bool ZAppBundle::GetSignFolderInfo(const string &strFolder, JValue &jvNode, bool bGetName) +{ + JValue jvInfo; + string strInfoPlistData; + string strInfoPlistPath = strFolder + "/Info.plist"; + ReadFile(strInfoPlistPath.c_str(), strInfoPlistData); + jvInfo.readPList(strInfoPlistData); + string strBundleId = jvInfo["CFBundleIdentifier"]; + string strBundleExe = jvInfo["CFBundleExecutable"]; + string strBundleVersion = jvInfo["CFBundleVersion"]; + if (strBundleId.empty() || strBundleExe.empty()) + { + return false; + } + + string strInfoPlistSHA1Base64; + string strInfoPlistSHA256Base64; + SHASumBase64(strInfoPlistData, strInfoPlistSHA1Base64, strInfoPlistSHA256Base64); + + jvNode["bid"] = strBundleId; + jvNode["bver"] = strBundleVersion; + jvNode["exec"] = strBundleExe; + jvNode["sha1"] = strInfoPlistSHA1Base64; + jvNode["sha2"] = strInfoPlistSHA256Base64; + + if (bGetName) + { + string strBundleName = jvInfo["CFBundleDisplayName"]; + if (strBundleName.empty()) + { + strBundleName = jvInfo["CFBundleName"].asCString(); + } + jvNode["name"] = strBundleName; + } + + return true; +} + +bool ZAppBundle::GetObjectsToSign(const string &strFolder, JValue &jvInfo) +{ + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) + { + string strNode = strFolder + "/" + ptr->d_name; + if (DT_DIR == ptr->d_type) + { + if (IsPathSuffix(strNode, ".app") || IsPathSuffix(strNode, ".appex") || IsPathSuffix(strNode, ".framework") || IsPathSuffix(strNode, ".xctest")) + { + JValue jvNode; + jvNode["path"] = strNode.substr(m_strAppFolder.size() + 1); + if (GetSignFolderInfo(strNode, jvNode)) + { + if (GetObjectsToSign(strNode, jvNode)) + { + jvInfo["folders"].push_back(jvNode); + } + } + } + else + { + GetObjectsToSign(strNode, jvInfo); + } + } + else if (DT_REG == ptr->d_type) + { + if (IsPathSuffix(strNode, ".dylib")) + { + jvInfo["files"].push_back(strNode.substr(m_strAppFolder.size() + 1)); + } + } + } + ptr = readdir(dir); + } + closedir(dir); + } + return true; +} + +void ZAppBundle::GetFolderFiles(const string &strFolder, const string &strBaseFolder, set &setFiles) +{ + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) + { + string strNode = strFolder; + strNode += "/"; + strNode += ptr->d_name; + if (DT_DIR == ptr->d_type) + { + GetFolderFiles(strNode, strBaseFolder, setFiles); + } + else if (DT_REG == ptr->d_type) + { + setFiles.insert(strNode.substr(strBaseFolder.size() + 1)); + } + } + ptr = readdir(dir); + } + closedir(dir); + } +} + +bool ZAppBundle::GenerateCodeResources(const string &strFolder, JValue &jvCodeRes) +{ + jvCodeRes.clear(); + + set setFiles; + GetFolderFiles(strFolder, strFolder, setFiles); + + JValue jvInfo; + string strInfoPlistPath = strFolder + "/Info.plist"; + jvInfo.readPListFile(strInfoPlistPath.c_str()); + string strBundleExe = jvInfo["CFBundleExecutable"]; + setFiles.erase(strBundleExe); + setFiles.erase("_CodeSignature/CodeResources"); + + jvCodeRes["files"] = JValue(JValue::E_OBJECT); + jvCodeRes["files2"] = JValue(JValue::E_OBJECT); + + for (set::iterator it = setFiles.begin(); it != setFiles.end(); it++) + { + string strKey = *it; + string strFile = strFolder + "/" + strKey; + string strFileSHA1Base64; + string strFileSHA256Base64; + SHASumBase64File(strFile.c_str(), strFileSHA1Base64, strFileSHA256Base64); + + bool bomit1 = false; + bool bomit2 = false; + + if ("Info.plist" == strKey || "PkgInfo" == strKey) + { + bomit2 = true; + } + + if (IsPathSuffix(strKey, ".DS_Store")) + { + bomit2 = true; + } + + if (IsPathSuffix(strKey, ".lproj/locversion.plist")) + { + bomit1 = true; + bomit2 = true; + } + + if (!bomit1) + { + if (string::npos != strKey.rfind(".lproj/")) + { + jvCodeRes["files"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files"][strKey]["optional"] = true; + } + else + { + jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; + } + } + + if (!bomit2) + { + jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; + if (string::npos != strKey.rfind(".lproj/")) + { + jvCodeRes["files2"][strKey]["optional"] = true; + } + } + } + + jvCodeRes["rules"]["^.*"] = true; + jvCodeRes["rules"]["^.*\\.lproj/"]["optional"] = true; + jvCodeRes["rules"]["^.*\\.lproj/"]["weight"] = 1000.0; + jvCodeRes["rules"]["^.*\\.lproj/locversion.plist$"]["omit"] = true; + jvCodeRes["rules"]["^.*\\.lproj/locversion.plist$"]["weight"] = 1100.0; + jvCodeRes["rules"]["^Base\\.lproj/"]["weight"] = 1010.0; + jvCodeRes["rules"]["^version.plist$"] = true; + + jvCodeRes["rules2"]["^.*"] = true; + jvCodeRes["rules2"][".*\\.dSYM($|/)"]["weight"] = 11.0; + jvCodeRes["rules2"]["^(.*/)?\\.DS_Store$"]["omit"] = true; + jvCodeRes["rules2"]["^(.*/)?\\.DS_Store$"]["weight"] = 2000.0; + jvCodeRes["rules2"]["^.*\\.lproj/"]["optional"] = true; + jvCodeRes["rules2"]["^.*\\.lproj/"]["weight"] = 1000.0; + jvCodeRes["rules2"]["^.*\\.lproj/locversion.plist$"]["omit"] = true; + jvCodeRes["rules2"]["^.*\\.lproj/locversion.plist$"]["weight"] = 1100.0; + jvCodeRes["rules2"]["^Base\\.lproj/"]["weight"] = 1010.0; + jvCodeRes["rules2"]["^Info\\.plist$"]["omit"] = true; + jvCodeRes["rules2"]["^Info\\.plist$"]["weight"] = 20.0; + jvCodeRes["rules2"]["^PkgInfo$"]["omit"] = true; + jvCodeRes["rules2"]["^PkgInfo$"]["weight"] = 20.0; + jvCodeRes["rules2"]["^embedded\\.provisionprofile$"]["weight"] = 20.0; + jvCodeRes["rules2"]["^version\\.plist$"]["weight"] = 20.0; + + return true; +} + +void ZAppBundle::GetChangedFiles(JValue &jvNode, vector &arrChangedFiles) +{ + if (jvNode.has("files")) + { + for (size_t i = 0; i < jvNode["files"].size(); i++) + { + arrChangedFiles.push_back(jvNode["files"][i]); + } + } + + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + JValue &jvSubNode = jvNode["folders"][i]; + GetChangedFiles(jvSubNode, arrChangedFiles); + string strPath = jvSubNode["path"]; + arrChangedFiles.push_back(strPath + "/_CodeSignature/CodeResources"); + arrChangedFiles.push_back(strPath + "/" + jvSubNode["exec"].asString()); + } + } +} + +void ZAppBundle::GetNodeChangedFiles(JValue &jvNode, bool dontGenerateEmbeddedMobileProvision) +{ + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + GetNodeChangedFiles(jvNode["folders"][i], dontGenerateEmbeddedMobileProvision); + } + } + + vector arrChangedFiles; + GetChangedFiles(jvNode, arrChangedFiles); + for (size_t i = 0; i < arrChangedFiles.size(); i++) + { + jvNode["changed"].push_back(arrChangedFiles[i]); + } +// TODO: try + if (dontGenerateEmbeddedMobileProvision) { + if ("/" == jvNode["path"]) + { //root + jvNode["changed"].push_back("embedded.mobileprovision"); + } + } +} + +int ZAppBundle::GetSignCount(JValue &jvNode) { + int ans = 1; + if (jvNode.has("files")) + { + ans += jvNode["files"].size(); + } + + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + ans += GetSignCount(jvNode["folders"][i]); + } + } + return ans; +} + +bool ZAppBundle::SignNode(JValue &jvNode) +{ + if (jvNode.has("folders")) + { + for (size_t i = 0; i < jvNode["folders"].size(); i++) + { + if (!SignNode(jvNode["folders"][i])) + { + return false; + } + } + } + + if (jvNode.has("files")) + { + for (size_t i = 0; i < jvNode["files"].size(); i++) + { + const char *szFile = jvNode["files"][i].asCString(); + ZLog::PrintV(">>> SignFile: \t%s\n", szFile); + ZMachO macho; + if (!macho.InitV("%s/%s", m_strAppFolder.c_str(), szFile)) + { + return false; + } + + if (!macho.Sign(m_pSignAsset, m_bForceSign, mainBundleIdentifier, "", "", "")) + { + return false; + } + if(progressHandler) { + progressHandler(); + } + } + } + + ZBase64 b64; + string strInfoPlistSHA1; + string strInfoPlistSHA256; + string strFolder = jvNode["path"]; + string strBundleId = jvNode["bid"]; + string strBundleExe = jvNode["exec"]; + b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); + b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); + if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) + { + ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); + return false; + } + + string strBaseFolder = m_strAppFolder; + if ("/" != strFolder) + { + strBaseFolder += "/"; + strBaseFolder += strFolder; + } + + string strExePath = strBaseFolder + "/" + strBundleExe; + ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); + + ZMachO macho; + if (!macho.Init(strExePath.c_str())) + { + ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); + return false; + } + + RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); + CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); + string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; + + JValue jvCodeRes; + if (!m_bForceSign) + { + jvCodeRes.readPListFile(strCodeResFile.c_str()); + } + + if (m_bForceSign || jvCodeRes.isNull()) + { //create + if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) + { + ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); + return false; + } + } + else if (jvNode.has("changed")) + { //use existsed + for (size_t i = 0; i < jvNode["changed"].size(); i++) + { + string strFile = jvNode["changed"][i].asCString(); + string strRealFile = m_strAppFolder + "/" + strFile; + + string strFileSHA1Base64; + string strFileSHA256Base64; + if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) + { + ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); + return false; + } + + string strKey = strFile; + if ("/" != strFolder) + { + strKey = strFile.substr(strFolder.size() + 1); + } + jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; + + ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); + } + } + + string strCodeResData; + jvCodeRes.writePList(strCodeResData); + if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) + { + ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); + return false; + } + + bool bForceSign = m_bForceSign; + if ("/" == strFolder && !m_strDyLibPath.empty()) + { //inject dylib + macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); + } + + if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) + { + return false; + } + + if(progressHandler) { + progressHandler(); + } + return true; +} + +void ZAppBundle::GetPlugIns(const string &strFolder, vector &arrPlugIns) +{ + DIR *dir = opendir(strFolder.c_str()); + if (NULL != dir) + { + dirent *ptr = readdir(dir); + while (NULL != ptr) + { + if (0 != strcmp(ptr->d_name, ".") && 0 != strcmp(ptr->d_name, "..")) + { + if (DT_DIR == ptr->d_type) + { + string strSubFolder = strFolder; + strSubFolder += "/"; + strSubFolder += ptr->d_name; + if (IsPathSuffix(strSubFolder, ".app") || IsPathSuffix(strSubFolder, ".appex")) + { + arrPlugIns.push_back(strSubFolder); + } + GetPlugIns(strSubFolder, arrPlugIns); + } + } + ptr = readdir(dir); + } + closedir(dir); + } +} + +bool ZAppBundle::ConfigureFolderSign(ZSignAsset *pSignAsset, + const string &strFolder, + const string &execName, + const string &strBundleID, + const string &strBundleVersion, + const string &strDisplayName, + const string &strDyLibFile, + bool bForce, + bool bWeakInject, + bool bEnableCache, + bool dontGenerateEmbeddedMobileProvision + ) +{ + m_bForceSign = bForce; + m_pSignAsset = pSignAsset; + m_bWeakInject = bWeakInject; + if (NULL == m_pSignAsset) + { + return false; + } + + if (!FindAppFolder(strFolder, m_strAppFolder)) + { + ZLog::ErrorV(">>> Can't Find App Folder! %s\n", strFolder.c_str()); + return false; + } + + JValue jvInfoPlist; + bool jvInfoReadSuccess = jvInfoPlist.readPListPath("%s/Info.plist", m_strAppFolder.c_str()); + if (!strBundleID.empty() || !strDisplayName.empty() || !strBundleVersion.empty()) + { //modify bundle id + + if (jvInfoReadSuccess) + { + m_bForceSign = true; + if (!strBundleID.empty()) + { + string strOldBundleID = jvInfoPlist["CFBundleIdentifier"]; + jvInfoPlist["CFBundleIdentifier"] = strBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s\n", strOldBundleID.c_str(), strBundleID.c_str()); + + //modify plugins bundle id + vector arrPlugIns; + GetPlugIns(m_strAppFolder, arrPlugIns); + for (size_t i = 0; i < arrPlugIns.size(); i++) + { + string &strPlugin = arrPlugIns[i]; + JValue jvPlugInInfoPlist; + if (jvPlugInInfoPlist.readPListPath("%s/Info.plist", strPlugin.c_str())) + { + string strOldPlugInBundleID = jvPlugInInfoPlist["CFBundleIdentifier"]; + string strNewPlugInBundleID = strOldPlugInBundleID; + StringReplace(strNewPlugInBundleID, strOldBundleID, strBundleID); + jvPlugInInfoPlist["CFBundleIdentifier"] = strNewPlugInBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s, PlugIn\n", strOldPlugInBundleID.c_str(), strNewPlugInBundleID.c_str()); + + if (jvPlugInInfoPlist.has("WKCompanionAppBundleIdentifier")) + { + string strOldWKCBundleID = jvPlugInInfoPlist["WKCompanionAppBundleIdentifier"]; + string strNewWKCBundleID = strOldWKCBundleID; + StringReplace(strNewWKCBundleID, strOldBundleID, strBundleID); + jvPlugInInfoPlist["WKCompanionAppBundleIdentifier"] = strNewWKCBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s, PlugIn-WKCompanionAppBundleIdentifier\n", strOldWKCBundleID.c_str(), strNewWKCBundleID.c_str()); + } + + if (jvPlugInInfoPlist.has("NSExtension")) + { + if (jvPlugInInfoPlist["NSExtension"].has("NSExtensionAttributes")) + { + if (jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"].has("WKAppBundleIdentifier")) + { + string strOldWKBundleID = jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"]; + string strNewWKBundleID = strOldWKBundleID; + StringReplace(strNewWKBundleID, strOldBundleID, strBundleID); + jvPlugInInfoPlist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = strNewWKBundleID; + ZLog::PrintV(">>> BundleId: \t%s -> %s, NSExtension-NSExtensionAttributes-WKAppBundleIdentifier\n", strOldWKBundleID.c_str(), strNewWKBundleID.c_str()); + } + } + } + + jvPlugInInfoPlist.writePListPath("%s/Info.plist", strPlugin.c_str()); + } + } + } + + if (!strDisplayName.empty()) + { + string strOldDisplayName = jvInfoPlist["CFBundleDisplayName"]; + jvInfoPlist["CFBundleName"] = strDisplayName; + jvInfoPlist["CFBundleDisplayName"] = strDisplayName; + ZLog::PrintV(">>> BundleName: %s -> %s\n", strOldDisplayName.c_str(), strDisplayName.c_str()); + } + + if (!strBundleVersion.empty()) + { + string strOldBundleVersion = jvInfoPlist["CFBundleVersion"]; + jvInfoPlist["CFBundleVersion"] = strBundleVersion; + jvInfoPlist["CFBundleShortVersionString"] = strBundleVersion; + ZLog::PrintV(">>> BundleVersion: %s -> %s\n", strOldBundleVersion.c_str(), strBundleVersion.c_str()); + } + + jvInfoPlist.writePListPath("%s/Info.plist", m_strAppFolder.c_str()); + } + else + { + ZLog::ErrorV(">>> Can't Find App's Info.plist! %s\n", strFolder.c_str()); + return false; + } + } + + if (!strDisplayName.empty()) + { + m_bForceSign = true; + JValue jvInfoPlistStrings; + if (jvInfoPlistStrings.readPListPath("%s/zh_CN.lproj/InfoPlist.strings", m_strAppFolder.c_str())) + { + jvInfoPlistStrings["CFBundleName"] = strDisplayName; + jvInfoPlistStrings["CFBundleDisplayName"] = strDisplayName; + jvInfoPlistStrings.writePListPath("%s/zh_CN.lproj/InfoPlist.strings", m_strAppFolder.c_str()); + } + jvInfoPlistStrings.clear(); + if (jvInfoPlistStrings.readPListPath("%s/zh-Hans.lproj/InfoPlist.strings", m_strAppFolder.c_str())) + { + jvInfoPlistStrings["CFBundleName"] = strDisplayName; + jvInfoPlistStrings["CFBundleDisplayName"] = strDisplayName; + jvInfoPlistStrings.writePListPath("%s/zh-Hans.lproj/InfoPlist.strings", m_strAppFolder.c_str()); + } + } + if (dontGenerateEmbeddedMobileProvision) { + if (!WriteFile(pSignAsset->m_strProvisionData, "%s/embedded.mobileprovision", m_strAppFolder.c_str())) + { //embedded.mobileprovision + ZLog::ErrorV(">>> Can't Write embedded.mobileprovision!\n"); + return false; + } + } + + if (!strDyLibFile.empty()) + { //inject dylib + string strDyLibData; + ReadFile(strDyLibFile.c_str(), strDyLibData); + if (!strDyLibData.empty()) + { + string strFileName = basename((char *)strDyLibFile.c_str()); + if (WriteFile(strDyLibData, "%s/%s", m_strAppFolder.c_str(), strFileName.c_str())) + { + StringFormat(m_strDyLibPath, "@executable_path/%s", strFileName.c_str()); + } + } + } + + string strCacheName; + SHA1Text(m_strAppFolder, strCacheName); + if (!IsFileExistsV("%s/zsign_cache.json", m_strAppFolder.c_str())) + { + m_bForceSign = true; + } + mainBundleIdentifier = string(jvInfoPlist["CFBundleIdentifier"]); + ZLog::PrintV("mainBundleIdentifier = %s", mainBundleIdentifier.c_str()); + + JValue jvRoot; + if (m_bForceSign) + { + jvRoot["path"] = "/"; + jvRoot["root"] = m_strAppFolder; + if (!GetSignFolderInfo(m_strAppFolder, jvRoot, true)) + { + ZLog::ErrorV(">>> Can't Get BundleID, BundleVersion, or BundleExecute in Info.plist! %s\n", m_strAppFolder.c_str()); + return false; + } + if (!GetObjectsToSign(m_strAppFolder, jvRoot)) + { + return false; + } + jvRoot["files"].push_back(execName); + GetNodeChangedFiles(jvRoot, dontGenerateEmbeddedMobileProvision); + } + else + { + jvRoot.readPath("%s/zsign_cache.json", m_strAppFolder.c_str()); + } + + ZLog::PrintV(">>> Signing: \t%s ...\n", m_strAppFolder.c_str()); + ZLog::PrintV(">>> AppName: \t%s\n", jvRoot["name"].asCString()); + ZLog::PrintV(">>> BundleId: \t%s\n", jvRoot["bid"].asCString()); + ZLog::PrintV(">>> BundleVer: \t%s\n", jvRoot["bver"].asCString()); + ZLog::PrintV(">>> TeamId: \t%s\n", m_pSignAsset->m_strTeamId.c_str()); + ZLog::PrintV(">>> SubjectCN: \t%s\n", m_pSignAsset->m_strSubjectCN.c_str()); + ZLog::PrintV(">>> ReadCache: \t%s\n", m_bForceSign ? "NO" : "YES"); + ZLog::PrintV(">>> Exclude MobileProvision: \t%s\n", dontGenerateEmbeddedMobileProvision ? "NO" : "YES"); + + config = jvRoot; + + return true; +} + +int ZAppBundle::GetSignCount() { + return GetSignCount(config); +} + +bool ZAppBundle::StartSign(bool enableCache) { + if (SignNode(config)) + { + if (enableCache) + { + config.styleWritePath("%s/zsign_cache.json", m_strAppFolder.c_str()); + } + return true; + } + return false; +} diff --git a/ZSign/bundle.h b/ZSign/bundle.h new file mode 100644 index 0000000..83d21db --- /dev/null +++ b/ZSign/bundle.h @@ -0,0 +1,42 @@ +#pragma once +#include "common/common.h" +#include "common/json.h" +#include "openssl.h" + +class ZAppBundle +{ +public: + ZAppBundle(); + +public: + bool ConfigureFolderSign(ZSignAsset *pSignAsset, const string &strFolder, const string &execName, const string &strBundleID, const string &strBundleVersion, const string &strDisplayName, const string &strDyLibFile, bool bForce, bool bWeakInject, bool bEnableCache, bool dontGenerateEmbeddedMobileProvision); + bool StartSign(bool enableCache); + int GetSignCount(); +private: + bool SignNode(JValue &jvNode); + void GetNodeChangedFiles(JValue &jvNode, bool dontGenerateEmbeddedMobileProvision); + void GetChangedFiles(JValue &jvNode, vector &arrChangedFiles); + void GetPlugIns(const string &strFolder, vector &arrPlugIns); + int GetSignCount(JValue &jvNode); + +private: + bool FindAppFolder(const string &strFolder, string &strAppFolder); + bool GetObjectsToSign(const string &strFolder, JValue &jvInfo); + bool GetSignFolderInfo(const string &strFolder, JValue &jvNode, bool bGetName = false); + +private: + bool GenerateCodeResources(const string &strFolder, JValue &jvCodeRes); + void GetFolderFiles(const string &strFolder, const string &strBaseFolder, set &setFiles); + +private: + bool m_bForceSign; + bool m_bWeakInject; + string m_strDyLibPath; + ZSignAsset *m_pSignAsset; + string mainBundleIdentifier; + JValue config; + +public: + string m_strAppFolder; + std::function progressHandler; +}; diff --git a/ZSign/common/base64.cpp b/ZSign/common/base64.cpp new file mode 100644 index 0000000..bde88ef --- /dev/null +++ b/ZSign/common/base64.cpp @@ -0,0 +1,203 @@ +#include "base64.h" +#include + +#define B0(a) (a & 0xFF) +#define B1(a) (a >> 8 & 0xFF) +#define B2(a) (a >> 16 & 0xFF) +#define B3(a) (a >> 24 & 0xFF) + +ZBase64::ZBase64(void) +{ +} + +ZBase64::~ZBase64(void) +{ + if (!m_arrEnc.empty()) + { + for (size_t i = 0; i < m_arrEnc.size(); i++) + { + delete[] m_arrEnc[i]; + } + m_arrEnc.clear(); + } + + if (!m_arrDec.empty()) + { + for (size_t i = 0; i < m_arrDec.size(); i++) + { + delete[] m_arrDec[i]; + } + m_arrDec.clear(); + } +} + +char ZBase64::GetB64char(int nIndex) +{ + static const char szTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (nIndex >= 0 && nIndex < 64) + { + return szTable[nIndex]; + } + return '='; +} + +int ZBase64::GetB64Index(char ch) +{ + int index = -1; + if (ch >= 'A' && ch <= 'Z') + { + index = ch - 'A'; + } + else if (ch >= 'a' && ch <= 'z') + { + index = ch - 'a' + 26; + } + else if (ch >= '0' && ch <= '9') + { + index = ch - '0' + 52; + } + else if (ch == '+') + { + index = 62; + } + else if (ch == '/') + { + index = 63; + } + return index; +} + +const char *ZBase64::Encode(const char *szSrc, int nSrcLen) +{ + if (0 == nSrcLen) + { + nSrcLen = (int)strlen(szSrc); + } + + if (nSrcLen <= 0) + { + return ""; + } + + char *szEnc = new char[nSrcLen * 3 + 128]; + m_arrEnc.push_back(szEnc); + + int i = 0; + int len = 0; + unsigned char *psrc = (unsigned char *)szSrc; + char *p64 = szEnc; + for (i = 0; i < nSrcLen - 3; i += 3) + { + unsigned long ulTmp = *(unsigned long *)psrc; + int b0 = GetB64char((B0(ulTmp) >> 2) & 0x3F); + int b1 = GetB64char((B0(ulTmp) << 6 >> 2 | B1(ulTmp) >> 4) & 0x3F); + int b2 = GetB64char((B1(ulTmp) << 4 >> 2 | B2(ulTmp) >> 6) & 0x3F); + int b3 = GetB64char((B2(ulTmp) << 2 >> 2) & 0x3F); + *((unsigned long *)p64) = b0 | b1 << 8 | b2 << 16 | b3 << 24; + len += 4; + p64 += 4; + psrc += 3; + } + + if (i < nSrcLen) + { + int rest = nSrcLen - i; + unsigned long ulTmp = 0; + for (int j = 0; j < rest; ++j) + { + *(((unsigned char *)&ulTmp) + j) = *psrc++; + } + p64[0] = GetB64char((B0(ulTmp) >> 2) & 0x3F); + p64[1] = GetB64char((B0(ulTmp) << 6 >> 2 | B1(ulTmp) >> 4) & 0x3F); + p64[2] = rest > 1 ? GetB64char((B1(ulTmp) << 4 >> 2 | B2(ulTmp) >> 6) & 0x3F) : '='; + p64[3] = rest > 2 ? GetB64char((B2(ulTmp) << 2 >> 2) & 0x3F) : '='; + p64 += 4; + len += 4; + } + *p64 = '\0'; + return szEnc; +} + +const char *ZBase64::Encode(const string &strInput) +{ + return Encode(strInput.data(), strInput.size()); +} + +const char *ZBase64::Decode(const char *szSrc, int nSrcLen, int *pDecLen) +{ + if (0 == nSrcLen) + { + nSrcLen = (int)strlen(szSrc); + } + + if (nSrcLen <= 0) + { + return ""; + } + + char *szDec = new char[nSrcLen]; + m_arrDec.push_back(szDec); + + int i = 0; + int len = 0; + unsigned char *psrc = (unsigned char *)szSrc; + char *pbuf = szDec; + for (i = 0; i < nSrcLen - 4; i += 4) + { + unsigned long ulTmp = *(unsigned long *)psrc; + + int b0 = (GetB64Index((char)B0(ulTmp)) << 2 | GetB64Index((char)B1(ulTmp)) << 2 >> 6) & 0xFF; + int b1 = (GetB64Index((char)B1(ulTmp)) << 4 | GetB64Index((char)B2(ulTmp)) << 2 >> 4) & 0xFF; + int b2 = (GetB64Index((char)B2(ulTmp)) << 6 | GetB64Index((char)B3(ulTmp)) << 2 >> 2) & 0xFF; + + *((unsigned long *)pbuf) = b0 | b1 << 8 | b2 << 16; + psrc += 4; + pbuf += 3; + len += 3; + } + + if (i < nSrcLen) + { + int rest = nSrcLen - i; + unsigned long ulTmp = 0; + for (int j = 0; j < rest; ++j) + { + *(((unsigned char *)&ulTmp) + j) = *psrc++; + } + + int b0 = (GetB64Index((char)B0(ulTmp)) << 2 | GetB64Index((char)B1(ulTmp)) << 2 >> 6) & 0xFF; + *pbuf++ = b0; + len++; + + if ('=' != B1(ulTmp) && '=' != B2(ulTmp)) + { + int b1 = (GetB64Index((char)B1(ulTmp)) << 4 | GetB64Index((char)B2(ulTmp)) << 2 >> 4) & 0xFF; + *pbuf++ = b1; + len++; + } + + if ('=' != B2(ulTmp) && '=' != B3(ulTmp)) + { + int b2 = (GetB64Index((char)B2(ulTmp)) << 6 | GetB64Index((char)B3(ulTmp)) << 2 >> 2) & 0xFF; + *pbuf++ = b2; + len++; + } + } + *pbuf = '\0'; + + if (NULL != pDecLen) + { + *pDecLen = (int)(pbuf - szDec); + } + + return szDec; +} + +const char *ZBase64::Decode(const char *szSrc, string &strOutput) +{ + strOutput.clear(); + int nDecLen = 0; + const char *p = Decode(szSrc, 0, &nDecLen); + strOutput.append(p, nDecLen); + return strOutput.data(); +} diff --git a/ZSign/common/base64.h b/ZSign/common/base64.h new file mode 100644 index 0000000..3b38dc1 --- /dev/null +++ b/ZSign/common/base64.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +using namespace std; + +class ZBase64 +{ +public: + ZBase64(void); + ~ZBase64(void); + +public: + const char *Encode(const char *szSrc, int nSrcLen = 0); + const char *Encode(const string &strInput); + const char *Decode(const char *szSrc, int nSrcLen = 0, int *pDecLen = NULL); + const char *Decode(const char *szSrc, string &strOutput); + +private: + inline int GetB64Index(char ch); + inline char GetB64char(int nIndex); + +private: + vector m_arrDec; + vector m_arrEnc; +}; diff --git a/ZSign/common/common.cpp b/ZSign/common/common.cpp new file mode 100644 index 0000000..92ad02d --- /dev/null +++ b/ZSign/common/common.cpp @@ -0,0 +1,849 @@ +#include "common.h" +#include "base64.h" +#include +#include +#include +#include +#include "../Utils.hpp" +#include + +#define PARSEVALIST(szFormatArgs, szArgs) \ + ZBuffer buffer; \ + char szBuffer[PATH_MAX] = {0}; \ + char *szArgs = szBuffer; \ + va_list args; \ + va_start(args, szFormatArgs); \ + int nRet = vsnprintf(szArgs, PATH_MAX, szFormatArgs, args); \ + va_end(args); \ + if (nRet > PATH_MAX - 1) \ + { \ + char *szNewBuffer = buffer.GetBuffer(nRet + 1); \ + if (NULL != szNewBuffer) \ + { \ + szArgs = szNewBuffer; \ + va_start(args, szFormatArgs); \ + vsnprintf(szArgs, nRet + 1, szFormatArgs, args); \ + va_end(args); \ + } \ + } + +bool IsRegularFile(const char *file) +{ + struct stat info; + stat(file, &info); + return S_ISREG(info.st_mode); +} + +void *MapFile(const char *path, size_t offset, size_t size, size_t *psize, bool ro) +{ + refreshFile(path); + int fd = open(path, ro ? O_RDONLY : O_RDWR); + if (fd <= 0) + { + return NULL; + } + + if (0 == size) + { + struct stat stat; + fstat(fd, &stat); + size = stat.st_size; + } + + if (NULL != psize) + { + *psize = size; + } + + void *base = mmap(NULL, size, ro ? PROT_READ : PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); + close(fd); + + if (MAP_FAILED == base) + { + base = NULL; + } + + return base; +} + +bool WriteFile(const char *szFile, const char *szData, size_t sLen) +{ + if (NULL == szFile) + { + return false; + } + + FILE *fp = fopen(szFile, "wb"); + if (NULL != fp) + { + int64_t towrite = sLen; + if (NULL != szData) + { + while (towrite > 0) + { + int64_t nwrite = fwrite(szData + (sLen - towrite), 1, towrite, fp); + if (nwrite <= 0) + { + break; + } + towrite -= nwrite; + } + } + fclose(fp); + return (towrite > 0) ? false : true; + } + else + { + ZLog::ErrorV("WriteFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); + } + + return false; +} + +bool WriteFile(const char *szFile, const string &strData) +{ + return WriteFile(szFile, strData.data(), strData.size()); +} + +bool WriteFile(string &strData, const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szPath) + return WriteFile(szPath, strData); +} + +bool WriteFile(const char *szData, size_t sLen, const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szPath) + return WriteFile(szPath, szData, sLen); +} + +bool ReadFile(const char *szFile, string &strData) +{ + strData.clear(); + + if (!IsFileExists(szFile)) + { + return false; + } + + FILE *fp = fopen(szFile, "rb"); + if (NULL != fp) + { + strData.reserve(GetFileSize(fileno(fp))); + + char buf[4096] = {0}; + size_t nread = fread(buf, 1, 4096, fp); + while (nread > 0) + { + strData.append(buf, nread); + nread = fread(buf, 1, 4096, fp); + } + fclose(fp); + return true; + } + else + { + ZLog::ErrorV("ReadFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); + } + + return false; +} + +bool ReadFile(string &strData, const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szPath) + return ReadFile(szPath, strData); +} + +bool AppendFile(const char *szFile, const char *szData, size_t sLen) +{ + FILE *fp = fopen(szFile, "ab+"); + if (NULL != fp) + { + int64_t towrite = sLen; + while (towrite > 0) + { + int64_t nwrite = fwrite(szData + (sLen - towrite), 1, towrite, fp); + if (nwrite <= 0) + { + break; + } + towrite -= nwrite; + } + + fclose(fp); + return (towrite > 0) ? false : true; + } + else + { + ZLog::ErrorV("AppendFile: Failed in fopen! %s, %s\n", szFile, strerror(errno)); + } + return false; +} + +bool AppendFile(const char *szFile, const string &strData) +{ + return AppendFile(szFile, strData.data(), strData.size()); +} + +bool IsFolder(const char *szFolder) +{ + struct stat st; + stat(szFolder, &st); + return S_ISDIR(st.st_mode); +} + +bool IsFolderV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFolder) + return IsFolder(szFolder); +} + +bool CreateFolder(const char *szFolder) +{ + if (!IsFolder(szFolder)) + { +#if defined(WINDOWS) + return (0 == mkdir(szFolder)); +#else + return (0 == mkdir(szFolder, 0755)); +#endif + } + return false; +} + +bool CreateFolderV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFolder) + return CreateFolder(szFolder); +} + +int RemoveFolderCallBack(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) +{ + int ret = remove(fpath); + if (ret) + { + perror(fpath); + } + return ret; +} + +bool RemoveFolder(const char *szFolder) +{ + if (!IsFolder(szFolder)) + { + RemoveFile(szFolder); + return true; + } + return nftw(szFolder, RemoveFolderCallBack, 64, FTW_DEPTH | FTW_PHYS); +} + +bool RemoveFolderV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFolder) + return RemoveFolder(szFolder); +} + +bool RemoveFile(const char *szFile) +{ + return (0 == remove(szFile)); +} + +bool RemoveFileV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFile); + return RemoveFile(szFile); +} + +bool IsFileExists(const char *szFile) +{ + if (NULL == szFile) + { + return false; + } + return (0 == access(szFile, F_OK)); +} + +bool IsFileExistsV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFile) + return IsFileExists(szFile); +} + +bool IsZipFile(const char *szFile) +{ + if (NULL != szFile && !IsFolder(szFile)) + { + FILE *fp = fopen(szFile, "rb"); + if (NULL != fp) + { + uint8_t buf[2] = {0}; + fread(buf, 1, 2, fp); + fclose(fp); + return (0 == memcmp("PK", buf, 2)); + } + } + return false; +} +#define PATH_BUFFER_LENGTH 1024 + +string GetCanonicalizePath(const char *szPath) +{ + string strPath = szPath; + if (!strPath.empty()) + { + if ('/' != szPath[0]) + { + char path[PATH_MAX] = {0}; + +#if defined(WINDOWS) + + if (NULL != _fullpath((char *)"./", path, PATH_BUFFER_LENGTH)) + { + strPath = path; + strPath += "/"; + strPath += szPath; + } +#else + if (NULL != realpath("./", path)) + { + strPath = path; + strPath += "/"; + strPath += szPath; + } +#endif + } + StringReplace(strPath, "/./", "/"); + } + return strPath; +} + +int64_t GetFileSize(int fd) +{ + int64_t nSize = 0; + struct stat stbuf; + if (0 == fstat(fd, &stbuf)) + { + if (S_ISREG(stbuf.st_mode)) + { + nSize = stbuf.st_size; + } + } + return (nSize < 0 ? 0 : nSize); +} + +int64_t GetFileSize(const char *szFile) +{ + int64_t nSize = 0; + int fd = open(szFile, O_RDONLY); + if (fd >= 0) + { + nSize = GetFileSize(fd); + close(fd); + } + return nSize; +} + +int64_t GetFileSizeV(const char *szFormatPath, ...) +{ + PARSEVALIST(szFormatPath, szFile) + return GetFileSize(szFile); +} + +string GetFileSizeString(const char *szFile) +{ + return FormatSize(GetFileSize(szFile), 1024); +} + +string FormatSize(int64_t size, int64_t base) +{ + double fsize = 0; + char ret[64] = {0}; + if (size > base * base * base * base) + { + fsize = (size * 1.0) / (base * base * base * base); + sprintf(ret, "%.2f TB", fsize); + } + else if (size > base * base * base) + { + fsize = (size * 1.0) / (base * base * base); + sprintf(ret, "%.2f GB", fsize); + } + else if (size > base * base) + { + fsize = (size * 1.0) / (base * base); + sprintf(ret, "%.2f MB", fsize); + } + else if (size > base) + { + fsize = (size * 1.0) / (base); + sprintf(ret, "%.2f KB", fsize); + } + else + { + sprintf(ret, "%" PRId64 " B", size); + } + return ret; +} + +bool IsPathSuffix(const string &strPath, const char *suffix) +{ + size_t nPos = strPath.rfind(suffix); + if (string::npos != nPos) + { + if (nPos == (strPath.size() - strlen(suffix))) + { + return true; + } + } + return false; +} + +time_t GetUnixStamp() +{ + time_t ustime = 0; + time(&ustime); + return ustime; +} + +uint64_t GetMicroSecond() +{ + struct timeval tv = {0}; + gettimeofday(&tv, NULL); + return tv.tv_sec * 1000000 + tv.tv_usec; +} + +bool SystemExec(const char *szFormatCmd, ...) +{ + /*PARSEVALIST(szFormatCmd, szCmd) + + if (strlen(szCmd) <= 0) + { + return false; + } + + int status = system(szCmd); + + if (-1 == status) + { + ZLog::ErrorV("SystemExec: \"%s\", Error!\n", szCmd); + return false; + } + else + { +#if !defined(WINDOWS) + if (WIFEXITED(status)) + { + if (0 == WEXITSTATUS(status)) + { + return true; + } + else + { + ZLog::ErrorV("SystemExec: \"%s\", Failed! Exit-Status: %d\n", szCmd, WEXITSTATUS(status)); + return false; + } + } + else + { + return true; + } +#endif + } + */ + return false; +} + +uint16_t _Swap(uint16_t value) +{ + return ((value >> 8) & 0x00ff) | + ((value << 8) & 0xff00); +} + +uint32_t _Swap(uint32_t value) +{ + value = ((value >> 8) & 0x00ff00ff) | + ((value << 8) & 0xff00ff00); + value = ((value >> 16) & 0x0000ffff) | + ((value << 16) & 0xffff0000); + return value; +} + +uint64_t _Swap(uint64_t value) +{ + value = (value & 0x00000000ffffffffULL) << 32 | (value & 0xffffffff00000000ULL) >> 32; + value = (value & 0x0000ffff0000ffffULL) << 16 | (value & 0xffff0000ffff0000ULL) >> 16; + value = (value & 0x00ff00ff00ff00ffULL) << 8 | (value & 0xff00ff00ff00ff00ULL) >> 8; + return value; +} + +uint32_t ByteAlign(uint32_t uValue, uint32_t uAlign) +{ + return (uValue + (uAlign - uValue % uAlign)); +} + +const char *StringFormat(string &strFormat, const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szFormat) + strFormat = szFormat; + return strFormat.c_str(); +} + +string &StringReplace(string &context, const string &from, const string &to) +{ + size_t lookHere = 0; + size_t foundHere; + while ((foundHere = context.find(from, lookHere)) != string::npos) + { + context.replace(foundHere, from.size(), to); + lookHere = foundHere + to.size(); + } + return context; +} + +void StringSplit(const string &src, const string &split, vector &dest) +{ + size_t oldPos = 0; + size_t newPos = src.find(split, oldPos); + while (newPos != string::npos) + { + dest.push_back(src.substr(oldPos, newPos - oldPos)); + oldPos = newPos + split.size(); + newPos = src.find(split, oldPos); + } + if (oldPos < src.size()) + { + dest.push_back(src.substr(oldPos)); + } +} + +bool SHA1Text(const string &strData, string &strOutput) +{ + string strSHASum; + SHASum(E_SHASUM_TYPE_1, strData, strSHASum); + + strOutput.clear(); + char buf[16] = {0}; + for (size_t i = 0; i < strSHASum.size(); i++) + { + sprintf(buf, "%02x", (uint8_t)strSHASum[i]); + strOutput += buf; + } + return (!strOutput.empty()); +} + +void PrintSHASum(const char *prefix, const uint8_t *hash, uint32_t size, const char *suffix) +{ + ZLog::PrintV("%s", prefix); + for (uint32_t i = 0; i < size; i++) + { + ZLog::PrintV("%02x", hash[i]); + } + ZLog::PrintV("%s", suffix); +} + +void PrintSHASum(const char *prefix, const string &strSHASum, const char *suffix) +{ + PrintSHASum(prefix, (const uint8_t *)strSHASum.data(), strSHASum.size(), suffix); +} + +void PrintDataSHASum(const char *prefix, int nSumType, const string &strData, const char *suffix) +{ + string strSHASum; + SHASum(nSumType, strData, strSHASum); + PrintSHASum(prefix, strSHASum, suffix); +} + +void PrintDataSHASum(const char *prefix, int nSumType, uint8_t *data, size_t size, const char *suffix) +{ + string strSHASum; + SHASum(nSumType, data, size, strSHASum); + PrintSHASum(prefix, strSHASum, suffix); +} + +bool SHASum(int nSumType, uint8_t *data, size_t size, string &strOutput) +{ + strOutput.clear(); + if (1 == nSumType) + { + uint8_t hash[20]; + memset(hash, 0, 20); + SHA1(data, size, hash); + strOutput.append((const char *)hash, 20); + } + else + { + uint8_t hash[32]; + memset(hash, 0, 32); + SHA256(data, size, hash); + strOutput.append((const char *)hash, 32); + } + return true; +} + +bool SHASum(int nSumType, const string &strData, string &strOutput) +{ + return SHASum(nSumType, (uint8_t *)strData.data(), strData.size(), strOutput); +} + +bool SHASum(const string &strData, string &strSHA1, string &strSHA256) +{ + SHASum(E_SHASUM_TYPE_1, strData, strSHA1); + SHASum(E_SHASUM_TYPE_256, strData, strSHA256); + return (!strSHA1.empty() && !strSHA256.empty()); +} + +bool SHASumFile(const char *szFile, string &strSHA1, string &strSHA256) +{ + size_t sSize = 0; + uint8_t *pBase = (uint8_t *)MapFile(szFile, 0, 0, &sSize, true); + + SHASum(E_SHASUM_TYPE_1, pBase, sSize, strSHA1); + SHASum(E_SHASUM_TYPE_256, pBase, sSize, strSHA256); + + if (NULL != pBase && sSize > 0) + { + munmap(pBase, sSize); + } + return (!strSHA1.empty() && !strSHA256.empty()); +} + +bool SHASumBase64(const string &strData, string &strSHA1Base64, string &strSHA256Base64) +{ + ZBase64 b64; + string strSHA1; + string strSHA256; + SHASum(strData, strSHA1, strSHA256); + strSHA1Base64 = b64.Encode(strSHA1); + strSHA256Base64 = b64.Encode(strSHA256); + return (!strSHA1Base64.empty() && !strSHA256Base64.empty()); +} + +bool SHASumBase64File(const char *szFile, string &strSHA1Base64, string &strSHA256Base64) +{ + ZBase64 b64; + string strSHA1; + string strSHA256; + SHASumFile(szFile, strSHA1, strSHA256); + strSHA1Base64 = b64.Encode(strSHA1); + strSHA256Base64 = b64.Encode(strSHA256); + return (!strSHA1Base64.empty() && !strSHA256Base64.empty()); +} + +ZBuffer::ZBuffer() +{ + m_pData = NULL; + m_uSize = 0; +} + +ZBuffer::~ZBuffer() +{ + Free(); +} + +char *ZBuffer::GetBuffer(uint32_t uSize) +{ + if (uSize <= m_uSize) + { + return m_pData; + } + + char *pData = (char *)realloc(m_pData, uSize); + if (NULL == pData) + { + Free(); + return NULL; + } + + m_pData = pData; + m_uSize = uSize; + return m_pData; +} + +void ZBuffer::Free() +{ + if (NULL != m_pData) + { + free(m_pData); + } + m_pData = NULL; + m_uSize = 0; +} + +ZTimer::ZTimer() +{ + Reset(); +} + +uint64_t ZTimer::Reset() +{ + m_uBeginTime = GetMicroSecond(); + return m_uBeginTime; +} + +uint64_t ZTimer::Print(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szFormat) + uint64_t uElapse = GetMicroSecond() - m_uBeginTime; + ZLog::PrintV("%s (%.03fs, %lluus)\n", szFormat, uElapse / 1000000.0, uElapse); + return Reset(); +} + +uint64_t ZTimer::PrintResult(bool bSuccess, const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szFormat) + uint64_t uElapse = GetMicroSecond() - m_uBeginTime; + ZLog::PrintResultV(bSuccess, "%s (%.03fs, %lluus)\n", szFormat, uElapse / 1000000.0, uElapse); + return Reset(); +} + +int ZLog::g_nLogLevel = ZLog::E_INFO; +vector ZLog::logs; + +void ZLog::SetLogLever(int nLogLevel) +{ + g_nLogLevel = nLogLevel; +} + +void ZLog::writeToLogFile(const std::string& message) { +// const char* documentsPath = getDocumentsDirectory(); +// std::string logFilePath = std::string(documentsPath) + "/logs.txt"; +// +// std::ofstream logFile(logFilePath, std::ios_base::app); +// if (logFile.is_open()) { +// logFile << message; +// logFile.close(); +// } else { +// std::cerr << "Failed to open log file: " << logFilePath << std::endl; +// } + writeToNSLog(message.data()); + logs.push_back(message); +} + +void ZLog::Print(int nLevel, const char *szLog) +{ + if (g_nLogLevel >= nLevel) + { + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::PrintV(int nLevel, const char *szFormatArgs, ...) { + if (g_nLogLevel >= nLevel) { + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +bool ZLog::Error(const char *szLog) +{ + write(STDOUT_FILENO, "\033[31m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +bool ZLog::ErrorV(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, "\033[31m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +bool ZLog::Success(const char *szLog) +{ + write(STDOUT_FILENO, "\033[32m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return true; +} + +bool ZLog::SuccessV(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, "\033[32m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return true; +} + +bool ZLog::PrintResult(bool bSuccess, const char *szLog) +{ + return bSuccess ? Success(szLog) : Error(szLog); +} + +bool ZLog::PrintResultV(bool bSuccess, const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + return bSuccess ? Success(szLog) : Error(szLog); +} + +bool ZLog::Warn(const char *szLog) +{ + write(STDOUT_FILENO, "\033[33m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +bool ZLog::WarnV(const char *szFormatArgs, ...) +{ + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, "\033[33m", 5); + write(STDOUT_FILENO, szLog, strlen(szLog)); + write(STDOUT_FILENO, "\033[0m", 4); + writeToLogFile(szLog); + return false; +} + +void ZLog::Print(const char *szLog) +{ + if (g_nLogLevel >= E_INFO) + { + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::PrintV(const char *szFormatArgs, ...) +{ + if (g_nLogLevel >= E_INFO) + { + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::Debug(const char *szLog) +{ + if (g_nLogLevel >= E_DEBUG) + { + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +void ZLog::DebugV(const char *szFormatArgs, ...) +{ + if (g_nLogLevel >= E_DEBUG) + { + PARSEVALIST(szFormatArgs, szLog) + write(STDOUT_FILENO, szLog, strlen(szLog)); + writeToLogFile(szLog); + } +} + +bool ZLog::IsDebug() +{ + return (E_DEBUG == g_nLogLevel); +} diff --git a/ZSign/common/common.h b/ZSign/common/common.h new file mode 100644 index 0000000..d84a2e4 --- /dev/null +++ b/ZSign/common/common.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +using namespace std; + +#define LE(x) _Swap(x) +#define BE(x) _Swap(x) + +#if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) +#endif + +uint16_t _Swap(uint16_t value); +uint32_t _Swap(uint32_t value); +uint64_t _Swap(uint64_t value); + +bool ReadFile(const char *szFile, string &strData); +bool ReadFile(string &strData, const char *szFormatPath, ...); +bool WriteFile(const char *szFile, const string &strData); +bool WriteFile(const char *szFile, const char *szData, size_t sLen); +bool WriteFile(string &strData, const char *szFormatPath, ...); +bool WriteFile(const char *szData, size_t sLen, const char *szFormatPath, ...); +bool AppendFile(const char *szFile, const string &strData); +bool AppendFile(const char *szFile, const char *szData, size_t sLen); +bool AppendFile(const string &strData, const char *szFormatPath, ...); +bool IsRegularFile(const char *szFile); +bool IsFolder(const char *szFolder); +bool IsFolderV(const char *szFormatPath, ...); +bool CreateFolder(const char *szFolder); +bool CreateFolderV(const char *szFormatPath, ...); +bool RemoveFile(const char *szFile); +bool RemoveFileV(const char *szFormatPath, ...); +bool RemoveFolder(const char *szFolder); +bool RemoveFolderV(const char *szFormatPath, ...); +bool IsFileExists(const char *szFile); +bool IsFileExistsV(const char *szFormatPath, ...); +int64_t GetFileSize(int fd); +int64_t GetFileSize(const char *szFile); +int64_t GetFileSizeV(const char *szFormatPath, ...); +string GetFileSizeString(const char *szFile); +bool IsZipFile(const char *szFile); +string GetCanonicalizePath(const char *szPath); +void *MapFile(const char *path, size_t offset, size_t size, size_t *psize, bool ro); +bool IsPathSuffix(const string &strPath, const char *suffix); + +const char *StringFormat(string &strFormat, const char *szFormatArgs, ...); +string &StringReplace(string &context, const string &from, const string &to); +void StringSplit(const string &src, const string &split, vector &dest); + +string FormatSize(int64_t size, int64_t base = 1024); +time_t GetUnixStamp(); +uint64_t GetMicroSecond(); +bool SystemExec(const char *szFormatCmd, ...); +uint32_t ByteAlign(uint32_t uValue, uint32_t uAlign); + +enum +{ + E_SHASUM_TYPE_1 = 1, + E_SHASUM_TYPE_256 = 2, +}; + +bool SHASum(int nSumType, uint8_t *data, size_t size, string &strOutput); +bool SHASum(int nSumType, const string &strData, string &strOutput); +bool SHASum(const string &strData, string &strSHA1, string &strSHA256); +bool SHA1Text(const string &strData, string &strOutput); +bool SHASumFile(const char *szFile, string &strSHA1, string &strSHA256); +bool SHASumBase64(const string &strData, string &strSHA1Base64, string &strSHA256Base64); +bool SHASumBase64File(const char *szFile, string &strSHA1Base64, string &strSHA256Base64); +void PrintSHASum(const char *prefix, const uint8_t *hash, uint32_t size, const char *suffix = "\n"); +void PrintSHASum(const char *prefix, const string &strSHASum, const char *suffix = "\n"); +void PrintDataSHASum(const char *prefix, int nSumType, const string &strData, const char *suffix = "\n"); +void PrintDataSHASum(const char *prefix, int nSumType, uint8_t *data, size_t size, const char *suffix = "\n"); + +class ZBuffer +{ +public: + ZBuffer(); + ~ZBuffer(); + +public: + char *GetBuffer(uint32_t uSize); + +private: + void Free(); + +private: + char *m_pData; + uint32_t m_uSize; +}; + +class ZTimer +{ +public: + ZTimer(); + +public: + uint64_t Reset(); + uint64_t Print(const char *szFormatArgs, ...); + uint64_t PrintResult(bool bSuccess, const char *szFormatArgs, ...); + +private: + uint64_t m_uBeginTime; +}; + +class ZLog +{ +public: + enum eLogType + { + E_NONE = 0, + E_ERROR = 1, + E_WARN = 2, + E_INFO = 3, + E_DEBUG = 4 + }; + +public: + static bool IsDebug(); + static void Print(const char *szLog); + static void PrintV(const char *szFormatArgs, ...); + static void Debug(const char *szLog); + static void DebugV(const char *szFormatArgs, ...); + static bool Warn(const char *szLog); + static bool WarnV(const char *szFormatArgs, ...); + static bool Error(const char *szLog); + static bool ErrorV(const char *szFormatArgs, ...); + static bool Success(const char *szLog); + static bool SuccessV(const char *szFormatArgs, ...); + static bool PrintResult(bool bSuccess, const char *szLog); + static bool PrintResultV(bool bSuccess, const char *szFormatArgs, ...); + static void Print(int nLevel, const char *szLog); + static void PrintV(int nLevel, const char *szFormatArgs, ...); + static void SetLogLever(int nLogLevel); + static vector logs; + +private: + static int g_nLogLevel; + static void writeToLogFile(const std::string& message); + +}; diff --git a/ZSign/common/json.cpp b/ZSign/common/json.cpp new file mode 100644 index 0000000..99721bb --- /dev/null +++ b/ZSign/common/json.cpp @@ -0,0 +1,3061 @@ +#include "json.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "base64.h" + +#ifndef WIN32 +#define _atoi64(val) strtoll(val, NULL, 10) +#endif + +const JValue JValue::null; +const string JValue::nullData; + +JValue::JValue(TYPE type) : m_eType(type) +{ + m_Value.vFloat = 0; +} + +JValue::JValue(int val) : m_eType(E_INT) +{ + m_Value.vInt64 = val; +} + +JValue::JValue(int64_t val) : m_eType(E_INT) +{ + m_Value.vInt64 = val; +} + +JValue::JValue(bool val) : m_eType(E_BOOL) +{ + m_Value.vBool = val; +} + +JValue::JValue(double val) : m_eType(E_FLOAT) +{ + m_Value.vFloat = val; +} + +JValue::JValue(const char *val) : m_eType(E_STRING) +{ + m_Value.vString = NewString(val); +} + +JValue::JValue(const string &val) : m_eType(E_STRING) +{ + m_Value.vString = NewString(val.c_str()); +} + +JValue::JValue(const JValue &other) +{ + CopyValue(other); +} + +JValue::JValue(const char *val, size_t len) : m_eType(E_DATA) +{ + m_Value.vData = new string(); + m_Value.vData->append(val, len); +} + +JValue::~JValue() +{ + Free(); +} + +void JValue::clear() +{ + Free(); +} + +bool JValue::isInt() const +{ + return (E_INT == m_eType); +} + +bool JValue::isNull() const +{ + return (E_NULL == m_eType); +} + +bool JValue::isBool() const +{ + return (E_BOOL == m_eType); +} + +bool JValue::isFloat() const +{ + return (E_FLOAT == m_eType); +} + +bool JValue::isString() const +{ + return (E_STRING == m_eType); +} + +bool JValue::isArray() const +{ + return (E_ARRAY == m_eType); +} + +bool JValue::isObject() const +{ + return (E_OBJECT == m_eType); +} + +bool JValue::isEmpty() const +{ + switch (m_eType) + { + case E_NULL: + return true; + break; + case E_INT: + return (0 == m_Value.vInt64); + break; + case E_BOOL: + return (false == m_Value.vBool); + break; + case E_FLOAT: + return (0 == m_Value.vFloat); + break; + case E_ARRAY: + case E_OBJECT: + return (0 == size()); + break; + case E_STRING: + return (0 == strlen(asCString())); + case E_DATE: + return (0 == m_Value.vDate); + break; + case E_DATA: + return (NULL == m_Value.vData) ? true : m_Value.vData->empty(); + break; + } + return true; +} + +JValue::operator const char *() const +{ + return asCString(); +} + +JValue::operator int() const +{ + return asInt(); +} + +JValue::operator int64_t() const +{ + return asInt64(); +} + +JValue::operator double() const +{ + return asFloat(); +} + +JValue::operator string() const +{ + return asCString(); +} + +JValue::operator bool() const +{ + return asBool(); +} + +char *JValue::NewString(const char *cstr) +{ + char *str = NULL; + if (NULL != cstr) + { + size_t len = (strlen(cstr) + 1) * sizeof(char); + str = (char *)malloc(len); + memcpy(str, cstr, len); + } + return str; +} + +void JValue::CopyValue(const JValue &src) +{ + m_eType = src.m_eType; + switch (m_eType) + { + case E_ARRAY: + m_Value.vArray = (NULL == src.m_Value.vArray) ? NULL : new vector(*(src.m_Value.vArray)); + break; + case E_OBJECT: + m_Value.vObject = (NULL == src.m_Value.vObject) ? NULL : new map(*(src.m_Value.vObject)); + break; + case E_STRING: + m_Value.vString = (NULL == src.m_Value.vString) ? NULL : NewString(src.m_Value.vString); + break; + case E_DATA: + { + if (NULL != src.m_Value.vData) + { + m_Value.vData = new string(); + *m_Value.vData = *src.m_Value.vData; + } + else + { + m_Value.vData = NULL; + } + } + break; + default: + m_Value = src.m_Value; + break; + } +} + +void JValue::Free() +{ + switch (m_eType) + { + case E_INT: + { + m_Value.vInt64 = 0; + } + break; + case E_BOOL: + { + m_Value.vBool = false; + } + break; + case E_FLOAT: + { + m_Value.vFloat = 0.0; + } + break; + case E_STRING: + { + if (NULL != m_Value.vString) + { + free(m_Value.vString); + m_Value.vString = NULL; + } + } + break; + case E_ARRAY: + { + if (NULL != m_Value.vArray) + { + delete m_Value.vArray; + m_Value.vArray = NULL; + } + } + break; + case E_OBJECT: + { + if (NULL != m_Value.vObject) + { + delete m_Value.vObject; + m_Value.vObject = NULL; + } + } + break; + case E_DATE: + { + m_Value.vDate = 0; + } + break; + case E_DATA: + { + if (NULL != m_Value.vData) + { + delete m_Value.vData; + m_Value.vData = NULL; + } + } + break; + default: + break; + } + m_eType = E_NULL; +} + +JValue &JValue::operator=(const JValue &other) +{ + if (this != &other) + { + Free(); + CopyValue(other); + } + return (*this); +} + +JValue::TYPE JValue::type() const +{ + return m_eType; +} + +int JValue::asInt() const +{ + return (int)asInt64(); +} + +int64_t JValue::asInt64() const +{ + switch (m_eType) + { + case E_INT: + return m_Value.vInt64; + break; + case E_BOOL: + return m_Value.vBool ? 1 : 0; + break; + case E_FLOAT: + return int(m_Value.vFloat); + break; + case E_STRING: + return _atoi64(asCString()); + break; + default: + break; + } + return 0; +} + +double JValue::asFloat() const +{ + switch (m_eType) + { + case E_INT: + return double(m_Value.vInt64); + break; + case E_BOOL: + return m_Value.vBool ? 1.0 : 0.0; + break; + case E_FLOAT: + return m_Value.vFloat; + break; + case E_STRING: + return atof(asCString()); + break; + default: + break; + } + return 0.0; +} + +bool JValue::asBool() const +{ + switch (m_eType) + { + case E_BOOL: + return m_Value.vBool; + break; + case E_INT: + return (0 != m_Value.vInt64); + break; + case E_FLOAT: + return (0.0 != m_Value.vFloat); + break; + case E_ARRAY: + return (NULL == m_Value.vArray) ? false : (m_Value.vArray->size() > 0); + break; + case E_OBJECT: + return (NULL == m_Value.vObject) ? false : (m_Value.vObject->size() > 0); + break; + case E_STRING: + return (NULL == m_Value.vString) ? false : (strlen(m_Value.vString) > 0); + break; + case E_DATE: + return (m_Value.vDate > 0); + break; + case E_DATA: + return (NULL == m_Value.vData) ? false : (m_Value.vData->size() > 0); + break; + default: + break; + } + return false; +} + +string JValue::asString() const +{ + switch (m_eType) + { + case E_BOOL: + return m_Value.vBool ? "true" : "false"; + break; + case E_INT: + { + char buf[256]; + sprintf(buf, "%" PRId64, m_Value.vInt64); + return buf; + } + break; + case E_FLOAT: + { + char buf[256]; + sprintf(buf, "%lf", m_Value.vFloat); + return buf; + } + break; + case E_ARRAY: + return "array"; + break; + case E_OBJECT: + return "object"; + break; + case E_STRING: + return (NULL == m_Value.vString) ? "" : m_Value.vString; + break; + case E_DATE: + return "date"; + break; + case E_DATA: + return "data"; + break; + default: + break; + } + return ""; +} + +const char *JValue::asCString() const +{ + if (E_STRING == m_eType && NULL != m_Value.vString) + { + return m_Value.vString; + } + return ""; +} + +size_t JValue::size() const +{ + switch (m_eType) + { + case E_ARRAY: + return (NULL == m_Value.vArray) ? 0 : m_Value.vArray->size(); + break; + case E_OBJECT: + return (NULL == m_Value.vObject) ? 0 : m_Value.vObject->size(); + break; + case E_DATA: + return (NULL == m_Value.vData) ? 0 : m_Value.vData->size(); + break; + default: + break; + } + return 0; +} + +JValue &JValue::operator[](int index) +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +const JValue &JValue::operator[](int index) const +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +JValue &JValue::operator[](int64_t index) +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +const JValue &JValue::operator[](int64_t index) const +{ + return (*this)[(size_t)(index < 0 ? 0 : index)]; +} + +JValue &JValue::operator[](size_t index) +{ + if (E_ARRAY != m_eType || NULL == m_Value.vArray) + { + Free(); + m_eType = E_ARRAY; + m_Value.vArray = new vector(); + } + + size_t sum = m_Value.vArray->size(); + if (sum <= index) + { + size_t fill = index - sum; + for (size_t i = 0; i <= fill; i++) + { + m_Value.vArray->push_back(null); + } + } + + return m_Value.vArray->at(index); +} + +const JValue &JValue::operator[](size_t index) const +{ + if (E_ARRAY == m_eType && NULL != m_Value.vArray) + { + if (index < m_Value.vArray->size()) + { + return m_Value.vArray->at(index); + } + } + return null; +} + +JValue &JValue::operator[](const string &key) +{ + return (*this)[key.c_str()]; +} + +const JValue &JValue::operator[](const string &key) const +{ + return (*this)[key.c_str()]; +} + +JValue &JValue::operator[](const char *key) +{ + map::iterator it; + if (E_OBJECT != m_eType || NULL == m_Value.vObject) + { + Free(); + m_eType = E_OBJECT; + m_Value.vObject = new map(); + } + else + { + it = m_Value.vObject->find(key); + if (it != m_Value.vObject->end()) + { + return it->second; + } + } + it = m_Value.vObject->insert(m_Value.vObject->end(), make_pair(key, null)); + return it->second; +} + +const JValue &JValue::operator[](const char *key) const +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + map::const_iterator it = m_Value.vObject->find(key); + if (it != m_Value.vObject->end()) + { + return it->second; + } + } + return null; +} + +bool JValue::has(const char *key) const +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + if (m_Value.vObject->end() != m_Value.vObject->find(key)) + { + return true; + } + } + + return false; +} + +int JValue::index(const char *ele) const +{ + if (E_ARRAY == m_eType && NULL != m_Value.vArray) + { + for (size_t i = 0; i < m_Value.vArray->size(); i++) + { + if (ele == (*m_Value.vArray)[i].asString()) + { + return (int)i; + } + } + } + + return -1; +} + +JValue &JValue::at(int index) +{ + return (*this)[index]; +} + +JValue &JValue::at(size_t index) +{ + return (*this)[index]; +} + +JValue &JValue::at(const char *key) +{ + return (*this)[key]; +} + +bool JValue::remove(int index) +{ + if (index >= 0) + { + return remove((size_t)index); + } + return false; +} + +bool JValue::remove(size_t index) +{ + if (E_ARRAY == m_eType && NULL != m_Value.vArray) + { + if (index < m_Value.vArray->size()) + { + m_Value.vArray->erase(m_Value.vArray->begin() + index); + return true; + } + } + return false; +} + +bool JValue::remove(const char *key) +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + if (m_Value.vObject->end() != m_Value.vObject->find(key)) + { + m_Value.vObject->erase(key); + return !has(key); + } + } + return false; +} + +bool JValue::keys(vector &arrKeys) const +{ + if (E_OBJECT == m_eType && NULL != m_Value.vObject) + { + arrKeys.reserve(m_Value.vObject->size()); + map::iterator itbeg = m_Value.vObject->begin(); + map::iterator itend = m_Value.vObject->end(); + for (; itbeg != itend; itbeg++) + { + arrKeys.push_back((itbeg->first).c_str()); + } + return true; + } + return false; +} + +string JValue::write() const +{ + string strDoc; + return write(strDoc); +} + +const char *JValue::write(string &strDoc) const +{ + strDoc.clear(); + JWriter::FastWrite((*this), strDoc); + return strDoc.c_str(); +} + +bool JValue::read(const string &strdoc, string *pstrerr) +{ + return read(strdoc.c_str(), pstrerr); +} + +bool JValue::read(const char *pdoc, string *pstrerr) +{ + JReader reader; + bool bret = reader.parse(pdoc, *this); + if (!bret) + { + if (NULL != pstrerr) + { + reader.error(*pstrerr); + } + } + return bret; +} + +JValue &JValue::front() +{ + if (E_ARRAY == m_eType) + { + if (size() > 0) + { + return *(m_Value.vArray->begin()); + } + } + else if (E_OBJECT == m_eType) + { + if (size() > 0) + { + return m_Value.vObject->begin()->second; + } + } + return (*this); +} + +JValue &JValue::back() +{ + if (E_ARRAY == m_eType) + { + if (size() > 0) + { + return *(m_Value.vArray->rbegin()); + } + } + else if (E_OBJECT == m_eType) + { + if (size() > 0) + { + return m_Value.vObject->rbegin()->second; + } + } + return (*this); +} + +bool JValue::join(JValue &jv) +{ + if ((E_OBJECT == m_eType || E_NULL == m_eType) && E_OBJECT == jv.type()) + { + vector arrKeys; + jv.keys(arrKeys); + for (size_t i = 0; i < arrKeys.size(); i++) + { + (*this)[arrKeys[i]] = jv[arrKeys[i]]; + } + return true; + } + else if ((E_ARRAY == m_eType || E_NULL == m_eType) && E_ARRAY == jv.type()) + { + size_t count = this->size(); + for (size_t i = 0; i < jv.size(); i++) + { + (*this)[count] = jv[i]; + count++; + } + return true; + } + + return false; +} + +bool JValue::append(JValue &jv) +{ + if (E_ARRAY == m_eType || E_NULL == m_eType) + { + (*this)[((this->size() > 0) ? this->size() : 0)] = jv; + return true; + } + return false; +} + +bool JValue::push_back(int val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(bool val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(double val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(int64_t val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(const char *val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(const string &val) +{ + return push_back(JValue(val)); +} + +bool JValue::push_back(const JValue &jval) +{ + if (E_ARRAY == m_eType || E_NULL == m_eType) + { + (*this)[size()] = jval; + return true; + } + return false; +} + +bool JValue::push_back(const char *val, size_t len) +{ + return push_back(JValue(val, len)); +} + +std::string JValue::styleWrite() const +{ + string strDoc; + return styleWrite(strDoc); +} + +const char *JValue::styleWrite(string &strDoc) const +{ + strDoc.clear(); + JWriter jw; + strDoc = jw.StyleWrite(*this); + return strDoc.c_str(); +} + +void JValue::assignDate(time_t val) +{ + Free(); + m_eType = E_DATE; + m_Value.vDate = val; +} + +void JValue::assignData(const char *val, size_t size) +{ + Free(); + m_eType = E_DATA; + m_Value.vData = new string(); + m_Value.vData->append(val, size); +} + +void JValue::assignDateString(time_t val) +{ + Free(); + m_eType = E_STRING; + m_Value.vString = NewString(JWriter::d2s(val).c_str()); +} + +time_t JValue::asDate() const +{ + switch (m_eType) + { + case E_DATE: + return m_Value.vDate; + break; + case E_STRING: + { + if (isDateString()) + { + tm ft = {0}; + sscanf(m_Value.vString + 5, "%04d-%02d-%02dT%02d:%02d:%02dZ", &ft.tm_year, &ft.tm_mon, &ft.tm_mday, &ft.tm_hour, &ft.tm_min, &ft.tm_sec); + ft.tm_mon -= 1; + ft.tm_year -= 1900; + return mktime(&ft); + } + } + break; + default: + break; + } + return 0; +} + +string JValue::asData() const +{ + switch (m_eType) + { + case E_DATA: + return (NULL == m_Value.vData) ? nullData : *m_Value.vData; + break; + case E_STRING: + { + if (isDataString()) + { + ZBase64 b64; + int nDataLen = 0; + const char *pdata = b64.Decode(m_Value.vString + 5, 0, &nDataLen); + string strdata; + strdata.append(pdata, nDataLen); + return strdata; + } + } + break; + default: + break; + } + + return nullData; +} + +bool JValue::isData() const +{ + return (E_DATA == m_eType); +} + +bool JValue::isDate() const +{ + return (E_DATE == m_eType); +} + +bool JValue::isDataString() const +{ + if (E_STRING == m_eType) + { + if (NULL != m_Value.vString) + { + if (strlen(m_Value.vString) >= 5) + { + if (0 == memcmp(m_Value.vString, "data:", 5)) + { + return true; + } + } + } + } + + return false; +} + +bool JValue::isDateString() const +{ + if (E_STRING == m_eType) + { + if (NULL != m_Value.vString) + { + if (25 == strlen(m_Value.vString)) + { + if (0 == memcmp(m_Value.vString, "date:", 5)) + { + const char *pdate = m_Value.vString + 5; + if ('T' == pdate[10] && 'Z' == pdate[19]) + { + return true; + } + } + } + } + } + + return false; +} + +bool JValue::readPList(const string &strdoc, string *pstrerr /*= NULL*/) +{ + return readPList(strdoc.data(), strdoc.size(), pstrerr); +} + +bool JValue::readPList(const char *pdoc, size_t len /*= 0*/, string *pstrerr /*= NULL*/) +{ + if (NULL == pdoc) + { + return false; + } + + if (0 == len) + { + len = strlen(pdoc); + } + + PReader reader; + bool bret = reader.parse(pdoc, len, *this); + if (!bret) + { + if (NULL != pstrerr) + { + reader.error(*pstrerr); + } + } + + return bret; +} + +bool JValue::readFile(const char *file, string *pstrerr /*= NULL*/) +{ + if (NULL != file) + { + FILE *fp = fopen(file, "rb"); + if (NULL != fp) + { + string strdata; + struct stat stbuf; + if (0 == fstat(fileno(fp), &stbuf)) + { + if (S_ISREG(stbuf.st_mode)) + { + strdata.reserve(stbuf.st_size); + } + } + + char buf[4096] = {0}; + int nread = (int)fread(buf, 1, 4096, fp); + while (nread > 0) + { + strdata.append(buf, nread); + nread = (int)fread(buf, 1, 4096, fp); + } + fclose(fp); + return read(strdata, pstrerr); + } + } + + return false; +} + +bool JValue::readPListFile(const char *file, string *pstrerr /*= NULL*/) +{ + if (NULL != file) + { + FILE *fp = fopen(file, "rb"); + if (NULL != fp) + { + string strdata; + struct stat stbuf; + if (0 == fstat(fileno(fp), &stbuf)) + { + if (S_ISREG(stbuf.st_mode)) + { + strdata.reserve(stbuf.st_size); + } + } + + char buf[4096] = {0}; + int nread = (int)fread(buf, 1, 4096, fp); + while (nread > 0) + { + strdata.append(buf, nread); + nread = (int)fread(buf, 1, 4096, fp); + } + fclose(fp); + return readPList(strdata, pstrerr); + } + } + + return false; +} + +bool JValue::WriteDataToFile(const char *file, const char *data, size_t len) +{ + if (NULL == file || NULL == data || len <= 0) + { + return false; + } + + FILE *fp = fopen(file, "wb"); + if (NULL != fp) + { + int towrite = (int)len; + while (towrite > 0) + { + int nwrite = (int)fwrite(data + (len - towrite), 1, towrite, fp); + if (nwrite <= 0) + { + break; + } + towrite -= nwrite; + } + + fclose(fp); + return (towrite > 0) ? false : true; + } + + return false; +} + +bool JValue::writeFile(const char *file) +{ + string strdata; + write(strdata); + return WriteDataToFile(file, strdata.data(), strdata.size()); +} + +bool JValue::writePListFile(const char *file) +{ + string strdata; + writePList(strdata); + return WriteDataToFile(file, strdata.data(), strdata.size()); +} + +bool JValue::styleWriteFile(const char *file) +{ + string strdata; + styleWrite(strdata); + return WriteDataToFile(file, strdata.data(), strdata.size()); +} + +bool JValue::readPath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return readFile(file); +} + +bool JValue::readPListPath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return readPListFile(file); +} + +bool JValue::writePath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return writeFile(file); +} + +bool JValue::writePListPath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return writePListFile(file); +} + +bool JValue::styleWritePath(const char *path, ...) +{ + char file[1024] = {0}; + va_list args; + va_start(args, path); + vsnprintf(file, 1024, path, args); + va_end(args); + + return styleWriteFile(file); +} + +string JValue::writePList() const +{ + string strDoc; + return writePList(strDoc); +} + +const char *JValue::writePList(string &strDoc) const +{ + strDoc.clear(); + PWriter::FastWrite((*this), strDoc); + return strDoc.c_str(); +} + +// Class Reader +// ////////////////////////////////////////////////////////////////// +bool JReader::parse(const char *pdoc, JValue &root) +{ + root.clear(); + if (NULL != pdoc) + { + m_pBeg = pdoc; + m_pEnd = m_pBeg + strlen(pdoc); + m_pCur = m_pBeg; + m_pErr = m_pBeg; + m_strErr = "null"; + return readValue(root); + } + return false; +} + +bool JReader::readValue(JValue &jval) +{ + Token token; + readToken(token); + switch (token.type) + { + case Token::E_True: + jval = true; + break; + case Token::E_False: + jval = false; + break; + case Token::E_Null: + jval = JValue(); + break; + case Token::E_Number: + return decodeNumber(token, jval); + break; + case Token::E_ArrayBegin: + return readArray(jval); + break; + case Token::E_ObjectBegin: + return readObject(jval); + break; + case Token::E_String: + { + string strval; + bool bok = decodeString(token, strval); + if (bok) + { + jval = strval.c_str(); + } + return bok; + } + break; + default: + return addError("Syntax error: value, object or array expected.", token.pbeg); + break; + } + return true; +} + +bool JReader::readToken(Token &token) +{ + skipSpaces(); + token.pbeg = m_pCur; + switch (GetNextChar()) + { + case '{': + token.type = Token::E_ObjectBegin; + break; + case '}': + token.type = Token::E_ObjectEnd; + break; + case '[': + token.type = Token::E_ArrayBegin; + break; + case ']': + token.type = Token::E_ArrayEnd; + break; + case ',': + token.type = Token::E_ArraySeparator; + break; + case ':': + token.type = Token::E_MemberSeparator; + break; + case 0: + token.type = Token::E_End; + break; + case '"': + token.type = readString() ? Token::E_String : Token::E_Error; + break; + case '/': + case '#': + case ';': + { + skipComment(); + return readToken(token); + } + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + { + token.type = Token::E_Number; + readNumber(); + } + break; + case 't': + token.type = match("rue", 3) ? Token::E_True : Token::E_Error; + break; + case 'f': + token.type = match("alse", 4) ? Token::E_False : Token::E_Error; + break; + case 'n': + token.type = match("ull", 3) ? Token::E_Null : Token::E_Error; + break; + default: + token.type = Token::E_Error; + break; + } + token.pend = m_pCur; + return true; +} + +void JReader::skipSpaces() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + { + m_pCur++; + } + else + { + break; + } + } +} + +bool JReader::match(const char *pattern, int patternLength) +{ + if (m_pEnd - m_pCur < patternLength) + { + return false; + } + int index = patternLength; + while (index--) + { + if (m_pCur[index] != pattern[index]) + { + return false; + } + } + m_pCur += patternLength; + return true; +} + +void JReader::skipComment() +{ + char c = GetNextChar(); + if (c == '*') + { + while (m_pCur != m_pEnd) + { + char c = GetNextChar(); + if (c == '*' && *m_pCur == '/') + { + break; + } + } + } + else if (c == '/') + { + while (m_pCur != m_pEnd) + { + char c = GetNextChar(); + if (c == '\r' || c == '\n') + { + break; + } + } + } +} + +void JReader::readNumber() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if ((c >= '0' && c <= '9') || (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) + { + ++m_pCur; + } + else + { + break; + } + } +} + +bool JReader::readString() +{ + char c = 0; + while (m_pCur != m_pEnd) + { + c = GetNextChar(); + if ('\\' == c) + { + GetNextChar(); + } + else if ('"' == c) + { + break; + } + } + return ('"' == c); +} + +bool JReader::readObject(JValue &jval) +{ + string name; + Token tokenName; + jval = JValue(JValue::E_OBJECT); + while (readToken(tokenName)) + { + if (Token::E_ObjectEnd == tokenName.type) + { //empty + return true; + } + + if (Token::E_String != tokenName.type) + { + break; + } + + if (!decodeString(tokenName, name)) + { + return false; + } + + Token colon; + readToken(colon); + if (Token::E_MemberSeparator != colon.type) + { + return addError("Missing ':' after object member name", colon.pbeg); + } + + if (!readValue(jval[name.c_str()])) + { // error already set + return false; + } + + Token comma; + readToken(comma); + if (Token::E_ObjectEnd == comma.type) + { + return true; + } + + if (Token::E_ArraySeparator != comma.type) + { + return addError("Missing ',' or '}' in object declaration", comma.pbeg); + } + } + return addError("Missing '}' or object member name", tokenName.pbeg); +} + +bool JReader::readArray(JValue &jval) +{ + jval = JValue(JValue::E_ARRAY); + skipSpaces(); + if (']' == *m_pCur) // empty array + { + Token endArray; + readToken(endArray); + return true; + } + + size_t index = 0; + while (true) + { + if (!readValue(jval[index++])) + { //error already set + return false; + } + + Token token; + readToken(token); + if (Token::E_ArrayEnd == token.type) + { + break; + } + if (Token::E_ArraySeparator != token.type) + { + return addError("Missing ',' or ']' in array declaration", token.pbeg); + } + } + return true; +} + +bool JReader::decodeNumber(Token &token, JValue &jval) +{ + int64_t val = 0; + bool isNeg = false; + const char *pcur = token.pbeg; + if ('-' == *pcur) + { + pcur++; + isNeg = true; + } + for (const char *p = pcur; p != token.pend; p++) + { + char c = *p; + if ('.' == c || 'e' == c || 'E' == c) + { + return decodeDouble(token, jval); + } + else if (c < '0' || c > '9') + { + return addError("'" + string(token.pbeg, token.pend) + "' is not a number.", token.pbeg); + } + else + { + val = val * 10 + (c - '0'); + } + } + jval = isNeg ? -val : val; + return true; +} + +bool JReader::decodeDouble(Token &token, JValue &jval) +{ + const size_t szbuf = 512; + size_t len = size_t(token.pend - token.pbeg); + if (len <= szbuf) + { + char buf[szbuf]; + memcpy(buf, token.pbeg, len); + buf[len] = 0; + double val = 0; + if (1 == sscanf(buf, "%lf", &val)) + { + jval = val; + return true; + } + } + return addError("'" + string(token.pbeg, token.pend) + "' is too large or not a number.", token.pbeg); +} + +bool JReader::decodeString(Token &token, string &strdec) +{ + strdec = ""; + const char *pcur = token.pbeg + 1; + const char *pend = token.pend - 1; + strdec.reserve(size_t(token.pend - token.pbeg)); + while (pcur != pend) + { + char c = *pcur++; + if ('\\' == c) + { + if (pcur != pend) + { + char escape = *pcur++; + switch (escape) + { + case '"': + strdec += '"'; + break; + case '\\': + strdec += '\\'; + break; + case 'b': + strdec += '\b'; + break; + case 'f': + strdec += '\f'; + break; + case 'n': + strdec += '\n'; + break; + case 'r': + strdec += '\r'; + break; + case 't': + strdec += '\t'; + break; + case '/': + strdec += '/'; + break; + case 'u': + { // based on description from http://en.wikipedia.org/wiki/UTF-8 + + string strUnic; + strUnic.append(pcur, 4); + + pcur += 4; + + unsigned int cp = 0; + if (1 != sscanf(strUnic.c_str(), "%x", &cp)) + { + return addError("Bad escape sequence in string", pcur); + } + + string strUTF8; + + if (cp <= 0x7f) + { + strUTF8.resize(1); + strUTF8[0] = static_cast(cp); + } + else if (cp <= 0x7FF) + { + strUTF8.resize(2); + strUTF8[1] = static_cast(0x80 | (0x3f & cp)); + strUTF8[0] = static_cast(0xC0 | (0x1f & (cp >> 6))); + } + else if (cp <= 0xFFFF) + { + strUTF8.resize(3); + strUTF8[2] = static_cast(0x80 | (0x3f & cp)); + strUTF8[1] = 0x80 | static_cast((0x3f & (cp >> 6))); + strUTF8[0] = 0xE0 | static_cast((0xf & (cp >> 12))); + } + else if (cp <= 0x10FFFF) + { + strUTF8.resize(4); + strUTF8[3] = static_cast(0x80 | (0x3f & cp)); + strUTF8[2] = static_cast(0x80 | (0x3f & (cp >> 6))); + strUTF8[1] = static_cast(0x80 | (0x3f & (cp >> 12))); + strUTF8[0] = static_cast(0xF0 | (0x7 & (cp >> 18))); + } + + strdec += strUTF8; + } + break; + default: + return addError("Bad escape sequence in string", pcur); + break; + } + } + else + { + return addError("Empty escape sequence in string", pcur); + } + } + else if ('"' == c) + { + break; + } + else + { + strdec += c; + } + } + return true; +} + +bool JReader::addError(const string &message, const char *ploc) +{ + m_pErr = ploc; + m_strErr = message; + return false; +} + +char JReader::GetNextChar() +{ + return (m_pCur == m_pEnd) ? 0 : *m_pCur++; +} + +void JReader::error(string &strmsg) const +{ + strmsg = ""; + int row = 1; + const char *pcur = m_pBeg; + const char *plast = m_pBeg; + while (pcur < m_pErr && pcur <= m_pEnd) + { + char c = *pcur++; + if (c == '\r' || c == '\n') + { + if (c == '\r' && *pcur == '\n') + { + pcur++; + } + row++; + plast = pcur; + } + } + char msg[64]; + sprintf(msg, "Error: Line %d, Column %d, ", row, int(m_pErr - plast) + 1); + strmsg += msg + m_strErr + "\n"; +} + +// Class Writer +// ////////////////////////////////////////////////////////////////// +void JWriter::FastWrite(const JValue &jval, string &strDoc) +{ + strDoc = ""; + FastWriteValue(jval, strDoc); + //strDoc += "\n"; +} + +void JWriter::FastWriteValue(const JValue &jval, string &strDoc) +{ + switch (jval.type()) + { + case JValue::E_NULL: + strDoc += "null"; + break; + case JValue::E_INT: + strDoc += v2s(jval.asInt64()); + break; + case JValue::E_BOOL: + strDoc += jval.asBool() ? "true" : "false"; + break; + case JValue::E_FLOAT: + strDoc += v2s(jval.asFloat()); + break; + case JValue::E_STRING: + strDoc += v2s(jval.asCString()); + break; + case JValue::E_ARRAY: + { + strDoc += "["; + size_t usize = jval.size(); + for (size_t i = 0; i < usize; i++) + { + strDoc += (i > 0) ? "," : ""; + FastWriteValue(jval[i], strDoc); + } + strDoc += "]"; + } + break; + case JValue::E_OBJECT: + { + strDoc += "{"; + vector arrKeys; + jval.keys(arrKeys); + size_t usize = arrKeys.size(); + for (size_t i = 0; i < usize; i++) + { + const string &name = arrKeys[i]; + strDoc += (i > 0) ? "," : ""; + strDoc += v2s(name.c_str()) + ":"; + FastWriteValue(jval[name.c_str()], strDoc); + } + strDoc += "}"; + } + break; + case JValue::E_DATE: + { + strDoc += "\"date:"; + strDoc += d2s(jval.asDate()); + strDoc += "\""; + } + break; + case JValue::E_DATA: + { + strDoc += "\"data:"; + const string &strData = jval.asData(); + ZBase64 b64; + strDoc += b64.Encode(strData.data(), (int)strData.size()); + strDoc += "\""; + } + break; + } +} + +const string &JWriter::StyleWrite(const JValue &jval) +{ + m_strDoc = ""; + m_strTab = ""; + m_bAddChild = false; + StyleWriteValue(jval); + m_strDoc += "\n"; + return m_strDoc; +} + +void JWriter::StyleWriteValue(const JValue &jval) +{ + switch (jval.type()) + { + case JValue::E_NULL: + PushValue("null"); + break; + case JValue::E_INT: + PushValue(v2s(jval.asInt64())); + break; + case JValue::E_BOOL: + PushValue(jval.asBool() ? "true" : "false"); + break; + case JValue::E_FLOAT: + PushValue(v2s(jval.asFloat())); + break; + case JValue::E_STRING: + PushValue(v2s(jval.asCString())); + break; + case JValue::E_ARRAY: + StyleWriteArrayValue(jval); + break; + case JValue::E_OBJECT: + { + vector arrKeys; + jval.keys(arrKeys); + if (!arrKeys.empty()) + { + m_strDoc += '\n' + m_strTab + "{"; + m_strTab += '\t'; + size_t usize = arrKeys.size(); + for (size_t i = 0; i < usize; i++) + { + const string &name = arrKeys[i]; + m_strDoc += (i > 0) ? "," : ""; + m_strDoc += '\n' + m_strTab + v2s(name.c_str()) + " : "; + StyleWriteValue(jval[name]); + } + m_strTab.resize(m_strTab.size() - 1); + m_strDoc += '\n' + m_strTab + "}"; + } + else + { + PushValue("{}"); + } + } + break; + case JValue::E_DATE: + { + string strDoc; + strDoc += "\"date:"; + strDoc += d2s(jval.asDate()); + strDoc += "\""; + PushValue(strDoc); + } + break; + case JValue::E_DATA: + { + string strDoc; + strDoc += "\"data:"; + const string &strData = jval.asData(); + ZBase64 b64; + strDoc += b64.Encode(strData.data(), (int)strData.size()); + strDoc += "\""; + PushValue(strDoc); + } + break; + } +} + +void JWriter::StyleWriteArrayValue(const JValue &jval) +{ + size_t usize = jval.size(); + if (usize > 0) + { + bool isArrayMultiLine = isMultineArray(jval); + if (isArrayMultiLine) + { + m_strDoc += '\n' + m_strTab + "["; + m_strTab += '\t'; + bool hasChildValue = !m_childValues.empty(); + for (size_t i = 0; i < usize; i++) + { + m_strDoc += (i > 0) ? "," : ""; + if (hasChildValue) + { + m_strDoc += '\n' + m_strTab + m_childValues[i]; + } + else + { + m_strDoc += '\n' + m_strTab; + StyleWriteValue(jval[i]); + } + } + m_strTab.resize(m_strTab.size() - 1); + m_strDoc += '\n' + m_strTab + "]"; + } + else + { + m_strDoc += "[ "; + for (size_t i = 0; i < usize; ++i) + { + m_strDoc += (i > 0) ? ", " : ""; + m_strDoc += m_childValues[i]; + } + m_strDoc += " ]"; + } + } + else + { + PushValue("[]"); + } +} + +bool JWriter::isMultineArray(const JValue &jval) +{ + m_childValues.clear(); + size_t usize = jval.size(); + bool isMultiLine = (usize >= 25); + if (!isMultiLine) + { + for (size_t i = 0; i < usize; i++) + { + if (jval[i].size() > 0) + { + isMultiLine = true; + break; + } + } + } + if (!isMultiLine) + { + m_bAddChild = true; + m_childValues.reserve(usize); + size_t lineLength = 4 + (usize - 1) * 2; // '[ ' + ', '*n + ' ]' + for (size_t i = 0; i < usize; i++) + { + StyleWriteValue(jval[i]); + lineLength += m_childValues[i].length(); + } + m_bAddChild = false; + isMultiLine = lineLength >= 75; + } + return isMultiLine; +} + +void JWriter::PushValue(const string &strval) +{ + if (!m_bAddChild) + { + m_strDoc += strval; + } + else + { + m_childValues.push_back(strval); + } +} + +string JWriter::v2s(int64_t val) +{ + char buf[32]; + sprintf(buf, "%" PRId64, val); + return buf; +} + +string JWriter::v2s(double val) +{ + char buf[512]; + sprintf(buf, "%g", val); + return buf; +} + +string JWriter::d2s(time_t t) +{ + //t = (t > 0x7933F8EFF) ? (0x7933F8EFF - 1) : t; + + tm ft = {0}; + +#ifdef _WIN32 + localtime_s(&ft, &t); +#else + localtime_r(&t, &ft); +#endif + + ft.tm_year = (ft.tm_year < 0) ? 0 : ft.tm_year; + ft.tm_mon = (ft.tm_mon < 0) ? 0 : ft.tm_mon; + ft.tm_mday = (ft.tm_mday < 0) ? 0 : ft.tm_mday; + ft.tm_hour = (ft.tm_hour < 0) ? 0 : ft.tm_hour; + ft.tm_min = (ft.tm_min < 0) ? 0 : ft.tm_min; + ft.tm_sec = (ft.tm_sec < 0) ? 0 : ft.tm_sec; + + char szDate[64] = {0}; + sprintf(szDate, "%04d-%02d-%02dT%02d:%02d:%02dZ", ft.tm_year + 1900, ft.tm_mon + 1, ft.tm_mday, ft.tm_hour, ft.tm_min, ft.tm_sec); + return szDate; +} + +string JWriter::v2s(const char *pstr) +{ + if (NULL != strpbrk(pstr, "\"\\\b\f\n\r\t")) + { + string ret; + ret.reserve(strlen(pstr) * 2 + 3); + ret += "\""; + for (const char *c = pstr; 0 != *c; c++) + { + switch (*c) + { + case '\\': + { + c++; + bool bUnicode = false; + if ('u' == *c) + { + bool bFlag = true; + for (int i = 1; i <= 4; i++) + { + if (!isdigit(*(c + i))) + { + bFlag = false; + break; + } + } + bUnicode = bFlag; + } + + if (true == bUnicode) + { + ret += "\\u"; + } + else + { + ret += "\\\\"; + c--; + } + } + break; + case '\"': + ret += "\\\""; + break; + case '\b': + ret += "\\b"; + break; + case '\f': + ret += "\\f"; + break; + case '\n': + ret += "\\n"; + break; + case '\r': + ret += "\\r"; + break; + case '\t': + ret += "\\t"; + break; + default: + ret += *c; + break; + } + } + ret += "\""; + return ret; + } + else + { + return string("\"") + pstr + "\""; + } +} + +std::string JWriter::vstring2s(const char *pstr) +{ + return string("\\\"") + pstr + "\\\""; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +#define BE16TOH(x) ((((x)&0xFF00) >> 8) | (((x)&0x00FF) << 8)) + +#define BE32TOH(x) ((((x)&0xFF000000) >> 24) | (((x)&0x00FF0000) >> 8) | (((x)&0x0000FF00) << 8) | (((x)&0x000000FF) << 24)) + +#define BE64TOH(x) ((((x)&0xFF00000000000000ull) >> 56) | (((x)&0x00FF000000000000ull) >> 40) | (((x)&0x0000FF0000000000ull) >> 24) | (((x)&0x000000FF00000000ull) >> 8) | (((x)&0x00000000FF000000ull) << 8) | (((x)&0x0000000000FF0000ull) << 24) | (((x)&0x000000000000FF00ull) << 40) | (((x)&0x00000000000000FFull) << 56)) + +////////////////////////////////////////////////////////////////////////// +PReader::PReader() +{ + //xml + m_pBeg = NULL; + m_pEnd = NULL; + m_pCur = NULL; + m_pErr = NULL; + + //binary + m_pTrailer = NULL; + m_uObjects = 0; + m_uOffsetSize = 0; + m_pOffsetTable = 0; + m_uDictParamSize = 0; +} + +bool PReader::parse(const char *pdoc, size_t len, JValue &root) +{ + root.clear(); + if (NULL == pdoc) + { + return false; + } + + if (len < 30) + { + return false; + } + + if (0 == memcmp(pdoc, "bplist00", 8)) + { + return parseBinary(pdoc, len, root); + } + else + { + m_pBeg = pdoc; + m_pEnd = m_pBeg + len; + m_pCur = m_pBeg; + m_pErr = m_pBeg; + m_strErr = "null"; + + Token token; + readToken(token); + return readValue(root, token); + } +} + +bool PReader::readValue(JValue &pval, Token &token) +{ + switch (token.type) + { + case Token::E_True: + pval = true; + break; + case Token::E_False: + pval = false; + break; + case Token::E_Null: + pval = JValue(); + break; + case Token::E_Integer: + return decodeNumber(token, pval); + break; + case Token::E_Real: + return decodeDouble(token, pval); + break; + case Token::E_ArrayNull: + pval = JValue(JValue::E_ARRAY); + break; + case Token::E_ArrayBegin: + return readArray(pval); + break; + case Token::E_DictionaryNull: + pval = JValue(JValue::E_OBJECT); + break; + case Token::E_DictionaryBegin: + return readDictionary(pval); + break; + case Token::E_Date: + { + string strval; + decodeString(token, strval); + + tm ft = {0}; + sscanf(strval.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &ft.tm_year, &ft.tm_mon, &ft.tm_mday, &ft.tm_hour, &ft.tm_min, &ft.tm_sec); + ft.tm_mon -= 1; + ft.tm_year -= 1900; + pval.assignDate(mktime(&ft)); + } + break; + case Token::E_Data: + { + string strval; + decodeString(token, strval); + + ZBase64 b64; + int nDecLen = 0; + const char *data = b64.Decode(strval.data(), (int)strval.size(), &nDecLen); + pval.assignData(data, nDecLen); + } + break; + case Token::E_String: + { + string strval; + decodeString(token, strval, false); + XMLUnescape(strval); + pval = strval.c_str(); + } + break; + default: + return addError("Syntax error: value, dictionary or array expected.", token.pbeg); + break; + } + return true; +} + +bool PReader::readLabel(string &label) +{ + skipSpaces(); + + char c = *m_pCur++; + if ('<' != c) + { + return false; + } + + label.clear(); + label.reserve(10); + label += c; + + bool bEnd = false; + while (m_pCur != m_pEnd) + { + c = *m_pCur++; + if ('>' == c) + { + if ('/' == *(m_pCur - 1) || '?' == *(m_pCur - 1)) + { + label += *(m_pCur - 1); + } + + label += c; + break; + } + else if (' ' == c) + { + bEnd = true; + } + else if (!bEnd) + { + label += c; + } + } + + if ('>' != c) + { + label.clear(); + return false; + } + + return (!label.empty()); +} + +void PReader::endLabel(Token &token, const char *szLabel) +{ + string label; + readLabel(label); + if (szLabel != label) + { + token.type = Token::E_Error; + } +} + +bool PReader::readToken(Token &token) +{ + string label; + if (!readLabel(label)) + { + token.type = Token::E_Error; + return false; + } + + if ('?' == label.at(1) || '!' == label.at(1)) + { + return readToken(token); + } + + if ("" == label) + { + token.type = Token::E_DictionaryBegin; + } + else if ("" == label) + { + token.type = Token::E_DictionaryEnd; + } + else if ("" == label) + { + token.type = Token::E_ArrayBegin; + } + else if ("" == label) + { + token.type = Token::E_ArrayEnd; + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_Key : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.type = Token::E_Key; + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_String : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_Date : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readString() ? Token::E_Data : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readNumber() ? Token::E_Integer : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.pbeg = m_pCur; + token.type = readNumber() ? Token::E_Real : Token::E_Error; + token.pend = m_pCur; + + endLabel(token, ""); + } + else if ("" == label) + { + token.type = Token::E_True; + } + else if ("" == label) + { + token.type = Token::E_False; + } + else if ("" == label) + { + token.type = Token::E_ArrayNull; + } + else if ("" == label) + { + token.type = Token::E_DictionaryNull; + } + else if ("" == label || "" == label || "" == label || "" == label || "" == label) + { + token.type = Token::E_Null; + } + else if ("" == label) + { + return readToken(token); + } + else if ("" == label || "" == label) + { + token.type = Token::E_End; + } + else + { + token.type = Token::E_Error; + } + + return true; +} + +void PReader::skipSpaces() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') + { + m_pCur++; + } + else + { + break; + } + } +} + +bool PReader::readNumber() +{ + while (m_pCur != m_pEnd) + { + char c = *m_pCur; + if ((c >= '0' && c <= '9') || (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')) + { + ++m_pCur; + } + else + { + break; + } + } + return true; +} + +bool PReader::readString() +{ + while (m_pCur != m_pEnd) + { + if ('<' == *m_pCur) + { + break; + } + m_pCur++; + } + return ('<' == *m_pCur); +} + +bool PReader::readDictionary(JValue &pval) +{ + Token key; + string strKey; + pval = JValue(JValue::E_OBJECT); + while (readToken(key)) + { + if (Token::E_DictionaryEnd == key.type) + { //empty + return true; + } + + if (Token::E_Key != key.type) + { + break; + } + + strKey = ""; + if (!decodeString(key, strKey)) + { + return false; + } + XMLUnescape(strKey); + + Token val; + readToken(val); + if (!readValue(pval[strKey.c_str()], val)) + { + return false; + } + } + return addError("Missing '' or dictionary member name", key.pbeg); +} + +bool PReader::readArray(JValue &pval) +{ + pval = JValue(JValue::E_ARRAY); + + size_t index = 0; + while (true) + { + Token token; + readToken(token); + if (Token::E_ArrayEnd == token.type) + { + return true; + } + + if (!readValue(pval[index++], token)) + { + return false; + } + } + + return true; +} + +bool PReader::decodeNumber(Token &token, JValue &pval) +{ + int64_t val = 0; + bool isNeg = false; + const char *pcur = token.pbeg; + if ('-' == *pcur) + { + pcur++; + isNeg = true; + } + for (const char *p = pcur; p != token.pend; p++) + { + char c = *p; + if ('.' == c || 'e' == c || 'E' == c) + { + return decodeDouble(token, pval); + } + else if (c < '0' || c > '9') + { + return addError("'" + string(token.pbeg, token.pend) + "' is not a number.", token.pbeg); + } + else + { + val = val * 10 + (c - '0'); + } + } + pval = isNeg ? -val : val; + return true; +} + +bool PReader::decodeDouble(Token &token, JValue &pval) +{ + const size_t szbuf = 512; + size_t len = size_t(token.pend - token.pbeg); + if (len <= szbuf) + { + char buf[szbuf]; + memcpy(buf, token.pbeg, len); + buf[len] = 0; + double val = 0; + if (1 == sscanf(buf, "%lf", &val)) + { + pval = val; + return true; + } + } + return addError("'" + string(token.pbeg, token.pend) + "' is too large or not a number.", token.pbeg); +} + +bool PReader::decodeString(Token &token, string &strdec, bool filter) +{ + const char *pcur = token.pbeg; + const char *pend = token.pend; + strdec.reserve(size_t(token.pend - token.pbeg) + 6); + while (pcur != pend) + { + char c = *pcur++; + if (filter && ('\n' == c || '\r' == c || '\t' == c)) + { + continue; + } + strdec += c; + } + return true; +} + +bool PReader::addError(const string &message, const char *ploc) +{ + m_pErr = ploc; + m_strErr = message; + return false; +} + +void PReader::error(string &strmsg) const +{ + strmsg = ""; + int row = 1; + const char *pcur = m_pBeg; + const char *plast = m_pBeg; + while (pcur < m_pErr && pcur <= m_pEnd) + { + char c = *pcur++; + if (c == '\r' || c == '\n') + { + if (c == '\r' && *pcur == '\n') + { + pcur++; + } + row++; + plast = pcur; + } + } + char msg[64]; + sprintf(msg, "Error: Line %d, Column %d, ", row, int(m_pErr - plast) + 1); + strmsg += msg + m_strErr + "\n"; +} + +////////////////////////////////////////////////////////////////////////// +uint32_t PReader::getUInt24FromBE(const char *v) +{ + uint32_t ret = 0; + uint8_t *tmp = (uint8_t *)&ret; + memcpy(tmp, v, 3 * sizeof(char)); + byteConvert(tmp, sizeof(uint32_t)); + return ret; +} + +uint64_t PReader::getUIntVal(const char *v, size_t size) +{ + if (8 == size) + return BE64TOH(*((uint64_t *)v)); + else if (4 == size) + return BE32TOH(*((uint32_t *)v)); + else if (3 == size) + return getUInt24FromBE(v); + else if (2 == size) + return BE16TOH(*((uint16_t *)v)); + else + return *((uint8_t *)v); +} + +void PReader::byteConvert(uint8_t *v, size_t size) +{ + uint8_t tmp = 0; + for (size_t i = 0, j = 0; i < (size / 2); i++) + { + tmp = v[i]; + j = (size - 1) - i; + v[i] = v[j]; + v[j] = tmp; + } +} + +bool PReader::readUIntSize(const char *&pcur, size_t &size) +{ + JValue temp; + readBinaryValue(pcur, temp); + if (temp.isInt()) + { + size = (size_t)temp.asInt64(); + return true; + } + + assert(0); + return false; +} + +bool PReader::readUnicode(const char *pcur, size_t size, JValue &pv) +{ + if (0 == size) + { + pv = ""; + return false; + } + + uint16_t *unistr = (uint16_t *)malloc(2 * size); + memcpy(unistr, pcur, 2 * size); + for (size_t i = 0; i < size; i++) + { + byteConvert((uint8_t *)(unistr + i), 2); + } + + char *outbuf = (char *)malloc(3 * (size + 1)); + + size_t p = 0; + size_t i = 0; + uint16_t wc = 0; + while (i < size) + { + wc = unistr[i++]; + if (wc >= 0x800) + { + outbuf[p++] = (char)(0xE0 + ((wc >> 12) & 0xF)); + outbuf[p++] = (char)(0x80 + ((wc >> 6) & 0x3F)); + outbuf[p++] = (char)(0x80 + (wc & 0x3F)); + } + else if (wc >= 0x80) + { + outbuf[p++] = (char)(0xC0 + ((wc >> 6) & 0x1F)); + outbuf[p++] = (char)(0x80 + (wc & 0x3F)); + } + else + { + outbuf[p++] = (char)(wc & 0x7F); + } + } + + outbuf[p] = 0; + + pv = outbuf; + + free(outbuf); + outbuf = NULL; + free(unistr); + unistr = NULL; + + return true; +} + +bool PReader::readBinaryValue(const char *&pcur, JValue &pv) +{ + enum + { + BPLIST_NULL = 0x00, + BPLIST_FALSE = 0x08, + BPLIST_TRUE = 0x09, + BPLIST_FILL = 0x0F, + BPLIST_UINT = 0x10, + BPLIST_REAL = 0x20, + BPLIST_DATE = 0x30, + BPLIST_DATA = 0x40, + BPLIST_STRING = 0x50, + BPLIST_UNICODE = 0x60, + BPLIST_UNK_0x70 = 0x70, + BPLIST_UID = 0x80, + BPLIST_ARRAY = 0xA0, + BPLIST_SET = 0xC0, + BPLIST_DICT = 0xD0, + BPLIST_MASK = 0xF0 + }; + + uint8_t c = *pcur++; + uint8_t key = c & 0xF0; + uint8_t val = c & 0x0F; + + switch (key) + { + case BPLIST_NULL: + { + switch (val) + { + case BPLIST_TRUE: + { + pv = true; + } + break; + case BPLIST_FALSE: + { + pv = false; + } + break; + case BPLIST_NULL: + { + } + break; + default: + { + assert(0); + return false; + } + break; + } + } + break; + case BPLIST_UID: + case BPLIST_UINT: + { + size_t size = 1 << val; + switch (size) + { + case sizeof(uint8_t): + case sizeof(uint16_t): + case sizeof(uint32_t): + case sizeof(uint64_t): + { + pv = (int64_t)getUIntVal(pcur, size); + } + break; + default: + { + assert(0); + return false; + } + break; + }; + + pcur += size; + } + break; + case BPLIST_REAL: + { + size_t size = 1 << val; + + uint8_t *buf = (uint8_t *)malloc(size); + memcpy(buf, pcur, size); + byteConvert(buf, size); + + switch (size) + { + case sizeof(float): + pv = (double)(*(float *)buf); + case sizeof(double): + pv = (*(double *)buf); + break; + default: + { + assert(0); + free(buf); + return false; + } + break; + } + + free(buf); + } + break; + + case BPLIST_DATE: + { + if (3 == val) + { + size_t size = 1 << val; + uint8_t *buf = (uint8_t *)malloc(size); + memcpy(buf, pcur, size); + byteConvert(buf, size); + pv.assignDate(((time_t)(*(double *)buf)) + 978278400); + free(buf); + } + else + { + assert(0); + return false; + } + } + break; + + case BPLIST_DATA: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + pv.assignData(pcur, size); + } + break; + + case BPLIST_STRING: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + string strval; + strval.append(pcur, size); + strval.append(1, 0); + pv = strval.c_str(); + } + break; + + case BPLIST_UNICODE: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + readUnicode(pcur, size, pv); + } + break; + case BPLIST_ARRAY: + case BPLIST_UNK_0x70: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + for (size_t i = 0; i < size; i++) + { + uint64_t uIndex = getUIntVal((const char *)pcur + i * m_uDictParamSize, m_uDictParamSize); + if (uIndex < m_uObjects) + { + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uIndex * m_uOffsetSize, m_uOffsetSize)); + readBinaryValue(pval, pv[i]); + } + else + { + assert(0); + return false; + } + } + } + break; + + case BPLIST_SET: + case BPLIST_DICT: + { + size_t size = val; + if (0x0F == val) + { + if (!readUIntSize(pcur, size)) + { + return false; + } + } + + for (size_t i = 0; i < size; i++) + { + JValue pvKey; + JValue pvVal; + + uint64_t uKeyIndex = getUIntVal((const char *)pcur + i * m_uDictParamSize, m_uDictParamSize); + uint64_t uValIndex = getUIntVal((const char *)pcur + (i + size) * m_uDictParamSize, m_uDictParamSize); + + if (uKeyIndex < m_uObjects) + { + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uKeyIndex * m_uOffsetSize, m_uOffsetSize)); + readBinaryValue(pval, pvKey); + } + + if (uValIndex < m_uObjects) + { + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable + uValIndex * m_uOffsetSize, m_uOffsetSize)); + readBinaryValue(pval, pvVal); + } + + if (pvKey.isString() && !pvVal.isNull()) + { + pv[pvKey.asCString()] = pvVal; + } + } + } + break; + default: + { + assert(0); + return false; + } + } + + return true; +} + +bool PReader::parseBinary(const char *pbdoc, size_t len, JValue &pv) +{ + m_pBeg = pbdoc; + + m_pTrailer = m_pBeg + len - 26; + + m_uOffsetSize = m_pTrailer[0]; + m_uDictParamSize = m_pTrailer[1]; + m_uObjects = getUIntVal(m_pTrailer + 2, 8); + + if (0 == m_uObjects) + { + return false; + } + + m_pOffsetTable = m_pBeg + getUIntVal(m_pTrailer + 18, 8); + const char *pval = (m_pBeg + getUIntVal(m_pOffsetTable, m_uOffsetSize)); + return readBinaryValue(pval, pv); +} + +void PReader::XMLUnescape(string &strval) +{ + PWriter::StringReplace(strval, "&", "&"); + PWriter::StringReplace(strval, "<", "<"); + //PWriter::StringReplace(strval,">", ">"); //optional + //PWriter::StringReplace(strval, "'", "'"); //optional + //PWriter::StringReplace(strval, """, "\""); //optional +} + +////////////////////////////////////////////////////////////////////////// +void PWriter::FastWrite(const JValue &pval, string &strdoc) +{ + strdoc.clear(); + strdoc = "\n" + "\n" + "\n"; + + string strindent; + FastWriteValue(pval, strdoc, strindent); + + strdoc += ""; +} + +void PWriter::FastWriteValue(const JValue &pval, string &strdoc, string &strindent) +{ + if (pval.isObject()) + { + strdoc += strindent; + if (pval.isEmpty()) + { + strdoc += "\n"; + return; + } + strdoc += "\n"; + vector arrKeys; + if (pval.keys(arrKeys)) + { + strindent.push_back('\t'); + for (size_t i = 0; i < arrKeys.size(); i++) + { + if (!pval[arrKeys[i].c_str()].isNull()) + { + string strkey = arrKeys[i]; + XMLEscape(strkey); + strdoc += strindent; + strdoc += ""; + strdoc += strkey; + strdoc += "\n"; + FastWriteValue(pval[arrKeys[i].c_str()], strdoc, strindent); + } + } + strindent.erase(strindent.end() - 1); + } + strdoc += strindent; + strdoc += "\n"; + } + else if (pval.isArray()) + { + strdoc += strindent; + if (pval.isEmpty()) + { + strdoc += "\n"; + return; + } + strdoc += "\n"; + strindent.push_back('\t'); + for (size_t i = 0; i < pval.size(); i++) + { + FastWriteValue(pval[i], strdoc, strindent); + } + strindent.erase(strindent.end() - 1); + strdoc += strindent; + strdoc += "\n"; + } + else if (pval.isDate()) + { + strdoc += strindent; + strdoc += ""; + strdoc += JWriter::d2s(pval.asDate()); + strdoc += "\n"; + } + else if (pval.isData()) + { + ZBase64 b64; + string strdata = pval.asData(); + strdoc += strindent; + strdoc += "\n"; + strdoc += strindent; + strdoc += b64.Encode(strdata.data(), (int)strdata.size()); + strdoc += "\n"; + strdoc += strindent; + strdoc += "\n"; + } + else if (pval.isString()) + { + strdoc += strindent; + if (pval.isDateString()) + { + strdoc += ""; + strdoc += pval.asString().c_str() + 5; + strdoc += "\n"; + } + else if (pval.isDataString()) + { + strdoc += "\n"; + strdoc += strindent; + strdoc += pval.asString().c_str() + 5; + strdoc += "\n"; + strdoc += strindent; + strdoc += "\n"; + } + else + { + string strval = pval.asCString(); + XMLEscape(strval); + strdoc += ""; + strdoc += strval; + strdoc += "\n"; + } + } + else if (pval.isBool()) + { + strdoc += strindent; + strdoc += (pval.asBool() ? "\n" : "\n"); + } + else if (pval.isInt()) + { + strdoc += strindent; + strdoc += ""; + char temp[32] = {0}; + sprintf(temp, "%" PRId64, pval.asInt64()); + strdoc += temp; + strdoc += "\n"; + } + else if (pval.isFloat()) + { + strdoc += strindent; + strdoc += ""; + + double v = pval.asFloat(); + if (numeric_limits::infinity() == v) + { + strdoc += "+infinity"; + } + else + { + char temp[32] = {0}; + if (floor(v) == v) + { + sprintf(temp, "%" PRId64, (int64_t)v); + } + else + { + sprintf(temp, "%.15lf", v); + } + strdoc += temp; + } + + strdoc += "\n"; + } +} + +void PWriter::XMLEscape(string &strval) +{ + StringReplace(strval, "&", "&"); + StringReplace(strval, "<", "<"); + //StringReplace(strval, ">", ">"); //option + //StringReplace(strval, "'", "'"); //option + //StringReplace(strval, "\"", """); //option +} + +string &PWriter::StringReplace(string &context, const string &from, const string &to) +{ + size_t lookHere = 0; + size_t foundHere; + while ((foundHere = context.find(from, lookHere)) != string::npos) + { + context.replace(foundHere, from.size(), to); + lookHere = foundHere + to.size(); + } + return context; +} diff --git a/ZSign/common/json.h b/ZSign/common/json.h new file mode 100644 index 0000000..9ef6dd1 --- /dev/null +++ b/ZSign/common/json.h @@ -0,0 +1,414 @@ +#ifndef JSON_INCLUDED +#define JSON_INCLUDED + +#ifdef _WIN32 + +typedef signed char int8_t; +typedef short int int16_t; +typedef int int32_t; +typedef long long int int64_t; +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long long int uint64_t; + +#else + +#include +#include +#include + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + + + +class JValue +{ +public: + enum TYPE + { + E_NULL = 0, + E_INT, + E_BOOL, + E_FLOAT, + E_ARRAY, + E_OBJECT, + E_STRING, + E_DATE, + E_DATA, + }; + +public: + JValue(TYPE type = E_NULL); + JValue(int val); + JValue(bool val); + JValue(double val); + JValue(int64_t val); + JValue(const char *val); + JValue(const string &val); + JValue(const JValue &other); + JValue(const char *val, size_t len); + ~JValue(); + +public: + int asInt() const; + bool asBool() const; + double asFloat() const; + int64_t asInt64() const; + string asString() const; + const char *asCString() const; + time_t asDate() const; + string asData() const; + + void assignData(const char *val, size_t size); + void assignDate(time_t val); + void assignDateString(time_t val); + + TYPE type() const; + size_t size() const; + void clear(); + + JValue &at(int index); + JValue &at(size_t index); + JValue &at(const char *key); + + bool has(const char *key) const; + int index(const char *ele) const; + bool keys(vector &arrKeys) const; + + bool join(JValue &jv); + bool append(JValue &jv); + + bool remove(int index); + bool remove(size_t index); + bool remove(const char *key); + + JValue &back(); + JValue &front(); + + bool push_back(int val); + bool push_back(bool val); + bool push_back(double val); + bool push_back(int64_t val); + bool push_back(const char *val); + bool push_back(const string &val); + bool push_back(const JValue &jval); + bool push_back(const char *val, size_t len); + + bool isInt() const; + bool isNull() const; + bool isBool() const; + bool isFloat() const; + bool isArray() const; + bool isObject() const; + bool isString() const; + bool isEmpty() const; + bool isData() const; + bool isDate() const; + bool isDataString() const; + bool isDateString() const; + + operator int() const; + operator bool() const; + operator double() const; + operator int64_t() const; + operator string() const; + operator const char *() const; + + JValue &operator=(const JValue &other); + + JValue &operator[](int index); + const JValue &operator[](int index) const; + + JValue &operator[](size_t index); + const JValue &operator[](size_t index) const; + + JValue &operator[](int64_t index); + const JValue &operator[](int64_t index) const; + + JValue &operator[](const char *key); + const JValue &operator[](const char *key) const; + + JValue &operator[](const string &key); + const JValue &operator[](const string &key) const; + + friend bool operator==(const JValue &jv, const char *psz) + { + return (0 == strcmp(jv.asCString(), psz)); + } + + friend bool operator==(const char *psz, const JValue &jv) + { + return (0 == strcmp(jv.asCString(), psz)); + } + + friend bool operator!=(const JValue &jv, const char *psz) + { + return (0 != strcmp(jv.asCString(), psz)); + } + + friend bool operator!=(const char *psz, const JValue &jv) + { + return (0 != strcmp(jv.asCString(), psz)); + } + +private: + void Free(); + char *NewString(const char *cstr); + void CopyValue(const JValue &src); + bool WriteDataToFile(const char *file, const char *data, size_t len); + +public: + static const JValue null; + static const string nullData; + +private: + union HOLD { + bool vBool; + double vFloat; + int64_t vInt64; + char *vString; + vector *vArray; + map *vObject; + time_t vDate; + string *vData; + wchar_t *vUnicode; + } m_Value; + + TYPE m_eType; + +public: + string write() const; + const char *write(string &strDoc) const; + + string styleWrite() const; + const char *styleWrite(string &strDoc) const; + + bool read(const char *pdoc, string *pstrerr = NULL); + bool read(const string &strdoc, string *pstrerr = NULL); + + string writePList() const; + const char *writePList(string &strDoc) const; + + bool readPList(const string &strdoc, string *pstrerr = NULL); + bool readPList(const char *pdoc, size_t len = 0, string *pstrerr = NULL); + + bool readFile(const char *file, string *pstrerr = NULL); + bool readPListFile(const char *file, string *pstrerr = NULL); + + bool writeFile(const char *file); + bool writePListFile(const char *file); + bool styleWriteFile(const char *file); + + bool readPath(const char *path, ...); + bool readPListPath(const char *path, ...); + bool writePath(const char *path, ...); + bool writePListPath(const char *path, ...); + bool styleWritePath(const char *path, ...); +}; + +class JReader +{ +public: + bool parse(const char *pdoc, JValue &root); + void error(string &strmsg) const; + +private: + struct Token + { + enum TYPE + { + E_Error = 0, + E_End, + E_Null, + E_True, + E_False, + E_Number, + E_String, + E_ArrayBegin, + E_ArrayEnd, + E_ObjectBegin, + E_ObjectEnd, + E_ArraySeparator, + E_MemberSeparator + }; + TYPE type; + const char *pbeg; + const char *pend; + }; + + void skipSpaces(); + void skipComment(); + + bool match(const char *pattern, int patternLength); + + bool readToken(Token &token); + bool readValue(JValue &jval); + bool readArray(JValue &jval); + void readNumber(); + + bool readString(); + bool readObject(JValue &jval); + + bool decodeNumber(Token &token, JValue &jval); + bool decodeString(Token &token, string &decoded); + bool decodeDouble(Token &token, JValue &jval); + + char GetNextChar(); + bool addError(const string &message, const char *ploc); + +private: + const char *m_pBeg; + const char *m_pEnd; + const char *m_pCur; + const char *m_pErr; + string m_strErr; +}; + +class JWriter +{ +public: + static void FastWrite(const JValue &jval, string &strDoc); + static void FastWriteValue(const JValue &jval, string &strDoc); + +public: + const string &StyleWrite(const JValue &jval); + +private: + void PushValue(const string &strval); + void StyleWriteValue(const JValue &jval); + void StyleWriteArrayValue(const JValue &jval); + bool isMultineArray(const JValue &jval); + +public: + static string v2s(double val); + static string v2s(int64_t val); + static string v2s(const char *val); + + static string vstring2s(const char *val); + static string d2s(time_t t); + +private: + string m_strDoc; + string m_strTab; + bool m_bAddChild; + vector m_childValues; +}; + +////////////////////////////////////////////////////////////////////////// +class PReader +{ +public: + PReader(); + +public: + bool parse(const char *pdoc, size_t len, JValue &root); + void error(string &strmsg) const; + +private: + struct Token + { + enum TYPE + { + E_Error = 0, + E_End, + E_Null, + E_True, + E_False, + E_Key, + E_Data, + E_Date, + E_Integer, + E_Real, + E_String, + E_ArrayBegin, + E_ArrayEnd, + E_ArrayNull, + E_DictionaryBegin, + E_DictionaryEnd, + E_DictionaryNull, + E_ArraySeparator, + E_MemberSeparator + }; + + Token() + { + pbeg = NULL; + pend = NULL; + type = E_Error; + } + + TYPE type; + const char *pbeg; + const char *pend; + }; + + bool readToken(Token &token); + bool readLabel(string &label); + bool readValue(JValue &jval, Token &token); + bool readArray(JValue &jval); + bool readNumber(); + + bool readString(); + bool readDictionary(JValue &jval); + + void endLabel(Token &token, const char *szLabel); + + bool decodeNumber(Token &token, JValue &jval); + bool decodeString(Token &token, string &decoded, bool filter = true); + bool decodeDouble(Token &token, JValue &jval); + + void skipSpaces(); + bool addError(const string &message, const char *ploc); + +public: + bool parseBinary(const char *pbdoc, size_t len, JValue &pv); + +private: + uint32_t getUInt24FromBE(const char *v); + void byteConvert(uint8_t *v, size_t size); + uint64_t getUIntVal(const char *v, size_t size); + bool readUIntSize(const char *&pcur, size_t &size); + bool readBinaryValue(const char *&pcur, JValue &pv); + bool readUnicode(const char *pcur, size_t size, JValue &pv); + +public: + static void XMLUnescape(string &strval); + +private: //xml + const char *m_pBeg; + const char *m_pEnd; + const char *m_pCur; + const char *m_pErr; + string m_strErr; + +private: //binary + const char *m_pTrailer; + uint64_t m_uObjects; + uint8_t m_uOffsetSize; + const char *m_pOffsetTable; + uint8_t m_uDictParamSize; +}; + +class PWriter +{ +public: + static void FastWrite(const JValue &pval, string &strdoc); + static void FastWriteValue(const JValue &pval, string &strdoc, string &strindent); + +public: + static void XMLEscape(string &strval); + static string &StringReplace(string &context, const string &from, const string &to); +}; + +#endif // JSON_INCLUDED diff --git a/ZSign/common/mach-o.h b/ZSign/common/mach-o.h new file mode 100644 index 0000000..7c5deb9 --- /dev/null +++ b/ZSign/common/mach-o.h @@ -0,0 +1,580 @@ +#pragma once +#include +// typedef int cpu_type_t; +// typedef int cpu_subtype_t; +// typedef int vm_prot_t; + +// /* +// * Capability bits used in the definition of cpu_type. +// */ +// #define CPU_ARCH_MASK 0xff000000 /* mask for architecture bits */ +// #define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */ +// #define CPU_ARCH_ABI64_32 0x02000000 + +// /* +// * Machine types known by all. +// */ +// #define CPU_TYPE_ANY -1 +// #define CPU_TYPE_VAX 1 +// #define CPU_TYPE_MC680x0 6 +// #define CPU_TYPE_X86 7 +// #define CPU_TYPE_I386 CPU_TYPE_X86 /* compatibility */ +// #define CPU_TYPE_MIPS 8 +// #define CPU_TYPE_MC98000 10 +// #define CPU_TYPE_HPPA 11 +// #define CPU_TYPE_ARM 12 +// #define CPU_TYPE_MC88000 13 +// #define CPU_TYPE_SPARC 14 +// #define CPU_TYPE_I860 15 +// #define CPU_TYPE_ALPHA 16 +// #define CPU_TYPE_POWERPC 18 +// #define CPU_TYPE_X86_64 (CPU_TYPE_X86 | CPU_ARCH_ABI64) +// #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) +// #define CPU_TYPE_ARM64_32 (CPU_TYPE_ARM | CPU_ARCH_ABI64_32) +// #define CPU_TYPE_POWERPC64 (CPU_TYPE_POWERPC | CPU_ARCH_ABI64) + +// /* +// * Machine subtypes (these are defined here, instead of in a machine +// * dependent directory, so that any program can get all definitions +// * regardless of where is it compiled). +// */ + +// /* +// * Capability bits used in the definition of cpu_subtype. +// */ +// #define CPU_SUBTYPE_MASK 0xff000000 /* mask for feature flags */ +// #define CPU_SUBTYPE_LIB64 0x80000000 /* 64 bit libraries */ + +// /* +// * Object files that are hand-crafted to run on any +// * implementation of an architecture are tagged with +// * CPU_SUBTYPE_MULTIPLE. This functions essentially the same as +// * the "ALL" subtype of an architecture except that it allows us +// * to easily find object files that may need to be modified +// * whenever a new implementation of an architecture comes out. +// * +// * It is the responsibility of the implementor to make sure the +// * software handles unsupported implementations elegantly. +// */ +// #define CPU_SUBTYPE_MULTIPLE -1 +// #define CPU_SUBTYPE_LITTLE_ENDIAN 0 +// #define CPU_SUBTYPE_BIG_ENDIAN 1 + +// /* +// * I386 subtypes +// */ + +// #define CPU_SUBTYPE_INTEL(f, m) ((f) + ((m) << 4)) + +// #define CPU_SUBTYPE_I386_ALL CPU_SUBTYPE_INTEL(3, 0) +// #define CPU_SUBTYPE_386 CPU_SUBTYPE_INTEL(3, 0) +// #define CPU_SUBTYPE_486 CPU_SUBTYPE_INTEL(4, 0) +// #define CPU_SUBTYPE_486SX CPU_SUBTYPE_INTEL(4, 8) // 8 << 4 = 128 +// #define CPU_SUBTYPE_586 CPU_SUBTYPE_INTEL(5, 0) +// #define CPU_SUBTYPE_PENT CPU_SUBTYPE_INTEL(5, 0) +// #define CPU_SUBTYPE_PENTPRO CPU_SUBTYPE_INTEL(6, 1) +// #define CPU_SUBTYPE_PENTII_M3 CPU_SUBTYPE_INTEL(6, 3) +// #define CPU_SUBTYPE_PENTII_M5 CPU_SUBTYPE_INTEL(6, 5) +// #define CPU_SUBTYPE_CELERON CPU_SUBTYPE_INTEL(7, 6) +// #define CPU_SUBTYPE_CELERON_MOBILE CPU_SUBTYPE_INTEL(7, 7) +// #define CPU_SUBTYPE_PENTIUM_3 CPU_SUBTYPE_INTEL(8, 0) +// #define CPU_SUBTYPE_PENTIUM_3_M CPU_SUBTYPE_INTEL(8, 1) +// #define CPU_SUBTYPE_PENTIUM_3_XEON CPU_SUBTYPE_INTEL(8, 2) +// #define CPU_SUBTYPE_PENTIUM_M CPU_SUBTYPE_INTEL(9, 0) +// #define CPU_SUBTYPE_PENTIUM_4 CPU_SUBTYPE_INTEL(10, 0) +// #define CPU_SUBTYPE_PENTIUM_4_M CPU_SUBTYPE_INTEL(10, 1) +// #define CPU_SUBTYPE_ITANIUM CPU_SUBTYPE_INTEL(11, 0) +// #define CPU_SUBTYPE_ITANIUM_2 CPU_SUBTYPE_INTEL(11, 1) +// #define CPU_SUBTYPE_XEON CPU_SUBTYPE_INTEL(12, 0) +// #define CPU_SUBTYPE_XEON_MP CPU_SUBTYPE_INTEL(12, 1) + +// #define CPU_SUBTYPE_INTEL_FAMILY(x) ((x) & 15) +// #define CPU_SUBTYPE_INTEL_FAMILY_MAX 15 + +// #define CPU_SUBTYPE_INTEL_MODEL(x) ((x) >> 4) +// #define CPU_SUBTYPE_INTEL_MODEL_ALL 0 + +// /* +// * X86 subtypes. +// */ + +// #define CPU_SUBTYPE_X86_ALL 3 +// #define CPU_SUBTYPE_X86_64_ALL 3 +// #define CPU_SUBTYPE_X86_ARCH1 4 +// #define CPU_SUBTYPE_X86_64_H 8 + +// #define CPU_SUBTYPE_ARM_ALL 0 +// #define CPU_SUBTYPE_ARM_A500_ARCH 1 +// #define CPU_SUBTYPE_ARM_A500 2 +// #define CPU_SUBTYPE_ARM_A440 3 +// #define CPU_SUBTYPE_ARM_M4 4 +// #define CPU_SUBTYPE_ARM_V4T 5 +// #define CPU_SUBTYPE_ARM_V6 6 +// #define CPU_SUBTYPE_ARM_V5 7 +// #define CPU_SUBTYPE_ARM_V5TEJ 7 +// #define CPU_SUBTYPE_ARM_XSCALE 8 +// #define CPU_SUBTYPE_ARM_V7 9 +// #define CPU_SUBTYPE_ARM_V7S 11 +// #define CPU_SUBTYPE_ARM_V7K 12 +// #define CPU_SUBTYPE_ARM_V8 13 +// #define CPU_SUBTYPE_ARM_V6M 14 +// #define CPU_SUBTYPE_ARM_V7M 15 +// #define CPU_SUBTYPE_ARM_V7EM 16 + +// #define CPU_SUBTYPE_ARM64_ALL 0 +// #define CPU_SUBTYPE_ARM64_V8 1 +// #define CPU_SUBTYPE_ARM64E 2 +// #define CPU_SUBTYPE_ARM64_32_V8 1 + + +#define FAT_MAGIC 0xcafebabe +#define FAT_CIGAM 0xbebafeca + +#define MH_MAGIC 0xfeedface +#define MH_CIGAM 0xcefaedfe +#define MH_MAGIC_64 0xfeedfacf +#define MH_CIGAM_64 0xcffaedfe + + +/* Constants for the cmd field of new load commands, the type */ + +#define MH_OBJECT 0x1 /* relocatable object file */ +#define MH_EXECUTE 0x2 /* demand paged executable file */ +#define MH_FVMLIB 0x3 /* fixed VM shared library file */ +#define MH_CORE 0x4 /* core file */ +#define MH_PRELOAD 0x5 /* preloaded executable file */ +#define MH_DYLIB 0x6 /* dynamicly bound shared library file*/ +#define MH_DYLINKER 0x7 /* dynamic link editor */ +#define MH_BUNDLE 0x8 /* dynamicly bound bundle file */ +#define MH_DYLIB_STUB 0x9 // Dynamic shared library stub +#define MH_DSYM 0xa // Companion debug sections file +#define MH_KEXT_BUNDLE 0xb // Kernel extension + +/* Constants for the flags field of the mach_header */ +#define MH_NOUNDEFS 0x00000001 /* the object file has no undefined references, can be executed */ +#define MH_INCRLINK 0x00000002 /* the object file is the output of an incremental link against a base file and can't be link edited again */ +#define MH_DYLDLINK 0x00000004 /* the object file is input for the dynamic linker and can't be staticly link edited again */ +#define MH_BINDATLOAD 0x00000008 /* the object file's undefined references are bound by the dynamic linker when loaded. */ +#define MH_PREBOUND 0x00000010 /* the file has it's dynamic undefined references prebound. */ +#define MH_SPLIT_SEGS 0x00000020 +#define MH_LAZY_INIT 0x00000040 +#define MH_TWOLEVEL 0x00000080 +#define MH_FORCE_FLAT 0x00000100 +#define MH_NOMULTIDEFS 0x00000200 +#define MH_NOFIXPREBINDING 0x00000400 +#define MH_PREBINDABLE 0x00000800 +#define MH_ALLMODSBOUND 0x00001000 +#define MH_SUBSECTIONS_VIA_SYMBOLS 0x00002000 +#define MH_CANONICAL 0x00004000 +#define MH_WEAK_DEFINES 0x00008000 +#define MH_BINDS_TO_WEAK 0x00010000 +#define MH_ALLOW_STACK_EXECUTION 0x00020000 +#define MH_ROOT_SAFE 0x00040000 +#define MH_SETUID_SAFE 0x00080000 +#define MH_NO_REEXPORTED_DYLIBS 0x00100000 +#define MH_PIE 0x00200000 +#define MH_DEAD_STRIPPABLE_DYLIB 0x00400000 +#define MH_HAS_TLV_DESCRIPTORS 0x00800000 +#define MH_NO_HEAP_EXECUTION 0x01000000 +#define MH_APP_EXTENSION_SAFE 0x02000000 + + +/* Constants for the cmd field of all load commands, the type */ +#define LC_SEGMENT 0x00000001 /* segment of this file to be mapped */ +#define LC_SYMTAB 0x00000002 /* link-edit stab symbol table info */ +#define LC_SYMSEG 0x00000003 /* link-edit gdb symbol table info (obsolete) */ +#define LC_THREAD 0x00000004 /* thread */ +#define LC_UNIXTHREAD 0x00000005 /* unix thread (includes a stack) */ +#define LC_LOADFVMLIB 0x00000006 /* load a specified fixed VM shared library */ +#define LC_IDFVMLIB 0x00000007 /* fixed VM shared library identification */ +#define LC_IDENT 0x00000008 /* object identification info (obsolete) */ +#define LC_FVMFILE 0x00000009 /* fixed VM file inclusion (internal use) */ +#define LC_PREPAGE 0x0000000a /* prepage command (internal use) */ +#define LC_DYSYMTAB 0x0000000b /* dynamic link-edit symbol table info */ +#define LC_LOAD_DYLIB 0x0000000c /* load a dynamicly linked shared library */ +#define LC_ID_DYLIB 0x0000000d /* dynamicly linked shared lib identification */ +#define LC_LOAD_DYLINKER 0x0000000e /* load a dynamic linker */ +#define LC_ID_DYLINKER 0x0000000f /* dynamic linker identification */ +#define LC_PREBOUND_DYLIB 0x00000010 /* modules prebound for a dynamicly */ +#define LC_ROUTINES 0x00000011 +#define LC_SUB_FRAMEWORK 0x00000012 +#define LC_SUB_UMBRELLA 0x00000013 +#define LC_SUB_CLIENT 0x00000014 +#define LC_SUB_LIBRARY 0x00000015 +#define LC_TWOLEVEL_HINTS 0x00000016 +#define LC_PREBIND_CKSUM 0x00000017 +#define LC_LOAD_WEAK_DYLIB 0x80000018 +#define LC_SEGMENT_64 0x00000019 +#define LC_ROUTINES_64 0x0000001A +#define LC_UUID 0x0000001B +#define LC_RPATH 0x8000001C +#define LC_CODE_SIGNATURE 0x0000001D +#define LC_SEGMENT_SPLIT_INFO 0x0000001E +#define LC_REEXPORT_DYLIB 0x8000001F +#define LC_LAZY_LOAD_DYLIB 0x00000020 +#define LC_ENCRYPTION_INFO 0x00000021 +#define LC_DYLD_INFO 0x00000022 +#define LC_DYLD_INFO_ONLY 0x80000022 +#define LC_LOAD_UPWARD_DYLIB 0x80000023 +#define LC_VERSION_MIN_MACOSX 0x00000024 +#define LC_VERSION_MIN_IPHONEOS 0x00000025 +#define LC_FUNCTION_STARTS 0x00000026 +#define LC_DYLD_ENVIRONMENT 0x00000027 +#define LC_MAIN 0x80000028 +#define LC_DATA_IN_CODE 0x00000029 +#define LC_SOURCE_VERSION 0x0000002A +#define LC_DYLIB_CODE_SIGN_DRS 0x0000002B +#define LC_ENCRYPTION_INFO_64 0x0000002C +#define LC_LINKER_OPTION 0x0000002D +#define LC_LINKER_OPTIMIZATION_HINT 0x0000002E +#define LC_VERSION_MIN_TVOS 0x0000002F +#define LC_VERSION_MIN_WATCHOS 0x00000030 + +// /* Constants for the flags field of the segment_command */ +// #define SG_HIGHVM 0x00000001 /* the file contents for this segment is for +// the high part of the VM space, the low part +// is zero filled (for stacks in core files) */ +// #define SG_FVMLIB 0x00000002 /* this segment is the VM that is allocated by +// a fixed VM library, for overlap checking in +// the link editor */ +// #define SG_NORELOC 0x00000004 /* this segment has nothing that was relocated +// in it and nothing relocated to it, that is +// it maybe safely replaced without relocation*/ +// #define SG_PROTECTED_VERSION_1 0x00000008 // Segment is encryption protected + + +// // Section flag masks +// #define SECTION_TYPE 0x000000ff // Section type mask +// #define SECTION_ATTRIBUTES 0xffffff00 // Section attributes mask + +// // Section type (use SECTION_TYPE mask) + +// #define S_REGULAR 0x00 +// #define S_ZEROFILL 0x01 +// #define S_CSTRING_LITERALS 0x02 +// #define S_4BYTE_LITERALS 0x03 +// #define S_8BYTE_LITERALS 0x04 +// #define S_LITERAL_POINTERS 0x05 +// #define S_NON_LAZY_SYMBOL_POINTERS 0x06 +// #define S_LAZY_SYMBOL_POINTERS 0x07 +// #define S_SYMBOL_STUBS 0x08 +// #define S_MOD_INIT_FUNC_POINTERS 0x09 +// #define S_MOD_TERM_FUNC_POINTERS 0x0a +// #define S_COALESCED 0x0b +// #define S_GB_ZEROFILL 0x0c +// #define S_INTERPOSING 0x0d +// #define S_16BYTE_LITERALS 0x0e +// #define S_DTRACE_DOF 0x0f +// #define S_LAZY_DYLIB_SYMBOL_POINTERS 0x10 +// #define S_THREAD_LOCAL_REGULAR 0x11 +// #define S_THREAD_LOCAL_ZEROFILL 0x12 +// #define S_THREAD_LOCAL_VARIABLES 0x13 +// #define S_THREAD_LOCAL_VARIABLE_POINTERS 0x14 +// #define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS 0x15 + +// // Section attributes (use SECTION_ATTRIBUTES mask) + +// #define S_ATTR_PURE_INSTRUCTIONS 0x80000000 // Only pure instructions +// #define S_ATTR_NO_TOC 0x40000000 // Contains coalesced symbols +// #define S_ATTR_STRIP_STATIC_SYMS 0x20000000 // Can strip static symbols +// #define S_ATTR_NO_DEAD_STRIP 0x10000000 // No dead stripping +// #define S_ATTR_LIVE_SUPPORT 0x08000000 // Live blocks support +// #define S_ATTR_SELF_MODIFYING_CODE 0x04000000 // Self modifying code +// #define S_ATTR_DEBUG 0x02000000 // Debug section +// #define S_ATTR_SOME_INSTRUCTIONS 0x00000400 // Some machine instructions +// #define S_ATTR_EXT_RELOC 0x00000200 // Has external relocations +// #define S_ATTR_LOC_RELOC 0x00000100 // Has local relocations + + +//struct define + +#pragma pack(push, 1) + +struct fat_header +{ + uint32_t magic; /* FAT_MAGIC */ + uint32_t nfat_arch; /* number of structs that follow */ +}; + +struct fat_arch +{ + cpu_type_t cputype; /* cpu specifier (int) */ + cpu_subtype_t cpusubtype; /* machine specifier (int) */ + uint32_t offset; /* file offset to this object file */ + uint32_t size; /* size of this object file */ + uint32_t align; /* alignment as a power of 2 */ +}; + +struct mach_header +{ + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; + +struct mach_header_64 +{ + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; + +struct load_command +{ + uint32_t cmd; /* type of load command */ + uint32_t cmdsize; /* total size of command in bytes */ +}; + +struct uuid_command { + uint32_t cmd; + uint32_t cmdsize; + uint8_t uuid[16]; +}; + +struct entry_point_command { + uint32_t cmd; + uint32_t cmdsize; + uint64_t entryoff; + uint64_t stacksize; +}; + +struct codesignature_command { + uint32_t cmd; + uint32_t cmdsize; + uint32_t dataoff; + uint32_t datasize; +}; + +struct encryption_info_command { + uint32_t cmd; + uint32_t cmdsize; + uint32_t cryptoff; + uint32_t cryptsize; + uint32_t cryptid; +}; + +struct encryption_info_command_64 +{ + uint32_t cmd; + uint32_t cmdsize; + uint32_t cryptoff; + uint32_t cryptsize; + uint32_t cryptid; + uint32_t pad; +}; + +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +struct section { /* for 32-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint32_t addr; /* memory address of this section */ + uint32_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved */ + uint32_t reserved2; /* reserved */ +}; + +struct section_64 { /* for 64-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint64_t addr; /* memory address of this section */ + uint64_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved (for offset or index) */ + uint32_t reserved2; /* reserved (for count or sizeof) */ + uint32_t reserved3; /* reserved */ +}; + +union lc_str { + uint32_t offset; /* offset to the string */ +}; + +struct dylib { + union lc_str name; /* library's path name */ + uint32_t timestamp; /* library's build time stamp */ + uint32_t current_version; /* library's current version number */ + uint32_t compatibility_version; /* library's compatibility vers number*/ +}; + +struct dylib_command { + uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB,LC_REEXPORT_DYLIB */ + uint32_t cmdsize; /* includes pathname string */ + struct dylib dylib; /* the library identification */ +}; + +#pragma pack(pop) + +//////CodeSignature + +enum { + CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ + CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ + CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ + CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ + CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ + CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, /* der format entitlements */ + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ + CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ + CS_SUPPORTSSCATTER = 0x20100, + CS_SUPPORTSTEAMID = 0x20200, + CS_SUPPORTSCODELIMIT64 = 0x20300, + CS_SUPPORTSEXECSEG = 0x20400, + CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ + CSSLOT_INFOSLOT = 1, + CSSLOT_REQUIREMENTS = 2, + CSSLOT_RESOURCEDIR = 3, + CSSLOT_APPLICATION = 4, + CSSLOT_ENTITLEMENTS = 5, + CSSLOT_DER_ENTITLEMENTS = 7, /* der format entitlement type */ + CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ + CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ + CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ + CSSLOT_IDENTIFICATIONSLOT = 0x10001, + CSSLOT_TICKETSLOT = 0x10002, + CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ + CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ + CS_HASHTYPE_SHA1 = 1, + CS_HASHTYPE_SHA256 = 2, + CS_HASHTYPE_SHA256_TRUNCATED = 3, + CS_HASHTYPE_SHA384 = 4, + CS_SHA1_LEN = 20, + CS_SHA256_LEN = 32, + CS_SHA256_TRUNCATED_LEN = 20, + CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ + CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ + CS_EXECSEG_MAIN_BINARY = 0x1, + CS_EXECSEG_ALLOW_UNSIGNED = 0x10, + +/* + * Currently only to support Legacy VPN plugins, + * but intended to replace all the various platform code, dev code etc. bits. + */ + CS_SIGNER_TYPE_UNKNOWN = 0, + CS_SIGNER_TYPE_LEGACYVPN = 5, + +}; + +#pragma pack(push, 1) + +/* + * Structure of an embedded-signature SuperBlob + */ +struct CS_BlobIndex { + uint32_t type; /* type of entry */ + uint32_t offset; /* offset of entry */ +}; + +struct CS_SuperBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of SuperBlob */ + uint32_t count; /* number of index entries following */ + //CS_BlobIndex index[]; /* (count) entries */ + /* followed by Blobs in no particular order as indicated by offsets in index */ +}; + +/* + * C form of a CodeDirectory. + */ +struct CS_CodeDirectory { + uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ + uint32_t length; /* total length of CodeDirectory blob */ + uint32_t version; /* compatibility version */ + uint32_t flags; /* setup and mode flags */ + uint32_t hashOffset; /* offset of hash slot element at index zero */ + uint32_t identOffset; /* offset of identifier string */ + uint32_t nSpecialSlots; /* number of special hash slots */ + uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ + uint32_t codeLimit; /* limit to main image signature range */ + uint8_t hashSize; /* size of each hash in bytes */ + uint8_t hashType; /* type of hash (cdHashType* constants) */ + uint8_t spare1; /* unused (must be zero) */ + uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ + uint32_t spare2; /* unused (must be zero) */ + //char end_earliest[0]; + + /* Version 0x20100 */ + uint32_t scatterOffset; /* offset of optional scatter vector */ + //char end_withScatter[0]; + + /* Version 0x20200 */ + uint32_t teamOffset; /* offset of optional team identifier */ + //char end_withTeam[0]; + + /* Version 0x20300 */ + uint32_t spare3; /* unused (must be zero) */ + uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ + //char end_withCodeLimit64[0]; + + /* Version 0x20400 */ + uint64_t execSegBase; /* offset of executable segment */ + uint64_t execSegLimit; /* limit of executable segment */ + uint64_t execSegFlags; /* executable segment flags */ + //char end_withExecSeg[0]; + + /* followed by dynamic content as located by offset fields above */ +}; + +struct CS_Entitlement { + uint32_t magic; + uint32_t length; +}; + +struct CS_GenericBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of blob */ +}; + +struct CS_Scatter { + uint32_t count; // number of pages; zero for sentinel (only) + uint32_t base; // first page number + uint64_t targetOffset; // offset in target + uint64_t spare; // reserved +}; + +#pragma pack(pop) diff --git a/ZSign/macho.cpp b/ZSign/macho.cpp new file mode 100644 index 0000000..745b132 --- /dev/null +++ b/ZSign/macho.cpp @@ -0,0 +1,350 @@ +#include "common/common.h" +#include "common/json.h" +#include "common/mach-o.h" +#include "openssl.h" +#include "signing.h" +#include "macho.h" + +ZMachO::ZMachO() +{ + m_pBase = NULL; + m_sSize = 0; + m_bCSRealloced = false; +} + +ZMachO::~ZMachO() +{ + FreeArchOes(); +} + +bool ZMachO::Init(const char *szFile) +{ + m_strFile = szFile; + return OpenFile(szFile); +} + +bool ZMachO::InitV(const char *szFormatPath, ...) +{ + char szFile[PATH_MAX] = {0}; + va_list args; + va_start(args, szFormatPath); + vsnprintf(szFile, PATH_MAX, szFormatPath, args); + va_end(args); + + return Init(szFile); +} + +bool ZMachO::Free() +{ + FreeArchOes(); + return CloseFile(); +} + +bool ZMachO::NewArchO(uint8_t *pBase, uint32_t uLength) +{ + ZArchO *archo = new ZArchO(); + if (archo->Init(pBase, uLength)) + { + m_arrArchOes.push_back(archo); + return true; + } + delete archo; + return false; +} + +void ZMachO::FreeArchOes() +{ + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + ZArchO *archo = m_arrArchOes[i]; + delete archo; + } + m_pBase = NULL; + m_sSize = 0; + m_arrArchOes.clear(); +} + +bool ZMachO::OpenFile(const char *szPath) +{ + FreeArchOes(); + + m_sSize = 0; + m_pBase = (uint8_t *)MapFile(szPath, 0, 0, &m_sSize, false); + if (NULL != m_pBase) + { + uint32_t magic = *((uint32_t *)m_pBase); + if (FAT_CIGAM == magic || FAT_MAGIC == magic) + { + fat_header *pFatHeader = (fat_header *)m_pBase; + int nFatArch = (FAT_MAGIC == magic) ? pFatHeader->nfat_arch : LE(pFatHeader->nfat_arch); + for (int i = 0; i < nFatArch; i++) + { + fat_arch *pFatArch = (fat_arch *)(m_pBase + sizeof(fat_header) + sizeof(fat_arch) * i); + uint8_t *pArchBase = m_pBase + ((FAT_MAGIC == magic) ? pFatArch->offset : LE(pFatArch->offset)); + uint32_t uArchLength = (FAT_MAGIC == magic) ? pFatArch->size : LE(pFatArch->size); + if (!NewArchO(pArchBase, uArchLength)) + { + ZLog::ErrorV(">>> Invalid Arch File In Fat Macho File!\n"); + return false; + } + } + } + else if (MH_MAGIC == magic || MH_CIGAM == magic || MH_MAGIC_64 == magic || MH_CIGAM_64 == magic) + { + if (!NewArchO(m_pBase, (uint32_t)m_sSize)) + { + ZLog::ErrorV(">>> Invalid Macho File!\n"); + return false; + } + } + else + { + ZLog::ErrorV(">>> Invalid Macho File (2)!\n"); + return false; + } + } + + return (!m_arrArchOes.empty()); +} + +bool ZMachO::CloseFile() +{ + if (NULL == m_pBase || m_sSize <= 0) + { + return false; + } + + if ((munmap((void *)m_pBase, m_sSize)) < 0) + { + ZLog::ErrorV(">>> CodeSign Write(munmap) Failed! Error: %p, %lu, %s\n", m_pBase, m_sSize, strerror(errno)); + return false; + } + return true; +} + +void ZMachO::PrintInfo() +{ + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + ZArchO *archo = m_arrArchOes[i]; + archo->PrintInfo(); + } +} + +bool ZMachO::Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData) +{ + if (NULL == m_pBase || m_arrArchOes.empty()) + { + return false; + } + + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + ZArchO *archo = m_arrArchOes[i]; + if (strBundleId.empty()) + { + JValue jvInfo; + jvInfo.readPList(archo->m_strInfoPlist); + strBundleId = jvInfo["CFBundleIdentifier"].asCString(); + if (strBundleId.empty()) + { + strBundleId = basename((char *)m_strFile.c_str()); + } + } + + if (strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) + { + if (archo->m_strInfoPlist.empty()) + { + strInfoPlistSHA1.append(20, 0); + strInfoPlistSHA256.append(32, 0); + } + else + { + SHASum(archo->m_strInfoPlist, strInfoPlistSHA1, strInfoPlistSHA256); + } + } + + if (!archo->Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData)) + { + if (!archo->m_bEnoughSpace && !m_bCSRealloced) + { + m_bCSRealloced = true; + if (ReallocCodeSignSpace()) + { + return Sign(pSignAsset, bForce, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResourcesData); + } + } + return false; + } + } + + return CloseFile(); +} + +bool ZMachO::ReallocCodeSignSpace() +{ + ZLog::Warn(">>> Realloc CodeSignature Space... \n"); + + vector arrMachOesSizes; + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + string strNewArchOFile; + StringFormat(strNewArchOFile, "%s.archo.%d", m_strFile.c_str(), i); + uint32_t uNewLength = m_arrArchOes[i]->ReallocCodeSignSpace(strNewArchOFile); + if (uNewLength <= 0) + { + ZLog::Error(">>> Failed!\n"); + return false; + } + arrMachOesSizes.push_back(uNewLength); + } + ZLog::Warn(">>> Success!\n"); + + if (1 == m_arrArchOes.size()) + { + CloseFile(); + RemoveFile(m_strFile.c_str()); + string strNewArchOFile = m_strFile + ".archo.0"; + if (0 == rename(strNewArchOFile.c_str(), m_strFile.c_str())) + { + return OpenFile(m_strFile.c_str()); + } + } + else + { //fat + uint32_t uAlign = 16384; + vector arrArches; + fat_header fath = *((fat_header *)m_pBase); + int nFatArch = (FAT_MAGIC == fath.magic) ? fath.nfat_arch : LE(fath.nfat_arch); + for (int i = 0; i < nFatArch; i++) + { + fat_arch arch = *((fat_arch *)(m_pBase + sizeof(fat_header) + sizeof(fat_arch) * i)); + arrArches.push_back(arch); + } + CloseFile(); + + if (arrArches.size() != m_arrArchOes.size()) + { + return false; + } + + uint32_t uFatHeaderSize = sizeof(fat_header) + arrArches.size() * sizeof(fat_arch); + uint32_t uPadding1 = (uAlign - uFatHeaderSize % uAlign); + uint32_t uOffset = uFatHeaderSize + uPadding1; + for (size_t i = 0; i < arrArches.size(); i++) + { + fat_arch &arch = arrArches[i]; + uint32_t &uMachOSize = arrMachOesSizes[i]; + + arch.align = (FAT_MAGIC == fath.magic) ? 14 : BE((uint32_t)14); + arch.offset = (FAT_MAGIC == fath.magic) ? uOffset : BE(uOffset); + arch.size = (FAT_MAGIC == fath.magic) ? uMachOSize : BE(uMachOSize); + + uOffset += uMachOSize; + uOffset = uOffset + (uAlign - uOffset % uAlign); + } + + string strNewFatMachOFile = m_strFile + ".fato"; + + string strFatHeader; + strFatHeader.append((const char *)&fath, sizeof(fat_header)); + for (size_t i = 0; i < arrArches.size(); i++) + { + fat_arch &arch = arrArches[i]; + strFatHeader.append((const char *)&arch, sizeof(fat_arch)); + } + + string strPadding1; + strPadding1.append(uPadding1, 0); + + AppendFile(strNewFatMachOFile.c_str(), strFatHeader); + AppendFile(strNewFatMachOFile.c_str(), strPadding1); + + for (size_t i = 0; i < arrArches.size(); i++) + { + size_t sSize = 0; + string strNewArchOFile = m_strFile + ".archo." + JValue((int)i).asString(); + uint8_t *pData = (uint8_t *)MapFile(strNewArchOFile.c_str(), 0, 0, &sSize, true); + if (NULL == pData) + { + RemoveFile(strNewFatMachOFile.c_str()); + return false; + } + string strPadding; + strPadding.append((uAlign - sSize % uAlign), 0); + + AppendFile(strNewFatMachOFile.c_str(), (const char *)pData, sSize); + AppendFile(strNewFatMachOFile.c_str(), strPadding); + + munmap((void *)pData, sSize); + RemoveFile(strNewArchOFile.c_str()); + } + + RemoveFile(m_strFile.c_str()); + if (0 == rename(strNewFatMachOFile.c_str(), m_strFile.c_str())) + { + return OpenFile(m_strFile.c_str()); + } + } + + return false; +} + +bool ZMachO::InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate) +{ + ZLog::WarnV(">>> Inject DyLib: %s ... \n", szDyLibPath); + + vector arrMachOesSizes; + for (size_t i = 0; i < m_arrArchOes.size(); i++) + { + if (!m_arrArchOes[i]->InjectDyLib(bWeakInject, szDyLibPath, bCreate)) + { + ZLog::Error(">>> Failed!\n"); + return false; + } + } + ZLog::Warn(">>> Success!\n"); + return true; +} + +bool ZMachO::ChangeDylibPath(const char *oldPath, const char *newPath) { + ZLog::WarnV(">>> Change DyLib Path: %s -> %s ... \n", oldPath, newPath); + + bool pathChanged = true; + for (size_t i = 0; i < m_arrArchOes.size(); i++) { + if (!m_arrArchOes[i]->ChangeDylibPath(oldPath, newPath)) { + ZLog::Error(">>> Failed to change path in one of the architectures!\n"); + pathChanged = false; + } + } + + if (pathChanged) { + ZLog::Warn(">>> Successfully changed all dylib paths!\n"); + } + return pathChanged; +} + +std::vector ZMachO::ListDylibs() { + std::vector dylibList; + + for (size_t i = 0; i < m_arrArchOes.size(); i++) { + std::vector archDylibs = m_arrArchOes[i]->ListDylibs(); + dylibList.insert(dylibList.end(), archDylibs.begin(), archDylibs.end()); + } + + ZLog::WarnV(">>> Found %zu dylibs:\n", dylibList.size()); + + return dylibList; +} +bool ZMachO::RemoveDylib(const std::set &dylibNames) { + ZLog::Warn(">>> Removing specified dylibs...\n"); + + bool removalSuccessful = true; + for (size_t i = 0; i < m_arrArchOes.size(); i++) { + m_arrArchOes[i]->uninstallDylibs(dylibNames); + } + + ZLog::Warn(">>> Finished removing specified dylibs!\n"); + return removalSuccessful; +} diff --git a/ZSign/macho.h b/ZSign/macho.h new file mode 100644 index 0000000..e8bf3da --- /dev/null +++ b/ZSign/macho.h @@ -0,0 +1,34 @@ +#pragma once +#include "archo.h" + +class ZMachO +{ +public: + ZMachO(); + ~ZMachO(); + +public: + bool Init(const char *szFile); + bool InitV(const char *szFormatPath, ...); + bool Free(); + void PrintInfo(); + bool Sign(ZSignAsset *pSignAsset, bool bForce, string strBundleId, string strInfoPlistSHA1, string strInfoPlistSHA256, const string &strCodeResourcesData); + bool InjectDyLib(bool bWeakInject, const char *szDyLibPath, bool &bCreate); + bool ChangeDylibPath(const char *oldPath, const char *newPath); + std::vector ListDylibs(); + bool RemoveDylib(const std::set &dylibNames); +private: + bool OpenFile(const char *szPath); + bool CloseFile(); + + bool NewArchO(uint8_t *pBase, uint32_t uLength); + void FreeArchOes(); + bool ReallocCodeSignSpace(); + +private: + size_t m_sSize; + string m_strFile; + uint8_t *m_pBase; + bool m_bCSRealloced; + vector m_arrArchOes; +}; diff --git a/ZSign/openssl.cpp b/ZSign/openssl.cpp new file mode 100644 index 0000000..7147b06 --- /dev/null +++ b/ZSign/openssl.cpp @@ -0,0 +1,944 @@ +#include "common/common.h" +#include "common/base64.h" +#include "openssl.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class COpenSSLInit +{ +public: + COpenSSLInit() + { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); +#endif + }; +}; + +COpenSSLInit g_OpenSSLInit; + +const char *appleDevCACert = "" + "-----BEGIN CERTIFICATE-----\n" + "MIIEIjCCAwqgAwIBAgIIAd68xDltoBAwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE\n" + "BhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRp\n" + "ZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEz\n" + "MDIwNzIxNDg0N1oXDTIzMDIwNzIxNDg0N1owgZYxCzAJBgNVBAYTAlVTMRMwEQYD\n" + "VQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxv\n" + "cGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3Bl\n" + "ciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3\n" + "DQEBAQUAA4IBDwAwggEKAoIBAQDKOFSmy1aqyCQ5SOmM7uxfuH8mkbw0U3rOfGOA\n" + "YXdkXqUHI7Y5/lAtFVZYcC1+xG7BSoU+L/DehBqhV8mvexj/avoVEkkVCBmsqtsq\n" + "Mu2WY2hSFT2Miuy/axiV4AOsAX2XBWfODoWVN2rtCbauZ81RZJ/GXNG8V25nNYB2\n" + "NqSHgW44j9grFU57Jdhav06DwY3Sk9UacbVgnJ0zTlX5ElgMhrgWDcHld0WNUEi6\n" + "Ky3klIXh6MSdxmilsKP8Z35wugJZS3dCkTm59c3hTO/AO0iMpuUhXf1qarunFjVg\n" + "0uat80YpyejDi+l5wGphZxWy8P3laLxiX27Pmd3vG2P+kmWrAgMBAAGjgaYwgaMw\n" + "HQYDVR0OBBYEFIgnFwmpthhgi+zruvZHWcVSVKO3MA8GA1UdEwEB/wQFMAMBAf8w\n" + "HwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGg\n" + "H4YdaHR0cDovL2NybC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGG\n" + "MBAGCiqGSIb3Y2QGAgEEAgUAMA0GCSqGSIb3DQEBBQUAA4IBAQBPz+9Zviz1smwv\n" + "j+4ThzLoBTWobot9yWkMudkXvHcs1Gfi/ZptOllc34MBvbKuKmFysa/Nw0Uwj6OD\n" + "Dc4dR7Txk4qjdJukw5hyhzs+r0ULklS5MruQGFNrCk4QttkdUGwhgAqJTleMa1s8\n" + "Pab93vcNIx0LSiaHP7qRkkykGRIZbVf1eliHe2iK5IaMSuviSRSqpd1VAKmuu0sw\n" + "ruGgsbwpgOYJd+W+NKIByn/c4grmO7i77LpilfMFY0GCzQ87HUyVpNur+cmV6U/k\n" + "TecmmYHpvPm0KdIBembhLoz2IYrF+Hjhga6/05Cdqa3zr/04GpZnMBxRpVzscYqC\n" + "tGwPDBUf\n" + "-----END CERTIFICATE-----\n"; + +const char *appleDevCACertG3 = "" + "-----BEGIN CERTIFICATE-----\n" + "MIIEUTCCAzmgAwIBAgIQfK9pCiW3Of57m0R6wXjF7jANBgkqhkiG9w0BAQsFADBi\n" + "MQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBw\n" + "bGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3Qg\n" + "Q0EwHhcNMjAwMjE5MTgxMzQ3WhcNMzAwMjIwMDAwMDAwWjB1MUQwQgYDVQQDDDtB\n" + "cHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9ucyBDZXJ0aWZpY2F0aW9u\n" + "IEF1dGhvcml0eTELMAkGA1UECwwCRzMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJ\n" + "BgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2PWJ/KhZ\n" + "C4fHTJEuLVaQ03gdpDDppUjvC0O/LYT7JF1FG+XrWTYSXFRknmxiLbTGl8rMPPbW\n" + "BpH85QKmHGq0edVny6zpPwcR4YS8Rx1mjjmi6LRJ7TrS4RBgeo6TjMrA2gzAg9Dj\n" + "+ZHWp4zIwXPirkbRYp2SqJBgN31ols2N4Pyb+ni743uvLRfdW/6AWSN1F7gSwe0b\n" + "5TTO/iK1nkmw5VW/j4SiPKi6xYaVFuQAyZ8D0MyzOhZ71gVcnetHrg21LYwOaU1A\n" + "0EtMOwSejSGxrC5DVDDOwYqGlJhL32oNP/77HK6XF8J4CjDgXx9UO0m3JQAaN4LS\n" + "VpelUkl8YDib7wIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0j\n" + "BBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wRAYIKwYBBQUHAQEEODA2MDQGCCsG\n" + "AQUFBzABhihodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLWFwcGxlcm9vdGNh\n" + "MC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwuYXBwbGUuY29tL3Jvb3QuY3Js\n" + "MB0GA1UdDgQWBBQJ/sAVkPmvZAqSErkmKGMMl+ynsjAOBgNVHQ8BAf8EBAMCAQYw\n" + "EAYKKoZIhvdjZAYCAQQCBQAwDQYJKoZIhvcNAQELBQADggEBAK1lE+j24IF3RAJH\n" + "Qr5fpTkg6mKp/cWQyXMT1Z6b0KoPjY3L7QHPbChAW8dVJEH4/M/BtSPp3Ozxb8qA\n" + "HXfCxGFJJWevD8o5Ja3T43rMMygNDi6hV0Bz+uZcrgZRKe3jhQxPYdwyFot30ETK\n" + "XXIDMUacrptAGvr04NM++i+MZp+XxFRZ79JI9AeZSWBZGcfdlNHAwWx/eCHvDOs7\n" + "bJmCS1JgOLU5gm3sUjFTvg+RTElJdI+mUcuER04ddSduvfnSXPN/wmwLCTbiZOTC\n" + "NwMUGdXqapSqqdv+9poIZ4vvK7iqF0mDr8/LvOnP6pVxsLRFoszlh6oKw0E6eVza\n" + "UDSdlTs=\n" + "-----END CERTIFICATE-----\n"; + +const char *appleRootCACert = "" + "-----BEGIN CERTIFICATE-----\n" + "MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET\n" + "MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv\n" + "biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0\n" + "MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw\n" + "bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx\n" + "FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\n" + "ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+\n" + "+FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1\n" + "XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w\n" + "tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW\n" + "q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM\n" + "aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E\n" + "BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3\n" + "R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE\n" + "ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93\n" + "d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl\n" + "IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0\n" + "YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj\n" + "b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp\n" + "Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc\n" + "NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP\n" + "y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7\n" + "R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg\n" + "xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP\n" + "IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX\n" + "UKqK1drk/NAJBzewdXUh\n" + "-----END CERTIFICATE-----\n"; + +bool CMSError() +{ + ERR_print_errors_fp(stdout); + return false; +} + +ASN1_TYPE *_GenerateASN1Type(const string &value) +{ + long errline = -1; + char *genstr = NULL; + BIO *ldapbio = BIO_new(BIO_s_mem()); + CONF *cnf = NCONF_new(NULL); + + if (cnf == NULL) { + ZLog::Error(">>> NCONF_new failed\n"); + BIO_free(ldapbio); + } + string a = "asn1=SEQUENCE:A\n[A]\nC=OBJECT:sha256\nB=FORMAT:HEX,OCT:" + value + "\n"; + int code = BIO_puts(ldapbio, a.c_str()); + if (NCONF_load_bio(cnf, ldapbio, &errline) <= 0) { + BIO_free(ldapbio); + NCONF_free(cnf); + ZLog::PrintV(">>> NCONF_load_bio failed %d\n", errline); + } + BIO_free(ldapbio); + genstr = NCONF_get_string(cnf, "default", "asn1"); + + if (genstr == NULL) { + ZLog::Error(">>> NCONF_get_string failed\n"); + NCONF_free(cnf); + } + ASN1_TYPE *ret = ASN1_generate_nconf(genstr, cnf); + NCONF_free(cnf); + return ret; +} + +bool _GenerateCMS(X509 *scert, EVP_PKEY *spkey, const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput) +{ + if (!scert || !spkey) + { + return CMSError(); + } + + BIO *bother1; + unsigned long issuerHash = X509_issuer_name_hash(scert); + if (0x817d2f7a == issuerHash) + { + bother1 = BIO_new_mem_buf(appleDevCACert, (int)strlen(appleDevCACert)); + } + else if (0x9b16b75c == issuerHash) + { + bother1 = BIO_new_mem_buf(appleDevCACertG3, (int)strlen(appleDevCACertG3)); + } + else + { + ZLog::Error(">>> Unknown Issuer Hash!\n"); + return false; + } + + BIO *bother2 = BIO_new_mem_buf(appleRootCACert, (int)strlen(appleRootCACert)); + if (!bother1 || !bother2) + { + return CMSError(); + } + + X509 *ocert1 = PEM_read_bio_X509(bother1, NULL, 0, NULL); + X509 *ocert2 = PEM_read_bio_X509(bother2, NULL, 0, NULL); + if (!ocert1 || !ocert2) + { + return CMSError(); + } + + STACK_OF(X509) *otherCerts = sk_X509_new_null(); + if (!otherCerts) + { + return CMSError(); + } + + if (!sk_X509_push(otherCerts, ocert1)) + { + return CMSError(); + } + + if (!sk_X509_push(otherCerts, ocert2)) + { + return CMSError(); + } + + BIO *in = BIO_new_mem_buf(strCDHashData.c_str(), (int)strCDHashData.size()); + if (!in) + { + return CMSError(); + } + + int nFlags = CMS_PARTIAL | CMS_DETACHED | CMS_NOSMIMECAP | CMS_BINARY; + CMS_ContentInfo *cms = CMS_sign(NULL, NULL, otherCerts, NULL, nFlags); + if (!cms) + { + return CMSError(); + } + + CMS_SignerInfo * si = CMS_add1_signer(cms, scert, spkey, EVP_sha256(), nFlags); +// CMS_add1_signer(cms, NULL, NULL, EVP_sha1(), nFlags); + if (!si) { + return CMSError(); + } + + // add plist + ASN1_OBJECT * obj = OBJ_txt2obj("1.2.840.113635.100.9.1", 1); + if (!obj) { + return CMSError(); + } + + int addHashPlist = CMS_signed_add1_attr_by_OBJ(si, obj, 0x4, strCDHashesPlist.c_str(), (int)strCDHashesPlist.size()); + + if (!addHashPlist) { + return CMSError(); + } + + // add CDHashes + string sha256; + char buf[16] = {0}; + for (size_t i = 0; i < strAltnateCodeDirectorySlot256.size(); i++) + { + sprintf(buf, "%02x", (uint8_t)strAltnateCodeDirectorySlot256[i]); + sha256 += buf; + } + transform(sha256.begin(), sha256.end(), sha256.begin(), ::toupper); + + ASN1_OBJECT * obj2 = OBJ_txt2obj("1.2.840.113635.100.9.2", 1); + if (!obj2) { + return CMSError(); + } + + X509_ATTRIBUTE *attr = X509_ATTRIBUTE_new(); + X509_ATTRIBUTE_set1_object(attr, obj2); + + ASN1_TYPE *type_256 = _GenerateASN1Type(sha256); + X509_ATTRIBUTE_set1_data(attr, V_ASN1_SEQUENCE, + type_256->value.asn1_string->data, type_256->value.asn1_string->length); + int addHashSHA = CMS_signed_add1_attr(si, attr); + if (!addHashSHA) { + return CMSError(); + } + + if (!CMS_final(cms, in, NULL, nFlags)) + { + return CMSError(); + } + + BIO *out = BIO_new(BIO_s_mem()); + if (!out) + { + return CMSError(); + } + + //PEM_write_bio_CMS(out, cms); + if (!i2d_CMS_bio(out, cms)) + { + return CMSError(); + } + + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(out, &bptr); + if (!bptr) + { + return CMSError(); + } + + strCMSOutput.clear(); + strCMSOutput.append(bptr->data, bptr->length); + ASN1_TYPE_free(type_256); + return (!strCMSOutput.empty()); +} + +bool GenerateCMS(const string &strSignerCertData, const string &strSignerPKeyData, const string &strCDHashData, const string &strCDHashesPlist, string &strCMSOutput) +{ + BIO *bcert = BIO_new_mem_buf(strSignerCertData.c_str(), (int)strSignerCertData.size()); + BIO *bpkey = BIO_new_mem_buf(strSignerPKeyData.c_str(), (int)strSignerPKeyData.size()); + + if (!bcert || !bpkey) + { + return CMSError(); + } + + X509 *scert = PEM_read_bio_X509(bcert, NULL, 0, NULL); + EVP_PKEY *spkey = PEM_read_bio_PrivateKey(bpkey, NULL, 0, NULL); + if (!scert || !spkey) + { + return CMSError(); + } + + return ::_GenerateCMS(scert, spkey, strCDHashData, strCDHashesPlist, "", "", strCMSOutput); +} + +bool GetCMSContent(const string &strCMSDataInput, string &strContentOutput) +{ + if (strCMSDataInput.empty()) + { + return false; + } + + BIO *in = BIO_new(BIO_s_mem()); + OPENSSL_assert((size_t)BIO_write(in, strCMSDataInput.data(), (int)strCMSDataInput.size()) == strCMSDataInput.size()); + CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); + if (!cms) + { + return CMSError(); + } + + ASN1_OCTET_STRING **pos = CMS_get0_content(cms); + if (!pos) + { + return CMSError(); + } + + if (!(*pos)) + { + return CMSError(); + } + + strContentOutput.clear(); + strContentOutput.append((const char *)(*pos)->data, (*pos)->length); + return (!strContentOutput.empty()); +} + +bool GetCMSContent2(const void* strCMSDataInput, int size, string &strContentOutput) +{ + if (size == 0) + { + return false; + } + + BIO *in = BIO_new(BIO_s_mem()); + OPENSSL_assert((size_t)BIO_write(in, strCMSDataInput, size) == size); + CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); + if (!cms) + { + return CMSError(); + } + + ASN1_OCTET_STRING **pos = CMS_get0_content(cms); + if (!pos) + { + return CMSError(); + } + + if (!(*pos)) + { + return CMSError(); + } + + strContentOutput.clear(); + strContentOutput.append((const char *)(*pos)->data, (*pos)->length); + return (!strContentOutput.empty()); +} + +bool GetCertSubjectCN(X509 *cert, string &strSubjectCN) +{ + if (!cert) + { + return CMSError(); + } + + X509_NAME *name = X509_get_subject_name(cert); + + int common_name_loc = X509_NAME_get_index_by_NID(name, NID_commonName, -1); + if (common_name_loc < 0) + { + return CMSError(); + } + + X509_NAME_ENTRY *common_name_entry = X509_NAME_get_entry(name, common_name_loc); + if (common_name_entry == NULL) + { + return CMSError(); + } + + ASN1_STRING *common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); + if (common_name_asn1 == NULL) + { + return CMSError(); + } + + strSubjectCN.clear(); + strSubjectCN.append((const char *)common_name_asn1->data, common_name_asn1->length); + return (!strSubjectCN.empty()); +} + +bool GetCertSubjectCN(const string &strCertData, string &strSubjectCN) +{ + if (strCertData.empty()) + { + return false; + } + + BIO *bcert = BIO_new_mem_buf(strCertData.c_str(), strCertData.size()); + if (!bcert) + { + return CMSError(); + } + + X509 *cert = PEM_read_bio_X509(bcert, NULL, 0, NULL); + if (!cert) + { + return CMSError(); + } + + return GetCertSubjectCN(cert, strSubjectCN); +} + +void ParseCertSubject(const string &strSubject, JValue &jvSubject) +{ + vector arrNodes; + StringSplit(strSubject, "/", arrNodes); + for (size_t i = 0; i < arrNodes.size(); i++) + { + vector arrLines; + StringSplit(arrNodes[i], "=", arrLines); + if (2 == arrLines.size()) + { + jvSubject[arrLines[0]] = arrLines[1]; + } + } +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +string ASN1_TIMEtoString(ASN1_TIME *time) +{ +#else +string ASN1_TIMEtoString(const ASN1_TIME *time) +{ +#endif + BIO *out = BIO_new(BIO_s_mem()); + if (!out) + { + CMSError(); + return ""; + } + + ASN1_TIME_print(out, time); + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(out, &bptr); + if (!bptr) + { + CMSError(); + return ""; + } + string strTime; + strTime.append(bptr->data, bptr->length); + return strTime; +} + +bool GetCertInfo(X509 *cert, JValue &jvCertInfo) +{ + if (!cert) + { + return CMSError(); + } + + jvCertInfo["Version"] = (int)X509_get_version(cert); + + ASN1_INTEGER *asn1_i = X509_get_serialNumber(cert); + if (asn1_i) + { + BIGNUM *bignum = ASN1_INTEGER_to_BN(asn1_i, NULL); + if (bignum) + { + jvCertInfo["SerialNumber"] = BN_bn2hex(bignum); + } + } + + jvCertInfo["SignatureAlgorithm"] = OBJ_nid2ln(X509_get_signature_nid(cert)); + + EVP_PKEY *pubkey = X509_get_pubkey(cert); + int type = EVP_PKEY_id(pubkey); + jvCertInfo["PublicKey"]["Algorithm"] = OBJ_nid2ln(type); + +#if OPENSSL_VERSION_NUMBER < 0x10100000L + jvCertInfo["Validity"]["NotBefore"] = ASN1_TIMEtoString(X509_get_notBefore(cert)); + jvCertInfo["Validity"]["NotAfter"] = ASN1_TIMEtoString(X509_get_notAfter(cert)); +#else + jvCertInfo["Validity"]["NotBefore"] = ASN1_TIMEtoString(X509_get0_notBefore(cert)); + jvCertInfo["Validity"]["NotAfter"] = ASN1_TIMEtoString(X509_get0_notAfter(cert)); +#endif + + string strIssuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); + string strSubject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); + + ParseCertSubject(strIssuer, jvCertInfo["Issuer"]); + ParseCertSubject(strSubject, jvCertInfo["Subject"]); + + return (!strIssuer.empty() && !strSubject.empty()); +} + +bool GetCMSInfo(uint8_t *pCMSData, uint32_t uCMSLength, JValue &jvOutput) +{ + BIO *in = BIO_new(BIO_s_mem()); + OPENSSL_assert((size_t)BIO_write(in, pCMSData, uCMSLength) == uCMSLength); + CMS_ContentInfo *cms = d2i_CMS_bio(in, NULL); + if (!cms) + { + return CMSError(); + } + + int detached = CMS_is_detached(cms); + jvOutput["detached"] = detached; + + const ASN1_OBJECT *obj = CMS_get0_type(cms); + const char *sn = OBJ_nid2ln(OBJ_obj2nid(obj)); + jvOutput["contentType"] = sn; + + ASN1_OCTET_STRING **pos = CMS_get0_content(cms); + if (pos) + { + if ((*pos)) + { + ZBase64 b64; + jvOutput["content"] = b64.Encode((const char *)(*pos)->data, (*pos)->length); + } + } + + STACK_OF(X509) *certs = CMS_get1_certs(cms); + for (int i = 0; i < sk_X509_num(certs); i++) + { + JValue jvCertInfo; + if (GetCertInfo(sk_X509_value(certs, i), jvCertInfo)) + { + jvOutput["certs"].push_back(jvCertInfo); + } + } + + STACK_OF(CMS_SignerInfo) *sis = CMS_get0_SignerInfos(cms); + for (int i = 0; i < sk_CMS_SignerInfo_num(sis); i++) + { + CMS_SignerInfo *si = sk_CMS_SignerInfo_value(sis, i); + //int CMS_SignerInfo_get0_signer_id(CMS_SignerInfo *si, ASN1_OCTET_STRING **keyid, X509_NAME **issuer, ASN1_INTEGER **sno); + + int nSignedAttsCount = CMS_signed_get_attr_count(si); + for (int j = 0; j < nSignedAttsCount; j++) + { + X509_ATTRIBUTE *attr = CMS_signed_get_attr(si, j); + if (!attr) + { + continue; + } + int nCount = X509_ATTRIBUTE_count(attr); + if (nCount <= 0) + { + continue; + } + + ASN1_OBJECT *obj = X509_ATTRIBUTE_get0_object(attr); + if (!obj) + { + continue; + } + + char txtobj[128] = {0}; + OBJ_obj2txt(txtobj, 128, obj, 1); + + if (0 == strcmp("1.2.840.113549.1.9.3", txtobj)) + { //V_ASN1_OBJECT + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + jvOutput["attrs"]["ContentType"]["obj"] = txtobj; + jvOutput["attrs"]["ContentType"]["data"] = OBJ_nid2ln(OBJ_obj2nid(av->value.object)); + } + } + else if (0 == strcmp("1.2.840.113549.1.9.4", txtobj)) + { //V_ASN1_OCTET_STRING + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + string strSHASum; + char buf[16] = {0}; + for (int m = 0; m < av->value.octet_string->length; m++) + { + sprintf(buf, "%02x", (uint8_t)av->value.octet_string->data[m]); + strSHASum += buf; + } + jvOutput["attrs"]["MessageDigest"]["obj"] = txtobj; + jvOutput["attrs"]["MessageDigest"]["data"] = strSHASum; + } + } + else if (0 == strcmp("1.2.840.113549.1.9.5", txtobj)) + { //V_ASN1_UTCTIME + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + BIO *mem = BIO_new(BIO_s_mem()); + ASN1_UTCTIME_print(mem, av->value.utctime); + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(mem, &bptr); + BIO_set_close(mem, BIO_NOCLOSE); + string strTime; + strTime.append(bptr->data, bptr->length); + BIO_free_all(mem); + + jvOutput["attrs"]["SigningTime"]["obj"] = txtobj; + jvOutput["attrs"]["SigningTime"]["data"] = strTime; + } + } + else if (0 == strcmp("1.2.840.113635.100.9.2", txtobj)) + { //V_ASN1_SEQUENCE + jvOutput["attrs"]["CDHashes2"]["obj"] = txtobj; + for (int m = 0; m < nCount; m++) + { + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, m); + if (NULL != av) + { + ASN1_STRING *s = av->value.sequence; + + BIO *mem = BIO_new(BIO_s_mem()); + + ASN1_parse_dump(mem, s->data, s->length, 2, 0); + BUF_MEM *bptr = NULL; + BIO_get_mem_ptr(mem, &bptr); + BIO_set_close(mem, BIO_NOCLOSE); + string strData; + strData.append(bptr->data, bptr->length); + BIO_free_all(mem); + + string strSHASum; + size_t pos1 = strData.find("[HEX DUMP]:"); + if (string::npos != pos1) + { + size_t pos2 = strData.find("\n", pos1); + if (string::npos != pos2) + { + strSHASum = strData.substr(pos1 + 11, pos2 - pos1 - 11); + } + } + transform(strSHASum.begin(), strSHASum.end(), strSHASum.begin(), ::tolower); + jvOutput["attrs"]["CDHashes2"]["data"].push_back(strSHASum); + } + } + } + else if (0 == strcmp("1.2.840.113635.100.9.1", txtobj)) + { //V_ASN1_OCTET_STRING + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + string strPList; + strPList.append((const char *)av->value.octet_string->data, av->value.octet_string->length); + jvOutput["attrs"]["CDHashes"]["obj"] = txtobj; + jvOutput["attrs"]["CDHashes"]["data"] = strPList; + } + } + else + { + ASN1_TYPE *av = X509_ATTRIBUTE_get0_type(attr, 0); + if (NULL != av) + { + JValue jvAttr; + jvAttr["obj"] = txtobj; + jvAttr["name"] = OBJ_nid2ln(OBJ_obj2nid(obj)); + jvAttr["type"] = av->type; + jvAttr["count"] = nCount; + jvOutput["attrs"]["unknown"].push_back(jvAttr); + } + } + } + } + + return true; +} + +ZSignAsset::ZSignAsset() +{ + m_evpPKey = NULL; + m_x509Cert = NULL; +} + +bool ZSignAsset::Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword) +{ + ReadFile(strProvisionFile.c_str(), m_strProvisionData); + ReadFile(strEntitlementsFile.c_str(), m_strEntitlementsData); + if (m_strProvisionData.empty()) + { + ZLog::Error(">>> Can't Find Provision File!\n"); + return false; + } + + JValue jvProv; + string strProvContent; + if (GetCMSContent(m_strProvisionData, strProvContent)) + { + if (jvProv.readPList(strProvContent)) + { + m_strTeamId = jvProv["TeamIdentifier"][0].asCString(); + if (m_strEntitlementsData.empty()) + { + jvProv["Entitlements"].writePList(m_strEntitlementsData); + } + } + } + + if (m_strTeamId.empty()) + { + ZLog::Error(">>> Can't Find TeamId!\n"); + return false; + } + + X509 *x509Cert = NULL; + EVP_PKEY *evpPKey = NULL; + BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); + if (NULL != bioPKey) + { + evpPKey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str()); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + evpPKey = d2i_PrivateKey_bio(bioPKey, NULL); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + OSSL_PROVIDER_load(NULL, "legacy"); + PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL); + if (NULL != p12) + { + if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPKey, &x509Cert, NULL)) + { + CMSError(); + } + PKCS12_free(p12); + } + } + } + BIO_free(bioPKey); + } + + if (NULL == evpPKey) + { + ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n"); + return false; + } + + if (NULL == x509Cert && !strSignerCertFile.empty()) + { + BIO *bioCert = BIO_new_file(strSignerCertFile.c_str(), "r"); + if (NULL != bioCert) + { + x509Cert = PEM_read_bio_X509(bioCert, NULL, 0, NULL); + if (NULL == x509Cert) + { + BIO_reset(bioCert); + x509Cert = d2i_X509_bio(bioCert, NULL); + } + BIO_free(bioCert); + } + } + + if (NULL != x509Cert) + { + if (!X509_check_private_key(x509Cert, evpPKey)) + { + X509_free(x509Cert); + x509Cert = NULL; + } + } + + if (NULL == x509Cert) + { + for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++) + { + string strCertData = jvProv["DeveloperCertificates"][i].asData(); + BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), (int)strCertData.size()); + if (NULL != bioCert) + { + x509Cert = d2i_X509_bio(bioCert, NULL); + if (NULL != x509Cert) + { + if (X509_check_private_key(x509Cert, evpPKey)) + { + break; + } + X509_free(x509Cert); + x509Cert = NULL; + } + BIO_free(bioCert); + } + } + } + + if (NULL == x509Cert) + { + ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n"); + return false; + } + + if (!GetCertSubjectCN(x509Cert, m_strSubjectCN)) + { + ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n"); + return false; + } + + m_evpPKey = evpPKey; + m_x509Cert = x509Cert; + return true; +} + +bool ZSignAsset::GenerateCMS(const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput) +{ + return ::_GenerateCMS((X509 *)m_x509Cert, (EVP_PKEY *)m_evpPKey, strCDHashData, strCDHashesPlist, strCodeDirectorySlotSHA1, strAltnateCodeDirectorySlot256, strCMSOutput); +} + + std::string binary_to_hex(const char* data, int len) { + std::ostringstream oss; + for (int i = 0; i < len; ++i) { + uint8_t byte = data[i]; + oss << std::hex << std::setw(2) << std::setfill('0') << static_cast(byte); + } + return oss.str(); + } + +bool ZSignAsset::InitSimple(const void* strSignerPKeyData, int strSignerPKeyDataSize, const void* strProvisionData, int strProvisionDataSize, const string &strPassword){ + + JValue jvProv; + string strProvContent; + m_strEntitlementsData = ""; + if (GetCMSContent2(strProvisionData, strProvisionDataSize, strProvContent)) + { + if (jvProv.readPList(strProvContent)) + { + m_strTeamId = jvProv["TeamIdentifier"][0].asCString(); + if (m_strEntitlementsData.empty()) + { + jvProv["Entitlements"].writePList(m_strEntitlementsData); + } + } + } + + if (m_strTeamId.empty()) + { + ZLog::Error(">>> Can't Find TeamId!\n"); + return false; + } + + X509 *x509Cert = NULL; + EVP_PKEY *evpPKey = NULL; + + // BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); + char* digest = new char[16]; + MD5((unsigned char*) strSignerPKeyData, strSignerPKeyDataSize, (unsigned char*)digest); + ZLog::Error(binary_to_hex(digest, 16).data()); + delete[] digest; + + + BIO *bioPKey = BIO_new_mem_buf(strSignerPKeyData, strSignerPKeyDataSize); + if (NULL != bioPKey) + { + evpPKey = PEM_read_bio_PrivateKey(bioPKey, NULL, NULL, (void *)strPassword.c_str()); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + evpPKey = d2i_PrivateKey_bio(bioPKey, NULL); + if (NULL == evpPKey) + { + BIO_reset(bioPKey); + OSSL_PROVIDER_load(NULL, "legacy"); + PKCS12 *p12 = d2i_PKCS12_bio(bioPKey, NULL); + if (NULL != p12) + { + if (0 == PKCS12_parse(p12, strPassword.c_str(), &evpPKey, &x509Cert, NULL)) + { + CMSError(); + } + PKCS12_free(p12); + } + } + } + BIO_free(bioPKey); + } + + if (NULL == evpPKey) + { + ZLog::Error(">>> Can't Load P12 or PrivateKey File! Please Input The Correct File And Password!\n"); + return false; + } + + if (NULL != x509Cert) + { + if (!X509_check_private_key(x509Cert, evpPKey)) + { + X509_free(x509Cert); + x509Cert = NULL; + } + } + + if (NULL == x509Cert) + { + for (size_t i = 0; i < jvProv["DeveloperCertificates"].size(); i++) + { + string strCertData = jvProv["DeveloperCertificates"][i].asData(); + BIO *bioCert = BIO_new_mem_buf(strCertData.c_str(), (int)strCertData.size()); + if (NULL != bioCert) + { + x509Cert = d2i_X509_bio(bioCert, NULL); + if (NULL != x509Cert) + { + if (X509_check_private_key(x509Cert, evpPKey)) + { + break; + } + X509_free(x509Cert); + x509Cert = NULL; + } + BIO_free(bioCert); + } + } + } + + if (NULL == x509Cert) + { + ZLog::Error(">>> Can't Find Paired Certificate And PrivateKey!\n"); + return false; + } + + if (!GetCertSubjectCN(x509Cert, m_strSubjectCN)) + { + ZLog::Error(">>> Can't Find Paired Certificate Subject Common Name!\n"); + return false; + } + + m_evpPKey = evpPKey; + m_x509Cert = x509Cert; + return true; +} diff --git a/ZSign/openssl.h b/ZSign/openssl.h new file mode 100644 index 0000000..6e4dfec --- /dev/null +++ b/ZSign/openssl.h @@ -0,0 +1,28 @@ +#pragma once +#include "common/json.h" + +bool GetCertSubjectCN(const string &strCertData, string &strSubjectCN); +bool GetCMSInfo(uint8_t *pCMSData, uint32_t uCMSLength, JValue &jvOutput); +bool GetCMSContent(const string &strCMSDataInput, string &strContentOutput); +bool GenerateCMS(const string &strSignerCertData, const string &strSignerPKeyData, const string &strCDHashData, const string &strCDHashPlist, string &strCMSOutput); + +class ZSignAsset +{ +public: + ZSignAsset(); + +public: + bool GenerateCMS(const string &strCDHashData, const string &strCDHashesPlist, const string &strCodeDirectorySlotSHA1, const string &strAltnateCodeDirectorySlot256, string &strCMSOutput); + bool Init(const string &strSignerCertFile, const string &strSignerPKeyFile, const string &strProvisionFile, const string &strEntitlementsFile, const string &strPassword); + bool InitSimple(const void* strSignerPKeyData, int strSignerPKeyDataSize, const void* strProvisionData, int strProvisionDataSize, const string &strPassword); + +public: + string m_strTeamId; + string m_strSubjectCN; + string m_strProvisionData; + string m_strEntitlementsData; + +private: + void *m_evpPKey; + void *m_x509Cert; +}; diff --git a/ZSign/signing.cpp b/ZSign/signing.cpp new file mode 100644 index 0000000..64d5dd7 --- /dev/null +++ b/ZSign/signing.cpp @@ -0,0 +1,875 @@ +#include "common/common.h" +#include "common/json.h" +#include "common/mach-o.h" +#include "openssl.h" + +static void _DERLength(string &strBlob, uint64_t uLength) +{ + if (uLength < 128) + { + strBlob.append(1, (char)uLength); + } + else + { + uint32_t sLength = (64 - __builtin_clzll(uLength) + 7) / 8; + strBlob.append(1, (char)(0x80 | sLength)); + sLength *= 8; + do + { + strBlob.append(1, (char)(uLength >> (sLength -= 8))); + } while (sLength != 0); + } +} + +static string _DER(const JValue &data) +{ + string strOutput; + if (data.isBool()) + { + strOutput.append(1, 0x01); + strOutput.append(1, 1); + strOutput.append(1, data.asBool() ? 1 : 0); + } + else if (data.isInt()) + { + uint64_t uVal = data.asInt64(); + strOutput.append(1, 0x02); + _DERLength(strOutput, uVal); + + uint32_t sLength = (64 - __builtin_clzll(uVal) + 7) / 8; + sLength *= 8; + do + { + strOutput.append(1, (char)(uVal >> (sLength -= 8))); + } while (sLength != 0); + } + else if (data.isString()) + { + string strVal = data.asCString(); + strOutput.append(1, 0x0c); + _DERLength(strOutput, strVal.size()); + strOutput += strVal; + } + else if (data.isArray()) + { + string strArray; + size_t size = data.size(); + for (size_t i = 0; i < size; i++) + { + strArray += _DER(data[i]); + } + strOutput.append(1, 0x30); + _DERLength(strOutput, strArray.size()); + strOutput += strArray; + } + else if (data.isObject()) + { + string strDict; + vector arrKeys; + data.keys(arrKeys); + for (size_t i = 0; i < arrKeys.size(); i++) + { + string &strKey = arrKeys[i]; + string strVal = _DER(data[strKey]); + + strDict.append(1, 0x30); + _DERLength(strDict, (2 + strKey.size() + strVal.size())); + + strDict.append(1, 0x0c); + _DERLength(strDict, strKey.size()); + strDict += strKey; + + strDict += strVal; + } + + strOutput.append(1, 0x31); + _DERLength(strOutput, strDict.size()); + strOutput += strDict; + } + else if (data.isFloat()) + { + assert(false); + } + else if (data.isDate()) + { + assert(false); + } + else if (data.isData()) + { + assert(false); + } + else + { + assert(false && "Unsupported Entitlements DER Type"); + } + + return strOutput; +} + +uint32_t SlotParseGeneralHeader(const char *szSlotName, uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = LE(*(((uint32_t *)pSlotBase) + 1)); + ZLog::PrintV("\n > %s: \n", szSlotName); + ZLog::PrintV("\ttype: \t\t0x%x\n", LE(pbi->type)); + ZLog::PrintV("\toffset: \t%u\n", LE(pbi->offset)); + ZLog::PrintV("\tmagic: \t\t0x%x\n", LE(*((uint32_t *)pSlotBase))); + ZLog::PrintV("\tlength: \t%u\n", uSlotLength); + return uSlotLength; +} + +void SlotParseGeneralTailer(uint8_t *pSlotBase, uint32_t uSlotLength) +{ + PrintDataSHASum("\tSHA-1: \t", E_SHASUM_TYPE_1, pSlotBase, uSlotLength); + PrintDataSHASum("\tSHA-256:\t", E_SHASUM_TYPE_256, pSlotBase, uSlotLength); +} + +bool SlotParseRequirements(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_REQUIREMENTS", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + if (IsFileExists("/usr/bin/csreq")) + { + string strTempFile; + StringFormat(strTempFile, "/tmp/Requirements_%llu.blob", GetMicroSecond()); + WriteFile(strTempFile.c_str(), (const char *)pSlotBase, uSlotLength); + + string strCommand; + StringFormat(strCommand, "/usr/bin/csreq -r '%s' -t ", strTempFile.c_str()); + char result[1024] = {0}; + FILE *cmd = popen(strCommand.c_str(), "r"); + while (NULL != fgets(result, sizeof(result), cmd)) + { + printf("\treqtext: \t%s", result); + } + pclose(cmd); + RemoveFile(strTempFile.c_str()); + } + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Requirements.slot", (const char *)pSlotBase, uSlotLength); + } + return true; +} + +bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput) +{ + strOutput.clear(); + if (strBundleID.empty() || strSubjectCN.empty()) + { //ldid + strOutput = "\xfa\xde\x0c\x01\x00\x00\x00\x0c\x00\x00\x00\x00"; + return true; + } + + string strPaddedBundleID = strBundleID; + strPaddedBundleID.append(((strBundleID.size() % 4) ? (4 - (strBundleID.size() % 4)) : 0), 0); + + string strPaddedSubjectID = strSubjectCN; + strPaddedSubjectID.append(((strSubjectCN.size() % 4) ? (4 - (strSubjectCN.size() % 4)) : 0), 0); + + uint8_t magic1[] = {0xfa, 0xde, 0x0c, 0x01}; + uint32_t uLength1 = 0; + uint8_t pack1[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14}; + uint8_t magic2[] = {0xfa, 0xde, 0x0c, 0x00}; + uint32_t uLength2 = 0; + uint8_t pack2[] = {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02}; + uint32_t uBundldIDLength = (uint32_t)strBundleID.size(); + //string strPaddedBundleID + uint8_t pack3[] = { + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0f, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x00, + 0x00, + 0x0b, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x73, + 0x75, + 0x62, + 0x6a, + 0x65, + 0x63, + 0x74, + 0x2e, + 0x43, + 0x4e, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x01, + }; + uint32_t uSubjectCNLength = (uint32_t)strSubjectCN.size(); + //string strPaddedSubjectID + uint8_t pack4[] = { + 0x00, + 0x00, + 0x00, + 0x0e, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + 0x0a, + 0x2a, + 0x86, + 0x48, + 0x86, + 0xf7, + 0x63, + 0x64, + 0x06, + 0x02, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + }; + + uLength2 += sizeof(magic2) + sizeof(uLength2) + sizeof(pack2); + uLength2 += sizeof(uBundldIDLength) + strPaddedBundleID.size(); + uLength2 += sizeof(pack3); + uLength2 += sizeof(uSubjectCNLength) + strPaddedSubjectID.size(); + uLength2 += sizeof(pack4); + + uLength1 += sizeof(magic1) + sizeof(uLength1) + sizeof(pack1); + uLength1 += uLength2; + + uLength1 = BE(uLength1); + uLength2 = BE(uLength2); + uBundldIDLength = BE(uBundldIDLength); + uSubjectCNLength = BE(uSubjectCNLength); + + strOutput.append((const char *)magic1, sizeof(magic1)); + strOutput.append((const char *)&uLength1, sizeof(uLength1)); + strOutput.append((const char *)pack1, sizeof(pack1)); + strOutput.append((const char *)magic2, sizeof(magic2)); + strOutput.append((const char *)&uLength2, sizeof(uLength2)); + strOutput.append((const char *)pack2, sizeof(pack2)); + strOutput.append((const char *)&uBundldIDLength, sizeof(uBundldIDLength)); + strOutput.append(strPaddedBundleID.data(), strPaddedBundleID.size()); + strOutput.append((const char *)pack3, sizeof(pack3)); + strOutput.append((const char *)&uSubjectCNLength, sizeof(uSubjectCNLength)); + strOutput.append(strPaddedSubjectID.data(), strPaddedSubjectID.size()); + strOutput.append((const char *)pack4, sizeof(pack4)); + + return true; +} + +bool SlotParseEntitlements(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_ENTITLEMENTS", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + string strEntitlements = "\t\t\t"; + strEntitlements.append((const char *)pSlotBase + 8, uSlotLength - 8); + PWriter::StringReplace(strEntitlements, "\n", "\n\t\t\t"); + ZLog::PrintV("\tentitlements: \n%s\n", strEntitlements.c_str()); + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Entitlements.slot", (const char *)pSlotBase, uSlotLength); + WriteFile("./.zsign_debug/Entitlements.plist", (const char *)pSlotBase + 8, uSlotLength - 8); + } + return true; +} + +bool SlotParseDerEntitlements(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_DER_ENTITLEMENTS", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/Entitlements.der.slot", (const char *)pSlotBase, uSlotLength); + } + return true; +} + +bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput) +{ + strOutput.clear(); + if (strEntitlements.empty()) + { + return false; + } + + uint32_t uMagic = BE(CSMAGIC_EMBEDDED_ENTITLEMENTS); + uint32_t uLength = BE((uint32_t)strEntitlements.size() + 8); + + strOutput.append((const char *)&uMagic, sizeof(uMagic)); + strOutput.append((const char *)&uLength, sizeof(uLength)); + strOutput.append(strEntitlements.data(), strEntitlements.size()); + + return true; +} + +bool SlotBuildDerEntitlements(const string &strEntitlements, string &strOutput) +{ + strOutput.clear(); + if (strEntitlements.empty()) + { + return false; + } + + JValue jvInfo; + jvInfo.readPList(strEntitlements); + + string strRawEntitlementsData = _DER(jvInfo); + uint32_t uMagic = BE(CSMAGIC_EMBEDDED_DER_ENTITLEMENTS); + uint32_t uLength = BE((uint32_t)strRawEntitlementsData.size() + 8); + + strOutput.append((const char *)&uMagic, sizeof(uMagic)); + strOutput.append((const char *)&uLength, sizeof(uLength)); + strOutput.append(strRawEntitlementsData.data(), strRawEntitlementsData.size()); + + return true; +} + +bool SlotParseCodeDirectory(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_CODEDIRECTORY", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + vector arrCodeSlots; + vector arrSpecialSlots; + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + uint8_t *pHashes = pSlotBase + LE(cdHeader.hashOffset); + for (uint32_t i = 0; i < LE(cdHeader.nCodeSlots); i++) + { + arrCodeSlots.push_back(pHashes + cdHeader.hashSize * i); + } + for (uint32_t i = 0; i < LE(cdHeader.nSpecialSlots); i++) + { + arrSpecialSlots.push_back(pHashes - cdHeader.hashSize * (i + 1)); + } + + ZLog::PrintV("\tversion: \t0x%x\n", LE(cdHeader.version)); + ZLog::PrintV("\tflags: \t\t%u\n", LE(cdHeader.flags)); + ZLog::PrintV("\thashOffset: \t%u\n", LE(cdHeader.hashOffset)); + ZLog::PrintV("\tidentOffset: \t%u\n", LE(cdHeader.identOffset)); + ZLog::PrintV("\tnSpecialSlots: \t%u\n", LE(cdHeader.nSpecialSlots)); + ZLog::PrintV("\tnCodeSlots: \t%u\n", LE(cdHeader.nCodeSlots)); + ZLog::PrintV("\tcodeLimit: \t%u\n", LE(cdHeader.codeLimit)); + ZLog::PrintV("\thashSize: \t%u\n", cdHeader.hashSize); + ZLog::PrintV("\thashType: \t%u\n", cdHeader.hashType); + ZLog::PrintV("\tspare1: \t%u\n", cdHeader.spare1); + ZLog::PrintV("\tpageSize: \t%u\n", cdHeader.pageSize); + ZLog::PrintV("\tspare2: \t%u\n", LE(cdHeader.spare2)); + + uint32_t uVersion = LE(cdHeader.version); + if (uVersion >= 0x20100) + { + ZLog::PrintV("\tscatterOffset: \t%u\n", LE(cdHeader.scatterOffset)); + } + if (uVersion >= 0x20200) + { + ZLog::PrintV("\tteamOffset: \t%u\n", LE(cdHeader.teamOffset)); + } + if (uVersion >= 0x20300) + { + ZLog::PrintV("\tspare3: \t%u\n", LE(cdHeader.spare3)); + ZLog::PrintV("\tcodeLimit64: \t%llu\n", LE(cdHeader.codeLimit64)); + } + if (uVersion >= 0x20400) + { + ZLog::PrintV("\texecSegBase: \t%llu\n", LE(cdHeader.execSegBase)); + ZLog::PrintV("\texecSegLimit: \t%llu\n", LE(cdHeader.execSegLimit)); + ZLog::PrintV("\texecSegFlags: \t%llu\n", LE(cdHeader.execSegFlags)); + } + + ZLog::PrintV("\tidentifier: \t%s\n", pSlotBase + LE(cdHeader.identOffset)); + if (uVersion >= 0x20200) + { + ZLog::PrintV("\tteamid: \t%s\n", pSlotBase + LE(cdHeader.teamOffset)); + } + + ZLog::PrintV("\tSpecialSlots:\n"); + for (int i = LE(cdHeader.nSpecialSlots) - 1; i >= 0; i--) + { + const char *suffix = "\t\n"; + switch (i) + { + case 0: + suffix = "\tInfo.plist\n"; + break; + case 1: + suffix = "\tRequirements Slot\n"; + break; + case 2: + suffix = "\tCodeResources\n"; + break; + case 3: + suffix = "\tApplication Specific\n"; + break; + case 4: + suffix = "\tEntitlements Slot\n"; + break; + case 6: + suffix = "\tEntitlements(DER) Slot\n"; + break; + } + PrintSHASum("\t\t\t", arrSpecialSlots[i], cdHeader.hashSize, suffix); + } + + if (ZLog::IsDebug()) + { + ZLog::Print("\tCodeSlots:\n"); + for (uint32_t i = 0; i < LE(cdHeader.nCodeSlots); i++) + { + PrintSHASum("\t\t\t", arrCodeSlots[i], cdHeader.hashSize); + } + } + else + { + ZLog::Print("\tCodeSlots: \tomitted. (use -d option for details)\n"); + } + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + if (1 == cdHeader.hashType) + { + WriteFile("./.zsign_debug/CodeDirectory_SHA1.slot", (const char *)pSlotBase, uSlotLength); + } + else if (2 == cdHeader.hashType) + { + WriteFile("./.zsign_debug/CodeDirectory_SHA256.slot", (const char *)pSlotBase, uSlotLength); + } + } + + return true; +} + +bool SlotBuildCodeDirectory(bool bAlternate, + uint8_t *pCodeBase, + uint32_t uCodeLength, + uint8_t *pCodeSlotsData, + uint32_t uCodeSlotsDataLength, + uint64_t execSegLimit, + uint64_t execSegFlags, + const string &strBundleId, + const string &strTeamId, + const string &strInfoPlistSHA, + const string &strRequirementsSlotSHA, + const string &strCodeResourcesSHA, + const string &strEntitlementsSlotSHA, + const string &strDerEntitlementsSlotSHA, + bool isExecuteArch, + string &strOutput) +{ + strOutput.clear(); + if (NULL == pCodeBase || uCodeLength <= 0 || strBundleId.empty() || strTeamId.empty()) + { + return false; + } + + uint32_t uVersion = 0x20400; + + CS_CodeDirectory cdHeader; + memset(&cdHeader, 0, sizeof(cdHeader)); + cdHeader.magic = BE(CSMAGIC_CODEDIRECTORY); + cdHeader.length = 0; + cdHeader.version = BE(uVersion); + cdHeader.flags = 0; + cdHeader.hashOffset = 0; + cdHeader.identOffset = 0; + cdHeader.nSpecialSlots = 0; + cdHeader.nCodeSlots = 0; + cdHeader.codeLimit = BE(uCodeLength); + cdHeader.hashSize = bAlternate ? 32 : 20; + cdHeader.hashType = bAlternate ? 2 : 1; + cdHeader.spare1 = 0; + cdHeader.pageSize = 12; + cdHeader.spare2 = 0; + cdHeader.scatterOffset = 0; + cdHeader.teamOffset = 0; + cdHeader.execSegBase = 0; + cdHeader.execSegLimit = BE(execSegLimit); + cdHeader.execSegFlags = BE(execSegFlags); + + string strEmptySHA; + strEmptySHA.append(cdHeader.hashSize, 0); + vector arrSpecialSlots; + + if (isExecuteArch) + { + arrSpecialSlots.push_back(strDerEntitlementsSlotSHA.empty() ? strEmptySHA : strDerEntitlementsSlotSHA); + arrSpecialSlots.push_back(strEmptySHA); + } + arrSpecialSlots.push_back(strEntitlementsSlotSHA.empty() ? strEmptySHA : strEntitlementsSlotSHA); + arrSpecialSlots.push_back(strEmptySHA); + arrSpecialSlots.push_back(strCodeResourcesSHA.empty() ? strEmptySHA : strCodeResourcesSHA); + arrSpecialSlots.push_back(strRequirementsSlotSHA.empty() ? strEmptySHA : strRequirementsSlotSHA); + arrSpecialSlots.push_back(strInfoPlistSHA.empty() ? strEmptySHA : strInfoPlistSHA); + + uint32_t uPageSize = (uint32_t)pow(2, cdHeader.pageSize); + uint32_t uPages = uCodeLength / uPageSize; + uint32_t uRemain = uCodeLength % uPageSize; + uint32_t uCodeSlots = uPages + (uRemain > 0 ? 1 : 0); + + uint32_t uHeaderLength = 44; + if (uVersion >= 0x20100) + { + uHeaderLength += sizeof(cdHeader.scatterOffset); + } + if (uVersion >= 0x20200) + { + uHeaderLength += sizeof(cdHeader.teamOffset); + } + if (uVersion >= 0x20300) + { + uHeaderLength += sizeof(cdHeader.spare3); + uHeaderLength += sizeof(cdHeader.codeLimit64); + } + if (uVersion >= 0x20400) + { + uHeaderLength += sizeof(cdHeader.execSegBase); + uHeaderLength += sizeof(cdHeader.execSegLimit); + uHeaderLength += sizeof(cdHeader.execSegFlags); + } + + uint32_t uBundleIDLength = strBundleId.size() + 1; + uint32_t uTeamIDLength = strTeamId.size() + 1; + uint32_t uSpecialSlotsLength = arrSpecialSlots.size() * cdHeader.hashSize; + uint32_t uCodeSlotsLength = uCodeSlots * cdHeader.hashSize; + + uint32_t uSlotLength = uHeaderLength + uBundleIDLength + uSpecialSlotsLength + uCodeSlotsLength; + if (uVersion >= 0x20100) + { + //todo + } + if (uVersion >= 0x20200) + { + uSlotLength += uTeamIDLength; + } + + cdHeader.length = BE(uSlotLength); + cdHeader.identOffset = BE(uHeaderLength); + cdHeader.nSpecialSlots = BE((uint32_t)arrSpecialSlots.size()); + cdHeader.nCodeSlots = BE(uCodeSlots); + + uint32_t uHashOffset = uHeaderLength + uBundleIDLength + uSpecialSlotsLength; + if (uVersion >= 0x20100) + { + //todo + } + if (uVersion >= 0x20200) + { + uHashOffset += uTeamIDLength; + cdHeader.teamOffset = BE(uHeaderLength + uBundleIDLength); + } + cdHeader.hashOffset = BE(uHashOffset); + + strOutput.append((const char *)&cdHeader, uHeaderLength); + strOutput.append(strBundleId.data(), strBundleId.size() + 1); + if (uVersion >= 0x20100) + { + //todo + } + if (uVersion >= 0x20200) + { + strOutput.append(strTeamId.data(), strTeamId.size() + 1); + } + + for (uint32_t i = 0; i < LE(cdHeader.nSpecialSlots); i++) + { + strOutput.append(arrSpecialSlots[i].data(), arrSpecialSlots[i].size()); + } + + if (NULL != pCodeSlotsData && (uCodeSlotsDataLength == uCodeSlots * cdHeader.hashSize)) + { //use exists + strOutput.append((const char *)pCodeSlotsData, uCodeSlotsDataLength); + } + else + { + for (uint32_t i = 0; i < uPages; i++) + { + string strSHASum; + SHASum(cdHeader.hashType, pCodeBase + uPageSize * i, uPageSize, strSHASum); + strOutput.append(strSHASum.data(), strSHASum.size()); + } + if (uRemain > 0) + { + string strSHASum; + SHASum(cdHeader.hashType, pCodeBase + uPageSize * uPages, uRemain, strSHASum); + strOutput.append(strSHASum.data(), strSHASum.size()); + } + } + + return true; +} + +bool SlotParseCMSSignature(uint8_t *pSlotBase, CS_BlobIndex *pbi) +{ + uint32_t uSlotLength = SlotParseGeneralHeader("CSSLOT_SIGNATURESLOT", pSlotBase, pbi); + if (uSlotLength < 8) + { + return false; + } + + JValue jvInfo; + GetCMSInfo(pSlotBase + 8, uSlotLength - 8, jvInfo); + //ZLog::PrintV("%s\n", jvInfo.styleWrite().c_str()); + + ZLog::Print("\tCertificates: \n"); + for (size_t i = 0; i < jvInfo["certs"].size(); i++) + { + ZLog::PrintV("\t\t\t%s\t<=\t%s\n", jvInfo["certs"][i]["Subject"]["CN"].asCString(), jvInfo["certs"][i]["Issuer"]["CN"].asCString()); + } + + ZLog::Print("\tSignedAttrs: \n"); + if (jvInfo["attrs"].has("ContentType")) + { + ZLog::PrintV("\t ContentType: \t%s => %s\n", jvInfo["attrs"]["ContentType"]["obj"].asCString(), jvInfo["attrs"]["ContentType"]["data"].asCString()); + } + + if (jvInfo["attrs"].has("SigningTime")) + { + ZLog::PrintV("\t SigningTime: \t%s => %s\n", jvInfo["attrs"]["SigningTime"]["obj"].asCString(), jvInfo["attrs"]["SigningTime"]["data"].asCString()); + } + + if (jvInfo["attrs"].has("MessageDigest")) + { + ZLog::PrintV("\t MsgDigest: \t%s => %s\n", jvInfo["attrs"]["MessageDigest"]["obj"].asCString(), jvInfo["attrs"]["MessageDigest"]["data"].asCString()); + } + + if (jvInfo["attrs"].has("CDHashes")) + { + string strData = jvInfo["attrs"]["CDHashes"]["data"].asCString(); + StringReplace(strData, "\n", "\n\t\t\t\t"); + ZLog::PrintV("\t CDHashes: \t%s => \n\t\t\t\t%s\n", jvInfo["attrs"]["CDHashes"]["obj"].asCString(), strData.c_str()); + } + + if (jvInfo["attrs"].has("CDHashes2")) + { + ZLog::PrintV("\t CDHashes2: \t%s => \n", jvInfo["attrs"]["CDHashes2"]["obj"].asCString()); + for (size_t i = 0; i < jvInfo["attrs"]["CDHashes2"]["data"].size(); i++) + { + ZLog::PrintV("\t\t\t\t%s\n", jvInfo["attrs"]["CDHashes2"]["data"][i].asCString()); + } + } + + for (size_t i = 0; i < jvInfo["attrs"]["unknown"].size(); i++) + { + JValue &jvAttr = jvInfo["attrs"]["unknown"][i]; + ZLog::PrintV("\t UnknownAttr: \t%s => %s, type: %d, count: %d\n", jvAttr["obj"].asCString(), jvAttr["name"].asCString(), jvAttr["type"].asInt(), jvAttr["count"].asInt()); + } + ZLog::Print("\n"); + + SlotParseGeneralTailer(pSlotBase, uSlotLength); + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/CMSSignature.slot", (const char *)pSlotBase, uSlotLength); + WriteFile("./.zsign_debug/CMSSignature.der", (const char *)pSlotBase + 8, uSlotLength - 8); + } + return true; +} + +bool SlotBuildCMSSignature(ZSignAsset *pSignAsset, + const string &strCodeDirectorySlot, + const string &strAltnateCodeDirectorySlot, + string &strOutput) +{ + strOutput.clear(); + + JValue jvHashes; + string strCDHashesPlist; + string strCodeDirectorySlotSHA1; + string strAltnateCodeDirectorySlot256; + SHASum(E_SHASUM_TYPE_1, strCodeDirectorySlot, strCodeDirectorySlotSHA1); + SHASum(E_SHASUM_TYPE_256, strAltnateCodeDirectorySlot, strAltnateCodeDirectorySlot256); + + size_t cdHashSize = strCodeDirectorySlotSHA1.size(); + jvHashes["cdhashes"][0].assignData(strCodeDirectorySlotSHA1.data(), cdHashSize); + jvHashes["cdhashes"][1].assignData(strAltnateCodeDirectorySlot256.data(), cdHashSize); + jvHashes.writePList(strCDHashesPlist); + + string strCMSData; + if (!pSignAsset->GenerateCMS(strCodeDirectorySlot, strCDHashesPlist, strCodeDirectorySlotSHA1, strAltnateCodeDirectorySlot256, strCMSData)) + { + return false; + } + + uint32_t uMagic = BE(CSMAGIC_BLOBWRAPPER); + uint32_t uLength = BE((uint32_t)strCMSData.size() + 8); + + strOutput.append((const char *)&uMagic, sizeof(uMagic)); + strOutput.append((const char *)&uLength, sizeof(uLength)); + strOutput.append(strCMSData.data(), strCMSData.size()); + return true; +} + +uint32_t GetCodeSignatureLength(uint8_t *pCSBase) +{ + CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; + if (NULL != psb && CSMAGIC_EMBEDDED_SIGNATURE == LE(psb->magic)) + { + return LE(psb->length); + } + return 0; +} + +bool ParseCodeSignature(uint8_t *pCSBase) +{ + CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; + if (NULL == psb || CSMAGIC_EMBEDDED_SIGNATURE != LE(psb->magic)) + { + return false; + } + + ZLog::PrintV("\n>>> CodeSignature Segment: \n"); + ZLog::PrintV("\tmagic: \t\t0x%x\n", LE(psb->magic)); + ZLog::PrintV("\tlength: \t%d\n", LE(psb->length)); + ZLog::PrintV("\tslots: \t\t%d\n", LE(psb->count)); + + CS_BlobIndex *pbi = (CS_BlobIndex *)(pCSBase + sizeof(CS_SuperBlob)); + for (uint32_t i = 0; i < LE(psb->count); i++, pbi++) + { + uint8_t *pSlotBase = pCSBase + LE(pbi->offset); + switch (LE(pbi->type)) + { + case CSSLOT_CODEDIRECTORY: + SlotParseCodeDirectory(pSlotBase, pbi); + break; + case CSSLOT_REQUIREMENTS: + SlotParseRequirements(pSlotBase, pbi); + break; + case CSSLOT_ENTITLEMENTS: + SlotParseEntitlements(pSlotBase, pbi); + break; + case CSSLOT_DER_ENTITLEMENTS: + SlotParseDerEntitlements(pSlotBase, pbi); + break; + case CSSLOT_ALTERNATE_CODEDIRECTORIES: + SlotParseCodeDirectory(pSlotBase, pbi); + break; + case CSSLOT_SIGNATURESLOT: + SlotParseCMSSignature(pSlotBase, pbi); + break; + case CSSLOT_IDENTIFICATIONSLOT: + SlotParseGeneralHeader("CSSLOT_IDENTIFICATIONSLOT", pSlotBase, pbi); + break; + case CSSLOT_TICKETSLOT: + SlotParseGeneralHeader("CSSLOT_TICKETSLOT", pSlotBase, pbi); + break; + default: + SlotParseGeneralTailer(pSlotBase, SlotParseGeneralHeader("CSSLOT_UNKNOWN", pSlotBase, pbi)); + break; + } + } + + if (ZLog::IsDebug()) + { + WriteFile("./.zsign_debug/CodeSignature.blob", (const char *)pCSBase, LE(psb->length)); + } + return true; +} + +bool SlotGetCodeSlotsData(uint8_t *pSlotBase, uint8_t *&pCodeSlots, uint32_t &uCodeSlotsLength) +{ + uint32_t uSlotLength = LE(*(((uint32_t *)pSlotBase) + 1)); + if (uSlotLength < 8) + { + return false; + } + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + pCodeSlots = pSlotBase + LE(cdHeader.hashOffset); + uCodeSlotsLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; + return true; +} + +bool GetCodeSignatureExistsCodeSlotsData(uint8_t *pCSBase, + uint8_t *&pCodeSlots1Data, + uint32_t &uCodeSlots1DataLength, + uint8_t *&pCodeSlots256Data, + uint32_t &uCodeSlots256DataLength) +{ + pCodeSlots1Data = NULL; + pCodeSlots256Data = NULL; + uCodeSlots1DataLength = 0; + uCodeSlots256DataLength = 0; + CS_SuperBlob *psb = (CS_SuperBlob *)pCSBase; + if (NULL == psb || CSMAGIC_EMBEDDED_SIGNATURE != LE(psb->magic)) + { + return false; + } + + CS_BlobIndex *pbi = (CS_BlobIndex *)(pCSBase + sizeof(CS_SuperBlob)); + for (uint32_t i = 0; i < LE(psb->count); i++, pbi++) + { + uint8_t *pSlotBase = pCSBase + LE(pbi->offset); + switch (LE(pbi->type)) + { + case CSSLOT_CODEDIRECTORY: + { + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + if (LE(cdHeader.length) > 8) + { + pCodeSlots1Data = pSlotBase + LE(cdHeader.hashOffset); + uCodeSlots1DataLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; + } + } + break; + case CSSLOT_ALTERNATE_CODEDIRECTORIES: + { + CS_CodeDirectory cdHeader = *((CS_CodeDirectory *)pSlotBase); + if (LE(cdHeader.length) > 8) + { + pCodeSlots256Data = pSlotBase + LE(cdHeader.hashOffset); + uCodeSlots256DataLength = LE(cdHeader.nCodeSlots) * cdHeader.hashSize; + } + } + break; + default: + break; + } + } + + return ((NULL != pCodeSlots1Data) && (NULL != pCodeSlots256Data) && uCodeSlots1DataLength > 0 && uCodeSlots256DataLength > 0); +} diff --git a/ZSign/signing.h b/ZSign/signing.h new file mode 100644 index 0000000..df026fc --- /dev/null +++ b/ZSign/signing.h @@ -0,0 +1,34 @@ +#pragma once +#include "openssl.h" + +bool ParseCodeSignature(uint8_t *pCSBase); +bool SlotBuildEntitlements(const string &strEntitlements, string &strOutput); +bool SlotBuildDerEntitlements(const string &strEntitlements, string &strOutput); +bool SlotBuildRequirements(const string &strBundleID, const string &strSubjectCN, string &strOutput); +bool GetCodeSignatureCodeSlotsData(uint8_t *pCSBase, uint8_t *&pCodeSlots1, uint32_t &uCodeSlots1Length, uint8_t *&pCodeSlots256, uint32_t &uCodeSlots256Length); +bool SlotBuildCodeDirectory(bool bAlternate, + uint8_t *pCodeBase, + uint32_t uCodeLength, + uint8_t *pCodeSlotsData, + uint32_t uCodeSlotsDataLength, + uint64_t execSegLimit, + uint64_t execSegFlags, + const string &strBundleId, + const string &strTeamId, + const string &strInfoPlistSHA, + const string &strRequirementsSlotSHA, + const string &strCodeResourcesSHA, + const string &strEntitlementsSlotSHA, + const string &strDerEntitlementsSlotSHA, + bool isExecuteArch, + string &strOutput); +bool SlotBuildCMSSignature(ZSignAsset *pSignAsset, + const string &strCodeDirectorySlot, + const string &strAltnateCodeDirectorySlot, + string &strOutput); +bool GetCodeSignatureExistsCodeSlotsData(uint8_t *pCSBase, + uint8_t *&pCodeSlots1Data, + uint32_t &uCodeSlots1DataLength, + uint8_t *&pCodeSlots256Data, + uint32_t &uCodeSlots256DataLength); +uint32_t GetCodeSignatureLength(uint8_t *pCSBase); diff --git a/ZSign/zsign.hpp b/ZSign/zsign.hpp new file mode 100644 index 0000000..e3446c2 --- /dev/null +++ b/ZSign/zsign.hpp @@ -0,0 +1,44 @@ +// +// zsign.hpp +// feather +// +// Created by HAHALOSAH on 5/22/24. +// + +#ifndef zsign_hpp +#define zsign_hpp + +#include +#import + +#ifdef __cplusplus +extern "C" { +#endif + +bool InjectDyLib(NSString *filePath, + NSString *dylibPath, + bool weakInject, + bool bCreate); + +bool ChangeDylibPath(NSString *filePath, + NSString *oldPath, + NSString *newPath); + +bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray); +bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray); + +void zsign(NSString *appPath, + NSString* execName, + NSData *prov, + NSData *key, + NSString *pass, + NSProgress* progress, + void(^completionHandler)(BOOL success, NSError *error) + ); + + +#ifdef __cplusplus +} +#endif + +#endif /* zsign_hpp */ diff --git a/ZSign/zsign.mm b/ZSign/zsign.mm new file mode 100644 index 0000000..62b055e --- /dev/null +++ b/ZSign/zsign.mm @@ -0,0 +1,232 @@ +#include "zsign.hpp" +#include "common/common.h" +#include "common/json.h" +#include "openssl.h" +#include "macho.h" +#include "bundle.h" +#include +#include +#include +#include + +NSString* getTmpDir() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + return [[[paths objectAtIndex:0] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"tmp"]; +} + +extern "C" { + +bool InjectDyLib(NSString *filePath, NSString *dylibPath, bool weakInject, bool bCreate) { + ZTimer gtimer; + @autoreleasepool { + // Convert NSString to std::string + std::string filePathStr = [filePath UTF8String]; + std::string dylibPathStr = [dylibPath UTF8String]; + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + bool success = machO.InjectDyLib(weakInject, dylibPathStr.c_str(), bCreate); + + machO.Free(); + + if (success) { + gtimer.Print(">>> Dylib injected successfully!"); + return true; + } else { + gtimer.Print(">>> Failed to inject dylib."); + return false; + } + } +} + +bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray) { + ZTimer gtimer; + @autoreleasepool { + // Convert NSString to std::string + std::string filePathStr = [filePath UTF8String]; + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + std::vector dylibPaths = machO.ListDylibs(); + + if (!dylibPaths.empty()) { + gtimer.Print(">>> List of dylibs in the Mach-O file:"); + for (vector::iterator it = dylibPaths.begin(); it < dylibPaths.end(); ++it) { + std::string dylibPath = *it; + NSString *dylibPathStr = [NSString stringWithUTF8String:dylibPath.c_str()]; + [dylibPathsArray addObject:dylibPathStr]; + } + } else { + gtimer.Print(">>> No dylibs found in the Mach-O file."); + } + + machO.Free(); + + return true; + } +} + +bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray) { + ZTimer gtimer; + @autoreleasepool { + std::string filePathStr = [filePath UTF8String]; + std::set dylibsToRemove; + + for (NSString *dylibPath in dylibPathsArray) { + dylibsToRemove.insert([dylibPath UTF8String]); + } + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + machO.RemoveDylib(dylibsToRemove); + + machO.Free(); + + gtimer.Print(">>> Dylibs uninstalled successfully!"); + return true; + } +} + + + +bool ChangeDylibPath(NSString *filePath, NSString *oldPath, NSString *newPath) { + ZTimer gtimer; + @autoreleasepool { + // Convert NSString to std::string + std::string filePathStr = [filePath UTF8String]; + std::string oldPathStr = [oldPath UTF8String]; + std::string newPathStr = [newPath UTF8String]; + + ZMachO machO; + bool initSuccess = machO.Init(filePathStr.c_str()); + if (!initSuccess) { + gtimer.Print(">>> Failed to initialize ZMachO."); + return false; + } + + bool success = machO.ChangeDylibPath(oldPathStr.c_str(), newPathStr.c_str()); + + machO.Free(); + + if (success) { + gtimer.Print(">>> Dylib path changed successfully!"); + return true; + } else { + gtimer.Print(">>> Failed to change dylib path."); + return false; + } + } +} + +NSError* makeErrorFromLog(const std::vector& vec) { + NSMutableString *result = [NSMutableString string]; + + for (size_t i = 0; i < vec.size(); ++i) { + // Convert each std::string to NSString + NSString *str = [NSString stringWithUTF8String:vec[i].c_str()]; + [result appendString:str]; + + // Append newline if it's not the last element + if (i != vec.size() - 1) { + [result appendString:@"\n"]; + } + } + + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : result + }; + return [NSError errorWithDomain:@"Failed to Sign" code:-1 userInfo:userInfo]; +} + +ZSignAsset zSignAsset; + +void zsign(NSString *appPath, + NSString* execName, + NSData *prov, + NSData *key, + NSString *pass, + NSProgress* progress, + void(^completionHandler)(BOOL success, NSError *error) + ) +{ + ZTimer gtimer; + ZTimer timer; + timer.Reset(); + + bool bForce = false; + bool bWeakInject = false; + bool bDontGenerateEmbeddedMobileProvision = YES; + + string strPassword; + + string strDyLibFile; + string strOutputFile; + + string strEntitlementsFile; + + bForce = true; + const char* strPKeyFileData = (const char*)[key bytes]; + const char* strProvFileData = (const char*)[prov bytes]; + strPassword = [pass cStringUsingEncoding:NSUTF8StringEncoding]; + + + string strPath = [appPath cStringUsingEncoding:NSUTF8StringEncoding]; + string execNameStr = [execName cStringUsingEncoding:NSUTF8StringEncoding]; + + bool _ = ZLog::logs.empty(); + + __block ZSignAsset zSignAsset; + + if (!zSignAsset.InitSimple(strPKeyFileData, (int)[key length], strProvFileData, (int)[prov length], strPassword)) { + completionHandler(NO, makeErrorFromLog(ZLog::logs)); + bool _ = ZLog::logs.empty(); + return; + } + + bool bEnableCache = true; + string strFolder = strPath; + + __block ZAppBundle bundle; + bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, execNameStr, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); + + if(!success) { + completionHandler(NO, makeErrorFromLog(ZLog::logs)); + bool _ = ZLog::logs.empty(); + return; + } + + int filesNeedToSign = bundle.GetSignCount(); + [progress setTotalUnitCount:filesNeedToSign]; + bundle.progressHandler = [&progress] { + [progress setCompletedUnitCount:progress.completedUnitCount + 1]; + }; + + + + + ZLog::PrintV(">>> Files Need to Sign: \t%d\n", filesNeedToSign); + bool bRet = bundle.StartSign(bEnableCache); + timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed"); + gtimer.Print(">>> Done."); + completionHandler(YES, nil); + _ = ZLog::logs.empty(); + + return; +} + +} diff --git a/ZSign/zsigner.h b/ZSign/zsigner.h new file mode 100644 index 0000000..8c5ce4d --- /dev/null +++ b/ZSign/zsigner.h @@ -0,0 +1,11 @@ +// +// zsigner.h +// LiveContainer +// +// Created by s s on 2024/11/10. +// +#import + +@interface ZSigner : NSObject ++ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; +@end diff --git a/ZSign/zsigner.m b/ZSign/zsigner.m new file mode 100644 index 0000000..ffc7def --- /dev/null +++ b/ZSign/zsigner.m @@ -0,0 +1,22 @@ +// +// zsigner.m +// LiveContainer +// +// Created by s s on 2024/11/10. +// + +#import "zsigner.h" +#import "zsign.hpp" + +NSProgress* currentZSignProgress; + +@implementation ZSigner ++ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { + NSProgress* ans = [NSProgress progressWithTotalUnitCount:1000]; + NSLog(@"[LC] init sign!"); + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + zsign(appPath, execName, prov, key, pass, ans, completionHandler); + }); + return ans; +} +@end From b4d6bdb6d1512f556b44f42a5ba2cf097ab64993 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 23 Nov 2024 19:53:25 +0800 Subject: [PATCH 06/32] fix zsign merge issues --- LiveContainerSwiftUI/LCAppInfo.m | 2 +- LiveContainerSwiftUI/LCSettingsView.swift | 3 --- LiveContainerSwiftUI/LCUtils.h | 2 +- LiveContainerSwiftUI/LCUtils.m | 4 ++-- LiveContainerSwiftUI/Localizable.xcstrings | 17 +++++++++++++++++ Resources/Info.plist | 4 ++-- ZSign/openssl.cpp | 9 +-------- ZSign/openssl.h | 1 + ZSign/zsign.hpp | 2 +- ZSign/zsign.mm | 11 ++++++----- ZSign/zsigner.h | 2 +- ZSign/zsigner.m | 3 +-- control | 2 +- 13 files changed, 35 insertions(+), 27 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index f16517f..3112d87 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -220,7 +220,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo int signRevision = 1; NSDate* expirationDate = info[@"LCExpirationDate"]; - if(expirationDate && [[[NSUserDefaults alloc] initWithSuiteName:[LCUtils appGroupID]] boolForKey:@"LCSignOnlyOnExpiration"]) { + if(expirationDate && [[[NSUserDefaults alloc] initWithSuiteName:[LCUtils appGroupID]] boolForKey:@"LCSignOnlyOnExpiration"] && !forceSign) { if([expirationDate laterDate:[NSDate now]] == expirationDate) { // not expired yet, don't sign again completetionHandler(nil); diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index a1d56c7..cb132f5 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -38,15 +38,12 @@ struct LCSettingsView: View { @State var sideJITServerAddress : String @State var deviceUDID: String - @State var isSideStore : Bool = true @State var isSideStore : Bool = true @EnvironmentObject private var sharedModel : SharedModel let storeName = LCUtils.getStoreName() - let storeName = LCUtils.getStoreName() - init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>) { _isJitLessEnabled = State(initialValue: LCUtils.certificatePassword() != nil) diff --git a/LiveContainerSwiftUI/LCUtils.h b/LiveContainerSwiftUI/LCUtils.h index 031d171..1c4f3bb 100644 --- a/LiveContainerSwiftUI/LCUtils.h +++ b/LiveContainerSwiftUI/LCUtils.h @@ -40,7 +40,7 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL; + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; -+ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; + (BOOL)isAppGroupAltStoreLike; + (Store)store; + (NSString *)appGroupID; diff --git a/LiveContainerSwiftUI/LCUtils.m b/LiveContainerSwiftUI/LCUtils.m index 4e3af82..3eae0da 100644 --- a/LiveContainerSwiftUI/LCUtils.m +++ b/LiveContainerSwiftUI/LCUtils.m @@ -224,7 +224,7 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc return [signer signAppAtURL:path provisioningProfiles:@[(id)profile] completionHandler:signCompletionHandler]; } -+ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { NSError *error; // use zsign as our signer~ @@ -234,7 +234,7 @@ + (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execNam [self loadStoreFrameworksWithError2:&error]; if (error) { - completionHandler(NO, error); + completionHandler(NO, nil, error); return nil; } diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index c101ef0..3c5cfd9 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -2190,6 +2190,23 @@ } } }, + "lc.settings.signer.desc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If ZSign reports an error, please try AltSign. Please restart LiveContainer after switching Signer." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "若ZSign签名遇到问题,可尝试使用AltSign。切换签名工具后请重启LiveContainer。" + } + } + } + }, "lc.settings.signOnlyOnExpiration" : { "extractionState" : "manual", "localizations" : { diff --git a/Resources/Info.plist b/Resources/Info.plist index a7e9d2c..851a8f5 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -42,7 +42,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.0.0 + 3.1.0 CFBundleSignature ???? CFBundleSupportedPlatforms @@ -61,7 +61,7 @@ CFBundleVersion - 3.0.0 + 3.1.0 LSApplicationCategoryType public.app-category.games LSApplicationQueriesSchemes diff --git a/ZSign/openssl.cpp b/ZSign/openssl.cpp index 7147b06..439a263 100644 --- a/ZSign/openssl.cpp +++ b/ZSign/openssl.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include @@ -843,6 +842,7 @@ bool ZSignAsset::InitSimple(const void* strSignerPKeyData, int strSignerPKeyData { jvProv["Entitlements"].writePList(m_strEntitlementsData); } + expirationDate = jvProv["ExpirationDate"].asDate(); } } @@ -854,14 +854,7 @@ bool ZSignAsset::InitSimple(const void* strSignerPKeyData, int strSignerPKeyData X509 *x509Cert = NULL; EVP_PKEY *evpPKey = NULL; - - // BIO *bioPKey = BIO_new_file(strSignerPKeyFile.c_str(), "r"); - char* digest = new char[16]; - MD5((unsigned char*) strSignerPKeyData, strSignerPKeyDataSize, (unsigned char*)digest); - ZLog::Error(binary_to_hex(digest, 16).data()); - delete[] digest; - BIO *bioPKey = BIO_new_mem_buf(strSignerPKeyData, strSignerPKeyDataSize); if (NULL != bioPKey) { diff --git a/ZSign/openssl.h b/ZSign/openssl.h index 6e4dfec..4a94f1b 100644 --- a/ZSign/openssl.h +++ b/ZSign/openssl.h @@ -21,6 +21,7 @@ class ZSignAsset string m_strSubjectCN; string m_strProvisionData; string m_strEntitlementsData; + time_t expirationDate; private: void *m_evpPKey; diff --git a/ZSign/zsign.hpp b/ZSign/zsign.hpp index e3446c2..e8366a0 100644 --- a/ZSign/zsign.hpp +++ b/ZSign/zsign.hpp @@ -33,7 +33,7 @@ void zsign(NSString *appPath, NSData *key, NSString *pass, NSProgress* progress, - void(^completionHandler)(BOOL success, NSError *error) + void(^completionHandler)(BOOL success, NSDate* expirationDate, NSError *error) ); diff --git a/ZSign/zsign.mm b/ZSign/zsign.mm index 62b055e..00fd709 100644 --- a/ZSign/zsign.mm +++ b/ZSign/zsign.mm @@ -161,7 +161,7 @@ void zsign(NSString *appPath, NSData *key, NSString *pass, NSProgress* progress, - void(^completionHandler)(BOOL success, NSError *error) + void(^completionHandler)(BOOL success, NSDate* expirationDate, NSError *error) ) { ZTimer gtimer; @@ -193,11 +193,12 @@ void zsign(NSString *appPath, __block ZSignAsset zSignAsset; if (!zSignAsset.InitSimple(strPKeyFileData, (int)[key length], strProvFileData, (int)[prov length], strPassword)) { - completionHandler(NO, makeErrorFromLog(ZLog::logs)); + completionHandler(NO, nil, makeErrorFromLog(ZLog::logs)); bool _ = ZLog::logs.empty(); return; } - + NSDate *date = [NSDate dateWithTimeIntervalSince1970:zSignAsset.expirationDate]; + bool bEnableCache = true; string strFolder = strPath; @@ -205,7 +206,7 @@ void zsign(NSString *appPath, bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, execNameStr, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); if(!success) { - completionHandler(NO, makeErrorFromLog(ZLog::logs)); + completionHandler(NO, nil, makeErrorFromLog(ZLog::logs)); bool _ = ZLog::logs.empty(); return; } @@ -223,7 +224,7 @@ void zsign(NSString *appPath, bool bRet = bundle.StartSign(bEnableCache); timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed"); gtimer.Print(">>> Done."); - completionHandler(YES, nil); + completionHandler(YES, date, nil); _ = ZLog::logs.empty(); return; diff --git a/ZSign/zsigner.h b/ZSign/zsigner.h index 8c5ce4d..c3597be 100644 --- a/ZSign/zsigner.h +++ b/ZSign/zsigner.h @@ -7,5 +7,5 @@ #import @interface ZSigner : NSObject -+ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; ++ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; @end diff --git a/ZSign/zsigner.m b/ZSign/zsigner.m index ffc7def..1e78a6e 100644 --- a/ZSign/zsigner.m +++ b/ZSign/zsigner.m @@ -11,9 +11,8 @@ NSProgress* currentZSignProgress; @implementation ZSigner -+ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { ++ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { NSProgress* ans = [NSProgress progressWithTotalUnitCount:1000]; - NSLog(@"[LC] init sign!"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ zsign(appPath, execName, prov, key, pass, ans, completionHandler); }); diff --git a/control b/control index 846835f..51d3489 100644 --- a/control +++ b/control @@ -1,6 +1,6 @@ Package: com.kdt.livecontainer Name: livecontainer -Version: 3.0.0 +Version: 3.1.0 Architecture: iphoneos-arm Description: Run iOS app without actually installing it! Maintainer: khanhduytran0 From 2510ea5a6939ef3d4b03b84b28083f4b0ab2463f Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 23 Nov 2024 20:34:13 +0800 Subject: [PATCH 07/32] fix app don't show up when signing failed --- LiveContainerSwiftUI/LCAppListView.swift | 7 +++++-- LiveContainerSwiftUI/LCAppModel.swift | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 7e7e9b0..196442d 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -451,12 +451,13 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { let finalNewApp = LCAppInfo(bundlePath: outputFolder.path) finalNewApp?.relativeBundlePath = appRelativePath - // patch it guard let finalNewApp else { errorInfo = "lc.appList.appInfoInitError".loc errorShow = true return } + + // patch and sign it var signError : String? = nil await withCheckedContinuation({ c in finalNewApp.signer = Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))! @@ -468,8 +469,10 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { }, forceSign: false) }) + // we leave it unsigned even if signing failed if let signError { - throw signError + errorInfo = signError + errorShow = true } if let appToReplace { diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index daa9e2d..78b0e93 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -90,7 +90,10 @@ class LCAppModel: ObservableObject, Hashable { } isAppRunning = true defer { - isAppRunning = false + DispatchQueue.main.async { + self.isAppRunning = false + } + } try await signApp(force: true) } From 962e0ba318558dc76176ce98b659cf60fce032c3 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Mon, 25 Nov 2024 14:07:28 +0800 Subject: [PATCH 08/32] maybe --- LCSharedUtils.m | 61 ------------- TweakLoader/Makefile | 2 +- TweakLoader/NSUserDefaults.m | 169 +++++++++++++++++++++++++++++++++++ UIKitPrivate.h | 1 + main.m | 14 +-- 5 files changed, 172 insertions(+), 75 deletions(-) create mode 100644 TweakLoader/NSUserDefaults.m diff --git a/LCSharedUtils.m b/LCSharedUtils.m index c098976..3005917 100644 --- a/LCSharedUtils.m +++ b/LCSharedUtils.m @@ -181,67 +181,6 @@ + (void)removeAppRunningByLC:(NSString*)LCScheme { } -// move all plists file from fromPath to toPath -+ (void)movePreferencesFromPath:(NSString*) plistLocationFrom toPath:(NSString*)plistLocationTo { - NSFileManager* fm = [[NSFileManager alloc] init]; - NSError* error1; - NSArray * plists = [fm contentsOfDirectoryAtPath:plistLocationFrom error:&error1]; - - // remove all plists in toPath first - NSArray *directoryContents = [fm contentsOfDirectoryAtPath:plistLocationTo error:&error1]; - for (NSString *item in directoryContents) { - // Check if the item is a plist and does not contain "LiveContainer" - if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) { - continue; - } - NSString *itemPath = [plistLocationTo stringByAppendingPathComponent:item]; - // Attempt to delete the file - [fm removeItemAtPath:itemPath error:&error1]; - } - - [fm createDirectoryAtPath:plistLocationTo withIntermediateDirectories:YES attributes:@{} error:&error1]; - // move all plists in fromPath to toPath - for (NSString* item in plists) { - if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) { - continue; - } - NSString* toPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationTo, item]; - NSString* fromPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationFrom, item]; - - [fm moveItemAtPath:fromPlistPath toPath:toPlistPath error:&error1]; - if(error1) { - NSLog(@"[LC] error1 = %@", error1.description); - } - - } - -} - -// to make apple happy and prevent, we have to load all preferences into NSUserDefault so that guest app can read them -+ (void)loadPreferencesFromPath:(NSString*) plistLocationFrom { - NSFileManager* fm = [[NSFileManager alloc] init]; - NSError* error1; - NSArray * plists = [fm contentsOfDirectoryAtPath:plistLocationFrom error:&error1]; - - // move all plists in fromPath to toPath - for (NSString* item in plists) { - if(![item hasSuffix:@".plist"] || [item containsString:@"livecontainer"]) { - continue; - } - NSString* fromPlistPath = [NSString stringWithFormat:@"%@/%@", plistLocationFrom, item]; - // load, the file and sync - NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithContentsOfFile:fromPlistPath]; - NSUserDefaults* nud = [[NSUserDefaults alloc] initWithSuiteName: [item substringToIndex:[item length]-6]]; - for(NSString* key in dict) { - [nud setObject:dict[key] forKey:key]; - } - - [nud synchronize]; - - } - -} - // move app data to private folder to prevent 0xdead10cc https://forums.developer.apple.com/forums/thread/126438 + (void)moveSharedAppFolderBack { NSFileManager *fm = NSFileManager.defaultManager; diff --git a/TweakLoader/Makefile b/TweakLoader/Makefile index 6feec1c..e7c665e 100644 --- a/TweakLoader/Makefile +++ b/TweakLoader/Makefile @@ -6,7 +6,7 @@ include $(THEOS)/makefiles/common.mk LIBRARY_NAME = TweakLoader -TweakLoader_FILES = TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m +TweakLoader_FILES = NSUserDefaults.m TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m TweakLoader_CFLAGS = -objc-arc TweakLoader_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks diff --git a/TweakLoader/NSUserDefaults.m b/TweakLoader/NSUserDefaults.m new file mode 100644 index 0000000..58757a3 --- /dev/null +++ b/TweakLoader/NSUserDefaults.m @@ -0,0 +1,169 @@ +// +// NSUserDefaults.m +// LiveContainer +// +// Created by s s on 2024/11/23. +// + +#import +#import "LCSharedUtils.h" +#import "UIKitPrivate.h" +#import "utils.h" +#import "LCSharedUtils.h" + +NSMutableDictionary* LCPreferences = 0; + +__attribute__((constructor)) +static void UIKitGuestHooksInit() { + NSLog(@"[LC] hook init"); + swizzle(NSUserDefaults.class, @selector(objectForKey:), @selector(hook_objectForKey:)); + swizzle(NSUserDefaults.class, @selector(boolForKey:), @selector(hook_boolForKey:)); + swizzle(NSUserDefaults.class, @selector(integerForKey:), @selector(hook_integerForKey:)); + swizzle(NSUserDefaults.class, @selector(setObject:forKey:), @selector(hook_setObject:forKey:)); + swizzle(NSUserDefaults.class, @selector(removeObjectForKey:), @selector(hook_removeObjectForKey:)); + swizzle(NSUserDefaults.class, @selector(dictionaryRepresentation), @selector(hook_dictionaryRepresentation)); + swizzle(NSUserDefaults.class, @selector(persistentDomainForName:), @selector(hook_persistentDomainForName:)); + swizzle(NSUserDefaults.class, @selector(removePersistentDomainForName:), @selector(hook_removePersistentDomainForName:)); + LCPreferences = [[NSMutableDictionary alloc] init]; + NSFileManager* fm = NSFileManager.defaultManager; + NSURL* libraryPath = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; + NSURL* preferenceFolderPath = [libraryPath URLByAppendingPathComponent:@"Preferences"]; + if(![fm fileExistsAtPath:preferenceFolderPath.path]) { + NSError* error; + [fm createDirectoryAtPath:preferenceFolderPath.path withIntermediateDirectories:YES attributes:@{} error:&error]; + } + +} + +NSURL* LCGetPreferencePath(NSString* identifier) { + NSFileManager* fm = NSFileManager.defaultManager; + NSURL* libraryPath = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; + NSURL* preferenceFilePath = [libraryPath URLByAppendingPathComponent:[NSString stringWithFormat: @"Preferences/%@.plist", identifier]]; + return preferenceFilePath; +} + +NSMutableDictionary* LCGetPreference(NSString* identifier) { + if(LCPreferences[identifier]) { + return LCPreferences[identifier]; + } + NSURL* preferenceFilePath = LCGetPreferencePath(identifier); + if([NSFileManager.defaultManager fileExistsAtPath:preferenceFilePath.path]) { + LCPreferences[identifier] = [NSMutableDictionary dictionaryWithContentsOfFile:preferenceFilePath.path]; + } else { + LCPreferences[identifier] = [[NSMutableDictionary alloc] init]; + } + return LCPreferences[identifier]; +} + + + +@implementation NSUserDefaults(LiveContainerHooks) + +- (id)hook_objectForKey:(NSString*)key { + // let LiveContainer itself and Apple stuff bypass + NSString* identifier = [self _identifier]; + NSLog(@"[LC] hook_objectForKey key = %@, identifier = %@", key, identifier); + id ans = [self hook_objectForKey:key]; + if(ans || [identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { + return ans; + } + NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + return preferenceDict[key]; +} + +- (BOOL)hook_boolForKey:(NSString*)key { + id obj = [self objectForKey:key]; + + if ([obj isKindOfClass:[NSNumber class]]) { + return [(NSNumber*)obj boolValue]; + } else if([obj isKindOfClass:[NSString class]]) { + if([[(NSString*)obj lowercaseString] isEqualToString:@"yes"] || [[(NSString*)obj lowercaseString] isEqualToString:@"true"]) { + return YES; + } else { + return NO; + } + } else { + return obj != 0; + } + +} + +- (NSInteger)hook_integerForKey:(NSString*)key { + id obj = [self objectForKey:key]; + if([obj isKindOfClass:[NSString class]]) { + return [(NSString*)obj integerValue]; + } else if ([obj isKindOfClass:[NSNumber class]]) { + return [(NSNumber*)obj integerValue]; + } + return 0; +} + +- (void)hook_setObject:(id)obj forKey:(NSString*)key { + // let apple bypass + NSString* identifier = [self _identifier]; + NSLog(@"[LC] hook_setObjectForKey key = %@, identifier = %@", key, identifier); + if([self hook_objectForKey:key]) { + [self hook_setObject:obj forKey:key]; + return; + } + + NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + preferenceDict[key] = obj; + NSURL* preferenceFilePath = LCGetPreferencePath(identifier); + [preferenceDict writeToURL:preferenceFilePath atomically:YES]; + + +} + +- (void)hook_removeObjectForKey:(NSString*)key { + NSString* identifier = [self _identifier]; + if([self hook_objectForKey:key]) { + [self hook_removeObjectForKey:key]; + return; + } + NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + [preferenceDict removeObjectForKey:key]; + NSURL* preferenceFilePath = LCGetPreferencePath(identifier); + [preferenceDict writeToURL:preferenceFilePath atomically:YES]; +} + +- (NSDictionary*) hook_dictionaryRepresentation { + NSString* identifier = [self _identifier]; + NSMutableDictionary* ans = [[self hook_dictionaryRepresentation] mutableCopy]; + if(ans) { + [ans addEntriesFromDictionary:LCGetPreference(identifier)]; + } else { + ans = LCGetPreference(identifier); + } + return ans; + +} + +- (NSDictionary*) hook_persistentDomainForName:(NSString*)domainName { + NSMutableDictionary* ans = [[self hook_persistentDomainForName:domainName] mutableCopy]; + if(ans) { + [ans addEntriesFromDictionary:LCGetPreference(domainName)]; + } else { + ans = LCGetPreference(domainName); + } + return ans; + +} + +- (void) hook_removePersistentDomainForName:(NSString*)domainName { + NSMutableDictionary* ans = [[self hook_persistentDomainForName:domainName] mutableCopy]; + if(ans) { + [self hook_removePersistentDomainForName:domainName]; + } else { + [LCPreferences removeObjectForKey:domainName]; + } + NSURL* preferenceFilePath = LCGetPreferencePath(domainName); + NSFileManager* fm = NSFileManager.defaultManager; + if([fm fileExistsAtPath:preferenceFilePath.path]) { + NSError* error; + [fm removeItemAtURL:preferenceFilePath error:&error]; + } + +} + +@end diff --git a/UIKitPrivate.h b/UIKitPrivate.h index 22613e3..1580e57 100644 --- a/UIKitPrivate.h +++ b/UIKitPrivate.h @@ -6,6 +6,7 @@ @interface NSUserDefaults(private) + (void)setStandardUserDefaults:(id)defaults; +- (NSString*)_identifier; @end @interface UIImage(private) diff --git a/main.m b/main.m index 1c246fd..13bc485 100644 --- a/main.m +++ b/main.m @@ -342,7 +342,7 @@ static void overwriteExecPath(NSString *bundlePath) { NSString *dirPath = [newHomePath stringByAppendingPathComponent:dir]; [fm createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil]; } - [LCSharedUtils loadPreferencesFromPath:[newHomePath stringByAppendingPathComponent:@"Library/Preferences"]]; + [lcUserDefaults setObject:dataUUID forKey:@"lastLaunchDataUUID"]; if(isSharedBundle) { [lcUserDefaults setObject:@"Shared" forKey:@"lastLaunchType"]; @@ -419,21 +419,9 @@ int LiveContainerMain(int argc, char *argv[]) { lcAppUrlScheme = NSBundle.mainBundle.infoDictionary[@"CFBundleURLTypes"][0][@"CFBundleURLSchemes"][0]; lcAppGroupPath = [[NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[NSClassFromString(@"LCSharedUtils") appGroupID]] path]; lcMainBundle = [NSBundle mainBundle]; - // move preferences first then the entire folder - NSString* lastLaunchDataUUID = [lcUserDefaults objectForKey:@"lastLaunchDataUUID"]; if(lastLaunchDataUUID) { - NSString* lastLaunchType = [lcUserDefaults objectForKey:@"lastLaunchType"]; - NSString* preferencesTo; - NSURL *libraryPathUrl = [NSFileManager.defaultManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; - NSURL *docPathUrl = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject; - if([lastLaunchType isEqualToString:@"Shared"]) { - preferencesTo = [libraryPathUrl.path stringByAppendingPathComponent:[NSString stringWithFormat:@"SharedDocuments/%@/Library/Preferences", lastLaunchDataUUID]]; - } else { - preferencesTo = [docPathUrl.path stringByAppendingPathComponent:[NSString stringWithFormat:@"Data/Application/%@/Library/Preferences", lastLaunchDataUUID]]; - } - [LCSharedUtils movePreferencesFromPath:[NSString stringWithFormat:@"%@/Preferences", libraryPathUrl.path] toPath:preferencesTo]; [lcUserDefaults removeObjectForKey:@"lastLaunchDataUUID"]; [lcUserDefaults removeObjectForKey:@"lastLaunchType"]; } From 11d8b7a90e552f90e190fb71d115ce0ad2cd53a0 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Mon, 25 Nov 2024 22:50:29 +0800 Subject: [PATCH 09/32] setObject debounce --- LCSharedUtils.h | 2 - TweakLoader/NSUserDefaults.m | 122 ++++++++++++++++++---- TweakLoader/dispatch_cancelable_block_t.h | 66 ++++++++++++ 3 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 TweakLoader/dispatch_cancelable_block_t.h diff --git a/LCSharedUtils.h b/LCSharedUtils.h index f8d6aa3..def7a4b 100644 --- a/LCSharedUtils.h +++ b/LCSharedUtils.h @@ -9,8 +9,6 @@ + (void)setWebPageUrlForNextLaunch:(NSString*)urlString; + (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId; + (void)setAppRunningByThisLC:(NSString*)bundleId; -+ (void)movePreferencesFromPath:(NSString*) plistLocationFrom toPath:(NSString*)plistLocationTo; -+ (void)loadPreferencesFromPath:(NSString*) plistLocationFrom; + (void)moveSharedAppFolderBack; + (void)removeAppRunningByLC:(NSString*)LCScheme; + (NSBundle*)findBundleWithBundleId:(NSString*)bundleId; diff --git a/TweakLoader/NSUserDefaults.m b/TweakLoader/NSUserDefaults.m index 58757a3..17aaac8 100644 --- a/TweakLoader/NSUserDefaults.m +++ b/TweakLoader/NSUserDefaults.m @@ -10,8 +10,12 @@ #import "UIKitPrivate.h" #import "utils.h" #import "LCSharedUtils.h" +#import "dispatch_cancelable_block_t.h" NSMutableDictionary* LCPreferences = 0; +NSMutableDictionary* LCPreferencesDispatchBlock = 0; +BOOL LCIsTerminateFlushHappened = NO; +dispatch_semaphore_t LCPreferencesDispatchBlockSemaphore; __attribute__((constructor)) static void UIKitGuestHooksInit() { @@ -25,6 +29,8 @@ static void UIKitGuestHooksInit() { swizzle(NSUserDefaults.class, @selector(persistentDomainForName:), @selector(hook_persistentDomainForName:)); swizzle(NSUserDefaults.class, @selector(removePersistentDomainForName:), @selector(hook_removePersistentDomainForName:)); LCPreferences = [[NSMutableDictionary alloc] init]; + LCPreferencesDispatchBlock = [[NSMutableDictionary alloc] init]; + LCPreferencesDispatchBlockSemaphore = dispatch_semaphore_create(1); NSFileManager* fm = NSFileManager.defaultManager; NSURL* libraryPath = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; NSURL* preferenceFolderPath = [libraryPath URLByAppendingPathComponent:@"Preferences"]; @@ -33,6 +39,37 @@ static void UIKitGuestHooksInit() { [fm createDirectoryAtPath:preferenceFolderPath.path withIntermediateDirectories:YES attributes:@{} error:&error]; } + // flush any scheduled write to disk now + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * _Nonnull notification) { + LCIsTerminateFlushHappened = YES; + dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); + for(NSString* key in LCPreferencesDispatchBlock) { + if(LCPreferencesDispatchBlock[key]) { + run_block_now(LCPreferencesDispatchBlock[key]); + } + } + dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); + }]; + [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification * _Nonnull notification) { + dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); + for(NSString* key in LCPreferencesDispatchBlock) { + LCIsTerminateFlushHappened = YES; + if(LCPreferencesDispatchBlock[key]) { + run_block_now(LCPreferencesDispatchBlock[key]); + } + } + [LCPreferencesDispatchBlock removeAllObjects]; + LCIsTerminateFlushHappened = NO; + dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); + }]; + + } NSURL* LCGetPreferencePath(NSString* identifier) { @@ -49,32 +86,59 @@ static void UIKitGuestHooksInit() { NSURL* preferenceFilePath = LCGetPreferencePath(identifier); if([NSFileManager.defaultManager fileExistsAtPath:preferenceFilePath.path]) { LCPreferences[identifier] = [NSMutableDictionary dictionaryWithContentsOfFile:preferenceFilePath.path]; + return LCPreferences[identifier]; } else { - LCPreferences[identifier] = [[NSMutableDictionary alloc] init]; + return nil; } - return LCPreferences[identifier]; + } - +void LCScheduleWriteBack(NSString* identifier) { + // debounce, write to disk if no write takes place after 2s + dispatch_cancelable_block_t task = dispatch_after_delay(2, ^{ + NSURL* preferenceFilePath = LCGetPreferencePath(identifier); + [LCPreferences[identifier] writeToURL:preferenceFilePath atomically:YES]; + if(!LCIsTerminateFlushHappened) { + LCPreferencesDispatchBlock[identifier] = nil; + } + }); + if(LCIsTerminateFlushHappened) { + // flush now + run_block_now(task); + return; + } + + if(LCPreferencesDispatchBlock[identifier]) { + cancel_block(LCPreferencesDispatchBlock[identifier]); + } + dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); + LCPreferencesDispatchBlock[identifier] = task; + dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); +} @implementation NSUserDefaults(LiveContainerHooks) - (id)hook_objectForKey:(NSString*)key { - // let LiveContainer itself and Apple stuff bypass + // let LiveContainer itself bypass NSString* identifier = [self _identifier]; - NSLog(@"[LC] hook_objectForKey key = %@, identifier = %@", key, identifier); - id ans = [self hook_objectForKey:key]; - if(ans || [identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { - return ans; + if([identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { + return [self hook_objectForKey:key]; } + + // priortize local preference file over values in native NSUserDefaults NSMutableDictionary* preferenceDict = LCGetPreference(identifier); - return preferenceDict[key]; + if(preferenceDict && preferenceDict[key]) { + return preferenceDict[key]; + } else { + return [self hook_objectForKey:key]; + } } - (BOOL)hook_boolForKey:(NSString*)key { id obj = [self objectForKey:key]; - - if ([obj isKindOfClass:[NSNumber class]]) { + if(!obj) { + return NO; + } else if ([obj isKindOfClass:[NSNumber class]]) { return [(NSNumber*)obj boolValue]; } else if([obj isKindOfClass:[NSString class]]) { if([[(NSString*)obj lowercaseString] isEqualToString:@"yes"] || [[(NSString*)obj lowercaseString] isEqualToString:@"true"]) { @@ -90,7 +154,9 @@ - (BOOL)hook_boolForKey:(NSString*)key { - (NSInteger)hook_integerForKey:(NSString*)key { id obj = [self objectForKey:key]; - if([obj isKindOfClass:[NSString class]]) { + if(!obj) { + return 0; + } else if([obj isKindOfClass:[NSString class]]) { return [(NSString*)obj integerValue]; } else if ([obj isKindOfClass:[NSNumber class]]) { return [(NSNumber*)obj integerValue]; @@ -99,19 +165,18 @@ - (NSInteger)hook_integerForKey:(NSString*)key { } - (void)hook_setObject:(id)obj forKey:(NSString*)key { - // let apple bypass + // let LiveContainer itself bypess NSString* identifier = [self _identifier]; - NSLog(@"[LC] hook_setObjectForKey key = %@, identifier = %@", key, identifier); - if([self hook_objectForKey:key]) { - [self hook_setObject:obj forKey:key]; - return; + if([identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { + return [self hook_setObject:obj forKey:key]; } - NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + if(!preferenceDict) { + preferenceDict = [[NSMutableDictionary alloc] init]; + LCPreferences[identifier] = preferenceDict; + } preferenceDict[key] = obj; - NSURL* preferenceFilePath = LCGetPreferencePath(identifier); - [preferenceDict writeToURL:preferenceFilePath atomically:YES]; - + LCScheduleWriteBack(identifier); } @@ -122,9 +187,13 @@ - (void)hook_removeObjectForKey:(NSString*)key { return; } NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + if(!preferenceDict) { + return; + } + [preferenceDict removeObjectForKey:key]; - NSURL* preferenceFilePath = LCGetPreferencePath(identifier); - [preferenceDict writeToURL:preferenceFilePath atomically:YES]; + // debounce, write to disk if no write takes place after 3s + LCScheduleWriteBack(identifier); } - (NSDictionary*) hook_dictionaryRepresentation { @@ -156,6 +225,13 @@ - (void) hook_removePersistentDomainForName:(NSString*)domainName { [self hook_removePersistentDomainForName:domainName]; } else { [LCPreferences removeObjectForKey:domainName]; + // delete NOW + if(LCPreferencesDispatchBlock[domainName]) { + cancel_block(LCPreferencesDispatchBlock[domainName]); + dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); + LCPreferencesDispatchBlock[domainName] = nil; + dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); + } } NSURL* preferenceFilePath = LCGetPreferencePath(domainName); NSFileManager* fm = NSFileManager.defaultManager; diff --git a/TweakLoader/dispatch_cancelable_block_t.h b/TweakLoader/dispatch_cancelable_block_t.h new file mode 100644 index 0000000..adb9032 --- /dev/null +++ b/TweakLoader/dispatch_cancelable_block_t.h @@ -0,0 +1,66 @@ +// https://gist.github.com/priore/0ae461cf6e12e64bdc0d +#ifndef dispatch_cancellable_block_h +#define dispatch_cancellable_block_h + +#import + +// https://github.com/SebastienThiebaud/dispatch_cancelable_block/issues/2 +typedef void(^dispatch_cancelable_block_t)(BOOL cancel, BOOL runNow); + +NS_INLINE dispatch_cancelable_block_t dispatch_after_delay(NSTimeInterval delay, dispatch_block_t block) { + if (block == nil) { + return nil; + } + + // First we have to create a new dispatch_cancelable_block_t and we also need to copy the block given (if you want more explanations about the __block storage type, read this: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW6 + __block dispatch_cancelable_block_t cancelableBlock = nil; + __block dispatch_block_t originalBlock = [block copy]; + + // This block will be executed in NOW() + delay + dispatch_cancelable_block_t delayBlock = ^(BOOL cancel, BOOL runNow){ + if(runNow) { + originalBlock(); + originalBlock = nil; + return; + } + + if (cancel == NO && originalBlock) { + originalBlock(); + } + + // We don't want to hold any objects in the memory + originalBlock = nil; + }; + + cancelableBlock = [delayBlock copy]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ + // We are now in the future (NOW() + delay). It means the block hasn't been canceled so we can execute it + if (cancelableBlock) { + cancelableBlock(NO, NO); + cancelableBlock = nil; + } + }); + + return cancelableBlock; +} + +NS_INLINE void cancel_block(dispatch_cancelable_block_t block) { + if (block == nil) { + return; + } + + block(YES, NO); + block = nil; +} + +NS_INLINE void run_block_now(dispatch_cancelable_block_t block) { + if (block == nil) { + return; + } + + block(NO, YES); + block = nil; +} + +#endif /* dispatch_cancellable_block_h */ From 486adaec1f0c0522d606bee228cbaa2f0e8d456c Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Tue, 26 Nov 2024 23:04:44 +0800 Subject: [PATCH 10/32] impelement Sign tweaks if needed #175 --- LiveContainerSwiftUI/LCAppModel.swift | 26 ++++- LiveContainerSwiftUI/LCTweaksView.swift | 76 ++------------ LiveContainerSwiftUI/Shared.swift | 125 ++++++++++++++++++++++-- 3 files changed, 153 insertions(+), 74 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 78b0e93..0224278 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -100,6 +100,12 @@ class LCAppModel: ObservableObject, Hashable { func signApp(force: Bool = false) async throws { var signError : String? = nil + defer { + DispatchQueue.main.async { + self.isSigningInProgress = false + } + } + await withCheckedContinuation({ c in appInfo.patchExecAndSignIfNeed(completionHandler: { error in signError = error; @@ -116,10 +122,28 @@ class LCAppModel: ObservableObject, Hashable { } }, forceSign: force) }) - self.isSigningInProgress = false if let signError { throw signError } + + // sign its tweak + guard let tweakFolder = appInfo.tweakFolder() else { + return + } + + let tweakFolderUrl : URL + if(appInfo.isShared) { + tweakFolderUrl = LCPath.lcGroupTweakPath.appendingPathComponent(tweakFolder) + } else { + tweakFolderUrl = LCPath.tweakPath.appendingPathComponent(tweakFolder) + } + try await LCUtils.signTweaks(tweakFolderUrl: tweakFolderUrl, force: force, signer: self.appInfo.signer) { p in + DispatchQueue.main.async { + self.isSigningInProgress = true + } + } + + } func jitLaunch() async { diff --git a/LiveContainerSwiftUI/LCTweaksView.swift b/LiveContainerSwiftUI/LCTweaksView.swift index 4c79c81..7f1a552 100644 --- a/LiveContainerSwiftUI/LCTweaksView.swift +++ b/LiveContainerSwiftUI/LCTweaksView.swift @@ -42,6 +42,9 @@ struct LCTweakFolderView : View { do { let files = try fm.contentsOfDirectory(atPath: baseUrl.path) for fileName in files { + if(fileName == "TweakInfo.plist"){ + continue + } let fileUrl = baseUrl.appendingPathComponent(fileName) var isFolder : ObjCBool = false fm.fileExists(atPath: fileUrl.path, isDirectory: &isFolder) @@ -188,7 +191,7 @@ struct LCTweakFolderView : View { renameFileInput.close(result: "") } ) - .fileImporter(isPresented: $choosingTweak, allowedContentTypes: [.dylib, .lcFramework, .deb], allowsMultipleSelection: true) { result in + .fileImporter(isPresented: $choosingTweak, allowedContentTypes: [.dylib, .lcFramework, /*.deb*/], allowsMultipleSelection: true) { result in Task { await startInstallTweak(result) } } } @@ -282,42 +285,14 @@ struct LCTweakFolderView : View { func signAllTweaks() async { do { - let fm = FileManager() - let tmpDir = fm.temporaryDirectory.appendingPathComponent("TweakTmp") - if fm.fileExists(atPath: tmpDir.path) { - try fm.removeItem(at: tmpDir) - } - try fm.createDirectory(at: tmpDir, withIntermediateDirectories: true) - - var tmpPaths : [URL] = [] - // copy items to tmp folders - for item in tweakItems { - let tmpPath = tmpDir.appendingPathComponent(item.fileUrl.lastPathComponent) - tmpPaths.append(tmpPath) - try fm.copyItem(at: item.fileUrl, to: tmpPath) - } - - if (LCUtils.certificatePassword() != nil) { - // if in jit-less mode, we need to sign - isTweakSigning = true - let error = await LCUtils.signFilesInFolder(url: tmpDir) { p in - - } + defer { isTweakSigning = false - if let error = error { - throw error - } } - for tmpFile in tmpPaths { - let toPath = self.baseUrl.appendingPathComponent(tmpFile.lastPathComponent) - // remove original item and move the signed ones back - if fm.fileExists(atPath: toPath.path) { - try fm.removeItem(at: toPath) - try fm.moveItem(at: tmpFile, to: toPath) - } + try await LCUtils.signTweaks(tweakFolderUrl: self.baseUrl, force: true, signer:Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))!) { p in + isTweakSigning = true } - + } catch { errorInfo = error.localizedDescription errorShow = true @@ -348,13 +323,7 @@ struct LCTweakFolderView : View { do { let fm = FileManager() let urls = try result.get() - var tmpPaths : [URL] = [] - // copy selected tweaks to tmp dir first - let tmpDir = fm.temporaryDirectory.appendingPathComponent("TweakTmp") - if fm.fileExists(atPath: tmpDir.path) { - try fm.removeItem(at: tmpDir) - } - try fm.createDirectory(at: tmpDir, withIntermediateDirectories: true) + // we will sign later before app launch for fileUrl in urls { // handle deb file @@ -364,48 +333,23 @@ struct LCTweakFolderView : View { if(!fileUrl.isFileURL) { throw "lc.tweakView.notFileError %@".localizeWithFormat(fileUrl.lastPathComponent) } - let toPath = tmpDir.appendingPathComponent(fileUrl.lastPathComponent) + let toPath = self.baseUrl.appendingPathComponent(fileUrl.lastPathComponent) try fm.copyItem(at: fileUrl, to: toPath) - tmpPaths.append(toPath) LCParseMachO((toPath.path as NSString).utf8String) { path, header in LCPatchAddRPath(path, header); } fileUrl.stopAccessingSecurityScopedResource() - } - - if (LCUtils.certificatePassword() != nil) { - // if in jit-less mode, we need to sign - isTweakSigning = true - let error = await LCUtils.signFilesInFolder(url: tmpDir) { p in - - } - isTweakSigning = false - if let error = error { - throw error - } - } - for tmpFile in tmpPaths { - let toPath = self.baseUrl.appendingPathComponent(tmpFile.lastPathComponent) - try fm.moveItem(at: tmpFile, to: toPath) - let isFramework = toPath.lastPathComponent.hasSuffix(".framework") let isTweak = toPath.lastPathComponent.hasSuffix(".dylib") self.tweakItems.append(LCTweakItem(fileUrl: toPath, isFolder: false, isFramework: isFramework, isTweak: isTweak)) } - - // clean up - try fm.removeItem(at: tmpDir) - } catch { errorInfo = error.localizedDescription errorShow = true return } - - - } } diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index c66d4e4..e6e2105 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -308,7 +308,7 @@ struct SiteAssociation : Codable { extension LCUtils { public static let appGroupUserDefault = UserDefaults.init(suiteName: LCUtils.appGroupID()) ?? UserDefaults.standard - public static func signFilesInFolder(url: URL, onProgressCreated: (Progress) -> Void) async -> String? { + public static func signFilesInFolder(url: URL, signer:Signer, onProgressCreated: (Progress) -> Void) async -> (String?, Date?) { let fm = FileManager() var ans : String? = nil let codesignPath = url.appendingPathComponent("_CodeSignature") @@ -322,24 +322,36 @@ extension LCUtils { do { try fm.copyItem(at: Bundle.main.executableURL!, to: tmpExecPath) } catch { - return nil + return (nil, nil) } - + var ansDate : Date? = nil await withCheckedContinuation { c in - let progress = LCUtils.signAppBundle(url) { success, expirationDate, error in + func compeletionHandler(success: Bool, expirationDate: Date?, error: Error?){ do { if let error = error { ans = error.localizedDescription } - try fm.removeItem(at: codesignPath) - try fm.removeItem(at: provisionPath) + if(fm.fileExists(atPath: codesignPath.path)) { + try fm.removeItem(at: codesignPath) + } + if(fm.fileExists(atPath: provisionPath.path)) { + try fm.removeItem(at: provisionPath) + } + try fm.removeItem(at: tmpExecPath) try fm.removeItem(at: tmpInfoPath) + ansDate = expirationDate } catch { ans = error.localizedDescription } c.resume() } + let progress : Progress? + if signer == .AltSign { + progress = LCUtils.signAppBundle(url, completionHandler: compeletionHandler) + } else { + progress = LCUtils.signAppBundle(withZSign: url, execName: "LiveContainer.tmp", completionHandler: compeletionHandler) + } guard let progress = progress else { ans = "lc.utils.initSigningError".loc c.resume() @@ -347,8 +359,107 @@ extension LCUtils { } onProgressCreated(progress) } - return ans + return (ans, ansDate) + + } + + public static func signTweaks(tweakFolderUrl: URL, force : Bool = false, signer:Signer, progressHandler : ((Progress) -> Void)? = nil) async throws { + guard LCUtils.certificatePassword() != nil else { + return + } + let fm = FileManager.default + var isFolder :ObjCBool = false + if(fm.fileExists(atPath: tweakFolderUrl.path, isDirectory: &isFolder) && !isFolder.boolValue) { + return + } + + // check if re-sign is needed + // if sign is expired, or inode number of any file changes, we need to re-sign + let tweakSignInfo = NSMutableDictionary(contentsOf: tweakFolderUrl.appendingPathComponent("TweakInfo.plist")) ?? NSMutableDictionary() + let expirationDate = tweakSignInfo["expirationDate"] as? Date + var signNeeded = false + if let expirationDate, expirationDate.compare(Date.now) == .orderedDescending, !force { + + let tweakFileINodeRecord = tweakSignInfo["files"] as? [String:NSNumber] ?? [String:NSNumber]() + let fileURLs = try fm.contentsOfDirectory(at: tweakFolderUrl, includingPropertiesForKeys: nil) + for fileURL in fileURLs { + if(fileURL.lastPathComponent == "TweakInfo.plist"){ + continue + } + let inodeNumber = try fm.attributesOfItem(atPath: fileURL.path)[.systemFileNumber] as? NSNumber + if let fileInodeNumber = tweakFileINodeRecord[fileURL.lastPathComponent] { + if(fileInodeNumber != inodeNumber) { + signNeeded = true + break + } + } else { + signNeeded = true + break + } + + print(fileURL.lastPathComponent) // Prints the file name + } + + } else { + signNeeded = true + } + + guard signNeeded else { + return + } + // sign start + + let tweakItems : [String] = [] + let tmpDir = fm.temporaryDirectory.appendingPathComponent("TweakTmp.app") + if fm.fileExists(atPath: tmpDir.path) { + try fm.removeItem(at: tmpDir) + } + try fm.createDirectory(at: tmpDir, withIntermediateDirectories: true) + + var tmpPaths : [URL] = [] + // copy items to tmp folders + let fileURLs = try fm.contentsOfDirectory(at: tweakFolderUrl, includingPropertiesForKeys: nil) + for fileURL in fileURLs { + if(fileURL.lastPathComponent == "TweakInfo.plist"){ + continue + } + + let tmpPath = tmpDir.appendingPathComponent(fileURL.lastPathComponent) + tmpPaths.append(tmpPath) + try fm.copyItem(at: fileURL, to: tmpPath) + } + + + let (error, expirationDate2) = await LCUtils.signFilesInFolder(url: tmpDir, signer: signer) { p in + if let progressHandler { + progressHandler(p) + } + } + if let error = error { + throw error + } + + // move signed files back and rebuild TweakInfo.plist + tweakSignInfo.removeAllObjects() + tweakSignInfo["expirationDate"] = expirationDate2 + var fileInodes = [String:NSNumber]() + for tmpFile in tmpPaths { + let toPath = tweakFolderUrl.appendingPathComponent(tmpFile.lastPathComponent) + // remove original item and move the signed ones back + if fm.fileExists(atPath: toPath.path) { + try fm.removeItem(at: toPath) + + } + try fm.moveItem(at: tmpFile, to: toPath) + if let inodeNumber = try fm.attributesOfItem(atPath: toPath.path)[.systemFileNumber] as? NSNumber { + fileInodes[tmpFile.lastPathComponent] = inodeNumber + } + } + try fm.removeItem(at: tmpDir) + tweakSignInfo["files"] = fileInodes + try tweakSignInfo.write(to: tweakFolderUrl.appendingPathComponent("TweakInfo.plist")) + } public static func getAppRunningLCScheme(bundleId: String) -> String? { From 3f0c008ffde7202cd07ce5dcc51fb6ce1cf3aca2 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Wed, 27 Nov 2024 13:40:09 +0800 Subject: [PATCH 11/32] Auto sign global tweak folder --- LiveContainerSwiftUI/LCAppModel.swift | 7 +++++++ LiveContainerSwiftUI/Shared.swift | 26 +++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 0224278..c56553f 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -143,6 +143,13 @@ class LCAppModel: ObservableObject, Hashable { } } + // sign global tweak + try await LCUtils.signTweaks(tweakFolderUrl: LCPath.tweakPath, force: force, signer: self.appInfo.signer) { p in + DispatchQueue.main.async { + self.isSigningInProgress = true + } + } + } diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index e6e2105..fefdd0f 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -383,6 +383,18 @@ extension LCUtils { let tweakFileINodeRecord = tweakSignInfo["files"] as? [String:NSNumber] ?? [String:NSNumber]() let fileURLs = try fm.contentsOfDirectory(at: tweakFolderUrl, includingPropertiesForKeys: nil) for fileURL in fileURLs { + let attributes = try fm.attributesOfItem(atPath: fileURL.path) + let fileType = attributes[.type] as? FileAttributeType + if(fileType != FileAttributeType.typeDirectory && fileType != FileAttributeType.typeRegular) { + continue + } + if(fileType == FileAttributeType.typeDirectory && !fileURL.lastPathComponent.hasSuffix(".framework")) { + continue + } + if(fileType == FileAttributeType.typeRegular && !fileURL.lastPathComponent.hasSuffix(".dylib")) { + continue + } + if(fileURL.lastPathComponent == "TweakInfo.plist"){ continue } @@ -420,7 +432,15 @@ extension LCUtils { // copy items to tmp folders let fileURLs = try fm.contentsOfDirectory(at: tweakFolderUrl, includingPropertiesForKeys: nil) for fileURL in fileURLs { - if(fileURL.lastPathComponent == "TweakInfo.plist"){ + let attributes = try fm.attributesOfItem(atPath: fileURL.path) + let fileType = attributes[.type] as? FileAttributeType + if(fileType != FileAttributeType.typeDirectory && fileType != FileAttributeType.typeRegular) { + continue + } + if(fileType == FileAttributeType.typeDirectory && !fileURL.lastPathComponent.hasSuffix(".framework")) { + continue + } + if(fileType == FileAttributeType.typeRegular && !fileURL.lastPathComponent.hasSuffix(".dylib")) { continue } @@ -429,6 +449,10 @@ extension LCUtils { try fm.copyItem(at: fileURL, to: tmpPath) } + if tmpPaths.isEmpty { + try fm.removeItem(at: tmpDir) + return + } let (error, expirationDate2) = await LCUtils.signFilesInFolder(url: tmpDir, signer: signer) { p in if let progressHandler { From 128b3d24e2bacebf7878a0b9404ab914fe84844b Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Wed, 27 Nov 2024 20:17:15 +0800 Subject: [PATCH 12/32] impelement [feature request] Change app language per app #171, add ZSign credit --- LiveContainerSwiftUI/LCAppInfo.h | 1 + LiveContainerSwiftUI/LCAppInfo.m | 14 ++++ LiveContainerSwiftUI/LCAppListView.swift | 1 + LiveContainerSwiftUI/LCAppModel.swift | 20 ++++++ LiveContainerSwiftUI/LCAppSettingsView.swift | 68 +++++++++++++++++++- LiveContainerSwiftUI/Localizable.xcstrings | 51 +++++++++++++++ LiveContainerSwiftUI/Shared.swift | 4 +- LiveContainerUI/Makefile | 17 ----- README.md | 6 ++ TweakLoader/NSUserDefaults.m | 7 ++ main.m | 20 ++++++ 11 files changed, 189 insertions(+), 20 deletions(-) delete mode 100644 LiveContainerUI/Makefile diff --git a/LiveContainerSwiftUI/LCAppInfo.h b/LiveContainerSwiftUI/LCAppInfo.h index 6a8e395..6291558 100644 --- a/LiveContainerSwiftUI/LCAppInfo.h +++ b/LiveContainerSwiftUI/LCAppInfo.h @@ -15,6 +15,7 @@ @property bool bypassAssertBarrierOnQueue; @property UIColor* cachedColor; @property Signer signer; +@property NSString* selectedLanguage; - (void)setBundlePath:(NSString*)newBundlePath; - (NSMutableDictionary*)info; diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 3112d87..6358ecd 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -93,6 +93,20 @@ - (void)setTweakFolder:(NSString *)tweakFolder { [self save]; } +- (NSString*)selectedLanguage { + return _info[@"LCSelectedLanguage"]; +} + +- (void)setSelectedLanguage:(NSString *)selectedLanguage { + if([selectedLanguage isEqualToString: @""]) { + _info[@"LCSelectedLanguage"] = nil; + } else { + _info[@"LCSelectedLanguage"] = selectedLanguage; + } + + [self save]; +} + - (NSString*)bundlePath { return _bundlePath; } diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 196442d..0ce51cc 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -486,6 +486,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { finalNewApp.setDataUUID(appToReplace.appInfo.getDataUUIDNoAssign()) finalNewApp.setTweakFolder(appToReplace.appInfo.tweakFolder()) finalNewApp.signer = appToReplace.appInfo.signer + finalNewApp.selectedLanguage = appToReplace.appInfo.selectedLanguage } DispatchQueue.main.async { if let appToReplace { diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index c56553f..36c1b36 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -23,6 +23,8 @@ class LCAppModel: ObservableObject, Hashable { @Published var uiDoSymlinkInbox : Bool @Published var uiBypassAssertBarrierOnQueue : Bool @Published var uiSigner : Signer + @Published var uiSelectedLanguage : String + @Published var supportedLanaguages : [String]? var jitAlert : YesNoHelper? = nil @@ -40,6 +42,7 @@ class LCAppModel: ObservableObject, Hashable { self.uiIsHidden = appInfo.isHidden self.uiIsLocked = appInfo.isLocked self.uiIsShared = appInfo.isShared + self.uiSelectedLanguage = appInfo.selectedLanguage ?? "" self.uiDataFolder = appInfo.getDataUUIDNoAssign() self.uiTweakFolder = appInfo.tweakFolder() self.uiDoSymlinkInbox = appInfo.doSymlinkInbox @@ -204,4 +207,21 @@ class LCAppModel: ObservableObject, Hashable { } delegate?.changeAppVisibility(app: self) } + + func loadSupportedLanguages() throws { + let fm = FileManager.default + if supportedLanaguages != nil { + return + } + supportedLanaguages = [] + let fileURLs = try fm.contentsOfDirectory(at: URL(fileURLWithPath: appInfo.bundlePath()!) , includingPropertiesForKeys: nil) + for fileURL in fileURLs { + let attributes = try fm.attributesOfItem(atPath: fileURL.path) + let fileType = attributes[.type] as? FileAttributeType + if(fileType == .typeDirectory && fileURL.lastPathComponent.hasSuffix(".lproj")) { + supportedLanaguages?.append(fileURL.deletingPathExtension().lastPathComponent) + } + } + + } } diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index 149f571..edd7b50 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -188,6 +188,58 @@ struct LCAppSettingsView : View{ Task { await setSigner(newValue) } }) + Section { + NavigationLink { + if let supportedLanguage = model.supportedLanaguages { + Form { + Picker(selection: $model.uiSelectedLanguage) { + Text("lc.common.auto".loc).tag("") + + ForEach(supportedLanguage, id:\.self) { language in + if language != "Base" { + VStack(alignment: .leading) { + Text(Locale(identifier: language).localizedString(forIdentifier: language) ?? language) + Text("\(Locale.current.localizedString(forIdentifier: language) ?? "") - \(language)") + .font(.footnote) + .foregroundStyle(.gray) + } + .tag(language) + } + + } + } label: { + Text("lc.common.language".loc) + } + .pickerStyle(.inline) + .onChange(of: model.uiSelectedLanguage, perform: { newValue in + Task { await setLanguage(newValue) } + }) + } + + } else { + Text("lc.appSettings.languageLoading".loc) + .onAppear() { + Task{ loadSupportedLanguages() } + } + } + } label: { + HStack { + Text("lc.common.language".loc) + Spacer() + if model.uiSelectedLanguage == "" { + Text("lc.common.auto".loc) + .foregroundStyle(.gray) + } else { + Text(Locale.current.localizedString(forIdentifier: model.uiSelectedLanguage) ?? model.uiSelectedLanguage) + .foregroundStyle(.gray) + } + } + + } + } + + + Section { Toggle(isOn: $model.uiDoSymlinkInbox) { Text("lc.appSettings.fixFilePicker".loc) @@ -411,7 +463,11 @@ struct LCAppSettingsView : View{ func setSigner(_ signer: Signer) async { appInfo.signer = signer model.uiSigner = signer - + } + + func setLanguage(_ lang: String) async { + appInfo.selectedLanguage = lang + model.uiSelectedLanguage = lang } func setSimlinkInbox(_ simlinkInbox : Bool) async { @@ -420,6 +476,16 @@ struct LCAppSettingsView : View{ } + func loadSupportedLanguages() { + do { + try model.loadSupportedLanguages() + } catch { + errorShow = true + errorInfo = error.localizedDescription + return + } + } + func setBypassAssertBarrierOnQueue(_ enabled : Bool) async { appInfo.bypassAssertBarrierOnQueue = enabled model.uiBypassAssertBarrierOnQueue = enabled diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 3c5cfd9..3582714 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -841,6 +841,23 @@ } } }, + "lc.appSettings.languageLoading" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Loading Available Languages…" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在加载可选语言" + } + } + } + }, "lc.appSettings.launchWithJit" : { "extractionState" : "manual", "localizations" : { @@ -1045,6 +1062,23 @@ } } }, + "lc.common.auto" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Auto" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "自动" + } + } + } + }, "lc.common.cancel" : { "extractionState" : "manual", "localizations" : { @@ -1181,6 +1215,23 @@ } } }, + "lc.common.language" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Language" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "语言" + } + } + } + }, "lc.common.miscellaneous" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index fefdd0f..e3539a4 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -116,12 +116,12 @@ class InputHelper : AlertHelper { extension String: LocalizedError { public var errorDescription: String? { return self } - private static var enBundle : Bundle? { + private static var enBundle : Bundle? = { let language = "en" let path = Bundle.main.path(forResource:language, ofType: "lproj") let bundle = Bundle(path: path!) return bundle - } + }() var loc: String { let message = NSLocalizedString(self, comment: "") diff --git a/LiveContainerUI/Makefile b/LiveContainerUI/Makefile deleted file mode 100644 index 15d6243..0000000 --- a/LiveContainerUI/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -include $(THEOS)/makefiles/common.mk - -FRAMEWORK_NAME = LiveContainerUI - -#LiveContainerUI_FILES = LCAppDelegate.m LCJITLessSetupViewController.m LCMachOUtils.m LCAppListViewController.m LCSettingsListController.m LCTabBarController.m LCTweakListViewController.m LCUtils.m MBRoundProgressView.m UIViewController+LCAlert.m unarchive.m LCAppInfo.m LCWebView.m -LiveContainerUI_FILES = LCVersionInfo.m LCJITLessSetupViewController.m LCUtils.m LCMachOUtils.m LCAppDelegateSwiftUI.m ../Localization.m -LiveContainerUI_CFLAGS = \ - -fobjc-arc \ - -DCONFIG_TYPE=\"$(CONFIG_TYPE)\" \ - -DCONFIG_BRANCH=\"$(CONFIG_BRANCH)\" \ - -DCONFIG_COMMIT=\"$(CONFIG_COMMIT)\" -LiveContainerUI_FRAMEWORKS = CoreGraphics QuartzCore UIKit UniformTypeIdentifiers -LiveContainerUI_PRIVATE_FRAMEWORKS = Preferences -LiveContainerUI_LIBRARIES = archive -LiveContainerUI_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks - -include $(THEOS_MAKE_PATH)/framework.mk diff --git a/README.md b/README.md index 4ba8e08..9de9716 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,12 @@ make package ### AltStoreTweak - The tweak that got injected into SideStore/AltStore to retrieve certificate from it everytime it launches. +### ZSign +- The app signer shipped with LiveContainer. +- Originally made by [zhlynn](https://github.com/zhlynn/zsign). +- Referenced from [Feather](https://github.com/khcrysalis/Feather) by hcrysalis who adopted ZSign to iOS. +- Changes are made to meet LiveContainer's need. + ## How does it work? ### Patching guest executable diff --git a/TweakLoader/NSUserDefaults.m b/TweakLoader/NSUserDefaults.m index 17aaac8..b66d4f6 100644 --- a/TweakLoader/NSUserDefaults.m +++ b/TweakLoader/NSUserDefaults.m @@ -52,6 +52,13 @@ static void UIKitGuestHooksInit() { } } dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); + + // restore language if needed + + NSArray* savedLaunguage = [NSUserDefaults.lcUserDefaults objectForKey:@"LCLastLanguages"]; + if(savedLaunguage) { + [NSUserDefaults.lcUserDefaults setObject:savedLaunguage forKey:@"AppleLanguages"]; + } }]; [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification object:nil diff --git a/main.m b/main.m index 13bc485..3c537e8 100644 --- a/main.m +++ b/main.m @@ -269,6 +269,18 @@ static void overwriteExecPath(NSString *bundlePath) { const char *appExecPath = appBundle.executablePath.UTF8String; *path = appExecPath; overwriteExecPath(appBundle.bundlePath); + + // save livecontainer's own language + NSArray* savedLaunguage = [lcUserDefaults objectForKey:@"LCLastLanguages"]; + if(!savedLaunguage) { + [lcUserDefaults setObject:[lcUserDefaults objectForKey:@"AppleLanguages"] forKey:@"LCLastLanguages"]; + } + // set user selected language + NSString* selectedLanguage = [appBundle infoDictionary][@"LCSelectedLanguage"]; + if(selectedLanguage) { + [lcUserDefaults setObject:@[selectedLanguage] forKey:@"AppleLanguages"]; + } + // Overwrite NSUserDefaults NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.bundleIdentifier]; @@ -495,6 +507,14 @@ int LiveContainerMain(int argc, char *argv[]) { } } [LCSharedUtils setAppRunningByThisLC:nil]; + + // recover language before reaching UI + NSArray* savedLaunguage = [lcUserDefaults objectForKey:@"LCLastLanguages"]; + if(savedLaunguage) { + [lcUserDefaults setObject:savedLaunguage forKey:@"AppleLanguages"]; + [lcUserDefaults removeObjectForKey:@"LCLastLanguages"]; + [lcUserDefaults synchronize]; + } void *LiveContainerSwiftUIHandle = dlopen("@executable_path/Frameworks/LiveContainerSwiftUI.framework/LiveContainerSwiftUI", RTLD_LAZY); assert(LiveContainerSwiftUIHandle); @autoreleasepool { From 01fa6aa01879d84df2221a4c12b6d7f298082172 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Thu, 28 Nov 2024 12:24:32 +0800 Subject: [PATCH 13/32] better NSUserDefault solution, move app delegate to swift --- LCSharedUtils.h | 1 + LCSharedUtils.m | 23 ++++ LiveContainerSwiftUI/AppDelegate.swift | 87 +++++++++++++ LiveContainerSwiftUI/LCAppDelegateSwiftUI.h | 14 --- LiveContainerSwiftUI/LCAppDelegateSwiftUI.m | 45 ------- LiveContainerSwiftUI/LCAppListView.swift | 4 +- LiveContainerSwiftUI/LCSwiftBridge.h | 18 --- LiveContainerSwiftUI/LCSwiftBridge.m | 34 ------ LiveContainerSwiftUI/LCUtils.m | 8 ++ .../project.pbxproj | 20 +-- LiveContainerSwiftUI/Makefile | 2 - LiveContainerSwiftUI/ObjcBridge.swift | 58 --------- TweakLoader/NSUserDefaults.m | 115 +++++------------- main.m | 13 +- 14 files changed, 170 insertions(+), 272 deletions(-) create mode 100644 LiveContainerSwiftUI/AppDelegate.swift delete mode 100644 LiveContainerSwiftUI/LCAppDelegateSwiftUI.h delete mode 100644 LiveContainerSwiftUI/LCAppDelegateSwiftUI.m delete mode 100644 LiveContainerSwiftUI/LCSwiftBridge.h delete mode 100644 LiveContainerSwiftUI/LCSwiftBridge.m delete mode 100644 LiveContainerSwiftUI/ObjcBridge.swift diff --git a/LCSharedUtils.h b/LCSharedUtils.h index def7a4b..a8243fd 100644 --- a/LCSharedUtils.h +++ b/LCSharedUtils.h @@ -12,4 +12,5 @@ + (void)moveSharedAppFolderBack; + (void)removeAppRunningByLC:(NSString*)LCScheme; + (NSBundle*)findBundleWithBundleId:(NSString*)bundleId; ++ (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)dataUUID; @end diff --git a/LCSharedUtils.m b/LCSharedUtils.m index 3005917..d972d3b 100644 --- a/LCSharedUtils.m +++ b/LCSharedUtils.m @@ -236,4 +236,27 @@ + (NSBundle*)findBundleWithBundleId:(NSString*)bundleId { return appBundle; } ++ (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)dataUUID { + NSFileManager* fm = [[NSFileManager alloc] init]; + NSError* error1; + + NSDictionary* preferences = [lcUserDefaults objectForKey:dataUUID]; + if(!preferences) { + return; + } + + [fm createDirectoryAtPath:plistLocationTo withIntermediateDirectories:YES attributes:@{} error:&error1]; + for(NSString* identifier in preferences) { + NSDictionary* preference = preferences[identifier]; + NSString *itemPath = [plistLocationTo stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.plist", identifier]]; + if([preference count] == 0) { + // Attempt to delete the file + [fm removeItemAtPath:itemPath error:&error1]; + continue; + } + [preference writeToFile:itemPath atomically:YES]; + } + [lcUserDefaults removeObjectForKey:dataUUID]; +} + @end diff --git a/LiveContainerSwiftUI/AppDelegate.swift b/LiveContainerSwiftUI/AppDelegate.swift new file mode 100644 index 0000000..c31ce1d --- /dev/null +++ b/LiveContainerSwiftUI/AppDelegate.swift @@ -0,0 +1,87 @@ +import UIKit +import SwiftUI + +@objc class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + private static var urlStrToOpen: String? = nil + private static var openUrlStrFunc: ((String) async -> Void)? + private static var bundleToLaunch: String? = nil + private static var launchAppFunc: ((String) async -> Void)? + + public static func setOpenUrlStrFunc(handler: @escaping ((String) async -> Void)){ + self.openUrlStrFunc = handler + if let urlStrToOpen = self.urlStrToOpen { + Task { await handler(urlStrToOpen) } + self.urlStrToOpen = nil + } else if let urlStr = UserDefaults.standard.string(forKey: "webPageToOpen") { + UserDefaults.standard.removeObject(forKey: "webPageToOpen") + Task { await handler(urlStr) } + } + } + + public static func setLaunchAppFunc(handler: @escaping ((String) async -> Void)){ + self.launchAppFunc = handler + if let bundleToLaunch = self.bundleToLaunch { + Task { await handler(bundleToLaunch) } + self.bundleToLaunch = nil + } + } + + private static func openWebPage(urlStr: String) { + if openUrlStrFunc == nil { + urlStrToOpen = urlStr + } else { + Task { await openUrlStrFunc!(urlStr) } + } + } + + private static func launchApp(bundleId: String) { + if launchAppFunc == nil { + bundleToLaunch = bundleId + } else { + Task { await launchAppFunc!(bundleId) } + } + } + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + let window = UIWindow(frame: UIScreen.main.bounds) + self.window = window + let contentView = LCTabView() + window.rootViewController = UIHostingController(rootView: contentView) + window.makeKeyAndVisible() + return true + } + + func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool { + if url.host == "open-web-page" { + if let urlComponent = URLComponents(url: url, resolvingAgainstBaseURL: false), let queryItem = urlComponent.queryItems?.first { + if queryItem.value?.isEmpty ?? true { + return true + } + + if let decodedData = Data(base64Encoded: queryItem.value ?? ""), + let decodedUrl = String(data: decodedData, encoding: .utf8) { + AppDelegate.openWebPage(urlStr: decodedUrl) + } + } + } else if url.host == "livecontainer-launch" { + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) { + for queryItem in components.queryItems ?? [] { + if queryItem.name == "bundle-name", let bundleId = queryItem.value { + AppDelegate.launchApp(bundleId: bundleId) + break + } + } + } + } + + return false + } + + func applicationWillTerminate(_ application: UIApplication) { + // Fix launching app if user opens JIT waiting dialog and kills the app. Won't trigger normally. + UserDefaults.standard.removeObject(forKey: "selected") + } + +} diff --git a/LiveContainerSwiftUI/LCAppDelegateSwiftUI.h b/LiveContainerSwiftUI/LCAppDelegateSwiftUI.h deleted file mode 100644 index 2b304af..0000000 --- a/LiveContainerSwiftUI/LCAppDelegateSwiftUI.h +++ /dev/null @@ -1,14 +0,0 @@ -#import -#import -@interface LCSwiftBridge : NSObject -+ (UIViewController * _Nonnull)getRootVC; -+ (void)openWebPageWithUrlStr:(NSString* _Nonnull)urlStr; -+ (void)launchAppWithBundleId:(NSString* _Nonnull)bundleId; -@end - -@interface LCAppDelegateSwiftUI : UIResponder - -@property (nonatomic, strong) UIWindow * _Nullable window; -@property (nonatomic, strong) UIViewController * _Nonnull rootViewController; - -@end diff --git a/LiveContainerSwiftUI/LCAppDelegateSwiftUI.m b/LiveContainerSwiftUI/LCAppDelegateSwiftUI.m deleted file mode 100644 index dcdf142..0000000 --- a/LiveContainerSwiftUI/LCAppDelegateSwiftUI.m +++ /dev/null @@ -1,45 +0,0 @@ -#import "LCAppDelegateSwiftUI.h" -#import -#import "LCUtils.h" -#import "../LCSharedUtils.h" - -@implementation LCAppDelegateSwiftUI - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - _rootViewController = [NSClassFromString(@"LCSwiftBridge") getRootVC]; - _window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - _window.rootViewController = _rootViewController; - [_window makeKeyAndVisible]; - return YES; -} - -- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { - // handle page open request from URL scheme - if([url.host isEqualToString:@"open-web-page"]) { - NSURLComponents* urlComponent = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - if(urlComponent.queryItems.count == 0){ - return YES; - } - - NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:urlComponent.queryItems[0].value options:0]; - NSString *decodedUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; - [NSClassFromString(@"LCSwiftBridge") openWebPageWithUrlStr:decodedUrl]; - } else if([url.host isEqualToString:@"livecontainer-launch"]) { - NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; - for (NSURLQueryItem* queryItem in components.queryItems) { - if ([queryItem.name isEqualToString:@"bundle-name"]) { - [NSClassFromString(@"LCSwiftBridge") launchAppWithBundleId:queryItem.value]; - break; - } - } - } - - return NO; -} - -- (void)applicationWillTerminate:(UIApplication *)application { - // fix launching app if user open JIT waiting dialogue and kill the app. Won't trigger normally - [NSUserDefaults.standardUserDefaults removeObjectForKey: @"selected"]; -} - -@end diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 0ce51cc..5198290 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -260,8 +260,8 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { app.delegate = self } - LCObjcBridge.setLaunchAppFunc(handler: launchAppWithBundleId) - LCObjcBridge.setOpenUrlStrFunc(handler: openWebView) + AppDelegate.setLaunchAppFunc(handler: launchAppWithBundleId) + AppDelegate.setOpenUrlStrFunc(handler: openWebView) } diff --git a/LiveContainerSwiftUI/LCSwiftBridge.h b/LiveContainerSwiftUI/LCSwiftBridge.h deleted file mode 100644 index cc1a567..0000000 --- a/LiveContainerSwiftUI/LCSwiftBridge.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// ObjcBridge.h -// LiveContainerSwiftUI -// -// Created by s s on 2024/8/22. -// - -#ifndef ObjcBridge_h -#define ObjcBridge_h -#include -#import "LiveContainerSwiftUI-Swift.h" -#endif /* ObjcBridge_h */ - -@interface LCSwiftBridge : NSObject -+ (UIViewController * _Nonnull)getRootVC; -+ (void)openWebPageWithUrlStr:(NSURL* _Nonnull)url; -+ (void)launchAppWithBundleId:(NSString* _Nonnull)bundleId; -@end diff --git a/LiveContainerSwiftUI/LCSwiftBridge.m b/LiveContainerSwiftUI/LCSwiftBridge.m deleted file mode 100644 index 2fd3332..0000000 --- a/LiveContainerSwiftUI/LCSwiftBridge.m +++ /dev/null @@ -1,34 +0,0 @@ -// -// ObjcBridge.m -// LiveContainerSwiftUI -// -// Created by s s on 2024/8/22. -// - -#import - -#import "LCSwiftBridge.h" - -@implementation LCSwiftBridge - -+ (UIViewController * _Nonnull)getRootVC { - return [LCObjcBridge getRootVC]; -} - -+ (void)openWebPageWithUrlStr:(NSString* _Nonnull)urlStr { - [LCObjcBridge openWebPageWithUrlStr:urlStr]; -} - -+ (void)launchAppWithBundleId:(NSString* _Nonnull)bundleId { - [LCObjcBridge launchAppWithBundleId:bundleId]; -} - -@end - -// make SFSafariView happy and open data: URLs -@implementation NSURL(hack) -- (BOOL)safari_isHTTPFamilyURL { - // Screw it, Apple - return YES; -} -@end diff --git a/LiveContainerSwiftUI/LCUtils.m b/LiveContainerSwiftUI/LCUtils.m index 3eae0da..12fb96d 100644 --- a/LiveContainerSwiftUI/LCUtils.m +++ b/LiveContainerSwiftUI/LCUtils.m @@ -7,6 +7,14 @@ #import "LCVersionInfo.h" #import "../ZSign/zsigner.h" +// make SFSafariView happy and open data: URLs +@implementation NSURL(hack) +- (BOOL)safari_isHTTPFamilyURL { + // Screw it, Apple + return YES; +} +@end + @implementation LCUtils #pragma mark Certificate & password diff --git a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj index 1552a57..7dab16e 100644 --- a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj +++ b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj @@ -9,7 +9,6 @@ /* Begin PBXBuildFile section */ 170C3DF92C99A489007F86FB /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 170C3DF82C99A489007F86FB /* Localizable.xcstrings */; }; 171014182CE9B42000673269 /* LCVersionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 171014172CE9B42000673269 /* LCVersionInfo.m */; }; - 171014192CE9B42000673269 /* LCAppDelegateSwiftUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 171014152CE9B42000673269 /* LCAppDelegateSwiftUI.m */; }; 1710141C2CE9B50C00673269 /* LCAppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1710141B2CE9B50C00673269 /* LCAppInfo.m */; }; 171014222CE9B63100673269 /* unarchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 171014212CE9B63100673269 /* unarchive.m */; }; 171014232CE9B63100673269 /* LCUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1710141F2CE9B63100673269 /* LCUtils.m */; }; @@ -19,20 +18,17 @@ 173564CC2C76FE3500C6C918 /* LCTweaksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C02C76FE3500C6C918 /* LCTweaksView.swift */; }; 173564CD2C76FE3500C6C918 /* LCSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C12C76FE3500C6C918 /* LCSettingsView.swift */; }; 173564CE2C76FE3500C6C918 /* Makefile in Sources */ = {isa = PBXBuildFile; fileRef = 173564C32C76FE3500C6C918 /* Makefile */; }; - 173564CF2C76FE3500C6C918 /* LCSwiftBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = 173564C42C76FE3500C6C918 /* LCSwiftBridge.m */; }; - 173564D12C76FE3500C6C918 /* ObjcBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C62C76FE3500C6C918 /* ObjcBridge.swift */; }; 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C72C76FE3500C6C918 /* LCAppBanner.swift */; }; 173564D32C76FE3500C6C918 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 173564C82C76FE3500C6C918 /* Assets.xcassets */; }; 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173F183F2C7B7B74002953AA /* LCWebView.swift */; }; 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178B4C3D2C77654400DD1F74 /* Shared.swift */; }; + 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179765E02CF8192200D40B95 /* AppDelegate.swift */; }; 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */; }; 17C536F42C98529D006C2C75 /* LCAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 170C3DF82C99A489007F86FB /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; - 171014142CE9B42000673269 /* LCAppDelegateSwiftUI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LCAppDelegateSwiftUI.h; sourceTree = ""; }; - 171014152CE9B42000673269 /* LCAppDelegateSwiftUI.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LCAppDelegateSwiftUI.m; sourceTree = ""; }; 171014162CE9B42000673269 /* LCVersionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LCVersionInfo.h; sourceTree = ""; }; 171014172CE9B42000673269 /* LCVersionInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LCVersionInfo.m; sourceTree = ""; }; 1710141A2CE9B50C00673269 /* LCAppInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LCAppInfo.h; sourceTree = ""; }; @@ -48,15 +44,13 @@ 173564BD2C76FE3500C6C918 /* LCTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCTabView.swift; sourceTree = ""; }; 173564C02C76FE3500C6C918 /* LCTweaksView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCTweaksView.swift; sourceTree = ""; }; 173564C12C76FE3500C6C918 /* LCSettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCSettingsView.swift; sourceTree = ""; }; - 173564C22C76FE3500C6C918 /* LCSwiftBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LCSwiftBridge.h; sourceTree = ""; }; 173564C32C76FE3500C6C918 /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; path = Makefile; sourceTree = ""; }; - 173564C42C76FE3500C6C918 /* LCSwiftBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LCSwiftBridge.m; sourceTree = ""; }; - 173564C62C76FE3500C6C918 /* ObjcBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjcBridge.swift; sourceTree = ""; }; 173564C72C76FE3500C6C918 /* LCAppBanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCAppBanner.swift; sourceTree = ""; }; 173564C82C76FE3500C6C918 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 173F183F2C7B7B74002953AA /* LCWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCWebView.swift; sourceTree = ""; }; 178B4C3D2C77654400DD1F74 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; 178B4C3F2C7766A300DD1F74 /* LiveContainerSwiftUI-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LiveContainerSwiftUI-Bridging-Header.h"; sourceTree = ""; }; + 179765E02CF8192200D40B95 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppModel.swift; sourceTree = ""; }; 17B9B88D2C760678009D079E /* LiveContainerSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveContainerSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppSettingsView.swift; sourceTree = ""; }; @@ -85,15 +79,11 @@ 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */, 173564C02C76FE3500C6C918 /* LCTweaksView.swift */, 173564C32C76FE3500C6C918 /* Makefile */, - 173564C62C76FE3500C6C918 /* ObjcBridge.swift */, 178B4C3F2C7766A300DD1F74 /* LiveContainerSwiftUI-Bridging-Header.h */, 178B4C3D2C77654400DD1F74 /* Shared.swift */, 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */, - 173564C22C76FE3500C6C918 /* LCSwiftBridge.h */, - 173564C42C76FE3500C6C918 /* LCSwiftBridge.m */, 170C3DF82C99A489007F86FB /* Localizable.xcstrings */, - 171014142CE9B42000673269 /* LCAppDelegateSwiftUI.h */, - 171014152CE9B42000673269 /* LCAppDelegateSwiftUI.m */, + 179765E02CF8192200D40B95 /* AppDelegate.swift */, 171014162CE9B42000673269 /* LCVersionInfo.h */, 171014172CE9B42000673269 /* LCVersionInfo.m */, 1710141A2CE9B50C00673269 /* LCAppInfo.h */, @@ -199,21 +189,19 @@ files = ( 171014222CE9B63100673269 /* unarchive.m in Sources */, 171014232CE9B63100673269 /* LCUtils.m in Sources */, + 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */, 171014242CE9B63100673269 /* LCMachOUtils.m in Sources */, 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */, 171014182CE9B42000673269 /* LCVersionInfo.m in Sources */, - 171014192CE9B42000673269 /* LCAppDelegateSwiftUI.m in Sources */, 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */, 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */, 173564CE2C76FE3500C6C918 /* Makefile in Sources */, 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */, 17C536F42C98529D006C2C75 /* LCAppSettingsView.swift in Sources */, - 173564CF2C76FE3500C6C918 /* LCSwiftBridge.m in Sources */, 173564C92C76FE3500C6C918 /* LCAppListView.swift in Sources */, 1710141C2CE9B50C00673269 /* LCAppInfo.m in Sources */, 173564CC2C76FE3500C6C918 /* LCTweaksView.swift in Sources */, 173564CD2C76FE3500C6C918 /* LCSettingsView.swift in Sources */, - 173564D12C76FE3500C6C918 /* ObjcBridge.swift in Sources */, 173564CA2C76FE3500C6C918 /* LCTabView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/LiveContainerSwiftUI/Makefile b/LiveContainerSwiftUI/Makefile index d617616..90e968e 100644 --- a/LiveContainerSwiftUI/Makefile +++ b/LiveContainerSwiftUI/Makefile @@ -6,9 +6,7 @@ FRAMEWORK_NAME = LiveContainerSwiftUI LiveContainerSwiftUI_FILES = \ $(shell find . -name '*.swift') \ -LCSwiftBridge.m \ LCAppInfo.m \ -LCAppDelegateSwiftUI.m \ LCVersionInfo.m \ LCUtils.m \ LCMachOUtils.m \ diff --git a/LiveContainerSwiftUI/ObjcBridge.swift b/LiveContainerSwiftUI/ObjcBridge.swift deleted file mode 100644 index 5a76e13..0000000 --- a/LiveContainerSwiftUI/ObjcBridge.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// text.swift -// LiveContainerSwiftUI -// -// Created by s s on 2024/8/21. -// - -import Foundation -import SwiftUI - - -@objc public class LCObjcBridge: NSObject { - private static var urlStrToOpen: String? = nil - private static var openUrlStrFunc: ((String) async -> Void)? - private static var bundleToLaunch: String? = nil - private static var launchAppFunc: ((String) async -> Void)? - - public static func setOpenUrlStrFunc(handler: @escaping ((String) async -> Void)){ - self.openUrlStrFunc = handler - if let urlStrToOpen = self.urlStrToOpen { - Task { await handler(urlStrToOpen) } - self.urlStrToOpen = nil - } else if let urlStr = UserDefaults.standard.string(forKey: "webPageToOpen") { - UserDefaults.standard.removeObject(forKey: "webPageToOpen") - Task { await handler(urlStr) } - } - } - - public static func setLaunchAppFunc(handler: @escaping ((String) async -> Void)){ - self.launchAppFunc = handler - if let bundleToLaunch = self.bundleToLaunch { - Task { await handler(bundleToLaunch) } - self.bundleToLaunch = nil - } - } - - @objc public static func openWebPage(urlStr: String) { - if openUrlStrFunc == nil { - urlStrToOpen = urlStr - } else { - Task { await openUrlStrFunc!(urlStr) } - } - } - - @objc public static func launchApp(bundleId: String) { - if launchAppFunc == nil { - bundleToLaunch = bundleId - } else { - Task { await launchAppFunc!(bundleId) } - } - } - - @objc public static func getRootVC() -> UIViewController { - let rootView = LCTabView() - let rootVC = UIHostingController(rootView: rootView) - return rootVC - } -} diff --git a/TweakLoader/NSUserDefaults.m b/TweakLoader/NSUserDefaults.m index b66d4f6..5440e22 100644 --- a/TweakLoader/NSUserDefaults.m +++ b/TweakLoader/NSUserDefaults.m @@ -10,12 +10,8 @@ #import "UIKitPrivate.h" #import "utils.h" #import "LCSharedUtils.h" -#import "dispatch_cancelable_block_t.h" NSMutableDictionary* LCPreferences = 0; -NSMutableDictionary* LCPreferencesDispatchBlock = 0; -BOOL LCIsTerminateFlushHappened = NO; -dispatch_semaphore_t LCPreferencesDispatchBlockSemaphore; __attribute__((constructor)) static void UIKitGuestHooksInit() { @@ -29,8 +25,6 @@ static void UIKitGuestHooksInit() { swizzle(NSUserDefaults.class, @selector(persistentDomainForName:), @selector(hook_persistentDomainForName:)); swizzle(NSUserDefaults.class, @selector(removePersistentDomainForName:), @selector(hook_removePersistentDomainForName:)); LCPreferences = [[NSMutableDictionary alloc] init]; - LCPreferencesDispatchBlock = [[NSMutableDictionary alloc] init]; - LCPreferencesDispatchBlockSemaphore = dispatch_semaphore_create(1); NSFileManager* fm = NSFileManager.defaultManager; NSURL* libraryPath = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; NSURL* preferenceFolderPath = [libraryPath URLByAppendingPathComponent:@"Preferences"]; @@ -44,15 +38,6 @@ static void UIKitGuestHooksInit() { object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull notification) { - LCIsTerminateFlushHappened = YES; - dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); - for(NSString* key in LCPreferencesDispatchBlock) { - if(LCPreferencesDispatchBlock[key]) { - run_block_now(LCPreferencesDispatchBlock[key]); - } - } - dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); - // restore language if needed NSArray* savedLaunguage = [NSUserDefaults.lcUserDefaults objectForKey:@"LCLastLanguages"]; @@ -60,21 +45,6 @@ static void UIKitGuestHooksInit() { [NSUserDefaults.lcUserDefaults setObject:savedLaunguage forKey:@"AppleLanguages"]; } }]; - [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidEnterBackgroundNotification - object:nil - queue:[NSOperationQueue mainQueue] - usingBlock:^(NSNotification * _Nonnull notification) { - dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); - for(NSString* key in LCPreferencesDispatchBlock) { - LCIsTerminateFlushHappened = YES; - if(LCPreferencesDispatchBlock[key]) { - run_block_now(LCPreferencesDispatchBlock[key]); - } - } - [LCPreferencesDispatchBlock removeAllObjects]; - LCIsTerminateFlushHappened = NO; - dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); - }]; } @@ -100,27 +70,10 @@ static void UIKitGuestHooksInit() { } -void LCScheduleWriteBack(NSString* identifier) { - // debounce, write to disk if no write takes place after 2s - dispatch_cancelable_block_t task = dispatch_after_delay(2, ^{ - NSURL* preferenceFilePath = LCGetPreferencePath(identifier); - [LCPreferences[identifier] writeToURL:preferenceFilePath atomically:YES]; - if(!LCIsTerminateFlushHappened) { - LCPreferencesDispatchBlock[identifier] = nil; - } - }); - if(LCIsTerminateFlushHappened) { - // flush now - run_block_now(task); - return; - } - - if(LCPreferencesDispatchBlock[identifier]) { - cancel_block(LCPreferencesDispatchBlock[identifier]); - } - dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); - LCPreferencesDispatchBlock[identifier] = task; - dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); +// save preference to livecontainer's user default +void LCSavePreference(void) { + NSString* containerId = [[NSString stringWithUTF8String:getenv("HOME")] lastPathComponent]; + [NSUserDefaults.lcUserDefaults setObject:LCPreferences forKey:containerId]; } @implementation NSUserDefaults(LiveContainerHooks) @@ -177,14 +130,15 @@ - (void)hook_setObject:(id)obj forKey:(NSString*)key { if([identifier isEqualToString:(__bridge id)kCFPreferencesCurrentApplication]) { return [self hook_setObject:obj forKey:key]; } - NSMutableDictionary* preferenceDict = LCGetPreference(identifier); - if(!preferenceDict) { - preferenceDict = [[NSMutableDictionary alloc] init]; - LCPreferences[identifier] = preferenceDict; + @synchronized (LCPreferences) { + NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + if(!preferenceDict) { + preferenceDict = [[NSMutableDictionary alloc] init]; + LCPreferences[identifier] = preferenceDict; + } + preferenceDict[key] = obj; + LCSavePreference(); } - preferenceDict[key] = obj; - LCScheduleWriteBack(identifier); - } - (void)hook_removeObjectForKey:(NSString*)key { @@ -193,14 +147,15 @@ - (void)hook_removeObjectForKey:(NSString*)key { [self hook_removeObjectForKey:key]; return; } - NSMutableDictionary* preferenceDict = LCGetPreference(identifier); - if(!preferenceDict) { - return; + @synchronized (LCPreferences) { + NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + if(!preferenceDict) { + return; + } + + [preferenceDict removeObjectForKey:key]; + LCSavePreference(); } - - [preferenceDict removeObjectForKey:key]; - // debounce, write to disk if no write takes place after 3s - LCScheduleWriteBack(identifier); } - (NSDictionary*) hook_dictionaryRepresentation { @@ -228,25 +183,21 @@ - (NSDictionary*) hook_persistentDomainForName:(NSString*)domainName { - (void) hook_removePersistentDomainForName:(NSString*)domainName { NSMutableDictionary* ans = [[self hook_persistentDomainForName:domainName] mutableCopy]; - if(ans) { - [self hook_removePersistentDomainForName:domainName]; - } else { - [LCPreferences removeObjectForKey:domainName]; - // delete NOW - if(LCPreferencesDispatchBlock[domainName]) { - cancel_block(LCPreferencesDispatchBlock[domainName]); - dispatch_semaphore_wait(LCPreferencesDispatchBlockSemaphore, DISPATCH_TIME_FOREVER); - LCPreferencesDispatchBlock[domainName] = nil; - dispatch_semaphore_signal(LCPreferencesDispatchBlockSemaphore); + @synchronized (LCPreferences) { + if(ans) { + [self hook_removePersistentDomainForName:domainName]; + } else { + // empty dictionary means deletion + [LCPreferences setObject:[[NSMutableArray alloc] init] forKey:domainName]; + LCSavePreference(); + } + NSURL* preferenceFilePath = LCGetPreferencePath(domainName); + NSFileManager* fm = NSFileManager.defaultManager; + if([fm fileExistsAtPath:preferenceFilePath.path]) { + NSError* error; + [fm removeItemAtURL:preferenceFilePath error:&error]; } } - NSURL* preferenceFilePath = LCGetPreferencePath(domainName); - NSFileManager* fm = NSFileManager.defaultManager; - if([fm fileExistsAtPath:preferenceFilePath.path]) { - NSError* error; - [fm removeItemAtURL:preferenceFilePath error:&error]; - } - } @end diff --git a/main.m b/main.m index 3c537e8..705b95a 100644 --- a/main.m +++ b/main.m @@ -434,6 +434,17 @@ int LiveContainerMain(int argc, char *argv[]) { NSString* lastLaunchDataUUID = [lcUserDefaults objectForKey:@"lastLaunchDataUUID"]; if(lastLaunchDataUUID) { + NSString* lastLaunchType = [lcUserDefaults objectForKey:@"lastLaunchType"]; + NSString* preferencesTo; + NSURL *libraryPathUrl = [NSFileManager.defaultManager URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; + NSURL *docPathUrl = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask].lastObject; + if([lastLaunchType isEqualToString:@"Shared"]) { + preferencesTo = [libraryPathUrl.path stringByAppendingPathComponent:[NSString stringWithFormat:@"SharedDocuments/%@/Library/Preferences", lastLaunchDataUUID]]; + } else { + preferencesTo = [docPathUrl.path stringByAppendingPathComponent:[NSString stringWithFormat:@"Data/Application/%@/Library/Preferences", lastLaunchDataUUID]]; + } + // recover preferences + [LCSharedUtils dumpPreferenceToPath:preferencesTo dataUUID:lastLaunchDataUUID]; [lcUserDefaults removeObjectForKey:@"lastLaunchDataUUID"]; [lcUserDefaults removeObjectForKey:@"lastLaunchType"]; } @@ -521,7 +532,7 @@ int LiveContainerMain(int argc, char *argv[]) { if ([lcUserDefaults boolForKey:@"LCLoadTweaksToSelf"]) { dlopen("@executable_path/Frameworks/TweakLoader.dylib", RTLD_LAZY); } - return UIApplicationMain(argc, argv, nil, @"LCAppDelegateSwiftUI"); + return UIApplicationMain(argc, argv, nil, @"LiveContainerSwiftUI.AppDelegate"); } } From 8d9e4c45f32474e29d6e06aa426cc73a2b7ab5e3 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 29 Nov 2024 00:05:50 +0800 Subject: [PATCH 14/32] bug fixes & faster signing --- LiveContainerSwiftUI/AppDelegate.swift | 6 +++ LiveContainerSwiftUI/LCAppInfo.m | 32 ++++++++++- LiveContainerSwiftUI/LCAppModel.swift | 7 +++ LiveContainerSwiftUI/LCMachOUtils.m | 26 +++++++++ LiveContainerSwiftUI/LCUtils.h | 1 + TweakLoader/dispatch_cancelable_block_t.h | 66 ----------------------- ZSign/bundle.cpp | 11 ++-- main.m | 12 ----- 8 files changed, 77 insertions(+), 84 deletions(-) delete mode 100644 TweakLoader/dispatch_cancelable_block_t.h diff --git a/LiveContainerSwiftUI/AppDelegate.swift b/LiveContainerSwiftUI/AppDelegate.swift index c31ce1d..4c8a1da 100644 --- a/LiveContainerSwiftUI/AppDelegate.swift +++ b/LiveContainerSwiftUI/AppDelegate.swift @@ -82,6 +82,12 @@ import SwiftUI func applicationWillTerminate(_ application: UIApplication) { // Fix launching app if user opens JIT waiting dialog and kills the app. Won't trigger normally. UserDefaults.standard.removeObject(forKey: "selected") + + if (UserDefaults.standard.object(forKey: "LCLastLanguages") != nil) { + // recover livecontainer's own language + UserDefaults.standard.set(UserDefaults.standard.object(forKey: "LCLastLanguages"), forKey: "AppleLanguages") + UserDefaults.standard.removeObject(forKey: "LCLastLanguages") + } } } diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 6358ecd..3909476 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -212,12 +212,42 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo } // Update patch - int currentPatchRev = 5; + int currentPatchRev = 6; if ([info[@"LCPatchRevision"] intValue] < currentPatchRev) { + // we must re-sign after upgrading patch version + forceSign = YES; NSString *execPath = [NSString stringWithFormat:@"%@/%@", appPath, info[@"CFBundleExecutable"]]; NSString *error = LCParseMachO(execPath.UTF8String, ^(const char *path, struct mach_header_64 *header) { LCPatchExecSlice(path, header); }); + // patch all libraries + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString* frameworksPath = [appPath stringByAppendingPathComponent:@"Frameworks"]; + NSArray *contents = [fileManager contentsOfDirectoryAtPath:frameworksPath error:nil]; + for (NSString *path in contents) { + NSString *fullPath = [frameworksPath stringByAppendingPathComponent:path]; + BOOL isDirectory; + [fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory]; + if (isDirectory && [path hasSuffix:@".framework"] && ![[path lastPathComponent] isEqualToString:@"CydiaSubstrate.framework"]) { + NSString *frameworkBinary = [fullPath stringByAppendingPathComponent:path.lastPathComponent.stringByDeletingPathExtension]; + NSString *error = LCParseMachO(frameworkBinary.UTF8String, ^(const char *path, struct mach_header_64 *header) { + LCPatchLibrary(path, header); + }); + if (error) { + completetionHandler(error); + return; + } + } else if ([path hasSuffix:@".dylib"] && ![[path lastPathComponent] isEqualToString:@"YouTubeDislikeReturn.dylib"]){ + NSString *error = LCParseMachO(fullPath.UTF8String, ^(const char *path, struct mach_header_64 *header) { + LCPatchLibrary(path, header); + }); + if (error) { + completetionHandler(error); + return; + } + } + } + if (error) { completetionHandler(error); return; diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 36c1b36..7508493 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -77,6 +77,13 @@ class LCAppModel: ObservableObject, Hashable { try await signApp(force: false) UserDefaults.standard.set(self.appInfo.relativeBundlePath, forKey: "selected") + if let selectedLanguage = self.appInfo.selectedLanguage { + // save livecontainer's own language + UserDefaults.standard.set(UserDefaults.standard.object(forKey: "AppleLanguages"), forKey:"LCLastLanguages") + // set user selected language + UserDefaults.standard.set([selectedLanguage], forKey: "AppleLanguages") + } + if appInfo.isJITNeeded { await self.jitLaunch() } else { diff --git a/LiveContainerSwiftUI/LCMachOUtils.m b/LiveContainerSwiftUI/LCMachOUtils.m index 66c5b99..6e65293 100644 --- a/LiveContainerSwiftUI/LCMachOUtils.m +++ b/LiveContainerSwiftUI/LCMachOUtils.m @@ -81,6 +81,32 @@ void LCPatchExecSlice(const char *path, struct mach_header_64 *header) { } } +void LCPatchLibrary(const char *path, struct mach_header_64 *header) { + uint8_t *imageHeaderPtr = (uint8_t*)header + sizeof(struct mach_header_64); + BOOL hasDylibCommand = NO, + hasLoaderCommand = NO; + const char *tweakLoaderPath = "@executable_path/../../Tweaks/TweakLoader.dylib"; + struct load_command *command = (struct load_command *)imageHeaderPtr; + for(int i = 0; i < header->ncmds > 0; i++) { + if(command->cmd == LC_ID_DYLIB) { + hasDylibCommand = YES; + } else if(command->cmd == LC_LOAD_DYLIB) { + struct dylib_command *dylib = (struct dylib_command *)command; + char *dylibName = (void *)dylib + dylib->dylib.name.offset; + if (!strncmp(dylibName, tweakLoaderPath, strlen(tweakLoaderPath))) { + hasLoaderCommand = YES; + } + } + command = (struct load_command *)((void *)command + command->cmdsize); + } + if (!hasDylibCommand) { + insertDylibCommand(LC_ID_DYLIB, path, header); + } + if (!hasLoaderCommand) { + insertDylibCommand(LC_LOAD_DYLIB, tweakLoaderPath, header); + } +} + NSString *LCParseMachO(const char *path, LCParseMachOCallback callback) { int fd = open(path, O_RDWR, (mode_t)0600); struct stat s; diff --git a/LiveContainerSwiftUI/LCUtils.h b/LiveContainerSwiftUI/LCUtils.h index 1c4f3bb..39d71af 100644 --- a/LiveContainerSwiftUI/LCUtils.h +++ b/LiveContainerSwiftUI/LCUtils.h @@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, Signer){ NSString *LCParseMachO(const char *path, LCParseMachOCallback callback); void LCPatchAddRPath(const char *path, struct mach_header_64 *header); void LCPatchExecSlice(const char *path, struct mach_header_64 *header); +void LCPatchLibrary(const char *path, struct mach_header_64 *header); void LCChangeExecUUID(struct mach_header_64 *header); void LCPatchAltStore(const char *path, struct mach_header_64 *header); diff --git a/TweakLoader/dispatch_cancelable_block_t.h b/TweakLoader/dispatch_cancelable_block_t.h deleted file mode 100644 index adb9032..0000000 --- a/TweakLoader/dispatch_cancelable_block_t.h +++ /dev/null @@ -1,66 +0,0 @@ -// https://gist.github.com/priore/0ae461cf6e12e64bdc0d -#ifndef dispatch_cancellable_block_h -#define dispatch_cancellable_block_h - -#import - -// https://github.com/SebastienThiebaud/dispatch_cancelable_block/issues/2 -typedef void(^dispatch_cancelable_block_t)(BOOL cancel, BOOL runNow); - -NS_INLINE dispatch_cancelable_block_t dispatch_after_delay(NSTimeInterval delay, dispatch_block_t block) { - if (block == nil) { - return nil; - } - - // First we have to create a new dispatch_cancelable_block_t and we also need to copy the block given (if you want more explanations about the __block storage type, read this: https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW6 - __block dispatch_cancelable_block_t cancelableBlock = nil; - __block dispatch_block_t originalBlock = [block copy]; - - // This block will be executed in NOW() + delay - dispatch_cancelable_block_t delayBlock = ^(BOOL cancel, BOOL runNow){ - if(runNow) { - originalBlock(); - originalBlock = nil; - return; - } - - if (cancel == NO && originalBlock) { - originalBlock(); - } - - // We don't want to hold any objects in the memory - originalBlock = nil; - }; - - cancelableBlock = [delayBlock copy]; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC), dispatch_get_main_queue(), ^{ - // We are now in the future (NOW() + delay). It means the block hasn't been canceled so we can execute it - if (cancelableBlock) { - cancelableBlock(NO, NO); - cancelableBlock = nil; - } - }); - - return cancelableBlock; -} - -NS_INLINE void cancel_block(dispatch_cancelable_block_t block) { - if (block == nil) { - return; - } - - block(YES, NO); - block = nil; -} - -NS_INLINE void run_block_now(dispatch_cancelable_block_t block) { - if (block == nil) { - return; - } - - block(NO, YES); - block = nil; -} - -#endif /* dispatch_cancellable_block_h */ diff --git a/ZSign/bundle.cpp b/ZSign/bundle.cpp index cc2a45f..6058e5d 100644 --- a/ZSign/bundle.cpp +++ b/ZSign/bundle.cpp @@ -409,13 +409,14 @@ bool ZAppBundle::SignNode(JValue &jvNode) jvCodeRes.readPListFile(strCodeResFile.c_str()); } + // LiveContainer don't need it if (m_bForceSign || jvCodeRes.isNull()) { //create - if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) - { - ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); - return false; - } +// if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) +// { +// ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); +// return false; +// } } else if (jvNode.has("changed")) { //use existsed diff --git a/main.m b/main.m index 705b95a..dc3087c 100644 --- a/main.m +++ b/main.m @@ -270,18 +270,6 @@ static void overwriteExecPath(NSString *bundlePath) { *path = appExecPath; overwriteExecPath(appBundle.bundlePath); - // save livecontainer's own language - NSArray* savedLaunguage = [lcUserDefaults objectForKey:@"LCLastLanguages"]; - if(!savedLaunguage) { - [lcUserDefaults setObject:[lcUserDefaults objectForKey:@"AppleLanguages"] forKey:@"LCLastLanguages"]; - } - // set user selected language - NSString* selectedLanguage = [appBundle infoDictionary][@"LCSelectedLanguage"]; - if(selectedLanguage) { - [lcUserDefaults setObject:@[selectedLanguage] forKey:@"AppleLanguages"]; - } - - // Overwrite NSUserDefaults NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.bundleIdentifier]; From fee7f51403d96172e391b2414269b3567dea2510 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 29 Nov 2024 12:26:44 +0800 Subject: [PATCH 15/32] move NSUserDefaults hook to main & rollback patch version --- LiveContainerSwiftUI/LCAppInfo.m | 32 +------------------ LiveContainerSwiftUI/LCMachOUtils.m | 26 --------------- Makefile | 4 +-- .../NSUserDefaults.m => NSUserDefaults.m | 18 ++++++++--- TweakLoader/Makefile | 2 +- main.m | 6 ++++ 6 files changed, 23 insertions(+), 65 deletions(-) rename TweakLoader/NSUserDefaults.m => NSUserDefaults.m (94%) diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 3909476..6358ecd 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -212,42 +212,12 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo } // Update patch - int currentPatchRev = 6; + int currentPatchRev = 5; if ([info[@"LCPatchRevision"] intValue] < currentPatchRev) { - // we must re-sign after upgrading patch version - forceSign = YES; NSString *execPath = [NSString stringWithFormat:@"%@/%@", appPath, info[@"CFBundleExecutable"]]; NSString *error = LCParseMachO(execPath.UTF8String, ^(const char *path, struct mach_header_64 *header) { LCPatchExecSlice(path, header); }); - // patch all libraries - NSFileManager *fileManager = [NSFileManager defaultManager]; - NSString* frameworksPath = [appPath stringByAppendingPathComponent:@"Frameworks"]; - NSArray *contents = [fileManager contentsOfDirectoryAtPath:frameworksPath error:nil]; - for (NSString *path in contents) { - NSString *fullPath = [frameworksPath stringByAppendingPathComponent:path]; - BOOL isDirectory; - [fileManager fileExistsAtPath:fullPath isDirectory:&isDirectory]; - if (isDirectory && [path hasSuffix:@".framework"] && ![[path lastPathComponent] isEqualToString:@"CydiaSubstrate.framework"]) { - NSString *frameworkBinary = [fullPath stringByAppendingPathComponent:path.lastPathComponent.stringByDeletingPathExtension]; - NSString *error = LCParseMachO(frameworkBinary.UTF8String, ^(const char *path, struct mach_header_64 *header) { - LCPatchLibrary(path, header); - }); - if (error) { - completetionHandler(error); - return; - } - } else if ([path hasSuffix:@".dylib"] && ![[path lastPathComponent] isEqualToString:@"YouTubeDislikeReturn.dylib"]){ - NSString *error = LCParseMachO(fullPath.UTF8String, ^(const char *path, struct mach_header_64 *header) { - LCPatchLibrary(path, header); - }); - if (error) { - completetionHandler(error); - return; - } - } - } - if (error) { completetionHandler(error); return; diff --git a/LiveContainerSwiftUI/LCMachOUtils.m b/LiveContainerSwiftUI/LCMachOUtils.m index 6e65293..66c5b99 100644 --- a/LiveContainerSwiftUI/LCMachOUtils.m +++ b/LiveContainerSwiftUI/LCMachOUtils.m @@ -81,32 +81,6 @@ void LCPatchExecSlice(const char *path, struct mach_header_64 *header) { } } -void LCPatchLibrary(const char *path, struct mach_header_64 *header) { - uint8_t *imageHeaderPtr = (uint8_t*)header + sizeof(struct mach_header_64); - BOOL hasDylibCommand = NO, - hasLoaderCommand = NO; - const char *tweakLoaderPath = "@executable_path/../../Tweaks/TweakLoader.dylib"; - struct load_command *command = (struct load_command *)imageHeaderPtr; - for(int i = 0; i < header->ncmds > 0; i++) { - if(command->cmd == LC_ID_DYLIB) { - hasDylibCommand = YES; - } else if(command->cmd == LC_LOAD_DYLIB) { - struct dylib_command *dylib = (struct dylib_command *)command; - char *dylibName = (void *)dylib + dylib->dylib.name.offset; - if (!strncmp(dylibName, tweakLoaderPath, strlen(tweakLoaderPath))) { - hasLoaderCommand = YES; - } - } - command = (struct load_command *)((void *)command + command->cmdsize); - } - if (!hasDylibCommand) { - insertDylibCommand(LC_ID_DYLIB, path, header); - } - if (!hasLoaderCommand) { - insertDylibCommand(LC_LOAD_DYLIB, tweakLoaderPath, header); - } -} - NSString *LCParseMachO(const char *path, LCParseMachOCallback callback) { int fd = open(path, O_RDWR, (mode_t)0600); struct stat s; diff --git a/Makefile b/Makefile index 7ea72e0..c38d8f2 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ export CONFIG_COMMIT = $(shell git log --oneline | sed '2,10000000d' | cut -b 1- # Build the app APPLICATION_NAME = LiveContainer -$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m fishhook/fishhook.c LCSharedUtils.m +$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m fishhook/fishhook.c LCSharedUtils.m NSUserDefaults.m $(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml $(APPLICATION_NAME)_CFLAGS = -fobjc-arc $(APPLICATION_NAME)_LDFLAGS = -e _LiveContainerMain -rpath @loader_path/Frameworks @@ -29,4 +29,4 @@ before-package:: @mv $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer $(THEOS_STAGING_DIR)/Applications/LiveContainer.app/LiveContainer_PleaseDoNotShortenTheExecutableNameBecauseItIsUsedToReserveSpaceForOverwritingThankYou before-all:: - @sh ./download_openssl.sh \ No newline at end of file + @sh ./download_openssl.sh diff --git a/TweakLoader/NSUserDefaults.m b/NSUserDefaults.m similarity index 94% rename from TweakLoader/NSUserDefaults.m rename to NSUserDefaults.m index 5440e22..c848d31 100644 --- a/TweakLoader/NSUserDefaults.m +++ b/NSUserDefaults.m @@ -2,7 +2,7 @@ // NSUserDefaults.m // LiveContainer // -// Created by s s on 2024/11/23. +// Created by s s on 2024/11/29. // #import @@ -11,10 +11,20 @@ #import "utils.h" #import "LCSharedUtils.h" +void swizzle(Class class, SEL originalAction, SEL swizzledAction) { + method_exchangeImplementations(class_getInstanceMethod(class, originalAction), class_getInstanceMethod(class, swizzledAction)); +} +@interface NSUserDefaults(LiveContainer) ++ (instancetype)lcSharedDefaults; ++ (instancetype)lcUserDefaults; ++ (NSString *)lcAppUrlScheme; ++ (NSString *)lcAppGroupPath; +@end + + NSMutableDictionary* LCPreferences = 0; -__attribute__((constructor)) -static void UIKitGuestHooksInit() { +void NUDGuestHooksInit() { NSLog(@"[LC] hook init"); swizzle(NSUserDefaults.class, @selector(objectForKey:), @selector(hook_objectForKey:)); swizzle(NSUserDefaults.class, @selector(boolForKey:), @selector(hook_boolForKey:)); @@ -33,13 +43,11 @@ static void UIKitGuestHooksInit() { [fm createDirectoryAtPath:preferenceFolderPath.path withIntermediateDirectories:YES attributes:@{} error:&error]; } - // flush any scheduled write to disk now [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationWillTerminateNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull notification) { // restore language if needed - NSArray* savedLaunguage = [NSUserDefaults.lcUserDefaults objectForKey:@"LCLastLanguages"]; if(savedLaunguage) { [NSUserDefaults.lcUserDefaults setObject:savedLaunguage forKey:@"AppleLanguages"]; diff --git a/TweakLoader/Makefile b/TweakLoader/Makefile index e7c665e..6feec1c 100644 --- a/TweakLoader/Makefile +++ b/TweakLoader/Makefile @@ -6,7 +6,7 @@ include $(THEOS)/makefiles/common.mk LIBRARY_NAME = TweakLoader -TweakLoader_FILES = NSUserDefaults.m TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m +TweakLoader_FILES = TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m TweakLoader_CFLAGS = -objc-arc TweakLoader_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks diff --git a/main.m b/main.m index dc3087c..3e43c8f 100644 --- a/main.m +++ b/main.m @@ -23,6 +23,8 @@ NSString* lcAppUrlScheme; NSBundle* lcMainBundle; +void NUDGuestHooksInit(); + @implementation NSUserDefaults(LiveContainer) + (instancetype)lcUserDefaults { return lcUserDefaults; @@ -363,6 +365,10 @@ static void overwriteExecPath(NSString *bundlePath) { [NSProcessInfo.processInfo performSelector:@selector(setArguments:) withObject:objcArgv]; NSProcessInfo.processInfo.processName = appBundle.infoDictionary[@"CFBundleExecutable"]; *_CFGetProgname() = NSProcessInfo.processInfo.processName.UTF8String; + + // hook NSUserDefault before running libraries' initializers + NUDGuestHooksInit(); + // Preload executable to bypass RT_NOLOAD uint32_t appIndex = _dyld_image_count(); void *appHandle = dlopen(*path, RTLD_LAZY|RTLD_GLOBAL|RTLD_FIRST); From 621dab0b55d4743063e66feaf26584e6aac36f33 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 29 Nov 2024 18:42:04 +0800 Subject: [PATCH 16/32] saperate keychain --- LiveContainerSwiftUI/LCAppBanner.swift | 2 + LiveContainerSwiftUI/LCSettingsView.swift | 1 + LiveContainerSwiftUI/Shared.swift | 15 +++ Makefile | 2 +- TweakLoader/Makefile | 2 +- TweakLoader/SecItem.m | 143 ++++++++++++++++++++++ 6 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 TweakLoader/SecItem.m diff --git a/LiveContainerSwiftUI/LCAppBanner.swift b/LiveContainerSwiftUI/LCAppBanner.swift index 5682d5c..ee9c10e 100644 --- a/LiveContainerSwiftUI/LCAppBanner.swift +++ b/LiveContainerSwiftUI/LCAppBanner.swift @@ -311,6 +311,8 @@ struct LCAppBanner : View { let dataFolderPath = LCPath.dataPath.appendingPathComponent(dataUUID) try fm.removeItem(at: dataFolderPath) + LCUtils.removeAppKeychain(dataUUID: dataUUID) + DispatchQueue.main.async { self.appDataFolders.removeAll(where: { f in return f == dataUUID diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index cb132f5..f4cc725 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -445,6 +445,7 @@ struct LCSettingsView: View { let fm = FileManager() for folder in foldersToDelete { try fm.removeItem(at: LCPath.dataPath.appendingPathComponent(folder)) + LCUtils.removeAppKeychain(dataUUID: folder) self.appDataFolderNames.removeAll(where: { s in return s == folder }) diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index e3539a4..4ab838b 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -9,6 +9,7 @@ import SwiftUI import UniformTypeIdentifiers import LocalAuthentication import SafariServices +import Security struct LCPath { public static let docPath = { @@ -577,4 +578,18 @@ extension LCUtils { return "Unknown Store" } } + + public static func removeAppKeychain(dataUUID label: String) { + [kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, kSecClassIdentity].forEach { + let status = SecItemDelete([ + kSecClass as String: $0, + kSecAttrLabel as String: label, + ] as CFDictionary) + if status != errSecSuccess && status != errSecItemNotFound { + //Error while removing class $0 + NSLog("[LC] Failed to find keychain items: \(status)") + } + } + } + } diff --git a/Makefile b/Makefile index c38d8f2..d305a07 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ export CONFIG_COMMIT = $(shell git log --oneline | sed '2,10000000d' | cut -b 1- # Build the app APPLICATION_NAME = LiveContainer -$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m fishhook/fishhook.c LCSharedUtils.m NSUserDefaults.m +$(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m LCSharedUtils.m NSUserDefaults.m $(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml $(APPLICATION_NAME)_CFLAGS = -fobjc-arc $(APPLICATION_NAME)_LDFLAGS = -e _LiveContainerMain -rpath @loader_path/Frameworks diff --git a/TweakLoader/Makefile b/TweakLoader/Makefile index 6feec1c..ae63081 100644 --- a/TweakLoader/Makefile +++ b/TweakLoader/Makefile @@ -6,7 +6,7 @@ include $(THEOS)/makefiles/common.mk LIBRARY_NAME = TweakLoader -TweakLoader_FILES = TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m +TweakLoader_FILES = SecItem.m TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m ../fishhook/fishhook.c TweakLoader_CFLAGS = -objc-arc TweakLoader_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks diff --git a/TweakLoader/SecItem.m b/TweakLoader/SecItem.m new file mode 100644 index 0000000..6e85ea3 --- /dev/null +++ b/TweakLoader/SecItem.m @@ -0,0 +1,143 @@ +// +// SecItem.m +// LiveContainer +// +// Created by s s on 2024/11/29. +// +#import +#import +#import "../fishhook/fishhook.h" + +NSString* SecItemLabelPrefix = 0; +OSStatus (*orig_SecItemAdd)(CFDictionaryRef attributes, CFTypeRef *result); +OSStatus (*orig_SecItemCopyMatching)(CFDictionaryRef query, CFTypeRef *result); +OSStatus (*orig_SecItemUpdate)(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); +OSStatus (*orig_SecItemDelete)(CFDictionaryRef query); + +OSStatus new_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { + NSMutableDictionary *attributesCopy = ((__bridge NSDictionary *)attributes).mutableCopy; + NSString *label = attributesCopy[(__bridge id)kSecAttrLabel]; + NSString* newLabel; + if(label) { + newLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, label]; + } else { + newLabel = SecItemLabelPrefix; + } + attributesCopy[(__bridge id)kSecAttrLabel] = newLabel; + + OSStatus status = orig_SecItemAdd((__bridge CFDictionaryRef)attributesCopy, result); + + if(status == errSecSuccess && result) { + id objcResult = (__bridge id)(*result); + // recover original label + if([objcResult isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary* finalQueryResult = [objcResult mutableCopy]; + NSString* origLabel = finalQueryResult[(__bridge id)kSecAttrLabel]; + finalQueryResult[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + *result = (__bridge CFTypeRef)finalQueryResult; + } else if ([objcResult isKindOfClass:[NSArray class]]) { + NSMutableArray* finalQueryResult = [objcResult mutableCopy]; + for(id item in finalQueryResult) { + if([item isKindOfClass:[NSDictionary class]]) { + NSString* origLabel = item[(__bridge id)kSecAttrLabel]; + item[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + } + + } + *result = (__bridge CFTypeRef)finalQueryResult; + } + return status; + } + return status; +} + +OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { + NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; + NSString *label = queryCopy[(__bridge id)kSecAttrLabel]; + NSString* newLabel; + if(label) { + newLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, label]; + } else { + newLabel = SecItemLabelPrefix; + } + queryCopy[(__bridge id)kSecAttrLabel] = newLabel; + + OSStatus status = orig_SecItemCopyMatching((__bridge CFDictionaryRef)queryCopy, result); + if(status == errSecSuccess && result) { + id objcResult = (__bridge id)(*result); + // recover original label + if([objcResult isKindOfClass:[NSDictionary class]]) { + NSMutableDictionary* finalQueryResult = [objcResult mutableCopy]; + NSString* origLabel = finalQueryResult[(__bridge id)kSecAttrLabel]; + finalQueryResult[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + *result = (__bridge CFTypeRef)finalQueryResult; + } else if ([objcResult isKindOfClass:[NSArray class]]) { + NSMutableArray* finalQueryResult = [objcResult mutableCopy]; + for(id item in finalQueryResult) { + if([item isKindOfClass:[NSDictionary class]]) { + NSString* origLabel = item[(__bridge id)kSecAttrLabel]; + item[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + } + + } + *result = (__bridge CFTypeRef)finalQueryResult; + } + return status; + } + + if(status != errSecItemNotFound) { + // return other error + return status; + } + + // try to find result in original keychain + status = orig_SecItemCopyMatching(query, result); + return status; +} + +OSStatus new_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { + NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; + NSString *queryLabel = queryCopy[(__bridge id)kSecAttrLabel]; + NSString* newQueryLabel; + if(queryLabel) { + newQueryLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, queryLabel]; + } else { + newQueryLabel = SecItemLabelPrefix; + } + queryCopy[(__bridge id)kSecAttrLabel] = newQueryLabel; + + NSMutableDictionary *attrCopy = ((__bridge NSDictionary *)attributesToUpdate).mutableCopy; + NSString *attrLabel = attrCopy[(__bridge id)kSecAttrLabel]; + NSString* newAttrLabel; + if(attrLabel) { + newAttrLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, queryLabel]; + } else { + newAttrLabel = SecItemLabelPrefix; + } + queryCopy[(__bridge id)kSecAttrLabel] = newAttrLabel; + + return orig_SecItemUpdate((__bridge CFDictionaryRef)queryCopy, (__bridge CFDictionaryRef)attrCopy); +} + +OSStatus new_SecItemDelete(CFDictionaryRef query){ + NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; + NSString *queryLabel = queryCopy[(__bridge id)kSecAttrLabel]; + NSString* newQueryLabel; + if(queryLabel) { + newQueryLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, queryLabel]; + } else { + newQueryLabel = SecItemLabelPrefix; + } + queryCopy[(__bridge id)kSecAttrLabel] = newQueryLabel; + + return orig_SecItemDelete((__bridge CFDictionaryRef)queryCopy); +} + +__attribute__((constructor)) +static void SecItemGuestHooksInit() { + SecItemLabelPrefix = [NSString stringWithUTF8String:getenv("HOME")].lastPathComponent; + rebind_symbols((struct rebinding[1]){{"SecItemAdd", (void *)new_SecItemAdd, (void **)&orig_SecItemAdd}},1); + rebind_symbols((struct rebinding[1]){{"SecItemCopyMatching", (void *)new_SecItemCopyMatching, (void **)&orig_SecItemCopyMatching}},1); + rebind_symbols((struct rebinding[1]){{"SecItemUpdate", (void *)new_SecItemUpdate, (void **)&orig_SecItemUpdate}},1); + rebind_symbols((struct rebinding[1]){{"SecItemDelete", (void *)new_SecItemDelete, (void **)&orig_SecItemDelete}},1); +} From 7f42bed460f7106fb3d185cc8adde774f9069e17 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 30 Nov 2024 16:14:56 +0800 Subject: [PATCH 17/32] impelement #219, solve #227 --- LiveContainerSwiftUI/LCSettingsView.swift | 40 ++++++++++++++--- LiveContainerSwiftUI/Localizable.xcstrings | 51 ++++++++++++++++++++++ NSUserDefaults.m | 1 - README.md | 2 +- TweakLoader/FBSSerialQueue.m | 9 ++++ 5 files changed, 95 insertions(+), 8 deletions(-) diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index f4cc725..14d7fb2 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -8,6 +8,12 @@ import Foundation import SwiftUI +enum PatchChoice { + case cancel + case autoPath + case archiveOnly +} + struct LCSettingsView: View { @State var errorShow = false @State var errorInfo = "" @@ -22,7 +28,7 @@ struct LCSettingsView: View { @State private var folderRemoveCount = 0 @StateObject private var keyChainRemovalAlert = YesNoHelper() - @StateObject private var patchAltStoreAlert = YesNoHelper() + @StateObject private var patchAltStoreAlert = AlertHelper() @State private var isAltStorePatched = false @State var isJitLessEnabled = false @@ -319,16 +325,29 @@ struct LCSettingsView: View { } .alert("lc.settings.patchStore %@".localizeWithFormat(LCUtils.getStoreName()), isPresented: $patchAltStoreAlert.show) { Button(role: .destructive) { - patchAltStoreAlert.close(result: true) + patchAltStoreAlert.close(result: .autoPath) } label: { Text("lc.common.ok".loc) } + if(isSideStore) { + Button { + patchAltStoreAlert.close(result: .archiveOnly) + } label: { + Text("lc.settings.patchStoreArchiveOnly".loc) + } + } + Button("lc.common.cancel".loc, role: .cancel) { - patchAltStoreAlert.close(result: false) + patchAltStoreAlert.close(result: .cancel) } } message: { - Text("lc.settings.patchStoreDesc %@ %@ %@ %@".localizeWithFormat(storeName, storeName, storeName, storeName)) + if(isSideStore) { + Text("lc.settings.patchStoreDesc %@ %@ %@ %@".localizeWithFormat(storeName, storeName, storeName, storeName) + "\n\n" + "lc.settings.patchStoreMultipleHint".loc) + } else { + Text("lc.settings.patchStoreDesc %@ %@ %@ %@".localizeWithFormat(storeName, storeName, storeName, storeName)) + } + } .onChange(of: isSignOnlyOnExpiration) { newValue in saveAppGroupItem(key: "LCSignOnlyOnExpiration", val: newValue) @@ -622,14 +641,23 @@ struct LCSettingsView: View { } func patchAltStore() async { - guard let result = await patchAltStoreAlert.open(), result else { + guard let result = await patchAltStoreAlert.open(), result != .cancel else { return } do { let altStoreIpa = try LCUtils.archiveTweakedAltStore() let storeInstallUrl = String(format: LCUtils.storeInstallURLScheme(), altStoreIpa.absoluteString) - await UIApplication.shared.open(URL(string: storeInstallUrl)!) + if(result == .archiveOnly) { + let movedAltStoreIpaUrl = LCPath.docPath.appendingPathComponent("Patched\(isSideStore ? "SideStore" : "AltStore").ipa") + try FileManager.default.moveItem(at: altStoreIpa, to: movedAltStoreIpaUrl) + successInfo = "lc.settings.patchStoreArchiveSuccess %@ %@".localizeWithFormat(storeName, storeName) + successShow = true + } else { + await UIApplication.shared.open(URL(string: storeInstallUrl)!) + } + + } catch { errorInfo = error.localizedDescription errorShow = true diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 3582714..3e489d0 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -2207,6 +2207,40 @@ } } }, + "lc.settings.patchStoreArchiveOnly" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Archive Only" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "仅打包" + } + } + } + }, + "lc.settings.patchStoreArchiveSuccess %@ %@" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Patched %@ has been saved to LiveContainer's document folder. Please install it with the %@ that LiveContainer is installed with." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "打包的%@已经保存到LiveContainer的“文件”文件夹,请将其手动安装到安装了LiveContainer的那个%@中。" + } + } + } + }, "lc.settings.patchStoreDesc %@ %@ %@ %@" : { "extractionState" : "manual", "localizations" : { @@ -2224,6 +2258,23 @@ } } }, + "lc.settings.patchStoreMultipleHint" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If you have multiple SideStore installed, please select “Archive Only” and install the tweaked IPA manually." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果你安装了多个SideStore,请选择“仅打包”并手动安装打过补丁的IPA。" + } + } + } + }, "lc.settings.renewJitLess" : { "extractionState" : "manual", "localizations" : { diff --git a/NSUserDefaults.m b/NSUserDefaults.m index c848d31..514bbfd 100644 --- a/NSUserDefaults.m +++ b/NSUserDefaults.m @@ -25,7 +25,6 @@ + (NSString *)lcAppGroupPath; NSMutableDictionary* LCPreferences = 0; void NUDGuestHooksInit() { - NSLog(@"[LC] hook init"); swizzle(NSUserDefaults.class, @selector(objectForKey:), @selector(hook_objectForKey:)); swizzle(NSUserDefaults.class, @selector(boolForKey:), @selector(hook_boolForKey:)); swizzle(NSUserDefaults.class, @selector(integerForKey:), @selector(hook_integerForKey:)); diff --git a/README.md b/README.md index 9de9716..6394022 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ make package ### ZSign - The app signer shipped with LiveContainer. - Originally made by [zhlynn](https://github.com/zhlynn/zsign). -- Referenced from [Feather](https://github.com/khcrysalis/Feather) by hcrysalis who adopted ZSign to iOS. +- LiveContainer uses [Feather's](https://github.com/khcrysalis/Feather) version of ZSign modified by hcrysalis. - Changes are made to meet LiveContainer's need. ## How does it work? diff --git a/TweakLoader/FBSSerialQueue.m b/TweakLoader/FBSSerialQueue.m index e5e673a..218227b 100644 --- a/TweakLoader/FBSSerialQueue.m +++ b/TweakLoader/FBSSerialQueue.m @@ -1,5 +1,6 @@ #import #import "utils.h" +#import @interface FBSSerialQueue1 : NSObject -(void)assertBarrierOnQueue1; @@ -15,6 +16,14 @@ - (void)assertBarrierOnQueue2 { } @end +@implementation VSSubscriptionRegistrationCenter(LiveContainerHook) + +- (void)setCurrentSubscription:(id)sub { + +} + +@end + __attribute__((constructor)) static void NSFMGuestHooksInit() { if(![[NSBundle.mainBundle infoDictionary][@"bypassAssertBarrierOnQueue"] boolValue]) { From 1b7cc268ef9727f53855c0b32670ff804065feef Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sun, 1 Dec 2024 14:28:36 +0800 Subject: [PATCH 18/32] fix boolForKey & zsign issue --- NSUserDefaults.m | 26 ++++++++++++++++++++++---- ZSign/macho.cpp | 2 ++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/NSUserDefaults.m b/NSUserDefaults.m index 514bbfd..8acff90 100644 --- a/NSUserDefaults.m +++ b/NSUserDefaults.m @@ -33,6 +33,7 @@ void NUDGuestHooksInit() { swizzle(NSUserDefaults.class, @selector(dictionaryRepresentation), @selector(hook_dictionaryRepresentation)); swizzle(NSUserDefaults.class, @selector(persistentDomainForName:), @selector(hook_persistentDomainForName:)); swizzle(NSUserDefaults.class, @selector(removePersistentDomainForName:), @selector(hook_removePersistentDomainForName:)); + swizzle(NSUserDefaults.class, @selector(registerDefaults:), @selector(hook_registerDefaults:)); LCPreferences = [[NSMutableDictionary alloc] init]; NSFileManager* fm = NSFileManager.defaultManager; NSURL* libraryPath = [fm URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask].lastObject; @@ -108,7 +109,8 @@ - (BOOL)hook_boolForKey:(NSString*)key { } else if ([obj isKindOfClass:[NSNumber class]]) { return [(NSNumber*)obj boolValue]; } else if([obj isKindOfClass:[NSString class]]) { - if([[(NSString*)obj lowercaseString] isEqualToString:@"yes"] || [[(NSString*)obj lowercaseString] isEqualToString:@"true"]) { + NSString* lowered = [(NSString*)obj lowercaseString]; + if([lowered isEqualToString:@"yes"] || [lowered isEqualToString:@"true"] || [lowered boolValue]) { return YES; } else { return NO; @@ -159,7 +161,6 @@ - (void)hook_removeObjectForKey:(NSString*)key { if(!preferenceDict) { return; } - [preferenceDict removeObjectForKey:key]; LCSavePreference(); } @@ -169,7 +170,9 @@ - (NSDictionary*) hook_dictionaryRepresentation { NSString* identifier = [self _identifier]; NSMutableDictionary* ans = [[self hook_dictionaryRepresentation] mutableCopy]; if(ans) { - [ans addEntriesFromDictionary:LCGetPreference(identifier)]; + @synchronized (LCPreferences) { + [ans addEntriesFromDictionary:LCGetPreference(identifier)]; + } } else { ans = LCGetPreference(identifier); } @@ -180,7 +183,9 @@ - (NSDictionary*) hook_dictionaryRepresentation { - (NSDictionary*) hook_persistentDomainForName:(NSString*)domainName { NSMutableDictionary* ans = [[self hook_persistentDomainForName:domainName] mutableCopy]; if(ans) { - [ans addEntriesFromDictionary:LCGetPreference(domainName)]; + @synchronized (LCPreferences) { + [ans addEntriesFromDictionary:LCGetPreference(domainName)]; + } } else { ans = LCGetPreference(domainName); } @@ -207,4 +212,17 @@ - (void) hook_removePersistentDomainForName:(NSString*)domainName { } } +- (void) hook_registerDefaults:(NSDictionary *)registrationDictionary { + NSString* identifier = [self _identifier]; + @synchronized (LCPreferences) { + NSMutableDictionary* preferenceDict = LCGetPreference(identifier); + if(!preferenceDict) { + preferenceDict = [[NSMutableDictionary alloc] init]; + LCPreferences[identifier] = preferenceDict; + } + [preferenceDict addEntriesFromDictionary:registrationDictionary]; + LCSavePreference(); + } +} + @end diff --git a/ZSign/macho.cpp b/ZSign/macho.cpp index 745b132..15aea21 100644 --- a/ZSign/macho.cpp +++ b/ZSign/macho.cpp @@ -4,6 +4,7 @@ #include "openssl.h" #include "signing.h" #include "macho.h" +#include "Utils.hpp" ZMachO::ZMachO() { @@ -119,6 +120,7 @@ bool ZMachO::CloseFile() ZLog::ErrorV(">>> CodeSign Write(munmap) Failed! Error: %p, %lu, %s\n", m_pBase, m_sSize, strerror(errno)); return false; } + refreshFile(m_strFile.c_str()); return true; } From 7b23e9ef9b51b8839ab2bb01d35d7244285164f2 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Mon, 2 Dec 2024 22:09:13 +0800 Subject: [PATCH 19/32] new file picker & notification fix, impelement #234 --- LiveContainerSwiftUI/LCAppInfo.h | 1 + LiveContainerSwiftUI/LCAppInfo.m | 25 ++++++- LiveContainerSwiftUI/LCAppListView.swift | 27 +++---- LiveContainerSwiftUI/LCAppModel.swift | 2 + LiveContainerSwiftUI/LCAppSettingsView.swift | 21 +++++- LiveContainerSwiftUI/LCSettingsView.swift | 2 +- LiveContainerSwiftUI/LCTweaksView.swift | 18 ++--- LiveContainerSwiftUI/Localizable.xcstrings | 38 +++++++++- LiveContainerSwiftUI/Shared.swift | 74 ++++++++++++++++++++ README.md | 6 +- main.m | 7 +- 11 files changed, 180 insertions(+), 41 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppInfo.h b/LiveContainerSwiftUI/LCAppInfo.h index 6291558..b8af535 100644 --- a/LiveContainerSwiftUI/LCAppInfo.h +++ b/LiveContainerSwiftUI/LCAppInfo.h @@ -15,6 +15,7 @@ @property bool bypassAssertBarrierOnQueue; @property UIColor* cachedColor; @property Signer signer; +@property bool doUseLCBundleId; @property NSString* selectedLanguage; - (void)setBundlePath:(NSString*)newBundlePath; diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 6358ecd..6bd7fe3 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -65,7 +65,11 @@ - (NSString*)version { } - (NSString*)bundleIdentifier { - return _info[@"CFBundleIdentifier"]; + if([self doUseLCBundleId]) { + return _info[@"LCOrignalBundleIdentifier"]; + } else { + return _info[@"CFBundleIdentifier"]; + } } - (NSString*)dataUUID { @@ -380,6 +384,25 @@ - (void)setDoSymlinkInbox:(bool)doSymlinkInbox { } +- (bool)doUseLCBundleId { + if(_info[@"doUseLCBundleId"] != nil) { + return [_info[@"doUseLCBundleId"] boolValue]; + } else { + return NO; + } +} +- (void)setDoUseLCBundleId:(bool)doUseLCBundleId { + _info[@"doUseLCBundleId"] = [NSNumber numberWithBool:doUseLCBundleId]; + if(doUseLCBundleId) { + _info[@"LCOrignalBundleIdentifier"] = _info[@"CFBundleIdentifier"]; + _info[@"CFBundleIdentifier"] = NSBundle.mainBundle.bundleIdentifier; + } else if (_info[@"LCOrignalBundleIdentifier"]) { + _info[@"CFBundleIdentifier"] = _info[@"LCOrignalBundleIdentifier"]; + [_info removeObjectForKey:@"LCOrignalBundleIdentifier"]; + } + [self save]; +} + - (bool)bypassAssertBarrierOnQueue { if(_info[@"bypassAssertBarrierOnQueue"] != nil) { return [_info[@"bypassAssertBarrierOnQueue"] boolValue]; diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 5198290..3a9ccf1 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -168,16 +168,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { if sharedModel.multiLCStatus != 2 { if !installprogressVisible { Button("Add".loc, systemImage: "plus", action: { - if choosingIPA { - choosingIPA = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { - choosingIPA = true - }) - } else { - choosingIPA = true - } - - + choosingIPA = true }) } else { ProgressView().progressViewStyle(.circular) @@ -203,9 +194,11 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } message: { Text(errorInfo) } - .fileImporter(isPresented: $choosingIPA, allowedContentTypes: [.ipa]) { result in - Task { await startInstallApp(result) } - } + .betterFileImporter(isPresented: $choosingIPA, types: [.ipa], multiple: false, callback: { fileUrls in + Task { await startInstallApp(fileUrls[0]) } + }, onDismiss: { + choosingIPA = false + }) .alert("lc.appList.installation".loc, isPresented: $installReplaceAlert.show) { ForEach(installOptions, id: \.self) { installOption in Button(role: installOption.isReplace ? .destructive : nil, action: { @@ -332,9 +325,8 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { - func startInstallApp(_ result:Result) async { + func startInstallApp(_ fileUrl:URL) async { do { - let fileUrl = try result.get() self.installprogressVisible = true try await installIpaFile(fileUrl) } catch { @@ -349,9 +341,6 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } func installIpaFile(_ url:URL) async throws { - if(!url.startAccessingSecurityScopedResource()) { - throw "lc.appList.ipaAccessError".loc; - } let fm = FileManager() let installProgress = Progress.discreteProgress(totalUnitCount: 100) @@ -370,7 +359,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { // decompress await decompress(url.path, fm.temporaryDirectory.path, decompressProgress) - url.stopAccessingSecurityScopedResource() + try fm.removeItem(at: url) let payloadContents = try fm.contentsOfDirectory(atPath: payloadPath.path) var appBundleName : String? = nil diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 7508493..062eedf 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -21,6 +21,7 @@ class LCAppModel: ObservableObject, Hashable { @Published var uiDataFolder : String? @Published var uiTweakFolder : String? @Published var uiDoSymlinkInbox : Bool + @Published var uiUseLCBundleId : Bool @Published var uiBypassAssertBarrierOnQueue : Bool @Published var uiSigner : Signer @Published var uiSelectedLanguage : String @@ -48,6 +49,7 @@ class LCAppModel: ObservableObject, Hashable { self.uiDoSymlinkInbox = appInfo.doSymlinkInbox self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue self.uiSigner = appInfo.signer + self.uiUseLCBundleId = appInfo.doUseLCBundleId } static func == (lhs: LCAppModel, rhs: LCAppModel) -> Bool { diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index edd7b50..34a06dc 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -240,6 +240,19 @@ struct LCAppSettingsView : View{ + Section { + Toggle(isOn: $model.uiUseLCBundleId) { + Text("lc.appSettings.useLCBundleId".loc) + } + .onChange(of: model.uiUseLCBundleId, perform: { newValue in + Task { await setDoUseLCBundleId(newValue) } + }) + } header: { + Text("lc.appSettings.fixes".loc) + } footer: { + Text("lc.appSettings.useLCBundleIdDesc".loc) + } + Section { Toggle(isOn: $model.uiDoSymlinkInbox) { Text("lc.appSettings.fixFilePicker".loc) @@ -247,8 +260,6 @@ struct LCAppSettingsView : View{ .onChange(of: model.uiDoSymlinkInbox, perform: { newValue in Task { await setSimlinkInbox(newValue) } }) - } header: { - Text("lc.appSettings.fixes".loc) } footer: { Text("lc.appSettings.fixFilePickerDesc".loc) } @@ -473,7 +484,11 @@ struct LCAppSettingsView : View{ func setSimlinkInbox(_ simlinkInbox : Bool) async { appInfo.doSymlinkInbox = simlinkInbox model.uiDoSymlinkInbox = simlinkInbox - + } + + func setDoUseLCBundleId(_ doUseLCBundleId : Bool) async { + appInfo.doUseLCBundleId = doUseLCBundleId + model.uiUseLCBundleId = doUseLCBundleId } func loadSupportedLanguages() { diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index 14d7fb2..a49d294 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -102,7 +102,7 @@ struct LCSettingsView: View { if isAltStorePatched { Button { - testJITLessMode() + testJITLessMode() } label: { Text("lc.settings.testJitLess".loc) } diff --git a/LiveContainerSwiftUI/LCTweaksView.swift b/LiveContainerSwiftUI/LCTweaksView.swift index 7f1a552..9974098 100644 --- a/LiveContainerSwiftUI/LCTweaksView.swift +++ b/LiveContainerSwiftUI/LCTweaksView.swift @@ -191,9 +191,11 @@ struct LCTweakFolderView : View { renameFileInput.close(result: "") } ) - .fileImporter(isPresented: $choosingTweak, allowedContentTypes: [.dylib, .lcFramework, /*.deb*/], allowsMultipleSelection: true) { result in - Task { await startInstallTweak(result) } - } + .betterFileImporter(isPresented: $choosingTweak, types: [.dylib, .lcFramework, /*.deb*/], multiple: true, callback: { fileUrls in + Task { await startInstallTweak(fileUrls) } + }, onDismiss: { + choosingTweak = false + }) } func deleteTweakItem(indexSet: IndexSet) { @@ -319,27 +321,21 @@ struct LCTweakFolderView : View { } } - func startInstallTweak(_ result: Result<[URL], any Error>) async { + func startInstallTweak(_ urls: [URL]) async { do { let fm = FileManager() - let urls = try result.get() // we will sign later before app launch for fileUrl in urls { // handle deb file - if(!fileUrl.startAccessingSecurityScopedResource()) { - throw "lc.tweakView.permissionDenied %@".localizeWithFormat(fileUrl.lastPathComponent) - } if(!fileUrl.isFileURL) { throw "lc.tweakView.notFileError %@".localizeWithFormat(fileUrl.lastPathComponent) } let toPath = self.baseUrl.appendingPathComponent(fileUrl.lastPathComponent) - try fm.copyItem(at: fileUrl, to: toPath) + try fm.moveItem(at: fileUrl, to: toPath) LCParseMachO((toPath.path as NSString).utf8String) { path, header in LCPatchAddRPath(path, header); } - fileUrl.stopAccessingSecurityScopedResource() - let isFramework = toPath.lastPathComponent.hasSuffix(".framework") let isTweak = toPath.lastPathComponent.hasSuffix(".dylib") diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 3e489d0..6e55df6 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -745,13 +745,13 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Fix File Picker" + "value" : "(Legacy) Fix File Picker" } }, "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "修复文件导入" + "value" : "(旧方法)修复文件导入" } } } @@ -1062,6 +1062,40 @@ } } }, + "lc.appSettings.useLCBundleId" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fix File Picker & Local Notification" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "修复文件导入和本地通知" + } + } + } + }, + "lc.appSettings.useLCBundleIdDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Fixes these issues by using LiveContainer’s bundle identifier when overwriting main bundle." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "通过使用LiveContainer的包名解决上述问题。" + } + } + } + }, "lc.common.auto" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 4ab838b..8ce93ad 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -176,6 +176,16 @@ extension View { self.modifier(TextFieldAlertModifier(isPresented: isPresented, title: title, text: text, placeholder: placeholder, action: action, actionCancel: actionCancel)) } + public func betterFileImporter( + isPresented: Binding, + types : [UTType], + multiple : Bool = false, + callback: @escaping ([URL]) -> (), + onDismiss: @escaping () -> Void + ) -> some View { + self.modifier(DocModifier(isPresented: isPresented, types: types, multiple: multiple, callback: callback, onDismiss: onDismiss)) + } + func onBackground(_ f: @escaping () -> Void) -> some View { self.onReceive( NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification), @@ -192,6 +202,70 @@ extension View { } +public struct DocModifier: ViewModifier { + + @State private var docController: UIDocumentPickerViewController? + @State private var delegate : UIDocumentPickerDelegate + + @Binding var isPresented: Bool + + var callback: ([URL]) -> () + private let onDismiss: () -> Void + private let types : [UTType] + private let multiple : Bool + + init(isPresented : Binding, types : [UTType], multiple : Bool, callback: @escaping ([URL]) -> (), onDismiss: @escaping () -> Void) { + self.callback = callback + self.onDismiss = onDismiss + self.types = types + self.multiple = multiple + self.delegate = Coordinator(callback: callback, onDismiss: onDismiss) + self._isPresented = isPresented + } + + public func body(content: Content) -> some View { + content.onChange(of: isPresented) { isPresented in + if isPresented, docController == nil { + let controller = UIDocumentPickerViewController(forOpeningContentTypes: types, asCopy: true) + controller.allowsMultipleSelection = multiple + controller.delegate = delegate + self.docController = controller + guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { + return + } + scene.windows.first?.rootViewController?.present(controller, animated: true) + } else if !isPresented, let docController = docController { + docController.dismiss(animated: true) + self.docController = nil + } + } + } + + private func shutdown() { + isPresented = false + docController = nil + } + + class Coordinator: NSObject, UIDocumentPickerDelegate { + var callback: ([URL]) -> () + private let onDismiss: () -> Void + + init(callback: @escaping ([URL]) -> Void, onDismiss: @escaping () -> Void) { + self.callback = callback + self.onDismiss = onDismiss + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + callback(urls) + onDismiss() + } + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + onDismiss() + } + } + +} + public struct TextFieldAlertModifier: ViewModifier { @State private var alertController: UIAlertController? diff --git a/README.md b/README.md index 5cdcc19..c45b0f6 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Without JIT, guest apps need to be codesigned, which requires retrieving the cer - Install your app via the "Apps" tab. - Tap the run icon, it will attempt to restart LiveContainer with guest app loaded. -Note: If you update or reinstall SideStore/AltStore, you'll need to reapply the patch. +Note: If you update or reinstall SideStore/AltStore, you'll need to reapply the patch. Re-patch is not needed when you refresh your store. ### With JIT (requires SideStore) - Tap the play icon, it will jump to SideStore and exit. @@ -41,8 +41,8 @@ The first LiveContainer (blue icon) always launches by default. If an app is already running in the first container, you'll be prompted to either open it in the second LiveContainer (gray icon) or terminate the current app and relaunch it in the first. If the app is already running in the second container, it will switch automatically. To use an app in the second container, you must convert this app to a shared app. You can do that by opening the first LiveContainer (blue), long press on your app, open the settings of your app and then "Convert to Shared App". After that, you can launch your app using LiveContainer2 (grey). -### Fix File Picker -Some apps may experience issues with their file pickers in LiveContainer. To resolve this, enable "Fix File Picker" in the app-specific settings. +### Fix File Picker & Local Notification +Some apps may experience issues with their file pickers or not be able to apply for notification permission in LiveContainer. To resolve this, enable "Fix File Picker & Local Notification" in the app-specific settings. ### "Open In App" Support - Tap the link icon in the top-right corner of the "Apps" tab and input the URL. LiveContainer will detect the appropriate app and ask if you want to launch it. diff --git a/main.m b/main.m index 3e43c8f..8938b8b 100644 --- a/main.m +++ b/main.m @@ -273,7 +273,12 @@ static void overwriteExecPath(NSString *bundlePath) { overwriteExecPath(appBundle.bundlePath); // Overwrite NSUserDefaults - NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.bundleIdentifier]; + if([appBundle.infoDictionary[@"doUseLCBundleId"] boolValue]) { + NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.infoDictionary[@"LCOrignalBundleIdentifier"]]; + } else { + NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.bundleIdentifier]; + } + // Set & save the folder it it does not exist in Info.plist NSString* dataUUID = appBundle.infoDictionary[@"LCDataUUID"]; From 4cd537e92eb203ffee07e2b8bcb84b2fe3bc2030 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Tue, 3 Dec 2024 13:32:37 +0800 Subject: [PATCH 20/32] use kSecAttrAlias to fix Minecraft crashes hugeBlack/LiveContainer#6 --- LiveContainerSwiftUI/Shared.swift | 2 +- TweakLoader/SecItem.m | 91 +++++++++++-------------------- 2 files changed, 32 insertions(+), 61 deletions(-) diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 8ce93ad..342e622 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -657,7 +657,7 @@ extension LCUtils { [kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, kSecClassIdentity].forEach { let status = SecItemDelete([ kSecClass as String: $0, - kSecAttrLabel as String: label, + "alis": label, ] as CFDictionary) if status != errSecSuccess && status != errSecItemNotFound { //Error while removing class $0 diff --git a/TweakLoader/SecItem.m b/TweakLoader/SecItem.m index 6e85ea3..33c8f62 100644 --- a/TweakLoader/SecItem.m +++ b/TweakLoader/SecItem.m @@ -16,31 +16,20 @@ OSStatus new_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { NSMutableDictionary *attributesCopy = ((__bridge NSDictionary *)attributes).mutableCopy; - NSString *label = attributesCopy[(__bridge id)kSecAttrLabel]; - NSString* newLabel; - if(label) { - newLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, label]; - } else { - newLabel = SecItemLabelPrefix; - } - attributesCopy[(__bridge id)kSecAttrLabel] = newLabel; + attributesCopy[@"alis"] = SecItemLabelPrefix; OSStatus status = orig_SecItemAdd((__bridge CFDictionaryRef)attributesCopy, result); - - if(status == errSecSuccess && result) { + if(status == errSecSuccess && result && *result) { id objcResult = (__bridge id)(*result); - // recover original label - if([objcResult isKindOfClass:[NSDictionary class]]) { + if(CFGetTypeID(*result) == CFDictionaryGetTypeID()) { NSMutableDictionary* finalQueryResult = [objcResult mutableCopy]; - NSString* origLabel = finalQueryResult[(__bridge id)kSecAttrLabel]; - finalQueryResult[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + finalQueryResult[@"alis"] = @""; *result = (__bridge CFTypeRef)finalQueryResult; - } else if ([objcResult isKindOfClass:[NSArray class]]) { + } else if (CFGetTypeID(*result) == CFArrayGetTypeID()) { NSMutableArray* finalQueryResult = [objcResult mutableCopy]; for(id item in finalQueryResult) { if([item isKindOfClass:[NSDictionary class]]) { - NSString* origLabel = item[(__bridge id)kSecAttrLabel]; - item[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + item[@"alis"] = @""; } } @@ -53,30 +42,20 @@ OSStatus new_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; - NSString *label = queryCopy[(__bridge id)kSecAttrLabel]; - NSString* newLabel; - if(label) { - newLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, label]; - } else { - newLabel = SecItemLabelPrefix; - } - queryCopy[(__bridge id)kSecAttrLabel] = newLabel; + queryCopy[@"alis"] = SecItemLabelPrefix; OSStatus status = orig_SecItemCopyMatching((__bridge CFDictionaryRef)queryCopy, result); - if(status == errSecSuccess && result) { + if(status == errSecSuccess && result && *result) { id objcResult = (__bridge id)(*result); - // recover original label if([objcResult isKindOfClass:[NSDictionary class]]) { NSMutableDictionary* finalQueryResult = [objcResult mutableCopy]; - NSString* origLabel = finalQueryResult[(__bridge id)kSecAttrLabel]; - finalQueryResult[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + finalQueryResult[@"alis"] = @""; *result = (__bridge CFTypeRef)finalQueryResult; } else if ([objcResult isKindOfClass:[NSArray class]]) { NSMutableArray* finalQueryResult = [objcResult mutableCopy]; for(id item in finalQueryResult) { if([item isKindOfClass:[NSDictionary class]]) { - NSString* origLabel = item[(__bridge id)kSecAttrLabel]; - item[(__bridge id)kSecAttrLabel] = [origLabel substringFromIndex:[SecItemLabelPrefix length]]; + item[@"alis"] = @""; } } @@ -85,52 +64,44 @@ OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { return status; } - if(status != errSecItemNotFound) { - // return other error + if(status != errSecParam) { return status; } - // try to find result in original keychain + // if this search don't support comment, we just use the original search status = orig_SecItemCopyMatching(query, result); return status; } OSStatus new_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; - NSString *queryLabel = queryCopy[(__bridge id)kSecAttrLabel]; - NSString* newQueryLabel; - if(queryLabel) { - newQueryLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, queryLabel]; - } else { - newQueryLabel = SecItemLabelPrefix; - } - queryCopy[(__bridge id)kSecAttrLabel] = newQueryLabel; + queryCopy[@"alis"] = SecItemLabelPrefix; NSMutableDictionary *attrCopy = ((__bridge NSDictionary *)attributesToUpdate).mutableCopy; - NSString *attrLabel = attrCopy[(__bridge id)kSecAttrLabel]; - NSString* newAttrLabel; - if(attrLabel) { - newAttrLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, queryLabel]; - } else { - newAttrLabel = SecItemLabelPrefix; - } - queryCopy[(__bridge id)kSecAttrLabel] = newAttrLabel; + attrCopy[@"alis"] = SecItemLabelPrefix; - return orig_SecItemUpdate((__bridge CFDictionaryRef)queryCopy, (__bridge CFDictionaryRef)attrCopy); + OSStatus status = orig_SecItemUpdate((__bridge CFDictionaryRef)queryCopy, (__bridge CFDictionaryRef)attrCopy); + if(status != errSecParam) { + return status; + } + + // if this search don't support comment, we just use the original search + status = orig_SecItemUpdate(query, attributesToUpdate); + return status; } OSStatus new_SecItemDelete(CFDictionaryRef query){ NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; - NSString *queryLabel = queryCopy[(__bridge id)kSecAttrLabel]; - NSString* newQueryLabel; - if(queryLabel) { - newQueryLabel = [NSString stringWithFormat:@"%@.%@", SecItemLabelPrefix, queryLabel]; - } else { - newQueryLabel = SecItemLabelPrefix; - } - queryCopy[(__bridge id)kSecAttrLabel] = newQueryLabel; + queryCopy[@"alis"] = SecItemLabelPrefix; - return orig_SecItemDelete((__bridge CFDictionaryRef)queryCopy); + OSStatus status = orig_SecItemDelete((__bridge CFDictionaryRef)queryCopy); + if(status != errSecParam) { + return status; + } + + // if this search don't support comment, we just use the original search + status = orig_SecItemDelete(query); + return status; } __attribute__((constructor)) From 8c2112805638c3e41dd7efffcc4fc33dc70d98e5 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Wed, 4 Dec 2024 16:07:48 +0800 Subject: [PATCH 21/32] Fix [BUG] hugeBlack/LiveContainer#7 --- TweakLoader/SecItem.m | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/TweakLoader/SecItem.m b/TweakLoader/SecItem.m index 33c8f62..dadc562 100644 --- a/TweakLoader/SecItem.m +++ b/TweakLoader/SecItem.m @@ -17,7 +17,8 @@ OSStatus new_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { NSMutableDictionary *attributesCopy = ((__bridge NSDictionary *)attributes).mutableCopy; attributesCopy[@"alis"] = SecItemLabelPrefix; - + [attributesCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; + OSStatus status = orig_SecItemAdd((__bridge CFDictionaryRef)attributesCopy, result); if(status == errSecSuccess && result && *result) { id objcResult = (__bridge id)(*result); @@ -43,6 +44,7 @@ OSStatus new_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; queryCopy[@"alis"] = SecItemLabelPrefix; + [queryCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; OSStatus status = orig_SecItemCopyMatching((__bridge CFDictionaryRef)queryCopy, result); if(status == errSecSuccess && result && *result) { @@ -67,7 +69,7 @@ OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { if(status != errSecParam) { return status; } - + // if this search don't support comment, we just use the original search status = orig_SecItemCopyMatching(query, result); return status; @@ -76,9 +78,11 @@ OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { OSStatus new_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; queryCopy[@"alis"] = SecItemLabelPrefix; + [queryCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; NSMutableDictionary *attrCopy = ((__bridge NSDictionary *)attributesToUpdate).mutableCopy; attrCopy[@"alis"] = SecItemLabelPrefix; + [attrCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; OSStatus status = orig_SecItemUpdate((__bridge CFDictionaryRef)queryCopy, (__bridge CFDictionaryRef)attrCopy); if(status != errSecParam) { @@ -93,7 +97,8 @@ OSStatus new_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUp OSStatus new_SecItemDelete(CFDictionaryRef query){ NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; queryCopy[@"alis"] = SecItemLabelPrefix; - + [queryCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; + OSStatus status = orig_SecItemDelete((__bridge CFDictionaryRef)queryCopy); if(status != errSecParam) { return status; From 37592c715c2c2d3760ba824b1aabf2ed2fbb3602 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 6 Dec 2024 21:46:40 +0800 Subject: [PATCH 22/32] Multi-Account support --- LCSharedUtils.h | 4 + LCSharedUtils.m | 72 ++++ LiveContainerSwiftUI/AppDelegate.swift | 25 +- LiveContainerSwiftUI/LCAppBanner.swift | 50 ++- LiveContainerSwiftUI/LCAppInfo.h | 8 +- LiveContainerSwiftUI/LCAppInfo.m | 27 +- LiveContainerSwiftUI/LCAppListView.swift | 72 ++-- LiveContainerSwiftUI/LCAppModel.swift | 42 ++- LiveContainerSwiftUI/LCAppSettingsView.swift | 244 ++++++++----- LiveContainerSwiftUI/LCContainer.swift | 130 +++++++ LiveContainerSwiftUI/LCContainerView.swift | 255 ++++++++++++++ .../LCSelectContainerView.swift | 115 ++++++ LiveContainerSwiftUI/LCSettingsView.swift | 34 +- LiveContainerSwiftUI/LCTabView.swift | 10 +- LiveContainerSwiftUI/LCWebView.swift | 15 +- .../project.pbxproj | 12 + LiveContainerSwiftUI/Localizable.xcstrings | 327 +++++++++++++++++- LiveContainerSwiftUI/Shared.swift | 23 ++ README.md | 7 +- TweakLoader/SecItem.m | 96 ++--- TweakLoader/UIKit+GuestHooks.m | 32 +- TweakLoader/utils.h | 1 + entitlements.xml | 4 +- main.m | 46 ++- 24 files changed, 1360 insertions(+), 291 deletions(-) create mode 100644 LiveContainerSwiftUI/LCContainer.swift create mode 100644 LiveContainerSwiftUI/LCContainerView.swift create mode 100644 LiveContainerSwiftUI/LCSelectContainerView.swift diff --git a/LCSharedUtils.h b/LCSharedUtils.h index a8243fd..daeeeb6 100644 --- a/LCSharedUtils.h +++ b/LCSharedUtils.h @@ -8,9 +8,13 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url; + (void)setWebPageUrlForNextLaunch:(NSString*)urlString; + (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId; ++ (NSString*)getContainerUsingLCSchemeWithFolderName:(NSString*)folderName; + (void)setAppRunningByThisLC:(NSString*)bundleId; ++ (void)setContainerUsingByThisLC:(NSString*)folderName; + (void)moveSharedAppFolderBack; + (void)removeAppRunningByLC:(NSString*)LCScheme; ++ (void)removeContainerUsingByLC:(NSString*)LCScheme; + (NSBundle*)findBundleWithBundleId:(NSString*)bundleId; + (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)dataUUID; ++ (NSString*)findDefaultContainerWithBundleId:(NSString*)bundleId; @end diff --git a/LCSharedUtils.m b/LCSharedUtils.m index d972d3b..669df36 100644 --- a/LCSharedUtils.m +++ b/LCSharedUtils.m @@ -97,12 +97,15 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url { NSString* launchBundleId = nil; NSString* openUrl = nil; + NSString* containerFolderName = nil; for (NSURLQueryItem* queryItem in components.queryItems) { if ([queryItem.name isEqualToString:@"bundle-name"]) { launchBundleId = queryItem.value; } else if ([queryItem.name isEqualToString:@"open-url"]){ NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:queryItem.value options:0]; openUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + } else if ([queryItem.name isEqualToString:@"container-folder-name"]) { + containerFolderName = queryItem.value; } } if(launchBundleId) { @@ -112,6 +115,7 @@ + (BOOL)launchToGuestAppWithURL:(NSURL *)url { // Attempt to restart LiveContainer with the selected guest app [lcUserDefaults setObject:launchBundleId forKey:@"selected"]; + [lcUserDefaults setObject:containerFolderName forKey:@"selectedContainer"]; return [self launchToGuestApp]; } @@ -133,6 +137,17 @@ + (NSURL*)appLockPath { return infoPath; } ++ (NSURL*)containerLockPath { + static dispatch_once_t once; + static NSURL *infoPath; + + dispatch_once(&once, ^{ + NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; + infoPath = [appGroupPath URLByAppendingPathComponent:@"LiveContainer/containerLock.plist"]; + }); + return infoPath; +} + + (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId { NSURL* infoPath = [self appLockPath]; NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path]; @@ -152,6 +167,25 @@ + (NSString*)getAppRunningLCSchemeWithBundleId:(NSString*)bundleId { return nil; } ++ (NSString*)getContainerUsingLCSchemeWithFolderName:(NSString*)folderName { + NSURL* infoPath = [self containerLockPath]; + NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path]; + if (!info) { + return nil; + } + + for (NSString* key in info) { + if([folderName isEqualToString:info[key]]) { + if([key isEqualToString:lcAppUrlScheme]) { + return nil; + } + return key; + } + } + + return nil; +} + // if you pass null then remove this lc from appLock + (void)setAppRunningByThisLC:(NSString*)bundleId { NSURL* infoPath = [self appLockPath]; @@ -169,6 +203,22 @@ + (void)setAppRunningByThisLC:(NSString*)bundleId { } ++ (void)setContainerUsingByThisLC:(NSString*)folderName { + NSURL* infoPath = [self containerLockPath]; + + NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path]; + if (!info) { + info = [NSMutableDictionary new]; + } + if(folderName == nil) { + [info removeObjectForKey:lcAppUrlScheme]; + } else { + info[lcAppUrlScheme] = folderName; + } + [info writeToFile:infoPath.path atomically:YES]; + +} + + (void)removeAppRunningByLC:(NSString*)LCScheme { NSURL* infoPath = [self appLockPath]; @@ -181,6 +231,18 @@ + (void)removeAppRunningByLC:(NSString*)LCScheme { } ++ (void)removeContainerUsingByLC:(NSString*)LCScheme { + NSURL* infoPath = [self containerLockPath]; + + NSMutableDictionary *info = [NSMutableDictionary dictionaryWithContentsOfFile:infoPath.path]; + if (!info) { + return; + } + [info removeObjectForKey:LCScheme]; + [info writeToFile:infoPath.path atomically:YES]; + +} + // move app data to private folder to prevent 0xdead10cc https://forums.developer.apple.com/forums/thread/126438 + (void)moveSharedAppFolderBack { NSFileManager *fm = NSFileManager.defaultManager; @@ -259,4 +321,14 @@ + (void)dumpPreferenceToPath:(NSString*)plistLocationTo dataUUID:(NSString*)data [lcUserDefaults removeObjectForKey:dataUUID]; } ++ (NSString*)findDefaultContainerWithBundleId:(NSString*)bundleId { + // find app's default container + NSURL *appGroupPath = [NSFileManager.defaultManager containerURLForSecurityApplicationGroupIdentifier:[LCSharedUtils appGroupID]]; + NSURL* appGroupFolder = [appGroupPath URLByAppendingPathComponent:@"LiveContainer"]; + + NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", appGroupFolder.path, bundleId]; + NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath]; + return infoDict[@"LCDataUUID"]; +} + @end diff --git a/LiveContainerSwiftUI/AppDelegate.swift b/LiveContainerSwiftUI/AppDelegate.swift index 4c8a1da..699395e 100644 --- a/LiveContainerSwiftUI/AppDelegate.swift +++ b/LiveContainerSwiftUI/AppDelegate.swift @@ -6,7 +6,8 @@ import SwiftUI private static var urlStrToOpen: String? = nil private static var openUrlStrFunc: ((String) async -> Void)? private static var bundleToLaunch: String? = nil - private static var launchAppFunc: ((String) async -> Void)? + private static var containerToLaunch: String? = nil + private static var launchAppFunc: ((String, String?) async -> Void)? public static func setOpenUrlStrFunc(handler: @escaping ((String) async -> Void)){ self.openUrlStrFunc = handler @@ -19,10 +20,10 @@ import SwiftUI } } - public static func setLaunchAppFunc(handler: @escaping ((String) async -> Void)){ + public static func setLaunchAppFunc(handler: @escaping ((String, String?) async -> Void)){ self.launchAppFunc = handler if let bundleToLaunch = self.bundleToLaunch { - Task { await handler(bundleToLaunch) } + Task { await handler(bundleToLaunch, containerToLaunch) } self.bundleToLaunch = nil } } @@ -35,11 +36,12 @@ import SwiftUI } } - private static func launchApp(bundleId: String) { + private static func launchApp(bundleId: String, container: String?) { if launchAppFunc == nil { bundleToLaunch = bundleId + containerToLaunch = container } else { - Task { await launchAppFunc!(bundleId) } + Task { await launchAppFunc!(bundleId, container) } } } @@ -67,12 +69,18 @@ import SwiftUI } } else if url.host == "livecontainer-launch" { if let components = URLComponents(url: url, resolvingAgainstBaseURL: false) { + var bundleId : String? = nil + var containerName : String? = nil for queryItem in components.queryItems ?? [] { - if queryItem.name == "bundle-name", let bundleId = queryItem.value { - AppDelegate.launchApp(bundleId: bundleId) - break + if queryItem.name == "bundle-name", let bundleId1 = queryItem.value { + bundleId = bundleId1 + } else if queryItem.name == "container-folder-name", let containerName1 = queryItem.value { + containerName = containerName1 } } + if let bundleId { + AppDelegate.launchApp(bundleId: bundleId, container: containerName) + } } } @@ -82,6 +90,7 @@ import SwiftUI func applicationWillTerminate(_ application: UIApplication) { // Fix launching app if user opens JIT waiting dialog and kills the app. Won't trigger normally. UserDefaults.standard.removeObject(forKey: "selected") + UserDefaults.standard.removeObject(forKey: "selectedContainer") if (UserDefaults.standard.object(forKey: "LCLastLanguages") != nil) { // recover livecontainer's own language diff --git a/LiveContainerSwiftUI/LCAppBanner.swift b/LiveContainerSwiftUI/LCAppBanner.swift index ee9c10e..a074219 100644 --- a/LiveContainerSwiftUI/LCAppBanner.swift +++ b/LiveContainerSwiftUI/LCAppBanner.swift @@ -88,7 +88,7 @@ struct LCAppBanner : View { } Text("\(appInfo.version()) - \(appInfo.bundleIdentifier())").font(.system(size: 12)).foregroundColor(dynamicColors ? mainColor : Color("FontColor")) - Text(LocalizedStringKey(model.uiDataFolder == nil ? "lc.appBanner.noDataFolder".loc : model.uiDataFolder!)).font(.system(size: 8)).foregroundColor(dynamicColors ? mainColor : Color("FontColor")) + Text(model.uiSelectedContainer?.name ?? "lc.appBanner.noDataFolder".loc).font(.system(size: 8)).foregroundColor(dynamicColors ? mainColor : Color("FontColor")) }) } Spacer() @@ -141,6 +141,15 @@ struct LCAppBanner : View { }) .contextMenu{ + if model.uiContainers.count > 1 { + Picker(selection: $model.uiSelectedContainer , label: Text("Containers")) { + ForEach(model.uiContainers, id:\.self) { container in + Text(container.name).tag(container) + } + } + } + + Section(appInfo.relativeBundlePath) { if #available(iOS 16.0, *){ @@ -148,7 +157,7 @@ struct LCAppBanner : View { Text(appInfo.relativeBundlePath) } if !model.uiIsShared { - if model.uiDataFolder != nil { + if let container = model.uiSelectedContainer { Button { openDataFolder() } label: { @@ -283,7 +292,7 @@ struct LCAppBanner : View { func openDataFolder() { - let url = URL(string:"shareddocuments://\(LCPath.docPath.path)/Data/Application/\(appInfo.dataUUID()!)") + let url = URL(string:"shareddocuments://\(LCPath.docPath.path)/Data/Application/\(model.uiSelectedContainer!.folderName)") UIApplication.shared.open(url!) } @@ -296,7 +305,8 @@ struct LCAppBanner : View { } var doRemoveAppFolder = false - if self.appInfo.getDataUUIDNoAssign() != nil { + let containers = appInfo.containers + if !containers.isEmpty { if let result = await appFolderRemovalAlert.open() { doRemoveAppFolder = result } @@ -307,16 +317,17 @@ struct LCAppBanner : View { try fm.removeItem(atPath: self.appInfo.bundlePath()!) self.delegate.removeApp(app: self.model) if doRemoveAppFolder { - let dataUUID = appInfo.dataUUID()! - let dataFolderPath = LCPath.dataPath.appendingPathComponent(dataUUID) - try fm.removeItem(at: dataFolderPath) - - LCUtils.removeAppKeychain(dataUUID: dataUUID) - - DispatchQueue.main.async { - self.appDataFolders.removeAll(where: { f in - return f == dataUUID - }) + for container in containers { + let dataUUID = container.folderName + let dataFolderPath = LCPath.dataPath.appendingPathComponent(dataUUID) + try fm.removeItem(at: dataFolderPath) + LCUtils.removeAppKeychain(dataUUID: dataUUID) + + DispatchQueue.main.async { + self.appDataFolders.removeAll(where: { f in + return f == dataUUID + }) + } } } @@ -328,12 +339,17 @@ struct LCAppBanner : View { func copyLaunchUrl() { - UIPasteboard.general.string = "livecontainer://livecontainer-launch?bundle-name=\(appInfo.relativeBundlePath!)" + if let fn = model.uiSelectedContainer?.folderName { + UIPasteboard.general.string = "livecontainer://livecontainer-launch?bundle-name=\(appInfo.relativeBundlePath!)&container-folder-name=\(fn)" + } else { + UIPasteboard.general.string = "livecontainer://livecontainer-launch?bundle-name=\(appInfo.relativeBundlePath!)" + } + } func openSafariViewToCreateAppClip() { do { - let data = try PropertyListSerialization.data(fromPropertyList: appInfo.generateWebClipConfig()!, format: .xml, options: 0) + let data = try PropertyListSerialization.data(fromPropertyList: appInfo.generateWebClipConfig(withContainerId: model.uiSelectedContainer?.folderName)!, format: .xml, options: 0) delegate.installMdm(data: data) } catch { errorShow = true @@ -343,7 +359,7 @@ struct LCAppBanner : View { } func saveIcon() { - let img = appInfo.icon()! + let img = appInfo.generateLiveContainerWrappedIcon()! self.saveIconFile = ImageDocument(uiImage: img) self.saveIconExporterShow = true } diff --git a/LiveContainerSwiftUI/LCAppInfo.h b/LiveContainerSwiftUI/LCAppInfo.h index b8af535..e474bed 100644 --- a/LiveContainerSwiftUI/LCAppInfo.h +++ b/LiveContainerSwiftUI/LCAppInfo.h @@ -17,6 +17,8 @@ @property Signer signer; @property bool doUseLCBundleId; @property NSString* selectedLanguage; +@property NSString* dataUUID; +@property NSArray* containerInfo; - (void)setBundlePath:(NSString*)newBundlePath; - (NSMutableDictionary*)info; @@ -25,14 +27,12 @@ - (NSString*)bundlePath; - (NSString*)bundleIdentifier; - (NSString*)version; -- (NSString*)dataUUID; -- (NSString*)getDataUUIDNoAssign; - (NSString*)tweakFolder; - (NSMutableArray*) urlSchemes; -- (void)setDataUUID:(NSString *)uuid; - (void)setTweakFolder:(NSString *)tweakFolder; - (instancetype)initWithBundlePath:(NSString*)bundlePath; -- (NSDictionary *)generateWebClipConfig; +- (UIImage *)generateLiveContainerWrappedIcon; +- (NSDictionary *)generateWebClipConfigWithContainerId:(NSString*)containerId; - (void)save; - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign; @end diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 6bd7fe3..649d6d9 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -73,13 +73,6 @@ - (NSString*)bundleIdentifier { } - (NSString*)dataUUID { - if (!_info[@"LCDataUUID"]) { - self.dataUUID = NSUUID.UUID.UUIDString; - } - return _info[@"LCDataUUID"]; -} - -- (NSString*)getDataUUIDNoAssign { return _info[@"LCDataUUID"]; } @@ -153,7 +146,14 @@ - (UIImage *)generateLiveContainerWrappedIcon { return newIcon; } -- (NSDictionary *)generateWebClipConfig { +- (NSDictionary *)generateWebClipConfigWithContainerId:(NSString*)containerId { + NSString* appClipUrl; + if(containerId) { + appClipUrl = [NSString stringWithFormat:@"livecontainer://livecontainer-launch?bundle-name=%@&container-folder-name=%@", self.bundlePath.lastPathComponent, containerId]; + } else { + appClipUrl = [NSString stringWithFormat:@"livecontainer://livecontainer-launch?bundle-name=%@", self.bundlePath.lastPathComponent]; + } + NSDictionary *payload = @{ @"FullScreen": @YES, @"Icon": UIImagePNGRepresentation(self.generateLiveContainerWrappedIcon), @@ -168,7 +168,7 @@ - (NSDictionary *)generateWebClipConfig { @"PayloadVersion": @(1), @"Precomposed": @NO, @"toPayloadOrganization": @"LiveContainer", - @"URL": [NSString stringWithFormat:@"livecontainer://livecontainer-launch?bundle-name=%@", self.bundlePath.lastPathComponent] + @"URL": appClipUrl }; return @{ @"ConsentText": @{ @@ -458,4 +458,13 @@ - (void)setCachedColor:(UIColor*) color { [self save]; } +- (NSArray* )containerInfo { + return _info[@"LCContainers"]; +} + +- (void)setContainerInfo:(NSArray *)containerInfo { + _info[@"LCContainers"] = containerInfo; + [self save]; +} + @end diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 3a9ccf1..a00aea3 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -16,9 +16,6 @@ struct AppReplaceOption : Hashable { struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { - @Binding var apps: [LCAppModel] - @Binding var hiddenApps: [LCAppModel] - @Binding var appDataFolderNames: [String] @Binding var tweakFolderNames: [String] @@ -49,10 +46,8 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { @EnvironmentObject private var sharedModel : SharedModel - init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>, tweakFolderNames: Binding<[String]>) { + init(appDataFolderNames: Binding<[String]>, tweakFolderNames: Binding<[String]>) { _installOptions = State(initialValue: []) - _apps = apps - _hiddenApps = hiddenApps _appDataFolderNames = appDataFolderNames _tweakFolderNames = tweakFolderNames @@ -61,7 +56,6 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { var body: some View { NavigationView { ScrollView { - NavigationLink( destination: navigateTo, isActive: $isNavigationActive, @@ -87,14 +81,14 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } .zIndex(.infinity) LazyVStack { - ForEach(apps, id: \.self) { app in + ForEach(sharedModel.apps, id: \.self) { app in LCAppBanner(appModel: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) } .transition(.scale) } .padding() - .animation(.easeInOut, value: apps) + .animation(.easeInOut, value: sharedModel.apps) VStack { if LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { @@ -105,27 +99,27 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { .font(.system(.title2).bold()) Spacer() } - ForEach(hiddenApps, id: \.self) { app in + ForEach(sharedModel.hiddenApps, id: \.self) { app in LCAppBanner(appModel: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) } } .padding() .transition(.opacity) - .animation(.easeInOut, value: apps) + .animation(.easeInOut, value: sharedModel.apps) - if hiddenApps.count == 0 { + if sharedModel.hiddenApps.count == 0 { Text("lc.appList.hideAppTip".loc) .foregroundStyle(.gray) } } - } else if hiddenApps.count > 0 { + } else if sharedModel.hiddenApps.count > 0 { LazyVStack { HStack { Text("lc.appList.hiddenApps".loc) .font(.system(.title2).bold()) Spacer() } - ForEach(hiddenApps, id: \.self) { app in + ForEach(sharedModel.hiddenApps, id: \.self) { app in if sharedModel.isHiddenAppUnlocked { LCAppBanner(appModel: app, delegate: self, appDataFolders: $appDataFolderNames, tweakFolders: $tweakFolderNames) } else { @@ -138,10 +132,10 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } } .padding() - .animation(.easeInOut, value: apps) + .animation(.easeInOut, value: sharedModel.apps) } - let appCount = sharedModel.isHiddenAppUnlocked ? apps.count + hiddenApps.count : apps.count + let appCount = sharedModel.isHiddenAppUnlocked ? sharedModel.apps.count + sharedModel.hiddenApps.count : sharedModel.apps.count Text(appCount > 0 ? "lc.appList.appCounter %lld".localizeWithFormat(appCount) : "lc.appList.installTip".loc) .foregroundStyle(.gray) .animation(.easeInOut, value: appCount) @@ -229,7 +223,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } ) .fullScreenCover(isPresented: $webViewOpened) { - LCWebView(url: $webViewURL, apps: $apps, hiddenApps: $hiddenApps, isPresent: $webViewOpened) + LCWebView(url: $webViewURL, isPresent: $webViewOpened) } .fullScreenCover(isPresented: $safariViewOpened) { SafariView(url: $safariViewURL) @@ -246,10 +240,10 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } func onAppear() { - for app in apps { + for app in sharedModel.apps { app.delegate = self } - for app in hiddenApps { + for app in sharedModel.hiddenApps { app.delegate = self } @@ -269,9 +263,9 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } if urlToOpen.scheme != "https" && urlToOpen.scheme != "http" { var appToLaunch : LCAppModel? = nil - var appListsToConsider = [apps] + var appListsToConsider = [sharedModel.apps] if sharedModel.isHiddenAppUnlocked || !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { - appListsToConsider.append(hiddenApps) + appListsToConsider.append(sharedModel.hiddenApps) } appLoop: for appList in appListsToConsider { @@ -383,11 +377,11 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { var outputFolder = LCPath.bundlePath.appendingPathComponent(appRelativePath) var appToReplace : LCAppModel? = nil // Folder exist! show alert for user to choose which bundle to replace - var sameBundleIdApp = self.apps.filter { app in + var sameBundleIdApp = sharedModel.apps.filter { app in return app.appInfo.bundleIdentifier()! == newAppInfo.bundleIdentifier() } if sameBundleIdApp.count == 0 { - sameBundleIdApp = self.hiddenApps.filter { app in + sameBundleIdApp = sharedModel.hiddenApps.filter { app in return app.appInfo.bundleIdentifier()! == newAppInfo.bundleIdentifier() } @@ -472,7 +466,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { finalNewApp.isShared = appToReplace.appInfo.isShared finalNewApp.bypassAssertBarrierOnQueue = appToReplace.appInfo.bypassAssertBarrierOnQueue finalNewApp.doSymlinkInbox = appToReplace.appInfo.doSymlinkInbox - finalNewApp.setDataUUID(appToReplace.appInfo.getDataUUIDNoAssign()) + finalNewApp.containerInfo = appToReplace.appInfo.containerInfo finalNewApp.setTweakFolder(appToReplace.appInfo.tweakFolder()) finalNewApp.signer = appToReplace.appInfo.signer finalNewApp.selectedLanguage = appToReplace.appInfo.selectedLanguage @@ -480,19 +474,19 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { DispatchQueue.main.async { if let appToReplace { if appToReplace.uiIsHidden { - self.hiddenApps.removeAll { appNow in + sharedModel.hiddenApps.removeAll { appNow in return appNow == appToReplace } - self.hiddenApps.append(LCAppModel(appInfo: finalNewApp, delegate: self)) + sharedModel.hiddenApps.append(LCAppModel(appInfo: finalNewApp, delegate: self)) } else { - self.apps.removeAll { appNow in + sharedModel.apps.removeAll { appNow in return appNow == appToReplace } - self.apps.append(LCAppModel(appInfo: finalNewApp, delegate: self)) + sharedModel.apps.append(LCAppModel(appInfo: finalNewApp, delegate: self)) } } else { - self.apps.append(LCAppModel(appInfo: finalNewApp, delegate: self)) + sharedModel.apps.append(LCAppModel(appInfo: finalNewApp, delegate: self)) } self.installprogressVisible = false @@ -501,10 +495,10 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { func removeApp(app: LCAppModel) { DispatchQueue.main.async { - self.apps.removeAll { now in + sharedModel.apps.removeAll { now in return app == now } - self.hiddenApps.removeAll { now in + sharedModel.hiddenApps.removeAll { now in return app == now } } @@ -513,27 +507,27 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { func changeAppVisibility(app: LCAppModel) { DispatchQueue.main.async { if app.appInfo.isHidden { - self.apps.removeAll { now in + sharedModel.apps.removeAll { now in return app == now } - self.hiddenApps.append(app) + sharedModel.hiddenApps.append(app) } else { - self.hiddenApps.removeAll { now in + sharedModel.hiddenApps.removeAll { now in return app == now } - self.apps.append(app) + sharedModel.apps.append(app) } } } - func launchAppWithBundleId(bundleId : String) async { + func launchAppWithBundleId(bundleId : String, container : String?) async { if bundleId == "" { return } var appFound : LCAppModel? = nil var isFoundAppLocked = false - for app in apps { + for app in sharedModel.apps { if app.appInfo.relativeBundlePath == bundleId { appFound = app if app.appInfo.isLocked { @@ -543,7 +537,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } } if appFound == nil && !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { - for app in hiddenApps { + for app in sharedModel.hiddenApps { if app.appInfo.relativeBundlePath == bundleId { appFound = app isFoundAppLocked = true @@ -571,7 +565,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { } do { - try await appFound.runApp() + try await appFound.runApp(containerFolderName: container) } catch { errorInfo = error.localizedDescription errorShow = true diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 062eedf..8a7b343 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -18,7 +18,9 @@ class LCAppModel: ObservableObject, Hashable { @Published var uiIsHidden : Bool @Published var uiIsLocked : Bool @Published var uiIsShared : Bool - @Published var uiDataFolder : String? + @Published var uiDefaultDataFolder : String? + @Published var uiContainers : [LCContainer] + @Published var uiSelectedContainer : LCContainer? @Published var uiTweakFolder : String? @Published var uiDoSymlinkInbox : Bool @Published var uiUseLCBundleId : Bool @@ -44,12 +46,20 @@ class LCAppModel: ObservableObject, Hashable { self.uiIsLocked = appInfo.isLocked self.uiIsShared = appInfo.isShared self.uiSelectedLanguage = appInfo.selectedLanguage ?? "" - self.uiDataFolder = appInfo.getDataUUIDNoAssign() + self.uiDefaultDataFolder = appInfo.dataUUID + self.uiContainers = appInfo.containers self.uiTweakFolder = appInfo.tweakFolder() self.uiDoSymlinkInbox = appInfo.doSymlinkInbox self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue self.uiSigner = appInfo.signer self.uiUseLCBundleId = appInfo.doUseLCBundleId + + for container in uiContainers { + if container.folderName == uiDefaultDataFolder { + self.uiSelectedContainer = container; + break + } + } } static func == (lhs: LCAppModel, rhs: LCAppModel) -> Bool { @@ -60,13 +70,34 @@ class LCAppModel: ObservableObject, Hashable { hasher.combine(ObjectIdentifier(self)) } - func runApp() async throws{ + func runApp(containerFolderName : String? = nil) async throws{ if isAppRunning { return } - if let runningLC = LCUtils.getAppRunningLCScheme(bundleId: self.appInfo.relativeBundlePath) { - let openURL = URL(string: "\(runningLC)://livecontainer-launch?bundle-name=\(self.appInfo.relativeBundlePath!)")! + if uiContainers.isEmpty { + let newName = NSUUID().uuidString + let newContainer = LCContainer(folderName: newName, name: newName, isShared: uiIsShared) + uiContainers.append(newContainer) + if uiSelectedContainer == nil { + uiSelectedContainer = newContainer; + } + appInfo.containers = uiContainers; + newContainer.makeLCContainerInfoPlist(appIdentifier: appInfo.bundleIdentifier()!, keychainGroupId: 0) + appInfo.dataUUID = newName + uiDefaultDataFolder = newName + } + if let containerFolderName { + for uiContainer in uiContainers { + if uiContainer.folderName == containerFolderName { + uiSelectedContainer = uiContainer + break + } + } + } + + if let fn = uiSelectedContainer?.folderName, let runningLC = LCUtils.getContainerUsingLCScheme(containerName: fn) { + let openURL = URL(string: "\(runningLC)://livecontainer-launch?bundle-name=\(self.appInfo.relativeBundlePath!)&container-folder-name=\(fn)")! if await UIApplication.shared.canOpenURL(openURL) { await UIApplication.shared.open(openURL) return @@ -79,6 +110,7 @@ class LCAppModel: ObservableObject, Hashable { try await signApp(force: false) UserDefaults.standard.set(self.appInfo.relativeBundlePath, forKey: "selected") + UserDefaults.standard.set(uiSelectedContainer?.folderName, forKey: "selectedContainer") if let selectedLanguage = self.appInfo.selectedLanguage { // save livecontainer's own language UserDefaults.standard.set(UserDefaults.standard.object(forKey: "AppleLanguages"), forKey:"LCLastLanguages") diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index 34a06dc..5e87e29 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -18,7 +18,6 @@ struct LCAppSettingsView : View{ @Binding var tweakFolders: [String] - @State private var uiPickerDataFolder : String? @State private var uiPickerTweakFolder : String? @StateObject private var renameFolderInput = InputHelper() @@ -27,6 +26,7 @@ struct LCAppSettingsView : View{ @State private var errorShow = false @State private var errorInfo = "" + @State private var selectUnusedContainerSheetShow = false @EnvironmentObject private var sharedModel : SharedModel @@ -35,7 +35,6 @@ struct LCAppSettingsView : View{ self._model = ObservedObject(wrappedValue: model) _appDataFolders = appDataFolders _tweakFolders = tweakFolders - self._uiPickerDataFolder = State(initialValue: model.uiDataFolder) self._uiPickerTweakFolder = State(initialValue: model.uiTweakFolder) } @@ -50,42 +49,6 @@ struct LCAppSettingsView : View{ .multilineTextAlignment(.trailing) } if !model.uiIsShared { - Menu { - Button { - Task{ await createFolder() } - } label: { - Label("lc.appSettings.newDataFolder".loc, systemImage: "plus") - } - if model.uiDataFolder != nil { - Button { - Task{ await renameDataFolder() } - } label: { - Label("lc.appSettings.renameDataFolder".loc, systemImage: "pencil") - } - } - - Picker(selection: $uiPickerDataFolder , label: Text("")) { - ForEach(appDataFolders, id:\.self) { folderName in - Button(folderName) { - setDataFolder(folderName: folderName) - }.tag(Optional(folderName)) - } - } - } label: { - HStack { - Text("lc.appSettings.dataFolder".loc) - .foregroundColor(.primary) - Spacer() - Text(model.uiDataFolder == nil ? "lc.appSettings.noDataFolder".loc : model.uiDataFolder!) - .multilineTextAlignment(.trailing) - } - } - .onChange(of: uiPickerDataFolder, perform: { newValue in - if newValue != model.uiDataFolder { - setDataFolder(folderName: newValue) - } - }) - Menu { Picker(selection: $uiPickerTweakFolder , label: Text("")) { Label("lc.common.none".loc, systemImage: "nosign").tag(Optional(nil)) @@ -110,14 +73,6 @@ struct LCAppSettingsView : View{ } else { - HStack { - Text("lc.appSettings.dataFolder".loc) - .foregroundColor(.primary) - Spacer() - Text(model.uiDataFolder == nil ? "lc.appSettings.noDataFolder".loc : model.uiDataFolder!) - .foregroundColor(.gray) - .multilineTextAlignment(.trailing) - } HStack { Text("lc.appSettings.tweakFolder".loc) .foregroundColor(.primary) @@ -142,6 +97,35 @@ struct LCAppSettingsView : View{ Text("lc.common.data".loc) } + Section { + List{ + ForEach(model.uiContainers, id:\.self) { container in + NavigationLink { + LCContainerView(container: container, uiDefaultDataFolder: $model.uiDefaultDataFolder, delegate: self) + } label: { + Text(container.name) + } + } + } + if(model.uiContainers.count < 3) { + Button { + Task{ await createFolder() } + } label: { + Text("lc.appSettings.newDataFolder".loc) + } + if(!model.uiIsShared) { + Button { + selectUnusedContainerSheetShow = true + } label: { + Text("lc.container.selectUnused".loc) + } + } + } + + } header: { + Text("lc.common.container".loc) + } + Section { Toggle(isOn: $model.uiIsJITNeeded) { @@ -288,7 +272,7 @@ struct LCAppSettingsView : View{ } .navigationTitle(appInfo.displayName()) - + .navigationBarTitleDisplayMode(.inline) .alert("lc.common.error".loc, isPresented: $errorShow) { Button("lc.common.ok".loc, action: { }) @@ -332,16 +316,14 @@ struct LCAppSettingsView : View{ } message: { Text("lc.appSettings.toPrivateAppDesc".loc) } + .sheet(isPresented: $selectUnusedContainerSheetShow) { + LCSelectContainerView(isPresent: $selectUnusedContainerSheetShow, delegate: self) + } } - - func setDataFolder(folderName: String?) { - self.appInfo.setDataUUID(folderName!) - self.model.uiDataFolder = folderName - self.uiPickerDataFolder = folderName - } - + func createFolder() async { - guard let newName = await renameFolderInput.open(initVal: NSUUID().uuidString), newName != "" else { + let newName = NSUUID().uuidString + guard let displayName = await renameFolderInput.open(initVal: newName), displayName != "" else { return } let fm = FileManager() @@ -355,38 +337,31 @@ struct LCAppSettingsView : View{ } self.appDataFolders.append(newName) - self.setDataFolder(folderName: newName) - - } - - func renameDataFolder() async { - if self.appInfo.getDataUUIDNoAssign() == nil { - return + let newContainer = LCContainer(folderName: newName, name: displayName, isShared: model.uiIsShared) + // assign keychain group + var keychainGroupSet : Set = Set(minimumCapacity: 3) + for i in 0...2 { + keychainGroupSet.insert(i) } - - let initVal = self.model.uiDataFolder == nil ? "" : self.model.uiDataFolder! - guard let newName = await renameFolderInput.open(initVal: initVal), newName != "" else { - return + for container in model.uiContainers { + keychainGroupSet.remove(container.keychainGroupId) } - let fm = FileManager() - let orig = LCPath.dataPath.appendingPathComponent(appInfo.getDataUUIDNoAssign()) - let dest = LCPath.dataPath.appendingPathComponent(newName) - do { - try fm.moveItem(at: orig, to: dest) - } catch { + guard let freeKeyChainGroup = keychainGroupSet.first else { + errorInfo = "lc.container.notEnoughKeychainGroup".loc errorShow = true - errorInfo = error.localizedDescription return } - let i = self.appDataFolders.firstIndex(of: self.appInfo.getDataUUIDNoAssign()) - guard let i = i else { - return + model.uiContainers.append(newContainer) + if model.uiSelectedContainer == nil { + model.uiSelectedContainer = newContainer; } - - self.appDataFolders[i] = newName - self.setDataFolder(folderName: newName) - + if model.uiDefaultDataFolder == nil { + model.uiDefaultDataFolder = newName + appInfo.dataUUID = newName + } + appInfo.containers = model.uiContainers; + newContainer.makeLCContainerInfoPlist(appIdentifier: appInfo.bundleIdentifier()!, keychainGroupId: freeKeyChainGroup) } func setTweakFolder(folderName: String?) { @@ -404,11 +379,11 @@ struct LCAppSettingsView : View{ try LCPath.ensureAppGroupPaths() let fm = FileManager() try fm.moveItem(atPath: appInfo.bundlePath(), toPath: LCPath.lcGroupBundlePath.appendingPathComponent(appInfo.relativeBundlePath).path) - if let dataFolder = appInfo.getDataUUIDNoAssign(), dataFolder.count > 0 { - try fm.moveItem(at: LCPath.dataPath.appendingPathComponent(dataFolder), - to: LCPath.lcGroupDataPath.appendingPathComponent(dataFolder)) + for container in model.uiContainers { + try fm.moveItem(at: LCPath.dataPath.appendingPathComponent(container.folderName), + to: LCPath.lcGroupDataPath.appendingPathComponent(container.folderName)) appDataFolders.removeAll(where: { s in - return s == dataFolder + return s == container.folderName }) } if let tweakFolder = appInfo.tweakFolder(), tweakFolder.count > 0 { @@ -443,11 +418,10 @@ struct LCAppSettingsView : View{ do { let fm = FileManager() try fm.moveItem(atPath: appInfo.bundlePath(), toPath: LCPath.bundlePath.appendingPathComponent(appInfo.relativeBundlePath).path) - if let dataFolder = appInfo.getDataUUIDNoAssign(), dataFolder.count > 0 { - try fm.moveItem(at: LCPath.lcGroupDataPath.appendingPathComponent(dataFolder), - to: LCPath.dataPath.appendingPathComponent(dataFolder)) - appDataFolders.append(dataFolder) - model.uiDataFolder = dataFolder + for container in model.uiContainers { + try fm.moveItem(at: LCPath.lcGroupDataPath.appendingPathComponent(container.folderName), + to: LCPath.dataPath.appendingPathComponent(container.folderName)) + appDataFolders.append(container.folderName) } if let tweakFolder = appInfo.tweakFolder(), tweakFolder.count > 0 { try fm.moveItem(at: LCPath.lcGroupTweakPath.appendingPathComponent(tweakFolder), @@ -518,3 +492,95 @@ struct LCAppSettingsView : View{ } } } + + + +extension LCAppSettingsView : LCContainerViewDelegate { + func unbindContainer(container: LCContainer) { + model.uiContainers.removeAll { c in + c === container + } + + // if the deleted container is the default one, we change to another one + if container.folderName == model.uiDefaultDataFolder && !model.uiContainers.isEmpty{ + setDefaultContainer(container: model.uiContainers[0]) + } + // if the deleted container is the selected one, we change to the default one + if model.uiSelectedContainer === container && !model.uiContainers.isEmpty { + for container in model.uiContainers { + if container.folderName == model.uiDefaultDataFolder { + model.uiSelectedContainer = container + break + } + } + } + + if model.uiContainers.isEmpty { + model.uiSelectedContainer = nil + model.uiDefaultDataFolder = nil + appInfo.dataUUID = nil + } + appInfo.containers = model.uiContainers + } + + func setDefaultContainer(container newDefaultContainer: LCContainer ) { + if model.uiSelectedContainer?.folderName == model.uiDefaultDataFolder { + model.uiSelectedContainer = newDefaultContainer + } + + appInfo.dataUUID = newDefaultContainer.folderName + model.uiDefaultDataFolder = newDefaultContainer.folderName + } + + func saveContainer(container: LCContainer) { + container.makeLCContainerInfoPlist(appIdentifier: appInfo.bundleIdentifier()!, keychainGroupId: container.keychainGroupId) + appInfo.containers = model.uiContainers + } + + +} + +extension LCAppSettingsView : LCSelectContainerViewDelegate { + func addContainers(containers: Set) { + if containers.count + model.uiContainers.count > 3 { + errorInfo = "lc.container.tooMuchContainers".loc + errorShow = true + return + } + + for folderName in containers { + let newContainer = LCContainer(folderName: folderName, name: folderName, isShared: false) + newContainer.loadName() + if newContainer.keychainGroupId == -1 { + // assign keychain group for old containers + var keychainGroupSet : Set = Set(minimumCapacity: 3) + for i in 0...2 { + keychainGroupSet.insert(i) + } + for container in model.uiContainers { + keychainGroupSet.remove(container.keychainGroupId) + } + guard let freeKeyChainGroup = keychainGroupSet.first else { + errorInfo = "lc.container.notEnoughKeychainGroup".loc + errorShow = true + return + } + newContainer.makeLCContainerInfoPlist(appIdentifier: appInfo.bundleIdentifier()!, keychainGroupId: freeKeyChainGroup) + } + + + model.uiContainers.append(newContainer) + if model.uiSelectedContainer == nil { + model.uiSelectedContainer = newContainer; + } + if model.uiDefaultDataFolder == nil { + model.uiDefaultDataFolder = folderName + appInfo.dataUUID = folderName + } + + + } + appInfo.containers = model.uiContainers; + + } +} diff --git a/LiveContainerSwiftUI/LCContainer.swift b/LiveContainerSwiftUI/LCContainer.swift new file mode 100644 index 0000000..eebc8a6 --- /dev/null +++ b/LiveContainerSwiftUI/LCContainer.swift @@ -0,0 +1,130 @@ +// +// LCAppInfo.swift +// LiveContainerSwiftUI +// +// Created by s s on 2024/12/5. +// + +import Foundation +import UIKit + +class LCContainer : ObservableObject, Hashable { + @Published var folderName : String + @Published var name : String + @Published var isShared : Bool + private var infoDict : [String:Any]? + public var containerURL : URL { + if isShared { + return LCPath.lcGroupDataPath.appendingPathComponent("\(folderName)") + } else { + return LCPath.dataPath.appendingPathComponent("\(folderName)") + } + } + private var infoDictUrl : URL { + return containerURL.appendingPathComponent("LCContainerInfo.plist") + } + public var keychainGroupId : Int { + get { + if infoDict == nil { + infoDict = NSDictionary(contentsOf: infoDictUrl) as? [String : Any] + } + guard let infoDict else { + return -1 + } + return infoDict["keychainGroupId"] as? Int ?? -1 + } + } + + public var appIdentifier : String? { + get { + if infoDict == nil { + infoDict = NSDictionary(contentsOf: infoDictUrl) as? [String : Any] + } + guard let infoDict else { + return nil + } + return infoDict["appIdentifier"] as? String ?? nil + } + } + + init(folderName: String, name: String, isShared : Bool) { + self.folderName = folderName + self.name = name + self.isShared = isShared + } + + convenience init(infoDict : [String : Any], isShared : Bool) { + self.init(folderName: infoDict["folderName"] as? String ?? "ERROR", name: infoDict["name"] as? String ?? "ERROR", isShared: isShared) + } + + func toDict() -> [String : Any] { + return [ + "folderName" : folderName, + "name" : name, + ] + } + + func makeLCContainerInfoPlist(appIdentifier : String, keychainGroupId : Int) { + infoDict = [ + "appIdentifier" : appIdentifier, + "name" : name, + "keychainGroupId" : keychainGroupId + ] + do { + let fm = FileManager.default + if(!fm.fileExists(atPath: infoDictUrl.deletingLastPathComponent().path)) { + try fm.createDirectory(at: infoDictUrl.deletingLastPathComponent(), withIntermediateDirectories: true) + } + } catch { + + } + + (infoDict! as NSDictionary).write(to: infoDictUrl, atomically: true) + } + + func reloadInfoPlist() { + infoDict = NSDictionary(contentsOf: infoDictUrl) as? [String : Any] + } + + func loadName() { + if infoDict == nil { + infoDict = NSDictionary(contentsOf: infoDictUrl) as? [String : Any] + } + guard let infoDict else { + return + } + name = infoDict["name"] as? String ?? "ERROR" + } + + static func == (lhs: LCContainer, rhs: LCContainer) -> Bool { + return lhs === rhs + } + + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(self)) + } +} + +extension LCAppInfo { + var containers : [LCContainer] { + get { + // upgrade + if let oldDataUUID = dataUUID, containerInfo == nil { + containerInfo = [[ + "folderName": oldDataUUID, + "name": oldDataUUID, + ]] + } + let dictArr = containerInfo as? [[String : Any]] ?? [] + return dictArr.map{ dict in + return LCContainer(infoDict: dict, isShared: isShared) + } + } + set { + containerInfo = newValue.map { container in + return container.toDict() + } + } + } + +} diff --git a/LiveContainerSwiftUI/LCContainerView.swift b/LiveContainerSwiftUI/LCContainerView.swift new file mode 100644 index 0000000..2d932cd --- /dev/null +++ b/LiveContainerSwiftUI/LCContainerView.swift @@ -0,0 +1,255 @@ +// +// LCContainerView.swift +// LiveContainerSwiftUI +// +// Created by s s on 2024/12/6. +// + +import SwiftUI + +protocol LCContainerViewDelegate { + func unbindContainer(container: LCContainer) + func setDefaultContainer(container: LCContainer) + func saveContainer(container: LCContainer) +} + +struct LCContainerView : View { + @ObservedObject var container : LCContainer + let delegate : LCContainerViewDelegate + @Binding var uiDefaultDataFolder : String? + + @StateObject private var removeContainerAlert = YesNoHelper() + @StateObject private var deleteDataAlert = YesNoHelper() + @StateObject private var removeKeychainAlert = YesNoHelper() + @Environment(\.dismiss) private var dismiss + @State private var typingContainerName : String = "" + @State private var inUse = false + + @State private var errorShow = false + @State private var errorInfo = "" + @State private var successShow = false + @State private var successInfo = "" + + init(container: LCContainer, uiDefaultDataFolder : Binding, delegate: LCContainerViewDelegate) { + self._container = ObservedObject(wrappedValue: container) + self.delegate = delegate + self._typingContainerName = State(initialValue: container.name) + self._uiDefaultDataFolder = Binding(projectedValue: uiDefaultDataFolder) + } + + var body: some View { + Form { + Section { + HStack { + Text("lc.container.containerName".loc) + Spacer() + TextField(container.name, text: $typingContainerName) + .multilineTextAlignment(.trailing) + .onSubmit { + container.name = typingContainerName + saveContainer() + } + } + HStack { + Text("lc.container.containerFolderName".loc) + Spacer() + Text(container.folderName) + .foregroundStyle(.gray) + } + if container.folderName == uiDefaultDataFolder { + Text("lc.container.alreadyDefaultContainer".loc) + .foregroundStyle(.gray) + } else { + Button { + setAsDefault() + } label: { + Text("lc.container.setDefaultContainer".loc) + } + } + } footer: { + Text("lc.container.defaultContainerDesc".loc) + } + + Section { + if inUse { + Text("lc.container.inUse".loc) + .foregroundStyle(.gray) + } else { + if !container.isShared { + Button { + openDataFolder() + } label: { + Text("lc.appBanner.openDataFolder".loc) + } + Button { + unbindContainer() + } label: { + Text("lc.container.unbind".loc) + } + } + Button(role:.destructive) { + Task { await deleteData() } + } label: { + Text("lc.container.deleteData".loc) + } + + Button(role:.destructive) { + Task { await cleanUpKeychain() } + } label: { + Text("lc.settings.cleanKeychain".loc) + } + + Button(role:.destructive) { + Task { await removeContainer() } + } label: { + Text("lc.container.removeContainer".loc) + } + + } + } + } + .navigationTitle(container.name) + .navigationBarTitleDisplayMode(.inline) + .alert("lc.common.error".loc, isPresented: $errorShow){ + } message: { + Text(errorInfo) + } + .alert("lc.common.success".loc, isPresented: $successShow){ + } message: { + Text(successInfo) + } + + .alert("lc.container.removeContainer".loc, isPresented: $removeContainerAlert.show) { + Button(role: .destructive) { + removeContainerAlert.close(result: true) + } label: { + Text("lc.common.delete".loc) + } + Button("lc.common.cancel".loc, role: .cancel) { + removeContainerAlert.close(result: false) + } + } message: { + Text("lc.container.removeContainerDesc".loc) + } + + .alert("lc.container.deleteData".loc, isPresented: $deleteDataAlert.show) { + Button(role: .destructive) { + deleteDataAlert.close(result: true) + } label: { + Text("lc.common.delete".loc) + } + Button("lc.common.cancel".loc, role: .cancel) { + deleteDataAlert.close(result: false) + } + } message: { + Text("lc.container.deleteDataDesc".loc) + } + + .alert("lc.settings.cleanKeychain".loc, isPresented: $removeKeychainAlert.show) { + Button(role: .destructive) { + removeKeychainAlert.close(result: true) + } label: { + Text("lc.common.delete".loc) + } + Button("lc.common.cancel".loc, role: .cancel) { + removeKeychainAlert.close(result: false) + } + } message: { + Text("lc.container.removeKeychainDesc".loc) + } + .onAppear() { + container.reloadInfoPlist() + inUse = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) != nil + } + + } + + + func saveContainer() { + if let usingLC = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) { + errorInfo = "lc.container.inUseBy %@".localizeWithFormat(usingLC) + errorShow = true + return + } + + delegate.saveContainer(container: container) + } + + func openDataFolder() { + let url = URL(string:"shareddocuments://\(LCPath.docPath.path)/Data/Application/\(container.folderName)") + UIApplication.shared.open(url!) + } + + func setAsDefault() { + delegate.setDefaultContainer(container: container) + } + + func removeContainer() async { + if let usingLC = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) { + errorInfo = "lc.container.inUseBy %@".localizeWithFormat(usingLC) + errorShow = true + return + } + guard let ans = await removeContainerAlert.open(), ans else { + return + } + do { + let fm = FileManager.default + try fm.removeItem(at: container.containerURL) + } catch { + errorInfo = error.localizedDescription + errorShow = true + return + } + + dismiss() + delegate.unbindContainer(container: container) + } + + func unbindContainer() { + if let usingLC = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) { + errorInfo = "lc.container.inUseBy %@".localizeWithFormat(usingLC) + errorShow = true + return + } + + dismiss() + delegate.unbindContainer(container: container) + } + + func cleanUpKeychain() async { + if let usingLC = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) { + errorInfo = "lc.container.inUseBy %@".localizeWithFormat(usingLC) + errorShow = true + return + } + guard let ans = await removeKeychainAlert.open(), ans else { + return + } + + LCUtils.removeAppKeychain(dataUUID: container.folderName) + } + + func deleteData() async { + if let usingLC = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) { + errorInfo = "lc.container.inUseBy %@".localizeWithFormat(usingLC) + errorShow = true + return + } + guard let ans = await deleteDataAlert.open(), ans else { + return + } + do { + let fm = FileManager.default + for file in try fm.contentsOfDirectory(at: container.containerURL, includingPropertiesForKeys: nil) { + if file.lastPathComponent == "LCContainerInfo.plist" { + continue + } + try fm.removeItem(at: file) + } + } catch { + errorInfo = error.localizedDescription + errorShow = true + } + } +} diff --git a/LiveContainerSwiftUI/LCSelectContainerView.swift b/LiveContainerSwiftUI/LCSelectContainerView.swift new file mode 100644 index 0000000..3829e36 --- /dev/null +++ b/LiveContainerSwiftUI/LCSelectContainerView.swift @@ -0,0 +1,115 @@ +// +// LCSelectContainerView.swift +// LiveContainerSwiftUI +// +// Created by s s on 2024/12/6. +// + +import SwiftUI + +protocol LCSelectContainerViewDelegate { + func addContainers(containers: Set) +} + +struct LCSelectContainerView : View{ + @State private var multiSelection = Set() + @State private var unusedContainers : [LCContainer] = [] + @Binding var isPresent : Bool + public var delegate : LCSelectContainerViewDelegate + + @EnvironmentObject private var sharedModel : SharedModel + + var body: some View { + NavigationView { + List(selection: $multiSelection) { + ForEach(unusedContainers, id: \.folderName) { container in + VStack(alignment: .leading) { + Text(container.name) + Text("\(container.folderName) - \(container.appIdentifier ?? "lc.container.selectUnused.unknownApp".loc)") + .font(.footnote) + .foregroundStyle(.gray) + } + } + if unusedContainers.count == 0 { + Text("lc.container.selectUnused.noUnused".loc) + } + } + .environment(\.editMode, .constant(.active)) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button { + isPresent = false + } label: { + Text("lc.common.cancel".loc) + } + } + + ToolbarItem(placement: .confirmationAction) { + if multiSelection.count > 0 { + Button { + isPresent = false + delegate.addContainers(containers: multiSelection) + } label: { + Text("lc.common.ok".loc) + } + } + } + + } + .navigationTitle(Text("lc.container.selectUnused".loc)) + .navigationBarTitleDisplayMode(.inline) + } + .onAppear() { + loadUnusedContainers() + } + + } + + func loadUnusedContainers() { + // load document folders + var appDataFolderNames: [String] = [] + multiSelection.removeAll() + unusedContainers = [] + do { + let fm = FileManager.default + try fm.createDirectory(at: LCPath.dataPath, withIntermediateDirectories: true) + let dataDirs = try fm.contentsOfDirectory(atPath: LCPath.dataPath.path) + for dataDir in dataDirs { + let dataDirUrl = LCPath.dataPath.appendingPathComponent(dataDir) + if !dataDirUrl.hasDirectoryPath { + continue + } + appDataFolderNames.append(dataDir) + } + } catch { + + } + + + var folderNameToAppDict : [String:LCAppModel] = [:] + for app in sharedModel.apps { + for container in app.appInfo.containers { + folderNameToAppDict[container.folderName] = app; + } + } + for app in sharedModel.hiddenApps { + for container in app.appInfo.containers { + folderNameToAppDict[container.folderName] = app; + } + } + + var unusedFolders : [String] = [] + for appDataFolderName in appDataFolderNames { + if folderNameToAppDict[appDataFolderName] == nil { + unusedFolders.append(appDataFolderName) + } + } + + unusedContainers = unusedFolders.map { folder in + let ans = LCContainer(folderName: folder, name: folder, isShared: false) + ans.loadName() + return ans; + } + + } +} diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index a49d294..954aeec 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -20,8 +20,6 @@ struct LCSettingsView: View { @State var successShow = false @State var successInfo = "" - @Binding var apps: [LCAppModel] - @Binding var hiddenApps: [LCAppModel] @Binding var appDataFolderNames: [String] @StateObject private var appFolderRemovalAlert = YesNoHelper() @@ -50,7 +48,7 @@ struct LCSettingsView: View { let storeName = LCUtils.getStoreName() - init(apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, appDataFolderNames: Binding<[String]>) { + init(appDataFolderNames: Binding<[String]>) { _isJitLessEnabled = State(initialValue: LCUtils.certificatePassword() != nil) if(LCUtils.appGroupUserDefault.object(forKey: "LCSignOnlyOnExpiration") == nil) { @@ -65,8 +63,6 @@ struct LCSettingsView: View { _isSideStore = State(initialValue: LCUtils.store() == .SideStore) - _apps = apps - _hiddenApps = hiddenApps _appDataFolderNames = appDataFolderNames if let configSideJITServerAddress = LCUtils.appGroupUserDefault.string(forKey: "LCSideJITServerAddress") { @@ -436,17 +432,15 @@ struct LCSettingsView: View { func cleanUpUnusedFolders() async { var folderNameToAppDict : [String:LCAppModel] = [:] - for app in apps { - guard let folderName = app.appInfo.getDataUUIDNoAssign() else { - continue + for app in sharedModel.apps { + for container in app.appInfo.containers { + folderNameToAppDict[container.folderName] = app; } - folderNameToAppDict[folderName] = app } - for app in hiddenApps { - guard let folderName = app.appInfo.getDataUUIDNoAssign() else { - continue + for app in sharedModel.hiddenApps { + for container in app.appInfo.containers { + folderNameToAppDict[container.folderName] = app; } - folderNameToAppDict[folderName] = app } var foldersToDelete : [String] = [] @@ -499,25 +493,27 @@ struct LCSettingsView: View { do { var appDataFoldersInUse : Set = Set(); var tweakFoldersInUse : Set = Set(); - for app in apps { + for app in sharedModel.apps { if !app.appInfo.isShared { continue } - if let folder = app.appInfo.getDataUUIDNoAssign() { - appDataFoldersInUse.update(with: folder); + for container in app.appInfo.containers { + appDataFoldersInUse.update(with: container.folderName); } + + if let folder = app.appInfo.tweakFolder() { tweakFoldersInUse.update(with: folder); } } - for app in hiddenApps { + for app in sharedModel.hiddenApps { if !app.appInfo.isShared { continue } - if let folder = app.appInfo.getDataUUIDNoAssign() { - appDataFoldersInUse.update(with: folder); + for container in app.appInfo.containers { + appDataFoldersInUse.update(with: container.folderName); } if let folder = app.appInfo.tweakFolder() { tweakFoldersInUse.update(with: folder); diff --git a/LiveContainerSwiftUI/LCTabView.swift b/LiveContainerSwiftUI/LCTabView.swift index ae39002..fa3c9a7 100644 --- a/LiveContainerSwiftUI/LCTabView.swift +++ b/LiveContainerSwiftUI/LCTabView.swift @@ -9,8 +9,6 @@ import Foundation import SwiftUI struct LCTabView: View { - @State var apps: [LCAppModel] - @State var hiddenApps: [LCAppModel] @State var appDataFolderNames: [String] @State var tweakFolderNames: [String] @@ -82,15 +80,15 @@ struct LCTabView: View { } catch { NSLog("[LC] error:\(error)") } - _apps = State(initialValue: tempApps) + DataManager.shared.model.apps = tempApps + DataManager.shared.model.hiddenApps = tempHiddenApps _appDataFolderNames = State(initialValue: tempAppDataFolderNames) _tweakFolderNames = State(initialValue: tempTweakFolderNames) - _hiddenApps = State(initialValue: tempHiddenApps) } var body: some View { TabView { - LCAppListView(apps: $apps, hiddenApps: $hiddenApps, appDataFolderNames: $appDataFolderNames, tweakFolderNames: $tweakFolderNames) + LCAppListView(appDataFolderNames: $appDataFolderNames, tweakFolderNames: $tweakFolderNames) .tabItem { Label("lc.tabView.apps".loc, systemImage: "square.stack.3d.up.fill") } @@ -101,7 +99,7 @@ struct LCTabView: View { } } - LCSettingsView(apps: $apps, hiddenApps: $hiddenApps, appDataFolderNames: $appDataFolderNames) + LCSettingsView(appDataFolderNames: $appDataFolderNames) .tabItem { Label("lc.tabView.settings".loc, systemImage: "gearshape.fill") } diff --git a/LiveContainerSwiftUI/LCWebView.swift b/LiveContainerSwiftUI/LCWebView.swift index 09f5a18..1a2986c 100644 --- a/LiveContainerSwiftUI/LCWebView.swift +++ b/LiveContainerSwiftUI/LCWebView.swift @@ -17,9 +17,6 @@ struct LCWebView: View { @State private var uiLoadStatus = 0.0 @State private var pageTitle = "" - @Binding var apps : [LCAppModel] - @Binding var hiddenApps : [LCAppModel] - @State private var runAppAlert = YesNoHelper() @State private var runAppAlertMsg = "" @@ -28,12 +25,10 @@ struct LCWebView: View { @EnvironmentObject private var sharedModel : SharedModel - init(url: Binding, apps: Binding<[LCAppModel]>, hiddenApps: Binding<[LCAppModel]>, isPresent: Binding) { + init(url: Binding, isPresent: Binding) { self._webView = State(initialValue: WebView()) self._url = url - self._apps = apps self._isPresent = isPresent - self._hiddenApps = hiddenApps } var body: some View { @@ -144,9 +139,9 @@ struct LCWebView: View { public func onURLSchemeDetected(url: URL) async { var appToLaunch : LCAppModel? = nil - var appListsToConsider = [apps] + var appListsToConsider = [sharedModel.apps] if sharedModel.isHiddenAppUnlocked || !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") { - appListsToConsider.append(hiddenApps) + appListsToConsider.append(sharedModel.hiddenApps) } appLoop: for appList in appListsToConsider { @@ -194,11 +189,11 @@ struct LCWebView: View { public func onUniversalLinkDetected(url: URL, bundleIDs: [String]) async { var bundleIDToAppDict: [String: LCAppModel] = [:] - for app in apps { + for app in sharedModel.apps { bundleIDToAppDict[app.appInfo.bundleIdentifier()!] = app } if !LCUtils.appGroupUserDefault.bool(forKey: "LCStrictHiding") || sharedModel.isHiddenAppUnlocked { - for app in hiddenApps { + for app in sharedModel.hiddenApps { bundleIDToAppDict[app.appInfo.bundleIdentifier()!] = app } } diff --git a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj index 7dab16e..fa45d07 100644 --- a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj +++ b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj @@ -8,11 +8,14 @@ /* Begin PBXBuildFile section */ 170C3DF92C99A489007F86FB /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 170C3DF82C99A489007F86FB /* Localizable.xcstrings */; }; + 170D51B52D02E29F009000A6 /* LCSelectContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170D51B42D02E291009000A6 /* LCSelectContainerView.swift */; }; + 170F86832D02A34C00E642C3 /* LCContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170F86822D02A34100E642C3 /* LCContainerView.swift */; }; 171014182CE9B42000673269 /* LCVersionInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 171014172CE9B42000673269 /* LCVersionInfo.m */; }; 1710141C2CE9B50C00673269 /* LCAppInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1710141B2CE9B50C00673269 /* LCAppInfo.m */; }; 171014222CE9B63100673269 /* unarchive.m in Sources */ = {isa = PBXBuildFile; fileRef = 171014212CE9B63100673269 /* unarchive.m */; }; 171014232CE9B63100673269 /* LCUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1710141F2CE9B63100673269 /* LCUtils.m */; }; 171014242CE9B63100673269 /* LCMachOUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 1710141D2CE9B63100673269 /* LCMachOUtils.m */; }; + 171510872D01D61E00ED4B2E /* LCContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171510862D01D61B00ED4B2E /* LCContainer.swift */; }; 173564C92C76FE3500C6C918 /* LCAppListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564BC2C76FE3500C6C918 /* LCAppListView.swift */; }; 173564CA2C76FE3500C6C918 /* LCTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564BD2C76FE3500C6C918 /* LCTabView.swift */; }; 173564CC2C76FE3500C6C918 /* LCTweaksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C02C76FE3500C6C918 /* LCTweaksView.swift */; }; @@ -29,6 +32,8 @@ /* Begin PBXFileReference section */ 170C3DF82C99A489007F86FB /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; + 170D51B42D02E291009000A6 /* LCSelectContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCSelectContainerView.swift; sourceTree = ""; }; + 170F86822D02A34100E642C3 /* LCContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCContainerView.swift; sourceTree = ""; }; 171014162CE9B42000673269 /* LCVersionInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LCVersionInfo.h; sourceTree = ""; }; 171014172CE9B42000673269 /* LCVersionInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LCVersionInfo.m; sourceTree = ""; }; 1710141A2CE9B50C00673269 /* LCAppInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LCAppInfo.h; sourceTree = ""; }; @@ -40,6 +45,7 @@ 171014212CE9B63100673269 /* unarchive.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = unarchive.m; sourceTree = ""; }; 171014252CE9B6ED00673269 /* archive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = archive.h; sourceTree = ""; }; 171014262CE9B6ED00673269 /* archive_entry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = archive_entry.h; sourceTree = ""; }; + 171510862D01D61B00ED4B2E /* LCContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCContainer.swift; sourceTree = ""; }; 173564BC2C76FE3500C6C918 /* LCAppListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCAppListView.swift; sourceTree = ""; }; 173564BD2C76FE3500C6C918 /* LCTabView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCTabView.swift; sourceTree = ""; }; 173564C02C76FE3500C6C918 /* LCTweaksView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCTweaksView.swift; sourceTree = ""; }; @@ -70,6 +76,7 @@ 173564BB2C76FE1500C6C918 /* LiveContainerSwiftUI */ = { isa = PBXGroup; children = ( + 170D51B42D02E291009000A6 /* LCSelectContainerView.swift */, 173F183F2C7B7B74002953AA /* LCWebView.swift */, 173564C82C76FE3500C6C918 /* Assets.xcassets */, 173564C72C76FE3500C6C918 /* LCAppBanner.swift */, @@ -80,6 +87,8 @@ 173564C02C76FE3500C6C918 /* LCTweaksView.swift */, 173564C32C76FE3500C6C918 /* Makefile */, 178B4C3F2C7766A300DD1F74 /* LiveContainerSwiftUI-Bridging-Header.h */, + 171510862D01D61B00ED4B2E /* LCContainer.swift */, + 170F86822D02A34100E642C3 /* LCContainerView.swift */, 178B4C3D2C77654400DD1F74 /* Shared.swift */, 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */, 170C3DF82C99A489007F86FB /* Localizable.xcstrings */, @@ -192,6 +201,8 @@ 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */, 171014242CE9B63100673269 /* LCMachOUtils.m in Sources */, 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */, + 170D51B52D02E29F009000A6 /* LCSelectContainerView.swift in Sources */, + 171510872D01D61E00ED4B2E /* LCContainer.swift in Sources */, 171014182CE9B42000673269 /* LCVersionInfo.m in Sources */, 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */, 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */, @@ -203,6 +214,7 @@ 173564CC2C76FE3500C6C918 /* LCTweaksView.swift in Sources */, 173564CD2C76FE3500C6C918 /* LCSettingsView.swift in Sources */, 173564CA2C76FE3500C6C918 /* LCTabView.swift in Sources */, + 170F86832D02A34C00E642C3 /* LCContainerView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 6e55df6..532569a 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -649,7 +649,7 @@ "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "此App的数据当前在%@中。打开%@至App界面活后再试。" + "value" : "此App的数据当前在%@中。打开%@至App界面后再试。" } } } @@ -1130,6 +1130,23 @@ } } }, + "lc.common.container" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Containers" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器" + } + } + } + }, "lc.common.copy" : { "extractionState" : "manual", "localizations" : { @@ -1368,6 +1385,312 @@ } } }, + "lc.container.alreadyDefaultContainer" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This is the default container" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "该容器为默认容器" + } + } + } + }, + "lc.container.containerFolderName" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Container Folder Name" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器文件夹名" + } + } + } + }, + "lc.container.containerName" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Container Name" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器名称" + } + } + } + }, + "lc.container.defaultContainerDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "LiveContainer will use the default container when none is specified." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "当没有指定容器时,LiveContainer会使用默认容器。" + } + } + } + }, + "lc.container.deleteData" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Delete Data" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除数据" + } + } + } + }, + "lc.container.deleteDataDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Do you want to delete all data of this container?" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定要删除该容器的所有数据吗?" + } + } + } + }, + "lc.container.inUse" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "This container is currently in use" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器当前正在使用" + } + } + } + }, + "lc.container.inUseBy %@" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Container is currently used by %@." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器当前正在被%@使用。" + } + } + } + }, + "lc.container.notEnoughKeychainGroup" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Not enough keychain Access Group." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "无法分配空闲的钥匙串访问组。" + } + } + } + }, + "lc.container.removeContainer" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Remove Container" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "删除容器" + } + } + } + }, + "lc.container.removeContainerDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Do you want to remove this container?" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定要删除此容器吗?" + } + } + } + }, + "lc.container.removeKeychainDesc" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Do you want to remove the keychain of this container?" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "你确定要清除该容器的钥匙串吗?" + } + } + } + }, + "lc.container.selectUnused" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Select Unused Containers" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "选择未使用的容器" + } + } + } + }, + "lc.container.selectUnused.noUnused" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "No unused data folders." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "无未使用的数据文件夹。" + } + } + } + }, + "lc.container.selectUnused.unknownApp" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown app" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "未知App" + } + } + } + }, + "lc.container.setDefaultContainer" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set As Default Container" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "设置为默认容器" + } + } + } + }, + "lc.container.tooMuchContainers" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Can't add more than 3 containers." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "不能添加超过3个容器。" + } + } + } + }, + "lc.container.unbind" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unbind From App" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "与App解绑" + } + } + } + }, "lc.guestTweak.appSwitchTip %@" : { "extractionState" : "manual", "localizations" : { @@ -1727,7 +2050,7 @@ "zh_CN" : { "stringUnit" : { "state" : "translated", - "value" : "清理Keychain" + "value" : "清理钥匙串" } } } diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 342e622..2744180 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -57,6 +57,9 @@ class SharedModel: ObservableObject { // 0= not installed, 1= is installed, 2=current liveContainer is the second one @Published var multiLCStatus = 0 + @Published var apps : [LCAppModel] = [] + @Published var hiddenApps : [LCAppModel] = [] + func updateMultiLCStatus() { if LCUtils.appUrlScheme()?.lowercased() != "livecontainer" { multiLCStatus = 2 @@ -581,6 +584,26 @@ extension LCUtils { return nil } + public static func getContainerUsingLCScheme(containerName: String) -> String? { + // Retrieve the app group path using the app group ID + let infoPath = LCPath.lcGroupDocPath.appendingPathComponent("containerLock.plist") + // Read the plist file into a dictionary + guard let info = NSDictionary(contentsOf: infoPath) as? [String: String] else { + return nil + } + // Iterate over the dictionary to find the matching bundle ID + for (key, value) in info { + if value == containerName { + if key == LCUtils.appUrlScheme() { + return nil + } + return key + } + } + + return nil + } + private static func authenticateUser(completion: @escaping (Bool, Error?) -> Void) { // Create a context for authentication let context = LAContext() diff --git a/README.md b/README.md index c45b0f6..70354d8 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,12 @@ make package - Jump to the entry point - The guest app's entry point calls `UIApplicationMain` and start up like any other iOS apps. +### Multi-Account support & Keychain Semi-Sapeartion +[3 keychain access groups](./entitlements.xml) are created and LiveContainer allocates them to each container of same app. So you can create 3 container with different keychain access groups. + +#### Why only 3? +The [original thought was 256](https://github.com/hugeBlack/LiveContainer/blob/256keychainAccessGroup/entitlements.xml), but due to a [SideStore bug](https://github.com/SideStore/SideStore/issues/782) (latest AltStore don't have it), we can only declare 3 keychain access groups before SideStore fails to sign. So the limit is 3. + ## Limitations - Entitlements from the guest app are not applied to the host app. This isn't a big deal since sideloaded apps requires only basic entitlements. - App Permissions are globally applied. @@ -133,7 +139,6 @@ make package - Querying custom URL schemes might not work(?) ## TODO -- Isolate Keychain per app - Use ChOma instead of custom MachO parser ## License diff --git a/TweakLoader/SecItem.m b/TweakLoader/SecItem.m index dadc562..5600b75 100644 --- a/TweakLoader/SecItem.m +++ b/TweakLoader/SecItem.m @@ -7,111 +7,83 @@ #import #import #import "../fishhook/fishhook.h" +#import "utils.h" +#import -NSString* SecItemLabelPrefix = 0; OSStatus (*orig_SecItemAdd)(CFDictionaryRef attributes, CFTypeRef *result); OSStatus (*orig_SecItemCopyMatching)(CFDictionaryRef query, CFTypeRef *result); OSStatus (*orig_SecItemUpdate)(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); OSStatus (*orig_SecItemDelete)(CFDictionaryRef query); +NSString* accessGroup = nil; +NSString* containerId = nil; + OSStatus new_SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result) { NSMutableDictionary *attributesCopy = ((__bridge NSDictionary *)attributes).mutableCopy; - attributesCopy[@"alis"] = SecItemLabelPrefix; - [attributesCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; + attributesCopy[(__bridge id)kSecAttrAccessGroup] = accessGroup; + // for keychain deletion in LCUI + attributesCopy[@"alis"] = containerId; OSStatus status = orig_SecItemAdd((__bridge CFDictionaryRef)attributesCopy, result); - if(status == errSecSuccess && result && *result) { - id objcResult = (__bridge id)(*result); - if(CFGetTypeID(*result) == CFDictionaryGetTypeID()) { - NSMutableDictionary* finalQueryResult = [objcResult mutableCopy]; - finalQueryResult[@"alis"] = @""; - *result = (__bridge CFTypeRef)finalQueryResult; - } else if (CFGetTypeID(*result) == CFArrayGetTypeID()) { - NSMutableArray* finalQueryResult = [objcResult mutableCopy]; - for(id item in finalQueryResult) { - if([item isKindOfClass:[NSDictionary class]]) { - item[@"alis"] = @""; - } - - } - *result = (__bridge CFTypeRef)finalQueryResult; - } - return status; + if(status == errSecParam) { + return orig_SecItemAdd(attributes, result); } + return status; } OSStatus new_SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result) { NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; - queryCopy[@"alis"] = SecItemLabelPrefix; - [queryCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; - + queryCopy[(__bridge id)kSecAttrAccessGroup] = accessGroup; OSStatus status = orig_SecItemCopyMatching((__bridge CFDictionaryRef)queryCopy, result); - if(status == errSecSuccess && result && *result) { - id objcResult = (__bridge id)(*result); - if([objcResult isKindOfClass:[NSDictionary class]]) { - NSMutableDictionary* finalQueryResult = [objcResult mutableCopy]; - finalQueryResult[@"alis"] = @""; - *result = (__bridge CFTypeRef)finalQueryResult; - } else if ([objcResult isKindOfClass:[NSArray class]]) { - NSMutableArray* finalQueryResult = [objcResult mutableCopy]; - for(id item in finalQueryResult) { - if([item isKindOfClass:[NSDictionary class]]) { - item[@"alis"] = @""; - } - - } - *result = (__bridge CFTypeRef)finalQueryResult; - } - return status; + if(status == errSecParam) { + // if this search don't support kSecAttrAccessGroup, we just use the original search + return orig_SecItemCopyMatching(query, result); } - if(status != errSecParam) { - return status; - } - - // if this search don't support comment, we just use the original search - status = orig_SecItemCopyMatching(query, result); return status; } OSStatus new_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate) { NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; - queryCopy[@"alis"] = SecItemLabelPrefix; - [queryCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; + queryCopy[(__bridge id)kSecAttrAccessGroup] = accessGroup; NSMutableDictionary *attrCopy = ((__bridge NSDictionary *)attributesToUpdate).mutableCopy; - attrCopy[@"alis"] = SecItemLabelPrefix; - [attrCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; + attrCopy[(__bridge id)kSecAttrAccessGroup] = accessGroup; OSStatus status = orig_SecItemUpdate((__bridge CFDictionaryRef)queryCopy, (__bridge CFDictionaryRef)attrCopy); - if(status != errSecParam) { - return status; + + if(status == errSecParam) { + return orig_SecItemUpdate(query, attributesToUpdate); } - // if this search don't support comment, we just use the original search - status = orig_SecItemUpdate(query, attributesToUpdate); return status; } OSStatus new_SecItemDelete(CFDictionaryRef query){ NSMutableDictionary *queryCopy = ((__bridge NSDictionary *)query).mutableCopy; - queryCopy[@"alis"] = SecItemLabelPrefix; - [queryCopy removeObjectForKey:(__bridge id)kSecAttrAccessGroup]; - + queryCopy[(__bridge id)kSecAttrAccessGroup] = accessGroup; OSStatus status = orig_SecItemDelete((__bridge CFDictionaryRef)queryCopy); - if(status != errSecParam) { - return status; + if(status == errSecParam) { + return new_SecItemDelete(query); } - // if this search don't support comment, we just use the original search - status = orig_SecItemDelete(query); return status; } __attribute__((constructor)) static void SecItemGuestHooksInit() { - SecItemLabelPrefix = [NSString stringWithUTF8String:getenv("HOME")].lastPathComponent; + containerId = [NSString stringWithUTF8String:getenv("HOME")].lastPathComponent; + NSString* containerInfoPath = [[NSString stringWithUTF8String:getenv("HOME")] stringByAppendingPathComponent:@"LCContainerInfo.plist"]; + NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:containerInfoPath]; + int keychainGroupId = [infoDict[@"keychainGroupId"] intValue]; + NSString* groupId = [[NSUserDefaults.lcMainBundle.bundleIdentifier componentsSeparatedByString:@"."] lastObject]; + if(keychainGroupId == 0) { + accessGroup = [NSString stringWithFormat:@"%@.com.kdt.livecontainer.shared", groupId]; + } else { + accessGroup = [NSString stringWithFormat:@"%@.com.kdt.livecontainer.shared.%d", groupId, keychainGroupId]; + } + rebind_symbols((struct rebinding[1]){{"SecItemAdd", (void *)new_SecItemAdd, (void **)&orig_SecItemAdd}},1); rebind_symbols((struct rebinding[1]){{"SecItemCopyMatching", (void *)new_SecItemCopyMatching, (void **)&orig_SecItemCopyMatching}},1); rebind_symbols((struct rebinding[1]){{"SecItemUpdate", (void *)new_SecItemUpdate, (void **)&orig_SecItemUpdate}},1); diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index 48259fc..da93600 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -11,6 +11,24 @@ static void UIKitGuestHooksInit() { swizzle(UIScene.class, @selector(scene:didReceiveActions:fromTransitionContext:), @selector(hook_scene:didReceiveActions:fromTransitionContext:)); } +NSString* findDefaultContainerWithBundleId(NSString* bundleId) { + // find app's default container + NSString *appGroupPath = [NSUserDefaults lcAppGroupPath]; + NSString* appGroupFolder = [appGroupPath stringByAppendingPathComponent:@"LiveContainer"]; + + NSString* bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", appGroupFolder, bundleId]; + NSDictionary* infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath]; + if(!infoDict) { + NSString* lcDocFolder = [[NSString stringWithUTF8String:getenv("LC_HOME_PATH")] stringByAppendingPathComponent:@"Documents"]; + + bundleInfoPath = [NSString stringWithFormat:@"%@/Applications/%@/Info.plist", lcDocFolder, bundleId]; + infoDict = [NSDictionary dictionaryWithContentsOfFile:bundleInfoPath]; + } + + return infoDict[@"LCDataUUID"]; +} + + void LCShowSwitchAppConfirmation(NSURL *url, NSString* bundleId) { if ([NSUserDefaults.lcUserDefaults boolForKey:@"LCSwitchAppWithoutAsking"]) { [NSClassFromString(@"LCSharedUtils") launchToGuestAppWithURL:url]; @@ -150,6 +168,7 @@ void handleLiveContainerLaunch(NSURL* url) { // check if there are other LCs is running this app NSString* bundleName = nil; NSString* openUrl = nil; + NSString* containerFolderName = nil; NSURLComponents* components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; for (NSURLQueryItem* queryItem in components.queryItems) { if ([queryItem.name isEqualToString:@"bundle-name"]) { @@ -157,17 +176,22 @@ void handleLiveContainerLaunch(NSURL* url) { } else if ([queryItem.name isEqualToString:@"open-url"]) { NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:queryItem.value options:0]; openUrl = [[NSString alloc] initWithData:decodedData encoding:NSUTF8StringEncoding]; + } else if ([queryItem.name isEqualToString:@"container-folder-name"]) { + containerFolderName = queryItem.value; } } - - if ([bundleName isEqualToString:NSBundle.mainBundle.bundlePath.lastPathComponent]) { + NSString* containerId = [NSString stringWithUTF8String:getenv("HOME")].lastPathComponent; + if(!containerFolderName) { + containerFolderName = findDefaultContainerWithBundleId(bundleName); + } + if ([bundleName isEqualToString:NSBundle.mainBundle.bundlePath.lastPathComponent] && [containerId isEqualToString:containerFolderName]) { if(openUrl) { openUniversalLink(openUrl); } } else { - NSString* runningLC = [NSClassFromString(@"LCSharedUtils") getAppRunningLCSchemeWithBundleId:bundleName]; + NSString* runningLC = [NSClassFromString(@"LCSharedUtils") getContainerUsingLCSchemeWithFolderName:containerFolderName]; if(runningLC) { - NSString* urlStr = [NSString stringWithFormat:@"%@://livecontainer-launch?bundle-name=%@", runningLC, bundleName]; + NSString* urlStr = [NSString stringWithFormat:@"%@://livecontainer-launch?bundle-name=%@&container-folder-name=%@", runningLC, bundleName, containerFolderName]; [UIApplication.sharedApplication openURL:[NSURL URLWithString:urlStr] options:@{} completionHandler:nil]; return; } diff --git a/TweakLoader/utils.h b/TweakLoader/utils.h index a193b99..2556414 100644 --- a/TweakLoader/utils.h +++ b/TweakLoader/utils.h @@ -9,4 +9,5 @@ void swizzle(Class class, SEL originalAction, SEL swizzledAction); + (instancetype)lcUserDefaults; + (NSString *)lcAppUrlScheme; + (NSString *)lcAppGroupPath; ++ (NSBundle *)lcMainBundle; @end diff --git a/entitlements.xml b/entitlements.xml index a58ed4b..2bf63c8 100644 --- a/entitlements.xml +++ b/entitlements.xml @@ -18,7 +18,9 @@ keychain-access-groups - *.com.kdt.livecontainer.shared + *.com.kdt.livecontainer.shared + *.com.kdt.livecontainer.shared.1 + *.com.kdt.livecontainer.shared.2 diff --git a/main.m b/main.m index 8938b8b..c115ee0 100644 --- a/main.m +++ b/main.m @@ -193,7 +193,7 @@ static void overwriteExecPath(NSString *bundlePath) { return (void *)header + entryoff; } -static NSString* invokeAppMain(NSString *selectedApp, int argc, char *argv[]) { +static NSString* invokeAppMain(NSString *selectedApp, NSString *selectedContainer, int argc, char *argv[]) { NSString *appError = nil; if (!LCSharedUtils.certificatePassword) { // First of all, let's check if we have JIT @@ -228,8 +228,20 @@ static void overwriteExecPath(NSString *bundlePath) { if(!appBundle) { return @"App not found"; } + + // find container in Info.plist + NSString* dataUUID = selectedContainer; + if(!dataUUID) { + dataUUID = appBundle.infoDictionary[@"LCDataUUID"]; + } + + if(dataUUID == nil) { + return @"Container not found!"; + } + if(isSharedBundle) { [LCSharedUtils setAppRunningByThisLC:selectedApp]; + [LCSharedUtils setContainerUsingByThisLC:dataUUID]; } NSError *error; @@ -279,16 +291,6 @@ static void overwriteExecPath(NSString *bundlePath) { NSUserDefaults.standardUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:appBundle.bundleIdentifier]; } - - // Set & save the folder it it does not exist in Info.plist - NSString* dataUUID = appBundle.infoDictionary[@"LCDataUUID"]; - if(dataUUID == nil) { - NSMutableDictionary* infoDict = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath]]; - dataUUID = NSUUID.UUID.UUIDString; - infoDict[@"LCDataUUID"] = dataUUID; - [infoDict writeToFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath] atomically:YES]; - } - // Overwrite home and tmp path NSString *newHomePath = nil; if(isSharedBundle) { @@ -451,16 +453,27 @@ int LiveContainerMain(int argc, char *argv[]) { [LCSharedUtils moveSharedAppFolderBack]; NSString *selectedApp = [lcUserDefaults stringForKey:@"selected"]; - NSString* runningLC = [LCSharedUtils getAppRunningLCSchemeWithBundleId:selectedApp]; + NSString *selectedContainer = [lcUserDefaults stringForKey:@"selectedContainer"]; + if(selectedApp && !selectedContainer) { + selectedContainer = [LCSharedUtils findDefaultContainerWithBundleId:selectedApp]; + } + NSString* runningLC = [LCSharedUtils getContainerUsingLCSchemeWithFolderName:selectedContainer]; // if another instance is running, we just switch to that one, these should be called after uiapplication initialized if(selectedApp && runningLC) { [lcUserDefaults removeObjectForKey:@"selected"]; + [lcUserDefaults removeObjectForKey:@"selectedContainer"]; NSString* selectedAppBackUp = selectedApp; selectedApp = nil; dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)); dispatch_after(delay, dispatch_get_main_queue(), ^{ // Base64 encode the data - NSString* urlStr = [NSString stringWithFormat:@"%@://livecontainer-launch?bundle-name=%@", runningLC, selectedAppBackUp]; + NSString* urlStr; + if(selectedContainer) { + urlStr = [NSString stringWithFormat:@"%@://livecontainer-launch?bundle-name=%@&container-folder-name=%@", runningLC, selectedAppBackUp, selectedContainer]; + } else { + urlStr = [NSString stringWithFormat:@"%@://livecontainer-launch?bundle-name=%@", runningLC, selectedAppBackUp]; + } + NSURL* url = [NSURL URLWithString:urlStr]; if([[UIApplication sharedApplication] canOpenURL:url]){ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; @@ -482,6 +495,7 @@ int LiveContainerMain(int argc, char *argv[]) { } } else { [LCSharedUtils removeAppRunningByLC: runningLC]; + [LCSharedUtils removeContainerUsingByLC: runningLC]; } }); @@ -491,6 +505,7 @@ int LiveContainerMain(int argc, char *argv[]) { NSString *launchUrl = [lcUserDefaults stringForKey:@"launchAppUrlScheme"]; [lcUserDefaults removeObjectForKey:@"selected"]; + [lcUserDefaults removeObjectForKey:@"selectedContainer"]; // wait for app to launch so that it can receive the url if(launchUrl) { [lcUserDefaults removeObjectForKey:@"launchAppUrlScheme"]; @@ -508,16 +523,17 @@ int LiveContainerMain(int argc, char *argv[]) { } NSSetUncaughtExceptionHandler(&exceptionHandler); setenv("LC_HOME_PATH", getenv("HOME"), 1); - NSString *appError = invokeAppMain(selectedApp, argc, argv); + NSString *appError = invokeAppMain(selectedApp, selectedContainer, argc, argv); if (appError) { [lcUserDefaults setObject:appError forKey:@"error"]; [LCSharedUtils setAppRunningByThisLC:nil]; + [LCSharedUtils setContainerUsingByThisLC:nil]; // potentially unrecovable state, exit now return 1; } } [LCSharedUtils setAppRunningByThisLC:nil]; - + [LCSharedUtils setContainerUsingByThisLC:nil]; // recover language before reaching UI NSArray* savedLaunguage = [lcUserDefaults objectForKey:@"LCLastLanguages"]; if(savedLaunguage) { From 7faf79c1847c46bd9163b732792947863bc9b3a3 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 7 Dec 2024 00:26:31 +0800 Subject: [PATCH 23/32] bugfix --- LiveContainerSwiftUI/LCAppSettingsView.swift | 18 +++++++++++++++--- LiveContainerSwiftUI/LCContainer.swift | 8 +++++++- LiveContainerSwiftUI/LCContainerView.swift | 12 ++++++------ TweakLoader/UIKit+GuestHooks.m | 8 +++++++- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index 5e87e29..b569412 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -99,9 +99,9 @@ struct LCAppSettingsView : View{ Section { List{ - ForEach(model.uiContainers, id:\.self) { container in + ForEach($model.uiContainers, id:\.self) { $container in NavigationLink { - LCContainerView(container: container, uiDefaultDataFolder: $model.uiDefaultDataFolder, delegate: self) + LCContainerView(container: $container, uiDefaultDataFolder: $model.uiDefaultDataFolder, delegate: self) } label: { Text(container.name) } @@ -327,7 +327,13 @@ struct LCAppSettingsView : View{ return } let fm = FileManager() - let dest = LCPath.dataPath.appendingPathComponent(newName) + let dest : URL + if model.uiIsShared { + dest = LCPath.lcGroupDataPath.appendingPathComponent(newName) + } else { + dest = LCPath.dataPath.appendingPathComponent(newName) + } + do { try fm.createDirectory(at: dest, withIntermediateDirectories: false) } catch { @@ -396,6 +402,9 @@ struct LCAppSettingsView : View{ appInfo.setBundlePath(LCPath.lcGroupBundlePath.appendingPathComponent(appInfo.relativeBundlePath).path) appInfo.isShared = true model.uiIsShared = true + for container in model.uiContainers { + container.isShared = true + } } catch { errorInfo = error.localizedDescription errorShow = true @@ -432,6 +441,9 @@ struct LCAppSettingsView : View{ appInfo.setBundlePath(LCPath.bundlePath.appendingPathComponent(appInfo.relativeBundlePath).path) appInfo.isShared = false model.uiIsShared = false + for container in model.uiContainers { + container.isShared = false + } } catch { errorShow = true errorInfo = error.localizedDescription diff --git a/LiveContainerSwiftUI/LCContainer.swift b/LiveContainerSwiftUI/LCContainer.swift index eebc8a6..01a8e20 100644 --- a/LiveContainerSwiftUI/LCContainer.swift +++ b/LiveContainerSwiftUI/LCContainer.swift @@ -108,16 +108,22 @@ class LCContainer : ObservableObject, Hashable { extension LCAppInfo { var containers : [LCContainer] { get { + var upgrade = false // upgrade if let oldDataUUID = dataUUID, containerInfo == nil { containerInfo = [[ "folderName": oldDataUUID, "name": oldDataUUID, ]] + upgrade = true } let dictArr = containerInfo as? [[String : Any]] ?? [] return dictArr.map{ dict in - return LCContainer(infoDict: dict, isShared: isShared) + let ans = LCContainer(infoDict: dict, isShared: isShared) + if upgrade { + ans.makeLCContainerInfoPlist(appIdentifier: bundleIdentifier()!, keychainGroupId: 0) + } + return ans } } set { diff --git a/LiveContainerSwiftUI/LCContainerView.swift b/LiveContainerSwiftUI/LCContainerView.swift index 2d932cd..ff5dc5d 100644 --- a/LiveContainerSwiftUI/LCContainerView.swift +++ b/LiveContainerSwiftUI/LCContainerView.swift @@ -14,7 +14,7 @@ protocol LCContainerViewDelegate { } struct LCContainerView : View { - @ObservedObject var container : LCContainer + @Binding var container : LCContainer let delegate : LCContainerViewDelegate @Binding var uiDefaultDataFolder : String? @@ -30,10 +30,10 @@ struct LCContainerView : View { @State private var successShow = false @State private var successInfo = "" - init(container: LCContainer, uiDefaultDataFolder : Binding, delegate: LCContainerViewDelegate) { - self._container = ObservedObject(wrappedValue: container) + init(container: Binding, uiDefaultDataFolder : Binding, delegate: LCContainerViewDelegate) { + self._container = Binding(projectedValue: container) self.delegate = delegate - self._typingContainerName = State(initialValue: container.name) + self._typingContainerName = State(initialValue: container.wrappedValue.name) self._uiDefaultDataFolder = Binding(projectedValue: uiDefaultDataFolder) } @@ -43,10 +43,10 @@ struct LCContainerView : View { HStack { Text("lc.container.containerName".loc) Spacer() - TextField(container.name, text: $typingContainerName) + TextField("lc.container.containerName".loc, text: $typingContainerName) .multilineTextAlignment(.trailing) .onSubmit { - container.name = typingContainerName + $container.name.wrappedValue = typingContainerName saveContainer() } } diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index da93600..6e21c3f 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -82,9 +82,15 @@ void LCShowAppNotFoundAlert(NSString* bundleId) { } void openUniversalLink(NSString* decodedUrl) { + NSURL* urlToOpen = [NSURL URLWithString: decodedUrl]; + if(![urlToOpen.scheme isEqualToString:@"https"] && ![urlToOpen.scheme isEqualToString:@"http"]) { + [UIApplication.sharedApplication.delegate application:UIApplication.sharedApplication openURL:urlToOpen options:@{}]; + return; + } + UIActivityContinuationManager* uacm = [[UIApplication sharedApplication] _getActivityContinuationManager]; NSUserActivity* activity = [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb]; - activity.webpageURL = [NSURL URLWithString: decodedUrl]; + activity.webpageURL = urlToOpen; NSDictionary* dict = @{ @"UIApplicationLaunchOptionsUserActivityKey": activity, @"UICanvasConnectionOptionsUserActivityKey": activity, From 3f308990c98e2a73d36a0f79954825f25a79465d Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 7 Dec 2024 10:29:34 +0800 Subject: [PATCH 24/32] update build env --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5f0e0e..bc6f35f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,7 +7,7 @@ on: jobs: build: name: Build - runs-on: macos-latest + runs-on: macos-15 steps: - name: Checkout From fb0c16f17edf8f26cba7a2e58b736f2b47e7bc30 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 7 Dec 2024 12:33:37 +0800 Subject: [PATCH 25/32] Hide injectLCItself --- LiveContainerSwiftUI/LCSettingsView.swift | 117 ++++++++++++---------- LiveContainerSwiftUI/Shared.swift | 1 + 2 files changed, 63 insertions(+), 55 deletions(-) diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index 954aeec..feae2d8 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -107,12 +107,14 @@ struct LCSettingsView: View { Text("lc.settings.signOnlyOnExpiration".loc) } } - -// Button { -// export() -// } label: { -// Text("export cert") -// } + if sharedModel.developerMode { + Button { + export() + } label: { + Text("export cert") + } + } + Picker(selection: $defaultSigner) { Text("AltSign").tag(Signer.AltSign) @@ -193,14 +195,16 @@ struct LCSettingsView: View { } footer: { Text("lc.settings.silentSwitchAppDesc".loc) } - - Section { - Toggle(isOn: $injectToLCItelf) { - Text("lc.settings.injectLCItself".loc) + if sharedModel.developerMode { + Section { + Toggle(isOn: $injectToLCItelf) { + Text("lc.settings.injectLCItself".loc) + } + } footer: { + Text("lc.settings.injectLCItselfDesc".loc) } - } footer: { - Text("lc.settings.injectLCItselfDesc".loc) } + if sharedModel.isHiddenAppUnlocked { Section { Toggle(isOn: $strictHiding) { @@ -265,6 +269,9 @@ struct LCSettingsView: View { VStack{ Text(LCUtils.getVersionInfo()) .foregroundStyle(.gray) + .onTapGesture(count: 10) { + sharedModel.developerMode = true + } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) .background(Color(UIColor.systemGroupedBackground)) @@ -661,47 +668,47 @@ struct LCSettingsView: View { } -// func export() { -// let fileManager = FileManager.default -// let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! -// -// // 1. Copy embedded.mobileprovision from the main bundle to Documents -// if let embeddedURL = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") { -// let destinationURL = documentsURL.appendingPathComponent("embedded.mobileprovision") -// do { -// try fileManager.copyItem(at: embeddedURL, to: destinationURL) -// print("Successfully copied embedded.mobileprovision to Documents.") -// } catch { -// print("Error copying embedded.mobileprovision: \(error)") -// } -// } else { -// print("embedded.mobileprovision not found in the main bundle.") -// } -// -// // 2. Read "certData" from UserDefaults and save to cert.p12 in Documents -// if let certData = LCUtils.certificateData() { -// let certFileURL = documentsURL.appendingPathComponent("cert.p12") -// do { -// try certData.write(to: certFileURL) -// print("Successfully wrote certData to cert.p12 in Documents.") -// } catch { -// print("Error writing certData to cert.p12: \(error)") -// } -// } else { -// print("certData not found in UserDefaults.") -// } -// -// // 3. Read "certPassword" from UserDefaults and save to pass.txt in Documents -// if let certPassword = LCUtils.certificatePassword() { -// let passwordFileURL = documentsURL.appendingPathComponent("pass.txt") -// do { -// try certPassword.write(to: passwordFileURL, atomically: true, encoding: .utf8) -// print("Successfully wrote certPassword to pass.txt in Documents.") -// } catch { -// print("Error writing certPassword to pass.txt: \(error)") -// } -// } else { -// print("certPassword not found in UserDefaults.") -// } -// } + func export() { + let fileManager = FileManager.default + let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! + + // 1. Copy embedded.mobileprovision from the main bundle to Documents + if let embeddedURL = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") { + let destinationURL = documentsURL.appendingPathComponent("embedded.mobileprovision") + do { + try fileManager.copyItem(at: embeddedURL, to: destinationURL) + print("Successfully copied embedded.mobileprovision to Documents.") + } catch { + print("Error copying embedded.mobileprovision: \(error)") + } + } else { + print("embedded.mobileprovision not found in the main bundle.") + } + + // 2. Read "certData" from UserDefaults and save to cert.p12 in Documents + if let certData = LCUtils.certificateData() { + let certFileURL = documentsURL.appendingPathComponent("cert.p12") + do { + try certData.write(to: certFileURL) + print("Successfully wrote certData to cert.p12 in Documents.") + } catch { + print("Error writing certData to cert.p12: \(error)") + } + } else { + print("certData not found in UserDefaults.") + } + + // 3. Read "certPassword" from UserDefaults and save to pass.txt in Documents + if let certPassword = LCUtils.certificatePassword() { + let passwordFileURL = documentsURL.appendingPathComponent("pass.txt") + do { + try certPassword.write(to: passwordFileURL, atomically: true, encoding: .utf8) + print("Successfully wrote certPassword to pass.txt in Documents.") + } catch { + print("Error writing certPassword to pass.txt: \(error)") + } + } else { + print("certPassword not found in UserDefaults.") + } + } } diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 2744180..9389b87 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -54,6 +54,7 @@ struct LCPath { class SharedModel: ObservableObject { @Published var isHiddenAppUnlocked = false + @Published var developerMode = false // 0= not installed, 1= is installed, 2=current liveContainer is the second one @Published var multiLCStatus = 0 From 4c89b7753bdc15502627952120119ea95d4b1e61 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sun, 8 Dec 2024 20:05:11 +0800 Subject: [PATCH 26/32] Fix #243 #242 --- LiveContainerSwiftUI/LCAppListView.swift | 1 + TweakLoader/UIKit+GuestHooks.m | 15 ++++++++++++++- UIKitPrivate.h | 8 ++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index a00aea3..fb46f0d 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -470,6 +470,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { finalNewApp.setTweakFolder(appToReplace.appInfo.tweakFolder()) finalNewApp.signer = appToReplace.appInfo.signer finalNewApp.selectedLanguage = appToReplace.appInfo.selectedLanguage + finalNewApp.dataUUID = appToReplace.appInfo.dataUUID } DispatchQueue.main.async { if let appToReplace { diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index 6e21c3f..a63b7de 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -8,6 +8,7 @@ __attribute__((constructor)) static void UIKitGuestHooksInit() { swizzle(UIApplication.class, @selector(_applicationOpenURLAction:payload:origin:), @selector(hook__applicationOpenURLAction:payload:origin:)); + swizzle(UIApplication.class, @selector(_connectUISceneFromFBSScene:transitionContext:), @selector(hook__connectUISceneFromFBSScene:transitionContext:)); swizzle(UIScene.class, @selector(scene:didReceiveActions:fromTransitionContext:), @selector(hook_scene:didReceiveActions:fromTransitionContext:)); } @@ -84,7 +85,13 @@ void LCShowAppNotFoundAlert(NSString* bundleId) { void openUniversalLink(NSString* decodedUrl) { NSURL* urlToOpen = [NSURL URLWithString: decodedUrl]; if(![urlToOpen.scheme isEqualToString:@"https"] && ![urlToOpen.scheme isEqualToString:@"http"]) { - [UIApplication.sharedApplication.delegate application:UIApplication.sharedApplication openURL:urlToOpen options:@{}]; + NSData *data = [decodedUrl dataUsingEncoding:NSUTF8StringEncoding]; + NSString *encodedUrl = [data base64EncodedStringWithOptions:0]; + + NSString* finalUrl = [NSString stringWithFormat:@"%@://open-url?url=%@", NSUserDefaults.lcAppUrlScheme, encodedUrl]; + NSURL* url = [NSURL URLWithString: finalUrl]; + + [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil]; return; } @@ -269,6 +276,12 @@ - (void)hook__applicationOpenURLAction:(id)action payload:(NSDictionary *)payloa [self hook__applicationOpenURLAction:action payload:payload origin:origin]; return; } + +- (void)hook__connectUISceneFromFBSScene:(id)scene transitionContext:(UIApplicationSceneTransitionContext*)context { + context.payload = nil; + context.actions = nil; + [self hook__connectUISceneFromFBSScene:scene transitionContext:context]; +} @end // Handler for SceneDelegate diff --git a/UIKitPrivate.h b/UIKitPrivate.h index 1580e57..4747d61 100644 --- a/UIKitPrivate.h +++ b/UIKitPrivate.h @@ -40,6 +40,14 @@ - (instancetype)initWithURL:(NSURL *)arg1; @end +@interface FBSSceneTransitionContext : NSObject +@property (nonatomic,copy) NSSet * actions; +@end + +@interface UIApplicationSceneTransitionContext : FBSSceneTransitionContext +@property (nonatomic,retain) NSDictionary * payload; +@end + @interface UITableViewHeaderFooterView(private) - (void)setText:(NSString *)text; - (NSString *)text; From df7a822ce1cb20df3f44d713f25099c77d0628d9 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Mon, 9 Dec 2024 13:15:16 +0800 Subject: [PATCH 27/32] fix Some apps setting got reset hugeBlack/LiveContainer#13 --- NSUserDefaults.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NSUserDefaults.m b/NSUserDefaults.m index 8acff90..68ca082 100644 --- a/NSUserDefaults.m +++ b/NSUserDefaults.m @@ -220,7 +220,11 @@ - (void) hook_registerDefaults:(NSDictionary *)registrationDictio preferenceDict = [[NSMutableDictionary alloc] init]; LCPreferences[identifier] = preferenceDict; } - [preferenceDict addEntriesFromDictionary:registrationDictionary]; + for(NSString* key in registrationDictionary) { + if(![preferenceDict objectForKey:key]) { + preferenceDict[key] = registrationDictionary[key]; + } + } LCSavePreference(); } } From 386ae51841550930f6ac10d29cfae08bd06ac944 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Tue, 10 Dec 2024 21:35:29 +0800 Subject: [PATCH 28/32] impelement #252 --- .../LCAppPreferenceView.swift | 403 ++++++++++++++++++ LiveContainerSwiftUI/LCAppSettingsView.swift | 23 + LiveContainerSwiftUI/LCContainerView.swift | 13 + .../project.pbxproj | 6 + LiveContainerSwiftUI/Localizable.xcstrings | 17 + Makefile | 1 - Resources/Root.plist | 155 ------- 7 files changed, 462 insertions(+), 156 deletions(-) create mode 100644 LiveContainerSwiftUI/LCAppPreferenceView.swift delete mode 100644 Resources/Root.plist diff --git a/LiveContainerSwiftUI/LCAppPreferenceView.swift b/LiveContainerSwiftUI/LCAppPreferenceView.swift new file mode 100644 index 0000000..5642400 --- /dev/null +++ b/LiveContainerSwiftUI/LCAppPreferenceView.swift @@ -0,0 +1,403 @@ +// +// LCAppPreferenceView.swift +// LiveContainerSwiftUI +// +// Created by s s on 2024/12/10. +// +import SwiftUI + +protocol LCAppPreferencesDelegate { + func get(key: String) -> AnyHashable? + func set(key: String, val: AnyHashable?) + func localize(_ key: String) -> String +} + + +struct PSTextField: View { + let title : String + let key : String + let defaultValue : String? + let delegate : LCAppPreferencesDelegate + @State var val : String + + init(title: String, key: String, defaultValue: String?, delegate: LCAppPreferencesDelegate) { + self.title = title + self.key = key + self.defaultValue = defaultValue + self.delegate = delegate + self._val = State(initialValue: delegate.get(key: key) as? String ?? defaultValue ?? "") + } + + var body: some View { + HStack { + Text(delegate.localize(title)) + Spacer() + TextField("", text: $val) + .onSubmit { + delegate.set(key: key, val: val) + } + .multilineTextAlignment(.trailing) + } + } + +} + +struct PSTitleValue: View { + let title : String + let key : String + let defaultValue : AnyHashable + let values : [AnyHashable]? + let titles : [String]? + let displayValue : String + let delegate : LCAppPreferencesDelegate + + init(title: String, key: String, defaultValue: AnyHashable, values: [AnyHashable]?, titles: [String]?, delegate: LCAppPreferencesDelegate) { + self.title = title + self.key = key + self.defaultValue = defaultValue + self.values = values + self.titles = titles + self.delegate = delegate + let realValue = delegate.get(key: key) + var defaultValueText = defaultValue as? String ?? "" + let realValueText = realValue as? String ?? defaultValueText + guard let values, let titles, values.count == titles.count else { + displayValue = realValueText + return + } + + for i in 0.. AnyHashable? { + return appUserDefaultDict[key] as? AnyHashable? ?? nil + } + + func set(key: String, val: AnyHashable?) { + appUserDefaultDict[key] = val + (appUserDefaultDict as NSDictionary).write(toFile: userDefaultsPath, atomically: true) + } + + func localize(_ key: String) -> String { + if let table { + let message = NSLocalizedString(key, tableName: table, bundle: settingsBundle, comment: "") + if message != key { + return message + } + if let forcedString = enBundle?.localizedString(forKey: key, value: nil, table: table){ + return forcedString + } else { + return key + } + } else { + return key + } + + } + +} + +struct AppPreferenceView: View { + let rootPlistUrl : URL + let bundleId : String + let settingsBundle : Bundle + let userDefaultsURL : URL + + init(bundleId: String, settingsBundle: Bundle, userDefaultsURL: URL) { + self.bundleId = bundleId + self.settingsBundle = settingsBundle + self.userDefaultsURL = userDefaultsURL + self.rootPlistUrl = settingsBundle.bundleURL.appendingPathComponent("Root.plist") + + } + + var body: some View { + AppPreferencePageView(preferencePlistURL: rootPlistUrl, bundleId: bundleId, userDefaultsURL : userDefaultsURL, settingsBundle: settingsBundle) + } +} diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index b569412..81f39df 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -508,6 +508,10 @@ struct LCAppSettingsView : View{ extension LCAppSettingsView : LCContainerViewDelegate { + func getBundleId() -> String { + return model.appInfo.bundleIdentifier()! + } + func unbindContainer(container: LCContainer) { model.uiContainers.removeAll { c in c === container @@ -549,6 +553,25 @@ extension LCAppSettingsView : LCContainerViewDelegate { appInfo.containers = model.uiContainers } + func getSettingsBundle() -> Bundle? { + return Bundle(url: URL(fileURLWithPath: appInfo.bundlePath()).appendingPathComponent("Settings.bundle")) + } + + func getUserDefaultsURL(container: LCContainer) -> URL { + let preferencesFolderUrl = container.containerURL.appendingPathComponent("Library/Preferences") + let fm = FileManager.default + do { + let doExist = fm.fileExists(atPath: preferencesFolderUrl.path) + if !doExist { + try fm.createDirectory(at: preferencesFolderUrl, withIntermediateDirectories: true) + } + + } catch { + errorInfo = "Cannot create Library/Preferences folder!".loc + errorShow = true + } + return preferencesFolderUrl + } } diff --git a/LiveContainerSwiftUI/LCContainerView.swift b/LiveContainerSwiftUI/LCContainerView.swift index ff5dc5d..e9f4ca2 100644 --- a/LiveContainerSwiftUI/LCContainerView.swift +++ b/LiveContainerSwiftUI/LCContainerView.swift @@ -11,12 +11,17 @@ protocol LCContainerViewDelegate { func unbindContainer(container: LCContainer) func setDefaultContainer(container: LCContainer) func saveContainer(container: LCContainer) + + func getSettingsBundle() -> Bundle? + func getUserDefaultsURL(container: LCContainer) -> URL + func getBundleId() -> String } struct LCContainerView : View { @Binding var container : LCContainer let delegate : LCContainerViewDelegate @Binding var uiDefaultDataFolder : String? + @State var settingsBundle : Bundle? = nil @StateObject private var removeContainerAlert = YesNoHelper() @StateObject private var deleteDataAlert = YesNoHelper() @@ -56,6 +61,13 @@ struct LCContainerView : View { Text(container.folderName) .foregroundStyle(.gray) } + if let settingsBundle { + NavigationLink { + AppPreferenceView(bundleId: delegate.getBundleId(), settingsBundle: settingsBundle, userDefaultsURL: delegate.getUserDefaultsURL(container: container)) + } label: { + Text("lc.container.preferences".loc) + } + } if container.folderName == uiDefaultDataFolder { Text("lc.container.alreadyDefaultContainer".loc) .foregroundStyle(.gray) @@ -159,6 +171,7 @@ struct LCContainerView : View { } .onAppear() { container.reloadInfoPlist() + settingsBundle = delegate.getSettingsBundle() inUse = LCUtils.getContainerUsingLCScheme(containerName: container.folderName) != nil } diff --git a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj index fa45d07..3816ac3 100644 --- a/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj +++ b/LiveContainerSwiftUI/LiveContainerSwiftUI.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 173564D22C76FE3500C6C918 /* LCAppBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173564C72C76FE3500C6C918 /* LCAppBanner.swift */; }; 173564D32C76FE3500C6C918 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 173564C82C76FE3500C6C918 /* Assets.xcassets */; }; 173F18402C7B7B74002953AA /* LCWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173F183F2C7B7B74002953AA /* LCWebView.swift */; }; + 17583D342D08555000FBA7F0 /* LCAppPreferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17583D332D08554400FBA7F0 /* LCAppPreferenceView.swift */; }; 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 178B4C3D2C77654400DD1F74 /* Shared.swift */; }; 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179765E02CF8192200D40B95 /* AppDelegate.swift */; }; 17A7640C2C9D1B6C00456519 /* LCAppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A7640B2C9D1B6C00456519 /* LCAppModel.swift */; }; @@ -54,6 +55,7 @@ 173564C72C76FE3500C6C918 /* LCAppBanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LCAppBanner.swift; sourceTree = ""; }; 173564C82C76FE3500C6C918 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 173F183F2C7B7B74002953AA /* LCWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCWebView.swift; sourceTree = ""; }; + 17583D332D08554400FBA7F0 /* LCAppPreferenceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LCAppPreferenceView.swift; sourceTree = ""; }; 178B4C3D2C77654400DD1F74 /* Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shared.swift; sourceTree = ""; }; 178B4C3F2C7766A300DD1F74 /* LiveContainerSwiftUI-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "LiveContainerSwiftUI-Bridging-Header.h"; sourceTree = ""; }; 179765E02CF8192200D40B95 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -83,6 +85,7 @@ 173564BC2C76FE3500C6C918 /* LCAppListView.swift */, 173564C12C76FE3500C6C918 /* LCSettingsView.swift */, 173564BD2C76FE3500C6C918 /* LCTabView.swift */, + 17583D332D08554400FBA7F0 /* LCAppPreferenceView.swift */, 17C536F32C98529D006C2C75 /* LCAppSettingsView.swift */, 173564C02C76FE3500C6C918 /* LCTweaksView.swift */, 173564C32C76FE3500C6C918 /* Makefile */, @@ -198,6 +201,7 @@ files = ( 171014222CE9B63100673269 /* unarchive.m in Sources */, 171014232CE9B63100673269 /* LCUtils.m in Sources */, + 17583D342D08555000FBA7F0 /* LCAppPreferenceView.swift in Sources */, 179765E12CF8192600D40B95 /* AppDelegate.swift in Sources */, 171014242CE9B63100673269 /* LCMachOUtils.m in Sources */, 178B4C3E2C77654400DD1F74 /* Shared.swift in Sources */, @@ -254,6 +258,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGNING_ALLOWED = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -317,6 +322,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGNING_ALLOWED = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 532569a..36349a6 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -1538,6 +1538,23 @@ } } }, + "lc.container.preferences" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Container Preferences" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "容器设置" + } + } + } + }, "lc.container.removeContainer" : { "extractionState" : "manual", "localizations" : { diff --git a/Makefile b/Makefile index d305a07..a10a453 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,6 @@ $(APPLICATION_NAME)_FILES = dyld_bypass_validation.m main.m utils.m LCSharedUtil $(APPLICATION_NAME)_CODESIGN_FLAGS = -Sentitlements.xml $(APPLICATION_NAME)_CFLAGS = -fobjc-arc $(APPLICATION_NAME)_LDFLAGS = -e _LiveContainerMain -rpath @loader_path/Frameworks -$(APPLICATION_NAME)_FRAMEWORKS = UIKit include $(THEOS_MAKE_PATH)/application.mk diff --git a/Resources/Root.plist b/Resources/Root.plist deleted file mode 100644 index 1421e16..0000000 --- a/Resources/Root.plist +++ /dev/null @@ -1,155 +0,0 @@ - - - - - items - - - cell - PSGroupCell - footerText - JIT-less allows you to use LiveContainer without having to enable JIT. Requires AltStore or SideStore. - label - JIT-less - - - action - setupJITLessPressed - cell - PSButtonCell - id - setup-jitless - label - Setup JIT-less - - - cell - PSGroupCell - footerText - If you see frequent re-sign, enable this option. - - - cell - PSSwitchCell - default - - defaults - com.kdt.livecontainer - key - LCIgnoreALTCertificate - label - Ignore ALTCertificate.p12 - - - cell - PSGroupCell - footerText - Frame shortcut icons with LiveContainer icon. - label - Miscellaneous - - - cell - PSSwitchCell - default - - defaults - com.kdt.livecontainer - key - LCFrameShortcutIcons - label - Frame shortcut icon - - - cell - PSGroupCell - footerText - By default, LiveContainer asks you before switching app. Enable this to switch app immediately. Any unsaved data will be lost. - - - cell - PSSwitchCell - default - - defaults - com.kdt.livecontainer - key - LCSwitchAppWithoutAsking - label - Switch app without asking - - - cell - PSGroupCell - footerText - Place your tweaks into the global “Tweaks” folder and LiveContainer will pick them up. - - - cell - PSSwitchCell - default - - defaults - com.kdt.livecontainer - key - LCLoadTweaksToSelf - label - Load tweaks to LiveContainer itself - - - cell - PSGroupCell - - - action - copyAppGroupPathPressed - cell - PSButtonCell - id - setup-jitless - label - Copy AppGroup path - - - action - copyDocumentsPathPressed - cell - PSButtonCell - id - setup-jitless - label - Copy Documents path - - - cell - PSGroupCell - footerText - Please don’t use LiveContainer for piracy. - label - About me - - - action - openSourceCode - cell - PSButtonCell - icon - GitHub.png - label - khanhduytran0/LiveContainer - - - action - openTwitter - cell - PSButtonCell - icon - Twitter.png - label - @TranKha50277352 - - - title - Settings - - From 0385e3d35fcb7d3097be5b9c588ac54c4e20e36e Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Fri, 13 Dec 2024 18:19:46 +0800 Subject: [PATCH 29/32] fix [Feature Suggestion] Load apps in Landscape mode hugeBlack/LiveContainer#11 --- LiveContainerSwiftUI/LCAppBanner.swift | 10 ++- LiveContainerSwiftUI/LCAppInfo.h | 8 ++ LiveContainerSwiftUI/LCAppInfo.m | 18 ++++- LiveContainerSwiftUI/LCAppListView.swift | 4 + LiveContainerSwiftUI/LCAppModel.swift | 2 + LiveContainerSwiftUI/LCAppSettingsView.swift | 20 +++++ LiveContainerSwiftUI/Localizable.xcstrings | 85 ++++++++++++++++---- LiveContainerSwiftUI/Shared.swift | 3 + TweakLoader/Makefile | 1 + TweakLoader/UIKit+GuestHooks.m | 67 +++++++++++++++ UIKitPrivate.h | 41 ++++++++++ 11 files changed, 235 insertions(+), 24 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppBanner.swift b/LiveContainerSwiftUI/LCAppBanner.swift index a074219..4c104d1 100644 --- a/LiveContainerSwiftUI/LCAppBanner.swift +++ b/LiveContainerSwiftUI/LCAppBanner.swift @@ -64,15 +64,17 @@ struct LCAppBanner : View { HStack { Text(appInfo.displayName()).font(.system(size: 16)).bold() if model.uiIsShared { - Text("lc.appBanner.shared".loc).font(.system(size: 8)).bold().padding(2) - .frame(width: 50, height:16) + Image(systemName: "arrowshape.turn.up.left.fill") + .font(.system(size: 8)) + .frame(width: 16, height:16) .background( Capsule().fill(Color("BadgeColor")) ) } if model.uiIsJITNeeded { - Text("JIT").font(.system(size: 8)).bold().padding(2) - .frame(width: 30, height:16) + Image(systemName: "bolt.fill") + .font(.system(size: 8)) + .frame(width: 16, height:16) .background( Capsule().fill(Color("JITBadgeColor")) ) diff --git a/LiveContainerSwiftUI/LCAppInfo.h b/LiveContainerSwiftUI/LCAppInfo.h index e474bed..dcd0abc 100644 --- a/LiveContainerSwiftUI/LCAppInfo.h +++ b/LiveContainerSwiftUI/LCAppInfo.h @@ -2,6 +2,12 @@ #import #import "LCUtils.h" +typedef NS_ENUM(NSInteger, LCOrientationLock){ + Disabled = 0, + Landscape = 1, + Portrait = 2 +}; + @interface LCAppInfo : NSObject { NSMutableDictionary* _info; NSString* _bundlePath; @@ -15,10 +21,12 @@ @property bool bypassAssertBarrierOnQueue; @property UIColor* cachedColor; @property Signer signer; +@property LCOrientationLock orientationLock; @property bool doUseLCBundleId; @property NSString* selectedLanguage; @property NSString* dataUUID; @property NSArray* containerInfo; +@property bool autoSaveDisabled; - (void)setBundlePath:(NSString*)newBundlePath; - (NSMutableDictionary*)info; diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index 649d6d9..e7e26df 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -12,7 +12,7 @@ - (instancetype)initWithBundlePath:(NSString*)bundlePath { if(self) { _bundlePath = bundlePath; _info = [NSMutableDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Info.plist", bundlePath]]; - + _autoSaveDisabled = false; } return self; } @@ -187,7 +187,10 @@ - (NSDictionary *)generateWebClipConfigWithContainerId:(NSString*)containerId { } - (void)save { - [_info writeToFile:[NSString stringWithFormat:@"%@/Info.plist", _bundlePath] atomically:YES]; + if(!_autoSaveDisabled) { + [_info writeToFile:[NSString stringWithFormat:@"%@/Info.plist", _bundlePath] atomically:YES]; + } + } - (void)preprocessBundleBeforeSiging:(NSURL *)bundleURL completion:(dispatch_block_t)completion { @@ -422,7 +425,16 @@ - (Signer)signer { } - (void)setSigner:(Signer)newSigner { _info[@"signer"] = [NSNumber numberWithInt:(int) newSigner]; - NSLog(@"[LC] new signer = %d", (int) newSigner); + [self save]; + +} + +- (LCOrientationLock)orientationLock { + return (LCOrientationLock) [((NSNumber*) _info[@"LCOrientationLock"]) intValue]; + +} +- (void)setOrientationLock:(LCOrientationLock)orientationLock { + _info[@"LCOrientationLock"] = [NSNumber numberWithInt:(int) orientationLock]; [self save]; } diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index fb46f0d..3b8d5b2 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -460,6 +460,7 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { if let appToReplace { // copy previous configration to new app + finalNewApp.autoSaveDisabled = true finalNewApp.isLocked = appToReplace.appInfo.isLocked finalNewApp.isHidden = appToReplace.appInfo.isHidden finalNewApp.isJITNeeded = appToReplace.appInfo.isJITNeeded @@ -471,6 +472,9 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { finalNewApp.signer = appToReplace.appInfo.signer finalNewApp.selectedLanguage = appToReplace.appInfo.selectedLanguage finalNewApp.dataUUID = appToReplace.appInfo.dataUUID + finalNewApp.orientationLock = appToReplace.appInfo.orientationLock + finalNewApp.autoSaveDisabled = false + finalNewApp.save() } DispatchQueue.main.async { if let appToReplace { diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 8a7b343..9c2dcb4 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -26,6 +26,7 @@ class LCAppModel: ObservableObject, Hashable { @Published var uiUseLCBundleId : Bool @Published var uiBypassAssertBarrierOnQueue : Bool @Published var uiSigner : Signer + @Published var uiOrientationLock : LCOrientationLock @Published var uiSelectedLanguage : String @Published var supportedLanaguages : [String]? @@ -52,6 +53,7 @@ class LCAppModel: ObservableObject, Hashable { self.uiDoSymlinkInbox = appInfo.doSymlinkInbox self.uiBypassAssertBarrierOnQueue = appInfo.bypassAssertBarrierOnQueue self.uiSigner = appInfo.signer + self.uiOrientationLock = appInfo.orientationLock self.uiUseLCBundleId = appInfo.doUseLCBundleId for container in uiContainers { diff --git a/LiveContainerSwiftUI/LCAppSettingsView.swift b/LiveContainerSwiftUI/LCAppSettingsView.swift index 81f39df..6e09644 100644 --- a/LiveContainerSwiftUI/LCAppSettingsView.swift +++ b/LiveContainerSwiftUI/LCAppSettingsView.swift @@ -236,6 +236,21 @@ struct LCAppSettingsView : View{ } footer: { Text("lc.appSettings.useLCBundleIdDesc".loc) } + if sharedModel.isPhone { + Section { + Picker(selection: $model.uiOrientationLock) { + Text("lc.common.disabled".loc).tag(LCOrientationLock.Disabled) + Text("lc.apppSettings.orientationLock.landscape".loc).tag(LCOrientationLock.Landscape) + Text("lc.apppSettings.orientationLock.portrait".loc).tag(LCOrientationLock.Portrait) + } label: { + Text("lc.apppSettings.orientationLock".loc) + } + .onChange(of: model.uiOrientationLock, perform: { newValue in + Task { await setOrientationLock(newValue) } + }) + } + } + Section { Toggle(isOn: $model.uiDoSymlinkInbox) { @@ -477,6 +492,11 @@ struct LCAppSettingsView : View{ model.uiUseLCBundleId = doUseLCBundleId } + func setOrientationLock(_ lock : LCOrientationLock) async { + appInfo.orientationLock = lock + model.uiOrientationLock = lock + } + func loadSupportedLanguages() { do { try model.loadSupportedLanguages() diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 36349a6..470a998 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -222,23 +222,6 @@ } } }, - "lc.appBanner.shared" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "SHARED" - } - }, - "zh_CN" : { - "stringUnit" : { - "state" : "translated", - "value" : "共享" - } - } - } - }, "lc.appBanner.uninstall" : { "extractionState" : "manual", "localizations" : { @@ -637,6 +620,57 @@ } } }, + "lc.apppSettings.orientationLock" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Orientation Lock" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "旋转锁定" + } + } + } + }, + "lc.apppSettings.orientationLock.landscape" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Landscape" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "横屏" + } + } + } + }, + "lc.apppSettings.orientationLock.portrait" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Portrait" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "竖屏" + } + } + } + }, "lc.appSettings.appOpenInOtherLc %@ %@" : { "extractionState" : "manual", "localizations" : { @@ -1198,6 +1232,23 @@ } } }, + "lc.common.disabled" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Disabled" + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "禁用" + } + } + } + }, "lc.common.done" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 9389b87..6678827 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -60,6 +60,9 @@ class SharedModel: ObservableObject { @Published var apps : [LCAppModel] = [] @Published var hiddenApps : [LCAppModel] = [] + let isPhone: Bool = { + UIDevice.current.userInterfaceIdiom == .phone + }() func updateMultiLCStatus() { if LCUtils.appUrlScheme()?.lowercased() != "livecontainer" { diff --git a/TweakLoader/Makefile b/TweakLoader/Makefile index ae63081..f84f202 100644 --- a/TweakLoader/Makefile +++ b/TweakLoader/Makefile @@ -9,5 +9,6 @@ LIBRARY_NAME = TweakLoader TweakLoader_FILES = SecItem.m TweakLoader.m NSBundle+FixCydiaSubstrate.m NSFileManager+GuestHooks.m UIKit+GuestHooks.m utils.m DocumentPicker.m FBSSerialQueue.m ../Localization.m ../fishhook/fishhook.c TweakLoader_CFLAGS = -objc-arc TweakLoader_INSTALL_PATH = /Applications/LiveContainer.app/Frameworks +TweakLoader_PRIVATE_FRAMEWORKS = CoreServices FrontBoard RunningBoardServices include $(THEOS_MAKE_PATH)/library.mk diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index a63b7de..7637be8 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -5,11 +5,35 @@ #import #import "Localization.h" +UIInterfaceOrientation orientationLock = UIInterfaceOrientationUnknown; + __attribute__((constructor)) static void UIKitGuestHooksInit() { swizzle(UIApplication.class, @selector(_applicationOpenURLAction:payload:origin:), @selector(hook__applicationOpenURLAction:payload:origin:)); swizzle(UIApplication.class, @selector(_connectUISceneFromFBSScene:transitionContext:), @selector(hook__connectUISceneFromFBSScene:transitionContext:)); swizzle(UIScene.class, @selector(scene:didReceiveActions:fromTransitionContext:), @selector(hook_scene:didReceiveActions:fromTransitionContext:)); + + NSInteger orientationLockDirection = [NSBundle.mainBundle.infoDictionary[@"LCOrientationLock"] integerValue]; + if([UIDevice.currentDevice userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { + switch (orientationLockDirection) { + case 1: + orientationLock = UIInterfaceOrientationLandscapeRight; + break; + case 2: + orientationLock = UIInterfaceOrientationPortrait; + break; + default: + break; + } + if(orientationLock != UIInterfaceOrientationUnknown) { + swizzle(FBSSceneParameters.class, @selector(initWithXPCDictionary:), @selector(hook_initWithXPCDictionary:)); + swizzle(UIViewController.class, @selector(__supportedInterfaceOrientations), @selector(hook___supportedInterfaceOrientations)); + swizzle(UIViewController.class, @selector(shouldAutorotateToInterfaceOrientation:), @selector(hook_shouldAutorotateToInterfaceOrientation:)); + swizzle(UIWindow.class, @selector(setAutorotates:forceUpdateInterfaceOrientation:), @selector(hook_setAutorotates:forceUpdateInterfaceOrientation:)); + } + + } + } NSString* findDefaultContainerWithBundleId(NSString* bundleId) { @@ -281,6 +305,15 @@ - (void)hook__connectUISceneFromFBSScene:(id)scene transitionContext:(UIApplicat context.payload = nil; context.actions = nil; [self hook__connectUISceneFromFBSScene:scene transitionContext:context]; + if(orientationLock != UIInterfaceOrientationUnknown) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:@"com.apple.springboard"]; + }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:NSUserDefaults.lcMainBundle.bundleIdentifier]; + }); + } } @end @@ -342,3 +375,37 @@ - (void)hook_scene:(id)scene didReceiveActions:(NSSet *)actions fromTransitionCo [self hook_scene:scene didReceiveActions:newActions fromTransitionContext:context]; } @end + +@implementation FBSSceneParameters(LiveContainerHook) +- (instancetype)hook_initWithXPCDictionary:(NSDictionary*)dict { + + FBSSceneParameters* ans = [self hook_initWithXPCDictionary:dict]; + UIMutableApplicationSceneSettings* settings = [ans.settings mutableCopy]; + UIMutableApplicationSceneClientSettings* clientSettings = [ans.clientSettings mutableCopy]; + [settings setInterfaceOrientation:orientationLock]; + [clientSettings setInterfaceOrientation:orientationLock]; + ans.settings = settings; + ans.clientSettings = clientSettings; + return ans; +} +@end + + + +@implementation UIViewController(LiveContainerHook) + +- (UIInterfaceOrientationMask)hook___supportedInterfaceOrientations { + return (UIInterfaceOrientationMask)(1 << orientationLock); +} + +- (BOOL)hook_shouldAutorotateToInterfaceOrientation:(NSInteger)orientation { + return YES; +} + +@end + +@implementation UIWindow(hook) +- (void)hook_setAutorotates:(BOOL)autorotates forceUpdateInterfaceOrientation:(BOOL)force { + [self hook_setAutorotates:YES forceUpdateInterfaceOrientation:YES]; +} +@end diff --git a/UIKitPrivate.h b/UIKitPrivate.h index 4747d61..67063f3 100644 --- a/UIKitPrivate.h +++ b/UIKitPrivate.h @@ -52,3 +52,44 @@ - (void)setText:(NSString *)text; - (NSString *)text; @end + +@interface UIApplicationSceneSettings : NSObject +@end + +@interface UIApplicationSceneClientSettings : NSObject +@end + +@interface UIMutableApplicationSceneSettings : UIApplicationSceneSettings +@property (assign,nonatomic) UIDeviceOrientation deviceOrientation; +- (void)setInterfaceOrientation:(NSInteger)o; +@end + + + +@interface UIMutableApplicationSceneClientSettings : UIApplicationSceneClientSettings +@property (assign,nonatomic) UIDeviceOrientation deviceOrientation; +@property(nonatomic, assign) NSInteger interfaceOrientation; +@property(nonatomic, assign) NSInteger statusBarStyle; +@end + + + +@interface FBSMutableSceneParameters : NSObject +@property(nonatomic, copy) UIMutableApplicationSceneSettings *settings; +@end + + +@interface FBSSceneParameters : NSObject +@property(nonatomic, copy) UIApplicationSceneSettings *settings; +@property(nonatomic, copy) UIApplicationSceneClientSettings *clientSettings; +- (instancetype)initWithXPCDictionary:(NSDictionary*)dict; +@end + +@interface UIWindow (private) +- (void)setAutorotates:(BOOL)autorotates forceUpdateInterfaceOrientation:(BOOL)force; +@end + +@interface LSApplicationWorkspace : NSObject ++ (instancetype)defaultWorkspace; +- (BOOL)openApplicationWithBundleID:(NSString *)arg1 ; +@end From a16097217b4392ab3e666fcbc94093744f24c233 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sat, 14 Dec 2024 15:25:33 +0800 Subject: [PATCH 30/32] Zsign improvement --- LiveContainerSwiftUI/LCAppInfo.h | 2 +- LiveContainerSwiftUI/LCAppInfo.m | 31 ++-- LiveContainerSwiftUI/LCAppListView.swift | 10 +- LiveContainerSwiftUI/LCAppModel.swift | 8 +- LiveContainerSwiftUI/LCUtils.h | 2 +- LiveContainerSwiftUI/LCUtils.m | 4 +- LiveContainerSwiftUI/Localizable.xcstrings | 17 ++ LiveContainerSwiftUI/Shared.swift | 2 +- ZSign/bundle.cpp | 191 ++++++++++----------- ZSign/bundle.h | 3 +- ZSign/macho.cpp | 15 ++ ZSign/macho.h | 2 + ZSign/zsign.hpp | 1 - ZSign/zsign.mm | 17 +- ZSign/zsigner.h | 2 +- ZSign/zsigner.m | 4 +- 16 files changed, 176 insertions(+), 135 deletions(-) diff --git a/LiveContainerSwiftUI/LCAppInfo.h b/LiveContainerSwiftUI/LCAppInfo.h index dcd0abc..26336e5 100644 --- a/LiveContainerSwiftUI/LCAppInfo.h +++ b/LiveContainerSwiftUI/LCAppInfo.h @@ -42,5 +42,5 @@ typedef NS_ENUM(NSInteger, LCOrientationLock){ - (UIImage *)generateLiveContainerWrappedIcon; - (NSDictionary *)generateWebClipConfigWithContainerId:(NSString*)containerId; - (void)save; -- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign; +- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign; @end diff --git a/LiveContainerSwiftUI/LCAppInfo.m b/LiveContainerSwiftUI/LCAppInfo.m index e7e26df..0ae52ab 100644 --- a/LiveContainerSwiftUI/LCAppInfo.m +++ b/LiveContainerSwiftUI/LCAppInfo.m @@ -208,13 +208,12 @@ - (void)preprocessBundleBeforeSiging:(NSURL *)bundleURL completion:(dispatch_blo }); } -// return "SignNeeded" if sign is needed, other wise return an error -- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign { +- (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(bool success, NSString* errorInfo))completetionHandler progressHandler:(void(^)(NSProgress* progress))progressHandler forceSign:(BOOL)forceSign { NSString *appPath = self.bundlePath; NSString *infoPath = [NSString stringWithFormat:@"%@/Info.plist", appPath]; NSMutableDictionary *info = _info; if (!info) { - completetionHandler(@"Info.plist not found"); + completetionHandler(NO, @"Info.plist not found"); return; } @@ -226,7 +225,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo LCPatchExecSlice(path, header); }); if (error) { - completetionHandler(error); + completetionHandler(NO, error); return; } info[@"LCPatchRevision"] = @(currentPatchRev); @@ -234,7 +233,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo } if (!LCUtils.certificatePassword) { - completetionHandler(nil); + completetionHandler(YES, nil); return; } @@ -244,7 +243,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo if(expirationDate && [[[NSUserDefaults alloc] initWithSuiteName:[LCUtils appGroupID]] boolForKey:@"LCSignOnlyOnExpiration"] && !forceSign) { if([expirationDate laterDate:[NSDate now]] == expirationDate) { // not expired yet, don't sign again - completetionHandler(nil); + completetionHandler(YES, nil); return; } } @@ -256,7 +255,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo CC_SHA1(LCUtils.certificateData.bytes, (CC_LONG)LCUtils.certificateData.length, digest); signID = *(uint64_t *)digest + signRevision; } else { - completetionHandler(@"Failed to find signing certificate. Please refresh your store and try again."); + completetionHandler(NO, @"Failed to find signing certificate. Please refresh your store and try again."); return; } @@ -283,7 +282,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo void (^signCompletionHandler)(BOOL success, NSDate* expirationDate, NSError *error) = ^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ - if (!error) { + if (success) { info[@"LCJITLessSignID"] = @(signID); } @@ -291,19 +290,13 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo [NSFileManager.defaultManager removeItemAtPath:tmpExecPath error:nil]; - if(!error && expirationDate) { + if(success && expirationDate) { info[@"LCExpirationDate"] = expirationDate; } // Save sign ID and restore bundle ID [self save]; - if(error) { - completetionHandler(error.localizedDescription); - return; - } else { - completetionHandler(nil); - return; - } + completetionHandler(success, error.localizedDescription); }); }; @@ -312,14 +305,14 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo switch ([self signer]) { case ZSign: - progress = [LCUtils signAppBundleWithZSign:appPathURL execName:info[@"CFBundleExecutable"] completionHandler:signCompletionHandler]; + progress = [LCUtils signAppBundleWithZSign:appPathURL completionHandler:signCompletionHandler]; break; case AltSign: progress = [LCUtils signAppBundle:appPathURL completionHandler:signCompletionHandler]; break; default: - completetionHandler(@"Signer Not Found"); + completetionHandler(NO, @"Signer Not Found"); break; } @@ -330,7 +323,7 @@ - (void)patchExecAndSignIfNeedWithCompletionHandler:(void(^)(NSString* errorInfo } else { // no need to sign again - completetionHandler(nil); + completetionHandler(YES, nil); return; } } diff --git a/LiveContainerSwiftUI/LCAppListView.swift b/LiveContainerSwiftUI/LCAppListView.swift index 3b8d5b2..855df98 100644 --- a/LiveContainerSwiftUI/LCAppListView.swift +++ b/LiveContainerSwiftUI/LCAppListView.swift @@ -442,10 +442,12 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { // patch and sign it var signError : String? = nil + var signSuccess = false await withCheckedContinuation({ c in finalNewApp.signer = Signer(rawValue: LCUtils.appGroupUserDefault.integer(forKey: "LCDefaultSigner"))! - finalNewApp.patchExecAndSignIfNeed(completionHandler: { error in + finalNewApp.patchExecAndSignIfNeed(completionHandler: { success, error in signError = error + signSuccess = success c.resume() }, progressHandler: { signProgress in installProgress.addChild(signProgress!, withPendingUnitCount: 20) @@ -454,7 +456,11 @@ struct LCAppListView : View, LCAppBannerDelegate, LCAppModelDelegate { // we leave it unsigned even if signing failed if let signError { - errorInfo = signError + if signSuccess { + errorInfo = "\("lc.appList.signSuccessWithError".loc)\n\n\(signError)" + } else { + errorInfo = signError + } errorShow = true } diff --git a/LiveContainerSwiftUI/LCAppModel.swift b/LiveContainerSwiftUI/LCAppModel.swift index 9c2dcb4..b4a2719 100644 --- a/LiveContainerSwiftUI/LCAppModel.swift +++ b/LiveContainerSwiftUI/LCAppModel.swift @@ -146,6 +146,7 @@ class LCAppModel: ObservableObject, Hashable { func signApp(force: Bool = false) async throws { var signError : String? = nil + var signSuccess = false defer { DispatchQueue.main.async { self.isSigningInProgress = false @@ -153,8 +154,9 @@ class LCAppModel: ObservableObject, Hashable { } await withCheckedContinuation({ c in - appInfo.patchExecAndSignIfNeed(completionHandler: { error in + appInfo.patchExecAndSignIfNeed(completionHandler: { success, error in signError = error; + signSuccess = success; c.resume() }, progressHandler: { signProgress in guard let signProgress else { @@ -169,7 +171,9 @@ class LCAppModel: ObservableObject, Hashable { }, forceSign: force) }) if let signError { - throw signError + if !signSuccess { + throw signError + } } // sign its tweak diff --git a/LiveContainerSwiftUI/LCUtils.h b/LiveContainerSwiftUI/LCUtils.h index 39d71af..392c956 100644 --- a/LiveContainerSwiftUI/LCUtils.h +++ b/LiveContainerSwiftUI/LCUtils.h @@ -41,7 +41,7 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); + (void)removeCodeSignatureFromBundleURL:(NSURL *)appURL; + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; -+ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; + (BOOL)isAppGroupAltStoreLike; + (Store)store; + (NSString *)appGroupID; diff --git a/LiveContainerSwiftUI/LCUtils.m b/LiveContainerSwiftUI/LCUtils.m index 12fb96d..b658522 100644 --- a/LiveContainerSwiftUI/LCUtils.m +++ b/LiveContainerSwiftUI/LCUtils.m @@ -232,7 +232,7 @@ + (NSProgress *)signAppBundle:(NSURL *)path completionHandler:(void (^)(BOOL suc return [signer signAppAtURL:path provisioningProfiles:@[(id)profile] completionHandler:signCompletionHandler]; } -+ (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execName completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { ++ (NSProgress *)signAppBundleWithZSign:(NSURL *)path completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { NSError *error; // use zsign as our signer~ @@ -248,7 +248,7 @@ + (NSProgress *)signAppBundleWithZSign:(NSURL *)path execName:(NSString*)execNam NSLog(@"[LC] starting signing..."); - NSProgress* ans = [NSClassFromString(@"ZSigner") signWithAppPath:[path path] execName:execName prov:profileData key: self.certificateData pass:self.certificatePassword completionHandler:completionHandler]; + NSProgress* ans = [NSClassFromString(@"ZSigner") signWithAppPath:[path path] prov:profileData key: self.certificateData pass:self.certificatePassword completionHandler:completionHandler]; return ans; } diff --git a/LiveContainerSwiftUI/Localizable.xcstrings b/LiveContainerSwiftUI/Localizable.xcstrings index 470a998..74ec3d2 100644 --- a/LiveContainerSwiftUI/Localizable.xcstrings +++ b/LiveContainerSwiftUI/Localizable.xcstrings @@ -603,6 +603,23 @@ } } }, + "lc.appList.signSuccessWithError" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "App was signed successfully, but LiveContainer was unable to sign the following files. You may be able to use the app, but do expect crash." + } + }, + "zh_CN" : { + "stringUnit" : { + "state" : "translated", + "value" : "App签名成功,但以下文件签名失败。App有可能可以正常运行。" + } + } + } + }, "lc.appList.urlInvalidError" : { "extractionState" : "manual", "localizations" : { diff --git a/LiveContainerSwiftUI/Shared.swift b/LiveContainerSwiftUI/Shared.swift index 6678827..69e514b 100644 --- a/LiveContainerSwiftUI/Shared.swift +++ b/LiveContainerSwiftUI/Shared.swift @@ -432,7 +432,7 @@ extension LCUtils { if signer == .AltSign { progress = LCUtils.signAppBundle(url, completionHandler: compeletionHandler) } else { - progress = LCUtils.signAppBundle(withZSign: url, execName: "LiveContainer.tmp", completionHandler: compeletionHandler) + progress = LCUtils.signAppBundle(withZSign: url, completionHandler: compeletionHandler) } guard let progress = progress else { ans = "lc.utils.initSigningError".loc diff --git a/ZSign/bundle.cpp b/ZSign/bundle.cpp index 6058e5d..dc09fe9 100644 --- a/ZSign/bundle.cpp +++ b/ZSign/bundle.cpp @@ -132,7 +132,7 @@ bool ZAppBundle::GetObjectsToSign(const string &strFolder, JValue &jvInfo) } else if (DT_REG == ptr->d_type) { - if (IsPathSuffix(strNode, ".dylib")) + if (IsPathSuffix(strNode, ".dylib") || is_64bit_macho(strNode.c_str())) { jvInfo["files"].push_back(strNode.substr(m_strAppFolder.size() + 1)); } @@ -360,110 +360,111 @@ bool ZAppBundle::SignNode(JValue &jvNode) if (!macho.Sign(m_pSignAsset, m_bForceSign, mainBundleIdentifier, "", "", "")) { - return false; +// return false; + signFailedFiles += szFile; + signFailedFiles += "\n"; } if(progressHandler) { progressHandler(); } } } - - ZBase64 b64; - string strInfoPlistSHA1; - string strInfoPlistSHA256; - string strFolder = jvNode["path"]; - string strBundleId = jvNode["bid"]; - string strBundleExe = jvNode["exec"]; - b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); - b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); - if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) - { - ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); - return false; - } - - string strBaseFolder = m_strAppFolder; - if ("/" != strFolder) - { - strBaseFolder += "/"; - strBaseFolder += strFolder; - } - - string strExePath = strBaseFolder + "/" + strBundleExe; - ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); - - ZMachO macho; - if (!macho.Init(strExePath.c_str())) - { - ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); - return false; - } - - RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); - CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); - string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; - - JValue jvCodeRes; - if (!m_bForceSign) - { - jvCodeRes.readPListFile(strCodeResFile.c_str()); - } - - // LiveContainer don't need it - if (m_bForceSign || jvCodeRes.isNull()) - { //create + // LiveContainer don't need CodeResources +// ZBase64 b64; +// string strInfoPlistSHA1; +// string strInfoPlistSHA256; +// string strFolder = jvNode["path"]; +// string strBundleId = jvNode["bid"]; +// string strBundleExe = jvNode["exec"]; +// b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); +// b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); +// if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) +// { +// ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); +// return false; +// } +// +// string strBaseFolder = m_strAppFolder; +// if ("/" != strFolder) +// { +// strBaseFolder += "/"; +// strBaseFolder += strFolder; +// } +// +// string strExePath = strBaseFolder + "/" + strBundleExe; +// ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); +// +// ZMachO macho; +// if (!macho.Init(strExePath.c_str())) +// { +// ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); +// return false; +// } +// +// RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); +// CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); +// string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; +// +// JValue jvCodeRes; +// if (!m_bForceSign) +// { +// jvCodeRes.readPListFile(strCodeResFile.c_str()); +// } + +// if (m_bForceSign || jvCodeRes.isNull()) +// { //create // if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) // { // ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); // return false; // } - } - else if (jvNode.has("changed")) - { //use existsed - for (size_t i = 0; i < jvNode["changed"].size(); i++) - { - string strFile = jvNode["changed"][i].asCString(); - string strRealFile = m_strAppFolder + "/" + strFile; - - string strFileSHA1Base64; - string strFileSHA256Base64; - if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) - { - ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); - return false; - } - - string strKey = strFile; - if ("/" != strFolder) - { - strKey = strFile.substr(strFolder.size() + 1); - } - jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; - jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; - jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; - - ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); - } - } - - string strCodeResData; - jvCodeRes.writePList(strCodeResData); - if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) - { - ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); - return false; - } - - bool bForceSign = m_bForceSign; - if ("/" == strFolder && !m_strDyLibPath.empty()) - { //inject dylib - macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); - } - - if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) - { - return false; - } +// } +// else if (jvNode.has("changed")) +// { //use existsed +// for (size_t i = 0; i < jvNode["changed"].size(); i++) +// { +// string strFile = jvNode["changed"][i].asCString(); +// string strRealFile = m_strAppFolder + "/" + strFile; +// +// string strFileSHA1Base64; +// string strFileSHA256Base64; +// if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) +// { +// ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); +// return false; +// } +// +// string strKey = strFile; +// if ("/" != strFolder) +// { +// strKey = strFile.substr(strFolder.size() + 1); +// } +// jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; +// jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; +// jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; +// +// ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); +// } +// } +// +// string strCodeResData; +// jvCodeRes.writePList(strCodeResData); +// if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) +// { +// ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); +// return false; +// } +// +// bool bForceSign = m_bForceSign; +// if ("/" == strFolder && !m_strDyLibPath.empty()) +// { //inject dylib +// macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); +// } +// +// if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) +// { +// return false; +// } if(progressHandler) { progressHandler(); @@ -501,7 +502,6 @@ void ZAppBundle::GetPlugIns(const string &strFolder, vector &arrPlugIns) bool ZAppBundle::ConfigureFolderSign(ZSignAsset *pSignAsset, const string &strFolder, - const string &execName, const string &strBundleID, const string &strBundleVersion, const string &strDisplayName, @@ -672,7 +672,6 @@ bool ZAppBundle::ConfigureFolderSign(ZSignAsset *pSignAsset, { return false; } - jvRoot["files"].push_back(execName); GetNodeChangedFiles(jvRoot, dontGenerateEmbeddedMobileProvision); } else diff --git a/ZSign/bundle.h b/ZSign/bundle.h index 83d21db..29ea063 100644 --- a/ZSign/bundle.h +++ b/ZSign/bundle.h @@ -9,7 +9,7 @@ class ZAppBundle ZAppBundle(); public: - bool ConfigureFolderSign(ZSignAsset *pSignAsset, const string &strFolder, const string &execName, const string &strBundleID, const string &strBundleVersion, const string &strDisplayName, const string &strDyLibFile, bool bForce, bool bWeakInject, bool bEnableCache, bool dontGenerateEmbeddedMobileProvision); + bool ConfigureFolderSign(ZSignAsset *pSignAsset, const string &strFolder, const string &strBundleID, const string &strBundleVersion, const string &strDisplayName, const string &strDyLibFile, bool bForce, bool bWeakInject, bool bEnableCache, bool dontGenerateEmbeddedMobileProvision); bool StartSign(bool enableCache); int GetSignCount(); private: @@ -39,4 +39,5 @@ class ZAppBundle public: string m_strAppFolder; std::function progressHandler; + string signFailedFiles; }; diff --git a/ZSign/macho.cpp b/ZSign/macho.cpp index 15aea21..062e832 100644 --- a/ZSign/macho.cpp +++ b/ZSign/macho.cpp @@ -350,3 +350,18 @@ bool ZMachO::RemoveDylib(const std::set &dylibNames) { ZLog::Warn(">>> Finished removing specified dylibs!\n"); return removalSuccessful; } + + +bool is_64bit_macho(const char *filepath) { + FILE *file = fopen(filepath, "rb"); + if (!file) { + return false; // Failed to open file + } + + uint32_t magic; + fread(&magic, sizeof(uint32_t), 1, file); + fclose(file); + + // 64-bit Mach-O magic number is 0xfeedfacf + return magic == 0xfeedfacf; +} diff --git a/ZSign/macho.h b/ZSign/macho.h index e8bf3da..473a10d 100644 --- a/ZSign/macho.h +++ b/ZSign/macho.h @@ -32,3 +32,5 @@ class ZMachO bool m_bCSRealloced; vector m_arrArchOes; }; + +bool is_64bit_macho(const char *filepath); diff --git a/ZSign/zsign.hpp b/ZSign/zsign.hpp index e8366a0..393e08d 100644 --- a/ZSign/zsign.hpp +++ b/ZSign/zsign.hpp @@ -28,7 +28,6 @@ bool ListDylibs(NSString *filePath, NSMutableArray *dylibPathsArray); bool UninstallDylibs(NSString *filePath, NSArray *dylibPathsArray); void zsign(NSString *appPath, - NSString* execName, NSData *prov, NSData *key, NSString *pass, diff --git a/ZSign/zsign.mm b/ZSign/zsign.mm index 00fd709..93dc896 100644 --- a/ZSign/zsign.mm +++ b/ZSign/zsign.mm @@ -156,7 +156,6 @@ bool ChangeDylibPath(NSString *filePath, NSString *oldPath, NSString *newPath) { ZSignAsset zSignAsset; void zsign(NSString *appPath, - NSString* execName, NSData *prov, NSData *key, NSString *pass, @@ -178,15 +177,13 @@ void zsign(NSString *appPath, string strOutputFile; string strEntitlementsFile; - - bForce = true; + const char* strPKeyFileData = (const char*)[key bytes]; const char* strProvFileData = (const char*)[prov bytes]; strPassword = [pass cStringUsingEncoding:NSUTF8StringEncoding]; string strPath = [appPath cStringUsingEncoding:NSUTF8StringEncoding]; - string execNameStr = [execName cStringUsingEncoding:NSUTF8StringEncoding]; bool _ = ZLog::logs.empty(); @@ -203,7 +200,7 @@ void zsign(NSString *appPath, string strFolder = strPath; __block ZAppBundle bundle; - bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, execNameStr, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); + bool success = bundle.ConfigureFolderSign(&zSignAsset, strFolder, "", "", "", strDyLibFile, bForce, bWeakInject, bEnableCache, bDontGenerateEmbeddedMobileProvision); if(!success) { completionHandler(NO, nil, makeErrorFromLog(ZLog::logs)); @@ -224,7 +221,15 @@ void zsign(NSString *appPath, bool bRet = bundle.StartSign(bEnableCache); timer.PrintResult(bRet, ">>> Signed %s!", bRet ? "OK" : "Failed"); gtimer.Print(">>> Done."); - completionHandler(YES, date, nil); + NSError* signError = nil; + if(!bundle.signFailedFiles.empty()) { + NSDictionary* userInfo = @{ + NSLocalizedDescriptionKey : [NSString stringWithUTF8String:bundle.signFailedFiles.c_str()] + }; + signError = [NSError errorWithDomain:@"Failed to Sign" code:-1 userInfo:userInfo]; + } + + completionHandler(YES, date, signError); _ = ZLog::logs.empty(); return; diff --git a/ZSign/zsigner.h b/ZSign/zsigner.h index c3597be..2aa227e 100644 --- a/ZSign/zsigner.h +++ b/ZSign/zsigner.h @@ -7,5 +7,5 @@ #import @interface ZSigner : NSObject -+ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; ++ (NSProgress*)signWithAppPath:(NSString *)appPath prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler; @end diff --git a/ZSign/zsigner.m b/ZSign/zsigner.m index 1e78a6e..3efaa2f 100644 --- a/ZSign/zsigner.m +++ b/ZSign/zsigner.m @@ -11,10 +11,10 @@ NSProgress* currentZSignProgress; @implementation ZSigner -+ (NSProgress*)signWithAppPath:(NSString *)appPath execName:(NSString *)execName prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { ++ (NSProgress*)signWithAppPath:(NSString *)appPath prov:(NSData *)prov key:(NSData *)key pass:(NSString *)pass completionHandler:(void (^)(BOOL success, NSDate* expirationDate, NSError *error))completionHandler { NSProgress* ans = [NSProgress progressWithTotalUnitCount:1000]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - zsign(appPath, execName, prov, key, pass, ans, completionHandler); + zsign(appPath, prov, key, pass, ans, completionHandler); }); return ans; } From 890a6b7c91e832d2c5a6eb548131b96290ae3404 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sun, 15 Dec 2024 00:49:08 +0800 Subject: [PATCH 31/32] fix #15 #16 --- LiveContainerSwiftUI/LCSettingsView.swift | 2 +- LiveContainerSwiftUI/LCUtils.h | 2 +- LiveContainerSwiftUI/LCUtils.m | 29 ++++++++++++++++------- TweakLoader/UIKit+GuestHooks.m | 27 +++++++++++++++------ 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/LiveContainerSwiftUI/LCSettingsView.swift b/LiveContainerSwiftUI/LCSettingsView.swift index feae2d8..17adb08 100644 --- a/LiveContainerSwiftUI/LCSettingsView.swift +++ b/LiveContainerSwiftUI/LCSettingsView.swift @@ -402,7 +402,7 @@ struct LCSettingsView: View { return; } isJITLessTestInProgress = true - LCUtils.validateJITLessSetup { success, error in + LCUtils.validateJITLessSetup(with: defaultSigner) { success, error in if success { successInfo = "lc.jitlessSetup.success".loc successShow = true diff --git a/LiveContainerSwiftUI/LCUtils.h b/LiveContainerSwiftUI/LCUtils.h index 392c956..6301f99 100644 --- a/LiveContainerSwiftUI/LCUtils.h +++ b/LiveContainerSwiftUI/LCUtils.h @@ -27,7 +27,7 @@ void LCPatchAltStore(const char *path, struct mach_header_64 *header); @interface LCUtils : NSObject -+ (void)validateJITLessSetupWithCompletionHandler:(void (^)(BOOL success, NSError *error))completionHandler; ++ (void)validateJITLessSetupWithSigner:(Signer)signer completionHandler:(void (^)(BOOL success, NSError *error))completionHandler; + (NSURL *)archiveIPAWithBundleName:(NSString*)newBundleName error:(NSError **)error; + (NSURL *)archiveTweakedAltStoreWithError:(NSError **)error; + (NSData *)certificateData; diff --git a/LiveContainerSwiftUI/LCUtils.m b/LiveContainerSwiftUI/LCUtils.m index b658522..a835150 100644 --- a/LiveContainerSwiftUI/LCUtils.m +++ b/LiveContainerSwiftUI/LCUtils.m @@ -98,7 +98,7 @@ + (void)loadStoreFrameworksWithError:(NSError **)error { NSArray *signerFrameworks; - if([self store] == AltStore) { + if([NSFileManager.defaultManager fileExistsAtPath:[self.storeBundlePath URLByAppendingPathComponent:@"Frameworks/KeychainAccess.framework"].path]) { // AltStore requires 1 more framework than sidestore signerFrameworks = @[@"OpenSSL.framework", @"Roxas.framework", @"KeychainAccess.framework", @"AltStoreCore.framework"]; } else { @@ -326,10 +326,10 @@ + (void)writeStoreIDToSetupExecutableWithError:(NSError **)error { [data writeToURL:execPath options:0 error:error]; } -+ (void)validateJITLessSetupWithCompletionHandler:(void (^)(BOOL success, NSError *error))completionHandler { ++ (void)validateJITLessSetupWithSigner:(Signer)signer completionHandler:(void (^)(BOOL success, NSError *error))completionHandler { // Verify that the certificate is usable // Create a test app bundle - NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"CertificateValidation"]; + NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"CertificateValidation.app"]; [NSFileManager.defaultManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; NSString *tmpExecPath = [path stringByAppendingPathComponent:@"LiveContainer.tmp"]; NSString *tmpLibPath = [path stringByAppendingPathComponent:@"TestJITLess.dylib"]; @@ -341,12 +341,23 @@ + (void)validateJITLessSetupWithCompletionHandler:(void (^)(BOOL success, NSErro [info writeToFile:tmpInfoPath atomically:YES]; // Sign the test app bundle - [LCUtils signAppBundle:[NSURL fileURLWithPath:path] - completionHandler:^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - completionHandler(success, error); - }); - }]; + if(signer == AltSign) { + [LCUtils signAppBundle:[NSURL fileURLWithPath:path] + completionHandler:^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(success, error); + }); + }]; + } else { + [LCUtils signAppBundleWithZSign:[NSURL fileURLWithPath:path] + completionHandler:^(BOOL success, NSDate* expirationDate, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + completionHandler(success, error); + }); + }]; + } + + } + (NSURL *)archiveIPAWithBundleName:(NSString*)newBundleName error:(NSError **)error { diff --git a/TweakLoader/UIKit+GuestHooks.m b/TweakLoader/UIKit+GuestHooks.m index 7637be8..a69906d 100644 --- a/TweakLoader/UIKit+GuestHooks.m +++ b/TweakLoader/UIKit+GuestHooks.m @@ -26,6 +26,7 @@ static void UIKitGuestHooksInit() { break; } if(orientationLock != UIInterfaceOrientationUnknown) { + swizzle(UIApplication.class, @selector(_handleDelegateCallbacksWithOptions:isSuspended:restoreState:), @selector(hook__handleDelegateCallbacksWithOptions:isSuspended:restoreState:)); swizzle(FBSSceneParameters.class, @selector(initWithXPCDictionary:), @selector(hook_initWithXPCDictionary:)); swizzle(UIViewController.class, @selector(__supportedInterfaceOrientations), @selector(hook___supportedInterfaceOrientations)); swizzle(UIViewController.class, @selector(shouldAutorotateToInterfaceOrientation:), @selector(hook_shouldAutorotateToInterfaceOrientation:)); @@ -305,15 +306,22 @@ - (void)hook__connectUISceneFromFBSScene:(id)scene transitionContext:(UIApplicat context.payload = nil; context.actions = nil; [self hook__connectUISceneFromFBSScene:scene transitionContext:context]; - if(orientationLock != UIInterfaceOrientationUnknown) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +} + +-(BOOL)hook__handleDelegateCallbacksWithOptions:(id)arg1 isSuspended:(BOOL)arg2 restoreState:(BOOL)arg3 { + BOOL ans = [self hook__handleDelegateCallbacksWithOptions:arg1 isSuspended:arg2 restoreState:arg3]; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ +// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:@"com.apple.springboard"]; - }); - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [[LSApplicationWorkspace defaultWorkspace] openApplicationWithBundleID:NSUserDefaults.lcMainBundle.bundleIdentifier]; }); - } + + }); + + + return ans; } @end @@ -395,7 +403,12 @@ - (instancetype)hook_initWithXPCDictionary:(NSDictionary*)dict { @implementation UIViewController(LiveContainerHook) - (UIInterfaceOrientationMask)hook___supportedInterfaceOrientations { - return (UIInterfaceOrientationMask)(1 << orientationLock); + if(orientationLock == UIInterfaceOrientationLandscapeRight) { + return UIInterfaceOrientationMaskLandscape; + } else { + return UIInterfaceOrientationMaskPortrait; + } + } - (BOOL)hook_shouldAutorotateToInterfaceOrientation:(NSInteger)orientation { From 175a9d0b399812d32b9ac909752bf8cb67b7b679 Mon Sep 17 00:00:00 2001 From: Huge_Black Date: Sun, 15 Dec 2024 11:51:31 +0800 Subject: [PATCH 32/32] fix #257 --- Resources/Info.plist | 18 +++++ ZSign/bundle.cpp | 180 +++++++++++++++++++++---------------------- 2 files changed, 108 insertions(+), 90 deletions(-) diff --git a/Resources/Info.plist b/Resources/Info.plist index 851a8f5..aa7b003 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -264,5 +264,23 @@ remote-notification audio + + GCSupportsControllerUserInteraction + + GCSupportedGameControllers + + + ProfileName + DirectionalGamepad + + + ProfileName + ExtendedGamepad + + + ProfileName + MicroGamepad + + diff --git a/ZSign/bundle.cpp b/ZSign/bundle.cpp index dc09fe9..8bcfd59 100644 --- a/ZSign/bundle.cpp +++ b/ZSign/bundle.cpp @@ -369,102 +369,102 @@ bool ZAppBundle::SignNode(JValue &jvNode) } } } - // LiveContainer don't need CodeResources -// ZBase64 b64; -// string strInfoPlistSHA1; -// string strInfoPlistSHA256; -// string strFolder = jvNode["path"]; -// string strBundleId = jvNode["bid"]; -// string strBundleExe = jvNode["exec"]; -// b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); -// b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); -// if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) -// { -// ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); -// return false; -// } -// -// string strBaseFolder = m_strAppFolder; -// if ("/" != strFolder) -// { -// strBaseFolder += "/"; -// strBaseFolder += strFolder; -// } -// -// string strExePath = strBaseFolder + "/" + strBundleExe; -// ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); -// -// ZMachO macho; -// if (!macho.Init(strExePath.c_str())) -// { -// ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); -// return false; -// } -// -// RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); -// CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); -// string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; -// -// JValue jvCodeRes; -// if (!m_bForceSign) -// { -// jvCodeRes.readPListFile(strCodeResFile.c_str()); -// } + ZBase64 b64; + string strInfoPlistSHA1; + string strInfoPlistSHA256; + string strFolder = jvNode["path"]; + string strBundleId = jvNode["bid"]; + string strBundleExe = jvNode["exec"]; + b64.Decode(jvNode["sha1"].asCString(), strInfoPlistSHA1); + b64.Decode(jvNode["sha2"].asCString(), strInfoPlistSHA256); + if (strBundleId.empty() || strBundleExe.empty() || strInfoPlistSHA1.empty() || strInfoPlistSHA256.empty()) + { + ZLog::ErrorV(">>> Can't Get BundleID or BundleExecute or Info.plist SHASum in Info.plist! %s\n", strFolder.c_str()); + return false; + } + + string strBaseFolder = m_strAppFolder; + if ("/" != strFolder) + { + strBaseFolder += "/"; + strBaseFolder += strFolder; + } + + string strExePath = strBaseFolder + "/" + strBundleExe; + ZLog::PrintV(">>> SignFolder: %s, (%s)\n", ("/" == strFolder) ? basename((char *)m_strAppFolder.c_str()) : strFolder.c_str(), strBundleExe.c_str()); + + ZMachO macho; + if (!macho.Init(strExePath.c_str())) + { + ZLog::ErrorV(">>> Can't Parse BundleExecute File! %s\n", strExePath.c_str()); + return false; + } + + RemoveFolderV("%s/_CodeSignature", strBaseFolder.c_str()); + CreateFolderV("%s/_CodeSignature", strBaseFolder.c_str()); + string strCodeResFile = strBaseFolder + "/_CodeSignature/CodeResources"; + + JValue jvCodeRes; + if (!m_bForceSign) + { + jvCodeRes.readPListFile(strCodeResFile.c_str()); + } -// if (m_bForceSign || jvCodeRes.isNull()) -// { //create + if (m_bForceSign || jvCodeRes.isNull()) + { //create + // LiveContainer don't need CodeResources // if (!GenerateCodeResources(strBaseFolder, jvCodeRes)) // { // ZLog::ErrorV(">>> Create CodeResources Failed! %s\n", strBaseFolder.c_str()); // return false; // } -// } -// else if (jvNode.has("changed")) -// { //use existsed -// for (size_t i = 0; i < jvNode["changed"].size(); i++) -// { -// string strFile = jvNode["changed"][i].asCString(); -// string strRealFile = m_strAppFolder + "/" + strFile; -// -// string strFileSHA1Base64; -// string strFileSHA256Base64; -// if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) -// { -// ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); -// return false; -// } -// -// string strKey = strFile; -// if ("/" != strFolder) -// { -// strKey = strFile.substr(strFolder.size() + 1); -// } -// jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; -// jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; -// jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; -// -// ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); -// } -// } -// -// string strCodeResData; -// jvCodeRes.writePList(strCodeResData); -// if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) -// { -// ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); -// return false; -// } -// -// bool bForceSign = m_bForceSign; -// if ("/" == strFolder && !m_strDyLibPath.empty()) -// { //inject dylib -// macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); -// } -// -// if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) -// { -// return false; -// } + } + else if (jvNode.has("changed")) + { //use existsed + for (size_t i = 0; i < jvNode["changed"].size(); i++) + { + string strFile = jvNode["changed"][i].asCString(); + string strRealFile = m_strAppFolder + "/" + strFile; + + string strFileSHA1Base64; + string strFileSHA256Base64; + if (!SHASumBase64File(strRealFile.c_str(), strFileSHA1Base64, strFileSHA256Base64)) + { + ZLog::ErrorV(">>> Can't Get Changed File SHASumBase64! %s", strFile.c_str()); + return false; + } + + string strKey = strFile; + if ("/" != strFolder) + { + strKey = strFile.substr(strFolder.size() + 1); + } + jvCodeRes["files"][strKey] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash"] = "data:" + strFileSHA1Base64; + jvCodeRes["files2"][strKey]["hash2"] = "data:" + strFileSHA256Base64; + + ZLog::DebugV("\t\tChanged File: %s, %s\n", strFileSHA1Base64.c_str(), strKey.c_str()); + } + } + + string strCodeResData; + jvCodeRes.writePList(strCodeResData); + if (!WriteFile(strCodeResFile.c_str(), strCodeResData)) + { + ZLog::ErrorV("\tWriting CodeResources Failed! %s\n", strCodeResFile.c_str()); + return false; + } + + bool bForceSign = m_bForceSign; + if ("/" == strFolder && !m_strDyLibPath.empty()) + { //inject dylib + macho.InjectDyLib(m_bWeakInject, m_strDyLibPath.c_str(), bForceSign); + } + + if (!macho.Sign(m_pSignAsset, bForceSign, strBundleId, strInfoPlistSHA1, strInfoPlistSHA256, strCodeResData)) + { + return false; + } if(progressHandler) { progressHandler();