Restructure crates and implement Windows demo
Dadoum

Dadoum commited on 2026-01-30 16:17:23
Showing 28 changed files, with 166 additions and 83 deletions.

... ...
@@ -170,34 +170,6 @@ version = "1.0.100"
170 170
 source = "registry+https://github.com/rust-lang/crates.io-index"
171 171
 checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
172 172
 
173
-[[package]]
174
-name = "apple-account"
175
-version = "0.1.0"
176
-dependencies = [
177
- "adi",
178
- "aes",
179
- "aes-gcm",
180
- "base64 0.22.1",
181
- "bytes",
182
- "cbc",
183
- "chrono",
184
- "hex",
185
- "hmac",
186
- "iana-time-zone",
187
- "log",
188
- "pbkdf2",
189
- "plist",
190
- "plist-macros",
191
- "rand",
192
- "reqwest 0.13.1",
193
- "serde",
194
- "sha2",
195
- "srp",
196
- "sys-locale",
197
- "thiserror 2.0.17",
198
- "uuid",
199
-]
200
-
201 173
 [[package]]
202 174
 name = "apple-codesign"
203 175
 version = "0.1.0"
... ...
@@ -1108,6 +1080,34 @@ dependencies = [
1108 1080
  "scroll",
1109 1081
 ]
1110 1082
 
1083
+[[package]]
1084
+name = "grandslam"
1085
+version = "0.1.0"
1086
+dependencies = [
1087
+ "adi",
1088
+ "aes",
1089
+ "aes-gcm",
1090
+ "base64 0.22.1",
1091
+ "bytes",
1092
+ "cbc",
1093
+ "chrono",
1094
+ "hex",
1095
+ "hmac",
1096
+ "iana-time-zone",
1097
+ "log",
1098
+ "pbkdf2",
1099
+ "plist",
1100
+ "plist-macros",
1101
+ "rand",
1102
+ "reqwest 0.13.1",
1103
+ "serde",
1104
+ "sha2",
1105
+ "srp",
1106
+ "sys-locale",
1107
+ "thiserror 2.0.17",
1108
+ "uuid",
1109
+]
1110
+
1111 1111
 [[package]]
1112 1112
 name = "h2"
1113 1113
 version = "0.3.27"
... ...
@@ -1630,6 +1630,24 @@ version = "1.0.15"
1630 1630
 source = "registry+https://github.com/rust-lang/crates.io-index"
1631 1631
 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1632 1632
 
1633
+[[package]]
1634
+name = "itunes"
1635
+version = "0.1.0"
1636
+dependencies = [
1637
+ "adi",
1638
+ "base64 0.22.1",
1639
+ "bytes",
1640
+ "grandslam",
1641
+ "hex",
1642
+ "log",
1643
+ "plist",
1644
+ "plist-macros",
1645
+ "rand",
1646
+ "reqwest 0.13.1",
1647
+ "thiserror 2.0.17",
1648
+ "uuid",
1649
+]
1650
+
1633 1651
 [[package]]
1634 1652
 name = "jiff"
1635 1653
 version = "0.2.15"
... ...
@@ -2859,14 +2877,16 @@ dependencies = [
2859 2877
  "adi",
2860 2878
  "adid-proxy",
2861 2879
  "android-coreadi",
2862
- "apple-account",
2863 2880
  "base64 0.22.1",
2864 2881
  "dlopen2",
2865 2882
  "elf-loader",
2866 2883
  "env_logger 0.11.8",
2884
+ "grandslam",
2885
+ "itunes",
2867 2886
  "libc",
2868 2887
  "library-coreadi",
2869 2888
  "reqwest 0.13.1",
2889
+ "serde",
2870 2890
  "tokio",
2871 2891
  "xcode",
2872 2892
 ]
... ...
@@ -4091,7 +4111,7 @@ name = "xcode"
4091 4111
 version = "0.1.0"
4092 4112
 dependencies = [
4093 4113
  "adi",
4094
- "apple-account",
4114
+ "grandslam",
4095 4115
  "plist",
4096 4116
  "plist-macros",
4097 4117
  "reqwest 0.13.1",
... ...
@@ -1,13 +1,14 @@
1 1
 [workspace]
2 2
 resolver = "2"
3
-members = ["adi", "adid-proxy", "android-coreadi", "apple-account", "apple-codesign-quick", "elf-loader", "fairplay", "library-coreadi", "plist-macros", "purple-builder", "super-sideloader", "xcode"]
3
+members = ["adi", "adid-proxy", "android-coreadi", "grandslam", "apple-codesign-quick", "elf-loader", "fairplay", "itunes", "library-coreadi", "plist-macros", "purple-builder", "super-sideloader", "xcode"]
4 4
 
5 5
 [workspace.dependencies]
6 6
 adi = { path = "adi" }
7 7
 adid-proxy = { path = "adid-proxy" }
8 8
 android-coreadi = { path = "android-coreadi" }
9
-apple-account = { path = "apple-account" }
10 9
 elf-loader = { path = "elf-loader" }
10
+grandslam = { path = "grandslam" }
11
+itunes = { path = "itunes" }
11 12
 library-coreadi = { path = "library-coreadi" }
12 13
 plist-macros = { path = "plist-macros" }
13 14
 xcode = { path = "xcode" }
... ...
@@ -22,15 +22,22 @@ Create an `adi::ADIProxy` on a Darwin system that can connect to the `adid` daem
22 22
 Create an `adi::ADIProxy` on any Unix system (TODO: add Windows support) from
23 23
 any `libCoreADI.so` distribution supporting your architecture.
24 24
 
25
-### apple-account (unfinished yet)
25
+### library-coreadi
26
+
27
+Create an `adi::ADIProxy` given a distribution of CoreADI for your system. 
28
+
29
+This includes Intel Macs (using an old iTunes' binary), Windows 
30
+(with iCloud, iTunes, or any other MS Store Apple application), and Android 
31
+(with Apple Music for Android's binaries, or Apple TV for Android's) [non-exhaustive list].
32
+
33
+### grandslam
26 34
 
27 35
 A fully-featured, type-safe, well-designed Apple server API wrapper.
28 36
 
29 37
 You can log into an Apple account via GSA APIs with any ADIProxy, fetch the tokens,
30 38
 while handling every error correctly, including Apple's server errors.
31 39
 
32
-(currently, the Grandslam part lacks the code to do 2FA without a browser,
33
-and the iTunes connection part is not yet implemented
40
+(currently, the Grandslam part lacks the code to do 2FA without a browser)
34 41
 
35 42
 ### apple-codesign(-quick) (unfinished yet)
36 43
 
... ...
@@ -49,7 +56,7 @@ I will add feature as needed for the other packages
49 56
 ### fairplay (unfinished yet)
50 57
 
51 58
 Abstracting CoreFP for use on any system, a bit like `adi`, will be used in 
52
-`apple-account` iTunes part.
59
+`itunes` part.
53 60
 
54 61
 ### plist-macros
55 62
 
... ...
@@ -67,7 +74,7 @@ That Sideloader rewrite in Rust I postponed. Currently, it's full of apple-accou
67 74
 
68 75
 but I did not start to work on it :p
69 76
 
70
-## Okay but why have you rewrote everything?
77
+## Okay but why have you rewritten everything?
71 78
 
72 79
 I wasn't satisfied with what was already available :p.
73 80
 
... ...
@@ -283,7 +283,7 @@ pub mod android {
283 283
         #[serde(serialize_with = "serialize_ptr")]
284 284
         pub provisioning_path: *const std::ffi::c_char,
285 285
     }
286
-    // */
286
+
287 287
     impl ADIMessage for SetAndroidIDMessage {
288 288
         const MAGIC: u32 = 0xc774d292;
289 289
         type ReturnType = i32;
... ...
@@ -199,6 +199,7 @@ pub struct ADIPayload<Message: ADIMessage> {
199 199
     pub payload: Message,
200 200
 }
201 201
 
202
+#[must_use = "Must be freed using its proxy."]
202 203
 pub struct ADIBuffer<'lt, T>(pub Option<&'lt [T]>);
203 204
 
204 205
 impl<'lt, T> Deref for ADIBuffer<'lt, T> {
... ...
@@ -212,6 +213,7 @@ impl<'lt, T> Deref for ADIBuffer<'lt, T> {
212 213
     }
213 214
 }
214 215
 
216
+#[must_use = "Must be destroyed or finished using its proxy."]
215 217
 pub struct ADIProvisioningSession<'lt> {
216 218
     pub val: u32,
217 219
     pub proxy: PhantomData<&'lt dyn ADIProxy>,
... ...
@@ -219,14 +221,17 @@ pub struct ADIProvisioningSession<'lt> {
219 221
 
220 222
 impl<T> Drop for ADIBuffer<'_, T> {
221 223
     fn drop(&mut self) {
224
+        if !std::thread::panicking() {
222 225
             // it should never be called :(
223 226
             // in D we could have added a static assert here giving a compiling error and preventing misuse.
224 227
             panic!("The ADIBuffer {self:p} has not been disposed properly.");
225 228
         }
226 229
     }
230
+}
227 231
 
228 232
 impl Drop for ADIProvisioningSession<'_> {
229 233
     fn drop(&mut self) {
234
+        if !std::thread::panicking() {
230 235
             // it should never be called :(
231 236
             // in D we could have added a static assert here giving a compiling error and preventing misuse.
232 237
             panic!(
... ...
@@ -235,6 +240,7 @@ impl Drop for ADIProvisioningSession<'_> {
235 240
             );
236 241
         }
237 242
     }
243
+}
238 244
 
239 245
 #[derive(Debug, Clone)]
240 246
 pub struct ADIAccount {
... ...
@@ -127,10 +127,6 @@ impl<T: Deref<Target = [u8]>> AndroidCoreADIProxy<T> {
127 127
             .try_build()?,
128 128
         );
129 129
 
130
-        core_adi
131
-            .initialize()
132
-            .map_err(AndroidCoreADILoadingError::ADIError)?;
133
-
134 130
         Ok(core_adi)
135 131
     }
136 132
 }
... ...
@@ -1,5 +1,5 @@
1 1
 [package]
2
-name = "apple-account"
2
+name = "grandslam"
3 3
 version = "0.1.0"
4 4
 edition = "2024"
5 5
 
... ...
@@ -475,16 +475,12 @@ pub async fn login(
475 475
         StatusCode::UrlSwitchingRequired => {
476 476
             let url_switching_data = status
477 477
                 .get("X-Apple-I-Data")
478
-                .and_then(|data| data.as_string());
478
+                .and_then(|data| data.as_string())
479
+                .ok_or(AuthError::Structure(response_plist.clone()))?;
479 480
 
480
-            match url_switching_data {
481
-                Some(url_switching_data) => Ok(AuthOutcome::UrlSwitchingRequired(
481
+            Ok(AuthOutcome::UrlSwitchingRequired(
482 482
                 url_switching_data.to_string(),
483
-                )),
484
-                None => {
485
-                    panic!("URL Switching has been required but no data has been given for it.")
486
-                }
487
-            }
483
+            ))
488 484
         }
489 485
     }
490 486
 }
... ...
@@ -106,7 +106,7 @@ impl<'lt> BasicHTTPSession<'lt> {
106 106
         client_bundle_info: BundleInformation<'lt>,
107 107
         custom_certificate: Option<Certificate>,
108 108
     ) -> Result<Self, HTTPSessionCreationError> {
109
-        let client_builder = Client::builder();
109
+        let client_builder = Client::builder().danger_accept_invalid_certs(true);
110 110
         let client_builder = match custom_certificate {
111 111
             Some(certificate) => client_builder.add_root_certificate(certificate),
112 112
             None => client_builder,
... ...
@@ -2,5 +2,6 @@ pub mod bundle_information;
2 2
 pub mod device;
3 3
 pub mod grandslam;
4 4
 pub mod http_session;
5
-pub mod itunes;
6 5
 pub mod plist_request;
6
+
7
+pub use grandslam::*;
... ...
@@ -0,0 +1,20 @@
1
+[package]
2
+name = "itunes"
3
+version = "0.1.0"
4
+edition = "2024"
5
+
6
+[dependencies]
7
+adi.workspace = true
8
+grandslam.workspace = true
9
+plist.workspace = true
10
+plist-macros.workspace = true
11
+
12
+reqwest.workspace = true
13
+thiserror.workspace = true
14
+
15
+base64 = "0.22.1"
16
+rand = "0.9.2"
17
+hex = "0.4.3"
18
+bytes = "1.10.1"
19
+log = "0.4.29"
20
+uuid = { version = "1.19.0", features = ["v4"] }
... ...
@@ -1,13 +1,13 @@
1
-use crate::bundle_information::BundleInformation;
2
-use crate::device::Device;
3
-use crate::http_session::{
4
-    AnisetteHTTPSession, AppleError, BasicHTTPSession, HTTPSession, HTTPSessionCreationError,
5
-    URLBagError,
6
-};
7 1
 use adi::proxy::ADIError;
8 2
 use base64::Engine;
9 3
 use base64::prelude::BASE64_STANDARD;
10 4
 use bytes::Bytes;
5
+use grandslam::bundle_information::BundleInformation;
6
+use grandslam::device::Device;
7
+use grandslam::http_session::{
8
+    AnisetteHTTPSession, AppleError, BasicHTTPSession, HTTPSession, HTTPSessionCreationError,
9
+    URLBagError,
10
+};
11 11
 use plist::{Dictionary, Value};
12 12
 use plist_macros::dict;
13 13
 use reqwest::Method;
... ...
@@ -42,7 +42,7 @@ pub async fn http_session<'lt>(
42 42
     HTTPSession::new(http_session, url_bag)
43 43
 }
44 44
 
45
-pub(crate) fn parse_url_bag_response(url_bag_response: Bytes) -> Result<Dictionary, URLBagError> {
45
+pub fn parse_url_bag_response(url_bag_response: Bytes) -> Result<Dictionary, URLBagError> {
46 46
     let url_bag_plist =
47 47
         plist::from_bytes::<Dictionary>(&url_bag_response).map_err(|_| URLBagError::Parsing)?;
48 48
 
... ...
@@ -0,0 +1,3 @@
1
+pub mod itunes;
2
+
3
+pub use itunes::*;
... ...
@@ -6,8 +6,9 @@ edition = "2021"
6 6
 [dependencies]
7 7
 adi.workspace = true
8 8
 android-coreadi.workspace = true
9
-apple-account.workspace = true
10 9
 elf-loader.workspace = true
10
+grandslam.workspace = true
11
+itunes.workspace = true
11 12
 xcode.workspace = true
12 13
 
13 14
 tokio.workspace = true
... ...
@@ -15,6 +16,7 @@ libc = "0.2.176"
15 16
 reqwest = "0.13.1"
16 17
 env_logger = "0.11.8"
17 18
 base64 = "0.22.1"
19
+serde = { version = "1.0.228", features = ["derive"] }
18 20
 
19 21
 [target.'cfg(target_os = "macos")'.dependencies]
20 22
 adid-proxy.workspace = true
... ...
@@ -1,13 +1,45 @@
1
+use adi::core_adi;
1 2
 use adi::proxy::ADIProxy;
2 3
 use android_coreadi::AndroidCoreADIProxy;
3
-use apple_account::bundle_information::APPLE_TV_BUNDLE_INFORMATION;
4
-use apple_account::device::Device;
5
-use apple_account::grandslam::{AuthOutcome, AuthenticatedHTTPSession};
6
-use apple_account::http_session::AnisetteHTTPSession;
7
-use apple_account::{grandslam, itunes};
4
+use grandslam::bundle_information::APPLE_TV_BUNDLE_INFORMATION;
5
+use grandslam::device::Device;
6
+use grandslam::http_session::AnisetteHTTPSession;
7
+use grandslam::{AuthOutcome, AuthenticatedHTTPSession};
8 8
 use std::{env, fs};
9 9
 use xcode::{ViewDeveloperAction, XcodeSession, XCODE_BUNDLE_INFORMATION, XCODE_TOKEN_IDENTIFIER};
10 10
 
11
+fn build_device() -> Device {
12
+    // Fake mac
13
+    if cfg!(target_os = "windows") {
14
+        // Windows anisette provisioning endpoints wants you to use an appropriate Windows Client-Info.
15
+        // Apple doesn't care about the fact you are running Xcode on Windows.
16
+        let operating_system = "Windows";
17
+        let system_version = "10.0.18362/SP0.0.18362";
18
+        let system_edition = "Win10 Pro";
19
+        let system_arch = "x64";
20
+
21
+        Device {
22
+            device_model: "PC".to_string(),
23
+            operating_system_information: format!(
24
+                "{}; {}; {}; {}",
25
+                operating_system, system_version, system_edition, system_arch
26
+            ),
27
+            // It shoudl look like a FairPlay GUID.
28
+            // It is built with a certain logic too, but IIRC it's basically hashes all the way.
29
+            // device_uuid: "00000000.00000000.00000000.00000000.00000000.00000000.00000000".to_string(),
30
+
31
+            // That one is not possible in real situations. But Apple also accepts it /shrug
32
+            device_uuid: "A8B31C86-359B-4D95-8950-BA5DD8FFC46F".to_string(),
33
+        }
34
+    } else {
35
+        Device {
36
+            device_model: "MacBookPro13,2".to_string(),
37
+            operating_system_information: "macOS;15.6.1;24G90".to_string(),
38
+            device_uuid: "A8B31C86-359B-4D95-8950-BA5DD8FFC46F".to_string(),
39
+        }
40
+    }
41
+}
42
+
11 43
 async fn grandslam_test(
12 44
     proxy: &dyn ADIProxy,
13 45
     apple_id: &str,
... ...
@@ -16,11 +48,7 @@ async fn grandslam_test(
16 48
     // proxy.set_android_id("0123456789012345")?;
17 49
     // proxy.set_provisioning_path(c"./adi_files")?;
18 50
 
19
-    let device = Device {
20
-        device_model: "MacBookPro13,2".to_string(),
21
-        operating_system_information: "macOS;15.6.1;24G90".to_string(),
22
-        device_uuid: "A8B31C86-359B-4D95-8950-BA5DD8FFC46F".to_string(),
23
-    };
51
+    let device = build_device();
24 52
 
25 53
     // println!("{:#02X?}", proxy.get_all_provisioned_accounts());
26 54
 
... ...
@@ -33,7 +61,7 @@ async fn grandslam_test(
33 61
         grandslam::provision(&http_session).await?;
34 62
     }
35 63
 
36
-    // Here we should fetch_auth_mode first to know if we should log-in with a password.
64
+    // Here we should fetch_auth_mode first to know if we should log in with a password.
37 65
     let auth_outcome = grandslam::login(&http_session, apple_id, password).await?;
38 66
 
39 67
     match &auth_outcome {
... ...
@@ -150,13 +178,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
150 178
     #[cfg(target_os = "windows")]
151 179
     let library = dlopen2::symbor::Library::open("C:\\Program Files\\iTunes\\CoreADI64.dll")?;
152 180
     #[cfg(target_os = "windows")]
181
+    let proxy = {
153 182
         let proxy = library_coreadi::LibraryCoreADIProxy::new(&library)?;
154 183
 
184
+        core_adi::CoreADIADIProxy::initialize(&proxy)?;
185
+
186
+        proxy
187
+    };
188
+
155 189
     #[cfg(not(any(target_os = "windows", target_os = "macos")))]
156 190
     let proxy = {
157 191
         let core_adi_data = fs::read("nodistrib/lib/x86_64/libCoreADI.so")?;
158 192
         let proxy = AndroidCoreADIProxy::load_library(core_adi_data)?;
159
-
193
+        core_adi::CoreADIADIProxy::initialize(&proxy)?;
160 194
         proxy.set_android_id("0123456789012345")?;
161 195
         proxy.set_provisioning_path(c"./adi_files")?;
162 196
 
... ...
@@ -4,8 +4,8 @@ version = "0.1.0"
4 4
 edition = "2024"
5 5
 
6 6
 [dependencies]
7
-apple-account.workspace = true
8 7
 adi.workspace = true
8
+grandslam.workspace = true
9 9
 plist.workspace = true
10 10
 plist-macros.workspace = true
11 11
 
... ...
@@ -1,16 +1,13 @@
1 1
 mod developer_actions;
2 2
 
3
-use adi::proxy::{ADIError, ADIResult};
4
-use apple_account::bundle_information::BundleInformation;
5
-use apple_account::grandslam::{
6
-    AppTokenIdentifier, AuthenticatedHTTPSession, GRANDSLAM_DSID, Token,
7
-};
8
-use apple_account::http_session::{AnisetteHTTPSession, HTTPSessionCreationError};
9
-use apple_account::plist_request::plist_to_body;
3
+use adi::proxy::{ADIError};
4
+use grandslam::bundle_information::BundleInformation;
5
+use grandslam::plist_request::plist_to_body;
6
+use grandslam::{AppTokenIdentifier, AuthenticatedHTTPSession, Token};
10 7
 use plist::{Dictionary, Value};
11 8
 use plist_macros::{array, dict};
12
-use reqwest::{Method, RequestBuilder};
13
-use serde::{Deserialize, Serialize};
9
+use reqwest::{Method};
10
+use serde::{Serialize};
14 11
 use std::fmt::Display;
15 12
 use thiserror::Error;
16 13
 
17 14