Skip to content

Commit 0977502

Browse files
hugeBlackstossy11
andauthored
Fix pip & url scheme (#231)
* Add URL Scheme for custom app scripts * add PID and fix script base64 * Fix pip & url scheme --------- Co-authored-by: Stossy11 <69031796+stossy11@users.noreply.github.com>
1 parent 31aace0 commit 0977502

3 files changed

Lines changed: 124 additions & 78 deletions

File tree

StikJIT/JSSupport/RunJSView.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@ class RunJSViewModel: ObservableObject {
2323
self.semaphore = semaphore
2424
}
2525

26-
func runScript(path: URL) throws {
27-
let scriptContent = try String(contentsOf: path, encoding: .utf8)
28-
scriptName = path.lastPathComponent
26+
func runScript(path: URL, scriptName: String? = nil) throws {
27+
try runScript(data: Data(contentsOf: path), name: scriptName)
28+
}
29+
30+
func runScript(data: Data, name: String? = nil) throws {
31+
let scriptContent = String(data: data, encoding: .utf8)
32+
scriptName = name ?? "Script"
2933

3034
let getPidFunction: @convention(block) () -> Int = {
3135
return self.pid

StikJIT/Utilities/Extensions.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//
55
// Created by s s on 2025/7/9.
66
//
7+
import UniformTypeIdentifiers
78

89
extension FileManager {
910
func filePath(atPath path: String, withLength length: Int) -> String? {
@@ -17,3 +18,9 @@ public extension ProcessInfo {
1718
{ if let boot = FileManager.default.filePath(atPath: "/System/Volumes/Preboot", withLength: 36), let file = FileManager.default.filePath(atPath: "\(boot)/boot", withLength: 96) { return access("\(file)/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4", F_OK) == 0 } else { return (FileManager.default.filePath(atPath: "/private/preboot", withLength: 96).map { access("\($0)/usr/standalone/firmware/FUD/Ap,TrustedExecutionMonitor.img4", F_OK) == 0 }) ?? false } }()
1819
}
1920
}
21+
22+
extension UIDocumentPickerViewController {
23+
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
24+
return fix_init(forOpeningContentTypes: contentTypes, asCopy: true)
25+
}
26+
}

StikJIT/Views/HomeView.swift

Lines changed: 110 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import SwiftUI
99
import UniformTypeIdentifiers
1010
import Pipify
1111

12-
extension UIDocumentPickerViewController {
13-
@objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController {
14-
return fix_init(forOpeningContentTypes: contentTypes, asCopy: true)
15-
}
12+
struct JITEnableConfiguration {
13+
var bundleID: String? = nil
14+
var pid : Int? = nil
15+
var scriptData: Data? = nil
16+
var scriptName : String? = nil
1617
}
1718

1819
struct HomeView: View {
@@ -37,13 +38,13 @@ struct HomeView: View {
3738
@State private var pidStr = ""
3839

3940
@State private var viewDidAppeared = false
40-
@State private var pendingBundleIdToEnableJIT : String? = nil
41-
@State private var pendingPIDToEnableJIT : Int? = nil
41+
@State private var pendingJITEnableConfiguration : JITEnableConfiguration? = nil
4242
@AppStorage("enableAdvancedOptions") private var enableAdvancedOptions = false
4343

4444
@AppStorage("useDefaultScript") private var useDefaultScript = false
4545
@AppStorage("enablePiP") private var enablePiP = true
4646
@State var scriptViewShow = false
47+
@State var pipRequired = false
4748
@AppStorage("DefaultScriptName") var selectedScript = "attachDetach.js"
4849
@State var jsModel: RunJSViewModel?
4950

@@ -301,12 +302,12 @@ struct HomeView: View {
301302
bundleID = selectedBundle
302303
isShowingInstalledApps = false
303304
HapticFeedbackHelper.trigger()
304-
startJITInBackground(with: selectedBundle)
305+
startJITInBackground(bundleID: selectedBundle)
305306
}
306307
}
307308
.pipify(isPresented: Binding(
308-
get: { useDefaultScript && enablePiP && isProcessing },
309-
set: { newValue in isProcessing = newValue }
309+
get: { pipRequired && enablePiP },
310+
set: { newValue in pipRequired = newValue }
310311
)) {
311312
RunJSViewPiP(model: $jsModel)
312313
}
@@ -318,7 +319,6 @@ struct HomeView: View {
318319
ToolbarItem(placement: .topBarTrailing) {
319320
Button("Done") {
320321
scriptViewShow = false
321-
isProcessing = false
322322
}
323323
}
324324
}
@@ -348,7 +348,7 @@ struct HomeView: View {
348348
showAlert(title: "", message: "Invalid PID".localized, showOk: true, completion: { _ in })
349349
return
350350
}
351-
startJITInBackground(with: pid)
351+
startJITInBackground(pid: pid)
352352

353353
},
354354
actionCancel: {_ in
@@ -361,31 +361,37 @@ struct HomeView: View {
361361
return
362362
}
363363

364+
var config = JITEnableConfiguration()
364365
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
366+
367+
if let pidStr = components?.queryItems?.first(where: { $0.name == "pid" })?.value, let pid = Int(pidStr) {
368+
config.pid = pid
369+
}
365370
if let bundleId = components?.queryItems?.first(where: { $0.name == "bundle-id" })?.value {
366-
if viewDidAppeared {
367-
startJITInBackground(with: bundleId)
368-
} else {
369-
pendingBundleIdToEnableJIT = bundleId
370-
}
371-
} else if let pidStr = components?.queryItems?.first(where: { $0.name == "pid" })?.value, let pid = Int(pidStr) {
372-
if viewDidAppeared {
373-
startJITInBackground(with: pid)
374-
} else {
375-
pendingPIDToEnableJIT = pid
371+
config.bundleID = bundleId
372+
}
373+
if let scriptBase64URL = components?.queryItems?.first(where: { $0.name == "script-data" })?.value?.removingPercentEncoding {
374+
let base64 = base64URLToBase64(scriptBase64URL)
375+
if let scriptData = Data(base64Encoded: base64) {
376+
config.scriptData = scriptData
376377
}
377378
}
379+
if let scriptName = components?.queryItems?.first(where: { $0.name == "script-name" })?.value {
380+
config.scriptName = scriptName
381+
}
382+
383+
if viewDidAppeared {
384+
startJITInBackground(bundleID: config.bundleID, pid: config.pid, scriptData: config.scriptData, scriptName: config.scriptName, triggeredByURLScheme: true)
385+
} else {
386+
pendingJITEnableConfiguration = config
387+
}
378388

379389
}
380390
.onAppear() {
381391
viewDidAppeared = true
382-
if let pendingBundleIdToEnableJIT {
383-
startJITInBackground(with: pendingBundleIdToEnableJIT)
384-
self.pendingBundleIdToEnableJIT = nil
385-
}
386-
if let pendingPIDToEnableJIT {
387-
startJITInBackground(with: pendingPIDToEnableJIT)
388-
self.pendingPIDToEnableJIT = nil
392+
if let config = pendingJITEnableConfiguration {
393+
startJITInBackground(bundleID: config.bundleID, pid: config.pid, scriptData: config.scriptData, scriptName: config.scriptName, triggeredByURLScheme: true)
394+
self.pendingJITEnableConfiguration = nil
389395
}
390396
}
391397
}
@@ -410,84 +416,113 @@ struct HomeView: View {
410416
// but we'll keep it empty to avoid breaking anything
411417
}
412418

413-
private func getJsCallback(for scriptName: String? = nil) -> DebugAppCallback? {
414-
let name = scriptName ?? selectedScript
415-
let selectedScriptURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
416-
.appendingPathComponent("scripts").appendingPathComponent(name)
417-
418-
if !FileManager.default.fileExists(atPath: selectedScriptURL.path) {
419-
return nil
420-
}
421-
419+
private func getJsCallback(_ script: Data, name: String? = nil) -> DebugAppCallback {
422420
return { pid, debugProxyHandle, semaphore in
423421
jsModel = RunJSViewModel(pid: Int(pid), debugProxy: debugProxyHandle, semaphore: semaphore)
424422
scriptViewShow = true
423+
425424
DispatchQueue.global(qos: .background).async {
426425
do {
427-
try jsModel?.runScript(path: selectedScriptURL)
428-
isProcessing = false
426+
try jsModel?.runScript(data: script, name: name)
429427
} catch {
430428
showAlert(title: "Error Occurred While Executing the Default Script.".localized, message: error.localizedDescription, showOk: true)
431429
}
432430
}
433431
}
434432
}
435433

436-
private func startJITInBackground(with bundleID: String) {
434+
// launch app following this order: pid > bundleID
435+
// load script following this order: scriptData > script file from script name > saved script for bundleID > default script
436+
// if advanced mode is disabled the whole script loading will be skipped. If use default script is disabled default script will not be loaded
437+
private func startJITInBackground(bundleID: String? = nil, pid : Int? = nil, scriptData: Data? = nil, scriptName : String? = nil, triggeredByURLScheme: Bool = false) {
437438
isProcessing = true
438-
439439
// Add log message
440-
LogManager.shared.addInfoLog("Starting Debug for \(bundleID)")
440+
LogManager.shared.addInfoLog("Starting Debug for \(bundleID ?? String(pid ?? 0))")
441441

442442
DispatchQueue.global(qos: .background).async {
443-
var callback: DebugAppCallback? = nil
444-
if enableAdvancedOptions {
445-
let mapping = UserDefaults.standard.dictionary(forKey: "BundleScriptMap") as? [String: String]
446-
if let script = mapping?[bundleID] {
447-
callback = getJsCallback(for: script)
448-
} else if useDefaultScript {
449-
callback = getJsCallback()
443+
var scriptData = scriptData
444+
var scriptName = scriptName
445+
if enableAdvancedOptions && scriptData == nil {
446+
if scriptName == nil, let bundleID, let mapping = UserDefaults.standard.dictionary(forKey: "BundleScriptMap") as? [String: String] {
447+
scriptName = mapping[bundleID]
450448
}
449+
450+
if useDefaultScript && scriptName == nil {
451+
scriptName = selectedScript
452+
}
453+
454+
if scriptData == nil, let scriptName {
455+
let selectedScriptURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
456+
.appendingPathComponent("scripts").appendingPathComponent(scriptName)
457+
458+
if FileManager.default.fileExists(atPath: selectedScriptURL.path) {
459+
do {
460+
scriptData = try Data(contentsOf: selectedScriptURL)
461+
} catch {
462+
print("failed to load data from script \(error)")
463+
}
464+
465+
}
466+
}
467+
} else {
468+
scriptData = nil
451469
}
452-
let success = JITEnableContext.shared.debugApp(withBundleID: bundleID, logger: { message in
470+
471+
472+
var callback: DebugAppCallback? = nil
473+
474+
if let scriptData {
475+
callback = getJsCallback(scriptData, name: scriptName ?? bundleID ?? "Script")
476+
if triggeredByURLScheme {
477+
usleep(500000)
478+
}
453479

480+
pipRequired = true
481+
}
482+
483+
let logger: LogFunc = { message in
484+
454485
if let message = message {
455486
// Log messages from the JIT process
456487
LogManager.shared.addInfoLog(message)
457488
}
458-
}, jsCallback: callback)
489+
}
490+
var success : Bool
491+
if let pid {
492+
success = JITEnableContext.shared.debugApp(withPID: Int32(pid), logger: logger, jsCallback: callback)
493+
} else if let bundleID {
494+
success = JITEnableContext.shared.debugApp(withBundleID: bundleID, logger: logger, jsCallback: callback)
495+
} else {
496+
DispatchQueue.main.async {
497+
showAlert(title: "Failed to Debug App".localized, message: "Either bundle ID or PID should be specified.".localized, showOk: true)
498+
}
499+
success = false
500+
}
459501

460-
DispatchQueue.main.async {
461-
LogManager.shared.addInfoLog("Debug process completed for \(bundleID)")
462-
isProcessing = false
502+
if success {
503+
DispatchQueue.main.async {
504+
LogManager.shared.addInfoLog("Debug process completed for \(bundleID ?? String(pid ?? 0))")
505+
}
463506
}
507+
isProcessing = false
508+
pipRequired = false
464509
}
465510
}
466511

467-
private func startJITInBackground(with pid: Int) {
468-
isProcessing = true
469-
470-
// Add log message
471-
LogManager.shared.addInfoLog("Starting JIT for pid \(pid)")
472-
473-
DispatchQueue.global(qos: .background).async {
512+
func base64URLToBase64(_ base64url: String) -> String {
513+
var base64 = base64url
514+
.replacingOccurrences(of: "-", with: "+")
515+
.replacingOccurrences(of: "_", with: "/")
474516

475-
let jsCallback: DebugAppCallback? = (enableAdvancedOptions && useDefaultScript) ? getJsCallback() : nil
476-
let success = JITEnableContext.shared.debugApp(withPID: Int32(pid), logger: { message in
477-
478-
if let message = message {
479-
// Log messages from the JIT process
480-
LogManager.shared.addInfoLog(message)
481-
}
482-
}, jsCallback: jsCallback)
483-
484-
DispatchQueue.main.async {
485-
LogManager.shared.addInfoLog("JIT process completed for \(pid)")
486-
showAlert(title: "Success".localized, message: String(format: "JIT has been enabled for pid %d.".localized, pid), showOk: true, messageType: .success)
487-
isProcessing = false
488-
}
517+
// Pad with "=" to make length a multiple of 4
518+
let paddingLength = 4 - (base64.count % 4)
519+
if paddingLength < 4 {
520+
base64 += String(repeating: "=", count: paddingLength)
489521
}
522+
523+
return base64
490524
}
525+
491526
}
492527

493528
class InstalledAppsViewModel: ObservableObject {

0 commit comments

Comments
 (0)