imessage_private_api/
injection.rs1use imessage_core::config::AppPaths;
7use tracing::{error, info, warn};
8
9use crate::HELPER_DYLIB;
10use crate::service::PrivateApiService;
11
12pub async fn relaunch_app_with_dylib(app_name: &str) -> Result<(), String> {
15 let dylib_dir = AppPaths::user_data().join("private-api");
16 let dylib_path = dylib_dir.join("imessage-helper.dylib");
17
18 if !dylib_path.exists() {
19 return Err(format!(
20 "Helper dylib not found at {}",
21 dylib_path.display()
22 ));
23 }
24
25 let app_path = [
26 format!("/System/Applications/{app_name}.app/Contents/MacOS/{app_name}"),
27 format!("/Applications/{app_name}.app/Contents/MacOS/{app_name}"),
28 ]
29 .iter()
30 .find(|p| std::path::Path::new(p).exists())
31 .cloned()
32 .ok_or_else(|| format!("{app_name}.app binary not found"))?;
33
34 info!("Relaunching {app_name}.app with helper dylib...");
35 let child = tokio::process::Command::new(&app_path)
36 .env("DYLD_INSERT_LIBRARIES", &dylib_path)
37 .stdout(std::process::Stdio::null())
38 .stderr(std::process::Stdio::piped())
39 .spawn()
40 .map_err(|e| format!("Failed to spawn {app_name}.app: {e}"))?;
41
42 let log_name = app_name.to_string();
44 tokio::spawn(async move {
45 match child.wait_with_output().await {
46 Ok(output) => {
47 if !output.status.success() {
48 let stderr = String::from_utf8_lossy(&output.stderr);
49 warn!(
50 "{}.app exited with status {} after relaunch. stderr: {}",
51 log_name, output.status, stderr
52 );
53 }
54 }
55 Err(e) => warn!("Error waiting for {}.app after relaunch: {e}", log_name),
56 }
57 });
58
59 let hide_name = app_name.to_string();
61 tokio::spawn(async move {
62 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
63 let script = format!(
64 "tell application \"System Events\" to set visible of process \"{}\" to false",
65 hide_name
66 );
67 let _ = tokio::process::Command::new("osascript")
68 .arg("-e")
69 .arg(&script)
70 .output()
71 .await;
72 });
73
74 Ok(())
75}
76
77pub async fn inject_app_dylib(service: &PrivateApiService, app_name: &str) {
78 let dylib_dir = AppPaths::user_data().join("private-api");
80 if let Err(e) = std::fs::create_dir_all(&dylib_dir) {
81 error!("Failed to create dylib directory: {e}");
82 return;
83 }
84 let dylib_path = dylib_dir.join("imessage-helper.dylib");
85 if let Err(e) = std::fs::write(&dylib_path, HELPER_DYLIB) {
86 error!("Failed to write helper dylib: {e}");
87 return;
88 }
89
90 let app_bin = [
92 format!("/System/Applications/{app_name}.app/Contents/MacOS/{app_name}"),
93 format!("/Applications/{app_name}.app/Contents/MacOS/{app_name}"),
94 ]
95 .iter()
96 .find(|p| std::path::Path::new(p).exists())
97 .cloned();
98
99 let Some(app_path) = app_bin else {
100 warn!("{app_name}.app binary not found!");
101 return;
102 };
103
104 info!("Injecting helper dylib into {}.app", app_name);
105
106 let mut failure_count = 0u32;
107 let mut last_error_time = std::time::Instant::now();
108
109 while failure_count < 5 {
110 let _ = tokio::process::Command::new("killall")
112 .arg(app_name)
113 .output()
114 .await;
115 tokio::time::sleep(std::time::Duration::from_secs(1)).await;
116
117 if service.is_connected().await {
119 info!("{app_name} helper already connected, skipping injection");
120 return;
121 }
122
123 info!("Launching {app_name}.app with DYLD_INSERT_LIBRARIES...");
124 let result = tokio::process::Command::new(&app_path)
125 .env("DYLD_INSERT_LIBRARIES", &dylib_path)
126 .stdout(std::process::Stdio::null())
127 .stderr(std::process::Stdio::piped())
128 .spawn();
129
130 match result {
131 Ok(mut child) => {
132 let hide_name = app_name.to_string();
134 tokio::spawn(async move {
135 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
136 let script = format!(
137 "tell application \"System Events\" to set visible of process \"{}\" to false",
138 hide_name
139 );
140 let _ = tokio::process::Command::new("osascript")
141 .arg("-e")
142 .arg(&script)
143 .output()
144 .await;
145 });
146
147 match child.wait().await {
149 Ok(status) if status.success() => {
150 info!("{app_name}.app exited cleanly, restarting dylib...");
151 failure_count = 0;
152 }
153 Ok(status) => {
154 warn!("{app_name}.app exited with status: {status}");
155 if last_error_time.elapsed().as_secs() > 15 {
156 failure_count = 0;
157 }
158 failure_count += 1;
159 last_error_time = std::time::Instant::now();
160 }
161 Err(e) => {
162 warn!("Error waiting for {app_name}.app: {e}");
163 if last_error_time.elapsed().as_secs() > 15 {
164 failure_count = 0;
165 }
166 failure_count += 1;
167 last_error_time = std::time::Instant::now();
168 }
169 }
170 }
171 Err(e) => {
172 error!("Failed to spawn {app_name}.app: {e}");
173 failure_count += 1;
174 last_error_time = std::time::Instant::now();
175 }
176 }
177 }
178
179 if failure_count >= 5 {
180 error!("Failed to start {app_name}.app with dylib after 5 attempts, giving up");
181 }
182}