Add developer mode

This commit is contained in:
naturecodevoid
2023-02-19 08:06:33 -08:00
parent 49b9be160f
commit 81409227d6
15 changed files with 822 additions and 8 deletions

View File

@@ -83,7 +83,12 @@
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; };
99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */; };
99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A61299F3EC300ED09A9 /* FileExplorer.swift */; };
99D87A6529A04D5E00ED09A9 /* Inject in Frameworks */ = {isa = PBXBuildFile; productRef = 99D87A6429A04D5E00ED09A9 /* Inject */; };
99DE640129A1271100B920BF /* AsyncFallibleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE640029A1271100B920BF /* AsyncFallibleButton.swift */; };
99DE640329A1624500B920BF /* View+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE640229A1624500B920BF /* View+SideStore.swift */; };
99DE640629A1753800B920BF /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 99DE640529A1753800B920BF /* ZIPFoundation */; };
99E59E1D299BFE5D00FAF33D /* AppIconsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */; };
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -633,6 +638,10 @@
1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonProgressViewStyle.swift; sourceTree = "<group>"; };
1FFA56C1299994390011B6F5 /* OutputCapturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputCapturer.swift; sourceTree = "<group>"; };
1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = "<group>"; };
99D87A5F299F1B1100ED09A9 /* DevModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevModeView.swift; sourceTree = "<group>"; };
99D87A61299F3EC300ED09A9 /* FileExplorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileExplorer.swift; sourceTree = "<group>"; };
99DE640029A1271100B920BF /* AsyncFallibleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncFallibleButton.swift; sourceTree = "<group>"; };
99DE640229A1624500B920BF /* View+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+SideStore.swift"; sourceTree = "<group>"; };
99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconsView.swift; sourceTree = "<group>"; };
B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = "<group>"; };
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
@@ -1056,6 +1065,7 @@
1F07F5672955D16A00F7BE95 /* SFSafeSymbols in Frameworks */,
1FFA56C52999978C0011B6F5 /* LocalConsole in Frameworks */,
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
99DE640629A1753800B920BF /* ZIPFoundation in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1215,6 +1225,7 @@
1F545E82298D79E400589F68 /* ErrorLogView.swift */,
1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */,
99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */,
99D87A5F299F1B1100ED09A9 /* DevModeView.swift */,
);
path = Settings;
sourceTree = "<group>";
@@ -1241,6 +1252,8 @@
1F6E08E529280F4B005059C0 /* RatingStars.swift */,
1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */,
1F545E86298D86D800589F68 /* ModalNavigationLink.swift */,
99D87A61299F3EC300ED09A9 /* FileExplorer.swift */,
99DE640029A1271100B920BF /* AsyncFallibleButton.swift */,
);
path = "View Components";
sourceTree = "<group>";
@@ -1951,6 +1964,7 @@
1F66F5BD2938F06100A910CA /* StoreApp+Filterable.swift */,
1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */,
1F180F93298E7A2500D1C98B /* Source+Trusted.swift */,
99DE640229A1624500B920BF /* View+SideStore.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -2312,6 +2326,7 @@
1F1295802989B51F0048FCB9 /* ExpandableText */,
1FFA56C42999978C0011B6F5 /* LocalConsole */,
99D87A6429A04D5E00ED09A9 /* Inject */,
99DE640529A1753800B920BF /* ZIPFoundation */,
);
productName = AltStore;
productReference = BFD2476A2284B9A500981D42 /* SideStore.app */;
@@ -2389,6 +2404,7 @@
1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */,
1FFA56C32999978C0011B6F5 /* XCRemoteSwiftPackageReference "LocalConsole" */,
99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */,
99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
);
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
projectDirPath = "";
@@ -2758,12 +2774,14 @@
buildActionMask = 2147483647;
files = (
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
99DE640129A1271100B920BF /* AsyncFallibleButton.swift in Sources */,
1F6E08E029280B12005059C0 /* SafariView.swift in Sources */,
1F943C6C2927F90400ABE095 /* SettingsView.swift in Sources */,
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */,
1F66F5C02938F07C00A910CA /* Filterable.swift in Sources */,
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */,
D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */,
1F07F556295458D800F7BE95 /* Assets.swift in Sources */,
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
@@ -2835,6 +2853,7 @@
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */,
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */,
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
@@ -2871,6 +2890,7 @@
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */,
99DE640329A1624500B920BF /* View+SideStore.swift in Sources */,
1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */,
1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */,
1F66F5BA2938CA5700A910CA /* VisualEffectView.swift in Sources */,
@@ -3809,6 +3829,14 @@
minimumVersion = 1.0.0;
};
};
99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/weichsel/ZIPFoundation.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.9.9;
};
};
B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
@@ -3920,6 +3948,11 @@
package = 99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */;
productName = Inject;
};
99DE640529A1753800B920BF /* ZIPFoundation */ = {
isa = XCSwiftPackageProductDependency;
package = 99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
productName = ZIPFoundation;
};
B3C395F0284F2DE700DA9E2F /* KeychainAccess */ = {
isa = XCSwiftPackageProductDependency;
package = B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */;

View File

@@ -134,6 +134,15 @@
"branch" : "master",
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
}
},
{
"identity" : "zipfoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/weichsel/ZIPFoundation.git",
"state" : {
"revision" : "43ec568034b3731101dbf7670765d671c30f54f3",
"version" : "0.9.16"
}
}
],
"version" : 2

View File

@@ -10,6 +10,7 @@ import UIKit
import UserNotifications
import AVFoundation
import Intents
import LocalConsole
import AltStoreCore
import AltSign
@@ -64,6 +65,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
// Register default settings before doing anything else.
UserDefaults.registerDefaults()
LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled
DatabaseManager.shared.start { (error) in
if let error = error
{

View File

@@ -0,0 +1,22 @@
//
// View+SideStore.swift
// SideStore
//
// Created by naturecodevoid on 2/18/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
// https://stackoverflow.com/a/59228385 (modified)
extension View {
@ViewBuilder func isHidden(_ hidden: Binding<Bool>, remove: Bool = false) -> some View {
if hidden.wrappedValue {
if !remove {
self.hidden()
}
} else {
self
}
}
}

View File

@@ -31,7 +31,14 @@ internal enum Asset {
internal static let blurTint = ColorAsset(name: "BlurTint")
internal static let settingsBackground = ColorAsset(name: "SettingsBackground")
internal static let settingsHighlighted = ColorAsset(name: "SettingsHighlighted")
internal static let honeydewImage = ImageAsset(name: "Honeydew-image")
internal static let midnightImage = ImageAsset(name: "Midnight-image")
internal static let neonImage = ImageAsset(name: "Neon-image")
internal static let next = ImageAsset(name: "Next")
internal static let skyImage = ImageAsset(name: "Sky-image")
internal static let starburstImage = ImageAsset(name: "Starburst-image")
internal static let steelImage = ImageAsset(name: "Steel-image")
internal static let stormImage = ImageAsset(name: "Storm-image")
internal static let browse = ImageAsset(name: "Browse")
internal static let myApps = ImageAsset(name: "MyApps")
internal static let news = ImageAsset(name: "News")

View File

@@ -15,6 +15,8 @@ internal enum L10n {
internal static let close = L10n.tr("Localizable", "Action.close", fallback: "Close")
/// General Actions
internal static let done = L10n.tr("Localizable", "Action.done", fallback: "Done")
/// Enable
internal static let enable = L10n.tr("Localizable", "Action.enable", fallback: "Enable")
}
internal enum AddSourceView {
/// Continue
@@ -150,6 +152,10 @@ internal enum L10n {
/// AppRowView
internal static let sideloaded = L10n.tr("Localizable", "AppRowView.sideloaded", fallback: "Sideloaded")
}
internal enum AsyncFallibleButton {
/// AsyncFallibleButton
internal static let error = L10n.tr("Localizable", "AsyncFallibleButton.error", fallback: "An error occurred")
}
internal enum BrowseView {
/// Search
internal static let search = L10n.tr("Localizable", "BrowseView.search", fallback: "Search")
@@ -223,6 +229,41 @@ internal enum L10n {
/// Why do we need this?
internal static let whyDoWeNeedThis = L10n.tr("Localizable", "ConnectAppleIDView.whyDoWeNeedThis", fallback: "Why do we need this?")
}
internal enum DevModeView {
/// Console
internal static let console = L10n.tr("Localizable", "DevModeView.console", fallback: "Console")
/// Data File Explorer
internal static let dataExplorer = L10n.tr("Localizable", "DevModeView.dataExplorer", fallback: "Data File Explorer")
/// minimuxer debug actions
internal static let minimuxer = L10n.tr("Localizable", "DevModeView.minimuxer", fallback: "minimuxer debug actions")
/// SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**
///
/// You should only enable Developer Mode if you meet one of the following requirements:
/// - You are a SideStore developer or contributor
/// - When getting support, you were asked to do this by a helper
/// - You were asked to do this when you reported a bug or helped a developer test a change
///
/// **_We will not provide support if you break SideStore with Developer Mode._**
internal static let prompt = L10n.tr("Localizable", "DevModeView.prompt", fallback: "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**\n\nYou should only enable Developer Mode if you meet one of the following requirements:\n- You are a SideStore developer or contributor\n- When getting support, you were asked to do this by a helper\n- You were asked to do this when you reported a bug or helped a developer test a change\n\n**_We will not provide support if you break SideStore with Developer Mode._**")
/// Read the text!
internal static let read = L10n.tr("Localizable", "DevModeView.read", fallback: "Read the text!")
/// Skip Resign
internal static let skipResign = L10n.tr("Localizable", "DevModeView.skipResign", fallback: "Skip Resign")
/// Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). Useful for debugging ApplicationVerificationError
internal static let skipResignInfo = L10n.tr("Localizable", "DevModeView.skipResignInfo", fallback: "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). Useful for debugging ApplicationVerificationError")
/// DevModeView
internal static let title = L10n.tr("Localizable", "DevModeView.title", fallback: "Developer Mode")
/// Temporary File Explorer
internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.tmpExplorer", fallback: "Temporary File Explorer")
internal enum Minimuxer {
/// Dump provisioning profiles to Documents directory
internal static let dumpProfiles = L10n.tr("Localizable", "DevModeView.Minimuxer.dumpProfiles", fallback: "Dump provisioning profiles to Documents directory")
/// PublicStaging File Explorer
internal static let stagingExplorer = L10n.tr("Localizable", "DevModeView.Minimuxer.stagingExplorer", fallback: "PublicStaging File Explorer")
/// View provisioning profiles
internal static let viewProfiles = L10n.tr("Localizable", "DevModeView.Minimuxer.viewProfiles", fallback: "View provisioning profiles")
}
}
internal enum MyAppsView {
/// MyAppsView
internal static let active = L10n.tr("Localizable", "MyAppsView.active", fallback: "Active")

View File

@@ -204,6 +204,8 @@
</dict>
</dict>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
</dict>

View File

@@ -8,6 +8,8 @@
import Foundation
import Roxas
import SwiftUI
import ZIPFoundation
import AltStoreCore
import AltSign
@@ -15,6 +17,9 @@ import AltSign
@objc(ResignAppOperation)
final class ResignAppOperation: ResultOperation<ALTApplication>
{
static var skipResign: Bool = false
static var skipResignBinding: Binding<Bool> { Binding<Bool>(get: { skipResign }, set: { skipResign = $0 }) }
let context: InstallAppOperationContext
init(context: InstallAppOperationContext)
@@ -50,6 +55,23 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
guard let appBundleURL = self.process(result) else { return }
if ResignAppOperation.skipResign {
print("⚠️ WARNING: Skipping resign. Unless you correctly resigned the IPA before installing it, things will not work! Also, this might crash SideStore. You have been warned!")
let ipaFile = self.context.temporaryDirectory.appendingPathComponent("App.ipa")
let archive = Archive(url: ipaFile, accessMode: .create)!
for case let fileURL as URL in FileManager.default.enumerator(at: appBundleURL, includingPropertiesForKeys: [])! {
let relative = fileURL.description.replacingOccurrences(of: appBundleURL.description, with: "").removingPercentEncoding!
try! archive.addEntry(with: "Payload/App.app\(relative)", fileURL: fileURL)
}
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
try! FileManager.default.copyItem(at: ipaFile, to: destinationURL, shouldReplace: true)
// Use appBundleURL since we need an app bundle, not .ipa.
let resignedApplication = ALTApplication(fileURL: appBundleURL)!
self.finish(.success(resignedApplication))
return
}
print("Resigning App:", self.context.bundleIdentifier)
// Resign app bundle

View File

@@ -10,13 +10,12 @@
/* General Actions */
"Action.done" = "Done";
"Action.close" = "Close";
"Action.enable" = "Enable";
/* NewsView */
"NewsView.title" = "News";
"NewsView.Section.FromSources.title" = "From your Sources";
/* BrowseView */
"BrowseView.title" = "Browse";
"BrowseView.search" = "Search";
@@ -166,3 +165,27 @@
/* AppIconsView */
"AppIconsView.title" = "App Icon";
/* DevModeView */
"DevModeView.title" = "Developer Mode";
"DevModeView.prompt" = "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**
You should only enable Developer Mode if you meet one of the following requirements:
- You are a SideStore developer or contributor
- When getting support, you were asked to do this by a helper
- You were asked to do this when you reported a bug or helped a developer test a change
**_We will not provide support if you break SideStore with Developer Mode._**";
"DevModeView.read" = "Read the text!";
"DevModeView.console" = "Console";
"DevModeView.dataExplorer" = "Data File Explorer";
"DevModeView.tmpExplorer" = "Temporary File Explorer";
"DevModeView.skipResign" = "Skip Resign";
"DevModeView.skipResignInfo" = "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). Useful for debugging ApplicationVerificationError";
"DevModeView.minimuxer" = "minimuxer debug actions";
"DevModeView.Minimuxer.stagingExplorer" = "PublicStaging File Explorer";
"DevModeView.Minimuxer.viewProfiles" = "View provisioning profiles";
"DevModeView.Minimuxer.dumpProfiles" = "Dump provisioning profiles to Documents directory";
/* AsyncFallibleButton */
"AsyncFallibleButton.error" = "An error occurred";

View File

@@ -0,0 +1,118 @@
//
// AsyncFallibleButton.swift
// SideStore
//
// Created by naturecodevoid on 2/18/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
private enum AsyncFallibleButtonState {
case none
case loading
case success
case error
}
struct AsyncFallibleButton<Label: View>: View {
@ObservedObject private var iO = Inject.observer
let action: () throws -> Void
let label: (_ execute: @escaping () -> Void) -> Label
var afterFinish: (_ success: Bool) -> Void = { success in } // runs after the checkmark/X has disappeared
var wrapInButton = true
var secondsToDisplayResultIcon: Double = 3
@State private var state: AsyncFallibleButtonState = .none
@State private var showErrorAlert = false
@State private var errorAlertMessage = ""
private var inside: some View {
HStack {
label(execute)
if state != .none {
if wrapInButton {
Spacer()
}
switch (state) {
case .loading:
ProgressView()
case .success:
Image(systemSymbol: .checkmark)
.foregroundColor(Color.green)
case .error:
Image(systemSymbol: .xmark)
.foregroundColor(Color.red)
default:
Image(systemSymbol: .questionmark)
.foregroundColor(Color.yellow)
}
}
}
}
private var wrapped: some View {
if wrapInButton {
return AnyView(SwiftUI.Button(action: {
execute()
}) {
inside
})
} else {
return AnyView(inside)
}
}
var body: some View {
wrapped
.alert(isPresented: $showErrorAlert) {
Alert(
title: Text(L10n.AsyncFallibleButton.error),
message: Text(errorAlertMessage)
)
}
.disabled(state != .none)
.animation(.default, value: state)
.enableInjection()
}
func execute() {
if state != .none { return }
state = .loading
DispatchQueue.global().async {
do {
try action()
DispatchQueue.main.async { state = .success }
} catch {
DispatchQueue.main.async {
state = .error
errorAlertMessage = error.localizedDescription
showErrorAlert = true
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDisplayResultIcon) {
let lastState = state
state = .none
afterFinish(lastState == .success)
}
}
}
}
struct AsyncFallibleButton_Previews: PreviewProvider {
static var previews: some View {
AsyncFallibleButton(action: {
print("Start")
for index in 0...5000000 {
_ = index + index
}
throw NSError(domain: "TestError", code: -1)
//print("Finish")
}) { execute in
Text("Hello World")
}
}
}

View File

@@ -0,0 +1,366 @@
//
// FileExplorer.swift
// SideStore
//
// Created by naturecodevoid on 2/16/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
import ZIPFoundation
import UniformTypeIdentifiers
// https://stackoverflow.com/a/72165424
func allUTITypes() -> [UTType] {
let types: [UTType] =
[.item,
.content,
.compositeContent,
.diskImage,
.data,
.directory,
.resolvable,
.symbolicLink,
.executable,
.mountPoint,
.aliasFile,
.urlBookmarkData,
.url,
.fileURL,
.text,
.plainText,
.utf8PlainText,
.utf16ExternalPlainText,
.utf16PlainText,
.delimitedText,
.commaSeparatedText,
.tabSeparatedText,
.utf8TabSeparatedText,
.rtf,
.html,
.xml,
.yaml,
.sourceCode,
.assemblyLanguageSource,
.cSource,
.objectiveCSource,
.swiftSource,
.cPlusPlusSource,
.objectiveCPlusPlusSource,
.cHeader,
.cPlusPlusHeader]
let types_1: [UTType] =
[.script,
.appleScript,
.osaScript,
.osaScriptBundle,
.javaScript,
.shellScript,
.perlScript,
.pythonScript,
.rubyScript,
.phpScript,
.json,
.propertyList,
.xmlPropertyList,
.binaryPropertyList,
.pdf,
.rtfd,
.flatRTFD,
.webArchive,
.image,
.jpeg,
.tiff,
.gif,
.png,
.icns,
.bmp,
.ico,
.rawImage,
.svg,
.livePhoto,
.heif,
.heic,
.webP,
.threeDContent,
.usd,
.usdz,
.realityFile,
.sceneKitScene,
.arReferenceObject,
.audiovisualContent]
let types_2: [UTType] =
[.movie,
.video,
.audio,
.quickTimeMovie,
UTType("com.apple.quicktime-image"),
.mpeg,
.mpeg2Video,
.mpeg2TransportStream,
.mp3,
.mpeg4Movie,
.mpeg4Audio,
.appleProtectedMPEG4Audio,
.appleProtectedMPEG4Video,
.avi,
.aiff,
.wav,
.midi,
.playlist,
.m3uPlaylist,
.folder,
.volume,
.package,
.bundle,
.pluginBundle,
.spotlightImporter,
.quickLookGenerator,
.xpcService,
.framework,
.application,
.applicationBundle,
.applicationExtension,
.unixExecutable,
.exe,
.systemPreferencesPane,
.archive,
.gzip,
.bz2,
.zip,
.appleArchive,
.spreadsheet,
.presentation,
.database,
.message,
.contact,
.vCard,
.toDoItem,
.calendarEvent,
.emailMessage,
.internetLocation,
.internetShortcut,
.font,
.bookmark,
.pkcs12,
.x509Certificate,
.epub,
.log]
.compactMap({ $0 })
return types + types_1 + types_2
}
extension Binding<URL?>: Equatable {
public static func == (lhs: Binding<URL?>, rhs: Binding<URL?>) -> Bool {
return lhs.wrappedValue == rhs.wrappedValue
}
}
private struct DirectoryEntry: Identifiable {
var id = UUID()
var path: URL
var parent: URL
var isFile = false
var childFiles = [URL]()
var childDirectories: [DirectoryEntry]?
var filesAndDirectories: [DirectoryEntry]? {
if childFiles.count <= 0 { return childDirectories }
var filesAndDirectories = childDirectories ?? []
for file in childFiles {
filesAndDirectories.insert(DirectoryEntry(path: file, parent: path, isFile: true), at: 0)
}
return filesAndDirectories.sorted(by: { $0.asString < $1.asString })
}
var asString: String {
let str = path.description.replacingOccurrences(of: parent.description, with: "").removingPercentEncoding!
if str.count <= 0 {
return "/"
}
return str
}
}
private enum FileExplorerAction {
case delete
case zip
case insert
}
private struct File: View {
@ObservedObject private var iO = Inject.observer
var item: DirectoryEntry
@Binding var explorerHidden: Bool
@State var quickLookURL: URL?
@State var fileExplorerAction: FileExplorerAction?
@State var hidden = false
@State var isShowingFilePicker = false
@State var selectedFile: URL?
var body: some View {
AsyncFallibleButton(action: {
switch (fileExplorerAction) {
case .delete:
print("deleting \(item.path.description)")
try FileManager.default.removeItem(at: item.path)
case .zip:
print("zipping \(item.path.description)")
let dest = FileManager.default.documentsDirectory.appendingPathComponent(item.path.pathComponents.last! + ".zip")
do {
try FileManager.default.removeItem(at: dest)
} catch {}
try FileManager.default.zipItem(at: item.path, to: dest)
case .insert:
print("inserting \(selectedFile!.description) to \(item.path.description)")
try FileManager.default.copyItem(at: selectedFile!, to: item.path.appendingPathComponent(selectedFile!.pathComponents.last!))
explorerHidden = true
explorerHidden = false
default:
print("unknown action for \(item.path.description): \(String(describing: fileExplorerAction))")
}
}, label: { execute in
HStack {
Text(item.asString)
if item.isFile {
Text(getFileSize(file: item.path)).foregroundColor(.secondary)
}
Spacer()
Menu {
if item.isFile {
SwiftUI.Button(action: { quickLookURL = item.path }) {
Label("View/Share", systemSymbol: .eye)
}
} else {
SwiftUI.Button(action: {
fileExplorerAction = .zip
execute()
}) {
Label("Save to ZIP file", systemSymbol: .squareAndArrowDown)
}
SwiftUI.Button {
isShowingFilePicker = true
} label: {
Label("Insert file", systemSymbol: .plus)
}
}
if item.asString != "/" {
SwiftUI.Button(action: {
fileExplorerAction = .delete
execute()
}) {
Label("Delete", systemSymbol: .trash)
}
}
} label: {
Image(systemSymbol: .ellipsis)
.frame(width: 20, height: 20) // Make it easier to tap
}
}
.onChange(of: $selectedFile) { file in
guard file.wrappedValue != nil else { return }
fileExplorerAction = .insert
execute()
}
}, afterFinish: { success in
switch (fileExplorerAction) {
case .delete:
if success { hidden = true }
case .zip:
UIApplication.shared.open(URL(string: "shareddocuments://" + FileManager.default.documentsDirectory.description.replacingOccurrences(of: "file://", with: ""))!, options: [:], completionHandler: nil)
default: break
}
}, wrapInButton: false)
.quickLookPreview($quickLookURL)
.sheet(isPresented: $isShowingFilePicker) {
DocumentPicker(selectedUrl: $selectedFile, supportedTypes: allUTITypes().map({ $0.identifier }))
.ignoresSafeArea()
}
.isHidden($hidden)
.enableInjection()
}
func getFileSize(file: URL) -> String {
guard let attributes = try? FileManager.default.attributesOfItem(atPath: file.description.replacingOccurrences(of: "file://", with: "")) else { return "Unknown file size" }
var bytes = attributes[FileAttributeKey.size] as! Double
// https://stackoverflow.com/a/14919494 (ported to swift)
let thresh = 1024.0;
if (bytes < thresh) {
return String(describing: bytes) + " B";
}
let units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
var u = -1;
while (bytes >= thresh && u < units.count - 1) {
bytes /= thresh;
u += 1;
}
return String(format: "%.2f", bytes) + " " + units[u];
}
}
struct FileExplorer: View {
@ObservedObject private var iO = Inject.observer
var url: URL?
@State var hidden = false
var body: some View {
List([iterateOverDirectory(directory: url!, parent: url!)], children: \.filesAndDirectories) { item in
File(item: item, explorerHidden: $hidden)
}
.toolbar {
ToolbarItem {
SwiftUI.Button {
hidden = true
hidden = false
} label: {
Image(systemSymbol: .arrowClockwise)
}
}
}
.isHidden($hidden)
.enableInjection()
}
private func iterateOverDirectory(directory: URL, parent: URL) -> DirectoryEntry {
var directoryEntry = DirectoryEntry(path: directory, parent: parent)
if let contents = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) {
for entry in contents {
if entry.hasDirectoryPath {
if directoryEntry.childDirectories == nil { directoryEntry.childDirectories = [] }
directoryEntry.childDirectories!.append(iterateOverDirectory(directory: entry, parent: directory))
} else {
directoryEntry.childFiles.append(entry)
}
}
}
return directoryEntry
}
}
struct FileExplorer_Previews: PreviewProvider {
static var previews: some View {
FileExplorer(url: FileManager.default.altstoreSharedDirectory)
}
}

View File

@@ -9,13 +9,13 @@
import SwiftUI
import SFSafeSymbols
struct Icon: Identifiable {
private struct Icon: Identifiable {
var id: String { assetName }
var displayName: String
let assetName: String
}
struct SpecialIcon {
private struct SpecialIcon {
let assetName: String
let suffix: String?
let forceIndex: Int?

View File

@@ -0,0 +1,147 @@
//
// DevModeView.swift
// SideStore
//
// Created by naturecodevoid on 2/16/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
import LocalConsole
struct DevModePrompt: View {
@Binding var isShowingDevModePrompt: Bool
@Binding var isShowingDevModeMenu: Bool
@State var countdown = 0
var button: some View {
SwiftUI.Button(action: {
UserDefaults.standard.isDevModeEnabled = true
isShowingDevModePrompt = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
isShowingDevModeMenu = true
}
}) {
Text(countdown <= 0 ? L10n.Action.enable + " " + L10n.DevModeView.title : L10n.DevModeView.read + " (\(countdown))")
.foregroundColor(.red)
}
.disabled(countdown > 0)
}
var text: some View {
if #available(iOS 15.0, *) {
do {
return Text(try AttributedString(markdown: L10n.DevModeView.prompt, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)))
} catch {
return Text(L10n.DevModeView.prompt)
}
} else {
return Text(L10n.DevModeView.prompt)
}
}
var body: some View {
NavigationView {
ScrollView {
VStack {
text
.foregroundColor(.primary)
.padding(.bottom)
if #available(iOS 15.0, *) {
button.buttonStyle(.bordered)
} else {
button
}
}
.padding(.horizontal)
}
.frame(maxWidth: .infinity)
.navigationTitle(L10n.DevModeView.title)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
SwiftUI.Button(action: { isShowingDevModePrompt = false }) {
Text(L10n.Action.close)
}
}
}
.onAppear {
countdown = 20
tickCountdown()
}
}
}
func tickCountdown() {
if countdown <= 0 { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
countdown -= 1
tickCountdown()
}
}
}
struct DevModeMenu: View {
@ObservedObject private var iO = Inject.observer
@AppStorage("isConsoleEnabled")
var isConsoleEnabled: Bool = false
var body: some View {
List {
Section {
Toggle(L10n.DevModeView.console, isOn: self.$isConsoleEnabled)
.onChange(of: self.isConsoleEnabled) { value in
LCManager.shared.isVisible = value
}
NavigationLink(L10n.DevModeView.dataExplorer) {
FileExplorer(url: FileManager.default.altstoreSharedDirectory)
.navigationTitle(L10n.DevModeView.dataExplorer)
}.foregroundColor(.red)
NavigationLink(L10n.DevModeView.tmpExplorer) {
FileExplorer(url: FileManager.default.temporaryDirectory)
.navigationTitle(L10n.DevModeView.tmpExplorer)
}.foregroundColor(.red)
Toggle(L10n.DevModeView.skipResign, isOn: ResignAppOperation.skipResignBinding)
.foregroundColor(.red)
} footer: {
Text(L10n.DevModeView.skipResignInfo)
}
Section {
NavigationLink(L10n.DevModeView.Minimuxer.stagingExplorer + " (Coming soon, needs minimuxer additions)") {
FileExplorer(url: FileManager.default.altstoreSharedDirectory)
.navigationTitle(L10n.DevModeView.Minimuxer.stagingExplorer)
}.foregroundColor(.red).disabled(true)
NavigationLink(L10n.DevModeView.Minimuxer.viewProfiles + " (Coming soon, needs minimuxer additions)") {
}.disabled(true)
SwiftUI.Button(L10n.DevModeView.Minimuxer.dumpProfiles + " (Coming soon, needs minimuxer additions)", action: {
// TODO: dump profiles to Documents/ProfileDump/[current time]
}).disabled(true)
} header: {
Text(L10n.DevModeView.minimuxer)
}
}
.navigationTitle(L10n.DevModeView.title)
.enableInjection()
}
}
struct DevModeView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
List {
NavigationLink("DevModeMenu") {
DevModeMenu()
}
}
}
}
}

View File

@@ -26,10 +26,15 @@ struct SettingsView: View {
@AppStorage("isBackgroundRefreshEnabled")
var isBackgroundRefreshEnabled: Bool = true
@AppStorage("isDevModeEnabled")
var isDevModeEnabled: Bool = false
@State var isShowingConnectAppleIDView = false
@State var isShowingAddShortcutView = false
@State var isShowingFeedbackMailView = false
@State var isShowingResetPairingFileConfirmation = false
@State var isShowingDevModePrompt = false
@State var isShowingDevModeMenu = false
@State var externalURLToShow: URL?
@@ -163,10 +168,6 @@ struct SettingsView: View {
RefreshAttemptsView()
}
SwiftUI.Button("Toggle Console") {
LCManager.shared.isVisible.toggle()
}
if MailComposeView.canSendMail {
SwiftUI.Button("Send Feedback") {
self.isShowingFeedbackMailView = true
@@ -199,6 +200,21 @@ struct SettingsView: View {
.cancel()
])
}
if isDevModeEnabled {
NavigationLink(L10n.DevModeView.title, isActive: self.$isShowingDevModeMenu) {
DevModeMenu()
}
.foregroundColor(.red)
} else {
SwiftUI.Button(L10n.DevModeView.title) {
self.isShowingDevModePrompt = true
}
.foregroundColor(.red)
.sheet(isPresented: self.$isShowingDevModePrompt) {
DevModePrompt(isShowingDevModePrompt: self.$isShowingDevModePrompt, isShowingDevModeMenu: self.$isShowingDevModeMenu)
}
}
} header: {
Text(L10n.SettingsView.debug)
}

View File

@@ -26,6 +26,9 @@ public extension UserDefaults
@NSManaged var customAnisetteURL: String?
@NSManaged var preferredServerID: String?
@NSManaged var isDevModeEnabled: Bool
@NSManaged var isConsoleEnabled: Bool
@NSManaged var isBackgroundRefreshEnabled: Bool
@NSManaged var isDebugModeEnabled: Bool
@NSManaged var presentedLaunchReminderNotification: Bool
@@ -70,6 +73,8 @@ public extension UserDefaults
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
let defaults = [
#keyPath(UserDefaults.isDevModeEnabled): false,
#keyPath(UserDefaults.isConsoleEnabled): false,
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,