From 7b05673f6e92f21cbcd58d178371581569d0c8e8 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:22:26 +0700 Subject: [PATCH 1/3] test: add createrandomkey --- PlayTools/MysticRunes/PlayShadow.m | 2 +- PlayTools/MysticRunes/PlayedApple.swift | 35 +++++++++++++++++++++++++ PlayTools/PlayLoader.m | 29 ++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index b30fdb90..e7136a51 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -193,7 +193,7 @@ + (void) load { // Block UIAlertController presentation to bypass Endfield jailbreak message NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; if ([bundleID isEqualToString:@"com.gryphline.endfield.ios"] || - [bundleID isEqualToString:@"com.hypergryph.endfield"]) { + [bundleID isEqualToString:@"com.hypergryph.endfield"] || [bundleID isEqualToString:@"com.YostarJP.BlueArchive"]) { [self debugLogger:@"loading UIAlertController bypass"]; [objc_getClass("UIViewController") swizzleInstanceMethod:@selector(presentViewController:animated:completion:) withMethod:@selector(pm_endfield_presentViewController:animated:completion:)]; } diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index a1fd3c79..130dddce 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -198,4 +198,39 @@ public class PlayKeychain: NSObject { return errSecItemNotFound } + + @objc static public func keyCreateRandomKey(_ parameters: NSDictionary, + error: UnsafeMutablePointer?>?) -> Unmanaged? { + // Check if kSecAttrIsPermanent is set to 1 in kSecPrivateKeyAttrs. + // If it is, set it to 0 before fowarding the call to SecKeyCreateRandomKey, + // and then add the key to the keychain db with the original attributes (with kSecAttrIsPermanent set to 1) + var privateKeyAttrs = parameters[kSecPrivateKeyAttrs as String] as? [String: Any] ?? [:] + let isPermanent = privateKeyAttrs[kSecAttrIsPermanent as String] as? Bool ?? false + if isPermanent { + privateKeyAttrs[kSecAttrIsPermanent as String] = false + } + var parametersCopy = parameters as! [String: Any] // swiftlint:disable:this force_cast + parametersCopy[kSecPrivateKeyAttrs as String] = privateKeyAttrs + var error: Unmanaged? + guard let key = SecKeyCreateRandomKey(parametersCopy as CFDictionary, &error) else { + debugLogger("Failed to create random key: \(error!.takeRetainedValue())") + return nil + } + if isPermanent { + // Add the key to the keychain db with the original attributes + var keychainDict = [String: Any]() + keychainDict[kSecClass as String] = kSecClassKey + keychainDict[kSecAttrKeyType as String] = parameters[kSecAttrKeyType as String] + keychainDict[kSecAttrKeyClass as String] = parameters[kSecAttrKeyClass as String] + keychainDict["type"] = parameters[kSecAttrKeyType as String] + keychainDict["kcls"] = parameters[kSecAttrKeyClass as String] + keychainDict["v_Data"] = SecKeyCopyExternalRepresentation(key, nil) as? Data + keychainDict["r_Attributes"] = 1 + guard playChainDB.insert(keychainDict as NSDictionary) != nil else { + debugLogger("Failed to write keychain file") + return nil + } + } + return Unmanaged.passRetained(key) + } } diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index b6da6364..f0ec73ac 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -11,6 +11,9 @@ #import #import #import "NSObject+Swizzle.h" +#import + +@import MachO; // Get device model from playcover .plist // With a null terminator @@ -18,6 +21,9 @@ #define OEM_ID [[[PlaySettings shared] oemID] cStringUsingEncoding:NSUTF8StringEncoding] #define PLATFORM_IOS 2 +// Original implementations of the functions we interpose so we can call them in our implementations +uint32_t (*orig_dyld_image_count)(void) = _dyld_image_count; + // Define dyld_get_active_platform function for interpose int dyld_get_active_platform(void); int pt_dyld_get_active_platform(void) { return PLATFORM_IOS; } @@ -170,10 +176,27 @@ static OSStatus pt_SecItemDelete(CFDictionaryRef query) { return retval; } +static SecKeyRef pt_SecKeyCreateRandomKey(CFDictionaryRef parameters, CFErrorRef *error) { + SecKeyRef result; + if ([[PlaySettings shared] playChain]) { + result = [PlayKeychain keyCreateRandomKey:(__bridge NSDictionary * _Nonnull)(parameters) error:error]; + } else { + result = SecKeyCreateRandomKey(parameters, (void *)error); + } + + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecKeyCreateRandomKey: %@", parameters]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecKeyCreateRandomKey result: %@", result]]; + } + + return result; +} + DYLD_INTERPOSE(pt_SecItemCopyMatching, SecItemCopyMatching) DYLD_INTERPOSE(pt_SecItemAdd, SecItemAdd) DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) +DYLD_INTERPOSE(pt_SecKeyCreateRandomKey, SecKeyCreateRandomKey) static uint8_t ue_status = 0; @@ -276,12 +299,18 @@ static int pt_usleep(useconds_t time) { return usleep(time); } +static uint32_t pt_dyld_image_count(void) { + return orig_dyld_image_count() - 1; // No, PlayTools doesn't exist what are you talking about? +} + + DYLD_INTERPOSE(pt_open, open) DYLD_INTERPOSE(pt_stat, stat) DYLD_INTERPOSE(pt_access, access) DYLD_INTERPOSE(pt_rename, rename) DYLD_INTERPOSE(pt_unlink, unlink) DYLD_INTERPOSE(pt_usleep, usleep) +DYLD_INTERPOSE(pt_dyld_image_count, _dyld_image_count) @implementation PlayLoader From f64f4d965505ab7850c04b9b2ae2ae26ed088ee3 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:41:52 +0700 Subject: [PATCH 2/3] fix: GenerateKeyPair --- PlayTools/MysticRunes/PlayShadow.m | 2 +- PlayTools/MysticRunes/PlayedApple.swift | 60 ++++++++++++++++++++++++- PlayTools/PlayLoader.m | 19 ++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/PlayTools/MysticRunes/PlayShadow.m b/PlayTools/MysticRunes/PlayShadow.m index e7136a51..b30fdb90 100644 --- a/PlayTools/MysticRunes/PlayShadow.m +++ b/PlayTools/MysticRunes/PlayShadow.m @@ -193,7 +193,7 @@ + (void) load { // Block UIAlertController presentation to bypass Endfield jailbreak message NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; if ([bundleID isEqualToString:@"com.gryphline.endfield.ios"] || - [bundleID isEqualToString:@"com.hypergryph.endfield"] || [bundleID isEqualToString:@"com.YostarJP.BlueArchive"]) { + [bundleID isEqualToString:@"com.hypergryph.endfield"]) { [self debugLogger:@"loading UIAlertController bypass"]; [objc_getClass("UIViewController") swizzleInstanceMethod:@selector(presentViewController:animated:completion:) withMethod:@selector(pm_endfield_presentViewController:animated:completion:)]; } diff --git a/PlayTools/MysticRunes/PlayedApple.swift b/PlayTools/MysticRunes/PlayedApple.swift index 130dddce..e5c3b373 100644 --- a/PlayTools/MysticRunes/PlayedApple.swift +++ b/PlayTools/MysticRunes/PlayedApple.swift @@ -200,8 +200,9 @@ public class PlayKeychain: NSObject { } @objc static public func keyCreateRandomKey(_ parameters: NSDictionary, - error: UnsafeMutablePointer?>?) -> Unmanaged? { - // Check if kSecAttrIsPermanent is set to 1 in kSecPrivateKeyAttrs. + error: UnsafeMutablePointer?>?) + -> Unmanaged? { + // Check if kSecAttrIsPermanent is set to 1 in kSecPrivateKeyAttrs. // If it is, set it to 0 before fowarding the call to SecKeyCreateRandomKey, // and then add the key to the keychain db with the original attributes (with kSecAttrIsPermanent set to 1) var privateKeyAttrs = parameters[kSecPrivateKeyAttrs as String] as? [String: Any] ?? [:] @@ -233,4 +234,59 @@ public class PlayKeychain: NSObject { } return Unmanaged.passRetained(key) } + + @objc static public func keyGeneratePair(_ parameters: NSDictionary, + publicKey: UnsafeMutablePointer?>?, + privateKey: UnsafeMutablePointer?>?) -> OSStatus { + // Same as above but we need to disable kSecAttrIsPermanent for both the public and private key. + var privateKeyAttrs = parameters[kSecPrivateKeyAttrs as String] as? [String: Any] ?? [:] + let isPrivatePermanent = privateKeyAttrs[kSecAttrIsPermanent as String] as? Bool ?? false + if isPrivatePermanent { + privateKeyAttrs[kSecAttrIsPermanent as String] = false + } + var publicKeyAttrs = parameters[kSecPublicKeyAttrs as String] as? [String: Any] ?? [:] + let isPublicPermanent = (publicKeyAttrs[kSecAttrIsPermanent as String] as? Bool) ?? false + if isPublicPermanent { + publicKeyAttrs[kSecAttrIsPermanent as String] = false + } + var parametersCopy = parameters as! [String: Any] // swiftlint:disable:this force_cast + parametersCopy[kSecPrivateKeyAttrs as String] = privateKeyAttrs + parametersCopy[kSecPublicKeyAttrs as String] = publicKeyAttrs + var newPublicKey: SecKey? + var newPrivateKey: SecKey? + guard SecKeyGeneratePair(parametersCopy as CFDictionary, &newPublicKey, &newPrivateKey) != 0 else { + debugLogger("Failed to generate key pair.") + return errSecMissingEntitlement + } + if isPrivatePermanent { + // Add the keys to the keychain db with the original attributes + let publicKeyRef = newPublicKey + let privateKeyRef = newPrivateKey + var publicKeyDict = [String: Any]() + publicKeyDict[kSecClass as String] = kSecClassKey + publicKeyDict[kSecAttrKeyType as String] = parameters[kSecAttrKeyType as String] + publicKeyDict[kSecAttrKeyClass as String] = kSecAttrKeyClassPublic + publicKeyDict["type"] = parameters[kSecAttrKeyType as String] + publicKeyDict["kcls"] = kSecAttrKeyClassPublic + publicKeyDict["v_Data"] = SecKeyCopyExternalRepresentation(publicKeyRef!, nil) as? Data + publicKeyDict["r_Attributes"] = 1 + guard playChainDB.insert(publicKeyDict as NSDictionary) != nil else { + debugLogger("Failed to write public key to keychain db") + return errSecMissingEntitlement + } + var privateKeyDict = [String: Any]() + privateKeyDict[kSecClass as String] = kSecClassKey + privateKeyDict[kSecAttrKeyType as String] = parameters[kSecAttrKeyType as String] + privateKeyDict[kSecAttrKeyClass as String] = kSecAttrKeyClassPrivate + privateKeyDict["type"] = parameters[kSecAttrKeyType as String] + privateKeyDict["kcls"] = kSecAttrKeyClassPrivate + privateKeyDict["v_Data"] = SecKeyCopyExternalRepresentation(privateKeyRef!, nil) as? Data + privateKeyDict["r_Attributes"] = 1 + guard playChainDB.insert(privateKeyDict as NSDictionary) != nil else { + debugLogger("Failed to write private key to keychain db") + return errSecMissingEntitlement + } + } + return errSecSuccess + } } diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index f0ec73ac..8e76c592 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -192,11 +192,30 @@ static SecKeyRef pt_SecKeyCreateRandomKey(CFDictionaryRef parameters, CFErrorRef return result; } +// Deprecated, but some apps might still use it. +static OSStatus pt_SecKeyGeneratePair(CFDictionaryRef parameters, SecKeyRef *publicKey, SecKeyRef *privateKey) { + OSStatus retval; + if ([[PlaySettings shared] playChain]) { + retval = [PlayKeychain keyGeneratePair:(__bridge NSDictionary * _Nonnull)(parameters) publicKey:(void *)publicKey privateKey:(void *)privateKey]; + } else { + retval = SecKeyGeneratePair(parameters, (void *)publicKey, (void *)privateKey); + } + + if ([[PlaySettings shared] playChainDebugging]) { + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecKeyGeneratePair: %@", parameters]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecKeyGeneratePair public key result: %@", publicKey != NULL ? *publicKey : nil]]; + [PlayKeychain debugLogger: [NSString stringWithFormat:@"SecKeyGeneratePair private key result: %@", privateKey != NULL ? *privateKey : nil]]; + } + + return retval; +} + DYLD_INTERPOSE(pt_SecItemCopyMatching, SecItemCopyMatching) DYLD_INTERPOSE(pt_SecItemAdd, SecItemAdd) DYLD_INTERPOSE(pt_SecItemUpdate, SecItemUpdate) DYLD_INTERPOSE(pt_SecItemDelete, SecItemDelete) DYLD_INTERPOSE(pt_SecKeyCreateRandomKey, SecKeyCreateRandomKey) +DYLD_INTERPOSE(pt_SecKeyGeneratePair, SecKeyGeneratePair) static uint8_t ue_status = 0; From 59959faf300258e065e5a1935f3d9a548e4d6c97 Mon Sep 17 00:00:00 2001 From: ohaiibuzzle <23693150+ohaiibuzzle@users.noreply.github.com> Date: Mon, 9 Mar 2026 03:16:03 +0700 Subject: [PATCH 3/3] remove: dyld_count fix (doesn't do anything on its own anyway) --- PlayTools/PlayLoader.m | 8 -------- 1 file changed, 8 deletions(-) diff --git a/PlayTools/PlayLoader.m b/PlayTools/PlayLoader.m index 8e76c592..42881cef 100644 --- a/PlayTools/PlayLoader.m +++ b/PlayTools/PlayLoader.m @@ -21,9 +21,6 @@ #define OEM_ID [[[PlaySettings shared] oemID] cStringUsingEncoding:NSUTF8StringEncoding] #define PLATFORM_IOS 2 -// Original implementations of the functions we interpose so we can call them in our implementations -uint32_t (*orig_dyld_image_count)(void) = _dyld_image_count; - // Define dyld_get_active_platform function for interpose int dyld_get_active_platform(void); int pt_dyld_get_active_platform(void) { return PLATFORM_IOS; } @@ -318,10 +315,6 @@ static int pt_usleep(useconds_t time) { return usleep(time); } -static uint32_t pt_dyld_image_count(void) { - return orig_dyld_image_count() - 1; // No, PlayTools doesn't exist what are you talking about? -} - DYLD_INTERPOSE(pt_open, open) DYLD_INTERPOSE(pt_stat, stat) @@ -329,7 +322,6 @@ static uint32_t pt_dyld_image_count(void) { DYLD_INTERPOSE(pt_rename, rename) DYLD_INTERPOSE(pt_unlink, unlink) DYLD_INTERPOSE(pt_usleep, usleep) -DYLD_INTERPOSE(pt_dyld_image_count, _dyld_image_count) @implementation PlayLoader