Skip to content
This repository has been archived by the owner on Feb 19, 2025. It is now read-only.

Commit

Permalink
Merge pull request #30 from leminlimez/trollstore-support
Browse files Browse the repository at this point in the history
Trollstore/18.1b5 support
  • Loading branch information
leminlimez authored Oct 17, 2024
2 parents b1ed45f + e8aa14f commit 34b1cde
Show file tree
Hide file tree
Showing 8 changed files with 281 additions and 34 deletions.
49 changes: 49 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build

on:
push:
workflow_dispatch:

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

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

- name: Checkout theos/theos
uses: actions/checkout@main
with:
repository: theos/theos
ref: master
submodules: recursive
path: theos

- name: Checkout theos/sdks
uses: actions/checkout@main
with:
repository: theos/sdks
ref: master
nugget-checkout: iPhoneOS16.5.sdk
path: theos/sdks

- name: Ensure main utils are installed
uses: dhinakg/procursus-action@main
with:
packages: coreutils make xz ldid

- name: Build
run: |
export THEOS=theos
git submodule update --init
bash get_libraries.sh
bash ipabuild.sh
- name: Upload artifact
uses: actions/upload-artifact@main
with:
name: artifact
path: packages/*.ipa
126 changes: 117 additions & 9 deletions Nugget/Controllers/ApplyHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ class ApplyHandler: ObservableObject {
.MobileGestalt, .FeatureFlags, .SpringBoard, .Internal
]

@Published var trollstore: Bool = false

init() {
do {
try FileManager.default.contentsOfDirectory(at: URL(fileURLWithPath: "/var/mobile/Library/Caches"), includingPropertiesForKeys: nil)
trollstore = true
} catch {
trollstore = false
}
}

// MARK: Modifying Enabled Tweaks
func setTweakEnabled(_ tweak: TweakPage, isEnabled: Bool) {
if isEnabled {
Expand Down Expand Up @@ -78,29 +89,97 @@ class ApplyHandler: ObservableObject {
}
case .SkipSetup:
// Apply the skip setup file
var skipSetupData: Data = Data()
var cloudConfigData: Data = Data()
var purpleBuddyData: Data = Data()
if !resetting {
let plist: [String: Any] = [
let cloudConfigPlist: [String: Any] = [
"SkipSetup": ["WiFi", "Location", "Restore", "SIMSetup", "Android", "AppleID", "IntendedUser", "TOS", "Siri", "ScreenTime", "Diagnostics", "SoftwareUpdate", "Passcode", "Biometric", "Payment", "Zoom", "DisplayTone", "MessagingActivationUsingPhoneNumber", "HomeButtonSensitivity", "CloudStorage", "ScreenSaver", "TapToSetup", "Keyboard", "PreferredLanguage", "SpokenLanguage", "WatchMigration", "OnBoarding", "TVProviderSignIn", "TVHomeScreenSync", "Privacy", "TVRoom", "iMessageAndFaceTime", "AppStore", "Safety", "Multitasking", "ActionButton", "TermsOfAddress", "AccessibilityAppearance", "Welcome", "Appearance", "RestoreCompleted", "UpdateCompleted"],
"CloudConfigurationUIComplete": true
"CloudConfigurationUIComplete": true,
"IsSupervised": false
]
skipSetupData = try PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0)
cloudConfigData = try PropertyListSerialization.data(fromPropertyList: cloudConfigPlist, format: .xml, options: 0)
let purpleBuddyPlist: [String: Any] = [
"SetupDone": true,
"SetupFinishedAllSteps": true,
"UserChoseLanguage": true
]
purpleBuddyData = try PropertyListSerialization.data(fromPropertyList: purpleBuddyPlist, format: .xml, options: 0)
}
if resetting || !self.isExploitOnly() {
files.append(FileToRestore(contents: skipSetupData, path: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/SharedDeviceConfiguration.plist"))
files.append(FileToRestore(contents: cloudConfigData, path: "/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/SharedDeviceConfiguration.plist"))
if !self.isExploitOnly() {
files.append(FileToRestore(contents: purpleBuddyData, path: "ManagedPreferencesDomain/mobile/com.apple.purplebuddy.plist"))
}
}
}
}

func convertToDomain(path: String) -> String? {
// if it doesn't start with a / then it is already a domain
if !path.starts(with: "/") {
return path
}
let mappings: [String: String] = [
"/var/Managed Preferences": "ManagedPreferencesDomain",
"/var/root": "RootDomain",
"/var/preferences": "SystemPreferencesDomain",
"/var/MobileDevice": "MobileDeviceDomain",
"/var/mobile": "HomeDomain",
"/var/db": "DatabaseDomain",
"/var/containers/Shared/SystemGroup": "SysSharedContainerDomain-.",
"/var/containers/Data/SystemGroup": "SysContainerDomain-."
]
for (rootPath, domain) in mappings {
if path.starts(with: rootPath) {
return path.replacingOccurrences(of: rootPath, with: domain)
}
}
// no changes, return original path
return nil
}

func isExploitPatched() -> Bool {
if #available(iOS 18.2, *) {
return true
}
if #available(iOS 18.1, *) {
// get the build number
var osVersionString = [CChar](repeating: 0, count: 16)
var osVersionStringLen = size_t(osVersionString.count - 1)

let result = sysctlbyname("kern.osversion", &osVersionString, &osVersionStringLen, nil, 0)

if result == 0 {
// Convert C array to String
if let build = String(validatingUTF8: osVersionString) {
// check build number for iOS 18.1 beta 1-4, return false if user is on that
if build == "22B5007p" || build == "22B5023e" || build == "22B5034e" || build == "22B5045g" {
return false
}
} else {
print("Failed to convert build number to String")
}
} else {
print("sysctlbyname failed with error: \(String(cString: strerror(errno)))")
}
return true
}
return false
}

func isExploitOnly() -> Bool {
// Checks for the newer versions with the exploit patched
if self.isExploitPatched() {
return false
}
if self.enabledTweaks.contains(.StatusBar) {
return false
}
return true
}

// MARK: Actual Applying/Resetting Functions
func apply(udid: String, skipSetup: Bool) -> Bool {
func apply(udid: String, skipSetup: Bool, trollstore: Bool) -> Bool {
var filesToRestore: [FileToRestore] = []
do {
print("Tweak pages being applied: \(self.enabledTweaks)")
Expand All @@ -111,7 +190,23 @@ class ApplyHandler: ObservableObject {
try getTweakPageData(.SkipSetup, resetting: false, files: &filesToRestore)
}
if !filesToRestore.isEmpty {
RestoreManager.shared.restoreFiles(filesToRestore, udid: udid)
if trollstore {
RestoreManager.shared.tsRestoreFiles(filesToRestore)
} else if self.isExploitPatched() {
// convert to domains
var newFilesToRestore: [FileToRestore] = []
for file in filesToRestore {
if let newPath = self.convertToDomain(path: file.path) {
newFilesToRestore.append(FileToRestore(contents: file.contents, path: newPath, owner: file.owner, group: file.group, usesInodes: file.usesInodes))
print(newPath)
}
}
print()
print()
RestoreManager.shared.restoreFiles(newFilesToRestore, udid: udid)
} else {
RestoreManager.shared.restoreFiles(filesToRestore, udid: udid)
}
return true
} else {
print("No files to restore!")
Expand All @@ -123,15 +218,28 @@ class ApplyHandler: ObservableObject {
}
}

func reset(udid: String) -> Bool {
func reset(udid: String, trollstore: Bool) -> Bool {
var filesToRestore: [FileToRestore] = []
do {
print("Tweak pages being reset: \(self.removingTweaks)")
for tweak in self.removingTweaks {
try self.getTweakPageData(tweak, resetting: true, files: &filesToRestore)
}
if !filesToRestore.isEmpty {
RestoreManager.shared.restoreFiles(filesToRestore, udid: udid)
if trollstore {
RestoreManager.shared.tsRestoreFiles(filesToRestore)
} else if self.isExploitPatched() {
// convert to domains
var newFilesToRestore: [FileToRestore] = []
for file in filesToRestore {
if let newPath = self.convertToDomain(path: file.path) {
newFilesToRestore.append(FileToRestore(contents: file.contents, path: newPath, owner: file.owner, group: file.group, usesInodes: file.usesInodes))
}
}
RestoreManager.shared.restoreFiles(newFilesToRestore, udid: udid)
} else {
RestoreManager.shared.restoreFiles(filesToRestore, udid: udid)
}
return true
} else {
print("No files to restore!")
Expand Down
27 changes: 24 additions & 3 deletions Nugget/Sparserestore/Restore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ class RestoreManager {
// add the domain if needed
if startIdx == 0 {
list.append(Directory(path: "", domain: domain))
print(domain)
startIdx = 1
}
last_domain = domain
Expand All @@ -70,7 +69,6 @@ class RestoreManager {
}
full_path += path_items[i]
if i >= startIdx {
print(domain, full_path)
if i < path_items.count - 1 {
last_path = full_path
// it is a directory
Expand All @@ -81,7 +79,27 @@ class RestoreManager {
}
}
}
print()
}

func tsRestoreFiles(_ files: [FileToRestore]) {
for file in files {
do {
// if it is a domain, convert it to a path
var filePath = file.path
if file.path.starts(with: "HomeDomain/") {
filePath = file.path.replacingOccurrences(of: "HomeDomain/", with: "/var/mobile/")
} // TODO: Add more domains

if file.contents == Data() {
// if empty data, remove the file
try FileManager.default.removeItem(at: URL(fileURLWithPath: filePath))
} else {
try file.contents.write(to: URL(fileURLWithPath: filePath))
}
} catch {
print(error.localizedDescription)
}
}
}

func restoreFiles(_ files: [FileToRestore], udid: String) {
Expand Down Expand Up @@ -110,7 +128,9 @@ class RestoreManager {
var last_path: String = ""
var last_domain: String = ""
var exploit_only = true
print("Files: [")
for (_, file) in sortedFiles.enumerated() {
print(file.path + ",")
// for non exploit domains, the path will not start with /
if file.path.starts(with: "/") {
// file utilizes exploit
Expand All @@ -126,6 +146,7 @@ class RestoreManager {
if exploit_only {
backupFiles.append(ConcreteFile(path: "", domain: "SysContainerDomain-../../../../../../../../crash_on_purpose", contents: Data(), owner: 501, group: 501))
}
print("]")

// create backup
let mbdb = Backup(files: backupFiles)
Expand Down
38 changes: 20 additions & 18 deletions Nugget/Views/NavigatorViews/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,26 @@ struct HomeView: View {
.buttonStyle(TintedButton(material: .systemMaterial, fullwidth: false))
}
// select pairing file button
if pairingFile == nil {
HStack {
Button("Select Pairing File") {
showPairingFileImporter.toggle()
if !ApplyHandler.shared.trollstore {
if pairingFile == nil {
HStack {
Button("Select Pairing File") {
showPairingFileImporter.toggle()
}
.buttonStyle(TintedButton(color: .green, fullwidth: true))
Button {
UIApplication.shared.helpAlert(title: NSLocalizedString("Info", comment: "info header"), body: NSLocalizedString("Select a pairing file in order to restore the device. One can be gotten from apps like AltStore or SideStore. Tap \"Help\" for more info.", comment: "pairing file selector info"), link: "https://docs.sidestore.io/docs/getting-started/pairing-file")
} label: {
Image(systemName: "info")
}
.buttonStyle(TintedButton(material: .systemMaterial, fullwidth: false))
}
.buttonStyle(TintedButton(color: .green, fullwidth: true))
Button {
UIApplication.shared.helpAlert(title: NSLocalizedString("Info", comment: "info header"), body: NSLocalizedString("Select a pairing file in order to restore the device. One can be gotten from apps like AltStore or SideStore. Tap \"Help\" for more info.", comment: "pairing file selector info"), link: "https://docs.sidestore.io/docs/getting-started/pairing-file")
} label: {
Image(systemName: "info")
} else {
Button("Reset pairing file") {
pairingFile = nil
}
.buttonStyle(TintedButton(material: .systemMaterial, fullwidth: false))
}
} else {
Button("Reset pairing file") {
pairingFile = nil
.buttonStyle(TintedButton(color: .green, fullwidth: true))
}
.buttonStyle(TintedButton(color: .green, fullwidth: true))
}
}
.listRowInsets(EdgeInsets())
Expand Down Expand Up @@ -194,13 +196,13 @@ struct HomeView: View {
}

func applyChanges(reverting: Bool) {
if ready() {
if ApplyHandler.shared.trollstore || ready() {
if !reverting && ApplyHandler.shared.allEnabledTweaks().isEmpty {
// if there are no enabled tweaks then tell the user
UIApplication.shared.alert(body: "You do not have any tweaks enabled! Go to the tools page to select some.")
} else if ApplyHandler.shared.isExploitOnly() || skipSetup {
} else if ApplyHandler.shared.isExploitOnly() {
path.append(reverting ? "RevertChanges" : "ApplyChanges")
} else {
} else if !ApplyHandler.shared.trollstore {
// if applying non-exploit files, warn about setup
UIApplication.shared.confirmAlert(title: "Warning!", body: "You are applying non-exploit related files. This will make the setup screen appear. Click Cancel if you do not wish to proceed.\n\nWhen setting up, you MUST click \"Do not transfer apps & data\".\n\nIf you see a screen that says \"iPhone Partially Set Up\", DO NOT tap the big blue button. You must click \"Continue with Partial Setup\".", onOK: {
path.append(reverting ? "RevertChanges" : "ApplyChanges")
Expand Down
20 changes: 18 additions & 2 deletions Nugget/Views/NavigatorViews/LogView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ struct LogView: View {

DispatchQueue.global(qos: .background).async {
print("APPLYING")
if ApplyHandler.shared.trollstore {
// apply with trollstore
var succeeded: Bool = false
if resetting {
succeeded = ApplyHandler.shared.reset(udid: "", trollstore: true)
} else {
succeeded = ApplyHandler.shared.apply(udid: "", skipSetup: false, trollstore: true)
}
if succeeded {
// respring device
UIApplication.shared.alert(title: "Success!", body: "Please respring your device to apply changes.")
} else {
UIApplication.shared.alert(body: "Please read logs for full error info")
}
return
}
// get the device and create a directory for the backup files
let deviceList = MobileDevice.deviceList()
var udid: String
Expand All @@ -52,9 +68,9 @@ struct LogView: View {
udid = deviceList.first!
var succeeded: Bool = false
if resetting {
succeeded = ApplyHandler.shared.reset(udid: udid)
succeeded = ApplyHandler.shared.reset(udid: udid, trollstore: false)
} else {
succeeded = ApplyHandler.shared.apply(udid: udid, skipSetup: skipSetup)
succeeded = ApplyHandler.shared.apply(udid: udid, skipSetup: skipSetup, trollstore: false)
}
if succeeded && (log.contains("Restore Successful") || log.contains("crash_on_purpose")) {
if autoReboot {
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
![Artboard](https://github.com/leminlimez/Nugget-Mobile/blob/1881fdc2b721fd2675a2909e7fbc24769d11bb53/readme-images/icon.png)

# Nugget (mobile)
Unlock your device's full potential! Should work on all versions iOS 16.0 - 18.1 beta 4.
Unlock your device's full potential! Should work on all versions iOS 16.0 - 18.1 beta 4 with partial support for iOS 18.1 beta 5-6.

Sparserestore was patched in iOS 18.1 beta 5. Please do not make issues about this, it will not be fixed.
This will not work on iOS 18.1 beta 7 or newer. Please do not make issues about this, it will not be fixed. You will have to use the [pc version of Nugget](https://github.com/leminlimez/Nugget) unless a fix comes in the future.

A `.mobiledevicepairing` file and wireguard are required in order to use this. Read the [sections](#getting-your-mobiledevicepairing-file) below to see how to get those.

Expand Down
Loading

0 comments on commit 34b1cde

Please sign in to comment.