Merge pull request #369 from leminlimez/posterboard

Posterboard
This commit is contained in:
leminlimez
2025-03-24 11:35:12 -04:00
committed by GitHub
26 changed files with 228457 additions and 711 deletions

View File

@@ -60,6 +60,9 @@ Note: I am not responsible if your device bootloops. Please back up your data be
- PassBook
- Spotlight
- Voice Control
- PosterBoard: Animated wallpapers and descriptors.
- Community wallpapers can be found [here](https://cowabun.ga/wallpapers)
- See documentation on the structure of tendies files in `documentation.md`
- Risky (Hidden) Options:
- Disable thermalmonitord
- OTA Killer
@@ -118,6 +121,8 @@ If you would like to read more about the inner workings of the exploit and iOS r
## Credits
- [JJTech](https://github.com/JJTech0130) for Sparserestore/[TrollRestore](https://github.com/JJTech0130/TrollRestore)
- [PosterRestore](https://discord.gg/gWtzTVhMvh) for their help with PosterBoard
- Special thanks to dootskyre, [Middo](https://twitter.com/MWRevamped), [dulark](https://github.com/dularkian), forcequitOS, and pingubow for their work on this. It would not have been possible without them!
- [disfordottie](https://x.com/disfordottie) for some global flag features
- [Mikasa-san](https://github.com/Mikasa-san) for [Quiet Daemon](https://github.com/Mikasa-san/QuietDaemon)
- [sneakyf1shy](https://github.com/f1shy-dev) for [AI Eligibility](https://gist.github.com/f1shy-dev/23b4a78dc283edd30ae2b2e6429129b5) (iOS 18.1 beta 4 and below)

View File

@@ -23,19 +23,35 @@ class BackupFile:
@dataclass
class ConcreteFile(BackupFile):
contents: bytes
src_path: Optional[str] = None
owner: int = 0
group: int = 0
inode: Optional[int] = None
mode: _FileMode = DEFAULT
hash: bytes = None
size: int = None
def read_contents(self) -> bytes:
contents = self.contents
if self.contents == None:
with open(self.src_path, "rb") as in_file:
contents = in_file.read()
# prepopulate hash and size
self.hash = sha1(contents).digest()
self.size = len(contents)
return contents
def to_record(self) -> mbdb.MbdbRecord:
if self.inode is None:
self.inode = int.from_bytes(randbytes(8), "big")
if self.hash == None or self.size == None:
self.read_contents()
return mbdb.MbdbRecord(
domain=self.domain,
filename=self.path,
link="",
hash=sha1(self.contents).digest(),
hash=self.hash,
key=b"",
mode=self.mode | _FileMode.S_IFREG,
#unknown2=0,
@@ -46,7 +62,7 @@ class ConcreteFile(BackupFile):
mtime=int(datetime.now().timestamp()),
atime=int(datetime.now().timestamp()),
ctime=int(datetime.now().timestamp()),
size=len(self.contents),
size=self.size,
flags=4,
properties=[]
)
@@ -108,17 +124,25 @@ class SymbolicLink(BackupFile):
flags=4,
properties=[]
)
@dataclass
class AppBundle:
identifier: str
path: str
container_content_class: str
version: str = 804
@dataclass
class Backup:
files: list[BackupFile]
apps: list[AppBundle]
def write_to_directory(self, directory: Path):
for file in self.files:
if isinstance(file, ConcreteFile):
#print("Writing", file.path, "to", directory / sha1((file.domain + "-" + file.path).encode()).digest().hex())
with open(directory / sha1((file.domain + "-" + file.path).encode()).digest().hex(), "wb") as f:
f.write(file.contents)
f.write(file.read_contents())
with open(directory / "Manifest.mbdb", "wb") as f:
f.write(self.generate_manifest_db().to_bytes())
@@ -150,7 +174,7 @@ class Backup:
})
def generate_manifest(self) -> bytes: # Manifest.plist
return plistlib.dumps({
plist = {
"BackupKeyBag": b64decode("""
VkVSUwAAAAQAAAAFVFlQRQAAAAQAAAABVVVJRAAAABDud41d1b9NBICR1BH9JfVtSE1D
SwAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV1JBUAAA
@@ -182,4 +206,16 @@ class Backup:
"Lockdown": {},
"SystemDomainsVersion": "20.0",
"Version": "9.1"
})
}
# add the apps
if len(self.apps) > 0:
plist["Applications"] = {}
for app in self.apps:
appInfo = {
"CFBundleIdentifier": app.identifier,
"CFBundleVersion": app.version,
"ContainerContentClass": app.container_content_class,
"Path": app.path
}
plist["Applications"][app.identifier] = appInfo
return plistlib.dumps(plist)

View File

@@ -1,10 +1,12 @@
from . import backup, perform_restore
from pymobiledevice3.lockdown import LockdownClient
from pymobiledevice3.services.installation_proxy import InstallationProxyService
import os
class FileToRestore:
def __init__(self, contents: str, restore_path: str, domain: str = None, owner: int = 501, group: int = 501):
def __init__(self, contents: str, restore_path: str, contents_path: str = None, domain: str = None, owner: int = 501, group: int = 501):
self.contents = contents
self.contents_path = contents_path
self.restore_path = restore_path
self.domain = domain
self.owner = owner
@@ -74,7 +76,8 @@ def concat_regular_file(file: FileToRestore, files_list: list[FileToRestore], la
file.domain,
owner=file.owner,
group=file.group,
contents=file.contents
contents=file.contents,
src_path=file.contents_path
))
return new_last_domain, full_path
@@ -83,6 +86,9 @@ def restore_files(files: list, reboot: bool = False, lockdown_client: LockdownCl
# create the files to be backed up
files_list = [
]
apps_list = []
active_bundle_ids = []
apps = None
sorted_files = sorted(files, key=lambda x: x.restore_path, reverse=False)
# add the file paths
last_domain = ""
@@ -94,13 +100,27 @@ def restore_files(files: list, reboot: bool = False, lockdown_client: LockdownCl
else:
last_domain, last_path = concat_regular_file(file, files_list, last_domain, last_path)
exploit_only = False
# add the app bundle to the list
if last_domain.startswith("AppDomain"):
bundle_id = last_domain.removeprefix("AppDomain-")
if not bundle_id in active_bundle_ids:
if apps == None:
apps = InstallationProxyService(lockdown=lockdown_client).get_apps(application_type="Any", calculate_sizes=False)
app_info = apps[bundle_id]
active_bundle_ids.append(bundle_id)
apps_list.append(backup.AppBundle(
identifier=bundle_id,
path=app_info["Container"],
version=app_info["CFBundleVersion"],
container_content_class="Data/Application"
))
# crash the restore to skip the setup (only works for exploit files)
if exploit_only:
files_list.append(backup.ConcreteFile("", "SysContainerDomain-../../../../../../../.." + "/crash_on_purpose", contents=b""))
# create the backup
back = backup.Backup(files=files_list)
back = backup.Backup(files=files_list, apps=apps_list)
perform_restore(backup=back, reboot=reboot, lockdown_client=lockdown_client)

View File

@@ -0,0 +1,16 @@
import plistlib
def recursive_set(plist: dict, key: str, value: any):
new_plist: dict = plist
for k, v in plist.items():
if k == key:
new_plist[k] = value
elif isinstance(v, dict):
new_plist[k] = recursive_set(v, key, value)
return new_plist
def set_plist_value(file: str, key: str, value: any):
with open(file, 'rb') as in_fp:
plist = plistlib.load(in_fp)
new_plist = recursive_set(plist, key, value)
return plistlib.dumps(new_plist)

View File

@@ -2,8 +2,9 @@ from enum import Enum
from pymobiledevice3.lockdown import LockdownClient
class Device:
def __init__(self, uuid: int, name: str, version: str, build: str, model: str, hardware: str, cpu: str, locale: str, ld: LockdownClient):
def __init__(self, uuid: int, usb: bool, name: str, version: str, build: str, model: str, hardware: str, cpu: str, locale: str, ld: LockdownClient):
self.uuid = uuid
self.connected_via_usb = usb
self.name = name
self.version = version
self.build = build

View File

@@ -1,6 +1,6 @@
import traceback
import plistlib
from pathlib import Path
from tempfile import TemporaryDirectory
from PySide6.QtWidgets import QMessageBox
from PySide6.QtCore import QSettings
@@ -14,6 +14,7 @@ from devicemanagement.data_singleton import DataSingleton
from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, AdvancedPlistTweak, RdarFixTweak, NullifyFileTweak
from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks
from tweaks.posterboard_tweak import PosterboardTweak
from tweaks.basic_plist_locations import FileLocationsList, RiskyFileLocationsList
from Sparserestore.restore import restore_files, FileToRestore
@@ -108,6 +109,7 @@ class DeviceManager:
pass
dev = Device(
uuid=device.serial,
usb=device.is_usb,
name=vals['DeviceName'],
version=vals['ProductVersion'],
build=vals['BuildVersion'],
@@ -285,137 +287,145 @@ class DeviceManager:
## APPLYING OR REMOVING TWEAKS AND RESTORING
def apply_changes(self, resetting: bool = False, update_label=lambda x: None):
# set the tweaks and apply
# first open the file in read mode
update_label("Applying changes to files...")
gestalt_plist = None
if self.data_singleton.gestalt_path != None:
with open(self.data_singleton.gestalt_path, 'rb') as in_fp:
gestalt_plist = plistlib.load(in_fp)
# create the other plists
flag_plist: dict = {}
eligibility_files = None
ai_file = None
basic_plists: dict = {}
basic_plists_ownership: dict = {}
files_data: dict = {}
uses_domains: bool = False
try:
# set the tweaks and apply
# first open the file in read mode
update_label("Applying changes to files...")
gestalt_plist = None
if self.data_singleton.gestalt_path != None:
with open(self.data_singleton.gestalt_path, 'rb') as in_fp:
gestalt_plist = plistlib.load(in_fp)
# create the other plists
flag_plist: dict = {}
eligibility_files = None
ai_file = None
basic_plists: dict = {}
basic_plists_ownership: dict = {}
files_data: dict = {}
uses_domains: bool = False
# create the restore file list
files_to_restore: dict[FileToRestore] = [
]
tmp_pb_dir = None # temporary directory for unzipping pb files
# set the plist keys
if not resetting:
for tweak_name in tweaks:
tweak = tweaks[tweak_name]
if isinstance(tweak, FeatureFlagTweak):
flag_plist = tweak.apply_tweak(flag_plist)
elif isinstance(tweak, EligibilityTweak):
eligibility_files = tweak.apply_tweak()
elif isinstance(tweak, AITweak):
ai_file = tweak.apply_tweak()
elif isinstance(tweak, BasicPlistTweak) or isinstance(tweak, RdarFixTweak) or isinstance(tweak, AdvancedPlistTweak):
basic_plists = tweak.apply_tweak(basic_plists, self.allow_risky_tweaks)
basic_plists_ownership[tweak.file_location] = tweak.owner
if tweak.enabled and tweak.owner == 0:
uses_domains = True
elif isinstance(tweak, NullifyFileTweak):
tweak.apply_tweak(files_data)
if tweak.enabled and tweak.file_location.value.startswith("/var/mobile/"):
uses_domains = True
else:
if gestalt_plist != None:
gestalt_plist = tweak.apply_tweak(gestalt_plist)
elif tweak.enabled:
# no mobilegestalt file provided but applying mga tweaks, give warning
show_error_msg("No mobilegestalt file provided! Please select your file to apply mobilegestalt tweaks.")
update_label("Failed.")
return
# set the custom gestalt keys
if gestalt_plist != None:
gestalt_plist = CustomGestaltTweaks.apply_tweaks(gestalt_plist)
gestalt_data = None
if resetting:
gestalt_data = b""
elif gestalt_plist != None:
gestalt_data = plistlib.dumps(gestalt_plist)
# Generate backup
update_label("Generating backup...")
# create the restore file list
files_to_restore: dict[FileToRestore] = [
]
self.concat_file(
contents=plistlib.dumps(flag_plist),
path="/var/preferences/FeatureFlags/Global.plist",
files_to_restore=files_to_restore
)
self.add_skip_setup(files_to_restore, uses_domains)
if gestalt_data != None:
# set the plist keys
if not resetting:
for tweak_name in tweaks:
tweak = tweaks[tweak_name]
if isinstance(tweak, FeatureFlagTweak):
flag_plist = tweak.apply_tweak(flag_plist)
elif isinstance(tweak, EligibilityTweak):
eligibility_files = tweak.apply_tweak()
elif isinstance(tweak, AITweak):
ai_file = tweak.apply_tweak()
elif isinstance(tweak, BasicPlistTweak) or isinstance(tweak, RdarFixTweak) or isinstance(tweak, AdvancedPlistTweak):
basic_plists = tweak.apply_tweak(basic_plists, self.allow_risky_tweaks)
basic_plists_ownership[tweak.file_location] = tweak.owner
if tweak.enabled and tweak.owner == 0:
uses_domains = True
elif isinstance(tweak, NullifyFileTweak):
tweak.apply_tweak(files_data)
if tweak.enabled and tweak.file_location.value.startswith("/var/mobile/"):
uses_domains = True
elif isinstance(tweak, PosterboardTweak):
tmp_pb_dir = TemporaryDirectory()
tweak.apply_tweak(files_to_restore=files_to_restore, output_dir=tmp_pb_dir.name)
else:
if gestalt_plist != None:
gestalt_plist = tweak.apply_tweak(gestalt_plist)
elif tweak.enabled:
# no mobilegestalt file provided but applying mga tweaks, give warning
show_error_msg("No mobilegestalt file provided! Please select your file to apply mobilegestalt tweaks.")
update_label("Failed.")
return
# set the custom gestalt keys
if gestalt_plist != None:
gestalt_plist = CustomGestaltTweaks.apply_tweaks(gestalt_plist)
gestalt_data = None
if resetting:
gestalt_data = b""
elif gestalt_plist != None:
gestalt_data = plistlib.dumps(gestalt_plist)
# Generate backup
update_label("Generating backup...")
self.concat_file(
contents=gestalt_data,
path="/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist",
contents=plistlib.dumps(flag_plist),
path="/var/preferences/FeatureFlags/Global.plist",
files_to_restore=files_to_restore
)
if eligibility_files:
new_eligibility_files: dict[FileToRestore] = []
if not self.get_current_device_supported():
# update the files
for file in eligibility_files:
self.concat_file(
contents=file.contents,
path=file.restore_path,
files_to_restore=new_eligibility_files
)
else:
new_eligibility_files = eligibility_files
files_to_restore += new_eligibility_files
if ai_file != None:
self.concat_file(
contents=ai_file.contents,
path=ai_file.restore_path,
files_to_restore=files_to_restore
)
for location, plist in basic_plists.items():
ownership = basic_plists_ownership[location]
self.concat_file(
contents=plistlib.dumps(plist),
path=location.value,
files_to_restore=files_to_restore,
owner=ownership, group=ownership
)
for location, data in files_data.items():
self.concat_file(
contents=data,
path=location.value,
files_to_restore=files_to_restore,
owner=ownership, group=ownership
)
# reset basic tweaks
if resetting:
empty_data = plistlib.dumps({})
for location in FileLocationsList:
self.add_skip_setup(files_to_restore, uses_domains)
if gestalt_data != None:
self.concat_file(
contents=empty_data,
path=location.value,
contents=gestalt_data,
path="/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist",
files_to_restore=files_to_restore
)
if self.allow_risky_tweaks:
for location in RiskyFileLocationsList:
if eligibility_files:
new_eligibility_files: dict[FileToRestore] = []
if not self.get_current_device_supported():
# update the files
for file in eligibility_files:
self.concat_file(
contents=file.contents,
path=file.restore_path,
files_to_restore=new_eligibility_files
)
else:
new_eligibility_files = eligibility_files
files_to_restore += new_eligibility_files
if ai_file != None:
self.concat_file(
contents=ai_file.contents,
path=ai_file.restore_path,
files_to_restore=files_to_restore
)
for location, plist in basic_plists.items():
ownership = basic_plists_ownership[location]
self.concat_file(
contents=plistlib.dumps(plist),
path=location.value,
files_to_restore=files_to_restore,
owner=ownership, group=ownership
)
for location, data in files_data.items():
self.concat_file(
contents=data,
path=location.value,
files_to_restore=files_to_restore,
owner=ownership, group=ownership
)
# reset basic tweaks
if resetting:
empty_data = plistlib.dumps({})
for location in FileLocationsList:
self.concat_file(
contents=empty_data,
path=location.value,
files_to_restore=files_to_restore
)
if self.allow_risky_tweaks:
for location in RiskyFileLocationsList:
self.concat_file(
contents=empty_data,
path=location.value,
files_to_restore=files_to_restore
)
# restore to the device
update_label("Restoring to device...")
try:
# restore to the device
update_label("Restoring to device...")
restore_files(files=files_to_restore, reboot=self.auto_reboot, lockdown_client=self.data_singleton.current_device.ld)
if tmp_pb_dir != None:
tmp_pb_dir.cleanup()
msg = "Your device will now restart."
if not self.auto_reboot:
msg = "Please restart your device to see changes."
QMessageBox.information(None, "Success!", "All done! " + msg)
update_label("Success!")
except Exception as e:
if tmp_pb_dir != None:
tmp_pb_dir.cleanup()
show_apply_error(e, update_label)
## RESETTING MOBILE GESTALT

12
documentation.md Normal file
View File

@@ -0,0 +1,12 @@
# Documentation
## Tendies Files (PosterBoard wallpapers)
Tendies files store the file structure to be restored to PosterBoard.
There are 2 formats for these:
1. Container format: a containing folder has the name "container"
- This restores directly to the app container inside of /var/mobile/Containers/Applications/PosterBoard.app and will keep that file structure.
- Descriptor UUIDs and wallpaper IDs will not be randomized using this format.
2. Descriptor format: a containing folder has the name "descriptor" or "descriptors"
- This restores to descriptors inside the container. Currently, it restores to the 61 folder, but in future versions it may be handled by iOS version if needed in future versions. If the structure also changes, this may be automatically handled by Nugget in future versions.
- Descriptor UUIDs and wallpaper IDs will be randomized, preventing overlapping.
- It is recommended to use these if you are restoring descriptors to collections since this will be more future proof. Randomization of IDs is also safer.

View File

@@ -1,5 +1,6 @@
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout
from PySide6.QtGui import QFont
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QLabel, QVBoxLayout, QHBoxLayout, QWidget, QToolButton, QSizePolicy
from PySide6.QtGui import QFont, QIcon, QPixmap
from PySide6.QtCore import QSize
from webbrowser import open_new_tab
@@ -32,6 +33,41 @@ class GestaltDialog(QDialog):
super().accept()
class PBHelpDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
QBtn = (
QDialogButtonBox.Ok
)
self.buttonBox = QDialogButtonBox(QBtn)
self.buttonBox.accepted.connect(self.accept)
self.setWindowTitle("PosterBoard Info")
layout = QVBoxLayout()
message = QLabel("Descriptors will be under the Collections section when adding a new wallpaper.\n\nIf the wallpapers don't appear in the menu, you either have to wait a bit for them to load,\nor you've reached the maximum amount of wallpapers (15) and have to wipe them.")
layout.addWidget(message)
imgBox = QWidget()
imgBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
imgBox.setStyleSheet("QWidget { background: none; padding: 0px; border: none; }")
hlayout = QHBoxLayout()
tut1 = QToolButton()
tut1.setIconSize(QSize(138, 300))
tut1.setStyleSheet("QToolButton { background: none; padding: 0px; border: none; }")
tut1.setIcon(QIcon(QPixmap(":/gui/pb_tutorial1.png")))
hlayout.addWidget(tut1)
tut2 = QToolButton()
tut2.setIconSize(QSize(138, 300))
tut2.setStyleSheet("QToolButton { background: none; padding: 0px; border: none; }")
tut2.setIcon(QIcon(QPixmap(":/gui/pb_tutorial2.png")))
hlayout.addWidget(tut2)
imgBox.setLayout(hlayout)
layout.addWidget(imgBox)
layout.addWidget(self.buttonBox)
self.setLayout(layout)
class UpdateAppDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)

View File

@@ -12,13 +12,13 @@ from controllers.web_request_handler import is_update_available
from devicemanagement.constants import Version
from devicemanagement.device_manager import DeviceManager
from gui.dialogs import GestaltDialog, UpdateAppDialog
from gui.dialogs import GestaltDialog, UpdateAppDialog, PBHelpDialog
from tweaks.tweaks import tweaks
from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks, ValueTypeStrings
from tweaks.daemons_tweak import Daemon
App_Version = "4.2.3"
App_Version = "5.0"
App_Build = 0
class Page(Enum):
@@ -29,9 +29,10 @@ class Page(Enum):
Springboard = 4
InternalOptions = 5
Daemons = 6
RiskyTweaks = 7
Apply = 8
Settings = 9
Posterboard = 7
RiskyTweaks = 8
Apply = 9
Settings = 10
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, device_manager: DeviceManager):
@@ -40,6 +41,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui = Ui_Nugget()
self.ui.setupUi(self)
self.show_uuid = False
self.pb_mainLayout = None
self.loadSettings()
# Check for an update
@@ -63,6 +65,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.springboardOptionsPageBtn.clicked.connect(self.on_springboardOptionsPageBtn_clicked)
self.ui.internalOptionsPageBtn.clicked.connect(self.on_internalOptionsPageBtn_clicked)
self.ui.daemonsPageBtn.clicked.connect(self.on_daemonsPageBtn_clicked)
self.ui.posterboardPageBtn.clicked.connect(self.on_posterboardPageBtn_clicked)
self.ui.advancedPageBtn.clicked.connect(self.on_advancedPageBtn_clicked)
self.ui.applyPageBtn.clicked.connect(self.on_applyPageBtn_clicked)
self.ui.settingsPageBtn.clicked.connect(self.on_settingsPageBtn_clicked)
@@ -78,11 +81,12 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.leminTwitterBtn.clicked.connect(self.on_leminTwitterBtn_clicked)
self.ui.leminKoFiBtn.clicked.connect(self.on_leminKoFiBtn_clicked)
self.ui.jjtechBtn.clicked.connect(self.on_jjtechBtn_clicked)
self.ui.posterRestoreBtn.clicked.connect(self.on_posterRestoreBtn_clicked)
self.ui.disfordottieBtn.clicked.connect(self.on_disfordottieBtn_clicked)
self.ui.mikasaBtn.clicked.connect(self.on_mikasaBtn_clicked)
self.ui.libiBtn.clicked.connect(self.on_libiBtn_clicked)
self.ui.jjtechBtn.clicked.connect(self.on_jjtechBtn_clicked)
self.ui.qtBtn.clicked.connect(self.on_qtBtn_clicked)
self.ui.discordBtn.clicked.connect(self.on_discordBtn_clicked)
@@ -155,6 +159,14 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.spotlightChk.toggled.connect(self.on_spotlightChk_clicked)
self.ui.voiceControlChk.toggled.connect(self.on_voiceControlChk_clicked)
## POSTERBOARD PAGE ACTIONS
self.ui.modifyPosterboardsChk.toggled.connect(self.on_modifyPosterboardsChk_clicked)
self.ui.importTendiesBtn.clicked.connect(self.on_importTendiesBtn_clicked)
self.ui.resetPRBExtBtn.clicked.connect(self.on_resetPRBExtBtn_clicked)
self.ui.deleteAllDescriptorsBtn.clicked.connect(self.on_deleteAllDescriptorsBtn_clicked)
self.ui.findPBBtn.clicked.connect(self.on_findPBBtn_clicked)
self.ui.pbHelpBtn.clicked.connect(self.on_pbHelpBtn_clicked)
## RISKY OPTIONS PAGE ACTIONS
self.ui.disableOTAChk.toggled.connect(self.on_disableOTAChk_clicked)
self.ui.enableResolutionChk.toggled.connect(self.on_enableResolutionChk_clicked)
@@ -210,6 +222,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.resChangerContent.hide()
self.ui.resHeightWarningLbl.hide()
self.ui.resWidthWarningLbl.hide()
self.ui.pbActionLbl.hide()
## GENERAL INTERFACE FUNCTIONS
@@ -250,6 +263,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.springboardOptionsPageBtn.hide()
self.ui.internalOptionsPageBtn.hide()
self.ui.daemonsPageBtn.hide()
self.ui.posterboardPageBtn.hide()
self.ui.advancedPageBtn.hide()
self.ui.sidebarDiv2.hide()
@@ -261,18 +275,27 @@ class MainWindow(QtWidgets.QMainWindow):
self.ui.devicePicker.setEnabled(True)
# populate the ComboBox with device names
for device in self.device_manager.devices:
self.ui.devicePicker.addItem(device.name)
tag = ""
if self.device_manager.apply_over_wifi:
if device.connected_via_usb:
tag = " (@ USB)"
else:
tag = " (@ WiFi)"
self.ui.devicePicker.addItem(f"{device.name}{tag}")
# show all pages
self.ui.sidebarDiv1.show()
self.ui.springboardOptionsPageBtn.show()
self.ui.internalOptionsPageBtn.show()
self.ui.daemonsPageBtn.show()
self.ui.posterboardPageBtn.show()
if self.device_manager.allow_risky_tweaks:
self.ui.advancedPageBtn.show()
self.ui.resetPRBExtBtn.show()
else:
self.ui.advancedPageBtn.hide()
self.ui.resetPRBExtBtn.hide()
self.ui.sidebarDiv2.show()
self.ui.applyPageBtn.show()
@@ -451,6 +474,9 @@ class MainWindow(QtWidgets.QMainWindow):
def on_daemonsPageBtn_clicked(self):
self.ui.pages.setCurrentIndex(Page.Daemons.value)
def on_posterboardPageBtn_clicked(self):
self.ui.pages.setCurrentIndex(Page.Posterboard.value)
def on_advancedPageBtn_clicked(self):
self.ui.pages.setCurrentIndex(Page.RiskyTweaks.value)
@@ -513,8 +539,8 @@ class MainWindow(QtWidgets.QMainWindow):
def on_leminKoFiBtn_clicked(self):
webbrowser.open_new_tab("https://ko-fi.com/leminlimez")
def on_jjtechBtn_clicked(self):
webbrowser.open_new_tab("https://github.com/JJTech0130/TrollRestore")
def on_posterRestoreBtn_clicked(self):
webbrowser.open_new_tab("https://discord.gg/gWtzTVhMvh")
def on_disfordottieBtn_clicked(self):
webbrowser.open_new_tab("https://twitter.com/disfordottie")
def on_mikasaBtn_clicked(self):
@@ -522,6 +548,8 @@ class MainWindow(QtWidgets.QMainWindow):
def on_libiBtn_clicked(self):
webbrowser.open_new_tab("https://github.com/doronz88/pymobiledevice3")
def on_jjtechBtn_clicked(self):
webbrowser.open_new_tab("https://github.com/JJTech0130/TrollRestore")
def on_qtBtn_clicked(self):
webbrowser.open_new_tab("https://www.qt.io/product/development-tools")
@@ -815,6 +843,123 @@ class MainWindow(QtWidgets.QMainWindow):
def on_voiceControlChk_clicked(self, checked: bool):
tweaks["Daemons"].set_multiple_values(Daemon.VoiceControl.value, value=checked)
## PosterBoard Page
def delete_pb_file(self, file, widget):
if file in tweaks["PosterBoard"].tendies:
tweaks["PosterBoard"].tendies.remove(file)
widget.deleteLater()
def load_posterboard(self):
if len(tweaks["PosterBoard"].tendies) == 0:
return
if self.pb_mainLayout == None:
# Create scroll layout
self.pb_mainLayout = QtWidgets.QVBoxLayout()
self.pb_mainLayout.setContentsMargins(0, 0, 0, 0)
self.pb_mainLayout.setAlignment(QtCore.Qt.AlignmentFlag.AlignTop)
# Create a QWidget to act as the container for the scroll area
scrollWidget = QtWidgets.QWidget()
# Set the main layout (containing all the widgets) on the scroll widget
scrollWidget.setLayout(self.pb_mainLayout)
# Create a QScrollArea to hold the content widget (scrollWidget)
scrollArea = QtWidgets.QScrollArea()
scrollArea.setWidgetResizable(True) # Allow the content widget to resize within the scroll area
scrollArea.setFrameStyle(QtWidgets.QScrollArea.NoFrame) # Remove the outline from the scroll area
# Set the scrollWidget as the content widget of the scroll area
scrollArea.setWidget(scrollWidget)
# Set the size policy of the scroll area to expand in both directions
scrollArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
# Set the scroll area as the central widget of the main window
scrollLayout = QtWidgets.QVBoxLayout()
scrollLayout.setContentsMargins(0, 0, 0, 0)
scrollLayout.addWidget(scrollArea)
self.ui.pbFilesList.setLayout(scrollLayout)
widgets = {}
# Iterate through the files
for tendie in tweaks["PosterBoard"].tendies:
if tendie.loaded:
continue
widget = QtWidgets.QWidget()
widgets[tendie] = widget
# create the icon/label
titleBtn = QtWidgets.QToolButton(widget)
titleBtn.setIcon(QtGui.QIcon(tendie.get_icon()))
titleBtn.setIconSize(QtCore.QSize(20, 20))
titleBtn.setText(f" {tendie.name}")
titleBtn.setStyleSheet("QToolButton {\n background-color: transparent;\n icon-size: 20px;\n}")
titleBtn.setToolButtonStyle(QtCore.Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
titleBtn.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
delBtn = QtWidgets.QToolButton(widget)
delBtn.setIcon(QtGui.QIcon(":/icon/trash.svg"))
delBtn.clicked.connect(lambda _, file=tendie: (widgets[file].deleteLater(), tweaks["PosterBoard"].tendies.remove(file)))
spacer = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
# main layout
layout = QtWidgets.QHBoxLayout(widget)
layout.setContentsMargins(0, 0, 0, 9)
layout.addWidget(titleBtn)
layout.addItem(spacer)
layout.addWidget(delBtn)
# Add the widget to the mainLayout
widget.setLayout(layout)
self.pb_mainLayout.addWidget(widget)
tendie.loaded = True
def on_modifyPosterboardsChk_clicked(self, checked: bool):
tweaks["PosterBoard"].set_enabled(checked)
self.ui.posterboardPageContent.setDisabled(not checked)
def on_importTendiesBtn_clicked(self):
selected_files, _ = QtWidgets.QFileDialog.getOpenFileNames(self, "Select PosterBoard Files", "", "Zip Files (*.tendies)", options=QtWidgets.QFileDialog.ReadOnly)
tweaks["PosterBoard"].resetting = False
self.ui.pbActionLbl.hide()
if selected_files != None and len(selected_files) > 0:
# user selected files, add them
for file in selected_files:
if not tweaks["PosterBoard"].add_tendie(file):
# alert that there are too many
detailsBox = QtWidgets.QMessageBox()
detailsBox.setIcon(QtWidgets.QMessageBox.Critical)
detailsBox.setWindowTitle("Error!")
detailsBox.setText("You selected too many descriptors! The limit is 10.")
detailsBox.exec()
self.load_posterboard()
def on_deleteAllDescriptorsBtn_clicked(self):
if tweaks["PosterBoard"].resetting and tweaks["PosterBoard"].resetType == 0:
tweaks["PosterBoard"].resetting = False
self.ui.pbActionLbl.hide()
else:
tweaks["PosterBoard"].resetting = True
tweaks["PosterBoard"].resetType = 0
self.ui.pbActionLbl.setText("! Clearing Collections Wallpapers")
self.ui.pbActionLbl.show()
def on_resetPRBExtBtn_clicked(self):
if tweaks["PosterBoard"].resetting and tweaks["PosterBoard"].resetType == 1:
tweaks["PosterBoard"].resetting = False
self.ui.pbActionLbl.hide()
else:
tweaks["PosterBoard"].resetting = True
tweaks["PosterBoard"].resetType = 1
self.ui.pbActionLbl.setText("! Resetting PRB Extension")
self.ui.pbActionLbl.show()
def on_findPBBtn_clicked(self):
webbrowser.open_new_tab("https://cowabun.ga/wallpapers")
def on_pbHelpBtn_clicked(self):
dialog = PBHelpDialog()
dialog.exec()
## Risky Options Page
def on_disableOTAChk_clicked(self, checked: bool):
tweaks["DisableOTAFile"].set_enabled(checked)
@@ -864,8 +1009,10 @@ class MainWindow(QtWidgets.QMainWindow):
# toggle the button visibility
if checked:
self.ui.advancedPageBtn.show()
self.ui.resetPRBExtBtn.show()
else:
self.ui.advancedPageBtn.hide()
self.ui.resetPRBExtBtn.hide()
def on_showAllSpoofableChk_toggled(self, checked: bool):
self.device_manager.show_all_spoofable_models = checked
# save the setting

BIN
gui/pb_tutorial1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 420 KiB

BIN
gui/pb_tutorial2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

11
icon/arrow.clockwise.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 326-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 174.5 233.875">
<g>
<rect height="233.875" opacity="0" width="174.5" x="0" y="0"/>
<path d="M0 124.375C0 172.5 39 211.625 87.25 211.625C135.5 211.625 174.5 172.5 174.5 124.375C174.5 118.75 170.625 114.875 165.125 114.875C159.875 114.875 156.5 118.75 156.5 124.25C156.5 162.5 125.5 193.375 87.25 193.375C49 193.375 18 162.5 18 124.25C18 86 49 55 87.25 55C93.75 55 99.625 55.25 104.875 56.375L78.375 82.375C76.625 84 75.875 86.375 75.875 88.625C75.875 93.75 79.75 97.625 84.625 97.625C87.5 97.625 89.5 96.625 91.125 95.125L130.625 55.625C132.375 53.875 133.25 51.75 133.25 49.125C133.25 46.75 132.25 44.375 130.625 42.75L91.125 2.75C89.625 1 87.375 0 84.625 0C79.75 0 75.875 4.125 75.875 9.25C75.875 11.625 76.75 13.875 78.25 15.625L101.25 38.375C96.75 37.5 92 37.125 87.25 37.125C39 37.125 0 76.125 0 124.375Z" fill="#ff453a"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

11
icon/globe.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 326-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 201.875 201.875">
<g>
<rect height="201.875" opacity="0" width="201.875" x="0" y="0"/>
<path d="M100.875 195.375C128.875 195.375 151 154.25 151 101.125C151 47.625 129 6.375 100.875 6.375C72.75 6.375 50.875 47.625 50.875 101.125C50.875 154.25 72.875 195.375 100.875 195.375ZM100.875 20C119.5 20 136.25 58 136.25 101.125C136.25 143.75 119.5 181.75 100.875 181.75C82.375 181.75 65.625 143.75 65.625 101.125C65.625 58 82.375 20 100.875 20ZM93.625 7.75L93.625 193.5L108.125 193.5L108.125 7.75ZM100.875 136.25C70.5 136.25 43.25 144.375 29.625 157.5L40.75 166.625C53.5 156.375 74.5 150.75 100.875 150.75C127.375 150.75 148.25 156.375 161.125 166.625L172.25 157.5C158.5 144.375 131.375 136.25 100.875 136.25ZM190.875 93.625L10.875 93.625L10.875 108.125L190.875 108.125ZM100.875 66C131.375 66 158.5 57.875 172.25 44.75L161.125 35.625C148.25 45.875 127.375 51.5 100.875 51.5C74.5 51.5 53.5 45.875 40.75 35.625L29.625 44.75C43.25 57.875 70.5 66 100.875 66ZM100.875 201.75C156.625 201.75 201.875 156.625 201.875 100.875C201.875 45.125 156.625 0 100.875 0C45.25 0 0 45.125 0 100.875C0 156.625 45.25 201.75 100.875 201.75ZM100.875 186.75C53.5 186.75 15.125 148.25 15.125 100.875C15.125 53.5 53.5 15 100.875 15C148.25 15 186.75 53.5 186.75 100.875C186.75 148.25 148.25 186.75 100.875 186.75Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

14
icon/photo-stack.svg Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 326-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 235.75 250.625">
<g>
<rect height="250.625" opacity="0" width="235.75" x="0" y="0"/>
<path d="M179.597 29L56.1528 29C56.4256 19.9631 61.8854 14.875 71.25 14.875L164.5 14.875C173.865 14.875 179.324 19.9631 179.597 29Z" fill="white" fill-opacity="0.85"/>
<path d="M200.083 58.4578C196.553 57.8144 192.764 57.5 188.75 57.5L47.125 57.5C43.0623 57.5 39.2305 57.8194 35.6653 58.4746C36.7346 47.6228 43.8122 41.625 55.625 41.625L180.125 41.625C191.932 41.625 199.008 47.6165 200.083 58.4578Z" fill="white" fill-opacity="0.85"/>
<path d="M48.5 232.75L187.625 232.75C206.875 232.75 217 223 217 203.875L217 197.625L167.625 150.75C163.75 147 158.625 145.25 153.75 145.25C148.625 145.25 144 146.875 139.75 150.625L97.125 188.5L80 173.25C76 169.625 71.625 167.875 67.25 167.875C63.125 167.875 59.125 169.75 55.125 173.125L19.125 204C19.125 223 29.25 232.75 48.5 232.75ZM47.125 235.375L188.75 235.375C210 235.375 220.625 225 220.625 204L220.625 104C220.625 83.125 210 72.625 188.75 72.625L47.125 72.625C25.75 72.625 15.25 83 15.25 104L15.25 204C15.25 225 25.75 235.375 47.125 235.375ZM47.375 217.375C38.375 217.375 33.25 212.5 33.25 203L33.25 105C33.25 95.5 38.375 90.625 47.375 90.625L188.5 90.625C197.5 90.625 202.625 95.5 202.625 105L202.625 203C202.625 212.5 197.5 217.375 188.5 217.375Z" fill="white" fill-opacity="0.85"/>
<path d="M89 153.375C101.375 153.375 111.375 143.125 111.375 130.75C111.375 118.625 101.375 108.5 89 108.5C76.625 108.5 66.625 118.625 66.625 130.75C66.625 143.125 76.625 153.375 89 153.375Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

12
icon/photo.svg Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 326-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 231.875 182.375">
<g>
<rect height="182.375" opacity="0" width="231.875" x="0" y="0"/>
<path d="M220.25 143.875L162.875 89.625C158.625 85.75 153.375 83.75 148.375 83.75C143.125 83.75 138.25 85.625 133.75 89.5L89.75 128.875L71.75 112.75C67.625 109.125 63.125 107.25 58.5 107.25C54.375 107.25 50 109 46 112.625L8.5 146.25C8.625 164.75 16.5 174.625 31.5 174.625L192.375 174.625C210.625 174.625 220.25 164.5 220.25 143.875ZM31.875 182.375L200 182.375C221.375 182.375 231.875 171.875 231.875 150.875L231.875 31.625C231.875 10.625 221.375 0.125 200 0.125L31.875 0.125C10.625 0.125 0 10.625 0 31.625L0 150.875C0 171.875 10.625 182.375 31.875 182.375ZM32.125 164.25C23.125 164.25 18 159.375 18 150L18 32.5C18 23 23.125 18.25 32.125 18.25L199.75 18.25C208.75 18.25 213.875 23 213.875 32.5L213.875 150C213.875 159.375 208.75 164.25 199.75 164.25Z" fill="white" fill-opacity="0.85"/>
<path d="M73.5 91.875C86.125 91.875 96.5 81.5 96.5 68.75C96.5 56.125 86.125 45.625 73.5 45.625C60.75 45.625 50.375 56.125 50.375 68.75C50.375 81.5 60.75 91.875 73.5 91.875Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 326-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 201.875 201.875">
<g>
<rect height="201.875" opacity="0" width="201.875" x="0" y="0"/>
<path d="M100.875 201.75C156.625 201.75 201.875 156.625 201.875 100.875C201.875 45.125 156.625 0 100.875 0C45.25 0 0 45.125 0 100.875C0 156.625 45.25 201.75 100.875 201.75ZM100.875 182.75C55.625 182.75 19.125 146.125 19.125 100.875C19.125 55.625 55.625 19 100.875 19C146.125 19 182.75 55.625 182.75 100.875C182.75 146.125 146.125 182.75 100.875 182.75Z" fill="white" fill-opacity="0.85"/>
<path d="M98.875 121.375C104.5 121.375 107.875 117.875 107.875 113.5C107.875 113.25 107.875 113 107.875 112.75C107.875 107.5 111.125 104.125 117.75 99.75C127 93.75 134.5 88 134.5 75.625C134.5 58.375 119.125 49.625 102.125 49.625C84.875 49.625 73.5 57.375 70.5 66C69.875 67.75 69.5 69.5 69.5 71.25C69.5 76.125 73.5 78.75 77.125 78.75C80.875 78.75 83.125 77.125 85.25 74.5L87.375 72C91.625 66.75 96 64.625 101.375 64.625C109.625 64.625 115 69.375 115 76.5C115 83.125 110.75 86.125 102.375 91.875C95.625 96.625 89.875 101.875 89.875 111.875C89.875 112 89.875 112.375 89.875 112.5C89.875 118.375 93 121.375 98.875 121.375ZM98.625 151.375C104.625 151.375 110 146.5 110 140.375C110 134.125 104.75 129.375 98.625 129.375C92.375 129.375 87.125 134.25 87.125 140.375C87.125 146.375 92.5 151.375 98.625 151.375Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

11
icon/shippingbox.svg Normal file
View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 326-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 199.125 218.688">
<g>
<rect height="218.688" opacity="0" width="199.125" x="0" y="0"/>
<path d="M14.75 172.531L91 216.156C96.875 219.531 102.25 219.531 108 216.156L184.25 172.531C193.75 167.156 199.125 161.781 199.125 146.156L199.125 70.6562C199.125 59.2812 195 52.1562 185.75 46.7812L118.875 8.40625C105.75 0.90625 93.375 0.90625 80.25 8.40625L13.375 46.7812C4 52.1562 0 59.2812 0 70.6562L0 146.156C0 161.781 5.375 167.156 14.75 172.531ZM24.375 157.656C19 154.531 16.875 151.156 16.875 145.656L16.875 74.1562L90.75 116.906L90.75 195.906ZM174.75 157.656L108.375 195.906L108.375 116.906L182.25 74.1562L182.25 145.656C182.25 151.156 180 154.531 174.75 157.656ZM99.5 101.281L26.75 59.6562L54.5 43.5312L127.125 85.4062ZM145.125 75.1562L72 33.5312L87.25 24.7812C95.75 19.9062 103.375 19.7812 111.75 24.7812L172.375 59.6562Z" fill="white" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

9
icon/wallpaper.svg Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 11.6098 11.6035" width="11.6098px" height="11.6035px">
<defs>
<style>.defaults{-sfsymbols-rotates-clockwise:true;}.hierarchical-0:primary{-sfsymbols-motion-group:0;-sfsymbols-layer-tags:apple.photos;}.monochrome-0{-sfsymbols-motion-group:0;-sfsymbols-layer-tags:apple.photos;}.multicolor-0:tintColor{-sfsymbols-motion-group:0;-sfsymbols-layer-tags:apple.photos;}</style>
</defs>
<g id="s" transform="matrix(0.00634764926508069, 0, 0, 0.00634764926508069, -0.8886709809303284, 10.441900253295898)">
<path class="monochrome-0 multicolor-0:tintColor hierarchical-0:primary" d="m435-1015c-177 0-295 115-295 284 0 170 118 284 295 284h231c177 0 294-114 294-284 0-169-117-284-294-284Zm0 115h231c112 0 178 63 178 169 0 107-65 169-178 169h-231c-113 0-179-63-179-169s66-169 179-169Zm382-470c-125-125-289-127-410-7-120 120-117 284 8 409l163 163c126 125 289 127 409 7 121-119 119-284-7-409Zm-82 82 164 163c79 80 81 170 6 246-74 75-165 73-245-7l-163-164c-79-80-82-171-8-245 75-76 167-73 246 7Zm35 167c0 177 115 295 285 295s284-118 284-295v-231c0-176-114-293-284-293s-285 117-285 293Zm116 0v-231c0-112 63-178 169-178s168 65 168 178v231c0 114-63 180-168 180-106 0-169-66-169-180Zm242-87c-125 125-127 291-7 410 121 120 285 118 410-7l163-164c126-125 128-288 7-408-120-120-283-119-409 6Zm82 82 164-163c79-79 171-82 245-6 75 75 73 164-7 244l-163 164c-80 80-171 82-246 8-74-76-72-166 7-247Zm234 111c-177 0-295 115-295 284 0 170 118 284 295 284h231c177 0 294-114 294-284 0-169-117-284-294-284Zm0 115h231c112 0 178 63 178 169 0 107-65 169-178 169h-231c-113 0-179-63-179-169s66-169 179-169Zm87 243c-125-125-290-127-410-7s-118 285 7 410l164 163c126 125 288 127 408 7 121-120 120-284-6-409Zm-82 82 163 164c79 79 82 170 6 245-74 75-164 73-244-7l-164-163c-79-81-81-171-8-246 76-75 167-73 247 7Zm-679 463c0 177 115 295 285 295s284-118 284-295v-231c0-176-114-293-284-293s-285 117-285 293Zm116 0v-231c0-112 63-178 169-178s168 65 168 178v231c0 114-63 180-168 180-106 0-169-66-169-180Zm-471-382c-125 125-127 290-7 410 121 120 284 117 409-8l163-163c126-125 128-289 7-409-119-120-283-118-409 7Zm82 82 163-164c80-79 171-81 246-6s73 165-7 245l-164 163c-79 80-171 83-245 8-75-75-72-166 7-246Z" style="fill: rgb(255, 255, 255);"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -243,7 +243,7 @@ QSlider::tick:horizontal {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/phone.svg</normaloff>:/icon/phone.svg</iconset>
</property>
</widget>
@@ -308,7 +308,7 @@ QSlider::tick:horizontal {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/arrow-clockwise.svg</normaloff>:/icon/arrow-clockwise.svg</iconset>
</property>
<property name="checkable">
@@ -415,7 +415,7 @@ QSlider::tick:horizontal {
<string> Home</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/house.svg</normaloff>:/icon/house.svg</iconset>
</property>
<property name="checkable">
@@ -462,7 +462,7 @@ QSlider::tick:horizontal {
<string> Mobile Gestalt</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/iphone-island.svg</normaloff>:/icon/iphone-island.svg</iconset>
</property>
<property name="iconSize">
@@ -506,7 +506,7 @@ QSlider::tick:horizontal {
<string> Feature Flags</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/flag.svg</normaloff>:/icon/flag.svg</iconset>
</property>
<property name="checkable">
@@ -535,7 +535,7 @@ QSlider::tick:horizontal {
<string> Eligibility</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/geo-alt.svg</normaloff>:/icon/geo-alt.svg</iconset>
</property>
<property name="checkable">
@@ -564,7 +564,7 @@ QSlider::tick:horizontal {
<string> Springboard Options</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/app-indicator.svg</normaloff>:/icon/app-indicator.svg</iconset>
</property>
<property name="checkable">
@@ -593,7 +593,7 @@ QSlider::tick:horizontal {
<string> Internal Options</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/hdd.svg</normaloff>:/icon/hdd.svg</iconset>
</property>
<property name="checkable">
@@ -625,7 +625,7 @@ QSlider::tick:horizontal {
<string> Daemons</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/toggles.svg</normaloff>:/icon/toggles.svg</iconset>
</property>
<property name="checkable">
@@ -642,6 +642,38 @@ QSlider::tick:horizontal {
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="posterboardPageBtn">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string> Posterboard</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/wallpaper.svg</normaloff>:/icon/wallpaper.svg</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="cls" stdset="0">
<string>sidebarBtn</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="advancedPageBtn">
<property name="sizePolicy">
@@ -654,7 +686,7 @@ QSlider::tick:horizontal {
<string> Risky Options</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/star.svg</normaloff>:/icon/star.svg</iconset>
</property>
<property name="checkable">
@@ -698,7 +730,7 @@ QSlider::tick:horizontal {
<string> Apply</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/check-circle.svg</normaloff>:/icon/check-circle.svg</iconset>
</property>
<property name="checkable">
@@ -727,7 +759,7 @@ QSlider::tick:horizontal {
<string> Settings</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/gear.svg</normaloff>:/icon/gear.svg</iconset>
</property>
<property name="checkable">
@@ -841,7 +873,7 @@ QSlider::tick:horizontal {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/phone.svg</normaloff>:/icon/phone.svg</iconset>
</property>
</widget>
@@ -970,7 +1002,7 @@ QSlider::tick:horizontal {
<string>...</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/credits/big_nugget.png</normaloff>:/credits/big_nugget.png</iconset>
</property>
<property name="iconSize">
@@ -1057,7 +1089,7 @@ QSlider::tick:horizontal {
<string> Join the Discord</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/discord.svg</normaloff>:/icon/discord.svg</iconset>
</property>
<property name="toolButtonStyle">
@@ -1071,7 +1103,7 @@ QSlider::tick:horizontal {
<string> Star on Github</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/star.svg</normaloff>:/icon/star.svg</iconset>
</property>
<property name="toolButtonStyle">
@@ -1175,7 +1207,7 @@ QSlider::tick:horizontal {
<string> LeminLimez</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/credits/LeminLimez.png</normaloff>:/credits/LeminLimez.png</iconset>
</property>
<property name="toolButtonStyle">
@@ -1202,7 +1234,7 @@ QToolButton:pressed {
<string>...</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/twitter.svg</normaloff>:/icon/twitter.svg</iconset>
</property>
</widget>
@@ -1226,7 +1258,7 @@ QToolButton:pressed {
<string>...</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/github.svg</normaloff>:/icon/github.svg</iconset>
</property>
</widget>
@@ -1251,7 +1283,7 @@ QToolButton:pressed {
<string>...</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/currency-dollar.svg</normaloff>:/icon/currency-dollar.svg</iconset>
</property>
</widget>
@@ -1371,7 +1403,7 @@ QToolButton:pressed {
</widget>
</item>
<item>
<widget class="QToolButton" name="jjtechBtn">
<widget class="QToolButton" name="posterRestoreBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@@ -1398,8 +1430,8 @@ QToolButton:pressed {
}</string>
</property>
<property name="text">
<string>JJTech
Sparserestore</string>
<string>PosterRestore Team
Posterboard</string>
</property>
</widget>
</item>
@@ -1538,6 +1570,33 @@ QToolButton:pressed {
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="jjtechBtn">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
border-radius: 0px;
background: none;
border: 1px solid #3b3b3b;
border-left: none;
}
QToolButton:pressed {
background-color: #535353;
color: #FFFFFF;
}</string>
</property>
<property name="text">
<string>JJTech
Sparserestore</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="qtBtn">
<property name="sizePolicy">
@@ -1624,7 +1683,7 @@ QToolButton:pressed {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/iphone-island.svg</normaloff>:/icon/iphone-island.svg</iconset>
</property>
<property name="iconSize">
@@ -2162,7 +2221,7 @@ Note: OTA updates will be broken until this is disabled.</string>
<string> Add Key</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/plus.svg</normaloff>:/icon/plus.svg</iconset>
</property>
<property name="checkable">
@@ -2279,7 +2338,7 @@ what you are doing.</string>
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/flag.svg</normaloff>:/icon/flag.svg</iconset>
</property>
</widget>
@@ -2511,7 +2570,7 @@ Only works on iOS 18.0 beta 1-2.</string>
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/geo-alt.svg</normaloff>:/icon/geo-alt.svg</iconset>
</property>
</widget>
@@ -3151,7 +3210,7 @@ QComboBox QAbstractItemView::item:hover {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/app-indicator.svg</normaloff>:/icon/app-indicator.svg</iconset>
</property>
</widget>
@@ -3403,7 +3462,7 @@ QComboBox QAbstractItemView::item:hover {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/hdd.svg</normaloff>:/icon/hdd.svg</iconset>
</property>
</widget>
@@ -3726,7 +3785,7 @@ QComboBox QAbstractItemView::item:hover {
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/toggles.svg</normaloff>:/icon/toggles.svg</iconset>
</property>
</widget>
@@ -4038,6 +4097,349 @@ To work properly, also disable the daemon using the toggle above.</string>
</item>
</layout>
</widget>
<widget class="QWidget" name="posterboardPage">
<layout class="QVBoxLayout" name="verticalLayout_14">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="horizontalWidget_5" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_20">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="toolButton_10">
<property name="enabled">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true">QToolButton {
icon-size: 24px;
background-color: transparent;
padding-left: 0px;
padding-right: 5px;
border-radius: 0px;
}</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/wallpaper.svg</normaloff>:/icon/wallpaper.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="verticalWidget_4" native="true">
<layout class="QVBoxLayout" name="verticalLayout_12">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="posterboardLbl">
<property name="font">
<font>
<pointsize>-1</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Posterboard</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="modifyPosterboardsChk">
<property name="text">
<string>Modify</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="findPBBtn">
<property name="text">
<string> Find Wallpapers</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/globe.svg</normaloff>:/icon/globe.svg</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="pbHelpBtn">
<property name="minimumSize">
<size>
<width>35</width>
<height>35</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>35</width>
<height>35</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/questionmark.circle.svg</normaloff>:/icon/questionmark.circle.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_12">
<property name="styleSheet">
<string notr="true">QFrame {
color: #414141;
}</string>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="posterboardPageContent" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_18">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="importTendiesBtn">
<property name="toolTip">
<string>Select a wallpaper file with the .tendies extension.</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string> Import Files (.tendies)</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/import.svg</normaloff>:/icon/import.svg</iconset>
</property>
<property name="iconSize">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="pbActionLbl">
<property name="text">
<string>None</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_27">
<property name="styleSheet">
<string notr="true">QFrame {
color: #414141;
}</string>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="pbFilesList" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>35</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="horizontalSpacer_19">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="deleteAllDescriptorsBtn">
<property name="toolTip">
<string>Clears all the wallpapers in the Collections section so that you can import more.</string>
</property>
<property name="text">
<string> Clear Collections Wallpapers</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/arrow.clockwise.svg</normaloff>:/icon/arrow.clockwise.svg</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="resetPRBExtBtn">
<property name="toolTip">
<string>Wipes the PRB Extension folder.
Warning: This will remove all of your wallpapers and will restrict you from adding new ones until you restore again.</string>
</property>
<property name="text">
<string> Remove All Wallpapers</string>
</property>
<property name="icon">
<iconset resource="resources.qrc">
<normaloff>:/icon/arrow.clockwise.svg</normaloff>:/icon/arrow.clockwise.svg</iconset>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_20">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="advancedOptionsPage">
<layout class="QVBoxLayout" name="verticalLayout_14">
<property name="leftMargin">
@@ -4085,7 +4487,7 @@ To work properly, also disable the daemon using the toggle above.</string>
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/star.svg</normaloff>:/icon/star.svg</iconset>
</property>
</widget>
@@ -4468,7 +4870,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/check-circle.svg</normaloff>:/icon/check-circle.svg</iconset>
</property>
</widget>
@@ -4592,7 +4994,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
<string> Choose Gestalt File</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/folder.svg</normaloff>:/icon/folder.svg</iconset>
</property>
<property name="toolButtonStyle">
@@ -4623,7 +5025,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
<string> Apply Changes</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/check-circle.svg</normaloff>:/icon/check-circle.svg</iconset>
</property>
<property name="toolButtonStyle">
@@ -4805,7 +5207,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/gear.svg</normaloff>:/icon/gear.svg</iconset>
</property>
</widget>
@@ -5142,7 +5544,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/geo-alt.svg</normaloff>:/icon/geo-alt.svg</iconset>
</property>
</widget>
@@ -5427,7 +5829,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/pencil.svg</normaloff>:/icon/pencil.svg</iconset>
</property>
<property name="iconSize">
@@ -5558,7 +5960,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
<string> Import .cowperation</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/import.svg</normaloff>:/icon/import.svg</iconset>
</property>
<property name="iconSize">
@@ -5593,7 +5995,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
<string> New Operation</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/plus.svg</normaloff>:/icon/plus.svg</iconset>
</property>
<property name="iconSize">
@@ -5674,7 +6076,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/compass.svg</normaloff>:/icon/compass.svg</iconset>
</property>
</widget>
@@ -5810,7 +6212,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
}</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/iphone-island.svg</normaloff>:/icon/iphone-island.svg</iconset>
</property>
<property name="iconSize">
@@ -5904,7 +6306,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
<string>...</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/folder.svg</normaloff>:/icon/folder.svg</iconset>
</property>
</widget>
@@ -5915,7 +6317,7 @@ Warning: Disabling will cause the battery to show &quot;Unknown Part&quot; or &q
<string>...</string>
</property>
<property name="icon">
<iconset>
<iconset resource="resources.qrc">
<normaloff>:/icon/file-earmark-zip.svg</normaloff>:/icon/file-earmark-zip.svg</iconset>
</property>
</widget>

File diff suppressed because it is too large Load Diff

View File

@@ -30,5 +30,14 @@
<file>icon/iphone-island.svg</file>
<file>icon/flag.svg</file>
<file>credits/big_nugget.png</file>
<file>icon/photo-stack.svg</file>
<file>icon/photo.svg</file>
<file>icon/shippingbox.svg</file>
<file>icon/wallpaper.svg</file>
<file>icon/questionmark.circle.svg</file>
<file>icon/globe.svg</file>
<file>gui/pb_tutorial1.png</file>
<file>gui/pb_tutorial2.png</file>
<file>icon/arrow.clockwise.svg</file>
</qresource>
</RCC>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

181
tweaks/posterboard_tweak.py Normal file
View File

@@ -0,0 +1,181 @@
import os
import zipfile
import uuid
import re
from random import randint
from PySide6 import QtWidgets, QtCore, QtGui
from .tweak_classes import Tweak
from Sparserestore.restore import FileToRestore
from controllers.plist_handler import set_plist_value
from qt.ui_mainwindow import Ui_Nugget
class TendieFile:
path: str
name: str
descriptor_cnt: int
is_container: bool
unsafe_container: bool
loaded: bool
def __init__(self, path: str):
self.path = path
self.name = os.path.basename(path)
self.descriptor_cnt = 0
self.is_container = False
self.unsafe_container = False
self.loaded = False
# read the contents
with zipfile.ZipFile(path, mode="r") as archive:
for option in archive.namelist():
if "__macosx/" in option.lower():
continue
if "container" in option.lower():
self.is_container = True
# check for the unsafe file that requires prb reset
if "PBFPosterExtensionDataStoreSQLiteDatabase.sqlite3" in option:
self.unsafe_container = True
if "descriptor/" in option.lower():
item = option.lower().split("descriptor/")[1]
if item.count('/') == 1 and item.endswith('/'):
self.descriptor_cnt += 1
elif "descriptors/" in option.lower():
item = option.lower().split("descriptors/")[1]
if item.count('/') == 1 and item.endswith('/'):
self.descriptor_cnt += 1
def get_icon(self):
if self.is_container:
# container
return ":/icon/shippingbox.svg"
elif self.descriptor_cnt == 1:
# single descriptor
return ":/icon/photo.svg"
else:
# multiple descriptors
return ":/icon/photo-stack.svg"
class PosterboardTweak(Tweak):
def __init__(self):
super().__init__(key=None)
self.tendies: list[TendieFile] = []
self.bundle_id = "com.apple.PosterBoard"
self.resetting = False
self.resetType = 0 # 0 for descriptor 1 for prb
def add_tendie(self, file: str):
new_tendie = TendieFile(path=file)
if new_tendie.descriptor_cnt + self.get_descriptor_count() <= 10:
self.tendies.append(new_tendie)
# alert if prb reset is needed
if new_tendie.unsafe_container:
detailsBox = QtWidgets.QMessageBox()
detailsBox.setIcon(QtWidgets.QMessageBox.Critical)
detailsBox.setWindowTitle("Warning")
detailsBox.setText("NOTE: You may need to reset all wallpapers (enable Risky Options in settings) and then re-apply for this file to work.")
detailsBox.exec()
return True
return False
def get_descriptor_count(self):
cnt = 0
for tendie in self.tendies:
cnt += tendie.descriptor_cnt
return cnt
def update_plist_id(self, file_path: str, file_name: str, randomizedID: int):
if file_name == "com.apple.posterkit.provider.descriptor.identifier":
return str(randomizedID).encode()
elif file_name == "com.apple.posterkit.provider.contents.userInfo":
return set_plist_value(file=os.path.join(file_path, file_name), key="wallpaperRepresentingIdentifier", value=randomizedID)
elif file_name == "Wallpaper.plist":
return set_plist_value(file=os.path.join(file_path, file_name), key="identifier", value=randomizedID)
return None
def clean_path_name(self, path: str):
return path# re.sub('[^a-zA-Z0-9\.\/\-_ ]', '', path)
def recursive_add(self,
files_to_restore: list[FileToRestore],
curr_path: str, restore_path: str = "",
isAdding: bool = False,
randomizeUUID: bool = False, randomizedID: int = None
):
for folder in sorted(os.listdir(curr_path)):
if folder.startswith('.') or folder == "__MACOSX":
continue
if isAdding:
# randomize uuid
folder_name = folder
curr_randomized_id = randomizedID
if randomizeUUID:
folder_name = str(uuid.uuid4()).upper()
curr_randomized_id = randint(9999, 99999)
# if file then add it, otherwise recursively call again
if os.path.isfile(os.path.join(curr_path, folder)):
try:
# update plist ids if needed
new_contents = None
contents_path = os.path.join(curr_path, folder)
if curr_randomized_id != None:
new_contents = self.update_plist_id(curr_path, folder, curr_randomized_id)
if new_contents != None:
contents_path = None
files_to_restore.append(FileToRestore(
contents=new_contents,
contents_path=contents_path,
restore_path=self.clean_path_name(f"{restore_path}/{folder_name}"),
domain=f"AppDomain-{self.bundle_id}"
))
except IOError:
print(f"Failed to open file: {folder}") # TODO: Add QDebug equivalent
else:
self.recursive_add(files_to_restore, os.path.join(curr_path, folder), f"{restore_path}/{folder_name}", isAdding, randomizedID=curr_randomized_id)
else:
# look for container folder
name = folder.lower()
if name == "container":
self.recursive_add(files_to_restore, os.path.join(curr_path, folder), restore_path="/", isAdding=True)
return
elif name == "descriptor" or name == "descriptors":
self.recursive_add(
files_to_restore,
os.path.join(curr_path, folder),
restore_path="/Library/Application Support/PRBPosterExtensionDataStore/61/Extensions/com.apple.WallpaperKit.CollectionsPoster/descriptors",
isAdding=True,
randomizeUUID=True
)
else:
self.recursive_add(files_to_restore, os.path.join(curr_path, folder), isAdding=False)
def apply_tweak(self, files_to_restore: list[FileToRestore], output_dir: str):
# unzip the file
if not self.enabled:
return
if self.resetting:
# null out the folder
file_path = ""
if self.resetType == 0:
# resetting descriptors
file_path = "/61/Extensions/com.apple.WallpaperKit.CollectionsPoster/descriptors"
files_to_restore.append(FileToRestore(
contents=b"",
restore_path=f"/Library/Application Support/PRBPosterExtensionDataStore{file_path}",
domain=f"AppDomain-{self.bundle_id}"
))
return
elif self.tendies == None or len(self.tendies) == 0:
return
if os.name == "nt":
# try to get past directory name limit on windows
output_dir = "\\\\?\\" + output_dir
for tendie in self.tendies:
zip_output = os.path.join(output_dir, str(uuid.uuid4()))
os.makedirs(zip_output)
with zipfile.ZipFile(tendie.path, 'r') as zip_ref:
zip_ref.extractall(zip_output)
# add the files
self.recursive_add(files_to_restore, curr_path=output_dir)

View File

@@ -1,6 +1,7 @@
from devicemanagement.constants import Version
from .tweak_classes import MobileGestaltTweak, MobileGestaltMultiTweak, MobileGestaltPickerTweak, FeatureFlagTweak, BasicPlistTweak, AdvancedPlistTweak, RdarFixTweak, NullifyFileTweak
from .eligibility_tweak import EligibilityTweak, AITweak
from .posterboard_tweak import PosterboardTweak
from .basic_plist_locations import FileLocation
tweaks = {
@@ -259,6 +260,9 @@ tweaks = {
),
"ClearScreenTimeAgentPlist": NullifyFileTweak(FileLocation.screentime),
## PosterBoard
"PosterBoard": PosterboardTweak(),
## Risky Options
"DisableOTAFile": AdvancedPlistTweak(
FileLocation.ota,