From 093c6c444ef4a84052cf00c1f73d97aba6c4fa8c Mon Sep 17 00:00:00 2001 From: ichitaso Date: Thu, 15 Oct 2026 00:30:11 +0900 Subject: [PATCH 1/2] Update Tweak.m Temporary fix: Apple.APIError -80045 via SideStore https://discord.com/channels/949183273383395328/949186453987024936/1559672614438567947 I have configured it to output detailed logs. --- Compatibility/AltStoreAuthCompat/Tweak.m | 63 ++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) mode change 100644 => 100755 Compatibility/AltStoreAuthCompat/Tweak.m diff --git a/Compatibility/AltStoreAuthCompat/Tweak.m b/Compatibility/AltStoreAuthCompat/Tweak.m old mode 100644 new mode 100755 index c593be7..11ee2ca --- a/Compatibility/AltStoreAuthCompat/Tweak.m +++ b/Compatibility/AltStoreAuthCompat/Tweak.m @@ -114,6 +114,62 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte userInfo:@{NSLocalizedDescriptionKey: description}]; } +// Diagnostic only. Always returns a line so a missing line means the build +// without this code is loaded. Logs Apple's GSA status fields (ec / em / hsc) and +// the names (never the values) of the other keys in "Response". When the plist +// cannot be parsed or Apple reports a non-zero ec, a capped copy of the body is +// logged too (that is an error reply, not the SRP exchange). +static NSString *AAMBodyPreview(NSData *data) +{ + NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (text == nil) return [NSString stringWithFormat:@"(%lu bytes, not UTF-8)", (unsigned long)data.length]; + + NSArray *parts = [text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + NSString *collapsed = [[parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]] componentsJoinedByString:@" "]; + return collapsed.length > 1200 ? [collapsed substringToIndex:1200] : collapsed; +} + +static NSString *AAMStatusSummary(NSData *data) +{ + if (data.length == 0) return @"status (empty body)"; + + NSError *parseError = nil; + id plist = [NSPropertyListSerialization propertyListWithData:data + options:NSPropertyListImmutable + format:NULL + error:&parseError]; + if (![plist isKindOfClass:[NSDictionary class]]) + { + return [NSString stringWithFormat:@"status unparsed (%@) body=%@", + parseError.localizedDescription ?: [NSString stringWithFormat:@"root is %@", NSStringFromClass([plist class])], + AAMBodyPreview(data)]; + } + + NSDictionary *root = (NSDictionary *)plist; + NSDictionary *response = [root[@"Response"] isKindOfClass:[NSDictionary class]] ? root[@"Response"] : root; + NSDictionary *status = [response[@"Status"] isKindOfClass:[NSDictionary class]] ? response[@"Status"] : nil; + + NSMutableArray *otherKeys = [NSMutableArray array]; + for (id key in response) + { + if ([key isKindOfClass:[NSString class]] && ![key isEqualToString:@"Status"]) [otherKeys addObject:key]; + } + [otherKeys sortUsingSelector:@selector(compare:)]; + + NSString *em = [status[@"em"] description]; + if (em.length > 200) em = [em substringToIndex:200]; + + NSString *summary = [NSString stringWithFormat:@"status ec=%@ em=%@ hsc=%@ responseKeys=[%@]", + status[@"ec"] ?: @"(none)", + em ?: @"(none)", + status[@"hsc"] ?: @"(none)", + [otherKeys componentsJoinedByString:@","]]; + + BOOL isError = (status == nil) || ([status[@"ec"] integerValue] != 0); + if (isError) summary = [summary stringByAppendingFormat:@" body=%@", AAMBodyPreview(data)]; + return summary; +} + static NSURLSessionDataTask *AAMCreateAppleAuthenticationTask(id object, SEL selector, NSURLRequest *request, @@ -149,6 +205,7 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte firstByte == NSNotFound ? @"none" : [NSString stringWithFormat:@"0x%02lx", (unsigned long)firstByte], error == nil ? @"none" : [NSString stringWithFormat:@"%@/%ld", error.domain, (long)error.code]]; AAMAppendDiagnostic(responseMessage); + AAMAppendDiagnostic(AAMStatusSummary(data)); if (attempt < AAMMaximumRetryCount && AAMShouldRetry(httpResponse, error)) { @@ -203,9 +260,9 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte // AltStore 2.2.1 identifies the GSA request as a 2018 AuthKit client. // Apple now rejects GSA requests identifying as Xcode. The client-info // header is enforced here so the request cannot reuse that blocked token. - [updatedRequest setValue:@"AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.dt.Xcode/26.0)" + [updatedRequest setValue:@"AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.akd/1.0)" forHTTPHeaderField:@"User-Agent"]; - [updatedRequest setValue:@" " + [updatedRequest setValue:@" " forHTTPHeaderField:@"X-MMe-Client-Info"]; NSString *requestMessage = [NSString stringWithFormat:@"request path=%@ method=%@ correctedClientInfo=%@", @@ -240,7 +297,7 @@ static void AAMInitialize(void) method_setImplementation(class_getInstanceMethod(sessionClass, selector), (IMP)AAMDataTaskWithRequest); } - AAMAppendDiagnostic([NSString stringWithFormat:@"loaded in %@ sessionClass=%@", + AAMAppendDiagnostic([NSString stringWithFormat:@"loaded build=status-log-2 in %@ sessionClass=%@", NSBundle.mainBundle.bundleIdentifier ?: @"(unknown)", NSStringFromClass(sessionClass)]); [probeSession invalidateAndCancel]; From 659f9111df92f1378c5dea9be652d2da7464f71e Mon Sep 17 00:00:00 2001 From: ichitaso Date: Thu, 15 Oct 2026 14:56:45 +0900 Subject: [PATCH 2/2] v1.2.2 - Fixed Altstore Classic app crashing issue - Temporary fix again: Apple.APIError -80045 --- AltDaemon/AltDaemon.entitlements | 2 + AltDaemon/AnisetteDataManager.swift | 162 ++++++++++++++++++++++- AltDaemon/package/DEBIAN/control | 5 +- Compatibility/AltStoreAuthCompat/Tweak.m | 159 +++++++++++++++------- Repo/control | 2 +- 5 files changed, 280 insertions(+), 50 deletions(-) mode change 100644 => 100755 AltDaemon/AltDaemon.entitlements mode change 100644 => 100755 AltDaemon/AnisetteDataManager.swift diff --git a/AltDaemon/AltDaemon.entitlements b/AltDaemon/AltDaemon.entitlements old mode 100644 new mode 100755 index 47f380a..9413c8f --- a/AltDaemon/AltDaemon.entitlements +++ b/AltDaemon/AltDaemon.entitlements @@ -8,6 +8,8 @@ platform-application + com.apple.authkit.client.private + com.apple.private.InstallCoordination.allowed com.apple.private.mobileinstall.allowedSPI diff --git a/AltDaemon/AnisetteDataManager.swift b/AltDaemon/AnisetteDataManager.swift old mode 100644 new mode 100755 index aa56360..a94d09b --- a/AltDaemon/AnisetteDataManager.swift +++ b/AltDaemon/AnisetteDataManager.swift @@ -106,8 +106,8 @@ private struct AnisetteConfiguration "https://ani.sidestore.app", "https://ani.sidestore.zip", ] - static let defaultClientInfo = " " - static let defaultUserAgent = "AuthKit/1 (Macintosh; OS X 27.0) (com.apple.dt.Xcode/25183.54.10)" + static let defaultClientInfo = " " + static let defaultUserAgent = "AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.dt.Xcode/26.0)" let serverURL: URL let clientInfo: String @@ -884,6 +884,146 @@ private final class AnisetteProvisioningWebSocketSession: WebSocketDelegate, @un } } +// MARK: - On-device (AuthKit) anisette +// +// Mirrors what the Mac AltServer does: the anisette data is produced by the real +// device through AuthKit, so MachineID / OTP / device ID are coherent with each +// other instead of being emulated by a public server. Everything is resolved +// through the Objective-C runtime so the daemon still links and runs if AuthKit +// changes; any failure is reported and the v3 provider is used as a fallback. +private enum NativeAnisetteError: LocalizedError +{ + case frameworkUnavailable + case classUnavailable(String) + case selectorUnavailable(String) + case missingHeaders([String]) + + var errorDescription: String? + { + switch self + { + case .frameworkUnavailable: return "AuthKit could not be loaded." + case .classUnavailable(let name): return "AuthKit class \(name) is unavailable." + case .selectorUnavailable(let name): return "AuthKit selector \(name) is unavailable." + case .missingHeaders(let names): return "AuthKit did not return: \(names.joined(separator: ", "))." + } + } +} + +private enum NativeAnisetteProvider +{ + static func requestAnisetteData() throws -> ALTAnisetteData + { + guard dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW) != nil else + { + throw NativeAnisetteError.frameworkUnavailable + } + + guard let sessionClass = NSClassFromString("AKAppleIDSession") else { throw NativeAnisetteError.classUnavailable("AKAppleIDSession") } + guard let deviceClass = NSClassFromString("AKDevice") else { throw NativeAnisetteError.classUnavailable("AKDevice") } + + let allocSelector = NSSelectorFromString("alloc") + let initSelector = NSSelectorFromString("initWithIdentifier:") + let headersSelector = NSSelectorFromString("appleIDHeadersForRequest:") + let currentDeviceSelector = NSSelectorFromString("currentDevice") + + guard let allocated = (sessionClass as AnyObject).perform(allocSelector)?.takeUnretainedValue() as? NSObject, + allocated.responds(to: initSelector) + else { throw NativeAnisetteError.selectorUnavailable("initWithIdentifier:") } + + guard let session = allocated.perform(initSelector, with: "com.apple.gs.xcode.auth")?.takeUnretainedValue() as? NSObject, + session.responds(to: headersSelector) + else { throw NativeAnisetteError.selectorUnavailable("appleIDHeadersForRequest:") } + + let request = NSURLRequest(url: URL(string: "https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA")!) + guard let rawHeaders = session.perform(headersSelector, with: request)?.takeUnretainedValue() as? [AnyHashable: Any] else + { + throw NativeAnisetteError.missingHeaders(["headers"]) + } + + var headers: [String: String] = [:] + for (key, value) in rawHeaders + { + guard let key = key as? String else { continue } + headers[key.lowercased()] = (value as? String) ?? "\(value)" + } + + var device: NSObject? + if (deviceClass as AnyObject).responds(to: currentDeviceSelector) + { + device = (deviceClass as AnyObject).perform(currentDeviceSelector)?.takeUnretainedValue() as? NSObject + } + + func deviceString(_ name: String) -> String? + { + guard let device = device else { return nil } + let selector = NSSelectorFromString(name) + guard device.responds(to: selector) else { return nil } + return device.perform(selector)?.takeUnretainedValue() as? String + } + + func nonEmpty(_ value: String?) -> String? + { + guard let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines), !trimmed.isEmpty else { return nil } + return trimmed + } + + let machineID = nonEmpty(headers["x-apple-i-md-m"]) + let oneTimePassword = nonEmpty(headers["x-apple-i-md"]) + let routingInfo = nonEmpty(headers["x-apple-i-md-rinfo"]).flatMap { UInt64($0) } + let deviceID = nonEmpty(headers["x-mme-device-id"]) ?? nonEmpty(deviceString("uniqueDeviceIdentifier")) + + + // AuthKit on some iOS versions does not return X-Apple-I-MD-LU. Try the device + // object, then derive a stable value from the device ID (the same SHA-256 hex + // form the v3 path uses) so the request is still coherent and repeatable. + var localUserID = nonEmpty(headers["x-apple-i-md-lu"]) + ?? nonEmpty(deviceString("localUserUUID")) + ?? nonEmpty(deviceString("localUserID")) + if localUserID == nil, let deviceID = deviceID + { + localUserID = SHA256.hash(data: Data(deviceID.utf8)).map { String(format: "%02X", $0) }.joined() + } + let serial = nonEmpty(headers["x-apple-i-srl-no"]) ?? nonEmpty(deviceString("serialNumber")) ?? "0" + let rawDescription = nonEmpty(headers["x-mme-client-info"]) ?? nonEmpty(deviceString("serverFriendlyDescription")) + + + var missing: [String] = [] + if machineID == nil { missing.append("X-Apple-I-MD-M") } + if oneTimePassword == nil { missing.append("X-Apple-I-MD") } + if routingInfo == nil { missing.append("X-Apple-I-MD-RINFO") } + if localUserID == nil { missing.append("X-Apple-I-MD-LU") } + if deviceID == nil { missing.append("X-Mme-Device-Id") } + if rawDescription == nil { missing.append("X-MMe-Client-Info") } + guard missing.isEmpty, + let machineID = machineID, let oneTimePassword = oneTimePassword, let routingInfo = routingInfo, + let localUserID = localUserID, let deviceID = deviceID, let rawDescription = rawDescription + else { throw NativeAnisetteError.missingHeaders(missing) } + + // GSA rejects requests that identify as Xcode; keep the real device model and + // OS, but use the com.apple.akd client token that AltStore's compat module expects. + // AKDevice can also report a missing client token as "(null)/1.0". + var description = rawDescription.replacingOccurrences(of: "com\\.apple\\.dt\\.Xcode/[0-9.]+", + with: "com.apple.akd/1.0", + options: .regularExpression) + description = description.replacingOccurrences(of: "\\(null\\)/[0-9.]+", + with: "com.apple.akd/1.0", + options: .regularExpression) + description = description.replacingOccurrences(of: "(null)", with: "com.apple.akd") + + return ALTAnisetteData(machineID: machineID, + oneTimePassword: oneTimePassword, + localUserID: localUserID, + routingInfo: routingInfo, + deviceUniqueIdentifier: deviceID, + deviceSerialNumber: serial, + deviceDescription: description, + date: Date(), + locale: .current, + timeZone: .current) + } +} + struct AnisetteDataManager { static let shared = AnisetteDataManager() @@ -894,6 +1034,24 @@ struct AnisetteDataManager func requestAnisetteData() async throws -> ALTAnisetteData { + // ALTDAEMON_ANISETTE_MODE: "native" (device only), "v3" (public/self-hosted + // provider only), anything else = try the device first, then fall back to v3. + let mode = ProcessInfo.processInfo.environment["ALTDAEMON_ANISETTE_MODE"]?.lowercased() + if mode != "v3" + { + do + { + let data = try NativeAnisetteProvider.requestAnisetteData() + print("Anisette: on-device AuthKit succeeded (MID length: \(data.machineID.count), OTP length: \(data.oneTimePassword.count)).") + return data + } + catch + { + print("Anisette: on-device AuthKit failed:", error.localizedDescription) + if mode == "native" { throw error } + } + } + let payload = try await self.provider.requestAnisetteData() return ALTAnisetteData(machineID: payload.machineID, diff --git a/AltDaemon/package/DEBIAN/control b/AltDaemon/package/DEBIAN/control index 0599c75..bac4611 100644 --- a/AltDaemon/package/DEBIAN/control +++ b/AltDaemon/package/DEBIAN/control @@ -1,6 +1,6 @@ Package: com.rileytestut.altdaemon Name: AltDaemon Modern -Version: 1.2.0 +Version: 1.2.2 Architecture: iphoneos-arm64 Depends: ellekit (>= 1.2) Description: Reliable rootless AltDaemon with current Apple login compatibility. @@ -10,3 +10,6 @@ Maintainer: AltDaemonModern contributors Author: Riley Testut and AltDaemonModern contributors Homepage: https://github.com/altstoreio/AltStore Section: Tweaks +Depiction: https://cydia.ichitaso.com/depiction/altdaemon.html +SileoDepiction: https://cydia.ichitaso.com/depiction/altdaemon.json +Icon: https://raw.githubusercontent.com/ichitaso/AltDaemonModern/refs/heads/main/Repo/Icon-60%403x.png diff --git a/Compatibility/AltStoreAuthCompat/Tweak.m b/Compatibility/AltStoreAuthCompat/Tweak.m index 11ee2ca..8d7b921 100755 --- a/Compatibility/AltStoreAuthCompat/Tweak.m +++ b/Compatibility/AltStoreAuthCompat/Tweak.m @@ -2,6 +2,34 @@ #import #import +// 0: GSA and developer-services requests are rewritten to one coherent Mac identity +// (see below). Required with on-device anisette: Apple answers /services/v1/certificates +// with 403 FORBIDDEN_ERROR when the iPhone's own client info is sent. +// 1: pure logger; requests are not modified. +#define AAM_LOG_ONLY 0 + +// Client info used for every GSA request when AAM_LOG_ONLY is 0. +// 1: legacy MacBookPro13,2 tuple (matched the old public anisette servers). +// 2: Mac15,7 / macOS 15.7.9 tuple (the one observed from a working Mac AltServer run). +#define AAM_IDENTITY 2 + +// Same tuple for developerservices2.apple.com requests. It must match the GSA identity: +// mixing an iPhone sign-in with a Mac developer-services request is answered with +// "session expired" (AltStore error 1100). +// 1: apply the override (working configuration). 0: leave AltSign's value untouched. +#define AAM_DEV_IDENTITY 1 +#define AAM_DEV_CLIENT_INFO @" " + +#if AAM_IDENTITY == 1 +#define AAM_CLIENT_INFO @" " +#else +#define AAM_CLIENT_INFO @" " +#endif + +// The User-Agent AltSign sends (unmodified). It contains com.apple.dt.Xcode/26.0 and +// is accepted; only X-MMe-Client-Info is filtered. +#define AAM_USER_AGENT @"AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.dt.Xcode/26.0)" + typedef NSURLSessionDataTask *(*AAMDataTaskImplementation)(id, SEL, NSURLRequest *, @@ -114,60 +142,63 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte userInfo:@{NSLocalizedDescriptionKey: description}]; } -// Diagnostic only. Always returns a line so a missing line means the build -// without this code is loaded. Logs Apple's GSA status fields (ec / em / hsc) and -// the names (never the values) of the other keys in "Response". When the plist -// cannot be parsed or Apple reports a non-zero ec, a capped copy of the body is -// logged too (that is an error reply, not the SRP exchange). -static NSString *AAMBodyPreview(NSData *data) -{ - NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - if (text == nil) return [NSString stringWithFormat:@"(%lu bytes, not UTF-8)", (unsigned long)data.length]; - - NSArray *parts = [text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; - NSString *collapsed = [[parts filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"length > 0"]] componentsJoinedByString:@" "]; - return collapsed.length > 1200 ? [collapsed substringToIndex:1200] : collapsed; -} - +// Logs only Apple's status fields (ec / em / hsc). Never the response body. static NSString *AAMStatusSummary(NSData *data) { if (data.length == 0) return @"status (empty body)"; - NSError *parseError = nil; id plist = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListImmutable format:NULL - error:&parseError]; - if (![plist isKindOfClass:[NSDictionary class]]) - { - return [NSString stringWithFormat:@"status unparsed (%@) body=%@", - parseError.localizedDescription ?: [NSString stringWithFormat:@"root is %@", NSStringFromClass([plist class])], - AAMBodyPreview(data)]; - } + error:NULL]; + if (![plist isKindOfClass:[NSDictionary class]]) return @"status (not a property list)"; NSDictionary *root = (NSDictionary *)plist; NSDictionary *response = [root[@"Response"] isKindOfClass:[NSDictionary class]] ? root[@"Response"] : root; NSDictionary *status = [response[@"Status"] isKindOfClass:[NSDictionary class]] ? response[@"Status"] : nil; - NSMutableArray *otherKeys = [NSMutableArray array]; - for (id key in response) - { - if ([key isKindOfClass:[NSString class]] && ![key isEqualToString:@"Status"]) [otherKeys addObject:key]; - } - [otherKeys sortUsingSelector:@selector(compare:)]; - NSString *em = [status[@"em"] description]; if (em.length > 200) em = [em substringToIndex:200]; - NSString *summary = [NSString stringWithFormat:@"status ec=%@ em=%@ hsc=%@ responseKeys=[%@]", - status[@"ec"] ?: @"(none)", - em ?: @"(none)", - status[@"hsc"] ?: @"(none)", - [otherKeys componentsJoinedByString:@","]]; + return [NSString stringWithFormat:@"status ec=%@ em=%@ hsc=%@", + status[@"ec"] ?: @"(none)", em ?: @"(none)", status[@"hsc"] ?: @"(none)"]; +} + +// Retries run seconds later on a background queue. By then the session that issued +// the original request may already be invalidated, and creating a task on an +// invalidated NSURLSession throws an Objective-C exception (which crashed AltStore). +// Try the original session first, then a fresh ephemeral session. Returns nil only +// if neither works; the exception name and reason are logged for diagnosis. +static NSURLSessionDataTask *AAMCreateRetryTask(id object, + SEL selector, + NSURLRequest *request, + void (^completion)(NSData *, NSURLResponse *, NSError *)) +{ + @try + { + return AAMOriginalDataTaskImplementation(object, selector, request, completion); + } + @catch (NSException *exception) + { + AAMAppendDiagnostic([NSString stringWithFormat:@"retry task creation threw %@: %@", + exception.name, exception.reason ?: @"(no reason)"]); + } - BOOL isError = (status == nil) || ([status[@"ec"] integerValue] != 0); - if (isError) summary = [summary stringByAppendingFormat:@" body=%@", AAMBodyPreview(data)]; - return summary; + NSURLSession *freshSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]]; + @try + { + NSURLSessionDataTask *task = AAMOriginalDataTaskImplementation(freshSession, selector, request, completion); + [freshSession finishTasksAndInvalidate]; + AAMAppendDiagnostic(@"retry task created on a fresh session"); + return task; + } + @catch (NSException *exception) + { + AAMAppendDiagnostic([NSString stringWithFormat:@"fresh-session task creation threw %@: %@", + exception.name, exception.reason ?: @"(no reason)"]); + [freshSession invalidateAndCancel]; + return nil; + } } static NSURLSessionDataTask *AAMCreateAppleAuthenticationTask(id object, @@ -220,6 +251,17 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte request, completionHandler, attempt + 1); + if (retryTask == nil) + { + // The task could not be created at all (see AAMCreateRetryTask). + // Complete the caller with the last Apple error instead of hanging. + AAMAppendDiagnostic(@"retry aborted: could not create a task"); + if (completionHandler != nil) + { + completionHandler(nil, response, error ?: AAMAppleServiceError(httpResponse)); + } + return; + } [retryTask resume]; }); return; @@ -240,7 +282,20 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte } }; - return AAMOriginalDataTaskImplementation(object, selector, request, wrappedCompletion); + if (attempt == 0) + { + return AAMOriginalDataTaskImplementation(object, selector, request, wrappedCompletion); + } + + return AAMCreateRetryTask(object, selector, request, wrappedCompletion); +} + +// developerservices2.apple.com (certificates, App IDs, profiles) is a separate host from GSA. +static BOOL AAMIsDeveloperServicesRequest(NSURLRequest *request) +{ + NSString *host = request.URL.host.lowercaseString; + return [host isEqualToString:@"developerservices2.apple.com"] || + [host hasSuffix:@".developerservices2.apple.com"]; } static NSURLSessionDataTask *AAMDataTaskWithRequest(id object, @@ -248,6 +303,17 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte NSURLRequest *request, void (^completionHandler)(NSData *, NSURLResponse *, NSError *)) { + if (AAMIsDeveloperServicesRequest(request)) + { +#if AAM_DEV_IDENTITY == 1 + NSMutableURLRequest *devRequest = [request mutableCopy]; + [devRequest setValue:AAM_DEV_CLIENT_INFO forHTTPHeaderField:@"X-MMe-Client-Info"]; + return AAMOriginalDataTaskImplementation(object, selector, devRequest, completionHandler); +#else + return AAMOriginalDataTaskImplementation(object, selector, request, completionHandler); +#endif + } + if (!AAMIsAppleAuthenticationRequest(request)) { return AAMOriginalDataTaskImplementation(object, selector, request, completionHandler); @@ -260,15 +326,16 @@ static NSTimeInterval AAMRetryDelay(NSHTTPURLResponse *response, NSUInteger atte // AltStore 2.2.1 identifies the GSA request as a 2018 AuthKit client. // Apple now rejects GSA requests identifying as Xcode. The client-info // header is enforced here so the request cannot reuse that blocked token. - [updatedRequest setValue:@"AuthKit/1 (Macintosh; OS X 26.5.2) (com.apple.akd/1.0)" - forHTTPHeaderField:@"User-Agent"]; - [updatedRequest setValue:@" " - forHTTPHeaderField:@"X-MMe-Client-Info"]; +#if !AAM_LOG_ONLY + [updatedRequest setValue:AAM_USER_AGENT forHTTPHeaderField:@"User-Agent"]; + [updatedRequest setValue:AAM_CLIENT_INFO forHTTPHeaderField:@"X-MMe-Client-Info"]; +#endif - NSString *requestMessage = [NSString stringWithFormat:@"request path=%@ method=%@ correctedClientInfo=%@", + NSString *requestMessage = [NSString stringWithFormat:@"request path=%@ method=%@ correctedClientInfo=%@ headersOverridden=%@", updatedRequest.URL.path ?: @"(none)", updatedRequest.HTTPMethod ?: @"(none)", - hadCorrectClientInfo ? @"yes" : @"no"]; + hadCorrectClientInfo ? @"yes" : @"no", + AAM_LOG_ONLY ? @"no" : @"yes"]; AAMAppendDiagnostic(requestMessage); return AAMCreateAppleAuthenticationTask(object, selector, updatedRequest, completionHandler, 0); @@ -297,7 +364,7 @@ static void AAMInitialize(void) method_setImplementation(class_getInstanceMethod(sessionClass, selector), (IMP)AAMDataTaskWithRequest); } - AAMAppendDiagnostic([NSString stringWithFormat:@"loaded build=status-log-2 in %@ sessionClass=%@", + AAMAppendDiagnostic([NSString stringWithFormat:@"loaded build=native-final in %@ sessionClass=%@", NSBundle.mainBundle.bundleIdentifier ?: @"(unknown)", NSStringFromClass(sessionClass)]); [probeSession invalidateAndCancel]; diff --git a/Repo/control b/Repo/control index dd16df4..71ee32b 100644 --- a/Repo/control +++ b/Repo/control @@ -1,6 +1,6 @@ Package: com.rileytestut.altdaemon Name: AltDaemon Modern -Version: 1.2.0 +Version: 1.2.2 Architecture: iphoneos-arm64 Depends: ellekit (>= 1.2) Description: Reliable rootless AltDaemon with current Apple login compatibility.