Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

feat: add app detail isInjected #16

Merged
merged 3 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 55 additions & 42 deletions InjectGUI/Backend/Injector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,25 +33,25 @@ extension InjectStage {
var description: String {
switch self {
case .start:
return "Start Injecting"
"Start Injecting"
case .copyExecutableFileAsBackup:
return "Copying Executable File as Backup"
"Copying Executable File as Backup"
case .checkPermissionAndRun:
return "Checking Permission and Run"
"Checking Permission and Run"
case .handleKeygen:
return "Handling Keygen"
"Handling Keygen"
case .handleDeepCodeSign:
return "Handling Deep Code Sign"
"Handling Deep Code Sign"
case .handleAutoHandleHelper:
return "Handling Auto Handle Helper"
"Handling Auto Handle Helper"
case .handleTccutil:
return "Handling Tccutil"
"Handling Tccutil"
case .handleExtraShell:
return "Handling Extra Shell"
"Handling Extra Shell"
case .handleInjectLibInject:
return "Handling Inject Lib Inject"
"Handling Inject Lib Inject"
case .end:
return "Injecting Finished"
"Injecting Finished"
}
}
}
Expand Down Expand Up @@ -94,6 +94,7 @@ class Injector: ObservableObject {

func startInjectApp(package: String) {
// MARK: - 拦截 Setapp 旗下软件

if package.contains("com.setapp") {
let alert = NSAlert()
alert.messageText = "Please read the Setapp inject document first"
Expand Down Expand Up @@ -162,7 +163,7 @@ class Injector: ObservableObject {
if case .failure(let error) = completion {
let alert = NSAlert()
alert.messageText = "Command Execution Error"

// Extracting the AppleScript error message
var errorMessage = error.localizedDescription
if let appleScriptError = error as NSError? {
Expand Down Expand Up @@ -219,26 +220,26 @@ class Injector: ObservableObject {
func commandsForStage(_ stage: InjectStage) -> [(command: String, isAdmin: Bool)] {
switch stage {
case .copyExecutableFileAsBackup:
return self.copyExecutableFileAsBackupCommands()
self.copyExecutableFileAsBackupCommands()
case .checkPermissionAndRun:
return self.checkPermissionAndRunCommands()
self.checkPermissionAndRunCommands()
case .handleInjectLibInject:
return self.handleInjectLibInjectAdminCommands()
self.handleInjectLibInjectAdminCommands()
case .handleKeygen:
return self.handleKeygenCommands()
self.handleKeygenCommands()
case .handleDeepCodeSign:
return self.handleDeepCodeSignCommands()
self.handleDeepCodeSignCommands()
case .handleAutoHandleHelper:
return self.handleAutoHandleHelperCommands()
self.handleAutoHandleHelperCommands()
case .handleTccutil:
return self.handleTccutilCommands()
self.handleTccutilCommands()
case .handleExtraShell:
return self.handleExtraShellCommands()
self.handleExtraShellCommands()
// case .end:
// let openApp = "open '\((self.appDetail?.path ?? "").replacingOccurrences(of: "/Contents", with: ""))'"
// return [(openApp, false)]
default:
return []
[]
}
}

Expand All @@ -249,47 +250,59 @@ class Injector: ObservableObject {
case appleScript
case bash
}

private func getBridgeDir(executable: Bool? = nil) -> String {
if (executable ?? false) || self.injectDetail?.autoHandleSetapp == true {
return "/MacOS/"
self.getBridgeDir(executable: executable, injectDetail: self.injectDetail)
}

private func getBridgeDir(executable: Bool? = nil, injectDetail: AppList?) -> String {
if (executable ?? false) || injectDetail?.autoHandleSetapp == true {
"/MacOS/"
} else {
return self.injectDetail?.bridgeFile?.replacingOccurrences(of: "/Contents", with: "") ?? "/Frameworks/"
injectDetail?.bridgeFile?.replacingOccurrences(of: "/Contents", with: "") ?? "/Frameworks/"
}
}

private func genSourcePath(for type: GenScriptType) -> String {
func genSourcePath(for type: GenScriptType) -> String {
let bridgeDir = self.getBridgeDir()
let source = (self.appDetail?.path ?? "") + bridgeDir + (self.injectDetail?.injectFile ?? self.appDetail?.executable ?? "")
return self.transformPath(path: source, to: type)
}
private func genSourcePath(for type: GenScriptType, executable: Bool? = nil) -> String {

private func genSourcePath(for type: GenScriptType, executable: Bool) -> String {
let bridgeDir = self.getBridgeDir(executable: executable)
let source = (self.appDetail?.path ?? "") + bridgeDir + (executable ?? true ? (self.appDetail?.executable ?? "") : (self.injectDetail?.injectFile ?? ""))
let source = (self.appDetail?.path ?? "") + bridgeDir + (executable ? (self.appDetail?.executable ?? "") : (self.injectDetail?.injectFile ?? ""))
return self.transformPath(path: source, to: type)
}

private func genSourcePath(for type: GenScriptType, file: String? = nil) -> String {
let bridgeDir = self.getBridgeDir()
let source = (self.appDetail?.path ?? "") + bridgeDir + (file ?? (self.injectDetail?.injectFile ?? self.appDetail?.executable ?? ""))
func genSourcePath(for type: GenScriptType, file: String?) -> String {
self.genSourcePath(for: type, appList: self.injectDetail, file: file)
}

func genSourcePath(for type: GenScriptType, appList: AppList?, file: String?) -> String {
let bridgeDir = self.getBridgeDir(injectDetail: appList)
let source = (self.appDetail?.path ?? "") + bridgeDir + (file ?? "")
return self.transformPath(path: source, to: type)
}

private func genSourcePath(for type: GenScriptType, path: String? = nil) -> String {
let bridgeDir = self.getBridgeDir()
let source = path ?? ((self.appDetail?.path ?? "") + bridgeDir + (self.injectDetail?.injectFile ?? self.appDetail?.executable ?? ""))
func genSourcePath(for type: GenScriptType, appList: AppList) -> String {
let bridgeDir = self.getBridgeDir(injectDetail: appList)
let source = (self.appDetail?.path ?? "") + bridgeDir + (self.injectDetail?.injectFile ?? self.appDetail?.executable ?? "")
return self.transformPath(path: source, to: type)
}

private func genSourcePath(for type: GenScriptType, path: String?) -> String {
self.transformPath(path: path ?? "", to: type)
}

private func transformPath(path: String, to type: GenScriptType) -> String {
switch type {
case .none:
return path.replacingOccurrences(of: "%20", with: " ")
path.replacingOccurrences(of: "%20", with: " ")
case .appleScript:
return path.replacingOccurrences(of: "%20", with: " ").replacingOccurrences(of: " ", with: "\\\\ ")
path.replacingOccurrences(of: "%20", with: " ").replacingOccurrences(of: " ", with: "\\\\ ")
case .bash:
return path.replacingOccurrences(of: "%20", with: " ").replacingOccurrences(of: " ", with: "\\ ")
path.replacingOccurrences(of: "%20", with: " ").replacingOccurrences(of: " ", with: "\\ ")
}
}

Expand Down Expand Up @@ -392,7 +405,7 @@ class Injector: ObservableObject {
}

let entitlements = self.injectDetail?.entitlements
if let entitlements = entitlements {
if let entitlements {
let entitlementDownloadURL = injectConfiguration.generateInjectToolDownloadURL(name: entitlements)
let downloadIntoTmpPath = try? FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: URL(fileURLWithPath: "/"), create: true)
let entitlementsPath = downloadIntoTmpPath?.appendingPathComponent(entitlements).path
Expand Down Expand Up @@ -474,7 +487,7 @@ class Injector: ObservableObject {

shells.append((downloadCommand, false))
shells.append(("chmod +x \(downloadPath)", false))
if replaceCommands.count > 0 {
if !replaceCommands.isEmpty {
shells.append(contentsOf: replaceCommands.map { ($0, false) })
}
shells.append(("sudo sh \(downloadPath)", true))
Expand All @@ -488,7 +501,7 @@ class Injector: ObservableObject {
var shells: [(command: String, isAdmin: Bool)] = []
let helperFile = self.injectDetail?.helperFile?.allStrings // [String]?
let autoHandleHelper = self.injectDetail?.autoHandleHelper // Bool?
if let helperFile = helperFile, let autoHandleHelper = autoHandleHelper {
if let helperFile, let autoHandleHelper {
var helpers: [String] = []
if autoHandleHelper {
helpers = helperFile
Expand Down Expand Up @@ -519,7 +532,7 @@ class Injector: ObservableObject {
let rmCommand = ("sudo /bin/rm \(target)", true)
let rmPrivilegedHelper = "sudo /bin/rm /Library/PrivilegedHelperTools/\(helperName!)"
let xattrCommand = "sudo xattr -c '\((self.appDetail?.path ?? "").replacingOccurrences(of: "/Contents", with: ""))'"

let codeSignHelperCommand = "/usr/bin/codesign -f -s - --all-architectures --deep '\(targetHelper)'"
let codeSignAppCommand = "/usr/bin/codesign -f -s - --all-architectures --deep '\((self.appDetail?.path ?? "").replacingOccurrences(of: "/Contents", with: ""))'"

Expand Down Expand Up @@ -551,7 +564,7 @@ class Injector: ObservableObject {

func handleTccutilCommands() -> [(command: String, isAdmin: Bool)] {
let tccutil = self.injectDetail?.tccutil?.allStrings // [String]?
if let tccutil = tccutil {
if let tccutil {
var ids = [self.appDetail?.identifier]
if let componentApp = self.injectDetail?.componentApp {
ids.append(contentsOf: componentApp.map { self.readBundleID(app: URL(fileURLWithPath: (self.appDetail?.path ?? "").replacingOccurrences(of: "/Contents", with: "") + $0)) })
Expand Down
112 changes: 65 additions & 47 deletions InjectGUI/Backend/SoftwareManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,32 @@ struct AppDetail {
let path: String // -> path
let executable: String // -> CFBundleExecutable
let icon: NSImage
var isInjected: Bool = false
}

class SoftwareManager: ObservableObject {
static let shared = SoftwareManager()

@Published var appListCache: [String: AppDetail] = [:]
@Published var isLoading = false
@Published var isLoading = false

private init() {
refreshAppList()
}

private init() {
refreshAppList()
}
func refreshAppList() {
DispatchQueue.main.async {
self.isLoading = true
}

DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.getList()
DispatchQueue.main.async {
self?.isLoading = false
}
}
}
DispatchQueue.main.async {
self.isLoading = true
}

DispatchQueue.global(qos: .userInitiated).async { [weak self] in
self?.getList()
DispatchQueue.main.async {
self?.isLoading = false
}
}
}

private func loadAppInfo(
from plistPath: String
) -> AppDetail? {
Expand All @@ -64,15 +67,14 @@ class SoftwareManager: ObservableObject {
let iconFileRaw = plist["CFBundleIconFile"] as? String ?? plist["CFBundleIconName"] as? String

// 检查文件名并添加扩展名(如果需要)
let iconFile: String?
if let iconFileRaw = iconFileRaw {
iconFile = iconFileRaw.hasSuffix(
let iconFile: String? = if let iconFileRaw {
iconFileRaw.hasSuffix(
".icns"
) ? iconFileRaw : iconFileRaw.appending(
".icns"
)
} else {
iconFile = nil
nil
}

// 检查 iconFile 是否为 nil
Expand Down Expand Up @@ -102,38 +104,39 @@ class SoftwareManager: ObservableObject {
version: bundleVersion,
path: path,
executable: bundleExecutable,
icon: icon ?? NSImage()
icon: icon ?? NSImage(),
isInjected: checkForInjection(appPath: path, package: bundleIdentifier)
)
}

func getList() {
print("[*] Getting app list...")
let applicationDirectories = [
"/Applications",
"/Applications/Setapp",
]
let fileManager = FileManager.default

var newAppListCache: [String: AppDetail] = [:]

for directory in applicationDirectories {
guard let appPaths = try? fileManager.contentsOfDirectory(atPath: directory) else {
continue
}

for appPath in appPaths {
let fullPath = "\(directory)/\(appPath)"
let infoPlistPath = "\(fullPath)/Contents/Info.plist"
if let appInfo = loadAppInfo(from: infoPlistPath) {
newAppListCache[appInfo.identifier] = appInfo
}
}
}

DispatchQueue.main.async { [weak self] in
self?.appListCache = newAppListCache
}
}
print("[*] Getting app list...")
let applicationDirectories = [
"/Applications",
"/Applications/Setapp",
]
let fileManager = FileManager.default

var newAppListCache: [String: AppDetail] = [:]

for directory in applicationDirectories {
guard let appPaths = try? fileManager.contentsOfDirectory(atPath: directory) else {
continue
}

for appPath in appPaths {
let fullPath = "\(directory)/\(appPath)"
let infoPlistPath = "\(fullPath)/Contents/Info.plist"
if let appInfo = loadAppInfo(from: infoPlistPath) {
newAppListCache[appInfo.identifier] = appInfo
}
}
}

DispatchQueue.main.async { [weak self] in
self?.appListCache = newAppListCache
}
}

func addAnMaybeExistAppToList(appBaseLocate: String) {
// print("[*] try to add \(appBaseLocate) to list...")
Expand All @@ -150,4 +153,19 @@ class SoftwareManager: ObservableObject {
print("[*] Checking if \(package) is installed...")
return appListCache[package] != nil
}

private func checkForInjection(appPath: String, package: String) -> Bool {
let injector = Injector.shared

guard let appList = InjectConfiguration.shared.injectDetail(package: package) else {
return false
}
let paths = [
injector.genSourcePath(for: .none, appList: appList, file: "91Qiuchenly.dylib"),
injector.genSourcePath(for: .none, appList: appList).appending("_backup"),
injector.genSourcePath(for: .none, appList: appList).appending(".backup"),
]

return paths.contains { FileManager.default.fileExists(atPath: appPath + $0) }
}
}
Loading