2019-10-07 21:54:53 +02:00
|
|
|
#!/usr/bin/env python
|
|
|
|
from Scripts import *
|
|
|
|
import os, datetime, shutil, time, sys, argparse
|
|
|
|
|
|
|
|
# Using the techniques outlined by wolfmannight here: https://www.insanelymac.com/forum/topic/338810-create-legit-copy-of-macos-from-apple-catalog/
|
|
|
|
|
|
|
|
class buildMacOSInstallApp:
|
|
|
|
def __init__(self):
|
|
|
|
self.r = run.Run()
|
|
|
|
self.u = utils.Utils("Build macOS Install App")
|
|
|
|
self.target_files = [
|
|
|
|
"BaseSystem.dmg",
|
|
|
|
"BaseSystem.chunklist",
|
|
|
|
"InstallESDDmg.pkg",
|
2019-10-07 22:29:01 +02:00
|
|
|
"InstallInfo.plist",
|
2019-10-07 21:54:53 +02:00
|
|
|
"AppleDiagnostics.dmg",
|
|
|
|
"AppleDiagnostics.chunklist"
|
|
|
|
]
|
|
|
|
# Verify we're on macOS - this doesn't work anywhere else
|
|
|
|
if not sys.platform == "darwin":
|
|
|
|
self.u.head("WARNING")
|
|
|
|
print("")
|
|
|
|
print("This script only runs on macOS!")
|
|
|
|
print("")
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
def mount_dmg(self, dmg, no_browse = False):
|
|
|
|
# Mounts the passed dmg and returns the mount point(s)
|
|
|
|
args = ["/usr/bin/hdiutil", "attach", dmg, "-plist", "-noverify"]
|
|
|
|
if no_browse:
|
|
|
|
args.append("-nobrowse")
|
|
|
|
out = self.r.run({"args":args})
|
|
|
|
if out[2] != 0:
|
|
|
|
# Failed!
|
|
|
|
raise Exception("Mount Failed!", "{} failed to mount:\n\n{}".format(os.path.basename(dmg), out[1]))
|
|
|
|
# Get the plist data returned, and locate the mount points
|
|
|
|
try:
|
|
|
|
plist_data = plist.loads(out[0])
|
|
|
|
mounts = [x["mount-point"] for x in plist_data.get("system-entities", []) if "mount-point" in x]
|
|
|
|
return mounts
|
|
|
|
except:
|
|
|
|
raise Exception("Mount Failed!", "No mount points returned from {}".format(os.path.basename(dmg)))
|
|
|
|
|
|
|
|
def unmount_dmg(self, mount_point):
|
|
|
|
# Unmounts the passed dmg or mount point - retries with force if failed
|
|
|
|
# Can take either a single point or a list
|
|
|
|
if not type(mount_point) is list:
|
|
|
|
mount_point = [mount_point]
|
|
|
|
unmounted = []
|
|
|
|
for m in mount_point:
|
|
|
|
args = ["/usr/bin/hdiutil", "detach", m]
|
|
|
|
out = self.r.run({"args":args})
|
|
|
|
if out[2] != 0:
|
|
|
|
# Polite failed, let's crush this b!
|
|
|
|
args.append("-force")
|
|
|
|
out = self.r.run({"args":args})
|
|
|
|
if out[2] != 0:
|
|
|
|
# Oh... failed again... onto the next...
|
|
|
|
print(out[1])
|
|
|
|
continue
|
|
|
|
unmounted.append(m)
|
|
|
|
return unmounted
|
|
|
|
|
|
|
|
def main(self):
|
|
|
|
while True:
|
|
|
|
self.u.head()
|
|
|
|
print("")
|
|
|
|
print("Q. Quit")
|
|
|
|
print("")
|
|
|
|
fold = self.u.grab("Please drag and drop the output folder from gibMacOS here: ")
|
|
|
|
print("")
|
|
|
|
if fold.lower() == "q":
|
|
|
|
self.u.custom_quit()
|
|
|
|
f_path = self.u.check_path(fold)
|
|
|
|
if not f_path:
|
|
|
|
print("That path does not exist!\n")
|
|
|
|
self.u.grab("Press [enter] to return...")
|
|
|
|
continue
|
|
|
|
# Let's check if it's a folder. If not, make the next directory up the target
|
|
|
|
if not os.path.isdir(f_path):
|
|
|
|
f_path = os.path.dirname(os.path.realpath(f_path))
|
|
|
|
# Walk the contents of f_path and ensure we have all the needed files
|
|
|
|
lower_contents = [y.lower() for y in os.listdir(f_path)]
|
2023-12-15 01:09:02 +01:00
|
|
|
# Check if we got an InstallAssistant.pkg - and if so, just open that
|
|
|
|
if "installassistant.pkg" in lower_contents:
|
|
|
|
self.u.head("InstallAssistant.pkg Found")
|
|
|
|
print("")
|
|
|
|
print("Located InstallAssistant.pkg in the passed folder.\n")
|
|
|
|
print("As of macOS Big Sur (11.x), Apple changed how they distribute the OS files in")
|
|
|
|
print("the software update catalog.\n")
|
|
|
|
print("Double clicking the InstallAssistant.pkg will open it in Installer, which will")
|
|
|
|
print("copy the Install macOS [version].app to your /Applications folder.\n")
|
|
|
|
print("Opening InstallAssistant.pkg...")
|
|
|
|
self.r.run({"args":["open",os.path.join(f_path,"InstallAssistant.pkg")]})
|
|
|
|
print("")
|
|
|
|
self.u.grab("Press [enter] to return...")
|
|
|
|
continue
|
2019-10-07 21:54:53 +02:00
|
|
|
missing_list = [x for x in self.target_files if not x.lower() in lower_contents]
|
|
|
|
if len(missing_list):
|
|
|
|
self.u.head("Missing Required Files")
|
|
|
|
print("")
|
|
|
|
print("That folder is missing the following required files:")
|
|
|
|
print(", ".join(missing_list))
|
|
|
|
print("")
|
|
|
|
self.u.grab("Press [enter] to return...")
|
|
|
|
# Time to build the installer!
|
|
|
|
cwd = os.getcwd()
|
|
|
|
os.chdir(f_path)
|
|
|
|
base_mounts = []
|
|
|
|
try:
|
|
|
|
self.u.head("Building Installer")
|
|
|
|
print("")
|
|
|
|
print("Taking ownership of downloaded files...")
|
|
|
|
for x in self.target_files:
|
|
|
|
print(" - {}...".format(x))
|
|
|
|
self.r.run({"args":["chmod","a+x",x]})
|
|
|
|
print("Mounting BaseSystem.dmg...")
|
|
|
|
base_mounts = self.mount_dmg("BaseSystem.dmg")
|
|
|
|
if not len(base_mounts):
|
|
|
|
raise Exception("Mount Failed!", "No mount points were returned from BaseSystem.dmg")
|
|
|
|
base_mount = base_mounts[0] # Let's assume the first
|
|
|
|
print("Locating Installer app...")
|
|
|
|
install_app = next((x for x in os.listdir(base_mount) if os.path.isdir(os.path.join(base_mount,x)) and x.lower().endswith(".app") and not x.startswith(".")),None)
|
|
|
|
if not install_app:
|
|
|
|
raise Exception("Installer app not located in {}".format(base_mount))
|
|
|
|
print(" - Found {}".format(install_app))
|
|
|
|
# Copy the .app over
|
|
|
|
out = self.r.run({"args":["cp","-R",os.path.join(base_mount,install_app),os.path.join(f_path,install_app)]})
|
|
|
|
if out[2] != 0:
|
|
|
|
raise Exception("Copy Failed!", out[1])
|
|
|
|
print("Unmounting BaseSystem.dmg...")
|
|
|
|
for x in base_mounts:
|
|
|
|
self.unmount_dmg(x)
|
|
|
|
base_mounts = []
|
|
|
|
shared_support = os.path.join(f_path,install_app,"Contents","SharedSupport")
|
|
|
|
if not os.path.exists(shared_support):
|
|
|
|
print("Creating SharedSupport directory...")
|
|
|
|
os.makedirs(shared_support)
|
|
|
|
print("Copying files to SharedSupport...")
|
|
|
|
for x in self.target_files:
|
|
|
|
y = "InstallESD.dmg" if x.lower() == "installesddmg.pkg" else x # InstallESDDmg.pkg gets renamed to InstallESD.dmg - all others stay the same
|
|
|
|
print(" - {}{}".format(x, " --> {}".format(y) if y != x else ""))
|
|
|
|
out = self.r.run({"args":["cp","-R",os.path.join(f_path,x),os.path.join(shared_support,y)]})
|
|
|
|
if out[2] != 0:
|
|
|
|
raise Exception("Copy Failed!", out[1])
|
2019-10-07 22:29:01 +02:00
|
|
|
print("Patching InstallInfo.plist...")
|
|
|
|
with open(os.path.join(shared_support,"InstallInfo.plist"),"rb") as f:
|
|
|
|
p = plist.load(f)
|
|
|
|
if "Payload Image Info" in p:
|
|
|
|
pii = p["Payload Image Info"]
|
|
|
|
if "URL" in pii: pii["URL"] = pii["URL"].replace("InstallESDDmg.pkg","InstallESD.dmg")
|
|
|
|
if "id" in pii: pii["id"] = pii["id"].replace("com.apple.pkg.InstallESDDmg","com.apple.dmg.InstallESD")
|
|
|
|
pii.pop("chunklistURL",None)
|
|
|
|
pii.pop("chunklistid",None)
|
|
|
|
with open(os.path.join(shared_support,"InstallInfo.plist"),"wb") as f:
|
|
|
|
plist.dump(p,f)
|
2019-10-07 21:54:53 +02:00
|
|
|
print("")
|
|
|
|
print("Created: {}".format(install_app))
|
|
|
|
print("Saved to: {}".format(os.path.join(f_path,install_app)))
|
|
|
|
print("")
|
|
|
|
self.u.grab("Press [enter] to return...")
|
|
|
|
except Exception as e:
|
|
|
|
print("An error occurred:")
|
|
|
|
print(" - {}".format(e))
|
|
|
|
print("")
|
|
|
|
if len(base_mounts):
|
|
|
|
for x in base_mounts:
|
|
|
|
print(" - Unmounting {}...".format(x))
|
|
|
|
self.unmount_dmg(x)
|
|
|
|
print("")
|
|
|
|
self.u.grab("Press [enter] to return...")
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
b = buildMacOSInstallApp()
|
2019-10-07 22:29:01 +02:00
|
|
|
b.main()
|