diff --git a/StikJIT/StikJIT-Bridging-Header.h b/StikJIT/StikJIT-Bridging-Header.h index c290576..9bc29b7 100644 --- a/StikJIT/StikJIT-Bridging-Header.h +++ b/StikJIT/StikJIT-Bridging-Header.h @@ -5,3 +5,4 @@ #include "idevice.h" #include "heartbeat.h" #include "jit.h" +#include "applist.h" diff --git a/StikJIT/Views/HomeView.swift b/StikJIT/Views/HomeView.swift index d035752..a7842ce 100644 --- a/StikJIT/Views/HomeView.swift +++ b/StikJIT/Views/HomeView.swift @@ -8,9 +8,10 @@ import SwiftUI struct HomeView: View { - @AppStorage("bundleID") private var bundleID: String = "com.stossy11.MeloNX" + @AppStorage("bundleID") private var bundleID: String = "" @State private var isProcessing = false - + @State private var isShowingInstalledApps = false + var body: some View { ZStack { Color(.systemGroupedBackground) @@ -29,13 +30,16 @@ struct HomeView: View { .multilineTextAlignment(.center) } .padding(.top, 40) + RoundedTextField(placeholder: "Enter Bundle ID", text: $bundleID) .padding(.horizontal, 20) + Button(action: { HapticFeedbackHelper.trigger() - startJITInBackground() + startJITInBackground(with: bundleID) }) { - Label(isProcessing ? "Enabling..." : "Enable JIT", systemImage: isProcessing ? "hourglass" : "bolt.fill") + Label(isProcessing ? "Enabling..." : "Enable JIT", + systemImage: isProcessing ? "hourglass" : "bolt.fill") .font(.system(.title3, design: .rounded)) .fontWeight(.semibold) .frame(maxWidth: .infinity) @@ -49,13 +53,36 @@ struct HomeView: View { .disabled(isProcessing) .padding(.horizontal, 20) + Button(action: { + isShowingInstalledApps = true + }) { + Label("View Installed Apps", systemImage: "list.bullet") + .font(.system(.title3, design: .rounded)) + .fontWeight(.semibold) + .frame(maxWidth: .infinity) + .padding() + .background(Color.green) + .foregroundColor(.white) + .cornerRadius(16) + .shadow(color: Color.green.opacity(0.3), radius: 8, x: 0, y: 4) + } + .padding(.horizontal, 20) + Spacer() } .padding() } + .sheet(isPresented: $isShowingInstalledApps) { + InstalledAppsListView { selectedBundle in + bundleID = selectedBundle + isShowingInstalledApps = false + HapticFeedbackHelper.trigger() + startJITInBackground(with: selectedBundle) + } + } } - private func startJITInBackground() { + private func startJITInBackground(with bundleID: String) { isProcessing = true DispatchQueue.global(qos: .background).async { guard let cBundleID = strdup(bundleID) else { @@ -78,6 +105,79 @@ struct HomeView: View { } } +class InstalledAppsViewModel: ObservableObject { + @Published var apps: [String] = [] + + init() { + loadApps() + } + + func loadApps() { + guard let rawPointer = list_installed_apps() else { + self.apps = [] + return + } + + let output = String(cString: rawPointer) + free(rawPointer) + + if output.hasPrefix("Error:") { + self.apps = [] + } else { + self.apps = output.components(separatedBy: "\n").filter { !$0.isEmpty } + } + } +} + +struct InstalledAppsListView: View { + @StateObject var viewModel = InstalledAppsViewModel() + @Environment(\.dismiss) var dismiss + @State private var searchText: String = "" + + var onSelect: (String) -> Void + + var filteredApps: [String] { + if searchText.isEmpty { + return viewModel.apps + } else { + return viewModel.apps.filter { $0.localizedCaseInsensitiveContains(searchText) } + } + } + + var body: some View { + NavigationView { + ZStack { + Color(.systemGroupedBackground) + .edgesIgnoringSafeArea(.all) + + List { + ForEach(filteredApps, id: \.self) { app in + Button(action: { + onSelect(app) + }) { + Text(app) + .font(.system(.body, design: .rounded)) + .padding(.vertical, 8) + } + .listRowBackground(Color(.secondarySystemGroupedBackground)) + .cornerRadius(8) + } + } + .listStyle(InsetGroupedListStyle()) + .navigationTitle("Installed Apps") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Done") { + dismiss() + } + } + } + .searchable(text: $searchText, prompt: "Search Bundle IDs") + } + } + } +} + #Preview { HomeView() } diff --git a/StikJIT/idevice/applist.c b/StikJIT/idevice/applist.c new file mode 100644 index 0000000..f54c877 --- /dev/null +++ b/StikJIT/idevice/applist.c @@ -0,0 +1,102 @@ +// +// applist.c +// StikJIT +// +// Created by Stephen on 3/27/25. +// + +#include "idevice.h" +#include +#include +#include +#include +#include +#include + +char *list_installed_apps() { + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(LOCKDOWN_PORT); + if (inet_pton(AF_INET, "10.7.0.1", &addr.sin_addr) <= 0) { + return strdup("Error: Invalid IP address"); + } + + CFURLRef pairingFileURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), + CFSTR("pairing_file"), + CFSTR("plist"), + NULL); + if (pairingFileURL == NULL) { + return strdup("Error: Pairing file not found"); + } + + char pairingFilePath[PATH_MAX]; + if (!CFURLGetFileSystemRepresentation(pairingFileURL, TRUE, (UInt8 *)pairingFilePath, PATH_MAX)) { + CFRelease(pairingFileURL); + return strdup("Error: Pairing file path conversion failed"); + } + CFRelease(pairingFileURL); + + IdevicePairingFile *pairing_file = NULL; + IdeviceErrorCode err = idevice_pairing_file_read(pairingFilePath, &pairing_file); + if (err != IdeviceSuccess) { + return strdup("Error: Failed to read pairing file"); + } + + TcpProviderHandle *provider = NULL; + err = idevice_tcp_provider_new((struct sockaddr *)&addr, pairing_file, "ExampleProvider", &provider); + if (err != IdeviceSuccess) { + idevice_pairing_file_free(pairing_file); + return strdup("Error: Failed to create TCP provider"); + } + + InstallationProxyClientHandle *client = NULL; + err = installation_proxy_connect_tcp(provider, &client); + if (err != IdeviceSuccess) { + tcp_provider_free(provider); + return strdup("Error: Failed to connect to installation proxy"); + } + + void *apps = NULL; + size_t apps_len = 0; + err = installation_proxy_get_apps(client, NULL, NULL, 0, &apps, &apps_len); + if (err != IdeviceSuccess) { + installation_proxy_client_free(client); + tcp_provider_free(provider); + return strdup("Error: Failed to get apps"); + } + + plist_t *app_list = (plist_t *)apps; + char *result = malloc(8192); // Allocate memory for output + result[0] = '\0'; + + for (size_t i = 0; i < apps_len; i++) { + plist_t app = app_list[i]; + // Check if the app has an "Entitlements" dictionary. + plist_t entitlements = plist_dict_get_item(app, "Entitlements"); + if (entitlements) { + // Look for the "get-task-allow" key. + plist_t taskAllowNode = plist_dict_get_item(entitlements, "get-task-allow"); + if (taskAllowNode) { + int isAllowed = 0; + plist_get_bool_val(taskAllowNode, &isAllowed); + if (isAllowed) { + // Retrieve the bundle identifier if the entitlement is true. + plist_t bundle_id_node = plist_dict_get_item(app, "CFBundleIdentifier"); + if (bundle_id_node) { + char *bundle_id = NULL; + plist_get_string_val(bundle_id_node, &bundle_id); + strcat(result, bundle_id); + strcat(result, "\n"); + free(bundle_id); + } + } + } + } + } + + installation_proxy_client_free(client); + tcp_provider_free(provider); + + return result; +} diff --git a/StikJIT/idevice/applist.h b/StikJIT/idevice/applist.h new file mode 100644 index 0000000..051c459 --- /dev/null +++ b/StikJIT/idevice/applist.h @@ -0,0 +1,13 @@ +// +// applist.h +// StikJIT +// +// Created by Stephen on 3/27/25. +// + +#ifndef APPLIST_H +#define APPLIST_H + +char *list_installed_apps(void); + +#endif /* APPLIST_H */