3 # Download Apple sources and prepare them for OpenEmbedded.
5 # You need the following on your system to run this script:
10 # sudo (to mount HFS+ images)
23 from BeautifulSoup
import BeautifulSoup
26 import xml
.etree
.cElementTree
as ET
28 import cElementTree
as ET
30 DOWNLOAD_DIR
= "downloads"
36 class CustomCookieHandler(urllib2
.BaseHandler
):
38 Custom handler for cookies, as for some reason HTTPCookieProcessor
39 has issues parsing the cookies received from Apple.
42 self
.cookiejar
= Cookie
.SimpleCookie()
44 def http_request(self
, request
):
47 for key
in self
.cookiejar
:
48 attrs
.append( key
+ "=" + self
.cookiejar
[key
].value
)
50 if not request
.has_header("Cookie"):
51 request
.add_unredirected_header(
52 "Cookie", "; ".join(attrs
))
55 def http_response(self
, request
, response
):
57 cookie
= response
.info().getheader('set-cookie')
59 self
.cookiejar
.load(cookie
)
63 https_request
= http_request
64 https_response
= http_response
68 Convert an XML element into its python representation.
70 if e
.tag
== "integer":
72 elif e
.tag
== "string":
74 elif e
.tag
== "array":
75 return [ plist_value(c
) for c
in e
.getchildren() ]
79 for c
in e
.getchildren():
83 val
[key
] = plist_value(c
)
86 raise Exception("Could not parse dict entry %s" % e
)
88 def plist_to_hash(plist_string
):
90 Convert the contents of an Apple .plist file to a hash.
92 return plist_value(ET
.fromstring(plist_string
).find('dict'))
94 def extract_pkg(pkg
, dest
):
95 print "Extracting package %s to %s" % (pkg
, dest
)
96 if os
.system("cd %s && xar -xf %s Payload && zcat Payload | cpio -id && rm Payload" % (dest
, os
.path
.abspath(pkg
))):
97 raise Exception("Could not extract package")
99 def mount_dmg(dmg
, mnt
):
101 Convert a DMG image to an HFS+ image then mount it.
103 NOTE: requires sudo access
105 img
= os
.path
.join(TEMP_DIR
, os
.path
.basename(dmg
).replace(".dmg", ".img"))
107 # Check image is not mounted
108 if not os
.path
.exists(mnt
):
112 if not os
.path
.exists(img
):
113 print "Converting image %s to %s " % (dmg
, img
)
114 if os
.system("%s -i %s -o %s" % (os
.path
.join(TOOLS_DIR
, "dmg2img"), dmg
, img
)):
115 raise Exception("Could not convert image")
118 print "Mounting image %s on %s" % (img
, mnt
)
119 if os
.system("sudo mount -t hfsplus -o loop %s %s" % (img
, mnt
)):
120 raise Exception("Could not mount image")
124 def umount_dmg(dmg
, mnt
):
128 NOTE: requires sudo access
130 img
= os
.path
.join(TEMP_DIR
, os
.path
.basename(dmg
).replace(".dmg", ".img"))
131 print "Unmounting %s" % (mnt
)
132 if os
.system("sudo umount %s" % mnt
):
133 raise Exception("Could not unmount image")
137 return page
.info().getheader('content-type').split(';')[0]
139 def request(url
, data
=None):
141 Wrapper around urllibs2.urlopen to handle Apple's authentication form.
143 page
= urllib2
.urlopen(url
, data
)
145 if page
.geturl() != url
:
146 # check for connect form
147 soup
= BeautifulSoup(page
)
148 form
= soup
.find("form", {"name": "appleConnectForm"})
150 login_url
= urlparse
.urljoin(page
.geturl(), form
['action'])
153 print "* Logging into %s" % login_url
154 page
= urllib2
.urlopen(urllib2
.Request(login_url
, data
=urllib
.urlencode({
155 'theAccountName': apple_id
,
156 'theAccountPW': apple_password
,
159 '1.Continue.y': '1'}),
160 headers
={'Referer': page
.geturl()}))
163 while pagetype(page
) == "text/html":
164 soup
= BeautifulSoup(page
)
165 meta
= soup
.find('meta', {'http-equiv': 'REFRESH'})
169 refresh_url
= meta
['content'].split(';',1)[1].split("=",1)[1]
170 print "* Following refresh to %s" % refresh_url
171 page
= urllib2
.urlopen(urllib2
.Request(refresh_url
, data
=data
, headers
={'Referer': page
.geturl()}))
175 def download(url
, dest
):
177 Download a file to a directory.
179 tempname
= os
.path
.join(TEMP_DIR
, os
.path
.basename(url
)) + ".part"
180 filename
= os
.path
.join(dest
, os
.path
.basename(url
))
181 if os
.path
.exists(filename
):
182 print "* Not downloading %s" % url
185 print "* Downloading %s" % url
187 size
= page
.info().getheader('content-length')
188 if pagetype(page
) == "text/html":
189 raise Exception("We got an HTML page, download failed")
192 output
= open(tempname
, 'wb')
193 sys
.stdout
.write("\r* Receiving..")
196 data
= page
.read(100000)
200 sys
.stdout
.write("\r* Received %i bytes (%f %%)" % (done
, float(done
* 100) / float(size
)))
202 sys
.stdout
.write("\r* Received %i bytes (unknown)" % done
)
206 sys
.stdout
.write("\n")
210 shutil
.move(tempname
, filename
)
213 def build_tools(url
, binaries
):
215 Build DMG handling tools.
217 print "[ Building tools ]"
220 tool
= os
.path
.join(TOOLS_DIR
, bin
)
221 if not os
.path
.exists(tool
):
222 print "* Tool %s is missing" % bin
226 print "* Tools already built"
229 # download and extract tools
230 workdir
= os
.path
.join(TEMP_DIR
, "tools")
231 if os
.path
.exists(workdir
):
232 shutil
.rmtree(workdir
)
234 tarball
= download(url
, TEMP_DIR
)
235 if os
.system("tar -C %s --strip-components=1 -xf %s" % (workdir
, tarball
)):
236 raise Exception("Could not extract tarball")
239 if os
.system("make -C %s" % workdir
):
240 raise Exception("Could not build tools")
242 shutil
.move(os
.path
.join(workdir
, bin
), os
.path
.join(TOOLS_DIR
, bin
))
245 shutil
.rmtree(workdir
)
248 def get_darwin_sources(urls
):
250 Download the source .tar.gz of Darwin opensource components.
252 print "[ Darwin sources ]"
254 download(url
, OUTPUT_DIR
)
256 def get_firmware_key(firmware_version
, firmware_build
):
258 Return the key for a given firmware by parsing the iPhone wiki.
260 page
= urllib2
.urlopen('http://www.theiphonewiki.com/wiki/index.php?title=VFDecrypt_Keys:_2.x')
261 soup
= BeautifulSoup(page
)
263 for title
in soup
.findAll('span', {'class': 'mw-headline'}):
264 v
= title
.contents
[0].strip()
265 if v
.startswith(firmware_version
) and v
.count("(Build %s)" % firmware_build
):
266 p
= title
.parent
.findNextSibling('p')
267 return p
.contents
[0].strip().upper()
271 def generate_iphone_rootfs(url
, exclude
):
273 Generate a .tar.bz2 for the iPhone rootfs.
275 print "[ iPhone rootfs ]"
276 ipsw
= download(url
, DOWNLOAD_DIR
)
279 plist
= plist_to_hash(subprocess
.Popen(["unzip", "-p", ipsw
, "Restore.plist"], stdout
=subprocess
.PIPE
).communicate()[0])
281 tarname
= "iphone-rootfs-%s" % plist
['ProductVersion']
282 workdir
= os
.path
.join(TEMP_DIR
, tarname
)
283 tarball
= os
.path
.join(OUTPUT_DIR
, tarname
+ ".tar.bz2")
284 tartemp
= os
.path
.join(TEMP_DIR
, tarname
+ ".tar.bz2")
285 imagename
= plist
['SystemRestoreImages']['User']
286 image
= os
.path
.join(TEMP_DIR
, imagename
)
288 if os
.path
.exists(tarball
):
289 print "Skipping %s" % tarball
292 for i
in ['ProductType', 'ProductVersion', 'ProductBuildVersion']:
293 print "\t%s: %s" % (i
, plist
[i
])
296 print "Fetching decryption key for %s" % image
297 key
= get_firmware_key(plist
['ProductVersion'], plist
['ProductBuildVersion'])
299 raise Exception("Could not get decryption key for firmware")
300 print "Got decryption key %s" % key
303 if not os
.path
.exists(image
):
305 print "Extracting %s" % image
306 os
.system("unzip %s %s -d %s" % (ipsw
, imagename
, TEMP_DIR
))
309 temp
= image
+ ".decrypted"
310 if os
.system("%s -k %s -i %s -o %s" % (os
.path
.join(TOOLS_DIR
, "vfdecrypt"), key
, image
, temp
)):
311 raise Exception("Could not decrypt image")
313 os
.rename(temp
, image
)
317 mount_dmg(image
, workdir
)
319 print "Generating %s" % tarball
320 excludes
= " ".join([ "--exclude %s/%s" % (tarname
, x
) for x
in exclude
])
321 if os
.system("tar %s --hard-dereference -C %s -cjf %s %s" % (excludes
, TEMP_DIR
, tartemp
, tarname
)):
322 raise Exception("Could not create tarball")
323 shutil
.move(tartemp
, tarball
)
325 umount_dmg(image
, workdir
)
330 def generate_iphone_sdk(url
, iphone_version
, macosx_version
):
332 Generate a .tar.bz2 for the iPhone SDKs.
334 print "[ iPhone SDKs ]"
335 #request('http://developer.apple.com/iphone/login.action')
336 image
= download(url
, DOWNLOAD_DIR
)
338 tarname
= "iphone-sdks-%s" % iphone_version
339 workdir
= os
.path
.join(TEMP_DIR
, tarname
)
340 tarball
= os
.path
.join(OUTPUT_DIR
, tarname
+ ".tar.bz2")
341 tartemp
= os
.path
.join(TEMP_DIR
, tarname
+ ".tar.bz2")
343 if os
.path
.exists(tarball
):
344 print "Skipping %s" % tarball
348 if os
.path
.exists(workdir
):
349 shutil
.rmtree(workdir
)
352 mount_dmg(image
, MOUNT_DIR
)
355 # Extract iPhone stuff
356 sdk
= "iPhoneOS%s.sdk" % iphone_version
357 tmpdir
= os
.path
.join(TEMP_DIR
, sdk
)
359 extract_pkg("%s/Packages/iPhoneSDKHeadersAndLibs.pkg" % MOUNT_DIR
, tmpdir
)
360 shutil
.move("%s/Platforms/iPhoneOS.platform/Developer/SDKs/%s" % (tmpdir
, sdk
), os
.path
.join(workdir
, sdk
))
361 shutil
.rmtree(tmpdir
)
363 # Extract MacOS stuff
364 sdk
= "MacOSX%s.sdk" % macosx_version
365 tmpdir
= os
.path
.join(TEMP_DIR
, sdk
)
367 extract_pkg("%s/Packages/MacOSX%s.pkg" % (MOUNT_DIR
, macosx_version
), tmpdir
)
368 shutil
.move("%s/SDKs/%s" % (tmpdir
, sdk
), os
.path
.join(workdir
, sdk
))
369 shutil
.rmtree(tmpdir
)
372 umount_dmg(image
, MOUNT_DIR
)
374 print "Generating %s" % tarball
375 if os
.system("tar -C %s -cjf %s %s" % (TEMP_DIR
, tartemp
, tarname
)):
376 raise Exception("Could not create tarball")
377 shutil
.move(tartemp
, tarball
)
378 shutil
.rmtree(workdir
)
382 if __name__
== "__main__":
384 if len(sys
.argv
) < 4:
385 print """Usage: iphone-sources <manifest> <apple_id> <apple_password>"""
389 # Initialise directories
390 for d
in [DOWNLOAD_DIR
, OUTPUT_DIR
, MOUNT_DIR
, TEMP_DIR
, TOOLS_DIR
]:
391 if not os
.path
.exists(d
):
392 print "Creating %s" % d
396 manifest
= eval(file(sys
.argv
[1]).read())
397 apple_id
= sys
.argv
[2]
398 apple_password
= sys
.argv
[3]
401 opener
= urllib2
.build_opener(CustomCookieHandler())
402 urllib2
.install_opener(opener
)
404 # Perform all build steps
405 build_tools(manifest
['tools']['url'], manifest
['tools']['binaries'])
406 get_darwin_sources(manifest
['sources'])
407 generate_iphone_rootfs(manifest
['firmware']['url'], manifest
['firmware']['exclude'])
408 generate_iphone_sdk(manifest
['sdk']['url'], manifest
['firmware']['version'], manifest
['macosx']['version'])
411 for d
in [MOUNT_DIR
, TEMP_DIR
]: