Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
corpnewt
GitHub Repository: corpnewt/gibMacOS
Path: blob/master/MakeInstall.py
174 views
1
from Scripts import utils, diskwin, downloader, run
2
import os, sys, tempfile, shutil, zipfile, platform, json, time
3
4
class WinUSB:
5
6
def __init__(self):
7
self.u = utils.Utils("MakeInstall")
8
if not self.u.check_admin():
9
# Try to self-elevate
10
self.u.elevate(os.path.realpath(__file__))
11
exit()
12
self.min_plat = 9600
13
# Make sure we're on windows
14
self.verify_os()
15
# Setup initial vars
16
self.d = diskwin.Disk()
17
self.dl = downloader.Downloader()
18
self.r = run.Run()
19
self.scripts = "Scripts"
20
self.s_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.scripts)
21
# self.dd_url = "http://www.chrysocome.net/downloads/ddrelease64.exe"
22
self.dd_url = "https://github.com/corpnewt/gibMacOS/files/4573241/ddrelease64.exe.zip" # Rehost due to download issues
23
self.dd_name = ".".join(os.path.basename(self.dd_url).split(".")[:-1]) # Get the name without the last extension
24
self.z_json = "https://sourceforge.net/projects/sevenzip/best_release.json"
25
self.z_url2 = "https://www.7-zip.org/a/7z1806-x64.msi"
26
self.z_url = "https://www.7-zip.org/a/7z[[vers]]-x64.msi"
27
self.z_name = "7z.exe"
28
self.bi_url = "https://raw.githubusercontent.com/corpnewt/gibMacOS/master/Scripts/BOOTICEx64.exe"
29
self.bi_name = "BOOTICEx64.exe"
30
self.clover_url = "https://api.github.com/repos/CloverHackyColor/CloverBootloader/releases"
31
self.dids_url = "https://api.github.com/repos/dids/clover-builder/releases"
32
self.oc_url = "https://api.github.com/repos/acidanthera/OpenCorePkg/releases"
33
self.oc_boot = "boot"
34
self.oc_boot_alt = "bootX64"
35
self.oc_boot0 = "boot0"
36
self.oc_boot1 = "boot1f32"
37
# self.oc_boot_url = "https://github.com/acidanthera/OpenCorePkg/raw/master/Utilities/LegacyBoot/"
38
self.oc_boot_url = "https://github.com/acidanthera/OpenCorePkg/raw/870017d0e5d53abeaf0347997da912c3e382a04a/Utilities/LegacyBoot/"
39
self.diskpart = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Windows", "System32", "diskpart.exe")
40
# From Tim Sutton's brigadier: https://github.com/timsutton/brigadier/blob/master/brigadier
41
self.z_path = None
42
self.z_path64 = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Program Files", "7-Zip", "7z.exe")
43
self.z_path32 = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Program Files (x86)", "7-Zip", "7z.exe")
44
self.recovery_suffixes = (
45
"recoveryhdupdate.pkg",
46
"recoveryhdmetadmg.pkg",
47
"basesystem.dmg",
48
"recoveryimage.dmg"
49
)
50
self.dd_bootsector = True
51
self.boot0 = "boot0af"
52
self.boot1 = "boot1f32alt"
53
self.boot = "boot6"
54
self.efi_id = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" # EFI
55
self.bas_id = "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" # Microsoft Basic Data
56
self.hfs_id = "48465300-0000-11AA-AA11-00306543ECAC" # HFS+
57
self.rec_id = "426F6F74-0000-11AA-AA11-00306543ECAC" # Apple Boot partition (Recovery HD)
58
self.show_all_disks = False
59
60
def verify_os(self):
61
self.u.head("Verifying OS")
62
print("")
63
print("Verifying OS name...")
64
if not os.name=="nt":
65
print("")
66
print("This script is only for Windows!")
67
print("")
68
self.u.grab("Press [enter] to exit...")
69
exit(1)
70
print(" - Name = NT")
71
print("Verifying OS version...")
72
# Verify we're at version 9600 or greater
73
try:
74
# Set plat to the last item of the output split by . - looks like:
75
# Windows-8.1-6.3.9600
76
# or this:
77
# Windows-10-10.0.17134-SP0
78
plat = int(platform.platform().split(".")[-1].split("-")[0])
79
except:
80
plat = 0
81
if plat < self.min_plat:
82
print("")
83
print("Currently running {}, this script requires version {} or newer.".format(platform.platform(), self.min_plat))
84
print("")
85
self.u.grab("Press [enter] to exit...")
86
exit(1)
87
print(" - Version = {}".format(plat))
88
print("")
89
print("{} >= {}, continuing...".format(plat, self.min_plat))
90
91
def get_disks_of_type(self, disk_list, disk_type=(0,2)):
92
disks = {}
93
for disk in disk_list:
94
if disk_list[disk].get("type",0) in disk_type:
95
disks[disk] = disk_list[disk]
96
return disks
97
98
def check_dd(self):
99
# Checks if ddrelease64.exe exists in our Scripts dir
100
# and if not - downloads it
101
#
102
# Returns True if exists/downloaded successfully
103
# or False if issues.
104
# Check for dd.exe in the current dir
105
if os.path.exists(os.path.join(self.s_path, self.dd_name)):
106
# print("Located {}!".format(self.dd_name))
107
# Got it
108
return True
109
print("Couldn't locate {} - downloading...".format(self.dd_name))
110
temp = tempfile.mkdtemp()
111
z_file = os.path.basename(self.dd_url)
112
# Now we need to download
113
self.dl.stream_to_file(self.dd_url, os.path.join(temp,z_file))
114
print(" - Extracting...")
115
# Extract with built-in tools \o/
116
cwd = os.getcwd()
117
os.chdir(temp)
118
with zipfile.ZipFile(os.path.join(temp,z_file)) as z:
119
z.extractall(temp)
120
for x in os.listdir(temp):
121
if self.dd_name.lower() == x.lower():
122
# Found it
123
print(" - Found {}".format(x))
124
print(" - Copying to {} directory...".format(self.scripts))
125
shutil.copy(os.path.join(temp,x), os.path.join(self.s_path,x))
126
# Return to prior cwd
127
os.chdir(cwd)
128
# Remove the temp folder
129
shutil.rmtree(temp,ignore_errors=True)
130
print("")
131
return os.path.exists(os.path.join(self.s_path, self.dd_name))
132
133
def check_7z(self):
134
# Check the PATH var first
135
z_path = self.r.run({"args":["where.exe","7z.exe"]})[0].split("\n")[0].rstrip("\r")
136
self.z_path = next((x for x in (z_path,self.z_path64,self.z_path32) if x and os.path.isfile(x)),None)
137
if self.z_path:
138
return True
139
print("Didn't locate {} - downloading...".format(self.z_name))
140
# Didn't find it - let's do some stupid stuff
141
# First we get our json response - or rather, try to, then parse it
142
# looking for the current version
143
dl_url = None
144
try:
145
json_data = json.loads(self.dl.get_string(self.z_json))
146
v_num = json_data.get("release",{}).get("filename","").split("/")[-1].lower().split("-")[0].replace("7z","").replace(".exe","")
147
if len(v_num):
148
dl_url = self.z_url.replace("[[vers]]",v_num)
149
except:
150
pass
151
if not dl_url:
152
dl_url = self.z_url2
153
temp = tempfile.mkdtemp()
154
dl_file = self.dl.stream_to_file(dl_url, os.path.join(temp, self.z_name))
155
if not dl_file: # Didn't download right
156
shutil.rmtree(temp,ignore_errors=True)
157
return False
158
print("")
159
print("Installing 7zip...")
160
# From Tim Sutton's brigadier: https://github.com/timsutton/brigadier/blob/master/brigadier
161
out = self.r.run({"args":["msiexec", "/qn", "/i", os.path.join(temp, self.z_name)],"stream":True})
162
if out[2] != 0:
163
shutil.rmtree(temp,ignore_errors=True)
164
print("Error ({})".format(out[2]))
165
print("")
166
self.u.grab("Press [enter] to exit...")
167
exit(1)
168
print("")
169
self.z_path = self.z_path64 if os.path.exists(self.z_path64) else self.z_path32 if os.path.exists(self.z_path32) else None
170
return self.z_path and os.path.exists(self.z_path)
171
172
def check_bi(self):
173
# Checks for BOOTICEx64.exe in our scripts dir
174
# and downloads it if need be
175
if os.path.exists(os.path.join(self.s_path, self.bi_name)):
176
# print("Located {}!".format(self.bi_name))
177
# Got it
178
return True
179
print("Couldn't locate {} - downloading...".format(self.bi_name))
180
self.dl.stream_to_file(self.bi_url, os.path.join(self.s_path, self.bi_name))
181
print("")
182
return os.path.exists(os.path.join(self.s_path,self.bi_name))
183
184
def get_dl_url_from_json(self,json_data,suffix=(".lzma",".iso.7z")):
185
try: j_list = json.loads(json_data)
186
except: return None
187
j_list = j_list if isinstance(j_list,list) else [j_list]
188
for j in j_list:
189
dl_link = next((x.get("browser_download_url", None) for x in j.get("assets", []) if x.get("browser_download_url", "").endswith(suffix)), None)
190
if dl_link: break
191
if not dl_link:
192
return None
193
return { "url" : dl_link, "name" : os.path.basename(dl_link), "info" : j.get("body", None) }
194
195
def get_dl_info(self,clover_version=None):
196
# Returns the latest download package and info in a
197
# dictionary: { "url" : dl_url, "name" : name, "info" : update_info }
198
# Attempt Dids' repo first - falling back on Clover's official repo as needed
199
clover_urls = (self.clover_url,self.dids_url)
200
try:
201
assert int(clover_version) <= 5122 # Check if we're trying to get r5122 or prior
202
# If we didn't throw an exception, we can reverse the order of the URLs to check
203
# Dids' builder first
204
clover_urls = (self.dids_url,self.clover_url)
205
except:
206
pass # Wasn't a proper int, or was above 5122
207
for url in clover_urls:
208
# Tag is e.g. 5098 on Slice's repo, and e.g. v2.5k_r5098 or v5.0_r5xxx on Dids'
209
# accommodate as needed
210
if not clover_version:
211
search_url = url
212
elif url == self.clover_url:
213
# Using CloverHackyColor's repo - set the tag to the version
214
search_url = "{}/tags/{}".format(url,clover_version)
215
else:
216
# Using Dids' clover builder - figure out the prefix based on the version
217
search_url = "{}/tags/v{}_r{}".format(url,"5.0" if clover_version >= "5118" else "2.5k",clover_version)
218
print(" - Checking {}".format(search_url))
219
json_data = self.dl.get_string(search_url, False)
220
if not json_data: print(" --> Not found!")
221
else: return self.get_dl_url_from_json(json_data)
222
return None
223
224
def get_oc_dl_info(self):
225
json_data = self.dl.get_string(self.oc_url, False)
226
if not json_data: print(" --> Not found!")
227
else: return self.get_dl_url_from_json(json_data,suffix="-RELEASE.zip")
228
229
def diskpart_flag(self, disk, as_efi=False):
230
# Sets and unsets the GUID needed for a GPT EFI partition ID
231
self.u.head("Changing ID With DiskPart")
232
print("")
233
print("Setting type as {}...".format("EFI" if as_efi else "Basic Data"))
234
print("")
235
# - EFI system partition: c12a7328-f81f-11d2-ba4b-00a0c93ec93b
236
# - Basic data partition: ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
237
dp_script = "\n".join([
238
"select disk {}".format(disk.get("index",-1)),
239
"sel part 1",
240
"set id={}".format(self.efi_id if as_efi else self.bas_id)
241
])
242
temp = tempfile.mkdtemp()
243
script = os.path.join(temp, "diskpart.txt")
244
try:
245
with open(script,"w") as f:
246
f.write(dp_script)
247
except:
248
shutil.rmtree(temp)
249
print("Error creating script!")
250
print("")
251
self.u.grab("Press [enter] to return...")
252
return
253
# Let's try to run it!
254
out = self.r.run({"args":[self.diskpart,"/s",script],"stream":True})
255
# Ditch our script regardless of whether diskpart worked or not
256
shutil.rmtree(temp)
257
print("")
258
if out[2] != 0:
259
# Error city!
260
print("DiskPart exited with non-zero status ({}). Aborting.".format(out[2]))
261
else:
262
print("Done - You may need to replug your drive for the")
263
print("changes to take effect.")
264
print("")
265
self.u.grab("Press [enter] to return...")
266
267
def diskpart_erase(self, disk, gpt=False, clover_version = None, local_file = None):
268
# Generate a script that we can pipe to diskpart to erase our disk
269
self.u.head("Erasing With DiskPart")
270
print("")
271
# Then we'll re-gather our disk info on success and move forward
272
# Using MBR to effectively set the individual partition types
273
# Keeps us from having issues mounting the EFI on Windows -
274
# and also lets us explicitly set the partition id for the main
275
# data partition.
276
if not gpt:
277
print("Using MBR...")
278
dp_script = "\n".join([
279
"select disk {}".format(disk.get("index",-1)),
280
"clean",
281
"convert mbr",
282
"create partition primary size=200",
283
"format quick fs=fat32 label='BOOT'",
284
"active",
285
"create partition primary",
286
"select part 2",
287
"set id=AB", # AF = HFS, AB = Recovery
288
"select part 1",
289
"assign"
290
])
291
else:
292
print("Using GPT...")
293
dp_script = "\n".join([
294
"select disk {}".format(disk.get("index",-1)),
295
"clean",
296
"convert gpt",
297
"create partition primary size=200",
298
"format quick fs=fat32 label='BOOT'",
299
"create partition primary id={}".format(self.hfs_id)
300
])
301
temp = tempfile.mkdtemp()
302
script = os.path.join(temp, "diskpart.txt")
303
try:
304
with open(script,"w") as f:
305
f.write(dp_script)
306
except:
307
shutil.rmtree(temp)
308
print("Error creating script!")
309
print("")
310
self.u.grab("Press [enter] to return...")
311
return
312
# Let's try to run it!
313
out = self.r.run({"args":[self.diskpart,"/s",script],"stream":True})
314
# Ditch our script regardless of whether diskpart worked or not
315
shutil.rmtree(temp)
316
if out[2] != 0:
317
# Error city!
318
print("")
319
print("DiskPart exited with non-zero status ({}). Aborting.".format(out[2]))
320
print("")
321
self.u.grab("Press [enter] to return...")
322
return
323
# We should now have a fresh drive to work with
324
# Let's write an image or something
325
self.u.head("Updating Disk Information")
326
print("")
327
print("Re-populating list...")
328
self.d.update()
329
print("Relocating disk {}".format(disk["index"]))
330
disk = self.d.disks[str(disk["index"])]
331
self.select_package(disk, clover_version, local_file=local_file)
332
333
def select_package(self, disk, clover_version = None, local_file = None):
334
self.u.head("Select Recovery Package")
335
print("")
336
print("{}. {} - {} ({})".format(
337
disk.get("index",-1),
338
disk.get("model","Unknown"),
339
self.dl.get_size(disk.get("size",-1),strip_zeroes=True),
340
["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][disk.get("type",0)]
341
))
342
print("")
343
print("M. Main Menu")
344
print("Q. Quit")
345
print("")
346
print("(To copy a file's path, shift + right-click in Explorer and select 'Copy as path')\n")
347
menu = self.u.grab("Please paste the recovery update pkg/dmg path to extract: ")
348
if menu.lower() == "q":
349
self.u.custom_quit()
350
if menu.lower() == "m":
351
return
352
path = self.u.check_path(menu)
353
if not path:
354
self.select_package(disk, clover_version, local_file=local_file)
355
return
356
# Got the package - let's make sure it's named right - just in case
357
if os.path.basename(path).lower().endswith(".hfs"):
358
# We have an hfs image already - bypass extraction
359
self.dd_image(disk, path, clover_version, local_file=local_file)
360
return
361
# If it's a directory, find the first recovery hit
362
if os.path.isdir(path):
363
for f in os.listdir(path):
364
if f.lower().endswith(self.recovery_suffixes):
365
path = os.path.join(path, f)
366
break
367
# Make sure it's named right for recovery stuffs
368
if not path.lower().endswith(self.recovery_suffixes):
369
self.u.head("Invalid Package")
370
print("")
371
print("{} is not in the available recovery package names:\n{}".format(os.path.basename(path), ", ".join(self.recovery_suffixes)))
372
print("")
373
print("Ensure you're passing a proper recovery package.")
374
print("")
375
self.u.grab("Press [enter] to return to package selection...")
376
self.select_package(disk, clover_version, local_file=local_file)
377
return
378
self.u.head("Extracting Package")
379
print("")
380
temp = tempfile.mkdtemp()
381
cwd = os.getcwd()
382
os.chdir(temp)
383
print("Located {}...".format(os.path.basename(path)))
384
if not path.lower().endswith(".dmg"):
385
# Extract in sections and remove any files we run into
386
print("Extracting Recovery dmg...")
387
out = self.r.run({"args":[self.z_path, "e", "-txar", path, "*.dmg"]})
388
if out[2] != 0:
389
shutil.rmtree(temp,ignore_errors=True)
390
print("An error occurred extracting: {}".format(out[2]))
391
print("")
392
self.u.grab("Press [enter] to return...")
393
return
394
print("Extracting BaseSystem.dmg...")
395
# No files to delete here - let's extract the next part
396
out = self.r.run({"args":[self.z_path, "e", "*.dmg", "*/Base*.dmg"]})
397
if out[2] != 0:
398
shutil.rmtree(temp,ignore_errors=True)
399
print("An error occurred extracting: {}".format(out[2]))
400
print("")
401
self.u.grab("Press [enter] to return...")
402
return
403
# If we got here - we should delete everything in the temp folder except
404
# for a .dmg that starts with Base
405
del_list = [x for x in os.listdir(temp) if not (x.lower().startswith("base") and x.lower().endswith(".dmg"))]
406
for d in del_list:
407
os.remove(os.path.join(temp, d))
408
# Onto the last command
409
print("Extracting hfs...")
410
out = self.r.run({"args":[self.z_path, "e", "-tdmg", path if path.lower().endswith(".dmg") else "Base*.dmg", "*.hfs"]})
411
if out[2] != 0:
412
shutil.rmtree(temp,ignore_errors=True)
413
print("An error occurred extracting: {}".format(out[2]))
414
print("")
415
self.u.grab("Press [enter] to return...")
416
return
417
# If we got here - we should delete everything in the temp folder except
418
# for a .dmg that starts with Base
419
del_list = [x for x in os.listdir(temp) if not x.lower().endswith(".hfs")]
420
for d in del_list:
421
os.remove(os.path.join(temp, d))
422
print("Extracted successfully!")
423
hfs = next((x for x in os.listdir(temp) if x.lower().endswith(".hfs")),None)
424
# Now to dd our image - if it exists
425
if not hfs:
426
print("Missing the .hfs file! Aborting.")
427
print("")
428
self.u.grab("Press [enter] to return...")
429
else:
430
self.dd_image(disk, os.path.join(temp, hfs), clover_version, local_file=local_file)
431
shutil.rmtree(temp,ignore_errors=True)
432
433
def dd_image(self, disk, image, clover_version = None, local_file = None):
434
# Let's dd the shit out of our disk
435
self.u.head("Copying Image To Drive")
436
print("")
437
print("Image: {}".format(image))
438
print("")
439
print("Disk {}. {} - {} ({})".format(
440
disk.get("index",-1),
441
disk.get("model","Unknown"),
442
self.dl.get_size(disk.get("size",-1),strip_zeroes=True),
443
["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][disk.get("type",0)]
444
))
445
print("")
446
args = [
447
os.path.join(self.s_path, self.dd_name),
448
"if={}".format(image),
449
"of=\\\\?\\Device\\Harddisk{}\\Partition2".format(disk.get("index",-1)),
450
"bs=8M",
451
"--progress"
452
]
453
print(" ".join(args))
454
print("")
455
print("This may take some time!")
456
print("")
457
out = self.r.run({"args":args})
458
if len(out[1].split("Error")) > 1:
459
# We had some error text - dd, even when failing likes to give us a 0
460
# status code. It also sends a ton of text through stderr - so we comb
461
# that for "Error" then split by that to skip the extra fluff and show only
462
# the error.
463
print("An error occurred:\n\n{}".format("Error"+out[1].split("Error")[1]))
464
print("")
465
self.u.grab("Press [enter] to return to the main menu...")
466
return
467
# Install Clover/OC to the target drive
468
if clover_version == "OpenCore": self.install_oc(disk, local_file=local_file)
469
else: self.install_clover(disk, clover_version, local_file=local_file)
470
471
def install_oc(self, disk, local_file = None):
472
self.u.head("Installing OpenCore")
473
print("")
474
print("Gathering info...")
475
if not local_file:
476
o = self.get_oc_dl_info()
477
if o is None:
478
print(" - Error communicating with github!")
479
print("")
480
self.u.grab("Press [enter] to return...")
481
return
482
print(" - Got {}".format(o.get("name","Unknown Version")))
483
print("Downloading...")
484
temp = tempfile.mkdtemp()
485
os.chdir(temp)
486
self.dl.stream_to_file(o["url"], os.path.join(temp, o["name"]))
487
else:
488
print("Using local file: {}".format(local_file))
489
temp = tempfile.mkdtemp()
490
os.chdir(temp)
491
o = {"name":os.path.basename(local_file)}
492
# Copy to the temp folder
493
shutil.copy(local_file,os.path.join(temp,o["name"]))
494
print("") # Empty space to clear the download progress
495
if not os.path.exists(os.path.join(temp, o["name"])):
496
shutil.rmtree(temp,ignore_errors=True)
497
print(" - Download failed. Aborting...")
498
print("")
499
self.u.grab("Press [enter] to return...")
500
return
501
oc_zip = o["name"]
502
# Got a valid file in our temp dir
503
print("Extracting {}...".format(oc_zip))
504
out = self.r.run({"args":[self.z_path, "x", os.path.join(temp,oc_zip)]})
505
if out[2] != 0:
506
shutil.rmtree(temp,ignore_errors=True)
507
print(" - An error occurred extracting: {}".format(out[2]))
508
print("")
509
self.u.grab("Press [enter] to return...")
510
return
511
# We need to also gather our boot, boot0af, and boot1f32 files
512
print("Gathering DUET boot files...")
513
uefi_only = False
514
duet_loc = os.path.join(temp,"Utilities","LegacyBoot")
515
for x in (self.oc_boot,self.oc_boot_alt,self.oc_boot0,self.oc_boot1):
516
# Check the local dir first
517
if os.path.exists(os.path.join(duet_loc,x)):
518
print(" - {}".format(x))
519
# Copy it over
520
target_name = self.oc_boot if x == self.oc_boot_alt else x
521
shutil.copy(os.path.join(duet_loc,x), os.path.join(temp,target_name))
522
missing_list = [x for x in (self.oc_boot,self.oc_boot0,self.oc_boot1) if not os.path.exists(os.path.join(temp,x))]
523
if missing_list:
524
print(" - Missing: {}".format(", ".join(missing_list)))
525
print("Attempting to download...")
526
for x in missing_list:
527
print(" - {}".format(x))
528
self.dl.stream_to_file(self.oc_boot_url + x, os.path.join(temp,x),False)
529
if not all((os.path.exists(os.path.join(temp,x)) for x in missing_list)):
530
print("Could not located all required DUET files - USB will be UEFI ONLY")
531
uefi_only = True
532
# At this point, we should have a boot0xx file and an EFI folder in the temp dir
533
# We need to udpate the disk list though - to reflect the current file system on part 1
534
# of our current disk
535
self.d.update() # assumes our disk number stays the same
536
# Some users are having issues with the "partitions" key not populating - possibly a 3rd party disk management soft?
537
# Possibly a bad USB?
538
# We'll see if the key exists - if not, we'll throw an error.
539
if self.d.disks[str(disk["index"])].get("partitions",None) is None:
540
# No partitions found.
541
shutil.rmtree(temp,ignore_errors=True)
542
print("No partitions located on disk!")
543
print("")
544
self.u.grab("Press [enter] to return...")
545
return
546
part = self.d.disks[str(disk["index"])]["partitions"].get("0",{}).get("letter",None) # get the first partition's letter
547
if part is None:
548
shutil.rmtree(temp,ignore_errors=True)
549
print("Lost original disk - or formatting failed!")
550
print("")
551
self.u.grab("Press [enter] to return...")
552
return
553
# Here we have our disk and partitions and such - the BOOT partition
554
# will be the first partition
555
# Let's copy over the EFI folder and then dd the boot0xx file
556
print("Copying EFI folder to {}/EFI...".format(part))
557
source_efi = None
558
if os.path.exists(os.path.join(temp,"EFI")):
559
source_efi = os.path.join(temp,"EFI")
560
elif os.path.exists(os.path.join(temp,"X64","EFI")):
561
source_efi = os.path.join(temp,"X64","EFI")
562
if not source_efi:
563
print(" - Source EFI not found!")
564
print("")
565
self.u.grab("Press [enter] to return...")
566
return
567
if os.path.exists("{}/EFI".format(part)):
568
print(" - EFI exists - removing...")
569
shutil.rmtree("{}/EFI".format(part),ignore_errors=True)
570
time.sleep(1) # Added because windows is dumb
571
shutil.copytree(source_efi, "{}/EFI".format(part))
572
if not uefi_only:
573
# Copy boot over to the root of the EFI volume
574
print("Copying {} to {}/boot...".format(self.oc_boot,part))
575
shutil.copy(os.path.join(temp,self.oc_boot),"{}/boot".format(part))
576
# Use bootice to update the MBR and PBR - always on the first
577
# partition (which is 0 in bootice)
578
print("Updating the MBR with {}...".format(self.oc_boot0))
579
args = [
580
os.path.join(self.s_path,self.bi_name),
581
"/device={}".format(disk.get("index",-1)),
582
"/mbr",
583
"/restore",
584
"/file={}".format(os.path.join(temp,self.oc_boot0)),
585
"/keep_dpt",
586
"/quiet"
587
]
588
out = self.r.run({"args":args})
589
if out[2] != 0:
590
shutil.rmtree(temp,ignore_errors=True)
591
print(" - An error occurred updating the MBR: {}".format(out[2]))
592
print("")
593
self.u.grab("Press [enter] to return...")
594
return
595
print("Updating the PBR with {}...".format(self.oc_boot1))
596
args = [
597
os.path.join(self.s_path,self.bi_name),
598
"/device={}:0".format(disk.get("index",-1)),
599
"/pbr",
600
"/restore",
601
"/file={}".format(os.path.join(temp,self.oc_boot1)),
602
"/keep_bpb",
603
"/quiet"
604
]
605
out = self.r.run({"args":args})
606
if out[2] != 0:
607
shutil.rmtree(temp,ignore_errors=True)
608
print(" - An error occurred updating the PBR: {}".format(out[2]))
609
print("")
610
self.u.grab("Press [enter] to return...")
611
return
612
print("Cleaning up...")
613
shutil.rmtree(temp,ignore_errors=True)
614
print("")
615
print("Done.")
616
print("")
617
self.u.grab("Press [enter] to return to the main menu...")
618
619
def install_clover(self, disk, clover_version = None, local_file = None):
620
self.u.head("Installing Clover - {}".format("Latest" if not clover_version else "r"+clover_version))
621
print("")
622
print("Gathering info...")
623
if not local_file:
624
c = self.get_dl_info(clover_version)
625
if c is None:
626
if clover_version is None: print(" - Error communicating with github!")
627
else: print(" - Error gathering info for Clover r{}".format(clover_version))
628
print("")
629
self.u.grab("Press [enter] to return...")
630
return
631
print(" - Got {}".format(c.get("name","Unknown Version")))
632
print("Downloading...")
633
temp = tempfile.mkdtemp()
634
os.chdir(temp)
635
self.dl.stream_to_file(c["url"], os.path.join(temp, c["name"]))
636
else:
637
print("Using local file: {}".format(local_file))
638
temp = tempfile.mkdtemp()
639
os.chdir(temp)
640
c = {"name":os.path.basename(local_file)}
641
# Copy to the temp folder
642
shutil.copy(local_file,os.path.join(temp,c["name"]))
643
print("") # Empty space to clear the download progress
644
if not os.path.exists(os.path.join(temp, c["name"])):
645
shutil.rmtree(temp,ignore_errors=True)
646
print(" - Download failed. Aborting...")
647
print("")
648
self.u.grab("Press [enter] to return...")
649
return
650
clover_archive = c["name"]
651
# Got a valid file in our temp dir
652
print("Extracting {}...".format(clover_archive))
653
out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_archive)]})
654
if out[2] != 0:
655
shutil.rmtree(temp,ignore_errors=True)
656
print(" - An error occurred extracting: {}".format(out[2]))
657
print("")
658
self.u.grab("Press [enter] to return...")
659
return
660
# Should result in a .tar file
661
clover_tar = next((x for x in os.listdir(temp) if x.lower().endswith(".tar")),None)
662
if clover_tar:
663
# Got a .tar archive - get the .iso
664
print("Extracting {}...".format(clover_tar))
665
out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_tar)]})
666
if out[2] != 0:
667
shutil.rmtree(temp,ignore_errors=True)
668
print(" - An error occurred extracting: {}".format(out[2]))
669
print("")
670
self.u.grab("Press [enter] to return...")
671
return
672
# Should result in a .iso file
673
clover_iso = next((x for x in os.listdir(temp) if x.lower().endswith(".iso")),None)
674
if not clover_iso:
675
shutil.rmtree(temp,ignore_errors=True)
676
print(" - No .iso found - aborting...")
677
print("")
678
self.u.grab("Press [enter] to return...")
679
return
680
# Got the .iso - let's extract the needed parts
681
print("Extracting EFI from {}...".format(clover_iso))
682
out = self.r.run({"args":[self.z_path, "x", os.path.join(temp,clover_iso), "EFI*"]})
683
if out[2] != 0:
684
shutil.rmtree(temp,ignore_errors=True)
685
print(" - An error occurred extracting: {}".format(out[2]))
686
print("")
687
self.u.grab("Press [enter] to return...")
688
return
689
print("Extracting {} from {}...".format(self.boot0,clover_iso))
690
out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_iso), self.boot0, "-r"]})
691
if out[2] != 0:
692
shutil.rmtree(temp,ignore_errors=True)
693
print(" - An error occurred extracting: {}".format(out[2]))
694
print("")
695
self.u.grab("Press [enter] to return...")
696
return
697
print("Extracting {} from {}...".format(self.boot1,clover_iso))
698
out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_iso), self.boot1, "-r"]})
699
if out[2] != 0:
700
shutil.rmtree(temp,ignore_errors=True)
701
print(" - An error occurred extracting: {}".format(out[2]))
702
print("")
703
self.u.grab("Press [enter] to return...")
704
return
705
print("Extracting {} from {}...".format(self.boot,clover_iso))
706
out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_iso), self.boot, "-r"]})
707
if out[2] != 0:
708
shutil.rmtree(temp,ignore_errors=True)
709
print(" - An error occurred extracting: {}".format(out[2]))
710
print("")
711
self.u.grab("Press [enter] to return...")
712
return
713
# At this point, we should have a boot0xx file and an EFI folder in the temp dir
714
# We need to udpate the disk list though - to reflect the current file system on part 1
715
# of our current disk
716
self.d.update() # assumes our disk number stays the same
717
# Some users are having issues with the "partitions" key not populating - possibly a 3rd party disk management soft?
718
# Possibly a bad USB?
719
# We'll see if the key exists - if not, we'll throw an error.
720
if self.d.disks[str(disk["index"])].get("partitions",None) is None:
721
# No partitions found.
722
shutil.rmtree(temp,ignore_errors=True)
723
print("No partitions located on disk!")
724
print("")
725
self.u.grab("Press [enter] to return...")
726
return
727
part = self.d.disks[str(disk["index"])]["partitions"].get("0",{}).get("letter",None) # get the first partition's letter
728
if part is None:
729
shutil.rmtree(temp,ignore_errors=True)
730
print("Lost original disk - or formatting failed!")
731
print("")
732
self.u.grab("Press [enter] to return...")
733
return
734
# Here we have our disk and partitions and such - the CLOVER partition
735
# will be the first partition
736
# Let's copy over the EFI folder and then dd the boot0xx file
737
print("Copying EFI folder to {}/EFI...".format(part))
738
if os.path.exists("{}/EFI".format(part)):
739
print(" - EFI exists - removing...")
740
shutil.rmtree("{}/EFI".format(part),ignore_errors=True)
741
time.sleep(1) # Added because windows is dumb
742
shutil.copytree(os.path.join(temp,"EFI"), "{}/EFI".format(part))
743
# Copy boot6 over to the root of the EFI volume - and rename it to boot
744
print("Copying {} to {}/boot...".format(self.boot,part))
745
shutil.copy(os.path.join(temp,self.boot),"{}/boot".format(part))
746
# Use bootice to update the MBR and PBR - always on the first
747
# partition (which is 0 in bootice)
748
print("Updating the MBR with {}...".format(self.boot0))
749
args = [
750
os.path.join(self.s_path,self.bi_name),
751
"/device={}".format(disk.get("index",-1)),
752
"/mbr",
753
"/restore",
754
"/file={}".format(os.path.join(temp,self.boot0)),
755
"/keep_dpt",
756
"/quiet"
757
]
758
out = self.r.run({"args":args})
759
if out[2] != 0:
760
shutil.rmtree(temp,ignore_errors=True)
761
print(" - An error occurred updating the MBR: {}".format(out[2]))
762
print("")
763
self.u.grab("Press [enter] to return...")
764
return
765
print("Updating the PBR with {}...".format(self.boot1))
766
args = [
767
os.path.join(self.s_path,self.bi_name),
768
"/device={}:0".format(disk.get("index",-1)),
769
"/pbr",
770
"/restore",
771
"/file={}".format(os.path.join(temp,self.boot1)),
772
"/keep_bpb",
773
"/quiet"
774
]
775
out = self.r.run({"args":args})
776
if out[2] != 0:
777
shutil.rmtree(temp,ignore_errors=True)
778
print(" - An error occurred updating the PBR: {}".format(out[2]))
779
print("")
780
self.u.grab("Press [enter] to return...")
781
return
782
print("Cleaning up...")
783
shutil.rmtree(temp,ignore_errors=True)
784
print("")
785
print("Done.")
786
print("")
787
self.u.grab("Press [enter] to return to the main menu...")
788
789
def main(self):
790
# Start out with our cd in the right spot.
791
os.chdir(os.path.dirname(os.path.realpath(__file__)))
792
# Let's make sure we have the required files needed
793
self.u.head("Checking Required Tools")
794
print("")
795
if not self.check_dd():
796
print("Couldn't find or install {} - aborting!\n".format(self.dd_name))
797
self.u.grab("Press [enter] to exit...")
798
exit(1)
799
if not self.check_7z():
800
print("Couldn't find or install {} - aborting!\n".format(self.z_name))
801
self.u.grab("Press [enter] to exit...")
802
exit(1)
803
if not self.check_bi():
804
print("Couldn't find or install {} - aborting!\n".format(self.bi_name))
805
self.u.grab("Press [enter] to exit...")
806
exit(1)
807
# Let's just setup a real simple interface and try to write some data
808
self.u.head("Gathering Disk Info")
809
print("")
810
print("Populating list...")
811
self.d.update()
812
print("")
813
print("Done!")
814
# Let's serve up a list of *only* removable media
815
self.u.head("Potential Removable Media")
816
print("")
817
rem_disks = self.get_disks_of_type(self.d.disks) if not self.show_all_disks else self.d.disks
818
819
# Types: 0 = Unknown, 1 = No Root Dir, 2 = Removable, 3 = Local, 4 = Network, 5 = Disc, 6 = RAM disk
820
821
if self.show_all_disks:
822
print("!WARNING! This list includes ALL disk types.")
823
print("!WARNING! Be ABSOLUTELY sure before selecting")
824
print("!WARNING! a disk!")
825
else:
826
print("!WARNING! This list includes both Removable AND")
827
print("!WARNING! Unknown disk types. Be ABSOLUTELY sure")
828
print("!WARNING! before selecting a disk!")
829
print("")
830
for disk in sorted(rem_disks,key=lambda x:int(x)):
831
print("{}. {} - {} ({})".format(
832
disk,
833
rem_disks[disk].get("model","Unknown"),
834
self.dl.get_size(rem_disks[disk].get("size",-1),strip_zeroes=True),
835
["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][rem_disks[disk].get("type",0)]
836
))
837
if not len(rem_disks[disk].get("partitions",{})):
838
print(" No Mounted Partitions")
839
else:
840
parts = rem_disks[disk]["partitions"]
841
for p in sorted(parts,key=lambda x:int(x)):
842
print(" {}. {} ({}) {} - {}".format(
843
p,
844
parts[p].get("letter","No Letter"),
845
"No Name" if not parts[p].get("name",None) else parts[p].get("name","No Name"),
846
parts[p].get("file system","Unknown FS"),
847
self.dl.get_size(parts[p].get("size",-1),strip_zeroes=True)
848
))
849
print("")
850
print("Q. Quit")
851
print("")
852
print("Usage: [drive number][options] r[Clover revision (optional), requires C]\n (eg. 1B C r5092)")
853
print(" Options are as follows with precedence B > F > E > U > G:")
854
print(" B = Only install the boot manager to the drive's first partition.")
855
print(" F = Skip formatting the disk - will install the boot manager to the first")
856
print(" partition, and dd the recovery image to the second.")
857
print(" E = Sets the type of the drive's first partition to EFI.")
858
print(" U = Similar to E, but sets the type to Basic Data (useful for editing).")
859
print(" G = Format as GPT (default is MBR).")
860
print(" C = Use Clover instead of OpenCore.")
861
print(" L = Provide a local archive for the boot manager - must still use C if Clover.")
862
print(" D = Used without a drive number, toggles showing all disks (currently {}).".format("ENABLED" if self.show_all_disks else "DISABLED"))
863
print("")
864
menu = self.u.grab("Please select a disk or press [enter] with no options to refresh: ")
865
if not len(menu):
866
self.main()
867
return
868
if menu.lower() == "q":
869
self.u.custom_quit()
870
if menu.lower() == "d":
871
self.show_all_disks ^= True
872
self.main()
873
return
874
only_boot = set_efi = unset_efi = use_gpt = user_provided = no_format = False
875
local_file = None
876
use_oc = True
877
if "b" in menu.lower():
878
only_boot = True
879
menu = menu.lower().replace("b","")
880
if "c" in menu.lower():
881
use_oc = False
882
menu = menu.lower().replace("c","")
883
if "o" in menu.lower(): # Remove legacy "o" value
884
menu = menu.lower().replace("o","")
885
if "e" in menu.lower():
886
set_efi = True
887
menu = menu.lower().replace("e","")
888
if "u" in menu.lower():
889
unset_efi = True
890
menu = menu.lower().replace("u","")
891
if "g" in menu.lower():
892
use_gpt = True
893
menu = menu.lower().replace("g","")
894
if "l" in menu.lower():
895
user_provided = True
896
menu = menu.lower().replace("l","")
897
if "f" in menu.lower():
898
no_format = True
899
menu = menu.lower().replace("f","")
900
901
# Extract Clover version from args if found
902
clover_list = [x for x in menu.split() if x.lower().startswith("r") and all(y in "0123456789" for y in x[1:])]
903
menu = " ".join([x for x in menu.split() if not x in clover_list])
904
clover_version = None if not len(clover_list) else clover_list[0][1:] # Skip the "r" prefix
905
906
# Prepare for OC if need be
907
if use_oc: clover_version = "OpenCore"
908
909
selected_disk = rem_disks.get(menu.strip(),None)
910
if not selected_disk:
911
self.u.head("Invalid Choice")
912
print("")
913
print("Disk {} is not an option.".format(menu))
914
print("")
915
self.u.grab("Returning in 5 seconds...", timeout=5)
916
self.main()
917
return
918
# Got a disk!
919
if user_provided:
920
# Prompt the user for the target archive
921
while True:
922
self.u.head("Local Archive")
923
print("")
924
if use_oc:
925
print("NOTE: OpenCore archives are expected to be .zip!")
926
else:
927
print("NOTE: Clover archives are expected to be an ISO packed in either .tar.lzma or .7z!")
928
print("")
929
print("M. Return to the menu")
930
print("Q. Quit")
931
print("")
932
print("(To copy a file's path, shift + right-click in Explorer and select 'Copy as path')\n")
933
l = self.u.grab("Please {} archive path here: ".format("OpenCore" if use_oc else "Clover"))
934
if not len(l):
935
continue
936
if l.lower() == "m":
937
break
938
elif l.lower() == "q":
939
self.u.custom_quit()
940
l_check = self.u.check_path(l)
941
if not l_check or not l_check.lower().endswith(".zip" if use_oc else (".tar.lzma",".7z")):
942
continue
943
# Got a valid path that ends with the proper extension
944
local_file = l_check
945
break
946
# Check if we got something
947
if not local_file:
948
self.main()
949
return
950
if only_boot:
951
if use_oc: self.install_oc(selected_disk, local_file=local_file)
952
else: self.install_clover(selected_disk, clover_version, local_file=local_file)
953
elif no_format:
954
# Make sure we warn the user that the second partition **NEEDS** to be a RAW
955
# partition for dd to properly work
956
while True:
957
self.u.head("WARNING")
958
print("")
959
print("{}. {} - {} ({})".format(
960
selected_disk.get("index",-1),
961
selected_disk.get("model","Unknown"),
962
self.dl.get_size(selected_disk.get("size",-1),strip_zeroes=True),
963
["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][selected_disk.get("type",0)]
964
))
965
print("")
966
print("In order to continue without formatting, the selected disk's first")
967
print("partition MUST be FAT32, and the second MUST be RAW. If that is not")
968
print("the case, the operation WILL fail.")
969
print("")
970
yn = self.u.grab("Continue? (y/n): ")
971
if yn.lower() == "n":
972
self.main()
973
return
974
if yn.lower() == "y":
975
break
976
self.select_package(selected_disk, clover_version, local_file=local_file)
977
elif set_efi:
978
self.diskpart_flag(selected_disk, True)
979
elif unset_efi:
980
self.diskpart_flag(selected_disk, False)
981
else:
982
# Check erase
983
while True:
984
self.u.head("Erase {}".format(selected_disk.get("model","Unknown")))
985
print("")
986
print("{}. {} - {} ({})".format(
987
selected_disk.get("index",-1),
988
selected_disk.get("model","Unknown"),
989
self.dl.get_size(selected_disk.get("size",-1),strip_zeroes=True),
990
["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][selected_disk.get("type",0)]
991
))
992
print("")
993
print("If you continue - THIS DISK WILL BE ERASED")
994
print("ALL DATA WILL BE LOST AND ALL PARTITIONS WILL")
995
print("BE REMOVED!!!!!!!")
996
print("")
997
yn = self.u.grab("Continue? (y/n): ")
998
if yn.lower() == "n":
999
self.main()
1000
return
1001
if yn.lower() == "y":
1002
break
1003
# Got the OK to erase! Let's format a diskpart script!
1004
self.diskpart_erase(selected_disk, use_gpt, clover_version, local_file=local_file)
1005
self.main()
1006
1007
if __name__ == '__main__':
1008
w = WinUSB()
1009
w.main()
1010
1011